当前支持:Anthropic、Ollama 和 OpenAI 适配器
[!重要] 此插件按原样提供,主要为我的工作流程开发。因此,我不保证会定期更新或支持,并且我预期插件的 API 会经常更改。错误修复和功能增强将根据我的个人需求自行实施。如果需要,请随意分叉项目并根据需要进行定制,但请理解我在进一步开发中的参与会间歇性。要接收插件重大更改的通知,请订阅 此问题。
:sparkles: 功能
- :speech_balloon: 在 Neovim 中体验 Copilot Chat
- :electric_plug: 支持 OpenAI、Anthropic 和 Ollama
- :rocket: 内联代码创建和重构
- :robot: 变量、代理和工作流程以改进 LLM 输出
- :sparkles: 内置提示用于 LSP 错误和代码建议
- :building_construction: 创建你自己的 Neovim 自定义提示
- :floppy_disk: 保存和恢复你的对话
- :muscle: 异步执行以提高性能
:camera_flash: 截图
https://github.com/user-attachments/assets/1375f623-c088-4bf0-a5d7-8d81eaa3a94b
https://github.com/user-attachments/assets/8ae255ba-1f5c-470c-a252-f31d056297c3
:zap: 要求
- 已安装
curl
库 - Neovim 0.9.2 或更高版本
- (可选) 你选择的 LLM 的 API 密钥
:package: 安装
使用你首选的包管理器安装插件:
{
"olimorris/codecompanion.nvim",
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-treesitter/nvim-treesitter",
"nvim-telescope/telescope.nvim", -- 可选
{
"stevearc/dressing.nvim", -- 可选: 改善默认 Neovim UI
opts = {},
},
},
config = true
}
use({
"olimorris/codecompanion.nvim",
config = function()
require("codecompanion").setup()
end,
requires = {
"nvim-lua/plenary.nvim",
"nvim-treesitter/nvim-treesitter",
"nvim-telescope/telescope.nvim", -- 可选
"stevearc/dressing.nvim" -- 可选: 改善默认 Neovim UI
}
})
:gear: 配置
可以在 config.lua 文件中找到默认配置。你可以通过调用 setup
函数更改任何默认设置。例如:
require("codecompanion").setup({
opts = {
send_code = false
}
})
适配器
[!警告] 根据你的 选择的适配器,你可能需要设置 API 密钥。
插件使用适配器将插件连接到 LLM。目前插件支持:
- Anthropic (
anthropic
) - 需要 API 密钥 - Ollama (
ollama
) - OpenAI (
openai
) - 需要 API 密钥
策略是用户可以与插件互动的不同方式。chat 和 agent 策略利用缓冲区允许与 LLM 进行直接对话。inline 策略允许 LLM 的输出直接写入现有的 Neovim 缓冲区中。
要指定与默认设置不同的适配器,只需更改 strategies.*
表:
require("codecompanion").setup({
strategies = {
chat = {
adapter = "ollama",
},
inline = {
adapter = "ollama",
},
agent = {
adapter = "anthropic",
},
},
})
[!提示] 要创建你自己的适配器,请参考 ADAPTERS 指南。
配置环境变量
你可以如下自定义适配器的配置:
require("codecompanion").setup({
adapters = {
anthropic = function()
return require("codecompanion.adapters").use("anthropic", {
env = {
api_key = "ANTHROPIC_API_KEY_1"
},
})
end,
},
strategies = {
chat = {
adapter = "anthropic",
},
},
})
在上述示例中,我们在使用 Anthropic 适配器的基础上更改了它使用的默认 API 密钥的名称。
在 shell 中以纯文本形式保存 API 密钥并不总是安全的。得益于 这次 PR,你可以在配置中运行命令:
require("codecompanion").setup({
adapters = {
openai = function()
return require("codecompanion.adapters").use("openai", {
env = {
api_key = "cmd:op read op://personal/OpenAI/credential --no-newline",
},
})
end,
strategies = {
chat = {
adapter = "openai",
},
},
},
})
在这个示例中,我们使用 1Password CLI 读取 OpenAI 凭证。
配置适配器设置
LLM 有许多设置,比如 model、temperature 和 max_tokens。在适配器中,这些设置位于一个 schema 表中,可以在设置期间配置:
require("codecompanion").setup({
adapters = {
llama3 = function()
return require("codecompanion.adapters").use("ollama", {
name = "llama3", -- 确保模型与 Ollama 区分开来
schema = {
model = {
default = "llama3:latest",
},
num_ctx = {
default = 16384,
},
num_predict = {
default = -1,
},
},
})
end,
},
})
[!提示] 请参考你选择的 适配器 以查看可用的设置。
高亮组
插件在设置期间设置了以下高亮组:
CodeCompanionChatHeader
- 聊天缓冲区中的标题CodeCompanionChatSeparator
- 聊天缓冲区中标题之间的分隔符CodeCompanionChatTokens
- 聊天缓冲区中显示的虚拟文本,显示令牌计数CodeCompanionChatTool
- 聊天缓冲区中的工具CodeCompanionChatVariable
- 聊天缓冲区中的变量CodeCompanionVirtualText
- 插件中的所有其他虚拟文本
[!提示] 你可以在配置中更改这些高亮组链接到的内容。
:rocket: 入门
内联提示
https://github.com/user-attachments/assets/bf88836d-832d-4f69-a58e-371bbb8b9bd2
要开始与插件互动,你可以从命令行运行 :CodeCompanion <your prompt>
。你还可以在 Neovim 中进行可视选择并运行 :'<,'>CodeCompanion <your prompt>
以将其作为上下文发送。插件将首先使用 LLM 对你的提示进行分类,以确定在 Neovim 中将响应放置在哪里。你可以在 inline prompting 部分找到有关分类的更多信息。
为了方便起见,你还可以通过斜杠命令从命令行调用 default prompts:
/explain
- 解释缓冲区中选定代码的工作原理/tests
- 生成选定代码的单元测试/fix
- 修复选定代码/buffer
- 将当前缓冲区发送到 LLM 并附带提示/lsp
- 解释选定代码的 LSP 诊断/commit
- 生成提交消息
运行 :'<,'>CodeCompanion /fix
将触发插件开始按照 config 中定义的修复提示进行操作。某些斜杠命令还可以带有自定义提示。例如,运行 :'<,'>CodeCompanion /buffer refactor this code
将整个缓冲区作为上下文发送,并附带重构所选代码的提示。
还有一些键映射可用于接受或拒绝 LLM 中的编辑,详见 inline prompting 部分。
聊天缓冲区
聊天缓冲区是你在与插件互动时最常使用的地方。运行 :CodeCompanionChat
或 :'<,'>CodeCompanionChat
将打开一个聊天缓冲区,你可以在那里直接与 LLM 对话。为了方便起见,你可以使用 :CodeCompanionToggle
来切换聊天缓冲区的可见性。
在聊天缓冲区中,你可以访问以下变量:
#buffer
- 与 LLM 共享当前缓冲区的内容。你还可以使用#buffer:8-20
指定行号#buffers
- 与 LLM 共享所有当前打开的缓冲区#editor
- 与 LLM 共享你在编辑器视口中看到的缓冲区和行#lsp
- 与 LLM 共享当前缓冲区的 LSP 信息和代码
[!注意] 在聊天缓冲区中时,
?
键映射会显示所有可用的键映射、变量和工具。
代理 / 工具
https://github.com/user-attachments/assets/8bc083c7-f4f1-4eab-b9fe-ab6c4c30ee91
插件还支持 LLM 通过调用外部工具充当代理。在上面的视频中,我们要求 LLM 通过 @code_runner 工具执行缓冲区的内容,全部在一个聊天缓冲区中。
在聊天缓冲区中你可以访问以下工具:
@code_runner
- LLM 可以在 Docker 容器内触发任何代码的运行@rag
- LLM 可以浏览并搜索互联网以补充其响应的实时信息@buffer_editor
- LLM 可以通过搜索和替换块来编辑 Neovim 缓冲区中的代码
[!重要] 代理目前处于 alpha 阶段,我在这里将代理和工具术语互换使用。
操作调色板
:CodeCompanionActions
命令将打开 操作调色板,为你提供插件中所有功能的访问权限。Prompts 部分是你可以访问默认提示和你自己的自定义提示的位置。你会注意到有些提示在描述中有斜杠命令,比如 /commit
。这使你可以通过在命令行中执行 :CodeCompanion /commit
来触发它们。其中一些提示也有分配的键映射(可以覆盖!),提供了触发它们的更简单路径。
[!注意] 如果你在可视模式下,有些操作只会在 操作调色板 中可见。
命令列表
以下是插件中可用命令的完整列表:
CodeCompanionActions
- 打开 操作调色板CodeCompanion
- 插件的内联提示CodeCompanion <slash_cmd>
- 使用斜杠命令的插件内联提示,例如:/commit
CodeCompanionChat
- 打开一个新的聊天缓冲区CodeCompanionChat <adapter>
- 使用特定适配器打开一个新的聊天缓冲区CodeCompanionToggle
- 切换聊天缓冲区CodeCompanionAdd
- 将可视选择的聊天添加到当前聊天缓冲区
建议的工作流程
为了获得最佳工作流程,我推荐以下选项:
vim.api.nvim_set_keymap("n", "<C-a>", "<cmd>CodeCompanionActions<cr>", { noremap = true, silent = true })
vim.api.nvim_set_keymap("v", "<C-a>", "<cmd>CodeCompanionActions<cr>", { noremap = true, silent = true })
vim.api.nvim_set_keymap("n", "<LocalLeader>a", "<cmd>CodeCompanionToggle<cr>", { noremap = true, silent = true })
vim.api.nvim_set_keymap("v", "<LocalLeader>a", "<cmd>CodeCompanionToggle<cr>", { noremap = true, silent = true })
vim.api.nvim_set_keymap("v", "ga", "<cmd>CodeCompanionAdd<cr>", { noremap = true, silent = true })
-- 在命令行中将 'cc' 展开为 'CodeCompanion'
vim.cmd([[cab cc CodeCompanion]])
:bulb: 高级用法
定制操作调色板
一个 RECIPES 指南已经编写好,向你展示如何将自己的提示添加到 操作调色板。
聊天缓冲区
聊天缓冲区是你可以直接从 Neovim 与 LLM 对话的地方。它表现为一个常规的 markdown 缓冲区,并做了一些巧妙的添加。当缓冲区被写入(或“保存”)时,自动命令会触发将其内容作为提示发送给 LLM。当收到响应时,它会被流式回写到缓冲区。结果是你从 Neovim 内部体验与 LLM 对话的感觉。
如 入门 部分所述,聊天缓冲区中有许多变量可以使用。使用 #
调出完成菜单以查看可用选项。
键映射
在聊天缓冲区中,有许多可用的键映射:
?
- 调出帮助菜单<CR>
|<C-s>
- 将缓冲区发送到 LLM<C-c>
- 关闭缓冲区q
- 取消来自 LLM 的请求ga
- 更改适配器gx
- 清除缓冲区内容gx
- 添加一个代码块gs
- 将聊天保存到磁盘}
- 移动到下一个聊天{
- 移动到上一个聊天[
- 移动到下一个标题]
- 移动到上一个标题
已保存的聊天
聊天缓冲区默认不会保存到磁盘,但可以通过按 gs
保存。然后可以通过操作调色板恢复保存的聊天以及 加载保存的聊天 操作。
设置
如果 display.chat.show_settings
设置
- 之后 - 在视觉选择/光标之后
- 之前 - 在视觉选择/光标之前
- 新建 - 在新缓冲区中
- 替换 - 替换视觉选择
- 聊天 - 在聊天缓冲区中
在进行内联编辑后,您还可以使用以下键位映射:
ga
- 接受内联编辑gr
- 拒绝内联编辑
默认提示
[!注意] 请参阅 RECIPES 指南,以便在操作面板和斜杠命令中添加您自己的提示。
插件附带了许多默认提示(根据配置),可以通过键位映射和/或斜杠命令调用。这些提示已被精心策划,以模仿 GitHub 的 Copilot Chat 中的提示。
代理 / 工具
正如 Andrew Ng 在 Agentic Design Patterns Part 3, Tool Use 中概述的那样,LLM 可以通过利用外部工具作为代理。Andrew 提到了一些常见的例子,如网页搜索或代码执行,在使用 LLM 时具有明显的好处。
在插件中,代理只是通过 system
提示提供给 LLM 的上下文。这给予它知识和定义的模式,可以包括在其响应中供插件解析、执行和反馈。代理可以通过 @
键添加为聊天缓冲区中的参与者。
关于代理如何工作以及如何创建您自己的代理的更多信息,可以在 AGENTS 指南中找到。
工作流程
[!警告] 如果您使用外部 LLM,工作流程可能会大幅消耗令牌。
正如 Andrew Ng 在 The Batch 中概述的那样,代理工作流程有能力显著提高 LLM 的输出。事实上,旧模型如 GPT 3.5 使用传统零样本推理可能比新模型表现更好。Andrew 讨论 了如何通过多个提示调用 LLM 进行自我反思来利用代理工作流程。遵循 Andrew 的建议,插件通过使用工作流程支持这一点。在预定义的工作流程的各个阶段,插件将自动提示 LLM,而无需用户的任何输入或触发。
目前,插件附带了以下工作流程:
- 添加新功能
- 重构代码
当然,您可以通过遵循 RECIPES 指南添加新的工作流程。
:lollipop: 额外功能
钩子 / 用户事件
插件在其生命周期中触发以下事件:
CodeCompanionRequest
- 在 API 请求期间触发。输出data.status
,其值为started
或finished
CodeCompanionChatSaved
- 聊天保存到磁盘后触发CodeCompanionChat
- 在聊天缓冲区的各个时间点触发。具有以下属性:data.action = hide_buffer
- 当聊天缓冲区被隐藏时
CodeCompanionInline
- 在内联 API 请求期间与CodeCompanionRequest
一起触发。输出data.status
,其值为started
或finished
,以及data.placement
,指出 LLM 文本的位置CodeCompanionAgent
- 当代理正在运行时触发。输出data.status
,其值为started
或success
/failure
事件可以如下挂接:
local group = vim.api.nvim_create_augroup("CodeCompanionHooks", {})
vim.api.nvim_create_autocmd({ "User" }, {
pattern = "CodeCompanionInline",
group = group,
callback = function(args)
if args.data.status == "finished" then
-- 在内联请求完成后格式化缓冲区
require("conform").format({ bufnr = args.buf })
end
end,
})
状态栏
您可以在 Neovim 配置中加入一个视觉指示,以显示插件与 LLM 通信的情况。以下是两个受欢迎的状态栏插件的示例。
lualine.nvim:
local M = require("lualine.component"):extend()
M.processing = false
M.spinner_index = 1
local spinner_symbols = {
"⠋",
"⠙",
"⠹",
"⠸",
"⠼",
"⠴",
"⠦",
"⠧",
"⠇",
"⠏",
}
local spinner_symbols_len = 10
-- 初始化
function M:init(options)
M.super.init(self, options)
local group = vim.api.nvim_create_augroup("CodeCompanionHooks", {})
vim.api.nvim_create_autocmd({ "User" }, {
pattern = "CodeCompanionRequest",
group = group,
callback = function(request)
self.processing = (request.data.status == "started")
end,
})
end
-- 每次更新状态栏时运行的函数
function M:update_status()
if self.processing then
self.spinner_index = (self.spinner_index % spinner_symbols_len) + 1
return spinner_symbols[self.spinner_index]
else
return nil
end
end
return M
heirline.nvim:
local CodeCompanion = {
static = {
processing = false,
},
update = {
"User",
pattern = "CodeCompanionRequest",
callback = function(self, args)
self.processing = (args.data.status == "started")
vim.cmd("redrawstatus")
end,
},
{
condition = function(self)
return self.processing
end,
provider = " ",
hl = { fg = "yellow" },
},
}
Legendary.nvim
插件还支持出色的 legendary.nvim 插件。只需在您的配置中启用它:
require('legendary').setup({
extensions = {
codecompanion = true,
},
})
:gift: 贡献
我欢迎贡献,但它们将由我自行决定是否实施。可以在进行大规模 PR 之前先打开一个讨论,请确保您已经阅读了 CONTRIBUTING.md 指南。
:clap: 致谢
- Steven Arcangeli 对聊天缓冲区的天才创作和反馈
- Wtf.nvim 对 LSP 助手操作的贡献
- ChatGPT.nvim 对令牌计算的贡献