_inspector.lua 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. --- @class vim._inspector.Filter
  2. --- @inlinedoc
  3. ---
  4. --- Include syntax based highlight groups.
  5. --- (default: `true`)
  6. --- @field syntax boolean
  7. ---
  8. --- Include treesitter based highlight groups.
  9. --- (default: `true`)
  10. --- @field treesitter boolean
  11. ---
  12. --- Include extmarks. When `all`, then extmarks without a `hl_group` will also be included.
  13. --- (default: true)
  14. --- @field extmarks boolean|"all"
  15. ---
  16. --- Include semantic token highlights.
  17. --- (default: true)
  18. --- @field semantic_tokens boolean
  19. local defaults = {
  20. syntax = true,
  21. treesitter = true,
  22. extmarks = true,
  23. semantic_tokens = true,
  24. }
  25. ---Get all the items at a given buffer position.
  26. ---
  27. ---Can also be pretty-printed with `:Inspect!`. [:Inspect!]()
  28. ---
  29. ---@since 11
  30. ---@param bufnr? integer defaults to the current buffer
  31. ---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
  32. ---@param col? integer col to inspect, 0-based. Defaults to the col of the current cursor
  33. ---@param filter? vim._inspector.Filter Table with key-value pairs to filter the items
  34. ---@return {treesitter:table,syntax:table,extmarks:table,semantic_tokens:table,buffer:integer,col:integer,row:integer} (table) a table with the following key-value pairs. Items are in "traversal order":
  35. --- - treesitter: a list of treesitter captures
  36. --- - syntax: a list of syntax groups
  37. --- - semantic_tokens: a list of semantic tokens
  38. --- - extmarks: a list of extmarks
  39. --- - buffer: the buffer used to get the items
  40. --- - row: the row used to get the items
  41. --- - col: the col used to get the items
  42. function vim.inspect_pos(bufnr, row, col, filter)
  43. filter = vim.tbl_deep_extend('force', defaults, filter or {})
  44. bufnr = bufnr or 0
  45. if row == nil or col == nil then
  46. -- get the row/col from the first window displaying the buffer
  47. local win = bufnr == 0 and vim.api.nvim_get_current_win() or vim.fn.bufwinid(bufnr)
  48. if win == -1 then
  49. error('row/col is required for buffers not visible in a window')
  50. end
  51. local cursor = vim.api.nvim_win_get_cursor(win)
  52. row, col = cursor[1] - 1, cursor[2]
  53. end
  54. bufnr = vim._resolve_bufnr(bufnr)
  55. local results = {
  56. treesitter = {}, --- @type table[]
  57. syntax = {}, --- @type table[]
  58. extmarks = {},
  59. semantic_tokens = {},
  60. buffer = bufnr,
  61. row = row,
  62. col = col,
  63. }
  64. -- resolve hl links
  65. local function resolve_hl(data)
  66. if data.hl_group then
  67. local hlid = vim.api.nvim_get_hl_id_by_name(data.hl_group)
  68. local name = vim.fn.synIDattr(vim.fn.synIDtrans(hlid), 'name')
  69. data.hl_group_link = name
  70. end
  71. return data
  72. end
  73. -- treesitter
  74. if filter.treesitter then
  75. for _, capture in pairs(vim.treesitter.get_captures_at_pos(bufnr, row, col)) do
  76. capture.hl_group = '@' .. capture.capture .. '.' .. capture.lang
  77. results.treesitter[#results.treesitter + 1] = resolve_hl(capture)
  78. end
  79. end
  80. -- syntax
  81. if filter.syntax and vim.api.nvim_buf_is_valid(bufnr) then
  82. vim._with({ buf = bufnr }, function()
  83. for _, i1 in ipairs(vim.fn.synstack(row + 1, col + 1)) do
  84. results.syntax[#results.syntax + 1] =
  85. resolve_hl({ hl_group = vim.fn.synIDattr(i1, 'name') })
  86. end
  87. end)
  88. end
  89. -- namespace id -> name map
  90. local nsmap = {} --- @type table<integer,string>
  91. for name, id in pairs(vim.api.nvim_get_namespaces()) do
  92. nsmap[id] = name
  93. end
  94. --- Convert an extmark tuple into a table
  95. local function to_map(extmark)
  96. extmark = {
  97. id = extmark[1],
  98. row = extmark[2],
  99. col = extmark[3],
  100. opts = resolve_hl(extmark[4]),
  101. }
  102. extmark.ns_id = extmark.opts.ns_id
  103. extmark.ns = nsmap[extmark.ns_id] or ''
  104. extmark.end_row = extmark.opts.end_row or extmark.row -- inclusive
  105. extmark.end_col = extmark.opts.end_col or (extmark.col + 1) -- exclusive
  106. return extmark
  107. end
  108. --- Check if an extmark overlaps this position
  109. local function is_here(extmark)
  110. return (row >= extmark.row and row <= extmark.end_row) -- within the rows of the extmark
  111. and (row > extmark.row or col >= extmark.col) -- either not the first row, or in range of the col
  112. and (row < extmark.end_row or col < extmark.end_col) -- either not in the last row or in range of the col
  113. end
  114. -- all extmarks at this position
  115. local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, -1, 0, -1, { details = true })
  116. extmarks = vim.tbl_map(to_map, extmarks)
  117. extmarks = vim.tbl_filter(is_here, extmarks)
  118. if filter.semantic_tokens then
  119. results.semantic_tokens = vim.tbl_filter(function(extmark)
  120. return extmark.ns:find('vim_lsp_semantic_tokens') == 1
  121. end, extmarks)
  122. end
  123. if filter.extmarks then
  124. results.extmarks = vim.tbl_filter(function(extmark)
  125. return extmark.ns:find('vim_lsp_semantic_tokens') ~= 1
  126. and (filter.extmarks == 'all' or extmark.opts.hl_group)
  127. end, extmarks)
  128. end
  129. return results
  130. end
  131. ---Show all the items at a given buffer position.
  132. ---
  133. ---Can also be shown with `:Inspect`. [:Inspect]()
  134. ---
  135. ---Example: To bind this function to the vim-scriptease
  136. ---inspired `zS` in Normal mode:
  137. ---
  138. ---```lua
  139. ---vim.keymap.set('n', 'zS', vim.show_pos)
  140. ---```
  141. ---
  142. ---@since 11
  143. ---@param bufnr? integer defaults to the current buffer
  144. ---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
  145. ---@param col? integer col to inspect, 0-based. Defaults to the col of the current cursor
  146. ---@param filter? vim._inspector.Filter
  147. function vim.show_pos(bufnr, row, col, filter)
  148. local items = vim.inspect_pos(bufnr, row, col, filter)
  149. local lines = { {} }
  150. local function append(str, hl)
  151. table.insert(lines[#lines], { str, hl })
  152. end
  153. local function nl()
  154. table.insert(lines, {})
  155. end
  156. local function item(data, comment)
  157. append(' - ')
  158. append(data.hl_group, data.hl_group)
  159. append(' ')
  160. if data.hl_group ~= data.hl_group_link then
  161. append('links to ', 'MoreMsg')
  162. append(data.hl_group_link, data.hl_group_link)
  163. append(' ')
  164. end
  165. if comment then
  166. append(comment, 'Comment')
  167. end
  168. nl()
  169. end
  170. -- treesitter
  171. if #items.treesitter > 0 then
  172. append('Treesitter', 'Title')
  173. nl()
  174. for _, capture in ipairs(items.treesitter) do
  175. item(
  176. capture,
  177. string.format(
  178. 'priority: %d language: %s',
  179. capture.metadata.priority or vim.hl.priorities.treesitter,
  180. capture.lang
  181. )
  182. )
  183. end
  184. nl()
  185. end
  186. -- semantic tokens
  187. if #items.semantic_tokens > 0 then
  188. append('Semantic Tokens', 'Title')
  189. nl()
  190. local sorted_marks = vim.fn.sort(items.semantic_tokens, function(left, right)
  191. local left_first = left.opts.priority < right.opts.priority
  192. or left.opts.priority == right.opts.priority and left.opts.hl_group < right.opts.hl_group
  193. return left_first and -1 or 1
  194. end)
  195. for _, extmark in ipairs(sorted_marks) do
  196. item(extmark.opts, 'priority: ' .. extmark.opts.priority)
  197. end
  198. nl()
  199. end
  200. -- syntax
  201. if #items.syntax > 0 then
  202. append('Syntax', 'Title')
  203. nl()
  204. for _, syn in ipairs(items.syntax) do
  205. item(syn)
  206. end
  207. nl()
  208. end
  209. -- extmarks
  210. if #items.extmarks > 0 then
  211. append('Extmarks', 'Title')
  212. nl()
  213. for _, extmark in ipairs(items.extmarks) do
  214. if extmark.opts.hl_group then
  215. item(extmark.opts, extmark.ns)
  216. else
  217. append(' - ')
  218. append(extmark.ns, 'Comment')
  219. nl()
  220. end
  221. end
  222. nl()
  223. end
  224. if #lines[#lines] == 0 then
  225. table.remove(lines)
  226. end
  227. local chunks = {}
  228. for _, line in ipairs(lines) do
  229. vim.list_extend(chunks, line)
  230. table.insert(chunks, { '\n' })
  231. end
  232. if #chunks == 0 then
  233. chunks = {
  234. {
  235. 'No items found at position '
  236. .. items.row
  237. .. ','
  238. .. items.col
  239. .. ' in buffer '
  240. .. items.buffer,
  241. },
  242. }
  243. end
  244. vim.api.nvim_echo(chunks, false, {})
  245. end