diff options
author | bfredl <bjorn.linse@gmail.com> | 2022-08-25 21:35:13 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-25 21:35:13 +0200 |
commit | bfd1adc62c615e7b65bdfe6d3c21158708eb4314 (patch) | |
tree | ec839f0bb6d5c28780f9e694aa2bf764871382b3 | |
parent | 22f920030214c0023525c59daf763441baddba1a (diff) | |
parent | 73ee2b35d1fc738be9bd4b99f49ce8f84351fbe6 (diff) | |
download | rneovim-bfd1adc62c615e7b65bdfe6d3c21158708eb4314.tar.gz rneovim-bfd1adc62c615e7b65bdfe6d3c21158708eb4314.tar.bz2 rneovim-bfd1adc62c615e7b65bdfe6d3c21158708eb4314.zip |
Merge pull request #19946 from bfredl/newnode
feat: upstream some nvim-treesitter functions
-rw-r--r-- | runtime/doc/treesitter.txt | 61 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 49 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 44 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/query.lua | 13 | ||||
-rw-r--r-- | src/nvim/lua/treesitter.c | 57 | ||||
-rw-r--r-- | test/functional/treesitter/language_spec.lua | 31 | ||||
-rw-r--r-- | test/functional/treesitter/node_spec.lua | 49 | ||||
-rw-r--r-- | test/functional/treesitter/utils_spec.lua | 33 |
8 files changed, 326 insertions, 11 deletions
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 06409f9980..c80d8f4479 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -352,6 +352,15 @@ attribute: > ============================================================================== 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()* 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 {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* @@ -427,12 +456,16 @@ add_predicate({name}, {handler}, {force}) *add_predicate()* {handler} the handler function to be used signature will be (match, 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 Parameters: ~ - {node} the node - {source} The buffer or string from which the node is extracted + {node} (table) The node + {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()* Returns the runtime query {query_name} for {lang}. @@ -680,6 +713,17 @@ LanguageTree:language_for_range({self}, {range}) {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} + LanguageTree:parse({self}) *LanguageTree:parse()* Parses all defined regions using a treesitter parser for the language this tree represents. This will run the injection query for this language to @@ -740,6 +784,17 @@ LanguageTree:source({self}) *LanguageTree:source()* 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} + LanguageTree:trees({self}) *LanguageTree:trees()* Returns all trees this language tree contains. Does not include child languages. diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 70f2c425ed..82d41070ee 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -118,4 +118,53 @@ function M.get_string_parser(str, lang, opts) return LanguageTree.new(str, lang, opts) 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 diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 4d3b0631a2..70317a9f94 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -299,7 +299,7 @@ function LanguageTree:included_regions() end ---@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 return metadata[id].range end @@ -362,7 +362,7 @@ function LanguageTree:_get_injections() elseif name == 'combined' then combined = true 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 "_" -- Allows for other tags to be used in matches elseif string.sub(name, 1, 1) ~= '_' then @@ -371,7 +371,7 @@ function LanguageTree:_get_injections() end 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 @@ -549,6 +549,44 @@ function LanguageTree:contains(range) return false 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} --- ---@param range A text range, see |LanguageTree:contains| diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 103e85abfd..697e2e7691 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -181,9 +181,14 @@ end --- Gets the text corresponding to a given node --- ----@param node the node ----@param source The buffer or string from which the node is extracted -function M.get_node_text(node, source) +---@param node table The node +---@param source table The buffer or string from which the node is extracted +---@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 end_row, end_col, end_byte = node:end_() @@ -210,7 +215,7 @@ function M.get_node_text(node, source) end end - return table.concat(lines, '\n') + return concat and table.concat(lines, '\n') or lines elseif type(source) == 'string' then return source:sub(start_byte + 1, end_byte) end diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 954c3410d3..7ff4fbbff4 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -88,6 +88,10 @@ static struct luaL_Reg node_meta[] = { { "prev_sibling", node_prev_sibling }, { "next_named_sibling", node_next_named_sibling }, { "prev_named_sibling", node_prev_named_sibling }, + { "named_children", node_named_children }, + { "root", node_root }, + { "byte_length", node_byte_length }, + { NULL, NULL } }; @@ -619,7 +623,7 @@ void push_tree(lua_State *L, TSTree *tree, bool do_copy) 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); return ud; @@ -1062,6 +1066,57 @@ static int node_prev_named_sibling(lua_State *L) 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 static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx) { diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index 8aa8524a26..8e9941d797 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -7,6 +7,7 @@ local exec_lua = helpers.exec_lua local pcall_err = helpers.pcall_err local matches = helpers.matches local pending_c_parser = helpers.pending_c_parser +local insert = helpers.insert 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", pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0)")) 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) diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua index 21c287644e..87ce1b973c 100644 --- a/test/functional/treesitter/node_spec.lua +++ b/test/functional/treesitter/node_spec.lua @@ -59,4 +59,53 @@ describe('treesitter node API', function() exec_lua 'node = node:prev_named_sibling()' eq('int x', lua_eval('node_text(node)')) 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) diff --git a/test/functional/treesitter/utils_spec.lua b/test/functional/treesitter/utils_spec.lua new file mode 100644 index 0000000000..4f4c18a748 --- /dev/null +++ b/test/functional/treesitter/utils_spec.lua @@ -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) |