Skip to content

Repository files navigation

herdr-nvim

CI Release License

Neovim, built into your herdr workspace: a persistent nvim sidebar one key away, with quick access to the files your agent works on.

herdr-nvim-overview.mp4

Features

  • Full-height nvim sidebar, one key to toggle. Your panes move into the left half, and nvim takes the right. Toggle it off, and herdr restores the original layout. Each tab keeps its own persistent nvim, so buffers, cursor, and pending annotations survive the toggle.
  • Fuzzy file picker. It opens on the files your agent touched recently (newest first, with diff stats). Type to fuzzy-search the whole repo. opens the file in the sidebar at the right line.
  • Code annotations you send to the agent. Comment lines or a selection like a code review. Then send them all to any agent in the workspace (pi, claude, codex), with file:line and git context.

Requirements

nvim ≥ 0.10 · herdr ≥ 0.7.4 · runs inside a herdr session

Install

Both halves come from this repo:

1. The herdr plugin (sidebar + picker):

herdr plugin install ChmaraX/herdr-nvim
# or, for a local checkout: herdr plugin link /path/to/herdr-nvim

Bind keys to the two actions in ~/.config/herdr/config.toml (herdr binds none by default):

[[keys.command]]
key = "prefix+e"
type = "plugin_action"
command = "chmarax.herdr-nvim.toggle"
description = "nvim sidebar"

[[keys.command]]
key = "prefix+o"
type = "plugin_action"
command = "chmarax.herdr-nvim.pick-file"
description = "open file from agent output"

2. The nvim plugin (annotations), with your plugin manager (e.g. lazy.nvim):

{ "ChmaraX/herdr-nvim", opts = {} }

The sidebar

prefix+e toggles it. Each tab gets its own nvim, backed by a headless daemon that survives the toggle. Two tabs can show two different files in two sidebars. When you close and reopen a sidebar, it loses nothing. herdr removes the daemons of closed tabs automatically.

The file picker

prefix+o pops a fuzzy file picker. It has two modes:

  • Default view (no query): the files touched this session, newest first. It mines edits from the agent's session log and adds uncommitted git changes. For agents that herdr does not track, it scrapes recent pane output instead. The cursor starts on the newest file, so opens it with no typing.
  • Typing: fuzzy matches across the whole repo, ranked best first. This includes every file that git ls-files reports, and it honors .gitignore. The match is on the path and filename, not the file contents.

The repo-wide tier is served by fff-search (fff.nvim's core matcher): multi-term queries (cargo toml), typo tolerance, and frecency ranking. If a fff.nvim frecency database exists at ~/.cache/nvim/fff_nvim, the picker reuses it read-only (it copies the DB to a temp dir and opens the copy; your database is never opened, locked, or written) so files you actually open often rank higher. Set frecency = false to skip the DB reuse. The agent-touched session tier always ranks first and uses its own matcher regardless.

Each row shows:

  • the path, relative to the agent's cwd
  • a new badge for files created this session
  • green/red +N -M diff stats for uncommitted edits
  • a relative touched-age (2m, 3h)

If you start the picker from a non-agent pane (for example, the sidebar itself), it reads the agent in the same tab. So it searches the repo that you see.

The default view shows the latest max_files entries (20). A typed query is uncapped.

Annotations

Each action has a default keymap and a :Herdr subcommand (subcommands tab-complete):

Keymap Command Action
<leader>ac :Herdr comment comment the current line / selection (the command also takes a range: :5,10Herdr comment)
<leader>al :Herdr list list comments (float): hover to jump, edit, d delete
<leader>as :Herdr send paste all comments into the agent's input
<leader>aS :Herdr submit send all comments to the agent (auto-submits)

Keymaps are on by default (prefix <leader>a) and never override a map you already set. To bind your own, set keymaps = false and map the command:

require("herdr-nvim").setup({ keymaps = false })
vim.keymap.set({ "n", "x" }, "<leader>ac", "<CMD>Herdr comment<CR>", { desc = "Comment" })

Or call the Lua API directly (comment_line, comment_selection, comment_range(s, e), list_comments, send_all{ submit = false|true }). See :help herdr-nvim for the full reference.

Sending skips the picker when the target is obvious: the lone agent in the workspace, or the single agent sharing this tab (the sibling pane). The picker only appears when two or more agents could plausibly be meant.

Comments are ephemeral by design: in-memory only, extmark-tracked (they follow your edits), cleared after a successful send. The sent prompt includes each comment's file:line plus the repo and branch, so the agent has context.

For a pending-comment indicator (● 3) in your statusline: require("herdr-nvim").statusline().

Config

Two small config surfaces, one per half:

nvim sidesetup{} opts:

require("herdr-nvim").setup({
  prefix = "<leader>a",     -- keymap prefix
  keymaps = true,           -- set false to define your own
  clear_after_send = true,  -- comments are ephemeral by design
})

herdr side~/.config/herdr-nvim/config.toml (optional; missing or malformed files fall back to these defaults):

[sidebar]
nvim_bin = "nvim"     # binary used to spawn the per-tab nvim daemon
nvim_env = []         # env overrides for that nvim, applied to the daemon,
                      # the sidebar window, and open-file clients. For
                      # example, nvim_env = ["NVIM_APPNAME=myapp"] runs the
                      # sidebar under the config in ~/.config/myapp instead
                      # of vanilla nvim (replace myapp with your app name).
position = "right"    # right (default), left, top, or bottom

[picker]
scan_lines = 300    # pane lines scanned by the fallback text-scrape
max_files = 20      # session entries shown before you type a query
                    # (a typed query fuzzy-searches the whole repo, uncapped)
frecency = true     # let fff reuse ~/.cache/nvim/fff_nvim (read-only copy)

If your normal nvim config lives under a custom NVIM_APPNAME (any name you launch nvim with — e.g. a distro or your own config directory), set sidebar.nvim_env = ["NVIM_APPNAME=myapp"] so the sidebar's daemon and window run that configuration too. Without it the sidebar is vanilla nvim. The daemon still injects this plugin's lua over runtimepath after your config loads (VimEnter fallback), so annotations work under any appname.

Troubleshooting

herdr-nvim doctor                     # live checks: splits, toggle, daemon, remote-ui
herdr-nvim doctor --with-agent claude # also verify agent registration

Doctor runs labeled checks in a scratch workspace and always removes them afterward. The most common failure is daemon-healthy FAIL: the nvim daemon did not start. Make sure that sidebar.nvim_bin points at a working nvim ≥ 0.10.

Tests

just ci    # cargo fmt + cargo test + headless Lua suite

test

About

Neovim, fully integrated into your herdr workspace

Topics

Resources

Stars

204 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages