From ca10442e01e163dc1872ff8072a87c68e757b52d Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Thu, 20 Jun 2024 22:37:09 +0900 Subject: [PATCH 1/4] fix(treesitter): don't open fold when o/O adds a line below #28709 Problem: `o`-ing on a folded line opens the fold, because the new line gets the fold level from the above line (level '='), which extends the fold to the new line. `O` has a similar problem when run on the line below a fold. Solution: Use -1 for the added line to get the lower level from the above/below line. --- runtime/lua/vim/treesitter/_fold.lua | 2 +- test/functional/treesitter/fold_spec.lua | 63 +++++++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index eecf1ad6b1..746629bfbc 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -87,7 +87,7 @@ end ---@param srow integer ---@param erow integer 0-indexed, exclusive function FoldInfo:add_range(srow, erow) - list_insert(self.levels, srow + 1, erow, '=') + list_insert(self.levels, srow + 1, erow, -1) list_insert(self.levels0, srow + 1, erow, -1) end diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua index 278ba17be0..3e81cebe71 100644 --- a/test/functional/treesitter/fold_spec.lua +++ b/test/functional/treesitter/fold_spec.lua @@ -646,6 +646,67 @@ t3]]) } end) + it('does not extend closed fold with `o`/`O`', function() + local screen = Screen.new(60, 24) + screen:attach() + + insert(test_text) + parse('c') + command([[set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1]]) + + feed('5ggzco') + screen:expect({ + grid = [[ + {7:-}void ui_refresh(void) | + {7:│}{ | + {7:│} int width = INT_MAX, height = INT_MAX; | + {7:│} bool ext_widgets[kUIExtCount]; | + {7:+}{13:+--- 3 lines: for (UIExtension i = 0; (int)i < kUIExtCount}| + {7:│}^ | + {7:│} | + {7:│} bool inclusive = ui_override(); | + {7:-} for (size_t i = 0; i < ui_count; i++) { | + {7:2} UI *ui = uis[i]; | + {7:2} width = MIN(ui->width, width); | + {7:2} height = MIN(ui->height, height); | + {7:2} foo = BAR(ui->bazaar, bazaar); | + {7:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {7:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {7:3} } | + {7:2} } | + {7:│}} | + {1:~ }|*5 + {5:-- INSERT --} | + ]], + }) + + feed('O') + screen:expect({ + grid = [[ + {7:-}void ui_refresh(void) | + {7:│}{ | + {7:│} int width = INT_MAX, height = INT_MAX; | + {7:│} bool ext_widgets[kUIExtCount]; | + {7:+}{13:+--- 3 lines: for (UIExtension i = 0; (int)i < kUIExtCount}| + {7:│}^ | + {7:│} |*2 + {7:│} bool inclusive = ui_override(); | + {7:-} for (size_t i = 0; i < ui_count; i++) { | + {7:2} UI *ui = uis[i]; | + {7:2} width = MIN(ui->width, width); | + {7:2} height = MIN(ui->height, height); | + {7:2} foo = BAR(ui->bazaar, bazaar); | + {7:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {7:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {7:3} } | + {7:2} } | + {7:│}} | + {1:~ }|*4 + {5:-- INSERT --} | + ]], + }) + end) + it("doesn't open folds that are not touched", function() local screen = Screen.new(40, 8) screen:set_default_attr_ids({ @@ -674,7 +735,7 @@ t2]]) grid = [[ {1:-}# h1 | {1:│}t1 | - {1:│}^ | + {1:-}^ | {1:+}{2:+-- 2 lines: # h2·····················}| {3:~ }|*3 {4:-- INSERT --} | From d7ee06124d38c30fbb9af38390036a16de78d5dc Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Sat, 6 Jul 2024 15:34:05 +0900 Subject: [PATCH 2/4] fix(treesitter.foldexpr): robustness against ctrl-c Problem: Exiting the insert mode with ctrl-c does not trigger InsertLeave autocmd. This may lead to nil error in treesitter foldexpr. Solution: Check nil. Folds still can be stale after exiting the insert mode with ctrl-c, but it will be eventually updated correctly. An alternative solution would be to ensure that exiting the insert mode always triggers do_foldupdate. This can be done either by "fixing" ctrl-c or with on_key callback that checks ctrl-c (nvim-cmp does this). --- runtime/lua/vim/treesitter/_fold.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 746629bfbc..9a866e67fa 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -268,6 +268,15 @@ end ---@package function FoldInfo:do_foldupdate(bufnr) + -- InsertLeave is not executed when is used for exiting the insert mode, leaving + -- do_foldupdate untouched. If another execution of foldupdate consumes foldupdate_range, the + -- InsertLeave do_foldupdate gets nil foldupdate_range. In that case, skip the update. This is + -- correct because the update that consumed the range must have incorporated the range that + -- InsertLeave meant to update. + if not self.foldupdate_range then + return + end + local srow, erow = self.foldupdate_range[1], self.foldupdate_range[2] self.foldupdate_range = nil for _, win in ipairs(vim.fn.win_findbuf(bufnr)) do From a986048cb068227225f1ec9d397d430ae70868ee Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Sun, 29 Dec 2024 16:00:47 +0900 Subject: [PATCH 3/4] fix(treesitter.foldexpr): refresh in the buffers affected by OptionSet --- runtime/lua/vim/treesitter/_fold.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 9a866e67fa..a1cb7b0ba4 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -430,9 +430,13 @@ api.nvim_create_autocmd('OptionSet', { pattern = { 'foldminlines', 'foldnestmax' }, desc = 'Refresh treesitter folds', callback = function() - for bufnr, _ in pairs(foldinfos) do + local bufs = vim.v.option_type == 'local' and { api.nvim_get_current_buf() } + or vim.tbl_keys(foldinfos) + for _, bufnr in ipairs(bufs) do foldinfos[bufnr] = FoldInfo.new() - compute_folds_levels(bufnr, foldinfos[bufnr]) + api.nvim_buf_call(bufnr, function() + compute_folds_levels(bufnr, foldinfos[bufnr]) + end) foldinfos[bufnr]:foldupdate(bufnr, 0, api.nvim_buf_line_count(bufnr)) end end, From a3cc513b672e2f4cc5ec57c6a5f63199e8d53df8 Mon Sep 17 00:00:00 2001 From: Igor Date: Sun, 29 Dec 2024 12:23:24 -0300 Subject: [PATCH 4/4] fix(treesitter.foldexpr): only refresh valid buffers Problem: autocmd to refresh folds always uses the current buffer if the option type is local. However, the current buffer may not have a parser, and thus the assert that checks for a parser could fail. Solution: check if the foldinfo contains the buffer, and only refresh if so. --- runtime/lua/vim/treesitter/_fold.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index a1cb7b0ba4..c43857197e 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -430,8 +430,10 @@ api.nvim_create_autocmd('OptionSet', { pattern = { 'foldminlines', 'foldnestmax' }, desc = 'Refresh treesitter folds', callback = function() - local bufs = vim.v.option_type == 'local' and { api.nvim_get_current_buf() } - or vim.tbl_keys(foldinfos) + local buf = api.nvim_get_current_buf() + local bufs = vim.v.option_type == 'global' and vim.tbl_keys(foldinfos) + or foldinfos[buf] and { buf } + or {} for _, bufnr in ipairs(bufs) do foldinfos[bufnr] = FoldInfo.new() api.nvim_buf_call(bufnr, function()