No description
  • Lua 54.1%
  • Rust 45.9%
Find a file
Ben Trinh 9e7781da38 docs: document auto_commit_on_open
Why:
- New config option from the previous commit had no README mention.

What:
- Added a short note + example to the Undo/Redo Integration section,
  explaining why it exists (so the first undo past your first save
  has a real commit to land on) right next to the related caveat
  about unmatched pre-edit states.

Impact:
- Docs only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 21:30:20 +10:00
lua/localhistory feat: seed a commit on open for files with no history yet 2026-09-23 21:30:20 +10:00
plugin fix: move lua/ and plugin/ to repo root for plugin-manager installs 2026-09-22 22:09:28 +10:00
rust feat: add localhistoryd request/response logging and a log viewer 2026-09-22 22:47:14 +10:00
tests feat: seed a commit on open for files with no history yet 2026-09-23 21:30:20 +10:00
.gitignore feat: hook native undo/redo without overriding keys 2026-02-20 15:37:39 +11:00
README.md docs: document auto_commit_on_open 2026-09-23 21:30:20 +10:00

localhistory.nvim

Local, Git-inspired per-file history for Neovim.

This repo contains:

  • lua/, plugin/: the Neovim plugin
  • rust/: the Rust backend (localhistoryd)

Quickstart (Lua backend)

By default the plugin uses the Lua backend.

require("localhistory").setup()

Quickstart (Rust backend)

1) Build localhistoryd

rust.bin auto-detects rust/target/release/localhistoryd relative to wherever this plugin itself is installed, so all you need to do is get that binary built there. How depends on how you installed the plugin:

Installed via a plugin manager (from git): wire up its build/post-install hook to build the binary in place after clone/update:

-- lazy.nvim
{
  "bentrinh/localhistory.nvim",
  build = "cd rust && cargo build --release -p localhistoryd",
  -- Without this, plugin/localhistory.lua auto-runs setup() with defaults
  -- (Lua backend) before `config` below runs setup() again with your options.
  -- Harmless (config below still wins), but redundant — skip it.
  init = function()
    vim.g.localhistory_auto_setup = false
  end,
  config = function()
    require("localhistory").setup({ backend = "rust" })
  end,
}
-- packer.nvim
use({
  "bentrinh/localhistory.nvim",
  run = "cd rust && cargo build --release -p localhistoryd",
})
" vim-plug
Plug 'bentrinh/localhistory.nvim', { 'do': 'cd rust && cargo build --release -p localhistoryd' }

The hook only runs on install/update, so after pulling a new version that touches rust/, trigger your manager's build step again (e.g. :Lazy build localhistory.nvim, :PackerCompile after a change, or :PlugInstall!).

Manual clone (no plugin manager): from the repo root:

cd rust
cargo build --release -p localhistoryd

Either way, an alternative that puts localhistoryd on $PATH instead (useful if you'd rather not rely on the auto-detected path):

cargo install --path rust/crates/localhistoryd --locked

2) Configure the Neovim plugin

require("localhistory").setup({
  backend = "rust",
})

rust.bin only needs to be set explicitly if you installed the binary somewhere the auto-detection won't find (e.g. via cargo install, where the bare name "localhistoryd" resolves via $PATH):

require("localhistory").setup({
  backend = "rust",
  rust = { bin = "localhistoryd" },
})

If you'd rather disable auto-setup and configure explicitly:

vim.g.localhistory_auto_setup = false

require("localhistory").setup({ backend = "rust" })

Or, keeping auto-setup enabled, set this before the plugin loads:

vim.g.localhistory_config = {
  backend = "rust",
}

Commands

  • :LocalHistoryToggle
  • :LocalHistoryUndo
  • :LocalHistoryRedo
  • :LocalHistoryCheckout
  • :LocalHistoryDiff
  • :LocalHistoryDiffClose
  • :LocalHistoryOpenLog (rust backend only, see Debugging the rust daemon)

Debugging the rust daemon

By default localhistoryd logs nothing. To see what it's doing (each request it handles and the response it sends back), set a log level:

require("localhistory").setup({
  backend = "rust",
  rust = {
    -- tracing's RUST_LOG syntax: "error" | "warn" | "info" | "debug" | "trace",
    -- or a per-target filter like "localhistoryd=trace".
    log_level = "debug",
  },
})

Then :LocalHistoryOpenLog opens the log file (default: storage_path/localhistoryd.log; override with rust.log_path). Each Neovim session's daemon run is appended as its own --- localhistoryd session started ... --- block. Even with log_level unset, this file still captures any panic/crash output the daemon prints, so it's worth checking first if the rust backend seems stuck or unresponsive.

Browsing long histories

The sidebar renders history in pages (default: 200 commits) so huge histories stay fast. When older commits are hidden, a marker line shows how many remain:

~ 37 more commits - L: load more, A: show all
  • L — load the next page of older commits
  • A — render the entire history

Configure the page size (or disable pagination entirely):

require("localhistory").setup({
  visualizer = {
    page_size = 200, -- commits per page; 0 or nil = always render everything
  },
})

The Rust CLI supports the same limit for debugging:

localhistoryd --db-path /tmp/localhistory.sqlite3 graph --filepath /abs/path/to/file --limit 200

Undo/Redo Integration

By default, localhistory.nvim does not override Neovim's native u / <C-r>.

If you want localhistory to follow native undo/redo navigation, enable the opt-in hook:

require("localhistory").setup({
  hooks = {
    native_undo_redo = {
      enabled = true,
      keys = { undo = "u", redo = "<C-r>" },
      -- method = "autocmd", -- default
    },
  },
})

When enabled, localhistory observes native undo/redo and consolidates its current commit to the buffer content:

  • If the current buffer content matches an existing localhistory commit, localhistory moves to that commit.
  • If the content does not match any commit (unsaved intermediate undo states), localhistory does not move its pointer.

Since only saved states are ever commits, the very first native undo past your first edit-and-save normally has nothing to land on (the pre-edit state was never committed) and correctly does nothing. To avoid that, opening a file localhistory has never seen seeds a commit from its current on-disk content, so there's always a real commit for the first undo to reach:

require("localhistory").setup({
  auto_commit_on_open = true, -- default; set false to only start tracking from the first save
})

CLI (Rust)

localhistoryd also provides a small CLI for debugging without Neovim.

Examples:

localhistoryd --db-path /tmp/localhistory.sqlite3 tree --filepath /abs/path/to/file
localhistoryd --db-path /tmp/localhistory.sqlite3 graph --filepath /abs/path/to/file
localhistoryd --db-path /tmp/localhistory.sqlite3 content --filepath /abs/path/to/file
localhistoryd --db-path /tmp/localhistory.sqlite3 undo --filepath /abs/path/to/file

Daemon:

localhistoryd --db-path /tmp/localhistory.sqlite3 serve --stdio