A non-intrusive bridge between Neovim and the Pi coding agent. Work alongside an AI agent without losing control of your editor.
Single repo, both ecosystems. Install once — Neovim gets a plugin, Pi gets an extension.
Pi edits files on disk. Normally that means your editor fights you with reload prompts, cursor jumps, and lost unsaved work. nvim-pi fixes all of that:
- Your cursor stays put — Pi never steals focus or switches your buffer
- Your unsaved work is safe — dirty buffers get Pi's edits merged in-memory, not overwritten
- No reload prompts — clean buffers reload silently, dirty buffers merge seamlessly
When you have unsaved changes and Pi edits the same file:
| Scenario | What happens |
|---|---|
| Pi edits a different region than yours | Merged — both changes coexist in the buffer, saved to disk |
| Pi edits overlap with your changes | Conflict — buffer untouched, resolve with :PiAccept or :PiDiff |
| File not open in Neovim | Tracked — notification shown, no buffer to worry about |
| File open but unmodified | Reloaded — silently refreshed from disk |
Pi sees what you're working on — every time you send a prompt, it gets a fresh snapshot of your Neovim workspace:
- Active file and cursor position
- Which files have unsaved changes (so it avoids clobbering them)
- Recently active buffers
- Existing Pi/Neovim conflicts
This context is injected into the LLM's system prompt, making Pi a more informed collaborator.
- Notifications when Pi edits any file (even ones you don't have open)
- Line highlights that fade after a few seconds showing exactly what changed
- Sign column markers (
) on modified lines - Statusline component showing
PI:workingor PI:N conflicts
vim.pack (Neovim 0.12+):
-- plugin/nvim-pi.lua
vim.pack.add({ 'https://github.com/huijiro/nvim-pi' })
require("nvim-pi").setup()lazy.nvim:
{
"huijiro/nvim-pi",
config = function()
require("nvim-pi").setup()
end,
}Manual (any plugin manager that adds to runtimepath):
Clone the repo anywhere and add it to your runtimepath. The lua/ and plugin/ directories follow standard Neovim plugin structure.
All options with their defaults:
require("nvim-pi").setup({
auto_export_socket = true, -- export nvim socket so Pi can find it
show_notifications = true, -- show notifications for Pi activity
highlight_duration = 4000, -- highlight fade time in ms
sign_enabled = true, -- show sign column indicators
})As a pi package (recommended):
pi install git:github.com/huijiro/nvim-piFor development (symlink):
ln -s /path/to/nvim-pi/extensions ~/.pi/agent/extensions/nvim-piNo configuration needed — the extension auto-discovers your Neovim instance.
| Command | Description |
|---|---|
/nvim |
Interactive settings menu |
/nvim connect [socket] |
Connect to Neovim (auto-discover or specific socket) |
/nvim disconnect |
Disconnect from Neovim |
/nvim status |
Show connection info |
/nvim toggle |
Enable/disable the bridge |
/nvim notify |
Toggle Neovim-side notifications |
Shortcut: Ctrl+Shift+N — quick toggle on/off
Status bar: Shows nvim[dirname] in Pi's footer when connected.
| Command | Description |
|---|---|
:PiAccept |
Accept Pi's version of a conflicted file (discards your unsaved changes) |
:PiDiff |
Side-by-side diff of your buffer vs Pi's version on disk |
:PiActivity |
Show all recent Pi file modifications |
:PiStatus |
Show bridge connection status |
:PiClear |
Clear all Pi activity markers and highlights |
:PiExportSocket |
Re-export the Neovim socket for Pi discovery |
-- lualine
require("lualine").setup({
sections = {
lualine_x = {
{ require("nvim-pi").statusline },
},
},
})
-- manual statusline
vim.o.statusline = "%{v:lua.require('nvim-pi').statusline()}"Displays:
PI:working— while the Pi agent is processing PI:N conflicts— when there are unresolved merge conflicts
local pi = require("nvim-pi")
pi.is_pi_modified("/path/to/file") -- true if Pi recently edited this file
pi.has_conflict("/path/to/file") -- true if there's an unresolved conflict
pi.statusline() -- statusline string component┌─────────────────┐ ┌─────────────────┐
│ Pi Agent │ nvim --server <sock> │ Neovim │
│ │ --remote-expr luaeval() │ │
│ tool_result ────┼───────────────────────────────►│ on_file_changed │
│ │ (never steals cursor) │ ├─ clean → edit!│
│ before_agent ◄──┼───────────────────────────────┤ ├─ dirty → merge│
│ _start │ get_workspace_context_json() │ └─ fail → conflict
└─────────────────┘ └─────────────────┘
- Neovim starts → exports socket path to
PI_NVIM_SOCKETenv var - Pi starts → auto-discovers the socket (checks env vars, then scans
/run/user/<uid>/nvim.*) - Pi edits a file →
tool_resulthandler computes changed line ranges and sendson_file_changed()to Neovim vialuaeval() - Neovim handles it → reloads clean buffers, merges into dirty buffers, or flags a conflict
- You send a prompt →
before_agent_startqueries Neovim for workspace context and injects it into the system prompt
Auto-reconnection: if Neovim is restarted, the extension detects the stale socket and rediscovers automatically on the next operation.
- Neovim 0.10+ (uses
vim.api.nvim_buf_set_lines,v:fcs_choice, etc.) - Pi with extension support
- A terminal that supports Nerd Font icons (for signs:
,)
MIT