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