From e7020306a19a5211c834966ec067fff3b981bdb9 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 30 Jun 2024 06:40:31 +0800 Subject: [PATCH] feat(jumplist): allow opting out of removing unloaded buffers (#29347) Problem: Cannot opt out of removing unloaded buffers from the jumplist. Solution: Only enable that with "unload" flag in 'jumpoptions'. --- runtime/doc/options.txt | 5 ++- runtime/doc/vim_diff.txt | 2 ++ runtime/lua/vim/_meta/options.lua | 5 ++- src/nvim/buffer.c | 47 ++++++++++++++++++---------- src/nvim/option_vars.h | 1 + src/nvim/options.lua | 5 ++- src/nvim/optionstr.c | 2 +- test/functional/editor/jump_spec.lua | 46 ++++++++++++++++++++++++++- test/old/testdir/setup.vim | 1 + 9 files changed, 92 insertions(+), 22 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 1b71050620..70d0bd4369 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3619,7 +3619,7 @@ A jump table for the options with a short description can be found at |Q_op|. Otherwise only one space is inserted. *'jumpoptions'* *'jop'* -'jumpoptions' 'jop' string (default "") +'jumpoptions' 'jop' string (default "unload") global List of words that change the behavior of the |jumplist|. stack Make the jumplist behave like the tagstack. @@ -3632,6 +3632,9 @@ A jump table for the options with a short description can be found at |Q_op|. |alternate-file| or using |mark-motions| try to restore the |mark-view| in which the action occurred. + unload Remove unloaded buffers from the jumplist. + EXPERIMENTAL: this flag may change in the future. + *'keymap'* *'kmp'* 'keymap' 'kmp' string (default "") local to buffer diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index a51ffcf004..c1daf4a937 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -62,6 +62,7 @@ Defaults *nvim-defaults* - 'isfname' does not include ":" (on Windows). Drive letters are handled correctly without it. (Use |gF| for filepaths suffixed with ":line:col"). - 'joinspaces' is disabled +- 'jumpoptions' defaults to "unload" - 'langnoremap' is enabled - 'langremap' is disabled - 'laststatus' defaults to 2 (statusline is always shown) @@ -341,6 +342,7 @@ string options work. - 'inccommand' shows interactive results for |:substitute|-like commands and |:command-preview| commands - 'jumpoptions' "view" tries to restore the |mark-view| when moving through + "unload" removes unloaded buffer from the jumplist - the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|. - 'laststatus' global statusline support - 'mousescroll' amount to scroll by when scrolling with a mouse diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 902df8f7d6..fb04da14a5 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -3551,8 +3551,11 @@ vim.go.js = vim.go.joinspaces --- |alternate-file` or using `mark-motions` try to --- restore the `mark-view` in which the action occurred. --- +--- unload Remove unloaded buffers from the jumplist. +--- EXPERIMENTAL: this flag may change in the future. +--- --- @type string -vim.o.jumpoptions = "" +vim.o.jumpoptions = "unload" vim.o.jop = vim.o.jumpoptions vim.go.jumpoptions = vim.o.jumpoptions vim.go.jop = vim.go.jumpoptions diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 57245ce3f5..3812ff6b44 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1396,8 +1396,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit) // If the buffer to be deleted is not the current one, delete it here. if (buf != curbuf) { - // Remove the buffer to be deleted from the jump list. - buf_remove_from_jumplist(buf); + if (jop_flags & JOP_UNLOAD) { + // Remove the buffer to be deleted from the jump list. + buf_remove_from_jumplist(buf); + } close_windows(buf, false); @@ -1420,28 +1422,37 @@ int do_buffer(int action, int start, int dir, int count, int forceit) if (au_new_curbuf.br_buf != NULL && bufref_valid(&au_new_curbuf)) { buf = au_new_curbuf.br_buf; } else if (curwin->w_jumplistlen > 0) { - // Remove the current buffer from the jump list. - buf_remove_from_jumplist(curbuf); + if (jop_flags & JOP_UNLOAD) { + // Remove the current buffer from the jump list. + buf_remove_from_jumplist(curbuf); + } // It's possible that we removed all jump list entries, in that case we need to try another // approach if (curwin->w_jumplistlen > 0) { - // If the index is the same as the length, the current position was not yet added to the jump - // list. So we can safely go back to the last entry and search from there. - if (curwin->w_jumplistidx == curwin->w_jumplistlen) { - curwin->w_jumplistidx = curwin->w_jumplistlen - 1; - } - int jumpidx = curwin->w_jumplistidx; + if (jop_flags & JOP_UNLOAD) { + // If the index is the same as the length, the current position was not yet added to the + // jump list. So we can safely go back to the last entry and search from there. + if (jumpidx == curwin->w_jumplistlen) { + jumpidx = curwin->w_jumplistidx = curwin->w_jumplistlen - 1; + } + } else { + jumpidx--; + if (jumpidx < 0) { + jumpidx = curwin->w_jumplistlen - 1; + } + } + forward = jumpidx; - do { + while ((jop_flags & JOP_UNLOAD) || jumpidx != curwin->w_jumplistidx) { buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); if (buf != NULL) { - // Skip unlisted bufs. Also skip a quickfix + // Skip current and unlisted bufs. Also skip a quickfix // buffer, it might be deleted soon. - if (!buf->b_p_bl || bt_quickfix(buf)) { + if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf)) { buf = NULL; } else if (buf->b_ml.ml_mfp == NULL) { // skip unloaded buf, but may keep it for later @@ -1452,8 +1463,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } } if (buf != NULL) { // found a valid buffer: stop searching - curwin->w_jumplistidx = jumpidx; - update_jumplist = false; + if (jop_flags & JOP_UNLOAD) { + curwin->w_jumplistidx = jumpidx; + update_jumplist = false; + } break; } // advance to older entry in jump list @@ -1466,7 +1479,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) if (jumpidx == forward) { // List exhausted for sure break; } - } while (jumpidx != curwin->w_jumplistidx); + } } } @@ -3728,7 +3741,7 @@ void ex_buffer_all(exarg_T *eap) // Open the buffer in this window. swap_exists_action = SEA_DIALOG; - set_curbuf(buf, DOBUF_GOTO, false); + set_curbuf(buf, DOBUF_GOTO, !(jop_flags & JOP_UNLOAD)); if (!bufref_valid(&bufref)) { // Autocommands deleted the buffer. swap_exists_action = SEA_NONE; diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index 404e58661c..3326c0879a 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -540,6 +540,7 @@ EXTERN char *p_jop; ///< 'jumpooptions' EXTERN unsigned jop_flags; #define JOP_STACK 0x01 #define JOP_VIEW 0x02 +#define JOP_UNLOAD 0x04 EXTERN char *p_keymap; ///< 'keymap' EXTERN char *p_kp; ///< 'keywordprg' EXTERN char *p_km; ///< 'keymodel' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index efa2ea7dd9..365679261c 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -4495,7 +4495,7 @@ return { { abbreviation = 'jop', cb = 'did_set_jumpoptions', - defaults = { if_true = '' }, + defaults = { if_true = 'unload' }, deny_duplicates = true, desc = [=[ List of words that change the behavior of the |jumplist|. @@ -4508,6 +4508,9 @@ return { view When moving through the jumplist, |changelist|, |alternate-file| or using |mark-motions| try to restore the |mark-view| in which the action occurred. + + unload Remove unloaded buffers from the jumplist. + EXPERIMENTAL: this flag may change in the future. ]=], expand_cb = 'expand_set_jumpoptions', full_name = 'jumpoptions', diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 4cd830a2fc..cdbb8c2d6d 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -137,7 +137,7 @@ static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", "auto:3", "auto:4" "5", "6", "7", "8", "9", NULL }; static char *(p_spo_values[]) = { "camel", "noplainbuffer", NULL }; static char *(p_icm_values[]) = { "nosplit", "split", NULL }; -static char *(p_jop_values[]) = { "stack", "view", NULL }; +static char *(p_jop_values[]) = { "stack", "view", "unload", NULL }; static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", "line", "flush", NULL }; diff --git a/test/functional/editor/jump_spec.lua b/test/functional/editor/jump_spec.lua index 880831d9f8..6d2f75e7c5 100644 --- a/test/functional/editor/jump_spec.lua +++ b/test/functional/editor/jump_spec.lua @@ -194,7 +194,7 @@ describe("jumpoptions=stack behaves like 'tagstack'", function() end) end) -describe('buffer deletion', function() +describe('buffer deletion with jumpoptions+=unload', function() local base_file = 'Xtest-functional-buffer-deletion' local file1 = base_file .. '1' local file2 = base_file .. '2' @@ -227,6 +227,12 @@ describe('buffer deletion', function() command('edit ' .. file3) end) + after_each(function() + os.remove(file1) + os.remove(file2) + os.remove(file3) + end) + it('deletes jump list entries when the current buffer is deleted', function() command('edit ' .. file1) @@ -319,6 +325,44 @@ describe('buffer deletion', function() end) end) +describe('buffer deletion with jumpoptions-=unload', function() + local base_file = 'Xtest-functional-buffer-deletion' + local file1 = base_file .. '1' + local file2 = base_file .. '2' + local base_content = 'text' + local content1 = base_content .. '1' + local content2 = base_content .. '2' + + before_each(function() + clear() + command('clearjumps') + command('set jumpoptions-=unload') + + write_file(file1, content1, false, false) + write_file(file2, content2, false, false) + + command('edit ' .. file1) + command('edit ' .. file2) + end) + + after_each(function() + os.remove(file1) + os.remove(file2) + end) + + it('Ctrl-O reopens previous buffer with :bunload or :bdelete #28968', function() + eq(file2, fn.bufname('')) + command('bunload') + eq(file1, fn.bufname('')) + feed('') + eq(file2, fn.bufname('')) + command('bdelete') + eq(file1, fn.bufname('')) + feed('') + eq(file2, fn.bufname('')) + end) +end) + describe('jumpoptions=view', function() local file1 = 'Xtestfile-functional-editor-jumps' local file2 = 'Xtestfile-functional-editor-jumps-2' diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim index 8a5eeb3aab..2e4085ce03 100644 --- a/test/old/testdir/setup.vim +++ b/test/old/testdir/setup.vim @@ -13,6 +13,7 @@ if exists('s:did_load') set laststatus=1 set listchars=eol:$ set joinspaces + set jumpoptions= set mousemodel=extend set nohidden nosmarttab noautoindent noautoread noruler noshowcmd set nohlsearch noincsearch