Skip to content

XXiaoA/atone.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

atone.nvim

Modern undotree plugin for nvim

Stars Issues License

Image

Features

  • Blazing Fast
  • Mordern 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?" },
        },
        auto_diff = {
            quit = { "<C-c>", "q" },
            help = { "?", "g?" },
            undo = "u",
            redo = "<C-r>",
        },
        help = {
            quit_help = { "<C-c>", "q" },
        },
    },
    ui = {
        -- refer to `:h 'winborder'`
        border = "single",
        -- compact graph style
        compact = false,
        node_label = {
            custom = false,
            ---@param ctx AtoneNodeLabelContext
            ---@return AtoneNodeLabel
            formatter = function(ctx)
                return string.format("[%d] %s %s", ctx.seq, ctx.h_time, ctx.bookmark or "")
            end,
            extmark_opts = { strict = false },
        },
    },
})

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)
                return string.format("[%d] %s %s", ctx.seq, ctx.h_time, ctx.bookmark or "")
            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
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)
    return string.format("[%d] %s %s", ctx.seq, ctx.h_time, ctx.bookmark or "")
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" },
    }
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).
undo u (diff window) Undo one step in the attached buffer.
redo <C-r> (diff window) Redo one step in the attached buffer.
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
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

About

Modern undotree plugin for nvim

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors