From 090ade4af6344a7bc4ee56a8052c0739c0428c04 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 4 Apr 2023 12:58:16 +0100 Subject: [PATCH] refactor(treesitter): delegate region calculation to treesitter (#22576) --- .github/workflows/test.yml | 7 +- runtime/lua/vim/treesitter/languagetree.lua | 90 +++++++++------------ src/nvim/CMakeLists.txt | 2 +- src/nvim/lua/treesitter.c | 19 +++++ 4 files changed, 61 insertions(+), 57 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d18f874c4..7794e07077 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -376,13 +376,13 @@ jobs: libluajit-5.1-dev \ libmsgpack-dev \ libtermkey-dev \ - libtree-sitter-dev \ libunibilium-dev \ libuv1-dev \ lua-filesystem \ lua-lpeg \ lua-mpack \ luajit + # libtree-sitter-dev \ # libvterm-dev \ # lua-luv-dev @@ -397,7 +397,10 @@ jobs: # dependencies don't have the required version available. We use the # bundled versions for these with the hopes of being able to remove them # later on. - cmake -S cmake.deps -B .deps -G Ninja -D USE_BUNDLED=OFF -D USE_BUNDLED_LUV=ON -D USE_BUNDLED_LIBVTERM=ON + cmake -S cmake.deps -B .deps -G Ninja -D USE_BUNDLED=OFF \ + -D USE_BUNDLED_LUV=ON \ + -D USE_BUNDLED_LIBVTERM=ON \ + -D USE_BUNDLED_TS=ON cmake --build .deps - name: Build diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 922e4881ca..cf0ecbd839 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -57,7 +57,9 @@ local Range = require('vim.treesitter._range') ---@field private _injection_query Query Queries defining injected languages ---@field private _opts table Options ---@field private _parser TSParser Parser for language ----@field private _regions Range6[][] List of regions this tree should manage and parse +---@field private _regions Range6[][]? +---List of regions this tree should manage and parse. If nil then regions are +---taken from _trees. This is mostly a short-lived cache for included_regions() ---@field private _lang string Language name ---@field private _source (integer|string) Buffer or string to parse ---@field private _trees TSTree[] Reference to parsed tree (one for each language) @@ -91,7 +93,6 @@ function LanguageTree.new(source, lang, opts) _source = source, _lang = lang, _children = {}, - _regions = {}, _trees = {}, _opts = opts, _injection_query = injections[lang] and query.parse(lang, injections[lang]) @@ -237,27 +238,21 @@ function LanguageTree:parse() --- At least 1 region is invalid if not self:is_valid(true) then - local function _parsetree(index) - local parse_time, tree, tree_changes = - tcall(self._parser.parse, self._parser, self._trees[index], self._source) + -- If there are no ranges, set to an empty list + -- so the included ranges in the parser are cleared. + for i, ranges in ipairs(self:included_regions()) do + if not self._valid or not self._valid[i] then + self._parser:set_included_ranges(ranges) + local parse_time, tree, tree_changes = + tcall(self._parser.parse, self._parser, self._trees[i], self._source) - self:_do_callback('changedtree', tree_changes, tree) - self._trees[index] = tree - vim.list_extend(changes, tree_changes) + self:_do_callback('changedtree', tree_changes, tree) + self._trees[i] = tree + vim.list_extend(changes, tree_changes) - total_parse_time = total_parse_time + parse_time - regions_parsed = regions_parsed + 1 - end - - if #self._regions > 0 then - for i, ranges in ipairs(self._regions) do - if not self._valid or not self._valid[i] then - self._parser:set_included_ranges(ranges) - _parsetree(i) - end + total_parse_time = total_parse_time + parse_time + regions_parsed = regions_parsed + 1 end - else - _parsetree(1) end end @@ -403,7 +398,7 @@ function LanguageTree:_iter_regions(fn) local all_valid = true - for i, region in ipairs(self._regions) do + for i, region in ipairs(self:included_regions()) do if self._valid[i] == nil then self._valid[i] = true end @@ -454,7 +449,7 @@ function LanguageTree:set_included_regions(new_regions) end end - if #self._regions ~= #new_regions then + if #self:included_regions() ~= #new_regions then self._trees = {} self:invalidate() else @@ -462,13 +457,28 @@ function LanguageTree:set_included_regions(new_regions) return vim.deep_equal(new_regions[i], region) end) end + self._regions = new_regions end ---Gets the set of included regions ---@return integer[][] function LanguageTree:included_regions() - return self._regions + if self._regions then + return self._regions + end + + if #self._trees == 0 then + return { {} } + end + + local regions = {} ---@type Range6[][] + for i, _ in ipairs(self._trees) do + regions[i] = self._trees[i]:included_ranges(true) + end + + self._regions = regions + return regions end ---@private @@ -721,6 +731,8 @@ function LanguageTree:_edit( ) end + self._regions = nil + local changed_range = { start_row, start_col, @@ -730,42 +742,12 @@ function LanguageTree:_edit( end_byte_old, } - local new_range = { - start_row, - start_col, - start_byte, - end_row_new, - end_col_new, - end_byte_new, - } - - if #self._regions == 0 then - self._valid = false - end - -- Validate regions after editing the tree self:_iter_regions(function(_, region) - for i, r in ipairs(region) do + for _, r in ipairs(region) do if Range.intercepts(r, changed_range) then return false end - - -- Range after change. Adjust - if Range.cmp_pos.gt(r[1], r[2], changed_range[4], changed_range[5]) then - local byte_offset = new_range[6] - changed_range[6] - local row_offset = new_range[4] - changed_range[4] - - -- Update the range to avoid invalidation in set_included_regions() - -- which will compare the regions against the parsed injection regions - region[i] = { - r[1] + row_offset, - r[2], - r[3] + byte_offset, - r[4] + row_offset, - r[5], - r[6] + byte_offset, - } - end end return true end) diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index cb688785df..c0466e3de9 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -21,7 +21,7 @@ find_package(Iconv REQUIRED) find_package(Libtermkey 0.22 REQUIRED) find_package(Libvterm 0.3 REQUIRED) find_package(Msgpack 1.0.0 REQUIRED) -find_package(Treesitter REQUIRED) +find_package(Treesitter 0.20.8 REQUIRED) find_package(Unibilium 2.0 REQUIRED) target_link_libraries(main_lib INTERFACE diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 289a0cb9b4..ae3a7b6d75 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -64,6 +64,7 @@ static struct luaL_Reg tree_meta[] = { { "__tostring", tree_tostring }, { "root", tree_root }, { "edit", tree_edit }, + { "included_ranges", tree_get_ranges }, { "copy", tree_copy }, { NULL, NULL } }; @@ -512,6 +513,24 @@ static int tree_edit(lua_State *L) return 0; } +static int tree_get_ranges(lua_State *L) +{ + TSTree **tree = tree_check(L, 1); + if (!(*tree)) { + return 0; + } + + bool include_bytes = (lua_gettop(L) >= 2) && lua_toboolean(L, 2); + + uint32_t len; + TSRange *ranges = ts_tree_included_ranges(*tree, &len); + + push_ranges(L, ranges, len, include_bytes); + + xfree(ranges); + return 1; +} + // Use the top of the stack (without popping it) to create a TSRange, it can be // either a lua table or a TSNode static void range_from_lua(lua_State *L, TSRange *range)