krmcbride/opencode.nvim
GitHub: krmcbride/opencode.nvim
一个 Neovim 插件,用于在编辑器中集成 OpenCode AI 助手,实现代码审核和会话管理。
Stars: 0 | Forks: 0
# opencode.nvim
一个用于在 [snacks.nvim](https://github.com/folke/snacks.nvim) 终端内运行本地 [opencode](https://github.com/anomalyco/opencode) 附加模式 TUI 的 Neovim 插件,具备 Neovim 端会话桥接和本地集成功能。

[观看审核队列演示](https://github.com/user-attachments/assets/ad378562-14bb-4525-ae66-711d5da826e0)
## 功能特性
- 针对配置的后端服务器启动本地 `opencode attach` TUI
- 将活跃的附加 TUI 会话桥接回 Neovim
- 通过 OpenCode 的原生编辑器上下文集成,共享当前 Neovim 文件或可视选区
- 用于嵌入 OpenCode TUI 面板的 Snacks 终端集成
- 发送带有上下文扩展的提示词(`@this`、`@buffer`、`@diagnostics`)
- 将当前行或可视范围的直接审核评论发送到活跃会话
- 在快速修复列表中队列化多个审核评论,并作为一个直接后端请求发送
- 当 OpenCode 编辑文件时自动重新加载缓冲区
- 公开 `User` 自动命令,用于通知、状态行/tmux 钩子及其他本地集成
## 安装设置
### [lazy.nvim](https://github.com/folke/lazy.nvim)
```
{
"krmcbride/opencode.nvim",
dependencies = {
{ "folke/snacks.nvim", opts = { terminal = { enabled = true } } },
},
opts = {
server = {
url = "http://127.0.0.1:4096",
},
terminal = {
layout = "split",
width = 0.43,
env = {
-- Extra environment for the child `opencode attach` process
SOME_CHILD_PROCESS_FLAG = "1",
-- Disables OpenTUI's Kitty graphics probe so it does not leak raw
-- probe text into the embedded Snacks terminal buffer.
OPENTUI_GRAPHICS = "0",
},
},
},
init = function()
-- Required for auto-reload when opencode edits files
vim.o.autoread = true
end,
keys = {
{ "ac", function() require("opencode").start({ focus = true, continue = true }) end, mode = { "n", "t" }, desc = "Continue opencode" },
{ "an", function() require("opencode").start({ focus = true, continue = false }) end, mode = { "n", "t" }, desc = "New opencode session" },
{ "at", function() require("opencode").start({ focus = true, continue = true, layout = "tab" }) end, mode = { "n", "t" }, desc = "Open opencode tab" },
{ "aa", function() require("opencode").mention_selection({ focus = true }) end, mode = { "n", "x" }, desc = "Native mention selection" },
{ "aA", function() require("opencode").prompt("@this", { focus = true }) end, mode = { "n", "x" }, desc = "Append selection to prompt" },
{ "ab", function() require("opencode").prompt("@buffer", { focus = true }) end, desc = "Add buffer to prompt" },
{ "ad", function() require("opencode").prompt("@diagnostics", { focus = true }) end, desc = "Add diagnostics to prompt" },
{ "as", function() require("opencode").attach_session_prompt() end, desc = "Attach session ID" },
{ "av", function() require("opencode").review_selection() end, mode = "n", desc = "Review line" },
{ "av", function() require("opencode").review_visual_selection() end, mode = "x", desc = "Review selection" },
{ "ao", function() require("opencode").open_review_queue() end, desc = "Open review queue" },
{ "ae", function() require("opencode").edit_review_queue_comment() end, desc = "Edit queued review comment" },
{ "aD", function() require("opencode").delete_review_queue_comment() end, desc = "Delete queued review comment" },
{ "ap", function() require("opencode").send_review_queue() end, desc = "Send review queue" },
{ "aP", function() require("opencode").clear_review_queue() end, desc = "Clear review queue" },
},
}
```
如果您没有使用 `lazy.nvim`,请在使用插件 API 前自行调用 `require("opencode").setup({ ... })`。
## 配置
所有选项及其默认值:
```
require("opencode").setup({
server = {
url = "http://127.0.0.1:4096", -- Backend server URL
},
auto_reload = true, -- Reload matching buffers on OpenCode edit events
editor_context = {
enabled = true, -- Share active Neovim file/selection with the embedded OpenCode TUI
},
review_queue = {
signs = {
enabled = true, -- Show sign-column markers for queued review comments
text = "", -- Sign text; override with another glyph if desired
hl = "OpencodeReviewQueueSign",
priority = 20,
},
},
terminal = {
cmd = nil, -- Optional custom attach command
dir = ".", -- Directory passed to `opencode attach`
continue = true, -- Default launch behavior; `start` can override per call
layout = "split", -- "split" for a right pane, "tab" for a dedicated Neovim tab
width = 0.35,
env = nil,
},
})
```
### OpenCode TUI 插件
为了追踪活跃的附加 TUI 会话,OpenCode 还需要内置的 TUI 桥接插件。
请将其添加到您的 OpenCode `tui.json` 插件列表中,而不是 `opencode.json`:
```
{
"$schema": "https://opencode.ai/tui.json",
"plugin": [
"/path/to/opencode.nvim"
]
}
```
对于典型的 `lazy.nvim` 安装,该路径通常是:
```
{
"$schema": "https://opencode.ai/tui.json",
"plugin": [
"{env:HOME}/.local/share/nvim/lazy/opencode.nvim"
]
}
```
OpenCode 不会在插件规范中展开 `~`;请使用绝对路径或 `{env:HOME}`。
内置的 TUI 插件位于 `tui-plugin/` 中,并导出 OpenCode 插件 ID `opencode-nvim-bridge`。
桥接插件在 `opencode.nvim` 未使用其桥接环境变量启动 TUI 时是惰性的。
## API
### 终端控制
```
require("opencode").start() -- Start opencode if not running
require("opencode").start({ focus = true }) -- Start and focus if a new terminal is opened
require("opencode").start({ continue = true, focus = true }) -- Open with `--continue`, no-op if already open
require("opencode").start({ continue = false }) -- Open without `--continue`, no-op if already open
require("opencode").start({ continue = false, focus = true }) -- Open without `--continue`, no-op if already open
require("opencode").start({ layout = "tab", focus = true }) -- Open in a dedicated Neovim tab
require("opencode").attach_session("ses_...") -- Attach directly to a specific session id
require("opencode").attach_session_prompt() -- Prompt for a session id, then attach
require("opencode").status() -- Show terminal, backend, bridge, and SSE status
```
嵌入式终端在 OpenCode 输出中的文件引用上方映射了 `gf` 和 `gF`。
`gF` 跳转到引用的行,而诸如 `path#10-20` 或 `path:10-20` 的行范围会短暂高亮引用的范围。范围闪烁使用 `OpencodeFileReferenceRange` 高亮组,默认链接到 `Visual`,可以从您的 Neovim 配置中覆盖:
```
vim.api.nvim_set_hl(0, "OpencodeFileReferenceRange", {
bg = "#3b4261",
})
```
### 提示词
```
-- Insert the current line or selection through OpenCode's native editor integration.
require("opencode").mention_selection({ focus = true })
-- Add context through prompt appending (build up context, then submit in TUI).
require("opencode").prompt("@this") -- Current line or selection, legacy append path
require("opencode").prompt("@buffer") -- Current file
require("opencode").prompt("@diagnostics") -- LSP diagnostics
-- Focus the terminal after adding context
require("opencode").prompt("@this", { focus = true })
-- Or submit immediately
require("opencode").prompt("Fix @diagnostics", { submit = true })
require("opencode").prompt("Explain this", { clear = true, submit = true })
```
**提示词选项:**
| 选项 | 类型 | 描述 |
| :------- | :------ | :------------------------------------------------------------------------------------------------------ |
| `clear` | 布尔值 | 在附加前清除 TUI 输入 |
| `submit` | 布尔值 | 附加后提交 TUI 输入 |
| `focus` | 布尔值 | 附加后聚焦终端;同时进入终端模式并将光标移至行尾(见下方注释) |
**原生提及 vs 提示词附加:**
`mention_selection()` 使用 OpenCode 的原生编辑器上下文 WebSocket,并直接向嵌入的 TUI 发送 `at_mentioned` 通知。对于当前行和可视范围的提及,这是首选路径,因为它通过终端 PTY 写入文本或依赖自动补全焦点行为,直接创建 TUI 文件提及。
`prompt("@this")`、`prompt("@buffer")` 和 `prompt("@diagnostics")` 使用 opencode.nvim 较旧的提示词附加路径。保留此路径用于整个缓冲区提及、诊断、来自选择器的目录/文件引用,以及与旧版 OpenCode 的兼容性。如果嵌入的 TUI 尚未连接到编辑器上下文 WebSocket,`mention_selection()` 会回退到 `prompt("@this")`,除非使用 `{ fallback = false }` 调用。
**上下文占位符:**
这些占位符由 `opencode.nvim` 定义,而非 OpenCode 本身。插件在将提示词发送到附加的 TUI/后端之前,会将它们扩展为纯文本提示词和原生 OpenCode 风格的文件引用。
它们仅在提示词文本流经 `opencode.nvim` API(如 `require("opencode").prompt(...)` 或基于这些 API 构建的映射)时有效。在 OpenCode TUI 中直接键入 `@this`、`@buffer` 或 `@diagnostics` 不会触发任何特殊扩展。
| 占位符 | 扩展为 | 描述 |
| :------------- | :------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------- |
| `@this` | `@file.lua#21`、`@file.lua#21-30` 或 `@file.lua#21 的第 8-15 列` | 当前行、行范围或单行字符选择(列号作为文本;`@…#` 位于最后以供 TUI 自动补全) |
| `@buffer` | `@file.lua` | 当前缓冲区路径 |
| `@diagnostics` | 包含格式化诊断列表和尾随 `@file` 引用的提示文本 | 当前缓冲区的 LSP 诊断信息 |
### 审核
```
-- Review the current line with one composer that can queue or send.
require("opencode").review_selection()
-- Review the current visual range with one composer that can queue or send.
require("opencode").review_visual_selection()
-- Queue-only compatibility entrypoints.
require("opencode").queue_review_selection()
require("opencode").queue_review_visual_selection()
-- Inspect, send, or clear the queued comments.
require("opencode").open_review_queue()
require("opencode").edit_review_queue_comment()
require("opencode").delete_review_queue_comment()
require("opencode").send_review_queue()
require("opencode").clear_review_queue()
require("opencode").review_queue_count()
```
审核直接通过 `POST /session//prompt_async` 发送,使用:
- 一个文本部分用于您的评论
- 一个使用 `file://...?...start=&end=` 的带范围文件附件
审核弹出窗口是一个小型的、锚定在光标处的编辑器浮动窗口:
- `Ctrl-S` 在普通或插入模式下将评论队列化
- `Ctrl-Enter` 在普通或插入模式下立即发送评论
- `Ctrl-C` 在插入模式下取消
- `q` 在普通模式下取消
- `Enter` 插入新行
直接审核发送会在可用时重用最后持久化用户消息的 `agent`、`model` 和 `variant`,因此它们通常与活跃会话的现有模型选择匹配,无需更改 OpenCode 核心。
队列符号默认为 ``,使用 `OpencodeReviewQueueSign` 高亮组,默认链接到 `DiagnosticWarn`。默认标记是 Nerd Font 字形;如果您的字体无法渲染它,请用 `O`、`◉` 或其他符号列字形覆盖 `review_queue.signs.text`。
使用 `open_review_queue()` 或 `:Opencode review-queue-open` 打开队列会刷新快速修复列表,每个队列化评论对应一项,并为多行评论提供扁平化的单行摘要。在该快速修复列表中按 `e` 会重新打开选中的队列化评论到编辑器弹出窗口,当该文件可见时锚定在队列化的源位置。在源缓冲区中,`edit_review_queue_comment()` 编辑其符号位于当前光标行上的队列化评论,`delete_review_queue_comment()` 会提示确认后删除该当前行评论。在编辑弹出窗口中,`Ctrl-S` 将评论保存回队列,`Ctrl-Enter` 立即仅发送该队列化评论。发送单个评论使用与即时审核相同的单文本部分/单文件部分形式;发送多个队列化评论使用第一人称分组,以便 OpenCode 将每条评论都视为来自您(用户)的反馈。仅在后端发送成功后才清除已发送的评论;发送失败会保留队列化的评论。
## 用户命令
| 命令 | 描述 |
| :-------------------------------- | :--------------------------------------- |
| `:Opencode status` | 显示终端、后端、桥接和 SSE 状态 |
| `:Opencode review-queue-open` | 打开审核队列快速修复投影 |
| `:Opencode review-queue-edit` | 编辑当前光标行上的队列化评论 |
| `:Opencode review-queue-delete` | 确认后删除当前光标行上的队列化评论 |
| `:Opencode review-queue-send` | 将队列化的审核评论发送到活跃会话 |
| `:Opencode review-queue-clear` | 清除队列化的审核评论 |
`status` 包含后端 URL、SSE 目录、桥接 URL、桥接的 TUI 路由和活跃会话,以便您验证事件和直接审核的去向。
## 事件
`opencode.nvim` 公开三个有用的集成层面:
这为您提供了足够的空间来构建自己的通知、tmux/workmux 或窗口状态钩子、状态栏组件、按会话的 UI 状态或任何其他本地自动化,而无需将这些集成硬编码到插件中。
在加载插件后,使用 `vim.api.nvim_create_autocmd("User", ...)` 从您的普通 Neovim 配置中注册它们。
### `OpencodeEvent:*`
`OpencodeEvent:*` 来自后端 SSE 流。这是处理后端文件/编辑生命周期事件和服务器连接状态的正确层面。
```
vim.api.nvim_create_autocmd("User", {
pattern = "OpencodeEvent:*",
callback = function(args)
local event = args.data.event
if event.type == "session.idle" then
vim.notify("opencode finished responding")
end
end,
})
```
对于 `OpencodeEvent:*`,`args.data` 包括:
- `event`:后端 SSE 事件对象
- `url`:产生事件的后端基础 URL
### `OpencodeActiveEvent:*`
当安装了内置的 TUI 桥接插件时,`opencode.nvim` 还会将活跃嵌入会话的本地 OpenCode 事件作为自动命令转发。
这是针对关注用户实际查看的嵌入式 TUI 的集成最有用的层面,例如:
- 当活跃会话空闲或出错时的桌面通知
- 当代理忙碌或等待问题/权限提示时的 tmux/workmux 或窗口标题状态更新
- 对 `question.asked` / `permission.asked` 的本地 UI 反应,而无需全局监视每个后端事件
```
vim.api.nvim_create_autocmd("User", {
pattern = "OpencodeActiveEvent:*",
callback = function(args)
local event = args.data.event
if event.type == "session.idle" then
vim.notify("active embedded OpenCode session is idle")
end
end,
})
```
`OpencodeActiveEvent:*` 来自嵌入的 TUI 桥接插件,并限于当前附加的会话,这使其适用于通知、状态栏小部件或 tmux/workmux 钩子等本地集成。
当前转发的事件类型:
- `session.status`
- `session.idle`
- `session.error`
- `message.updated`
- `permission.asked`
- `permission.replied`
- `question.asked`
- `question.replied`
对于 `OpencodeActiveEvent:*`,`args.data` 包括:
- `event`:转发的 OpenCode 事件对象
- `route`:观察到事件时的本地 TUI 路由
- `session_id`:可用时的本地附加会话 ID
- `instance_id`:Neovim 桥接实例 ID
- `cwd`:TUI 工作目录快照
### `OpencodeSessionChanged`
当本地桥接报告活跃的嵌入式 TUI 路由、会话 ID 或 cwd 发生变化时,触发 `OpencodeSessionChanged`。
这是针对需要粗粒度会话感知状态而非每个单独生命周期事件的集成的正确层面,例如:
- 使用当前附加会话 ID 更新状态栏或 winbar
- 将活跃的 OpenCode cwd 镜像到 tmux/窗口元数据中
- 维护按 `(instance_id, session_id)` 键入的每会话缓存
```
vim.api.nvim_create_autocmd("User", {
pattern = "OpencodeSessionChanged",
callback = function(args)
local data = args.data
vim.notify(("route=%s session=%s cwd=%s"):format(data.route, data.session_id or "none", data.cwd or "none"))
end,
})
```
对于 `OpencodeSessionChanged`,`args.data` 包括:
- `route`:观察到事件时的本地 TUI 路由
- `session_id`:可用时的活跃嵌入 OpenCode 会话 ID
- `instance_id`:Neovim 桥接实例 ID
- `cwd`:TUI 工作目录快照
## 致谢
- 最初受 [NickvanDyke/opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) 启发
标签:AI编程助手, AI辅助编程, attach模式, Lua插件, Neovim插件, opencode集成, rizin, snacks.nvim, SOC Prime, TUI终端, 事件钩子, 代码审查, 代码编辑, 会话管理, 开发工具, 提示注入, 本地集成, 终端集成, 自动重载, 集群管理