patch 9.0.1921: not possible to use the jumplist like a stack

Problem:  not possible to use the jumplist like a stack
Solution: Add the 'jumpoptions' setting to make the jumplist
          a stack.

Add an option for using jumplist like tag stack

related: #7738
closes: #13134

ported from NeoVim:

- https://neovim.io/doc/user/motion.html#jumplist-stack
- neovim/neovim@39094b3
- neovim/neovim#11530
- https://vi.stackexchange.com/questions/18344/how-to-change-jumplist-behavior

Based on the feedback in the previous PR, it looks like many people like
this option.

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Co-authored-by: butwerenotthereyet <58348703+butwerenotthereyet@users.noreply.github.com>
diff --git a/src/mark.c b/src/mark.c
index b3c644b..22e3c62 100644
--- a/src/mark.c
+++ b/src/mark.c
@@ -146,6 +146,16 @@
     curwin->w_prev_pcmark = curwin->w_pcmark;
     curwin->w_pcmark = curwin->w_cursor;
 
+    if (jop_flags & JOP_STACK)
+    {
+	// jumpoptions=stack: if we're somewhere in the middle of the jumplist
+	// discard everything after the current index.
+	if (curwin->w_jumplistidx < curwin->w_jumplistlen - 1)
+	    // Discard the rest of the jumplist by cutting the length down to
+	    // contain nothing beyond the current index.
+	    curwin->w_jumplistlen = curwin->w_jumplistidx + 1;
+    }
+
     // If jumplist is full: remove oldest entry
     if (++curwin->w_jumplistlen > JUMPLISTSIZE)
     {
@@ -1288,6 +1298,7 @@
 {
     int	    i;
     int	    from, to;
+    int	    mustfree;
 
     if (loadfiles)
     {
@@ -1314,10 +1325,18 @@
 		    && wp->w_jumplist[i].fmark.mark.lnum
 				  == wp->w_jumplist[from].fmark.mark.lnum)
 		break;
-	if (i >= wp->w_jumplistlen)	    // no duplicate
-	    wp->w_jumplist[to++] = wp->w_jumplist[from];
-	else
+	if (i >= wp->w_jumplistlen)	// not duplicate
+	    mustfree = FALSE;
+	else if (i > from + 1)		// non-adjacent duplicate
+	    // jumpoptions=stack: remove duplicates only when adjacent.
+	    mustfree = !(jop_flags & JOP_STACK);
+	else				// adjacent duplicate
+	    mustfree = TRUE;
+
+	if (mustfree)
 	    vim_free(wp->w_jumplist[from].fname);
+	else
+	    wp->w_jumplist[to++] = wp->w_jumplist[from];
     }
     if (wp->w_jumplistidx == wp->w_jumplistlen)
 	wp->w_jumplistidx = to;
diff --git a/src/option.h b/src/option.h
index 3bd2eb9..6874830 100644
--- a/src/option.h
+++ b/src/option.h
@@ -601,6 +601,9 @@
 EXTERN int	p_fs;		// 'fsync'
 #endif
 EXTERN int	p_gd;		// 'gdefault'
+EXTERN char_u	*p_jop;		// 'jumpoptions'
+EXTERN unsigned	jop_flags;	//
+#define JOP_STACK		0x001
 #ifdef FEAT_PROP_POPUP
 # ifdef FEAT_QUICKFIX
 EXTERN char_u	*p_cpp;		// 'completepopup'
diff --git a/src/optiondefs.h b/src/optiondefs.h
index aeb3b57..4f3a152 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -1459,6 +1459,9 @@
     {"joinspaces",  "js",   P_BOOL|P_VI_DEF|P_VIM,
 			    (char_u *)&p_js, PV_NONE, NULL,
 			    {(char_u *)TRUE, (char_u *)0L} SCTX_INIT},
+    {"jumpoptions", "jop",  P_STRING|P_VI_DEF|P_VIM|P_ONECOMMA|P_NODUP,
+			    (char_u *)&p_jop, PV_NONE, did_set_jumpoptions,
+			    {(char_u *)"", (char_u *)0L} SCTX_INIT},
     {"key",	    NULL,   P_STRING|P_ALLOCED|P_VI_DEF|P_NO_MKRC,
 #ifdef FEAT_CRYPT
 			    (char_u *)&p_key, PV_KEY, did_set_cryptkey,
diff --git a/src/optionstr.c b/src/optionstr.c
index 31e30e1..aeeab27 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -35,6 +35,7 @@
 #endif
 static char *(p_cmp_values[]) = {"internal", "keepascii", NULL};
 static char *(p_dy_values[]) = {"lastline", "truncate", "uhex", NULL};
+static char *(p_jop_values[]) = {"stack", NULL};
 #ifdef FEAT_FOLDING
 static char *(p_fdo_values[]) = {"all", "block", "hor", "mark", "percent",
 				 "quickfix", "search", "tag", "insert",
@@ -120,6 +121,7 @@
     (void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, TRUE);
 #endif
     (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, TRUE);
+    (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, TRUE);
     (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, FALSE);
     (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, TRUE);
 #if defined(UNIX) || defined(VMS)
@@ -1852,6 +1854,18 @@
     return NULL;
 }
 
+/*
+ * The 'jumpoptions' option is changed.
+ */
+    char *
+did_set_jumpoptions(optset_T *args)
+{
+    if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, TRUE) != OK)
+	return e_invalid_argument;
+
+    return NULL;
+}
+
 #if defined(FEAT_KEYMAP) || defined(PROTO)
 /*
  * The 'keymap' option is changed.
diff --git a/src/proto/optionstr.pro b/src/proto/optionstr.pro
index db13f10..c1253b1 100644
--- a/src/proto/optionstr.pro
+++ b/src/proto/optionstr.pro
@@ -64,6 +64,7 @@
 char *did_set_iconstring(optset_T *args);
 char *did_set_imactivatekey(optset_T *args);
 char *did_set_isopt(optset_T *args);
+char *did_set_jumpoptions(optset_T *args);
 char *did_set_keymap(optset_T *args);
 char *did_set_keymodel(optset_T *args);
 char *did_set_keyprotocol(optset_T *args);
diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim
index f205d43..a195bca 100644
--- a/src/testdir/gen_opt_test.vim
+++ b/src/testdir/gen_opt_test.vim
@@ -112,6 +112,7 @@
       \ 'isident': [['', '@', '@,48-52'], ['xxx', '@48']],
       \ 'iskeyword': [['', '@', '@,48-52'], ['xxx', '@48']],
       \ 'isprint': [['', '@', '@,48-52'], ['xxx', '@48']],
+      \ 'jumpoptions': [['', 'stack'], ['xxx']],
       \ 'keymap': [['', 'accents'], ['xxx']],
       \ 'keymodel': [['', 'startsel', 'startsel,stopsel'], ['xxx']],
       \ 'keyprotocol': [['', 'xxx:none', 'yyy:mok2', 'zzz:kitty'],
diff --git a/src/testdir/test_jumplist.vim b/src/testdir/test_jumplist.vim
index 4b5e321..8fbf39f 100644
--- a/src/testdir/test_jumplist.vim
+++ b/src/testdir/test_jumplist.vim
@@ -98,4 +98,68 @@
   bwipe!
 endfunc
 
+" Test for 'jumpoptions'
+func Test_jumpoptions()
+  new
+  call setline(1, range(1, 200))
+  clearjumps
+  set jumpoptions=stack
+
+  " Jump around to add some locations to the jump list.
+  normal 10G
+  normal 20G
+  normal 30G
+  normal 40G
+  normal 50G
+  let bnr = bufnr()
+
+  " discards the tail when navigating from the middle
+  exe "normal \<C-O>\<C-O>"
+  call assert_equal([
+        \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 40, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 50, 'bufnr': bnr, 'col': 0, 'coladd': 0}
+        \ ], 3], getjumplist())
+
+  " new jump location is added immediately after the last one
+  normal 90G
+  call assert_equal([
+        \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \ ], 4], getjumplist())
+
+  " does not add the same location twice adjacently
+  normal 60G
+  normal 60G
+  call assert_equal([
+        \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 90, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 60, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \ ], 6], getjumplist())
+
+  " does add the same location twice non adjacently
+  normal 10G
+  normal 20G
+  call assert_equal([
+        \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 90, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 60, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \  {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+        \ ], 7], getjumplist())
+
+  set jumpoptions&
+  %bw!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 5abc4f7..5f36234 100644
--- a/src/version.c
+++ b/src/version.c
@@ -700,6 +700,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1921,
+/**/
     1920,
 /**/
     1919,