mirror of
https://github.com/neovim/neovim.git
synced 2025-10-25 20:07:09 +00:00
Merge pull request #21548 from figsoda/transform-capture
feat(treesitter): allow capture text to be transformed
This commit is contained in:
@@ -151,6 +151,12 @@ The following new APIs or features were added.
|
|||||||
|
|
||||||
• |:highlight| now supports an additional attribute "altfont".
|
• |:highlight| now supports an additional attribute "altfont".
|
||||||
|
|
||||||
|
• Treesitter captures can now be transformed by directives. This will allow
|
||||||
|
more complicated dynamic language injections.
|
||||||
|
|
||||||
|
• |vim.treesitter.query.get_node_text()| now accepts a `metadata` option for
|
||||||
|
writing custom directives using |vim.treesitter.query.add_directive()|.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
CHANGED FEATURES *news-changes*
|
CHANGED FEATURES *news-changes*
|
||||||
|
|
||||||
|
|||||||
@@ -746,6 +746,9 @@ get_node_text({node}, {source}, {opts})
|
|||||||
• {opts} (table|nil) Optional parameters.
|
• {opts} (table|nil) Optional parameters.
|
||||||
• concat: (boolean) Concatenate result in a string (default
|
• concat: (boolean) Concatenate result in a string (default
|
||||||
true)
|
true)
|
||||||
|
• metadata (table) Metadata of a specific capture. This
|
||||||
|
would be set to `metadata[capture_id]` when using
|
||||||
|
|vim.treesitter.query.add_directive()|.
|
||||||
|
|
||||||
Return: ~
|
Return: ~
|
||||||
(string[]|string|nil)
|
(string[]|string|nil)
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ function LanguageTree:_get_injections()
|
|||||||
|
|
||||||
-- Lang should override any other language tag
|
-- Lang should override any other language tag
|
||||||
if name == 'language' and not lang then
|
if name == 'language' and not lang then
|
||||||
lang = query.get_node_text(node, self._source) --[[@as string]]
|
lang = query.get_node_text(node, self._source, { metadata = metadata[id] })
|
||||||
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
|
||||||
|
|||||||
@@ -55,6 +55,38 @@ local function add_included_lang(base_langs, lang, ilang)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param buf (number)
|
||||||
|
---@param range (table)
|
||||||
|
---@param concat (boolean)
|
||||||
|
---@returns (string[]|string|nil)
|
||||||
|
local function buf_range_get_text(buf, range, concat)
|
||||||
|
local lines
|
||||||
|
local start_row, start_col, end_row, end_col = unpack(range)
|
||||||
|
local eof_row = a.nvim_buf_line_count(buf)
|
||||||
|
if start_row >= eof_row then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if end_col == 0 then
|
||||||
|
lines = a.nvim_buf_get_lines(buf, start_row, end_row, true)
|
||||||
|
end_col = -1
|
||||||
|
else
|
||||||
|
lines = a.nvim_buf_get_lines(buf, start_row, end_row + 1, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if #lines > 0 then
|
||||||
|
if #lines == 1 then
|
||||||
|
lines[1] = string.sub(lines[1], start_col + 1, end_col)
|
||||||
|
else
|
||||||
|
lines[1] = string.sub(lines[1], start_col + 1)
|
||||||
|
lines[#lines] = string.sub(lines[#lines], 1, end_col)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return concat and table.concat(lines, '\n') or lines
|
||||||
|
end
|
||||||
|
|
||||||
--- Gets the list of files used to make up a query
|
--- Gets the list of files used to make up a query
|
||||||
---
|
---
|
||||||
---@param lang string Language to get query for
|
---@param lang string Language to get query for
|
||||||
@@ -240,40 +272,22 @@ end
|
|||||||
---@param source (number|string) Buffer or string from which the {node} is extracted
|
---@param source (number|string) Buffer or string from which the {node} is extracted
|
||||||
---@param opts (table|nil) Optional parameters.
|
---@param opts (table|nil) Optional parameters.
|
||||||
--- - concat: (boolean) Concatenate result in a string (default true)
|
--- - concat: (boolean) Concatenate result in a string (default true)
|
||||||
|
--- - metadata (table) Metadata of a specific capture. This would be
|
||||||
|
--- set to `metadata[capture_id]` when using
|
||||||
|
--- |vim.treesitter.query.add_directive()|.
|
||||||
---@return (string[]|string|nil)
|
---@return (string[]|string|nil)
|
||||||
function M.get_node_text(node, source, opts)
|
function M.get_node_text(node, source, opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local concat = vim.F.if_nil(opts.concat, true)
|
local concat = vim.F.if_nil(opts.concat, true)
|
||||||
|
local metadata = opts.metadata or {}
|
||||||
|
|
||||||
local start_row, start_col, start_byte = node:start()
|
if metadata.text then
|
||||||
local end_row, end_col, end_byte = node:end_()
|
return metadata.text
|
||||||
|
elseif type(source) == 'number' then
|
||||||
if type(source) == 'number' then
|
return metadata.range and buf_range_get_text(source, metadata.range, concat)
|
||||||
local eof_row = a.nvim_buf_line_count(source)
|
or buf_range_get_text(source, { node:range() }, concat)
|
||||||
if start_row >= eof_row then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local lines ---@type string[]
|
|
||||||
if end_col == 0 then
|
|
||||||
lines = a.nvim_buf_get_lines(source, start_row, end_row, true)
|
|
||||||
end_col = -1
|
|
||||||
else
|
|
||||||
lines = a.nvim_buf_get_lines(source, start_row, end_row + 1, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
if #lines > 0 then
|
|
||||||
if #lines == 1 then
|
|
||||||
lines[1] = string.sub(lines[1], start_col + 1, end_col)
|
|
||||||
else
|
|
||||||
lines[1] = string.sub(lines[1], start_col + 1)
|
|
||||||
lines[#lines] = string.sub(lines[#lines], 1, end_col)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return concat and table.concat(lines, '\n') or lines
|
|
||||||
elseif type(source) == 'string' then
|
elseif type(source) == 'string' then
|
||||||
return source:sub(start_byte + 1, end_byte)
|
return source:sub(select(3, node:start()) + 1, select(3, node:end_()))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -431,9 +445,11 @@ local directive_handlers = {
|
|||||||
['offset!'] = function(match, _, _, pred, metadata)
|
['offset!'] = function(match, _, _, pred, metadata)
|
||||||
---@cast pred integer[]
|
---@cast pred integer[]
|
||||||
local capture_id = pred[2]
|
local capture_id = pred[2]
|
||||||
local offset_node = match[capture_id]
|
if not metadata[capture_id] then
|
||||||
local range = { offset_node:range() }
|
metadata[capture_id] = {}
|
||||||
---@cast range integer[] bug in sumneko
|
end
|
||||||
|
|
||||||
|
local range = metadata[capture_id].range or { match[capture_id]: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
|
||||||
local end_row_offset = pred[5] or 0
|
local end_row_offset = pred[5] or 0
|
||||||
@@ -446,12 +462,24 @@ 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
|
||||||
if not metadata[capture_id] then
|
|
||||||
metadata[capture_id] = {}
|
|
||||||
end
|
|
||||||
metadata[capture_id].range = range
|
metadata[capture_id].range = range
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
-- Transform the content of the node
|
||||||
|
-- Example: (#gsub! @_node ".*%.(.*)" "%1")
|
||||||
|
['gsub!'] = function(match, _, bufnr, pred, metadata)
|
||||||
|
assert(#pred == 4)
|
||||||
|
|
||||||
|
local id = pred[2]
|
||||||
|
local node = match[id]
|
||||||
|
local text = M.get_node_text(node, bufnr, { metadata = metadata[id] }) or ''
|
||||||
|
|
||||||
|
if not metadata[id] then
|
||||||
|
metadata[id] = {}
|
||||||
|
end
|
||||||
|
metadata[id].text = text:gsub(pred[3], pred[4])
|
||||||
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Adds a new predicate to be used in queries
|
--- Adds a new predicate to be used in queries
|
||||||
|
|||||||
@@ -277,6 +277,9 @@ end]]
|
|||||||
function fake_node:end_()
|
function fake_node:end_()
|
||||||
return 3, 0, 23
|
return 3, 0, 23
|
||||||
end
|
end
|
||||||
|
function fake_node:range()
|
||||||
|
return 3, 0, 3, 0
|
||||||
|
end
|
||||||
return vim.treesitter.get_node_text(fake_node, 0) == nil
|
return vim.treesitter.get_node_text(fake_node, 0) == nil
|
||||||
]])
|
]])
|
||||||
eq(true, result)
|
eq(true, result)
|
||||||
@@ -296,6 +299,9 @@ end]]
|
|||||||
function fake_node:end_()
|
function fake_node:end_()
|
||||||
return 1, 0, 7
|
return 1, 0, 7
|
||||||
end
|
end
|
||||||
|
function fake_node:range()
|
||||||
|
return 1, 0, 1, 0
|
||||||
|
end
|
||||||
return vim.treesitter.get_node_text(fake_node, 0) == ''
|
return vim.treesitter.get_node_text(fake_node, 0) == ''
|
||||||
]])
|
]])
|
||||||
eq(true, result)
|
eq(true, result)
|
||||||
@@ -728,7 +734,7 @@ int x = INT_MAX;
|
|||||||
return list
|
return list
|
||||||
]]
|
]]
|
||||||
|
|
||||||
eq({ 'offset!', 'set!' }, res_list)
|
eq({ 'gsub!', 'offset!', 'set!' }, res_list)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user