-
-
Notifications
You must be signed in to change notification settings - Fork 833
/
query_predicates.lua
238 lines (202 loc) · 6.11 KB
/
query_predicates.lua
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
local query = require "vim.treesitter.query"
local html_script_type_languages = {
["importmap"] = "json",
["module"] = "javascript",
["application/ecmascript"] = "javascript",
["text/ecmascript"] = "javascript",
}
local non_filetype_match_injection_language_aliases = {
ex = "elixir",
pl = "perl",
sh = "bash",
uxn = "uxntal",
ts = "typescript",
}
local function get_parser_from_markdown_info_string(injection_alias)
local match = vim.filetype.match { filename = "a." .. injection_alias }
return match or non_filetype_match_injection_language_aliases[injection_alias] or injection_alias
end
local function error(str)
vim.api.nvim_err_writeln(str)
end
local function valid_args(name, pred, count, strict_count)
local arg_count = #pred - 1
if strict_count then
if arg_count ~= count then
error(string.format("%s must have exactly %d arguments", name, count))
return false
end
elseif arg_count < count then
error(string.format("%s must have at least %d arguments", name, count))
return false
end
return true
end
---@param match (TSNode|nil)[]
---@param _pattern string
---@param _bufnr integer
---@param pred string[]
---@return boolean|nil
query.add_predicate("nth?", function(match, _pattern, _bufnr, pred)
if not valid_args("nth?", pred, 2, true) then
return
end
local node = match[pred[2]] ---@type TSNode
local n = tonumber(pred[3])
if node and node:parent() and node:parent():named_child_count() > n then
return node:parent():named_child(n) == node
end
return false
end, true)
---@param match (TSNode|nil)[]
---@param _pattern string
---@param _bufnr integer
---@param pred string[]
---@return boolean|nil
local function has_ancestor(match, _pattern, _bufnr, pred)
if not valid_args(pred[1], pred, 2) then
return
end
local node = match[pred[2]]
local ancestor_types = { unpack(pred, 3) }
if not node then
return true
end
local just_direct_parent = pred[1]:find("has-parent", 1, true)
node = node:parent()
while node do
if vim.tbl_contains(ancestor_types, node:type()) then
return true
end
if just_direct_parent then
node = nil
else
node = node:parent()
end
end
return false
end
query.add_predicate("has-ancestor?", has_ancestor, true)
query.add_predicate("has-parent?", has_ancestor, true)
---@param match (TSNode|nil)[]
---@param _pattern string
---@param bufnr integer
---@param pred string[]
---@return boolean|nil
query.add_predicate("is?", function(match, _pattern, bufnr, pred)
if not valid_args("is?", pred, 2) then
return
end
-- Avoid circular dependencies
local locals = require "nvim-treesitter.locals"
local node = match[pred[2]]
local types = { unpack(pred, 3) }
if not node then
return true
end
local _, _, kind = locals.find_definition(node, bufnr)
return vim.tbl_contains(types, kind)
end, true)
---@param match (TSNode|nil)[]
---@param _pattern string
---@param _bufnr integer
---@param pred string[]
---@return boolean|nil
query.add_predicate("has-type?", function(match, _pattern, _bufnr, pred)
if not valid_args(pred[1], pred, 2) then
return
end
local node = match[pred[2]]
local types = { unpack(pred, 3) }
if not node then
return true
end
return vim.tbl_contains(types, node:type())
end, true)
---@param match (TSNode|nil)[]
---@param _ string
---@param bufnr integer
---@param pred string[]
---@return boolean|nil
query.add_directive("set-lang-from-mimetype!", function(match, _, bufnr, pred, metadata)
local capture_id = pred[2]
local node = match[capture_id]
if not node then
return
end
local type_attr_value = vim.treesitter.get_node_text(node, bufnr)
local configured = html_script_type_languages[type_attr_value]
if configured then
metadata["injection.language"] = configured
else
local parts = vim.split(type_attr_value, "/", {})
metadata["injection.language"] = parts[#parts]
end
end, true)
---@param match (TSNode|nil)[]
---@param _ string
---@param bufnr integer
---@param pred string[]
---@return boolean|nil
query.add_directive("set-lang-from-info-string!", function(match, _, bufnr, pred, metadata)
local capture_id = pred[2]
local node = match[capture_id]
if not node then
return
end
local injection_alias = vim.treesitter.get_node_text(node, bufnr)
metadata["injection.language"] = get_parser_from_markdown_info_string(injection_alias)
end, true)
-- Just avoid some annoying warnings for this directive
query.add_directive("make-range!", function() end, true)
--- transform node text to lower case (e.g., to make @injection.language case insensitive)
---
---@param match (TSNode|nil)[]
---@param _ string
---@param bufnr integer
---@param pred string[]
---@return boolean|nil
query.add_directive("downcase!", function(match, _, bufnr, pred, metadata)
local id = pred[2]
local node = match[id]
if not node then
return
end
local text = vim.treesitter.get_node_text(node, bufnr, { metadata = metadata[id] }) or ""
if not metadata[id] then
metadata[id] = {}
end
metadata[id].text = string.lower(text)
end, true)
-- Trim blank lines from end of the region
-- Arguments are the captures to trim.
---@param match (TSNode|nil)[]
---@param _ string
---@param bufnr integer
---@param pred string[]
---@param metadata table
query.add_directive("trim!", function(match, _, bufnr, pred, metadata)
for _, id in ipairs { select(2, unpack(pred)) } do
local node = match[id]
local start_row, start_col, end_row, end_col = node:range()
-- Don't trim if region ends in middle of a line
if end_col ~= 0 then
return
end
while true do
-- As we only care when end_col == 0, always inspect one line above end_row.
local end_line = vim.api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)[1]
if end_line ~= "" then
break
end
end_row = end_row - 1
end
-- If this produces an invalid range, we just skip it.
if start_row < end_row or (start_row == end_row and start_col <= end_col) then
if not metadata[id] then
metadata[id] = {}
end
metadata[id].range = { start_row, start_col, end_row, end_col }
end
end
end, true)