patch 9.1.0572: cannot specify tab page closing behaviour

Problem:  cannot specify tab page closing behaviour
          (Gianluca Pacchiella)
Solution: Add the 'tabclose' option (LemonBoy).

fixes: #5967
closes: #15204

Signed-off-by: LemonBoy <thatlemon@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/option.h b/src/option.h
index 630d7bf..e84f7f9 100644
--- a/src/option.h
+++ b/src/option.h
@@ -955,7 +955,6 @@
 EXTERN char_u	*p_su;		// 'suffixes'
 EXTERN char_u	*p_sws;		// 'swapsync'
 EXTERN char_u	*p_swb;		// 'switchbuf'
-EXTERN char_u	*p_spk;		// 'splitkeep'
 EXTERN unsigned	swb_flags;
 // Keep in sync with p_swb_values in optionstr.c
 #define SWB_USEOPEN		0x001
@@ -964,9 +963,14 @@
 #define SWB_NEWTAB		0x008
 #define SWB_VSPLIT		0x010
 #define SWB_USELAST		0x020
+EXTERN char_u	*p_spk;		// 'splitkeep'
 #ifdef FEAT_SYN_HL
 EXTERN char_u	*p_syn;		// 'syntax'
 #endif
+EXTERN char_u	*p_tcl;		// 'tabclose'
+EXTERN unsigned	tcl_flags;	// flags from 'tabclose'
+#define TCL_LEFT		0x001
+#define TCL_USELAST		0x002
 EXTERN long	p_ts;		// 'tabstop'
 EXTERN int	p_tbs;		// 'tagbsearch'
 EXTERN char_u	*p_tc;		// 'tagcase'
diff --git a/src/optiondefs.h b/src/optiondefs.h
index 617f3ce..bf3ed35 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -2462,6 +2462,9 @@
 			    {(char_u *)0L, (char_u *)0L}
 #endif
 			    SCTX_INIT},
+    {"tabclose",    "tcl",  P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP,
+			    (char_u *)&p_tcl, PV_NONE, did_set_tabclose, expand_set_tabclose,
+			    {(char_u *)"", (char_u *)0L} SCTX_INIT},
     {"tabline",	    "tal",  P_STRING|P_VI_DEF|P_RALL|P_MLE,
 #ifdef FEAT_STL_OPT
 			    (char_u *)&p_tal, PV_NONE, did_set_tabline, NULL,
diff --git a/src/optionstr.c b/src/optionstr.c
index 417f785..b6249a2 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -81,6 +81,8 @@
 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.h
+static char *(p_tcl_values[]) = {"left", "uselast", NULL};
 #if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
 static char *(p_toolbar_values[]) = {"text", "icons", "tooltips", "horiz", NULL};
 #endif
@@ -166,6 +168,7 @@
     (void)opt_strings_flags(p_tbis, p_tbis_values, &tbis_flags, FALSE);
 #endif
     (void)opt_strings_flags(p_swb, p_swb_values, &swb_flags, TRUE);
+    (void)opt_strings_flags(p_tcl, p_tcl_values, &tcl_flags, TRUE);
 }
 
 #if defined(FEAT_EVAL) || defined(PROTO)
@@ -3669,6 +3672,26 @@
 	    matches);
 }
 
+/*
+ * The 'tabclose' option is changed.
+ */
+    char *
+did_set_tabclose(optset_T *args UNUSED)
+{
+    return did_set_opt_flags(p_tcl, p_tcl_values, &tcl_flags, TRUE);
+}
+
+    int
+expand_set_tabclose(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_tcl_values,
+	    ARRAY_LENGTH(p_tcl_values) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if defined(FEAT_STL_OPT) || defined(PROTO)
 /*
  * The 'tabline' option is changed.
diff --git a/src/proto/option.pro b/src/proto/option.pro
index 69463d4..1659131 100644
--- a/src/proto/option.pro
+++ b/src/proto/option.pro
@@ -69,6 +69,7 @@
 char *did_set_smoothscroll(optset_T *args);
 char *did_set_spell(optset_T *args);
 char *did_set_swapfile(optset_T *args);
+char *did_set_tabclose(optset_T *args);
 char *did_set_termguicolors(optset_T *args);
 char *did_set_terse(optset_T *args);
 char *did_set_textauto(optset_T *args);
diff --git a/src/proto/optionstr.pro b/src/proto/optionstr.pro
index 340ffc4..39c40f3 100644
--- a/src/proto/optionstr.pro
+++ b/src/proto/optionstr.pro
@@ -156,6 +156,7 @@
 int expand_set_swapsync(optexpand_T *args, int *numMatches, char_u ***matches);
 char *did_set_switchbuf(optset_T *args);
 int expand_set_switchbuf(optexpand_T *args, int *numMatches, char_u ***matches);
+int expand_set_tabclose(optexpand_T *args, int *numMatches, char_u ***matches);
 char *did_set_tabline(optset_T *args);
 char *did_set_tagcase(optset_T *args);
 int expand_set_tagcase(optexpand_T *args, int *numMatches, char_u ***matches);
diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim
index 8cca2b9..7674714 100644
--- a/src/testdir/gen_opt_test.vim
+++ b/src/testdir/gen_opt_test.vim
@@ -144,6 +144,7 @@
       \ 'splitkeep': [['cursor', 'screen', 'topline'], ['xxx']],
       \ 'swapsync': [['', 'sync', 'fsync'], ['xxx']],
       \ 'switchbuf': [['', 'useopen', 'split,newtab'], ['xxx']],
+      \ 'tabclose': [['', 'left', 'left,uselast'], ['xxx']],
       \ 'tagcase': [['smart', 'match'], ['', 'xxx', 'smart,match']],
       \ 'term': [[], []],
       \ 'termguicolors': [[], []],
diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim
index fbfbaae..02f5d7e 100644
--- a/src/testdir/test_options.vim
+++ b/src/testdir/test_options.vim
@@ -548,6 +548,9 @@
   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
@@ -1407,7 +1410,8 @@
   set nowrite
   call assert_fails('write Xwrfile', 'E142:')
   set write
-  close!
+  " close swapfile
+  bw!
 endfunc
 
 " Test for 'buftype' option
diff --git a/src/testdir/test_tabpage.vim b/src/testdir/test_tabpage.vim
index 3624790..1a40567 100644
--- a/src/testdir/test_tabpage.vim
+++ b/src/testdir/test_tabpage.vim
@@ -965,6 +965,64 @@
   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
diff --git a/src/version.c b/src/version.c
index 194099b..fc1fb24 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    572,
+/**/
     571,
 /**/
     570,
diff --git a/src/window.c b/src/window.c
index 7322905..c87cce0 100644
--- a/src/window.c
+++ b/src/window.c
@@ -3782,15 +3782,23 @@
     static tabpage_T *
 alt_tabpage(void)
 {
-    tabpage_T	*tp;
+    tabpage_T	*tp = NULL;
+    int		forward;
 
-    // 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.
-    for (tp = first_tabpage; tp->tp_next != curtab; tp = tp->tp_next)
-	;
+    // Use the previous tab page, if possible.
+    forward = curtab->tp_next != NULL &&
+	    ((tcl_flags & TCL_LEFT) == 0 || curtab == first_tabpage);
+
+    if (forward)
+	tp = curtab->tp_next;
+    else
+	for (tp = first_tabpage; tp->tp_next != curtab; tp = tp->tp_next)
+	    ;
+
     return tp;
 }