_inspector.lua 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  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 = bufnr == 0 and vim.api.nvim_get_current_buf() or 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. ---@since 11
  136. ---@param bufnr? integer defaults to the current buffer
  137. ---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
  138. ---@param col? integer col to inspect, 0-based. Defaults to the col of the current cursor
  139. ---@param filter? vim._inspector.Filter
  140. function vim.show_pos(bufnr, row, col, filter)
  141. local items = vim.inspect_pos(bufnr, row, col, filter)
  142. local lines = { {} }
  143. local function append(str, hl)
  144. table.insert(lines[#lines], { str, hl })
  145. end
  146. local function nl()
  147. table.insert(lines, {})
  148. end
  149. local function item(data, comment)
  150. append(' - ')
  151. append(data.hl_group, data.hl_group)
  152. append(' ')
  153. if data.hl_group ~= data.hl_group_link then
  154. append('links to ', 'MoreMsg')
  155. append(data.hl_group_link, data.hl_group_link)
  156. append(' ')
  157. end
  158. if comment then
  159. append(comment, 'Comment')
  160. end
  161. nl()
  162. end
  163. -- treesitter
  164. if #items.treesitter > 0 then
  165. append('Treesitter', 'Title')
  166. nl()
  167. for _, capture in ipairs(items.treesitter) do
  168. item(capture, capture.lang)
  169. end
  170. nl()
  171. end
  172. -- semantic tokens
  173. if #items.semantic_tokens > 0 then
  174. append('Semantic Tokens', 'Title')
  175. nl()
  176. local sorted_marks = vim.fn.sort(items.semantic_tokens, function(left, right)
  177. local left_first = left.opts.priority < right.opts.priority
  178. or left.opts.priority == right.opts.priority and left.opts.hl_group < right.opts.hl_group
  179. return left_first and -1 or 1
  180. end)
  181. for _, extmark in ipairs(sorted_marks) do
  182. item(extmark.opts, 'priority: ' .. extmark.opts.priority)
  183. end
  184. nl()
  185. end
  186. -- syntax
  187. if #items.syntax > 0 then
  188. append('Syntax', 'Title')
  189. nl()
  190. for _, syn in ipairs(items.syntax) do
  191. item(syn)
  192. end
  193. nl()
  194. end
  195. -- extmarks
  196. if #items.extmarks > 0 then
  197. append('Extmarks', 'Title')
  198. nl()
  199. for _, extmark in ipairs(items.extmarks) do
  200. if extmark.opts.hl_group then
  201. item(extmark.opts, extmark.ns)
  202. else
  203. append(' - ')
  204. append(extmark.ns, 'Comment')
  205. nl()
  206. end
  207. end
  208. nl()
  209. end
  210. if #lines[#lines] == 0 then
  211. table.remove(lines)
  212. end
  213. local chunks = {}
  214. for _, line in ipairs(lines) do
  215. vim.list_extend(chunks, line)
  216. table.insert(chunks, { '\n' })
  217. end
  218. if #chunks == 0 then
  219. chunks = {
  220. {
  221. 'No items found at position '
  222. .. items.row
  223. .. ','
  224. .. items.col
  225. .. ' in buffer '
  226. .. items.buffer,
  227. },
  228. }
  229. end
  230. vim.api.nvim_echo(chunks, false, {})
  231. end