NeovimのAIプラグインcodecompanion.nvimの使い方

※ v12.15.0(2025年3月3日)までの内容を反映しました。

この記事はVim駅伝の2024年8月30日の記事です。
前回(2024年8月28日)の記事はteramakoさんのNeovim nvim-cmp の補完ソースプラグインと gin.vim の action 補完ソースを書いた話でした。


NeovimのAIプラグインcodecompanion.nvimを紹介します。

codecompanion.nvimとは

codecompanion.nvimはNeovimのAIプラグインです。

次のようにAIチャットやエージェントの実行ができます。

  • チャット
  • バッファの編集
  • ファイル操作
  • コマンドの実行

Ollama、OpenAI、Copilot、Gemini、Anthropic、DeepSeekなどいろいろサポートしています。

プロンプトは間にLuaの処理を挟んで柔軟に定義できます。
たとえば、「LSPのdiagnosticsをまとめてAIに解説してもらうプロンプト」がデフォルトで定義されています(上記の動画)。

導入方法

curlが必要です。Neovimのバージョンは0.92以上のみ対応しています。

プラグインのインストール

リポジトリ名だけコピペしたい人向け
olimorris/codecompanion.nvim

各々が使っているプラグインマネージャーでインストールします。

インストール

lazy.nvimだと次のように書いてインストールできます。

lazy.nvimの例
return {
"olimorris/codecompanion.nvim",
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-treesitter/nvim-treesitter",
},
opts = {
-- ここに設定を書く
}
}

※ 遅延読み込みの例はこの記事の後半で紹介します。

設定の流れ

codecompanionでは各LLMをAdapterとして登録して使います。設定の流れは次のとおりです。

  1. APIキーの用意またはモデルのダウンロード
  2. adaptersに各LLMの設定を書く
  3. 使いたいadapterをstrategiesに書く

プラグインの設定はsetupの中に書いていきます。

require('codecompanion').setup({
adapters = {
-- TODO: ここにadapterを登録
},
strategies = {
-- TODO: ここでadapterを指定
},
})

※ lazy.nvimの場合、setupの中身はoptsに書きます。この書き方について知りたい人は別の記事「lazy.nvimの使い方から起動を爆速にする方法までを解説」を読んでください。

APIキーを設定する場合のadapters

OpenAIを使うなら、次のようにAPIキーを渡します。

require("codecompanion").setup({
adapters = {
openai = function()
return require("codecompanion.adapters").extend("openai", {
env = { api_key = vim.env.OPENAI_API_KEY },
})
end,
},
-- ...
})

APIキーを環境変数に書きたくない場合、1PasswordのCLIを実行させることもできます。詳しくはSetting an API Keyを読みましょう。

ローカルのLLMを動かす場合のadaptersの例

ここではOllamacodellama:7bの設定例を紹介します。
次のコマンドでモデルをダウンロードできます。

Terminal window
ollama run codellama:7b
require("codecompanion").setup({
adapters = {
ollama = function()
return require("codecompanion.adapters").extend("ollama", {
schema = {
model = {
default = "codellama:7b",
},
},
})
end,
},
-- ...
})

strategiesにadapterを指定

strategiesにどの機能でどのadapterを使うか指定します。

require("codecompanion").setup({
-- ...
strategies = {
chat = {
adapter = "openai",
},
inline = {
adapter = "openai",
},
agent = {
adapter = "openai",
},
},
})

チャット機能ではchatに指定したadapterが使われます。チャット以外ではinlineのadapterが使われます。
agent@fooのように書いたときに動くAIエージェントのことです。後で説明します。

使い方

CodeCompanionの機能やコマンドを紹介します。

チャットの開き方

チャットの開き方はいくつかあります。

:CodeCompanionChatだと毎回新しいチャットが開きます。
一方、:CodeCompanionChat Toggleだとチャットをトグルできます。

CodeCompanionChat

:CodeCompanionChatを実行すると、チャット専用バッファが開きます。最初はノーマルモードで開きます。

チャットの最初の画面

質問を入力後、Enterを押すとLLMに送信され、回答が始まります。

※モデルやPCのスペックによっては回答の開始までに時間がかかります。「回答中」をわかりやすくする設定は後で説明します。

チャット専用バッファ

画像の回答の審議はさておき、適切にコードブロックがハイライトされていますね。
チャットは通常のバッファと同じように編集できるため、回答が冗長で見づらい場合は適宜削除できます。

チャット閉じるにはCtrl+cを入力します。

?でヘルプが開きます。

チャットのヘルプ

キーマップの他に、Variables(変数)が出てきましたね。

変数

チャットでは変数が指定できます。

たとえば#buffer:18-21と書けば、開いているバッファの18~21行目の内容に関連した質問ができます。

チャットで変数を使った例

変数は他にもいくつか定義されています。

変数名LLMに共有される内容
#buffer現在のバッファ
#buffer:X-Y現在のバッファのX~Y行
#buffers開いているすべてのバッファ
#editor見えている範囲の画面
#lsp現在のバッファのLSPの情報

バッファの指定範囲をチャットに送る

バッファの特定の範囲をLLMに共有する方法は、#buffer以外にもあります。

共有したい部分をビジュアルモードで選択してコマンド:'<,'>CodeCompanionChat Addを実行すれば、チャットに入力内容が挿入されます。

※以前はCodeCompanionAddでしたが、CodeCompanionChat Addに変わりました。

Agents / Tools

エージェントも使えます。AIによる回答を解析して適切なツールを呼び出す機能です。

エージェントを使うにはメッセージでメンション風に@fooを添えます。

@files foo/bar.luaを作成してください。

@cmd_runner

@cmd_runnerを使うと、コマンドが実行できます。テストの実行やライブラリのインストールを指示できるということです。

@editor

@editorを使うと、開いているバッファを編集してもらえます。

@editor #buffer リファクタリングしてください

コード編集の場合、diffが表示されます。ga(accept)で反映、gr(reject)で拒否できます。

@files

@filesを使うと、ファイル関連の操作ができます。ファイルの読み込みや新規作成などなど。

@full_stack_dev

エージェントをいちいち判断するのは面倒ですよね?

@full_stack_devと書くだけで@cmd_runner@editor@filesの3つのエージェントが使えます。
迷ったらこれ

@rag

URLを渡したり検索したい場合は@ragを使います。

エージェントの公式ドキュメント:Using Agents and Tools | CodeCompanion

スラッシュコマンド

前述の「エージェント」はAIによる回答を解析して適切なツールを呼び出す機能でした。
一方「スラッシュコマンド」はスラッシュコマンドを書いた瞬間に何らかのアクションが発火されます。主な用途はコンテキストの共有です。

/buffer

/bufferはバッファをAIと共有できる機能です。

/buffer vs #buffer

スラッシュコマンド/bufferはバッファを自由に複数選択できます。
一方、変数#bufferはチャットのバッファに入る前のバッファ1つだけです。

じゃあ/bufferだけでいいじゃん、と思うかもしれませんが#bufferだと行数指定ができます。

/file

/fileでファイルをAIと共有できる機能です。

/file vs @files

/fileだとUIを使って読み込んでほしいファイルを選択できます。
一方、@filesはファイル操作をしてもらうためのツールです。文章としてfoo/bar.txtを読み込んでというような指定ができます。

ファイルの読み込みだけなら/fileの方がプレビューもあるので楽です。
ディレクトリ全体を読み込んでほしいときは@filesの方が楽です。

@filesの使用例
@files `openapi/`以下のYAMLファイルを読み込んで、おかしい所があれば教えて下さい。
/file vs /buffer

/fileでファイルを共有できるなら、/bufferいらないのでは?」と思うかもしれません。/bufferにもメリットはあります。

/fileだとカレントディレクトリ以下のみが対象です。一方、カレントディレクトリ以外のファイルでもバッファとして開いて/bufferで選択すれば、AIと共有できます。

/fetch

/fetchを使うと、指定したURLの内容を取得してAIと共有できます。キャッシュもしてくれます。

/symbols

/symbolsを使うとTree-sitterから作成したコードのシンボル情報をAIと共有できます。

※ 一部の言語のみ対応しています。対応言語はqueriesディレクトリを見てみましょう。

/symbols vs /fileや/buffer

/symbolsはシンボル情報だけを共有するため、ファイル全体を共有する/file/bufferよりもトークン数を節約できます。

/workspace

プロジェクトにcodecompanion-workspace.jsonというファイルを用意しておくと、そのファイルに書いたファイルやシステムプロンプトを一括で共有できます。

詳しくは別の記事codecompanion.nvimのworkspaceの作り方・使い方にて解説しています。

その他のスラッシュコマンド

他にもスラッシュコマンドはあります。詳しくは公式ドキュメントのUsing Slash Commandsをご覧ください。

:CodeCompanion

:CodeCompanion /hoge系のコマンドを実行すると、あらかじめ設定したプロンプトを実行できます。

公式ドキュメントに書かれているものからいくつかピックアップします。

:CodeCompanion /explain

:CodeCompanion /explainを実行すると、チャットで選択範囲の内容を説明してくれます

ノーマルモードでは現在の行のみが対象となってしまうため、:CodeCompanion /hoge系のコマンドではビジュアルモードで実行するとよいでしょう。

:CodeCompanion /tests

:CodeCompanion /testsを実行すると、選択範囲のテストコードを生成してくれます。コマンド名はtests(複数形)です。

testsの実行例

TypeScriptを使って試した所、jestが使われました。

実装を見てみると、プロンプトに「言語に合わせて適切なテストフレームワークを使ってね」と書かれています。

using an appropriate testing framework for the identified programming language

フレームワークを指定したい場合は、実装をコピペして独自のプロンプトを設定しましょう(後述)。

:CodeCompanion /fix

:CodeCompanion /fixを実行すると、選択範囲のコードの修正方法をチャットで解説してくれます。

:CodeCompanion /lsp

:CodeCompanion /lspを実行すると、選択範囲のdiagnosticsを自動でまとめて解説してくれます。

lspのdiagnosticsの解説例

diagnosticsのリスト化としても優秀です。

:CodeCompanion スラッシュなし

:CodeCompanion単体で実行すると、プロンプト画面が表示されます。

プロンプト画面

質問を入力後、その内容に応じてチャットで答えたりバッファを書き換えたりします。ビジュアルモードで選択範囲を編集させたいときに便利です。

Action Palette

:CodeCompanionActionsを実行すると、登録したプロンプトやワークフローの一覧が表示されます。選択すると実行できます。

Action Paletteの例

ビジュアルモードで実行すれば、前述の/lspなども一覧に入ってきます。

カスタマイズの例

最初に紹介した設定は最低限でした。ここからはもう少しカスタマイズした例を紹介します。

返答を日本語にする

opts.languageでAIの返答を日本語に指定できます。

require("codecompanion").setup({
opts = {
language = "Japanese",
},
}),

lazy.nvimの場合、lazy.nvimのotpsとプラグインのoptsの2つがあり、片方のoptsを書き忘れる可能性があるので注意です。

lazy.nvimの例
return {
"olimorris/codecompanion.nvim",
-- ...
opts = {
opts = {
language = "Japanese",
},
}
}

/filesなどのUIをプラグインにする

次のプラグインが入っている場合、/file/bufferの選択画面でそのUIを使えます。

require("codecompanion").setup({
strategies = {
chat = {
slash_commands = {
["buffer"] = {
opts = {
provider = "snacks",
},
},
["file"] = {
opts = {
provider = "snacks",
},
},
["help"] = {
opts = {
provider = "snacks",
},
},
["symbols"] = {
opts = {
provider = "snacks",
},
},
["workspace"] = {
opts = {
provider = "snacks",
},
},
},
},
},
})

実行中にステータスラインへ反映

CodeCompanionの実行(というよりLLM周り)は時間がかかるため、ステータスラインに実行中であることを表示しておくと便利です。

動画の例では画面右下にCompanionという文字とスピナーを表示しています。

たとえばlualine.nvimの例は次のとおりです。別のファイルに書いてlualine側で呼び出しています。

lualine/cc-component.lua
local M = require("lualine.component"):extend()
M.processing = false
M.spinner_index = 1
local spinner_symbols = {
"⠋",
"⠙",
"⠹",
"⠸",
"⠼",
"⠴",
"⠦",
"⠧",
"⠇",
"⠏",
}
local spinner_symbols_len = 10
-- Initializer
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)
if request.match == "CodeCompanionRequestStarted" then
self.processing = true
elseif request.match == "CodeCompanionRequestFinished" then
self.processing = false
end
end,
})
end
-- Function that runs every time statusline is updated
function M:update_status()
if self.processing then
self.spinner_index = (self.spinner_index % spinner_symbols_len) + 1
return "Companion " .. spinner_symbols[self.spinner_index]
else
end
end
return M
lualine/index.lua
require("lualine").setup({
-- ...
sections = {
-- ...
lualine_z = {
{ require("plug/lualine/cc-component") },
},
},
})

余談ですが、筆者がインストールした時はドキュメントが古かったため、OSSコントリビュートで更新しました。

公式ドキュメント:User Interface

独自のプロンプトを設定

prompt_libraryに設定することで、独自のプロンプトを定義できます。プロンプトごとにモデルも変更できます。

require("codecompanion").setup({
-- ...
prompt_library = {
["Hoge"] = {
strategy = "chat",
description = "foo bar",
prompts = {
{
role = "system",
content = "あなたは猫です。全部の語尾ににゃーとつけてください。",
},
{
role = "user",
content = "会話を始めます。",
},
},
},
},
})

:CodeCompanionActionsから実行できます。

promptscontent文字列を返す関数を書いてもOKです。これでかなり柔軟なカスタマイズが可能です。NeovimのAPIを呼んでごにょごにょできます。

興味のある人はCreating Promptsデフォルトの設定の実装を読んでみると面白いかもしれません。

入力が面倒な人向け

@files#bufferのようなCodeCompanionのエージェント・変数・スラッシュコマンドの補完が用意されています。nvim-cmpblink.cmpなどをサポートしています。
詳しくは公式ドキュメントのCompletionを見てみましょう。

@filesなど一部のエージェントでは反映時に承認が必要です。承認が面倒な場合は次のようにグローバル変数を設定しましょう。

vim.g.codecompanion_auto_tool_mode = true

公式ドキュメント:Using Agents and Tools | CodeCompanion

環境によってAdapterを分けたい

「会社のPCではOpenAI、自宅ではGitHub Copilot」のように分けたい場合は次のように分岐してあげます。

local my_adapter = vim.env.OPENAI_API_KEY and "openai" or "copilot"
require("codecompanion").setup({
adapters = {
openai = function()
return require("codecompanion.adapters").extend("openai", {
env = { api_key = vim.env.OPENAI_API_KEY },
})
end,
},
strategies = {
chat = {
adapter = my_adapter,
},
inline = {
adapter = my_adapter,
},
agent = {
adapter = my_adapter,
},
},
})

短縮入力の設定

CodeCompanionのコマンド名はCodeCompanionから始まります。

コマンド名が少し長いため、次のように設定しておくと楽です。

vim.cmd([[cab cc CodeCompanion]])

コマンドラインでccSpaceを入力したらCodeCompanionに展開されます。

補完プラグインによっては:CCのように入力すると候補として出てくるため、わざわざ設定しなくていいかもしれません。

lazy.nvimでの遅延読み込みの設定

筆者のlayz.nvimでの遅延読み込みの設定を紹介します。

---@module "lazy"
---@type LazyPluginSpec
return {
"olimorris/codecompanion.nvim",
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-treesitter/nvim-treesitter",
},
cmd = { "CodeCompanion", "CodeCompanionActions", "CodeCompanionChat" },
keys = {
{ "<Space>cc", "<Cmd>CodeCompanionChat Toggle<CR>", mode = { "n" } },
{ "<Space>cc", "<Cmd>CodeCompanionChat<CR>", mode = { "v" } },
{ "<Space>ca", "<Cmd>CodeCompanionActions<CR>", mode = { "n", "x" } },
},
}

チャットの履歴を保存したい

チャットの履歴を保存する方法は別の記事codecompanion.nvimでチャット履歴を保存する方法にて解説しています。

トラブルシューティング

随時追記します。

Noiceを使ってたのにUIの位置が変わってしまった

2025年2月17日追記:dressing.nvimはアーカイブされました。そもそもdressing.nvimを使わないようにしましょう
というわけでこの項目は不要ですが、一応残しておきます。

noice.nvimユーザーがいきなりdressing.nvimを入れると、UIが少し変わって困惑するかもしれません。

↓dressing.nvimのプロンプトの例。カーソル位置付近に表示されます。

dressing.nvimのプロンプトの例

↓noice.nvimのプロンプトの例。画面中央に表示されます。

noice.nvimのプロンプトの例

noice.nvimを優先させたい場合、dressing.nvimの設定を変えましょう。

{
"olimorris/codecompanion.nvim",
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-treesitter/nvim-treesitter",
"nvim-telescope/telescope.nvim", -- Optional
{
"stevearc/dressing.nvim", -- Optional: Improves the default Neovim UI
opts = {
input = {
enabled = false,
},
},
},
},
-- config = ...
},

以上、CodeCompanionの紹介でした。

開発がかなり活発なようです。この記事を書いている間にも何回も更新されてました。