Undo tree

Vim Motions tracks branching undo history in a shadow tree parallel to CM6’s linear undo stacks. When you undo and make a new edit, the old “future” becomes an alternative branch — accessible via g-/g+ or :earlier/:later. Inspired by Neovim’s undotree.

Chronological (g-/g+)

g- moves to the chronologically older state across ALL branches. g+ moves to the chronologically newer state. The buffer content changes — you see the document revert or advance.

Unlike u/Ctrl-R which only navigate the current branch, g-/g+ traverse every state in the order it was created, regardless of which branch it’s on.

By count (:earlier/:later)

CommandEffect
:earlier NGo back N changes
:later NGo forward N changes
:earlier NsGo to state from N seconds ago
:earlier NmGo to state from N minutes ago
:earlier NhGo to state from N hours ago
:earlier NdGo to state from N days ago
:earlier NfGo to Nth previous save point
:later NfGo to Nth next save point

Undo list (:undolist)

:undolist opens a modal showing all undo tree nodes with their sequence number, timestamp, change summary, save status, and branch count. The current node is marked with >.

:UndoTreeToggle (or :UndoTreeShow/:UndoTreeHide) opens a sidebar panel showing the undo tree as a visual hierarchy.

Keyboard navigation

KeyAction
jSelect next node
kSelect previous node
EnterNavigate to selected node
qClose sidebar

Click any node to navigate to it.

Neovim RPC ownership

With the Neovim RPC backend connected, u, <C-r>, g-, g+, :undolist, :earlier, and :later run natively in Neovim. :UndoTreeToggle, :UndoTreeShow, and :UndoTreeHide remain host commands because they manage an Obsidian workspace leaf. The sidebar fetches Neovim’s vim.fn.undotree() result instead of displaying CM6’s shadow history.

Features

  • Relative timestamps — “3m ago”, “1h ago”
  • Change summary — “+7 chars, -3 chars”
  • Branch indicators — shows when a node has multiple children
  • Collapse/expand — click the toggle to collapse alternate branches
  • Current node highlight — the active state is visually distinct
  • Saved markers — nodes where the file was saved are flagged

Lua API

vim.fn.undotree() returns a Neovim-compatible dictionary:

local tree = vim.fn.undotree()
print(tree.seq_last)   -- highest sequence number
print(tree.seq_cur)    -- current position
print(tree.time_cur)   -- timestamp of current state (Unix seconds)
print(#tree.entries)   -- number of entries on the main branch

Settings

Configure via Settings → Vim Motions → Undo tree or vimrc/Lua:

SettingDefaultDescription
enableUndoTreetrueEnable undo tree tracking
undoTreeMaxNodes1000Maximum nodes per file (oldest branches pruned)
undoTreePositionrightSidebar position (left/right)
undoTreeAutoOpenfalseAuto-open sidebar on branch creation
undoFilefalsePersist undo tree across sessions

Memory management

Undo trees are automatically evicted from memory when all editors for a file are closed. When undoFile is enabled, dirty trees are persisted before eviction. Reopening a file restores from persistence or starts fresh.

External modification detection

When undoFile is enabled and a file was modified outside Obsidian between sessions, a notification is shown indicating the undo tree is stale. The tree structure is preserved for :undolist display, but navigation is disabled for that session.

Vimrc

set undotree          " enable (default)
set noundotree        " disable
set undofile          " persist across sessions
set noundofile        " session-only (default)

Lua

vim.opt.undotree = true
vim.opt.undofile = true
vim.opt.undotreemaxnodes = 500