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