From 1df3f5ec6aca24cbe7b78ead5c37ad06a65c84e8 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 23 Feb 2023 17:05:20 +0000 Subject: feat(treesitter): upstream foldexpr from nvim-treesitter --- runtime/lua/vim/treesitter/_fold.lua | 173 +++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 runtime/lua/vim/treesitter/_fold.lua (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua new file mode 100644 index 0000000000..a66cc6d543 --- /dev/null +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -0,0 +1,173 @@ +local api = vim.api + +local M = {} + +--- Memoizes a function based on the buffer tick of the provided bufnr. +--- The cache entry is cleared when the buffer is detached to avoid memory leaks. +---@generic F: function +---@param fn F fn to memoize, taking the bufnr as first argument +---@return F +local function memoize_by_changedtick(fn) + ---@type table + local cache = {} + + ---@param bufnr integer + return function(bufnr, ...) + local tick = api.nvim_buf_get_changedtick(bufnr) + + if cache[bufnr] then + if cache[bufnr].last_tick == tick then + return cache[bufnr].result + end + else + local function detach_handler() + cache[bufnr] = nil + end + + -- Clean up logic only! + api.nvim_buf_attach(bufnr, false, { + on_detach = detach_handler, + on_reload = detach_handler, + }) + end + + cache[bufnr] = { + result = fn(bufnr, ...), + last_tick = tick, + } + + return cache[bufnr].result + end +end + +---@param bufnr integer +---@param capture string +---@param query_name string +---@param callback fun(id: integer, node:TSNode, metadata: TSMetadata) +local function iter_matches_with_capture(bufnr, capture, query_name, callback) + local parser = vim.treesitter.get_parser(bufnr) + + if not parser then + return + end + + parser:for_each_tree(function(tree, lang_tree) + local lang = lang_tree:lang() + local query = vim.treesitter.query.get_query(lang, query_name) + if query then + local root = tree:root() + local start, _, stop = root:range() + for _, match, metadata in query:iter_matches(root, bufnr, start, stop) do + for id, node in pairs(match) do + if query.captures[id] == capture then + callback(id, node, metadata) + end + end + end + end + end) +end + +---@private +--- TODO(lewis6991): copied from languagetree.lua. Consolidate +---@param node TSNode +---@param id integer +---@param metadata TSMetadata +---@return Range +local function get_range_from_metadata(node, id, metadata) + if metadata[id] and metadata[id].range then + return metadata[id].range --[[@as Range]] + end + return { node:range() } +end + +-- This is cached on buf tick to avoid computing that multiple times +-- Especially not for every line in the file when `zx` is hit +---@param bufnr integer +---@return table +local folds_levels = memoize_by_changedtick(function(bufnr) + local max_fold_level = vim.wo.foldnestmax + local function trim_level(level) + if level > max_fold_level then + return max_fold_level + end + return level + end + + -- start..stop is an inclusive range + local start_counts = {} ---@type table + local stop_counts = {} ---@type table + + local prev_start = -1 + local prev_stop = -1 + + local min_fold_lines = vim.wo.foldminlines + + iter_matches_with_capture(bufnr, 'fold', 'folds', function(id, node, metadata) + local range = get_range_from_metadata(node, id, metadata) + local start, stop, stop_col = range[1], range[3], range[4] + + if stop_col == 0 then + stop = stop - 1 + end + + local fold_length = stop - start + 1 + + -- Fold only multiline nodes that are not exactly the same as previously met folds + -- Checking against just the previously found fold is sufficient if nodes + -- are returned in preorder or postorder when traversing tree + if fold_length > min_fold_lines and not (start == prev_start and stop == prev_stop) then + start_counts[start] = (start_counts[start] or 0) + 1 + stop_counts[stop] = (stop_counts[stop] or 0) + 1 + prev_start = start + prev_stop = stop + end + end) + + ---@type table + local levels = {} + local current_level = 0 + + -- We now have the list of fold opening and closing, fill the gaps and mark where fold start + for lnum = 0, api.nvim_buf_line_count(bufnr) do + local last_trimmed_level = trim_level(current_level) + current_level = current_level + (start_counts[lnum] or 0) + local trimmed_level = trim_level(current_level) + current_level = current_level - (stop_counts[lnum] or 0) + + -- Determine if it's the start/end of a fold + -- NB: vim's fold-expr interface does not have a mechanism to indicate that + -- two (or more) folds start at this line, so it cannot distinguish between + -- ( \n ( \n )) \n (( \n ) \n ) + -- versus + -- ( \n ( \n ) \n ( \n ) \n ) + -- If it did have such a mechanism, (trimmed_level - last_trimmed_level) + -- would be the correct number of starts to pass on. + local prefix = '' + if trimmed_level - last_trimmed_level > 0 then + prefix = '>' + end + + levels[lnum + 1] = prefix .. tostring(trimmed_level) + end + + return levels +end) + +---@param lnum integer|nil +---@return string +function M.foldexpr(lnum) + lnum = lnum or vim.v.lnum + local bufnr = api.nvim_get_current_buf() + + ---@diagnostic disable-next-line:invisible + if not vim.treesitter._has_parser(bufnr) or not lnum then + return '0' + end + + local levels = folds_levels(bufnr) or {} + + return levels[lnum] or '0' +end + +return M -- cgit From 46b73bf22cb951151de9bf0712d42e194000b677 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 9 Mar 2023 15:28:55 +0000 Subject: perf(treesitter): more efficient foldexpr --- runtime/lua/vim/treesitter/_fold.lua | 290 +++++++++++++++++++++++------------ 1 file changed, 188 insertions(+), 102 deletions(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index a66cc6d543..435cb9fdb6 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -1,139 +1,157 @@ +local Range = require('vim.treesitter._range') + local api = vim.api -local M = {} +---@class FoldInfo +---@field levels table +---@field levels0 table +---@field private start_counts table +---@field private stop_counts table +local FoldInfo = {} +FoldInfo.__index = FoldInfo ---- Memoizes a function based on the buffer tick of the provided bufnr. ---- The cache entry is cleared when the buffer is detached to avoid memory leaks. ----@generic F: function ----@param fn F fn to memoize, taking the bufnr as first argument ----@return F -local function memoize_by_changedtick(fn) - ---@type table - local cache = {} - - ---@param bufnr integer - return function(bufnr, ...) - local tick = api.nvim_buf_get_changedtick(bufnr) - - if cache[bufnr] then - if cache[bufnr].last_tick == tick then - return cache[bufnr].result - end - else - local function detach_handler() - cache[bufnr] = nil - end +function FoldInfo.new() + return setmetatable({ + start_counts = {}, + stop_counts = {}, + levels0 = {}, + levels = {}, + }, FoldInfo) +end - -- Clean up logic only! - api.nvim_buf_attach(bufnr, false, { - on_detach = detach_handler, - on_reload = detach_handler, - }) - end +---@param srow integer +---@param erow integer +function FoldInfo:invalidate_range(srow, erow) + for i = srow, erow do + self.start_counts[i + 1] = nil + self.stop_counts[i + 1] = nil + self.levels0[i + 1] = nil + self.levels[i + 1] = nil + end +end - cache[bufnr] = { - result = fn(bufnr, ...), - last_tick = tick, - } +---@param srow integer +---@param erow integer +function FoldInfo:remove_range(srow, erow) + for i = erow - 1, srow, -1 do + table.remove(self.levels, i + 1) + table.remove(self.levels0, i + 1) + table.remove(self.start_counts, i + 1) + table.remove(self.stop_counts, i + 1) + end +end - return cache[bufnr].result +---@param srow integer +---@param erow integer +function FoldInfo:add_range(srow, erow) + for i = srow, erow - 1 do + table.insert(self.levels, i + 1, '-1') + table.insert(self.levels0, i + 1, -1) + table.insert(self.start_counts, i + 1, nil) + table.insert(self.stop_counts, i + 1, nil) end end ----@param bufnr integer ----@param capture string ----@param query_name string ----@param callback fun(id: integer, node:TSNode, metadata: TSMetadata) -local function iter_matches_with_capture(bufnr, capture, query_name, callback) - local parser = vim.treesitter.get_parser(bufnr) +---@param lnum integer +function FoldInfo:add_start(lnum) + self.start_counts[lnum] = (self.start_counts[lnum] or 0) + 1 +end - if not parser then - return - end +---@param lnum integer +function FoldInfo:add_stop(lnum) + self.stop_counts[lnum] = (self.stop_counts[lnum] or 0) + 1 +end - parser:for_each_tree(function(tree, lang_tree) - local lang = lang_tree:lang() - local query = vim.treesitter.query.get_query(lang, query_name) - if query then - local root = tree:root() - local start, _, stop = root:range() - for _, match, metadata in query:iter_matches(root, bufnr, start, stop) do - for id, node in pairs(match) do - if query.captures[id] == capture then - callback(id, node, metadata) - end - end - end - end - end) +---@param lnum integer +---@return integer +function FoldInfo:get_start(lnum) + return self.start_counts[lnum] or 0 +end + +---@param lnum integer +---@return integer +function FoldInfo:get_stop(lnum) + return self.stop_counts[lnum] or 0 end ---@private --- TODO(lewis6991): copied from languagetree.lua. Consolidate ---@param node TSNode ----@param id integer ---@param metadata TSMetadata ----@return Range -local function get_range_from_metadata(node, id, metadata) - if metadata[id] and metadata[id].range then - return metadata[id].range --[[@as Range]] +---@return Range4 +local function get_range_from_metadata(node, metadata) + if metadata and metadata.range then + return metadata.range --[[@as Range4]] end return { node:range() } end --- This is cached on buf tick to avoid computing that multiple times --- Especially not for every line in the file when `zx` is hit ----@param bufnr integer ----@return table -local folds_levels = memoize_by_changedtick(function(bufnr) +local function trim_level(level) local max_fold_level = vim.wo.foldnestmax - local function trim_level(level) - if level > max_fold_level then - return max_fold_level - end - return level + if level > max_fold_level then + return max_fold_level end + return level +end - -- start..stop is an inclusive range - local start_counts = {} ---@type table - local stop_counts = {} ---@type table +---@param bufnr integer +---@param info FoldInfo +---@param srow integer? +---@param erow integer? +local function get_folds_levels(bufnr, info, srow, erow) + srow = srow or 0 + erow = erow or api.nvim_buf_line_count(bufnr) + + info:invalidate_range(srow, erow) local prev_start = -1 local prev_stop = -1 - local min_fold_lines = vim.wo.foldminlines + vim.treesitter.get_parser(bufnr):for_each_tree(function(tree, ltree) + local query = vim.treesitter.query.get_query(ltree:lang(), 'folds') + if not query then + return + end + + -- erow in query is end-exclusive + local q_erow = erow and erow + 1 or -1 - iter_matches_with_capture(bufnr, 'fold', 'folds', function(id, node, metadata) - local range = get_range_from_metadata(node, id, metadata) - local start, stop, stop_col = range[1], range[3], range[4] + for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow or 0, q_erow) do + if query.captures[id] == 'fold' then + local range = get_range_from_metadata(node, metadata[id]) + local start, _, stop, stop_col = Range.unpack4(range) - if stop_col == 0 then - stop = stop - 1 - end + if stop_col == 0 then + stop = stop - 1 + end - local fold_length = stop - start + 1 + local fold_length = stop - start + 1 - -- Fold only multiline nodes that are not exactly the same as previously met folds - -- Checking against just the previously found fold is sufficient if nodes - -- are returned in preorder or postorder when traversing tree - if fold_length > min_fold_lines and not (start == prev_start and stop == prev_stop) then - start_counts[start] = (start_counts[start] or 0) + 1 - stop_counts[stop] = (stop_counts[stop] or 0) + 1 - prev_start = start - prev_stop = stop + -- Fold only multiline nodes that are not exactly the same as previously met folds + -- Checking against just the previously found fold is sufficient if nodes + -- are returned in preorder or postorder when traversing tree + if + fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop) + then + info:add_start(start + 1) + info:add_stop(stop + 1) + prev_start = start + prev_stop = stop + end + end end end) - ---@type table - local levels = {} - local current_level = 0 + local current_level = info.levels0[srow] or 0 -- We now have the list of fold opening and closing, fill the gaps and mark where fold start - for lnum = 0, api.nvim_buf_line_count(bufnr) do + for lnum = srow + 1, erow + 1 do local last_trimmed_level = trim_level(current_level) - current_level = current_level + (start_counts[lnum] or 0) + current_level = current_level + info:get_start(lnum) + info.levels0[lnum] = current_level + local trimmed_level = trim_level(current_level) - current_level = current_level - (stop_counts[lnum] or 0) + current_level = current_level - info:get_stop(lnum) -- Determine if it's the start/end of a fold -- NB: vim's fold-expr interface does not have a mechanism to indicate that @@ -148,11 +166,61 @@ local folds_levels = memoize_by_changedtick(function(bufnr) prefix = '>' end - levels[lnum + 1] = prefix .. tostring(trimmed_level) + info.levels[lnum] = prefix .. tostring(trimmed_level) + end +end + +local M = {} + +---@type table +local foldinfos = {} + +local function recompute_folds() + if api.nvim_get_mode().mode == 'i' then + -- foldUpdate() is guarded in insert mode. So update folds on InsertLeave + api.nvim_create_autocmd('InsertLeave', { + once = true, + callback = vim._foldupdate, + }) + return end - return levels -end) + vim._foldupdate() +end + +---@param bufnr integer +---@param foldinfo FoldInfo +---@param tree_changes Range4[] +local function on_changedtree(bufnr, foldinfo, tree_changes) + -- For some reason, queries seem to use the old buffer state in on_bytes. + -- Get around this by scheduling and manually updating folds. + vim.schedule(function() + for _, change in ipairs(tree_changes) do + local srow, _, erow = Range.unpack4(change) + get_folds_levels(bufnr, foldinfo, srow, erow) + end + recompute_folds() + end) +end + +---@param bufnr integer +---@param foldinfo FoldInfo +---@param start_row integer +---@param old_row integer +---@param new_row integer +local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) + local end_row_old = start_row + old_row + local end_row_new = start_row + new_row + if new_row < old_row then + foldinfo:remove_range(end_row_old, end_row_new) + elseif new_row > old_row then + foldinfo:add_range(start_row, end_row_new) + vim.schedule(function() + get_folds_levels(bufnr, foldinfo, start_row, end_row_new) + recompute_folds() + end) + end +end ---@param lnum integer|nil ---@return string @@ -165,9 +233,27 @@ function M.foldexpr(lnum) return '0' end - local levels = folds_levels(bufnr) or {} + if not foldinfos[bufnr] then + foldinfos[bufnr] = FoldInfo.new() + get_folds_levels(bufnr, foldinfos[bufnr]) + + local parser = vim.treesitter.get_parser(bufnr) + parser:register_cbs({ + on_changedtree = function(tree_changes) + on_changedtree(bufnr, foldinfos[bufnr], tree_changes) + end, + + on_bytes = function(_, _, start_row, _, _, old_row, _, _, new_row, _, _) + on_bytes(bufnr, foldinfos[bufnr], start_row, old_row, new_row) + end, + + on_detach = function() + foldinfos[bufnr] = nil + end, + }) + end - return levels[lnum] or '0' + return foldinfos[bufnr].levels[lnum] or '0' end return M -- cgit From 9d70fe062ca01ac0673faa6ccbb88345916aeea7 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 10 Mar 2023 16:10:05 +0000 Subject: feat(treesitter)!: consolidate query util functions - And address more type errors. - Removed the `concat` option from `get_node_text` since it was applied inconsistently and made typing awkward. --- runtime/lua/vim/treesitter/_fold.lua | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 435cb9fdb6..fd2c707d17 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -1,4 +1,5 @@ local Range = require('vim.treesitter._range') +local Query = require('vim.treesitter.query') local api = vim.api @@ -74,18 +75,6 @@ function FoldInfo:get_stop(lnum) return self.stop_counts[lnum] or 0 end ----@private ---- TODO(lewis6991): copied from languagetree.lua. Consolidate ----@param node TSNode ----@param metadata TSMetadata ----@return Range4 -local function get_range_from_metadata(node, metadata) - if metadata and metadata.range then - return metadata.range --[[@as Range4]] - end - return { node:range() } -end - local function trim_level(level) local max_fold_level = vim.wo.foldnestmax if level > max_fold_level then @@ -118,7 +107,7 @@ local function get_folds_levels(bufnr, info, srow, erow) for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow or 0, q_erow) do if query.captures[id] == 'fold' then - local range = get_range_from_metadata(node, metadata[id]) + local range = Query.get_range(node, bufnr, metadata[id]) local start, _, stop, stop_col = Range.unpack4(range) if stop_col == 0 then -- cgit From 35799a6629f10cc49e79381e61038b3a4ca3bb23 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 13 Mar 2023 10:44:43 +0000 Subject: fix(treesitter): foldexpr (#22652) The ranges passed to foldinfo.remove_range were in the wrong order. --- runtime/lua/vim/treesitter/_fold.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index fd2c707d17..90f4394fcc 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -200,8 +200,9 @@ end local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) local end_row_old = start_row + old_row local end_row_new = start_row + new_row + if new_row < old_row then - foldinfo:remove_range(end_row_old, end_row_new) + foldinfo:remove_range(end_row_new, end_row_old) elseif new_row > old_row then foldinfo:add_range(start_row, end_row_new) vim.schedule(function() -- cgit From 4e4203f71b0b9bb2ca4ad9abd2fbf4ea1deaf9a6 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 23 Mar 2023 11:23:51 +0000 Subject: fix(treesitter): annotations - Begin using `@package` in place of `@private` for functions that are accessed internally but outside their defined class. - Rename Node -> TSP.Node --- runtime/lua/vim/treesitter/_fold.lua | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 90f4394fcc..3e67e400c2 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -3,7 +3,7 @@ local Query = require('vim.treesitter.query') local api = vim.api ----@class FoldInfo +---@class TS.FoldInfo ---@field levels table ---@field levels0 table ---@field private start_counts table @@ -11,6 +11,7 @@ local api = vim.api local FoldInfo = {} FoldInfo.__index = FoldInfo +---@private function FoldInfo.new() return setmetatable({ start_counts = {}, @@ -20,6 +21,7 @@ function FoldInfo.new() }, FoldInfo) end +---@package ---@param srow integer ---@param erow integer function FoldInfo:invalidate_range(srow, erow) @@ -31,6 +33,7 @@ function FoldInfo:invalidate_range(srow, erow) end end +---@package ---@param srow integer ---@param erow integer function FoldInfo:remove_range(srow, erow) @@ -42,6 +45,7 @@ function FoldInfo:remove_range(srow, erow) end end +---@package ---@param srow integer ---@param erow integer function FoldInfo:add_range(srow, erow) @@ -53,22 +57,26 @@ function FoldInfo:add_range(srow, erow) end end +---@package ---@param lnum integer function FoldInfo:add_start(lnum) self.start_counts[lnum] = (self.start_counts[lnum] or 0) + 1 end +---@package ---@param lnum integer function FoldInfo:add_stop(lnum) self.stop_counts[lnum] = (self.stop_counts[lnum] or 0) + 1 end +---@packag ---@param lnum integer ---@return integer function FoldInfo:get_start(lnum) return self.start_counts[lnum] or 0 end +---@package ---@param lnum integer ---@return integer function FoldInfo:get_stop(lnum) @@ -84,7 +92,7 @@ local function trim_level(level) end ---@param bufnr integer ----@param info FoldInfo +---@param info TS.FoldInfo ---@param srow integer? ---@param erow integer? local function get_folds_levels(bufnr, info, srow, erow) @@ -161,7 +169,7 @@ end local M = {} ----@type table +---@type table local foldinfos = {} local function recompute_folds() @@ -178,7 +186,7 @@ local function recompute_folds() end ---@param bufnr integer ----@param foldinfo FoldInfo +---@param foldinfo TS.FoldInfo ---@param tree_changes Range4[] local function on_changedtree(bufnr, foldinfo, tree_changes) -- For some reason, queries seem to use the old buffer state in on_bytes. @@ -193,7 +201,7 @@ local function on_changedtree(bufnr, foldinfo, tree_changes) end ---@param bufnr integer ----@param foldinfo FoldInfo +---@param foldinfo TS.FoldInfo ---@param start_row integer ---@param old_row integer ---@param new_row integer @@ -212,13 +220,13 @@ local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) end end +---@package ---@param lnum integer|nil ---@return string function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - ---@diagnostic disable-next-line:invisible if not vim.treesitter._has_parser(bufnr) or not lnum then return '0' end -- cgit From ac7397f4a06e451fedde86fb4eba0038d0d75e68 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 24 Mar 2023 16:31:30 +0000 Subject: fix(treesitter): add missing deprecate --- runtime/lua/vim/treesitter/_fold.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 3e67e400c2..6547ab936e 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -1,5 +1,4 @@ local Range = require('vim.treesitter._range') -local Query = require('vim.treesitter.query') local api = vim.api @@ -105,7 +104,7 @@ local function get_folds_levels(bufnr, info, srow, erow) local prev_stop = -1 vim.treesitter.get_parser(bufnr):for_each_tree(function(tree, ltree) - local query = vim.treesitter.query.get_query(ltree:lang(), 'folds') + local query = vim.treesitter.query.get(ltree:lang(), 'folds') if not query then return end @@ -115,7 +114,7 @@ local function get_folds_levels(bufnr, info, srow, erow) for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow or 0, q_erow) do if query.captures[id] == 'fold' then - local range = Query.get_range(node, bufnr, metadata[id]) + local range = vim.treesitter.get_range(node, bufnr, metadata[id]) local start, _, stop, stop_col = Range.unpack4(range) if stop_col == 0 then -- cgit From a5c572bd446a89be2dccb2f7479ff1b017074640 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:07:33 +0200 Subject: docs: fix typos Co-authored-by: Gregory Anders Co-authored-by: Raphael Co-authored-by: C.D. MacEachern Co-authored-by: himanoa --- runtime/lua/vim/treesitter/_fold.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 6547ab936e..7df93d1b2e 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -68,7 +68,7 @@ function FoldInfo:add_stop(lnum) self.stop_counts[lnum] = (self.stop_counts[lnum] or 0) + 1 end ----@packag +---@package ---@param lnum integer ---@return integer function FoldInfo:get_start(lnum) -- cgit From 26cc946226d96bf6b474d850b961e1060346c96f Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 1 May 2023 10:32:29 +0100 Subject: fix(treesitter): foldexpr tweaks Some small general fixes found working on developing async parsing. --- runtime/lua/vim/treesitter/_fold.lua | 40 +++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 7df93d1b2e..51e60bf495 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -1,3 +1,5 @@ +local ts = vim.treesitter + local Range = require('vim.treesitter._range') local api = vim.api @@ -90,21 +92,45 @@ local function trim_level(level) return level end +--- If a parser doesn't have any ranges explicitly set, treesitter will +--- return a range with end_row and end_bytes with a value of UINT32_MAX, +--- so clip end_row to the max buffer line. +--- +--- TODO(lewis6991): Handle this generally +--- +--- @param bufnr integer +--- @param erow integer? +--- @return integer +local function normalise_erow(bufnr, erow) + local max_erow = api.nvim_buf_line_count(bufnr) - 1 + return math.min(erow or max_erow, max_erow) +end + ---@param bufnr integer ---@param info TS.FoldInfo ---@param srow integer? ---@param erow integer? local function get_folds_levels(bufnr, info, srow, erow) + if not api.nvim_buf_is_valid(bufnr) then + return false + end + srow = srow or 0 - erow = erow or api.nvim_buf_line_count(bufnr) + erow = normalise_erow(bufnr, erow) info:invalidate_range(srow, erow) local prev_start = -1 local prev_stop = -1 - vim.treesitter.get_parser(bufnr):for_each_tree(function(tree, ltree) - local query = vim.treesitter.query.get(ltree:lang(), 'folds') + local parser = ts.get_parser(bufnr) + + if not parser:is_valid() then + return + end + + parser:for_each_tree(function(tree, ltree) + local query = ts.query.get(ltree:lang(), 'folds') if not query then return end @@ -112,9 +138,9 @@ local function get_folds_levels(bufnr, info, srow, erow) -- erow in query is end-exclusive local q_erow = erow and erow + 1 or -1 - for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow or 0, q_erow) do + for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow, q_erow) do if query.captures[id] == 'fold' then - local range = vim.treesitter.get_range(node, bufnr, metadata[id]) + local range = ts.get_range(node, bufnr, metadata[id]) local start, _, stop, stop_col = Range.unpack4(range) if stop_col == 0 then @@ -226,7 +252,7 @@ function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - if not vim.treesitter._has_parser(bufnr) or not lnum then + if not ts._has_parser(bufnr) or not lnum then return '0' end @@ -234,7 +260,7 @@ function M.foldexpr(lnum) foldinfos[bufnr] = FoldInfo.new() get_folds_levels(bufnr, foldinfos[bufnr]) - local parser = vim.treesitter.get_parser(bufnr) + local parser = ts.get_parser(bufnr) parser:register_cbs({ on_changedtree = function(tree_changes) on_changedtree(bufnr, foldinfos[bufnr], tree_changes) -- cgit From fba18a3b62310f4535d979a05288101b9af2ef50 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 2 May 2023 10:07:18 +0100 Subject: fix(treesitter): do not calc folds on unloaded buffers Fixes #23423 --- runtime/lua/vim/treesitter/_fold.lua | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 51e60bf495..edceb8217a 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -111,10 +111,6 @@ end ---@param srow integer? ---@param erow integer? local function get_folds_levels(bufnr, info, srow, erow) - if not api.nvim_buf_is_valid(bufnr) then - return false - end - srow = srow or 0 erow = normalise_erow(bufnr, erow) @@ -210,13 +206,25 @@ local function recompute_folds() vim._foldupdate() end +--- Schedule a function only if bufnr is loaded +---@param bufnr integer +---@param fn function +local function schedule_if_loaded(bufnr, fn) + vim.schedule(function() + if not api.nvim_buf_is_loaded(bufnr) then + return + end + fn() + end) +end + ---@param bufnr integer ---@param foldinfo TS.FoldInfo ---@param tree_changes Range4[] local function on_changedtree(bufnr, foldinfo, tree_changes) -- For some reason, queries seem to use the old buffer state in on_bytes. -- Get around this by scheduling and manually updating folds. - vim.schedule(function() + schedule_if_loaded(bufnr, function() for _, change in ipairs(tree_changes) do local srow, _, erow = Range.unpack4(change) get_folds_levels(bufnr, foldinfo, srow, erow) @@ -238,7 +246,7 @@ local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) foldinfo:remove_range(end_row_new, end_row_old) elseif new_row > old_row then foldinfo:add_range(start_row, end_row_new) - vim.schedule(function() + schedule_if_loaded(bufnr, function() get_folds_levels(bufnr, foldinfo, start_row, end_row_new) recompute_folds() end) -- cgit From 3ba930844c302dc43d32a30ed453667409596c4a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 2 May 2023 22:27:14 +0100 Subject: perf(treesitter): insert/remove items efficiently (#23443) --- runtime/lua/vim/treesitter/_fold.lua | 63 +++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index edceb8217a..f6425d7cb9 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -34,15 +34,58 @@ function FoldInfo:invalidate_range(srow, erow) end end +--- Efficiently remove items from middle of a list a list. +--- +--- Calling table.remove() in a loop will re-index the tail of the table on +--- every iteration, instead this function will re-index the table exactly +--- once. +--- +--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524 +--- +---@param t any[] +---@param first integer +---@param last integer +local function list_remove(t, first, last) + local n = #t + for i = 0, n - first do + t[first + i] = t[last + 1 + i] + t[last + 1 + i] = nil + end +end + ---@package ---@param srow integer ---@param erow integer function FoldInfo:remove_range(srow, erow) - for i = erow - 1, srow, -1 do - table.remove(self.levels, i + 1) - table.remove(self.levels0, i + 1) - table.remove(self.start_counts, i + 1) - table.remove(self.stop_counts, i + 1) + list_remove(self.levels, srow + 1, erow) + list_remove(self.levels0, srow + 1, erow) + list_remove(self.start_counts, srow + 1, erow) + list_remove(self.stop_counts, srow + 1, erow) +end + +--- Efficiently insert items into the middle of a list. +--- +--- Calling table.insert() in a loop will re-index the tail of the table on +--- every iteration, instead this function will re-index the table exactly +--- once. +--- +--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524 +--- +---@param t any[] +---@param first integer +---@param last integer +---@param v any +local function list_insert(t, first, last, v) + local n = #t + + -- Shift table forward + for i = n - first, 0, -1 do + t[last + 1 + i] = t[first + i] + end + + -- Fill in new values + for i = first, last do + t[i] = v end end @@ -50,12 +93,10 @@ end ---@param srow integer ---@param erow integer function FoldInfo:add_range(srow, erow) - for i = srow, erow - 1 do - table.insert(self.levels, i + 1, '-1') - table.insert(self.levels0, i + 1, -1) - table.insert(self.start_counts, i + 1, nil) - table.insert(self.stop_counts, i + 1, nil) - end + list_insert(self.levels, srow + 1, erow, '-1') + list_insert(self.levels0, srow + 1, erow, -1) + list_insert(self.start_counts, srow + 1, erow, nil) + list_insert(self.stop_counts, srow + 1, erow, nil) end ---@package -- cgit From ef64e225f6f6c01280aa8472bebe812016f357bf Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 18 May 2023 10:52:01 +0100 Subject: fix(treesitter): allow foldexpr without highlights (#23672) Ref nvim-treesitter/nvim-treesitter#4748 --- runtime/lua/vim/treesitter/_fold.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index f6425d7cb9..a8f8c7967e 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -301,7 +301,8 @@ function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - if not ts._has_parser(bufnr) or not lnum then + local parser = vim.F.npcall(ts.get_parser, bufnr) + if not parser then return '0' end @@ -309,7 +310,6 @@ function M.foldexpr(lnum) foldinfos[bufnr] = FoldInfo.new() get_folds_levels(bufnr, foldinfos[bufnr]) - local parser = ts.get_parser(bufnr) parser:register_cbs({ on_changedtree = function(tree_changes) on_changedtree(bufnr, foldinfos[bufnr], tree_changes) -- cgit From c7e7f1d4b4b62c75bb54e652f25c6c6b8785a7f4 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Wed, 28 Jun 2023 03:05:09 +0900 Subject: fix(treesitter): make foldexpr work without highlighting (#24167) Problem: Treesitter fold is not updated if treesitter hightlight is not active. More precisely, updating folds requires `LanguageTree:parse()`. Solution: Call `parse()` before computing folds and compute folds when lines are added/removed. This doesn't guarantee correctness of the folds, because some changes that don't add/remove line won't update the folds even if they should (e.g. adding pair of braces). But it is good enough for most cases, while not introducing big overhead. Also, if highlighting is active, it is likely that `TSHighlighter._on_buf` already ran `parse()` (or vice versa). --- runtime/lua/vim/treesitter/_fold.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index a8f8c7967e..d308657237 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -162,9 +162,7 @@ local function get_folds_levels(bufnr, info, srow, erow) local parser = ts.get_parser(bufnr) - if not parser:is_valid() then - return - end + parser:parse() parser:for_each_tree(function(tree, ltree) local query = ts.query.get(ltree:lang(), 'folds') @@ -283,10 +281,12 @@ local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) local end_row_old = start_row + old_row local end_row_new = start_row + new_row - if new_row < old_row then - foldinfo:remove_range(end_row_new, end_row_old) - elseif new_row > old_row then - foldinfo:add_range(start_row, end_row_new) + if new_row ~= old_row then + if new_row < old_row then + foldinfo:remove_range(end_row_new, end_row_old) + else + foldinfo:add_range(start_row, end_row_new) + end schedule_if_loaded(bufnr, function() get_folds_levels(bufnr, foldinfo, start_row, end_row_new) recompute_folds() -- cgit From c44d819ae1f29cd34ee3b2350b5c702caed949c3 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Fri, 7 Jul 2023 19:12:46 +0900 Subject: fix(treesitter): update folds in all relevant windows (#24230) Problem: When using treesitter foldexpr, * :diffput/get open diff folds, and * folds are not updated in other windows that contain the updated buffer. Solution: Update folds in all windows that contain the updated buffer and use expr foldmethod. --- runtime/lua/vim/treesitter/_fold.lua | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index d308657237..a02d0a584d 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -232,20 +232,40 @@ local M = {} ---@type table local foldinfos = {} -local function recompute_folds() +--- Update the folds in the windows that contain the buffer and use expr foldmethod (assuming that +--- the user doesn't use different foldexpr for the same buffer). +--- +--- Nvim usually automatically updates folds when text changes, but it doesn't work here because +--- FoldInfo update is scheduled. So we do it manually. +local function foldupdate(bufnr) + local function do_update() + for _, win in ipairs(vim.fn.win_findbuf(bufnr)) do + api.nvim_win_call(win, function() + if vim.wo.foldmethod == 'expr' then + vim._foldupdate() + end + end) + end + end + if api.nvim_get_mode().mode == 'i' then -- foldUpdate() is guarded in insert mode. So update folds on InsertLeave api.nvim_create_autocmd('InsertLeave', { once = true, - callback = vim._foldupdate, + callback = do_update, }) return end - vim._foldupdate() + do_update() end ---- Schedule a function only if bufnr is loaded +--- Schedule a function only if bufnr is loaded. +--- We schedule fold level computation for the following reasons: +--- * queries seem to use the old buffer state in on_bytes for some unknown reason; +--- * to avoid textlock; +--- * to avoid infinite recursion: +--- get_folds_levels → parse → _do_callback → on_changedtree → get_folds_levels. ---@param bufnr integer ---@param fn function local function schedule_if_loaded(bufnr, fn) @@ -261,14 +281,12 @@ end ---@param foldinfo TS.FoldInfo ---@param tree_changes Range4[] local function on_changedtree(bufnr, foldinfo, tree_changes) - -- For some reason, queries seem to use the old buffer state in on_bytes. - -- Get around this by scheduling and manually updating folds. schedule_if_loaded(bufnr, function() for _, change in ipairs(tree_changes) do local srow, _, erow = Range.unpack4(change) get_folds_levels(bufnr, foldinfo, srow, erow) end - recompute_folds() + foldupdate(bufnr) end) end @@ -289,7 +307,7 @@ local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) end schedule_if_loaded(bufnr, function() get_folds_levels(bufnr, foldinfo, start_row, end_row_new) - recompute_folds() + foldupdate(bufnr) end) end end -- cgit From 2ca076e45fb3f1c08f6a1a374834df0701b8d778 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 10 Aug 2023 14:21:56 +0100 Subject: feat(treesitter)!: incremental injection parsing Problem: Treesitter highlighting is slow for large files with lots of injections. Solution: Only parse injections we are going to render during a redraw cycle. --- - `LanguageTree:parse()` will no longer parse injections by default and now requires an explicit range argument to be passed. - `TSHighlighter` now parses injections incrementally during on_win callbacks for the line range being rendered. - Plugins which require certain injections to be parsed must run `parser:parse({ start_row, end_row })` before using the tree. --- runtime/lua/vim/treesitter/_fold.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index a02d0a584d..912a6e8a9f 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -147,11 +147,14 @@ local function normalise_erow(bufnr, erow) return math.min(erow or max_erow, max_erow) end +-- TODO(lewis6991): Setup a decor provider so injections folds can be parsed +-- as the window is redrawn ---@param bufnr integer ---@param info TS.FoldInfo ---@param srow integer? ---@param erow integer? -local function get_folds_levels(bufnr, info, srow, erow) +---@param parse_injections? boolean +local function get_folds_levels(bufnr, info, srow, erow, parse_injections) srow = srow or 0 erow = normalise_erow(bufnr, erow) @@ -162,7 +165,7 @@ local function get_folds_levels(bufnr, info, srow, erow) local parser = ts.get_parser(bufnr) - parser:parse() + parser:parse(parse_injections and { srow, erow } or nil) parser:for_each_tree(function(tree, ltree) local query = ts.query.get(ltree:lang(), 'folds') -- cgit From ffb340bf63af42ac347e23e0488898adc4391328 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Thu, 24 Aug 2023 17:32:43 +0900 Subject: fix(treesitter): update folds only once on InsertLeave Problem: With treesitter fold, InsertLeave can be slow, because a single session of insert mode may schedule multiple fold updates in on_bytes and on_changedtree. Solution: Don't create duplicate autocmds. --- runtime/lua/vim/treesitter/_fold.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 912a6e8a9f..d82e04a5a8 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -235,6 +235,8 @@ local M = {} ---@type table local foldinfos = {} +local group = api.nvim_create_augroup('treesitter/fold', {}) + --- Update the folds in the windows that contain the buffer and use expr foldmethod (assuming that --- the user doesn't use different foldexpr for the same buffer). --- @@ -253,7 +255,15 @@ local function foldupdate(bufnr) if api.nvim_get_mode().mode == 'i' then -- foldUpdate() is guarded in insert mode. So update folds on InsertLeave + if #(api.nvim_get_autocmds({ + group = group, + buffer = bufnr, + })) > 0 then + return + end api.nvim_create_autocmd('InsertLeave', { + group = group, + buffer = bufnr, once = true, callback = do_update, }) -- cgit From 4607807f9fcb83d4e183f6f67e705ffd7f451077 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Sun, 20 Aug 2023 14:17:45 +0900 Subject: fix(treesitter): don't update fold if tree is unchanged Problem: Folds are opened when the visible range changes even if there are no modifications to the buffer, e.g, when using zM for the first time. If the parsed tree was invalid, on_win re-parses and gets empty tree changes, which triggers fold updates. Solution: Don't update folds in on_changedtree if there are no changes. --- runtime/lua/vim/treesitter/_fold.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index d82e04a5a8..8bc08c9c2e 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -299,7 +299,9 @@ local function on_changedtree(bufnr, foldinfo, tree_changes) local srow, _, erow = Range.unpack4(change) get_folds_levels(bufnr, foldinfo, srow, erow) end - foldupdate(bufnr) + if #tree_changes > 0 then + foldupdate(bufnr) + end end) end -- cgit From 9ce1623837a817c3f4f5deff9c8ba862578b6009 Mon Sep 17 00:00:00 2001 From: Till Bungert Date: Sun, 1 Oct 2023 21:10:51 +0200 Subject: feat(treesitter): add foldtext with treesitter highlighting (#25391) --- runtime/lua/vim/treesitter/_fold.lua | 92 ++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 8bc08c9c2e..c6a4b48d4f 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -361,4 +361,96 @@ function M.foldexpr(lnum) return foldinfos[bufnr].levels[lnum] or '0' end +---@package +---@return { [1]: string, [2]: string[] }[]|string +function M.foldtext() + local foldstart = vim.v.foldstart + local bufnr = api.nvim_get_current_buf() + + ---@type boolean, LanguageTree + local ok, parser = pcall(ts.get_parser, bufnr) + if not ok then + return vim.fn.foldtext() + end + + local query = ts.query.get(parser:lang(), 'highlights') + if not query then + return vim.fn.foldtext() + end + + local tree = parser:parse({ foldstart - 1, foldstart })[1] + + local line = api.nvim_buf_get_lines(bufnr, foldstart - 1, foldstart, false)[1] + if not line then + return vim.fn.foldtext() + end + + ---@type { [1]: string, [2]: string[], range: { [1]: integer, [2]: integer } }[] | { [1]: string, [2]: string[] }[] + local result = {} + + local line_pos = 0 + + for id, node, metadata in query:iter_captures(tree:root(), 0, foldstart - 1, foldstart) do + local name = query.captures[id] + local start_row, start_col, end_row, end_col = node:range() + + local priority = tonumber(metadata.priority or vim.highlight.priorities.treesitter) + + if start_row == foldstart - 1 and end_row == foldstart - 1 then + -- check for characters ignored by treesitter + if start_col > line_pos then + table.insert(result, { + line:sub(line_pos + 1, start_col), + { { 'Folded', priority } }, + range = { line_pos, start_col }, + }) + end + line_pos = end_col + + local text = line:sub(start_col + 1, end_col) + table.insert(result, { text, { { '@' .. name, priority } }, range = { start_col, end_col } }) + end + end + + local i = 1 + while i <= #result do + -- find first capture that is not in current range and apply highlights on the way + local j = i + 1 + while + j <= #result + and result[j].range[1] >= result[i].range[1] + and result[j].range[2] <= result[i].range[2] + do + for k, v in ipairs(result[i][2]) do + if not vim.tbl_contains(result[j][2], v) then + table.insert(result[j][2], k, v) + end + end + j = j + 1 + end + + -- remove the parent capture if it is split into children + if j > i + 1 then + table.remove(result, i) + else + -- highlights need to be sorted by priority, on equal prio, the deeper nested capture (earlier + -- in list) should be considered higher prio + if #result[i][2] > 1 then + table.sort(result[i][2], function(a, b) + return a[2] < b[2] + end) + end + + result[i][2] = vim.tbl_map(function(tbl) + return tbl[1] + end, result[i][2]) + result[i] = { result[i][1], result[i][2] } + + i = i + 1 + end + end + + return result +end + return M -- cgit From 3af59a415c98afc42755308e56912b302ad5eb3d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 3 Oct 2023 13:07:03 +0800 Subject: fix(treesitter): make Visual hl work consistently with foldtext (#25484) Problem: Visual highlight is inconsistent on a folded line with treesitter foldtext. Solution: Don't added Folded highlight as it is already in background. --- runtime/lua/vim/treesitter/_fold.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/_fold.lua') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index c6a4b48d4f..5c1cc06908 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -401,7 +401,7 @@ function M.foldtext() if start_col > line_pos then table.insert(result, { line:sub(line_pos + 1, start_col), - { { 'Folded', priority } }, + {}, range = { line_pos, start_col }, }) end -- cgit