root WRITEUP jumptree.vim

jumptree.vim

Undo tree semantics for the jumplist 2025-04-23

Project Files • GitHub Repo • jumptree.vim

Vim’s “stack” jumplist (when 'jumpoptions' includes stack) and classic jumplist (when 'jumpoptions' does not include stack) can be consolidated into a single jumplist that has the semantics of the undo tree. See the plugin’s readme for what this means.

I’m guessing few people will care about this plugin because hardly anyone in this world uses the undo tree (by uses the undo tree I mean having an intuition for g+ and g- and using them instinctively). But I care because I use the undo tree and undo trees rule and you should go learn about them.

Oh the hours debugging why my internal jumptree data structure kept getting corrupted. In the end it was a Neovim bug: if you position the cursor on the same line as the last jumplist entry, merely viewing the jumplist—with either :ju or getjumpilst()—permanently deletes that entry. Since jumptree.vim calls getjumplist() on CursorMoved, the entire jumplist would gradually erase itself as you moved the cursor around. Ultimately, this is the workaround I settled on:

function s:getjumplist(...)
  " make sure the cursor is not on the same line as the last jumplist entry,
  " because otherwise Neovim permanently deletes that entry when viewing the
  " jumplist. see src/nvim/mark.c:1196-1207, commit 5eca52aa
  let curpos = getcurpos()
  call cursor(line("''") % line('$') + 1, 0)
  let jumplist = call('getjumplist', a:000)
  call setpos('.', curpos)
  " for some reason, `autocmd CursorMoved * call setpos('.', getcurpos())`
  " breaks the '$' binding in blockwise visual mode |v_$|. this fixes it up
  if curpos[4] == v:maxcol | execute 'normal! $' | endif
  return jumplist
endfunction