fix(treesitter): offset directive associates range with capture (#18276)

Previously the `offset!` directive populated the metadata in such a way
that the new range could be attributed to a specific capture. #14046
made it so the directive simply stored just the new range in the
metadata and information about what capture the range is based from is
lost.

This change reverts that whilst also correcting the docs.
This commit is contained in:
Lewis Russell 2022-05-28 18:22:18 +01:00 committed by GitHub
parent 081eb72a80
commit eab4d03a32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 40 deletions

View File

@ -261,36 +261,37 @@ for a node or match and perform side effects. For example, the |set!|
predicate sets metadata on the match or node : > predicate sets metadata on the match or node : >
((identifier) @foo (#set! "type" "parameter")) ((identifier) @foo (#set! "type" "parameter"))
Here is a list of built-in directives: Built-in directives:
`set!` *ts-directive-set!* `set!` *ts-directive-set!*
Sets key/value metadata for a specific node or match : > Sets key/value metadata for a specific match or capture.
Value is accessible as either `metadata[key]` (match
specific) or `metadata[capture_id][key]` (capture specific).
Parameters: ~
{capture_id} (optional)
{key}
{value}
Examples: >
((identifier) @foo (#set! @foo "kind" "parameter")) ((identifier) @foo (#set! @foo "kind" "parameter"))
((node1) @left (node2) @right (#set! "type" "pair")) ((node1) @left (node2) @right (#set! "type" "pair"))
< <
`offset!` *ts-directive-offset!* `offset!` *ts-directive-offset!*
Takes the range of the captured node and applies the offsets Takes the range of the captured node and applies an offset.
to it's range : > This will generate a new range object for the captured node
as `metadata[capture_id].range`.
Parameters: ~
{capture_id}
{start_row}
{start_col}
{end_row}
{end_col}
Example: >
((identifier) @constant (#offset! @constant 0 1 0 -1)) ((identifier) @constant (#offset! @constant 0 1 0 -1))
< This will generate a range object for the captured node with <
the offsets applied. The arguments are
`({capture_id}, {start_row}, {start_col}, {end_row}, {end_col}, {key?})`
The default key is "offset".
*vim.treesitter.query.add_directive()*
vim.treesitter.query.add_directive({name}, {handler})
This adds a directive with the name {name} to be used in queries.
{handler} should be a function whose signature will be : >
handler(match, pattern, bufnr, predicate, metadata)
Handlers can set match level data by setting directly on the metadata object
`metadata.key = value` Handlers can set node level data by using the capture
id on the metadata table `metadata[capture_id].key = value`
*vim.treesitter.query.list_directives()*
vim.treesitter.query.list_directives()
This lists the currently available directives to use in queries.
Treesitter syntax highlighting (WIP) *lua-treesitter-highlight* Treesitter syntax highlighting (WIP) *lua-treesitter-highlight*
@ -409,10 +410,15 @@ Lua module: vim.treesitter.query *treesitter-query*
add_directive({name}, {handler}, {force}) *add_directive()* add_directive({name}, {handler}, {force}) *add_directive()*
Adds a new directive to be used in queries Adds a new directive to be used in queries
Handlers can set match level data by setting directly on the
metadata object `metadata.key = value`, additionally, handlers
can set node level data by using the capture id on the
metadata table `metadata[capture_id].key = value`
Parameters: ~ Parameters: ~
{name} the name of the directive, without leading # {name} the name of the directive, without leading #
{handler} the handler function to be used signature will {handler} the handler function to be used signature will
be (match, pattern, bufnr, predicate) be (match, pattern, bufnr, predicate, metadata)
add_predicate({name}, {handler}, {force}) *add_predicate()* add_predicate({name}, {handler}, {force}) *add_predicate()*
Adds a new predicate to be used in queries Adds a new predicate to be used in queries
@ -451,6 +457,8 @@ get_query_files({lang}, {query_name}, {is_included})
as `nil` as `nil`
list_directives() *list_directives()* list_directives() *list_directives()*
Lists the currently available directives to use in queries.
Return: ~ Return: ~
The list of supported directives. The list of supported directives.

View File

@ -295,6 +295,14 @@ function LanguageTree:included_regions()
return self._regions return self._regions
end end
---@private
local function get_node_range(node, id, metadata)
if metadata[id] and metadata[id].range then
return metadata[id].range
end
return { node:range() }
end
--- Gets language injection points by language. --- Gets language injection points by language.
--- ---
--- This is where most of the injection processing occurs. --- This is where most of the injection processing occurs.
@ -327,10 +335,10 @@ function LanguageTree:_get_injections()
-- Allow for captured nodes to be used -- Allow for captured nodes to be used
if type(content) == 'number' then if type(content) == 'number' then
content = { match[content] } content = { match[content]:range() }
end end
if content then if type(content) == 'table' and #content >= 4 then
vim.list_extend(ranges, content) vim.list_extend(ranges, content)
end end
end end
@ -351,7 +359,7 @@ function LanguageTree:_get_injections()
elseif name == 'combined' then elseif name == 'combined' then
combined = true combined = true
elseif name == 'content' and #ranges == 0 then elseif name == 'content' and #ranges == 0 then
table.insert(ranges, node) table.insert(ranges, get_node_range(node, id, metadata))
-- Ignore any tags that start with "_" -- Ignore any tags that start with "_"
-- Allows for other tags to be used in matches -- Allows for other tags to be used in matches
elseif string.sub(name, 1, 1) ~= '_' then elseif string.sub(name, 1, 1) ~= '_' then
@ -360,7 +368,7 @@ function LanguageTree:_get_injections()
end end
if #ranges == 0 then if #ranges == 0 then
table.insert(ranges, node) table.insert(ranges, get_node_range(node, id, metadata))
end end
end end
end end
@ -397,7 +405,10 @@ function LanguageTree:_get_injections()
for _, entry in pairs(patterns) do for _, entry in pairs(patterns) do
if entry.combined then if entry.combined then
table.insert(result[lang], vim.tbl_flatten(entry.regions)) local regions = vim.tbl_map(function(e)
return vim.tbl_flatten(e)
end, entry.regions)
table.insert(result[lang], regions)
else else
for _, ranges in ipairs(entry.regions) do for _, ranges in ipairs(entry.regions) do
table.insert(result[lang], ranges) table.insert(result[lang], ranges)

View File

@ -313,20 +313,22 @@ local directive_handlers = {
['set!'] = function(_, _, _, pred, metadata) ['set!'] = function(_, _, _, pred, metadata)
if #pred == 4 then if #pred == 4 then
-- (#set! @capture "key" "value") -- (#set! @capture "key" "value")
local capture = pred[2] local _, capture_id, key, value = unpack(pred)
if not metadata[capture] then if not metadata[capture_id] then
metadata[capture] = {} metadata[capture_id] = {}
end end
metadata[capture][pred[3]] = pred[4] metadata[capture_id][key] = value
else else
local _, key, value = unpack(pred)
-- (#set! "key" "value") -- (#set! "key" "value")
metadata[pred[2]] = pred[3] metadata[key] = value
end end
end, end,
-- Shifts the range of a node. -- Shifts the range of a node.
-- Example: (#offset! @_node 0 1 0 -1) -- Example: (#offset! @_node 0 1 0 -1)
['offset!'] = function(match, _, _, pred, metadata) ['offset!'] = function(match, _, _, pred, metadata)
local offset_node = match[pred[2]] local capture_id = pred[2]
local offset_node = match[capture_id]
local range = { offset_node:range() } local range = { offset_node:range() }
local start_row_offset = pred[3] or 0 local start_row_offset = pred[3] or 0
local start_col_offset = pred[4] or 0 local start_col_offset = pred[4] or 0
@ -340,7 +342,10 @@ local directive_handlers = {
-- If this produces an invalid range, we just skip it. -- If this produces an invalid range, we just skip it.
if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then
metadata.content = { range } if not metadata[capture_id] then
metadata[capture_id] = {}
end
metadata[capture_id].range = range
end end
end, end,
} }
@ -360,9 +365,14 @@ end
--- Adds a new directive to be used in queries --- Adds a new directive to be used in queries
--- ---
--- Handlers can set match level data by setting directly on the
--- metadata object `metadata.key = value`, additionally, handlers
--- can set node level data by using the capture id on the
--- metadata table `metadata[capture_id].key = value`
---
---@param name the name of the directive, without leading # ---@param name the name of the directive, without leading #
---@param handler the handler function to be used ---@param handler the handler function to be used
--- signature will be (match, pattern, bufnr, predicate) --- signature will be (match, pattern, bufnr, predicate, metadata)
function M.add_directive(name, handler, force) function M.add_directive(name, handler, force)
if directive_handlers[name] and not force then if directive_handlers[name] and not force then
error(string.format('Overriding %s', name)) error(string.format('Overriding %s', name))
@ -371,6 +381,7 @@ function M.add_directive(name, handler, force)
directive_handlers[name] = handler directive_handlers[name] = handler
end end
--- Lists the currently available directives to use in queries.
---@return The list of supported directives. ---@return The list of supported directives.
function M.list_directives() function M.list_directives()
return vim.tbl_keys(directive_handlers) return vim.tbl_keys(directive_handlers)