XXiaoA/atone.nvim

github github
editing-support
stars 188
issues 0
subscribers 1
forks 11
CREATED

UPDATED


with compact and branch_symbols enabled

Features

  • Blazing Fast
  • Modern UI
  • Live Syntax-Aware Diff: Instant, TreeSitter-powered diff previews with word-level inline highlighting.
  • Auto-attaching Tree: The undo tree automatically follows you as you switch between buffers.
  • Node Marks: Bookmark important undo states for quick navigation.
  • Highly Customizable: Almost every aspect can be configured.

How it relates to nvim's built-in :undotree?

The built-in undotree is great — it ships with Neovim, requires zero setup, and covers the basics well. If that's all you need, stick with it. atone.nvim is for people who want a bit more. Here's a quick comparison:

Feature nvim 0.12 :undotree atone.nvim
Setup None (built-in) Plugin install
Tree visualization Basic Standard + compact
Diff preview No TreeSitter highlighted + word-level inline
Node marks No Persistent, numbered, named, fuzzy-findable
Custom labels No Yes
Auto-attach to buffers No Yes
Customization Limited Highly

Both have their place, pick what fits your workflow.

Installation

You can install atone.nvim using your favorite plugin manager. Here comes a example for lazy.nvim

{
    "XXiaoA/atone.nvim",
    cmd = "Atone",
    ---@module "atone"
    ---@type AtoneConfig
    opts = {},
}

Commands

The main command is :Atone. It has the following subcommands:

Command Description
:Atone or :Atone open Opens the undo tree view.
:Atone toggle Toggles the undo tree view on and off.
:Atone close Closes the undo tree view.
:Atone focus Moves the cursor to the undo tree window.

Configuration

You can configure atone.nvim by passing a table to the setup function. Here are the default options:

require("atone").setup({
    layout = {
        ---@type "left"|"right"
        direction = "left",
        ---@type "adaptive"|number
        --- adaptive: adapt to width of tree graph
        --- float < 1: width = vim.o.columns * value
        --- integer >= 1: absolute width
        width = 0.25,
    },
    -- diff for the node under cursor
    -- shown under the tree graph
    diff_cur_node = {
        enabled = true,
        --- The diff window's height is set to a specified percentage of the original (namely tree graph) window's height.
        split_percent = 0.3,
        ---@type "adaptive"|number
        --- adaptive: same width as tree window (default)
        --- float < 1: width = vim.o.columns * value
        --- integer >= 1: absolute width
        --- Note that non-adaptive values create a float diff window anchored to a hidden
        --- dummy split window. this is an implementation detail that may cause
        --- unexpected edge-case bugs in certain window layouts.
        width = "adaptive",
        -- Use TreeSitter to highlight the source code inside diff hunks.
        treesitter = true,
        -- Highlight the exact changed word ranges inside modified lines.
        inline_diff = true,
    },
    -- automatically update the buffer that the tree is attached to
    -- only works for buffer whose buftype is <empty>
    auto_attach = {
        enabled = true,
        excluded_ft = { "oil" },
    },
    marks = {
        persist = true,
        persist_path = vim.fn.stdpath("data") .. "/atone_marks.json",
        --- finders are tried in order. "builtin" is always available.
        finders = { "fzf-lua", "telescope", "builtin" },
    },
    keymaps = {
        tree = {
            quit = { "<C-c>", "q" },
            next_node = "j", -- support v:count
            pre_node = "k", -- support v:count
            jump_to_G = "G",
            jump_to_gg = "gg",
            undo_to = "<CR>",
            set_mark = "m",
            delete_mark = { "x", "X" },
            delete_all_marks = "dM",
            goto_mark = { "'", "`" },
            mark_picker = "s",
            help = { "?", "g?" },
            float_diff = "gd",
        },
        auto_diff = {
            quit = { "<C-c>", "q" },
            help = { "?", "g?" },
            undo = "u",
            redo = "<C-r>",
            float_diff = "gd",
        },
        help = {
            quit_help = { "<C-c>", "q" },
        },
    },
    diff_float = {
        --- width of the diff float as a fraction of the editor width (0–1)
        width = 0.8,
        --- height of the diff float as a fraction of the editor height (0–1)
        height = 0.8,
        --- close the centred diff float when it loses focus.
        autoclose = true,
    },
    ui = {
        -- refer to `:h 'winborder'`
        border = "single",
        -- compact graph style
        compact = true,
        -- Draw the undo tree with git branch drawing symbols (U+F5D0-U+F60D).
        -- "auto": enable when the terminal renders them natively
        --   (kitty >= 0.36.2, wezterm >= 2025-04-15, ghostty >= 1.0).
        -- true: force enable. false: disable.
        branch_symbols = "auto",
        node_label = {
            custom = false,
            ---@param ctx AtoneNodeLabelContext
            ---@return AtoneNodeLabel
            formatter = function(ctx)
                return string.format(
                    "[%d] %s %s%s",
                    ctx.seq,
                    ctx.h_time,
                    ctx.bookmark or "",
                    ctx.is_sticky_ref and " [=]" or ""
                )
            end,
            extmark_opts = { strict = false },
        },
    },
})

Graph Symbols

With ui.branch_symbols = "auto" (the default), the undo tree is drawn with the git branch drawing symbols codepoints (U+F5D0-U+F60D) introduced by vim-flog when the terminal renders them natively, and falls back to the classic box-drawing characters otherwise. All terminals that support them use the same codepoints and render the glyphs themselves (no font needed):

  • kitty >= 0.36.2
  • wezterm >= 2025-04-15
  • ghostty >= 1.0

Other terminals need a patched font such as flog-symbols to show the glyphs at all, and even then the connecting lines are not guaranteed to line up.

Detection relies on environment signals that do not survive SSH, so remote sessions fall back to the classic symbols; set branch_symbols = true explicitly to force them.

Custom Labels

The tree uses the built-in label format by default. If you want full control over the label, turn on ui.node_label.custom and provide a formatter(ctx).

require("atone").setup({
    ui = {
        node_label = {
            custom = true,
            ---@param ctx AtoneNodeLabelContext
            ---@return AtoneNodeLabel
            formatter = function(ctx)
                local sticky = ctx.is_sticky_ref and " [=]" or ""
                return string.format("[%d] %s %s%s", ctx.seq, ctx.h_time, ctx.bookmark or "", sticky)
            end,
        },
    },
})

Available fields on ctx:

Field Type Description
seq integer Undo sequence number of the node
is_current boolean Whether this node is the current undo state
is_sticky_ref boolean Whether this node is the pinned sticky diff base
time integer Raw timestamp from Neovim's undotree
h_time string Human-readable time string
bookmark string? Bookmark label text, e.g. {a}
diff { added: integer, removed: integer } Added/removed line counts for the node

Things to know:

  • Fixed labels (custom = false) are built for the whole tree when the tree is refreshed.
  • Custom labels only write label text for the visible part of the tree.
  • When the viewport moves, the old visible range is restored to the plain graph text and the new visible range gets fresh label text.
  • Highlight groups returned by chunked labels are applied only for the currently visible label range.
  • Fixed-label highlighting is not used in custom mode. If you want highlighted custom labels, return highlighted chunks.

You may return either a plain string or a list of text chunks.

Plain string example:

formatter = function(ctx)
    local sticky = ctx.is_sticky_ref and " [=]" or ""
    return string.format("[%d] %s %s%s", ctx.seq, ctx.h_time, ctx.bookmark or "", sticky)
end

Chunked example with per-segment highlight groups:

formatter = function(ctx)
    return {
        "[",
        { ctx.seq, ctx.is_current and "AtoneCurrentNode" or "AtoneSeq" },
        "] ",
        { ctx.h_time, "Comment" },
        ctx.bookmark and " " or "",
        { ctx.bookmark or "", "AtoneMark" },
        ctx.is_sticky_ref and " " or "",
        ctx.is_sticky_ref and { "[=]", "AtoneStickyRef" } or "",
    }
end

When you return chunks, chunks with a highlight group are highlighted and chunks without one are shown as normal text.

Keymap Actions

The keymaps table in the configuration allows you to map keys to specific actions in different windows. The keys can be a single string or a table of strings.

Here are the available actions and their default keybindings:

Action Default Key(s) Description
next_node j Jump to the next node in the undo tree. Supports v:count.
pre_node k Jump to the previous node in the undo tree. Supports v:count.
jump_to_G G Jump to the node with the specified sequence number like G
jump_to_gg gg Jump to the node with the specified sequence number like gg
undo_to <CR> Revert the buffer to the state of the node under the cursor.
set_mark m Set a mark. Use N:name or N for slot (0-9).
delete_mark x, X Delete the mark on the node under cursor.
delete_all_marks dM Delete all marks in current buffer.
goto_mark ', ` Jump to a mark slot (0-9).
mark_picker s Open mark picker (fuzzy find).
set_sticky_ref = Toggle a sticky diff base.
undo u Undo one step in the attached buffer.
redo <C-r> Redo one step in the attached buffer.
float_diff gd Toggle a centred floating diff window.
quit <C-c>, q Close all atone.nvim windows (tree, diff, and help).
help ?, g? Show the help page.
quit_help <C-c>, q Close the help window.

Highlighting

atone.nvim uses the following highlight groups. You can customize them as what you did for normal highlight groups.

Highlight Group Default Description
AtoneSeq link to Number The sequence number of each node
AtoneSeqBracket link to Comment The brackets surrounding the node sequence number
AtoneCurrentNode link to Keyword The currently selected node in the undo tree
AtoneStickyRef link to Debug The pinned sticky diff reference node
AtoneMark link to BookmarkSign Mark labels on nodes
AtoneDiffAdd link to DiffAdd Background for added lines in the diff preview
AtoneDiffDelete link to DiffDelete Background for removed lines in the diff preview
AtoneDiffAddInline derived from DiffAdd Adaptive inline background for the changed range
AtoneDiffDeleteInline derived from DiffDelete Adaptive inline background for the changed range

Credits