_inspector.lua 7.9 KB

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