mirror of
https://github.com/neovim/neovim.git
synced 2025-12-02 15:03:01 +00:00
Merge pull request #19946 from bfredl/newnode
feat: upstream some nvim-treesitter functions
This commit is contained in:
@@ -352,6 +352,15 @@ attribute: >
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
Lua module: vim.treesitter *lua-treesitter-core*
|
Lua module: vim.treesitter *lua-treesitter-core*
|
||||||
|
|
||||||
|
get_node_range({node_or_range}) *get_node_range()*
|
||||||
|
Get the node's range or unpack a range table
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{node_or_range} (table)
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
start_row, start_col, end_row, end_col
|
||||||
|
|
||||||
get_parser({bufnr}, {lang}, {opts}) *get_parser()*
|
get_parser({bufnr}, {lang}, {opts}) *get_parser()*
|
||||||
Gets the parser for this bufnr / ft combination.
|
Gets the parser for this bufnr / ft combination.
|
||||||
|
|
||||||
@@ -374,6 +383,26 @@ get_string_parser({str}, {lang}, {opts}) *get_string_parser()*
|
|||||||
{lang} The language of this string
|
{lang} The language of this string
|
||||||
{opts} Options to pass to the created language tree
|
{opts} Options to pass to the created language tree
|
||||||
|
|
||||||
|
is_ancestor({dest}, {source}) *is_ancestor()*
|
||||||
|
Determines whether a node is the ancestor of another
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{dest} (table) the possible ancestor
|
||||||
|
{source} (table) the possible descendant node
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
(boolean) True if dest is an ancestor of source
|
||||||
|
|
||||||
|
node_contains({node}, {range}) *node_contains()*
|
||||||
|
Determines if a node contains a range
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{node} (table) The node
|
||||||
|
{range} (table) The range
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
(boolean) True if the node contains the range
|
||||||
|
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
Lua module: vim.treesitter.language *treesitter-language*
|
Lua module: vim.treesitter.language *treesitter-language*
|
||||||
@@ -427,12 +456,16 @@ add_predicate({name}, {handler}, {force}) *add_predicate()*
|
|||||||
{handler} the handler function to be used signature will be (match,
|
{handler} the handler function to be used signature will be (match,
|
||||||
pattern, bufnr, predicate)
|
pattern, bufnr, predicate)
|
||||||
|
|
||||||
get_node_text({node}, {source}) *get_node_text()*
|
get_node_text({node}, {source}, {opts}) *get_node_text()*
|
||||||
Gets the text corresponding to a given node
|
Gets the text corresponding to a given node
|
||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
{node} the node
|
{node} (table) The node
|
||||||
{source} The buffer or string from which the node is extracted
|
{source} (table) The buffer or string from which the node is
|
||||||
|
extracted
|
||||||
|
{opts} (table) Optional parameters.
|
||||||
|
• concat: (boolean default true) Concatenate result in a
|
||||||
|
string
|
||||||
|
|
||||||
get_query({lang}, {query_name}) *get_query()*
|
get_query({lang}, {query_name}) *get_query()*
|
||||||
Returns the runtime query {query_name} for {lang}.
|
Returns the runtime query {query_name} for {lang}.
|
||||||
@@ -678,6 +711,17 @@ LanguageTree:language_for_range({self}, {range})
|
|||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
{range} A text range, see |LanguageTree:contains|
|
{range} A text range, see |LanguageTree:contains|
|
||||||
|
{self}
|
||||||
|
|
||||||
|
*LanguageTree:named_node_for_range()*
|
||||||
|
LanguageTree:named_node_for_range({self}, {range}, {opts})
|
||||||
|
Gets the smallest named node that contains {range}
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{range} (table) A text range
|
||||||
|
{opts} (table) Options table
|
||||||
|
{opts.ignore_injections} (boolean) (default true) Ignore injected
|
||||||
|
languages.
|
||||||
{self}
|
{self}
|
||||||
|
|
||||||
LanguageTree:parse({self}) *LanguageTree:parse()*
|
LanguageTree:parse({self}) *LanguageTree:parse()*
|
||||||
@@ -738,6 +782,17 @@ LanguageTree:source({self}) *LanguageTree:source()*
|
|||||||
Returns the source content of the language tree (bufnr or string).
|
Returns the source content of the language tree (bufnr or string).
|
||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
|
{self}
|
||||||
|
|
||||||
|
*LanguageTree:tree_for_range()*
|
||||||
|
LanguageTree:tree_for_range({self}, {range}, {opts})
|
||||||
|
Gets the tree that contains {range}
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{range} (table) A text range
|
||||||
|
{opts} (table) Options table
|
||||||
|
{opts.ignore_injections} (boolean) (default true) Ignore injected
|
||||||
|
languages.
|
||||||
{self}
|
{self}
|
||||||
|
|
||||||
LanguageTree:trees({self}) *LanguageTree:trees()*
|
LanguageTree:trees({self}) *LanguageTree:trees()*
|
||||||
|
|||||||
@@ -118,4 +118,53 @@ function M.get_string_parser(str, lang, opts)
|
|||||||
return LanguageTree.new(str, lang, opts)
|
return LanguageTree.new(str, lang, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Determines whether a node is the ancestor of another
|
||||||
|
---
|
||||||
|
---@param dest table the possible ancestor
|
||||||
|
---@param source table the possible descendant node
|
||||||
|
---
|
||||||
|
---@returns (boolean) True if dest is an ancestor of source
|
||||||
|
function M.is_ancestor(dest, source)
|
||||||
|
if not (dest and source) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local current = source
|
||||||
|
while current ~= nil do
|
||||||
|
if current == dest then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
current = current:parent()
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the node's range or unpack a range table
|
||||||
|
---
|
||||||
|
---@param node_or_range table
|
||||||
|
---
|
||||||
|
---@returns start_row, start_col, end_row, end_col
|
||||||
|
function M.get_node_range(node_or_range)
|
||||||
|
if type(node_or_range) == 'table' then
|
||||||
|
return unpack(node_or_range)
|
||||||
|
else
|
||||||
|
return node_or_range:range()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Determines if a node contains a range
|
||||||
|
---@param node table The node
|
||||||
|
---@param range table The range
|
||||||
|
---
|
||||||
|
---@returns (boolean) True if the node contains the range
|
||||||
|
function M.node_contains(node, range)
|
||||||
|
local start_row, start_col, end_row, end_col = node:range()
|
||||||
|
local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2])
|
||||||
|
local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4])
|
||||||
|
|
||||||
|
return start_fits and end_fits
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ function LanguageTree:included_regions()
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
local function get_node_range(node, id, metadata)
|
local function get_range_from_metadata(node, id, metadata)
|
||||||
if metadata[id] and metadata[id].range then
|
if metadata[id] and metadata[id].range then
|
||||||
return metadata[id].range
|
return metadata[id].range
|
||||||
end
|
end
|
||||||
@@ -362,7 +362,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, get_node_range(node, id, metadata))
|
table.insert(ranges, get_range_from_metadata(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
|
||||||
@@ -371,7 +371,7 @@ function LanguageTree:_get_injections()
|
|||||||
end
|
end
|
||||||
|
|
||||||
if #ranges == 0 then
|
if #ranges == 0 then
|
||||||
table.insert(ranges, get_node_range(node, id, metadata))
|
table.insert(ranges, get_range_from_metadata(node, id, metadata))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -549,6 +549,44 @@ function LanguageTree:contains(range)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Gets the tree that contains {range}
|
||||||
|
---
|
||||||
|
---@param range table A text range
|
||||||
|
---@param opts table Options table
|
||||||
|
---@param opts.ignore_injections boolean (default true) Ignore injected languages.
|
||||||
|
function LanguageTree:tree_for_range(range, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
local ignore = vim.F.if_nil(opts.ignore_injections, true)
|
||||||
|
|
||||||
|
if not ignore then
|
||||||
|
for _, child in pairs(self._children) do
|
||||||
|
for _, tree in pairs(child:trees()) do
|
||||||
|
if tree_contains(tree, range) then
|
||||||
|
return tree
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, tree in pairs(self._trees) do
|
||||||
|
if tree_contains(tree, range) then
|
||||||
|
return tree
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Gets the smallest named node that contains {range}
|
||||||
|
---
|
||||||
|
---@param range table A text range
|
||||||
|
---@param opts table Options table
|
||||||
|
---@param opts.ignore_injections boolean (default true) Ignore injected languages.
|
||||||
|
function LanguageTree:named_node_for_range(range, opts)
|
||||||
|
local tree = self:tree_for_range(range, opts)
|
||||||
|
return tree:root():named_descendant_for_range(unpack(range))
|
||||||
|
end
|
||||||
|
|
||||||
--- Gets the appropriate language that contains {range}
|
--- Gets the appropriate language that contains {range}
|
||||||
---
|
---
|
||||||
---@param range A text range, see |LanguageTree:contains|
|
---@param range A text range, see |LanguageTree:contains|
|
||||||
|
|||||||
@@ -181,9 +181,14 @@ end
|
|||||||
|
|
||||||
--- Gets the text corresponding to a given node
|
--- Gets the text corresponding to a given node
|
||||||
---
|
---
|
||||||
---@param node the node
|
---@param node table The node
|
||||||
---@param source The buffer or string from which the node is extracted
|
---@param source table The buffer or string from which the node is extracted
|
||||||
function M.get_node_text(node, source)
|
---@param opts table Optional parameters.
|
||||||
|
--- - concat: (boolean default true) Concatenate result in a string
|
||||||
|
function M.get_node_text(node, source, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
local concat = vim.F.if_nil(opts.concat, true)
|
||||||
|
|
||||||
local start_row, start_col, start_byte = node:start()
|
local start_row, start_col, start_byte = node:start()
|
||||||
local end_row, end_col, end_byte = node:end_()
|
local end_row, end_col, end_byte = node:end_()
|
||||||
|
|
||||||
@@ -210,7 +215,7 @@ function M.get_node_text(node, source)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.concat(lines, '\n')
|
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(start_byte + 1, end_byte)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -88,6 +88,10 @@ static struct luaL_Reg node_meta[] = {
|
|||||||
{ "prev_sibling", node_prev_sibling },
|
{ "prev_sibling", node_prev_sibling },
|
||||||
{ "next_named_sibling", node_next_named_sibling },
|
{ "next_named_sibling", node_next_named_sibling },
|
||||||
{ "prev_named_sibling", node_prev_named_sibling },
|
{ "prev_named_sibling", node_prev_named_sibling },
|
||||||
|
{ "named_children", node_named_children },
|
||||||
|
{ "root", node_root },
|
||||||
|
{ "byte_length", node_byte_length },
|
||||||
|
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -619,7 +623,7 @@ void push_tree(lua_State *L, TSTree *tree, bool do_copy)
|
|||||||
lua_setfenv(L, -2); // [udata]
|
lua_setfenv(L, -2); // [udata]
|
||||||
}
|
}
|
||||||
|
|
||||||
static TSTree **tree_check(lua_State *L, uint16_t index)
|
static TSTree **tree_check(lua_State *L, int index)
|
||||||
{
|
{
|
||||||
TSTree **ud = luaL_checkudata(L, index, TS_META_TREE);
|
TSTree **ud = luaL_checkudata(L, index, TS_META_TREE);
|
||||||
return ud;
|
return ud;
|
||||||
@@ -1062,6 +1066,57 @@ static int node_prev_named_sibling(lua_State *L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int node_named_children(lua_State *L)
|
||||||
|
{
|
||||||
|
TSNode source;
|
||||||
|
if (!node_check(L, 1, &source)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
TSTreeCursor cursor = ts_tree_cursor_new(source);
|
||||||
|
|
||||||
|
lua_newtable(L);
|
||||||
|
int curr_index = 0;
|
||||||
|
|
||||||
|
if (ts_tree_cursor_goto_first_child(&cursor)) {
|
||||||
|
do {
|
||||||
|
TSNode node = ts_tree_cursor_current_node(&cursor);
|
||||||
|
if (ts_node_is_named(node)) {
|
||||||
|
push_node(L, node, 1);
|
||||||
|
lua_rawseti(L, -2, ++curr_index);
|
||||||
|
}
|
||||||
|
} while (ts_tree_cursor_goto_next_sibling(&cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
ts_tree_cursor_delete(&cursor);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int node_root(lua_State *L)
|
||||||
|
{
|
||||||
|
TSNode node;
|
||||||
|
if (!node_check(L, 1, &node)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSNode root = ts_tree_root_node(node.tree);
|
||||||
|
push_node(L, root, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int node_byte_length(lua_State *L)
|
||||||
|
{
|
||||||
|
TSNode node;
|
||||||
|
if (!node_check(L, 1, &node)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t start_byte = ts_node_start_byte(node);
|
||||||
|
uint32_t end_byte = ts_node_end_byte(node);
|
||||||
|
|
||||||
|
lua_pushnumber(L, end_byte - start_byte);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/// assumes the match table being on top of the stack
|
/// assumes the match table being on top of the stack
|
||||||
static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx)
|
static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ local exec_lua = helpers.exec_lua
|
|||||||
local pcall_err = helpers.pcall_err
|
local pcall_err = helpers.pcall_err
|
||||||
local matches = helpers.matches
|
local matches = helpers.matches
|
||||||
local pending_c_parser = helpers.pending_c_parser
|
local pending_c_parser = helpers.pending_c_parser
|
||||||
|
local insert = helpers.insert
|
||||||
|
|
||||||
before_each(clear)
|
before_each(clear)
|
||||||
|
|
||||||
@@ -84,5 +85,35 @@ describe('treesitter language API', function()
|
|||||||
eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers",
|
eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers",
|
||||||
pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0)"))
|
pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0)"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('retrieve the tree given a range', function ()
|
||||||
|
if pending_c_parser(pending) then return end
|
||||||
|
insert([[
|
||||||
|
int main() {
|
||||||
|
int x = 3;
|
||||||
|
}]])
|
||||||
|
|
||||||
|
exec_lua([[
|
||||||
|
langtree = vim.treesitter.get_parser(0, "c")
|
||||||
|
tree = langtree:tree_for_range({1, 3, 1, 3})
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq('<node translation_unit>', exec_lua('return tostring(tree:root())'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('retrieve the node given a range', function ()
|
||||||
|
if pending_c_parser(pending) then return end
|
||||||
|
insert([[
|
||||||
|
int main() {
|
||||||
|
int x = 3;
|
||||||
|
}]])
|
||||||
|
|
||||||
|
exec_lua([[
|
||||||
|
langtree = vim.treesitter.get_parser(0, "c")
|
||||||
|
node = langtree:named_node_for_range({1, 3, 1, 3})
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq('<node primitive_type>', exec_lua('return tostring(node)'))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|||||||
@@ -59,4 +59,53 @@ describe('treesitter node API', function()
|
|||||||
exec_lua 'node = node:prev_named_sibling()'
|
exec_lua 'node = node:prev_named_sibling()'
|
||||||
eq('int x', lua_eval('node_text(node)'))
|
eq('int x', lua_eval('node_text(node)'))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('can retrieve the children of a node', function()
|
||||||
|
insert([[
|
||||||
|
int main() {
|
||||||
|
int x = 3;
|
||||||
|
}]])
|
||||||
|
|
||||||
|
local len = exec_lua([[
|
||||||
|
tree = vim.treesitter.get_parser(0, "c"):parse()[1]
|
||||||
|
node = tree:root():child(0)
|
||||||
|
children = node:named_children()
|
||||||
|
|
||||||
|
return #children
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq(3, len)
|
||||||
|
eq('<node compound_statement>', lua_eval('tostring(children[3])'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can retrieve the tree root given a node', function()
|
||||||
|
insert([[
|
||||||
|
int main() {
|
||||||
|
int x = 3;
|
||||||
|
}]])
|
||||||
|
|
||||||
|
exec_lua([[
|
||||||
|
tree = vim.treesitter.get_parser(0, "c"):parse()[1]
|
||||||
|
root = tree:root()
|
||||||
|
node = root:child(0):child(2)
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq(lua_eval('tostring(root)'), lua_eval('tostring(node:root())'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can compute the byte length of a node', function()
|
||||||
|
insert([[
|
||||||
|
int main() {
|
||||||
|
int x = 3;
|
||||||
|
}]])
|
||||||
|
|
||||||
|
exec_lua([[
|
||||||
|
tree = vim.treesitter.get_parser(0, "c"):parse()[1]
|
||||||
|
root = tree:root()
|
||||||
|
child = root:child(0):child(0)
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq(28, lua_eval('root:byte_length()'))
|
||||||
|
eq(3, lua_eval('child:byte_length()'))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
33
test/functional/treesitter/utils_spec.lua
Normal file
33
test/functional/treesitter/utils_spec.lua
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
|
||||||
|
local clear = helpers.clear
|
||||||
|
local insert = helpers.insert
|
||||||
|
local eq = helpers.eq
|
||||||
|
local exec_lua = helpers.exec_lua
|
||||||
|
local pending_c_parser = helpers.pending_c_parser
|
||||||
|
|
||||||
|
before_each(clear)
|
||||||
|
|
||||||
|
describe('treesitter utils', function()
|
||||||
|
before_each(clear)
|
||||||
|
|
||||||
|
it('can find an ancestor', function()
|
||||||
|
if pending_c_parser(pending) then return end
|
||||||
|
|
||||||
|
insert([[
|
||||||
|
int main() {
|
||||||
|
int x = 3;
|
||||||
|
}]])
|
||||||
|
|
||||||
|
exec_lua([[
|
||||||
|
parser = vim.treesitter.get_parser(0, "c")
|
||||||
|
tree = parser:parse()[1]
|
||||||
|
root = tree:root()
|
||||||
|
ancestor = root:child(0)
|
||||||
|
child = ancestor:child(0)
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq(true, exec_lua('return vim.treesitter.is_ancestor(ancestor, child)'))
|
||||||
|
eq(false, exec_lua('return vim.treesitter.is_ancestor(child, ancestor)'))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
Reference in New Issue
Block a user