An OpenCode plugin that gives the AI agent awareness of your current Neovim editor state — what file you have open, where your cursor is, what text you have selected, and any diagnostics (errors/warnings). When you say "explain this" or "fix this", the agent knows what "this" is.
The plugin connects OpenCode to a running Neovim instance via its built-in RPC socket. When the agent needs to know what you're looking at, it queries Neovim for the current editor state and gets back a JSON response.
┌──────────────────────────────┬─────────────────────────────────┐
│ │ │
│ Neovim │ OpenCode │
│ │ │
│ cursor at line 42 │ User: "Why does this function │
│ ▌ │ return null sometimes?" │
│ function foo(items) { │ │
│ ~~~~~~~~ selected ~~~~~~~~ │ Agent: "The function at line │
│ │ 42 can return null when the │
│ │ `items` array is empty — the │
│ │ early return on line 44 exits │
│ │ before reaching the reduce." │
│ │ │
└──────────────────────────────┴─────────────────────────────────┘
The agent can see:
- Current file — absolute path of the open buffer
- Cursor position — line and column (1-indexed)
- Visual selection — start/end positions and the selected text
- Diagnostics — LSP errors, warnings, and hints for the current buffer
The plugin has two parts that are installed separately:
A Lua module that registers a global EditorContext() function. This function gathers the current editor state and returns it as JSON. It's queryable via Neovim's RPC socket from outside the editor.
An OpenCode plugin that registers an editor_context tool. When the agent calls it, the tool queries Neovim's EditorContext() function via the RPC socket and returns the result.
Using lazy.nvim:
{
"talldan/opencode-nvim-editor-context",
config = function()
require("editor-context").setup()
end,
}Add the plugin to your opencode.json configuration:
{
"plugin": ["opencode-nvim-editor-context"]
}This can go in your global config (~/.config/opencode/opencode.json) or a project-level config (opencode.json in your project root).
Restart OpenCode to load the plugin. The editor_context tool will be available to the AI agent automatically.
The tool communicates with Neovim via its RPC socket. In most cases, no configuration is needed — the tool auto-discovers running Neovim instances.
The tool automatically finds Neovim by:
- Checking the
NVIM_SOCKETenvironment variable (if set, always used) - Scanning for Neovim sockets in standard locations (
$TMPDIRand/tmp) - Preferring the Neovim instance whose working directory matches the current project
- Falling back to the first live Neovim instance found
This means if you just run nvim in your project directory, OpenCode will find it automatically.
If auto-discovery doesn't work for your setup (e.g., multiple Neovim instances in the same directory), you can set the socket path explicitly:
export NVIM_SOCKET=/tmp/nvim.sock
nvim --listen $NVIM_SOCKETMake sure NVIM_SOCKET is set in the environment where OpenCode runs.
If you use CMUX, you can set this in your workspace configuration so both Neovim and OpenCode share the socket path automatically.
Say you have a file open in Neovim with your cursor on a function, and you ask the agent "why does this return null sometimes?" The agent calls the editor_context tool and gets back:
{
"file": "/Users/you/project/src/utils.ts",
"cursor": { "line": 42, "col": 5 },
"selection": {
"start_line": 42,
"start_col": 1,
"end_line": 48,
"end_col": 2,
"text": "function foo(items: string[]) {\n if (!items.length) {\n return null;\n }\n return items.reduce((acc, item) => acc + item.length, 0);\n}"
},
"diagnostics": [
{ "line": 43, "col": 5, "severity": "WARN", "source": "typescript", "message": "Type 'null' is not assignable to type 'number'" }
]
}The agent now knows exactly what code you're looking at, what text you selected, and that there's a type warning on the early return. It can answer your question directly without needing to read the file or guess what you meant by "this".
OpenCode (agent) Neovim (editor)
│ │
│ nvim --headless --server │
│ $NVIM_SOCKET --remote-expr │
│ "luaeval('EditorContext()')" │
│ ─────────────────────────────────>│
│ │
│ JSON { file, cursor, │
│ selection, diagnostics } │
│ <─────────────────────────────────│
│ │
The tool uses Neovim's --remote-expr to evaluate a Lua expression on the running Neovim instance. This is a standard Neovim feature that works in any terminal — no CMUX or specific terminal emulator required.
Key design decisions:
--headlessflag on remote calls: Prevents Neovim from emitting terminal escape sequences when invoked from a subprocess (e.g., Bun's shell). Without this, the JSON response gets polluted with control codes.- Global
EditorContext()function: Registered as a global (not module-scoped) so it can be called vialuaeval()without needing the module require path. - Selection handling: Supports both active visual mode selections (using
getpos("v")andgetpos(".")) and the last visual selection marks ('<and'>). This means the agent can see what you selected even after you leave visual mode. - 1-indexed positions: All line and column numbers are 1-indexed to match what users see in the editor, even though Neovim's API is 0-indexed internally.
- Socket auto-discovery: When
NVIM_SOCKETis not set, the tool scans$TMPDIR/nvim.$USER/and/tmpfor Neovim socket files, verifies each is live, and useslsofto match the Neovim process's working directory against the current project. This allows zero-configuration usage — just runnvimand OpenCode will find it.
Optionally include a window of lines around the cursor (e.g., 20 lines above and below) so the agent has immediate context without needing to read the file separately.
Report on all open buffers, not just the current one. This would let the agent understand which files the user is working across.
Track unsaved modifications in the buffer compared to the file on disk. The agent could see what the user has been editing without needing git diff.