- Lua 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| doc | ||
| lua/opencode-pair | ||
| plugin | ||
| tests | ||
| LICENSE | ||
| README.md | ||
opencode-pair.nvim
A dependency-free Neovim 0.10+ client for sending discussed changes from an OpenCode session to Neovim as reviewable ghost text.
The plugin starts no process during setup. :OpenCodePairAttach finds the
workspace, launches the bridge through code-box, and lets you choose an
OpenCode session. After discussing a change, request it at the cursor, review
the ghost text, and explicitly accept or reject it.
Installation
With lazy.nvim:
{
"your-name/opencode-pair.nvim",
opts = {
keymaps = {
attach = "<leader>oa",
complete = { n = "<leader>oc", x = "<leader>oc" },
accept = { n = "<leader>oy", i = "<Tab>", s = "<Tab>" },
reject = { n = "<leader>on", i = "<Left>" },
status = "<leader>os",
detach = "<leader>od",
},
},
}
No keymaps are installed unless they are present in keymaps. Each action may
use one string for every supported mode or a table of mode-specific mappings.
A successful normal-mode request enters Insert mode automatically. In the
example, Tab accepts and Left rejects only while ghost text is visible. Tab
otherwise jumps through an active native snippet or inserts a normal Tab;
Left retains native cursor movement.
Configuration
require("opencode-pair").setup({
command = "code-box",
args = { "opencode-bridge" },
root_dir = nil,
context = {
before_lines = 40,
after_lines = 20,
},
timeout_ms = 120000,
keymaps = {},
})
Mode tables use n, i, s, and x, restricted to supported modes for each
action. Arrow and Tab fallback does not invoke mappings from other plugins.
Blink integration
When Blink owns <Tab>, let Blink consume the key and schedule acceptance
after its mapping callback completes:
["<Tab>"] = {
function(cmp)
local pair = require("opencode-pair")
if not pair.has_suggestion() then
return false
end
vim.schedule(function()
if pair.accept({ silent = true }) then
return
end
if cmp.select_and_accept() then
return
end
if cmp.snippet_forward() then
return
end
local tab = vim.api.nvim_replace_termcodes(
"<Tab>", true, false, true)
vim.api.nvim_feedkeys(tab, "ni", false)
end)
return true
end,
"select_and_accept",
"snippet_forward",
"fallback",
},
root_dir may be a path or a callback with the signature
function(bufnr, absolute_path). A callback may return a path or nil to
use root discovery. Discovery uses the nearest .git directory and then
falls back to Neovim's current working directory.
The current file must be inside the selected root. Context comes from the current buffer, including unsaved changes, byte-oriented cursor position, diagnostics in the included line range, and the current visual selection. Visual mappings preserve characterwise, linewise, and blockwise selections; an explicit Ex command range supplies complete lines.
Requests are always explicit. This keeps discussion-driven suggestions separate from speculative completion plugins. A newer request supersedes an older one locally; the bridge protocol does not abort work already started.
Commands
| Command | Action |
|---|---|
:OpenCodePairAttach |
Start the bridge and select a session |
:OpenCodePairComplete |
Request a completion immediately |
:OpenCodePairAccept |
Insert the visible suggestion |
:OpenCodePairReject |
Clear the visible suggestion |
:OpenCodePairStatus |
Show process, session, and backend state |
:OpenCodePairDetach |
Detach from the current session |
A suggestion is invalidated by edits, cursor movement, or leaving its buffer. Suggestions may contain up to 100 lines and 64 KiB. Acceptance inserts the complete response exactly at the captured byte position. Multiline suggestions require the cursor at the end of a line so the ghost-text preview matches the accepted edit.
Testing
nvim --headless \
-u tests/minimal_init.lua \
-l tests/run.lua
The test suite uses only Neovim's built-in Lua runtime.
Protocol
The bridge is launched as:
code-box opencode-bridge <canonical-project-root>
Requests and responses use newline-delimited JSON on stdin and stdout.
Diagnostics on stderr are retained for status reporting. The successful
hello response must explicitly confirm protocol = 1; extra response
fields are ignored.
License
MIT