From b1aa8f5eb8a5407e869335e9987b73f8515c37e5 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 13 Jul 2024 08:56:58 +0800 Subject: [PATCH] vim-patch:9.1.0572: cannot specify tab page closing behaviour (#29682) Problem: cannot specify tab page closing behaviour (Gianluca Pacchiella) Solution: Add the 'tabclose' option (LemonBoy). fixes: vim/vim#5967 closes: vim/vim#15204 https://github.com/vim/vim/commit/5247b0b92e191a046b034171a3b34031e317735f Co-authored-by: LemonBoy --- runtime/doc/news.txt | 1 + runtime/doc/options.txt | 13 +++++++ runtime/doc/quickref.txt | 1 + runtime/doc/tabpage.txt | 3 +- runtime/lua/vim/_meta/options.lua | 16 +++++++++ runtime/optwin.vim | 4 ++- src/nvim/option_vars.h | 6 +++- src/nvim/options.lua | 24 +++++++++++++ src/nvim/optionstr.c | 20 ++++++++++- src/nvim/window.c | 18 +++++++--- test/old/testdir/test_options.vim | 8 +++-- test/old/testdir/test_tabpage.vim | 58 +++++++++++++++++++++++++++++++ 12 files changed, 161 insertions(+), 11 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 604515a808..39b3a506ca 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -141,6 +141,7 @@ LUA OPTIONS • 'completeopt' flag "fuzzy" enables |fuzzy-matching| during |ins-completion|. +• 'tabclose' controls which tab page to focus when closing a tab page. PERFORMANCE diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 4bb589fb2c..caa6649b17 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6343,6 +6343,19 @@ A jump table for the options with a short description can be found at |Q_op|. 'S' flag in 'cpoptions'. Only normal file name characters can be used, `/\*?[|<>` are illegal. + *'tabclose'* *'tcl'* +'tabclose' 'tcl' string (default "") + global + This option controls the behavior when closing tab pages (e.g., using + |:tabclose|). When empty Vim goes to the next (right) tab page. + + Possible values (comma-separated list): + left If included, go to the previous tab page instead of + the next one. + uselast If included, go to the previously used tab page if + possible. This option takes precedence over the + others. + *'tabline'* *'tal'* 'tabline' 'tal' string (default "") global diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index 5eea9baa20..d77750b485 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -886,6 +886,7 @@ Short explanation of each option: *option-list* 'switchbuf' 'swb' sets behavior when switching to another buffer 'synmaxcol' 'smc' maximum column to find syntax items 'syntax' 'syn' syntax to be loaded for current buffer +'tabclose' 'tcl' which tab page to focus when closing a tab 'tabline' 'tal' custom format for the console tab pages line 'tabpagemax' 'tpm' maximum number of tab pages for |-p| and "tab all" 'tabstop' 'ts' number of spaces that in file uses diff --git a/runtime/doc/tabpage.txt b/runtime/doc/tabpage.txt index 2f50e31ee5..7bfa36e8ab 100644 --- a/runtime/doc/tabpage.txt +++ b/runtime/doc/tabpage.txt @@ -135,7 +135,8 @@ something else. :tabclose $ " close the last tab page :tabclose # " close the last accessed tab page -When a tab is closed the next tab page will become the current one. +When a tab is closed the next tab page will become the current one. This +behaviour can be customized using the 'tabclose' option. *:tabo* *:tabonly* :tabo[nly][!] Close all other tab pages. diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 40c12d94fd..332dbda6e9 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -6837,6 +6837,22 @@ vim.o.syn = vim.o.syntax vim.bo.syntax = vim.o.syntax vim.bo.syn = vim.bo.syntax +--- This option controls the behavior when closing tab pages (e.g., using +--- `:tabclose`). When empty Vim goes to the next (right) tab page. +--- +--- Possible values (comma-separated list): +--- left If included, go to the previous tab page instead of +--- the next one. +--- uselast If included, go to the previously used tab page if +--- possible. This option takes precedence over the +--- others. +--- +--- @type string +vim.o.tabclose = "" +vim.o.tcl = vim.o.tabclose +vim.go.tabclose = vim.o.tabclose +vim.go.tcl = vim.go.tabclose + --- When non-empty, this option determines the content of the tab pages --- line at the top of the Vim window. When empty Vim will use a default --- tab pages line. See `setting-tabline` for more info. diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 3b874f4cda..f168218c91 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1,7 +1,7 @@ " These commands create the option window. " " Maintainer: The Vim Project -" Last Change: 2024 Jun 05 +" Last Change: 2024 Jul 12 " Former Maintainer: Bram Moolenaar " If there already is an option window, jump to that one. @@ -507,6 +507,8 @@ endif call Header(gettext("multiple tab pages")) call AddOption("showtabline", gettext("0, 1 or 2; when to use a tab pages line")) call append("$", " \tset stal=" . &stal) +call AddOption("tabclose", gettext("behaviour when closing tab pages: left, uselast or empty")) +call append("$", " \tset tcl=" . &tcl) call AddOption("tabpagemax", gettext("maximum number of tab pages to open for -p and \"tab all\"")) call append("$", " \tset tpm=" . &tpm) call AddOption("tabline", gettext("custom tab pages line")) diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index 3326c0879a..b6b307befb 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -694,7 +694,6 @@ EXTERN unsigned tpf_flags; ///< flags from 'termpastefilter' EXTERN char *p_tfu; ///< 'tagfunc' EXTERN char *p_spc; ///< 'spellcapcheck' EXTERN char *p_spf; ///< 'spellfile' -EXTERN char *p_spk; ///< 'splitkeep' EXTERN char *p_spl; ///< 'spelllang' EXTERN char *p_spo; ///< 'spelloptions' EXTERN unsigned spo_flags; @@ -711,7 +710,12 @@ EXTERN unsigned swb_flags; #define SWB_NEWTAB 0x008 #define SWB_VSPLIT 0x010 #define SWB_USELAST 0x020 +EXTERN char *p_spk; ///< 'splitkeep' EXTERN char *p_syn; ///< 'syntax' +EXTERN char *p_tcl; ///< 'tabclose' +EXTERN unsigned tcl_flags; ///< flags from 'tabclose' +#define TCL_LEFT 0x001 +#define TCL_USELAST 0x002 EXTERN OptInt p_ts; ///< 'tabstop' EXTERN int p_tbs; ///< 'tagbsearch' EXTERN char *p_tc; ///< 'tagcase' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 6e317a426c..6345ef5ada 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -8506,6 +8506,30 @@ return { type = 'string', varname = 'p_syn', }, + { + abbreviation = 'tcl', + cb = 'did_set_tabclose', + defaults = { if_true = '' }, + deny_duplicates = true, + desc = [=[ + This option controls the behavior when closing tab pages (e.g., using + |:tabclose|). When empty Vim goes to the next (right) tab page. + + Possible values (comma-separated list): + left If included, go to the previous tab page instead of + the next one. + uselast If included, go to the previously used tab page if + possible. This option takes precedence over the + others. + ]=], + expand_cb = 'expand_set_tabclose', + full_name = 'tabclose', + list = 'onecomma', + scope = { 'global' }, + short_desc = N_('which tab page to focus when closing a tab'), + type = 'string', + varname = 'p_tcl', + }, { abbreviation = 'tal', cb = 'did_set_tabline', diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 1a4c142fdd..8e853b6ee0 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -98,11 +98,13 @@ static char *(p_ssop_values[]) = { "buffers", "winpos", "resize", "winsize", "lo "options", "help", "blank", "globals", "slash", "unix", "sesdir", "curdir", "folds", "cursor", "tabpages", "terminal", "skiprtp", NULL }; -// Keep in sync with SWB_ flags in option_defs.h +// Keep in sync with SWB_ flags in option_vars.h static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vsplit", "uselast", NULL }; static char *(p_spk_values[]) = { "cursor", "screen", "topline", NULL }; static char *(p_tc_values[]) = { "followic", "ignore", "match", "followscs", "smart", NULL }; +// Keep in sync with TCL_ flags in option_vars.h +static char *(p_tcl_values[]) = { "left", "uselast", NULL }; static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL }; // Note: Keep this in sync with check_opt_wim() static char *(p_wim_values[]) = { "full", "longest", "list", "lastused", NULL }; @@ -169,6 +171,7 @@ void didset_string_options(void) opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true); opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); opt_strings_flags(p_swb, p_swb_values, &swb_flags, true); + opt_strings_flags(p_tcl, p_tcl_values, &tcl_flags, true); opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); } @@ -2207,6 +2210,21 @@ int expand_set_switchbuf(optexpand_T *args, int *numMatches, char ***matches) matches); } +/// The 'tabclose' option is changed. +const char *did_set_tabclose(optset_T *args FUNC_ATTR_UNUSED) +{ + return did_set_opt_flags(p_tcl, p_tcl_values, &tcl_flags, true); +} + +int expand_set_tabclose(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_tcl_values, + ARRAY_SIZE(p_tcl_values) - 1, + numMatches, + matches); +} + /// The 'tabline' option is changed. const char *did_set_tabline(optset_T *args) { diff --git a/src/nvim/window.c b/src/nvim/window.c index 9203dd1bdb..132bf9ee60 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -3456,14 +3456,22 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp) // Return the tabpage that will be used if the current one is closed. static tabpage_T *alt_tabpage(void) { - // Use the next tab page if possible. - if (curtab->tp_next != NULL) { - return curtab->tp_next; + // Use the last accessed tab page, if possible. + if ((tcl_flags & TCL_USELAST) && valid_tabpage(lastused_tabpage)) { + return lastused_tabpage; } - // Find the last but one tab page. + // Use the previous tab page, if possible. + bool forward = curtab->tp_next != NULL + && ((tcl_flags & TCL_LEFT) == 0 || curtab == first_tabpage); + tabpage_T *tp; - for (tp = first_tabpage; tp->tp_next != curtab; tp = tp->tp_next) {} + if (forward) { + tp = curtab->tp_next; + } else { + for (tp = first_tabpage; tp->tp_next != curtab; tp = tp->tp_next) {} + } + return tp; } diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim index f7eace59c2..26b4f64487 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -558,6 +558,9 @@ func Test_set_completion_string_values() " call assert_equal('sync', getcompletion('set swapsync=', 'cmdline')[1]) call assert_equal('usetab', getcompletion('set switchbuf=', 'cmdline')[1]) call assert_equal('ignore', getcompletion('set tagcase=', 'cmdline')[1]) + if exists('+tabclose') + call assert_equal('left uselast', join(sort(getcompletion('set tabclose=', 'cmdline'))), ' ') + endif if exists('+termwintype') call assert_equal('conpty', getcompletion('set termwintype=', 'cmdline')[1]) endif @@ -1377,9 +1380,10 @@ func Test_write() new call setline(1, ['L1']) set nowrite - call assert_fails('write Xfile', 'E142:') + call assert_fails('write Xwrfile', 'E142:') set write - close! + " close swapfile + bw! endfunc " Test for 'buftype' option diff --git a/test/old/testdir/test_tabpage.vim b/test/old/testdir/test_tabpage.vim index 2bd2907a55..482da2de7f 100644 --- a/test/old/testdir/test_tabpage.vim +++ b/test/old/testdir/test_tabpage.vim @@ -967,6 +967,64 @@ func Test_tabpage_alloc_failure() call assert_equal(1, tabpagenr('$')) endfunc +func Test_tabpage_tabclose() + " Default behaviour, move to the right. + call s:reconstruct_tabpage_for_test(6) + norm! 4gt + setl tcl= + tabclose + call assert_equal("n3", bufname()) + + " Move to the left. + call s:reconstruct_tabpage_for_test(6) + norm! 4gt + setl tcl=left + tabclose + call assert_equal("n1", bufname()) + + " Move to the last used tab page. + call s:reconstruct_tabpage_for_test(6) + norm! 5gt + norm! 2gt + setl tcl=uselast + tabclose + call assert_equal("n3", bufname()) + + " Same, but the last used tab page is invalid. Move to the right. + call s:reconstruct_tabpage_for_test(6) + norm! 5gt + norm! 3gt + setl tcl=uselast + tabclose 5 + tabclose! + call assert_equal("n2", bufname()) + + " Same, but the last used tab page is invalid. Move to the left. + call s:reconstruct_tabpage_for_test(6) + norm! 5gt + norm! 3gt + setl tcl=uselast,left + tabclose 5 + tabclose! + call assert_equal("n0", bufname()) + + " Move left when moving right is not possible. + call s:reconstruct_tabpage_for_test(6) + setl tcl= + norm! 6gt + tabclose + call assert_equal("n3", bufname()) + + " Move right when moving left is not possible. + call s:reconstruct_tabpage_for_test(6) + setl tcl=left + norm! 1gt + tabclose + call assert_equal("n0", bufname()) + + setl tcl& +endfunc + " this was giving ml_get errors func Test_tabpage_last_line() enew