Let your coding agent point at code and tell you why you're looking at it.
demo.mp4
Agents describe locations in prose — "the guard in resolveSocket around line 29". You then go find it. showme.nvim closes that gap: the agent jumps your cursor there, tints the range, and prints its explanation inline with the code.
The Neovim half knows nothing about any particular agent. It exposes a small Lua API and a JSON-in/JSON-out RPC entry point, so anything that can run nvim --server can drive it. Both an opencode integration and a stdio MCP server ship in the box.
Neovim (lazy.nvim):
{ "chriswritescode-dev/showme.nvim", opts = {} }opts = {} is enough. Calling setup() is optional — the plugin works without it, and only exists to override highlights or keymaps.
opencode — install showme-nvim in your configuration directory and name it in opencode.json:
Or install the prebuilt plugin instead, which needs no package in your configuration directory and no config entry:
pnpm dlx showme-nvim installThat writes plugin/showme.js into $OPENCODE_CONFIG_DIR, $XDG_CONFIG_HOME/opencode, or ~/.config/opencode, whichever resolves first, or into a directory you pass as an argument. OpenCode loads every plugin/*.js there on its own. The file imports nothing but Node builtins, so that directory stays free of node_modules and portable if you sync it between machines, and it does not follow the package — run the command again to upgrade. From a clone, pnpm run opencode:install builds and installs in one step.
Either way you get a showme tool once a live Neovim instance is available. Tell your agent to use it when it wants you to look at something. One call carries one or more references; when it sends several, they become a group you cycle through with ]r and [r, each stop tinted and annotated in turn.
Install the Neovim half above regardless of which harness you use. The MCP server requires Node.js 22.18 or newer and nvim on PATH, on the same machine as your editor. It needs no OpenCode process or configuration.
A harness using the common mcpServers configuration format can launch it like this:
{
"mcpServers": {
"showme": {
"command": "pnpm",
"args": ["dlx", "showme-nvim", "--project", "/absolute/path/to/project"]
}
}
}Adapt the surrounding configuration to your harness; the transport is stdio. --project controls both relative file resolution and Neovim discovery. It defaults to the server's working directory, so set it explicitly for desktop clients or hosts launched elsewhere.
For a local checkout, run pnpm install and pnpm build, then use "command": "node" with "args": ["/absolute/path/to/showme.nvim/dist/mcp.js", "--project", "/absolute/path/to/project"]. No package publication is needed for this path. pnpm pack builds the executable automatically.
The MCP tool takes refs, a list of up to 10 { file, line, end_line?, note? } entries, using the same validation, behavior, and Neovim transport as the OpenCode tool.
The OpenCode plugin and the MCP server both probe Neovim at initialization. Without a live attached or project-matching instance, the plugin contributes no tool and MCP lists zero tools. An unrelated Neovim process alone does not enable it. Each RPC probe has a two-second timeout.
Availability is a startup snapshot, not a live watcher. Open Neovim before starting your harness; if you open it later, restart the OpenCode instance or reconnect the MCP server. If Neovim closes after registration, the next call rechecks discovery and reports that no instance is attached. The tool remains listed until reinitialization.
You probably have several Neovim instances open across different projects. Resolution runs in this order:
$NVIM— if the harness runs in a terminal inside Neovim, that's your editor by definition. It's probed for liveness first, so a stale value falls through instead of dead-ending. An explicit live$NVIMtakes precedence over project-directory matching.- Otherwise every Neovim server socket is probed in parallel for its
getcwd(), and the instance whose directory best matches the agent's project wins — exact match, then nearest enclosing directory. Instances in unrelated projects are never hijacked. - If nothing matches at initialization, the tool is omitted. If an already-registered tool is called after Neovim closes, the agent is told no Neovim is attached; that is not a tool error.
local showme = require("showme")
showme.show({
{ file = "src/auth.ts", line = 42, end_line = 48, note = "the check that runs twice" },
{ file = "src/session.ts", line = 10, note = "and the caller that made it necessary" },
})
showme.next() -- cycle forward, wraps
showme.prev() -- cycle back, wraps
showme.focus(2) -- jump to a specific stop
showme.clear() -- remove every mark
showme.status() -- { index, total, stops }show(refs, opts) takes opts.mode ("replace", the default, or "append") and opts.focus (a 1-based index, or false to mark without jumping).
One expression, JSON in and JSON out:
nvim --server "$NVIM" --remote-expr \
'v:lua.require("showme").rpc(''{"action":"show","refs":[{"file":"/abs/path.ts","line":42,"note":"here"}]}'')'Actions are show, focus, next, prev, clear, status. Every reply is {"ok":true,"location":"path:from-to","index":1,"total":3} or {"ok":false,"error":"..."}. Single quotes inside the payload must be doubled, per Vimscript string rules.
The TypeScript transport is reusable on its own if you're building another bridge:
import { send } from "showme-nvim/nvim"
await send(projectRoot, { action: "show", refs: [{ file, line, note }] })require("showme").setup({
bar = "▌ ",
max_note_lines = 12,
focus_window = true,
keymaps = { next = "]r", prev = "[r", clear = "<leader>ox" },
highlights = {
ShowmeRange = "CursorLine",
ShowmeNote = { fg = "#ff9e64", italic = true },
ShowmeNoteBar = "DiagnosticWarn",
},
})Pass keymaps = false to set none.
focus_window (default true) makes the referenced buffer's window current when a location is shown. This matters when the agent runs in a terminal inside Neovim: without it, the jump happens behind the terminal and you never see it. Set it to false to leave focus where it was. It is independent of opts.focus — that one selects which stop to show, this one decides where the cursor lands.
Each entry in highlights is either the name of a group to link to, or a table passed straight to nvim_set_hl, so the note can take its own color instead of borrowing one. ShowmeRange tints the referenced lines, ShowmeNote colors the note text, and ShowmeNoteBar colors the bar down its left edge; they default to CursorLine, Normal, and DiagnosticInfo. Linking to a semantic group like DiagnosticWarn follows your colorscheme, while a literal fg pins one color across themes. Both are reapplied on ColorScheme, so they survive a theme switch.
Notes reflow when the window changes. The text is re-wrapped on WinResized, VimResized, WinNew, and WinClosed, measured against the window's real text width so sign and number columns are accounted for.
<Tab> is deliberately not used for navigation: in normal mode it is <C-i>, and most terminals cannot distinguish the two, so binding it would cost you jumplist-forward. Every jump sets a ' mark, so <C-o> takes you back where you were.
Discovery only scans Neovim's default socket directory. An instance started with an explicit nvim --listen /custom/path is invisible unless the agent runs inside it, where $NVIM points at it directly.
MIT
{ "plugin": ["showme-nvim"] }