# HG changeset patch # User Christian Brabandt # Date 1695234606 -7200 # Node ID 1f3bcb7f3bd05edeab8f492c5793e3dde39934bc # Parent 63ab2d31e023aaacbdaedccc7a9d0cfa2679ad33 patch 9.0.1921: not possible to use the jumplist like a stack Commit: https://github.com/vim/vim/commit/87018255e3ad0f4dfa03e20318836d24af721caf Author: Yegappan Lakshmanan Date: Wed Sep 20 20:20:04 2023 +0200 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 Co-authored-by: Yegappan Lakshmanan Co-authored-by: butwerenotthereyet <58348703+butwerenotthereyet@users.noreply.github.com> diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -1135,7 +1135,46 @@ When you split a window, the jumplist wi If you have included the ' item in the 'viminfo' option the jumplist will be stored in the viminfo file and restored when starting Vim. + *jumplist-stack* +When 'jumpoptions' option includes "stack", the jumplist behaves like the tag +stack. When jumping to a new location from the middle of the jumplist, the +locations after the current position will be discarded. With this option set +you can move through a tree of jump locations. When going back up a branch and +then down another branch, CTRL-O still takes you further up the tree. +Given a jumplist like the following in which CTRL-O has been used to move back +three times to location X: > + + jump line col file/text + 2 1260 8 mark.c <-- location X-2 + 1 685 0 eval.c <-- location X-1 + > 0 462 36 eval.c <-- location X + 1 479 39 eval.c + 2 213 2 mark.c + 3 181 0 mark.c +< +jumping to (new) location Y results in the locations after the current +locations being removed: > + + jump line col file/text + 3 1260 8 mark.c <-- location X-2 + 2 685 0 eval.c <-- location X-1 + 1 462 36 eval.c <-- location X + > +< + +Then, when yet another location Z is jumped to, the new location Y appears +directly after location X in the jumplist and location X remains in the same +position relative to the locations (X-1, X-2, etc., ...) that had been before +it prior to the original jump from X to Y: > + + jump line col file/text + 4 1260 8 mark.c <-- location X-2 + 3 685 0 eval.c <-- location X-1 + 2 462 36 eval.c <-- location X + 1 100 0 buffer.c <-- location Y + > +< CHANGE LIST JUMPS *changelist* *change-list-jumps* *E664* When making a change the cursor position is remembered. One position is diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4899,6 +4899,16 @@ A jump table for the options with a shor Otherwise only one space is inserted. NOTE: This option is set when 'compatible' is set. + *'jumpoptions'* *'jop'* +'jumpoptions' 'jop' string (default "") + global + List of words that change the behavior of the |jumplist|. + stack Make the jumplist behave like the tagstack. + Relative location of entries in the jumplist is + preserved at the cost of discarding subsequent entries + when navigating backwards in the jumplist and then + jumping to a location. |jumplist-stack| + *'key'* 'key' string (default "") local to buffer diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -772,6 +772,7 @@ Short explanation of each option: *opti 'iskeyword' 'isk' characters included in keywords 'isprint' 'isp' printable characters 'joinspaces' 'js' two spaces after a period with a join command +'jumpoptions' 'jop' specifies how jumping is done 'key' encryption key 'keymap' 'kmp' name of a keyboard mapping 'keymodel' 'km' enable starting/stopping selection with keys diff --git a/runtime/doc/tags b/runtime/doc/tags --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -433,7 +433,9 @@ 'isp' options.txt /*'isp'* 'isprint' options.txt /*'isprint'* 'joinspaces' options.txt /*'joinspaces'* +'jop' options.txt /*'jop'* 'js' options.txt /*'js'* +'jumpoptions' options.txt /*'jumpoptions'* 'key' options.txt /*'key'* 'keymap' options.txt /*'keymap'* 'keymodel' options.txt /*'keymodel'* @@ -8300,6 +8302,7 @@ json_encode() builtin.txt /*json_encode( jtags tagsrch.txt /*jtags* jump-motions motion.txt /*jump-motions* jumplist motion.txt /*jumplist* +jumplist-stack motion.txt /*jumplist-stack* jumpto-diffs diff.txt /*jumpto-diffs* k motion.txt /*k* kcc uganda.txt /*kcc* diff --git a/src/mark.c b/src/mark.c --- a/src/mark.c +++ b/src/mark.c @@ -146,6 +146,16 @@ setpcmark(void) 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 @@ cleanup_jumplist(win_T *wp, int loadfile { int i; int from, to; + int mustfree; if (loadfiles) { @@ -1314,10 +1325,18 @@ cleanup_jumplist(win_T *wp, int loadfile && wp->w_jumplist[i].fmark.mark.lnum == wp->w_jumplist[from].fmark.mark.lnum) break; - if (i >= wp->w_jumplistlen) // no duplicate + 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]; - else - vim_free(wp->w_jumplist[from].fname); } if (wp->w_jumplistidx == wp->w_jumplistlen) wp->w_jumplistidx = to; diff --git a/src/option.h b/src/option.h --- a/src/option.h +++ b/src/option.h @@ -601,6 +601,9 @@ EXTERN char_u *p_fp; // 'formatprg' 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 --- a/src/optiondefs.h +++ b/src/optiondefs.h @@ -1459,6 +1459,9 @@ static struct vimoption options[] = {"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 --- a/src/optionstr.c +++ b/src/optionstr.c @@ -35,6 +35,7 @@ static char *(p_cm_values[]) = {"zip", " #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 @@ didset_string_options(void) (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 @@ did_set_isopt(optset_T *args) 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 --- a/src/proto/optionstr.pro +++ b/src/proto/optionstr.pro @@ -64,6 +64,7 @@ char *did_set_highlight(optset_T *args); 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 --- a/src/testdir/gen_opt_test.vim +++ b/src/testdir/gen_opt_test.vim @@ -112,6 +112,7 @@ let test_values = { \ '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 --- a/src/testdir/test_jumplist.vim +++ b/src/testdir/test_jumplist.vim @@ -98,4 +98,68 @@ d 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 \\" + 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 --- a/src/version.c +++ b/src/version.c @@ -700,6 +700,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1921, +/**/ 1920, /**/ 1919,