NeovimでLLMを動かすcodecompanion.nvimの使い方
この記事はVim駅伝の2024年8月30日の記事です。
前回(2024年8月28日)の記事はteramakoさんのNeovim nvim-cmp の補完ソースプラグインと gin.vim の action 補完ソースを書いた話でした。
今回はLLMをNeovimで使えるようにするcodecompanion.nvimを紹介します。
codecompanion.nvimとは
LLMを使ってNeovim上でチャットしたり、指示内容に合わせてバッファを書き換えるプラグインです。
記事執筆時(2024年8月28日)ではOllama、OpenAI、Copilot、Gemini、Anthropicをサポートしています。
プロンプトを渡す際に、間にLuaの処理を挟むことで柔軟に定義できます。
たとえば、「LSPのdiagnosticsをまとめてAIに解説してもらうプロンプト」がデフォルトで定義されています(上記の動画)。
導入方法
curl
が必要です。Neovimのバージョンは0.92以上のみ対応しています。
プラグインのインストール
パッケージマネージャーでインストールします。
olimorris/codecompanion.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 = {}, }, }, -- config = ...},
いくつか依存がありますが、Optional
とあるとおりtelescopeとdressingは無くても動きます。
モデルの導入
各自動かしたいモデルやAPIキーを用意します。
この記事では、CodeCompanionで動かすモデルとしてOllamaのcodellama:7bを使います。
次のコマンドでモデルをダウンロードできます。
ollama run codellama:7b
まずは簡単な設定
用意したモデルを使うための最低限の設定をしていきます。
adapters
adapters
で使用するLLMのツールやモデルを指定します。
require("codecompanion").setup({ adapters = { ollama = function() return require("codecompanion.adapters").extend("ollama", { schema = { model = { default = "codellama:7b", }, }, }) end, }, -- ...})
OpenAIのAPIキーやOllamaのリモートサーバーのURLもadapters
で指定できます。
strategies
strategies
にどの機能でどのadapterを使うか指定します。
require("codecompanion").setup({ -- ... strategies = { chat = { adapter = "ollama", }, inline = { adapter = "ollama", }, agent = { adapter = "ollama", }, },})
チャット機能ではchat
に指定したadapterが使われます。:CodeCompanion 〇〇
のようなチャット以外ではinline
のadapterが使われます。
agent
は後で説明します。
短縮入力の設定
CodeCompanionのコマンド名はCodeCompanion
から始まります。
コマンド名が少し長いため、次のように設定しておくと楽です。
vim.cmd([[cab cc CodeCompanion]])
コマンドラインでccSpaceを入力したらCodeCompanion
に展開されます。
使い方
CodeCompanionの機能とコマンドを紹介します。
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
以外にもあります。
共有したい部分をビジュアルモードで選択してコマンド:'<,'>CodeCompanionAdd
を実行すれば、チャットに入力内容が挿入されます。
CodeCompanion
:CodeCompanion /hoge
系のコマンドを実行すると、あらかじめ設定したプロンプトを実行できます。
/explain
:CodeCompanion /explain
を実行すると、チャットで選択範囲の内容を説明してくれます。
ノーマルモードでは現在の行のみが対象となってしまうため、:CodeCompanion /hoge
系のコマンドではビジュアルモードで実行するとよいでしょう。
/tests
:CodeCompanion /tests
を実行すると、選択範囲のテストコードを生成してくれます。コマンド名はtests
(複数形)です。

TypeScriptを使って試した所、jestが使われました。
実装を見てみると、プロンプトに「言語に合わせて適切なテストフレームワークを使ってね」と書かれています。
using an appropriate testing framework for the identified programming language
フレームワークを指定したい場合は、実装をコピペして独自のプロンプトを設定しましょう(後述)。
/fix
:CodeCompanion /fix
を実行すると、選択範囲のコードの修正方法をチャットで解説してくれます。
/buffer
:CodeCompanion /buffer 〇〇
を実行すると、指示内容に基づいて挙動が変化します。
リファクタリングしてほしい範囲を選択してから次のように実行すると、バッファが書き換わります。
:'<,'>CodeCompanion /buffer refactor this
一方、次のようにチャットで答えてねと指示すると、バッファの内容に基づいてチャットで答えてくれます。
:'<,'>CodeCompanion /buffer explain this. answer at chat
※ バッファを書き換えるのか、チャットで答えればいいのかを判断できずにエラーになることもしばしばあります。
/lsp
:CodeCompanion /lsp
を実行すると、選択範囲のdiagnosticsを自動でまとめて解説してくれます。

diagnosticsのリスト化としても優秀です。
スラッシュなし
:CodeCompanion
単体で実行すると、プロンプト画面が表示されます。

質問を入力後、その内容に応じてチャットで答えたりバッファを書き換えたりします。
Agents / Tools
外部ツールを呼び出してエージェントとして動かす機能もあります。@code_runner
、@rag
、@buffer_editor
の3つがサポートされています。
たとえばDockerのコンテナでコードを実行するには、チャットでメンション風に@code_runner
を添えます。
@code_runner can you run some code for me?
筆者の環境ではうまく動かせていないため、動かせるようになったら別途記事を書きます。
Action Palette
:CodeCompanionActions
を実行すると、登録したプロンプトの一覧が表示され、選択すると実行できます。

ビジュアルモードで実行すれば、前述の/lsp
なども一覧に入ってきます。
上記の画像はtelescope.nvimを使っています。telescope.nvim無しでも動きますが絞り込みはできません。
設定の詳細
最初に紹介した設定は最低限でした。ここからはもう少しカスタマイズした例を紹介します。
実行中にステータスラインへ反映
CodeCompanionの実行(というよりLLM周り)は時間がかかるため、ステータスラインに実行中であることを表示しておくと便利です。
動画の例では画面右下にCompanion
という文字とスピナーを表示しています。
たとえばlualine.nvimの例は次のとおりです。別のファイルに書いてlualine側で呼び出しています。
local M = require("lualine.component"):extend()
M.processing = falseM.spinner_index = 1
local spinner_symbols = { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏",}local spinner_symbols_len = 10
-- Initializerfunction 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 updatedfunction 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 endend
return M
require("lualine").setup({ -- ... sections = { -- ... lualine_z = { { require("plug/lualine/cc-component") }, }, },})
余談ですが、筆者がインストールした時はドキュメントが古かったため、OSSコントリビュートで更新しました。
独自のプロンプトを設定
default_prompts
に設定することで、独自のプロンプトを定義できます。プロンプトごとにモデルも変更できます。
require("codecompanion").setup({ -- ... default_prompts = { ["Hoge"] = { strategy = "chat", description = "foo bar", opts = { adapter = { name = "ollama", model = "elyza:jp8b", }, }, prompts = { { role = "system", content = "あなたは猫です。全部の語尾ににゃーとつけてください。", }, { role = "user", content = "会話を始めます。", }, }, }, },})
prompts
にプロンプトを書いていきます。
prompts
のcontent
は文字列を返す関数を書いてもOKです。これでかなり柔軟なカスタマイズが可能です。NeovimのAPIを読んでごにょごにょできます。
興味のある人はRECIPES.mdやデフォルトの設定の実装を読んでみると面白いかもしれません。
トラブルシューティング
随時追記します。
結果が出ない
次のようなメッセージが表示され、結果がどこにも反映されないことがあります。
Could not determine where to place the output from the prompt
メッセージのとおり、そもそも結果をどこに反映させればいいか判断できないことが原因です。
プロンプトで”answer at chat”や”replace”などの単語を含めましょう(それでもたまにうまくいかない)。
Noiceを使ってたのにUIの位置が変わってしまった
noice.nvimユーザーがいきなりdressing.nvimを入れると、UIが少し変わって困惑するかもしれません。
↓dressing.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の紹介でした。
開発がかなり活発なようです。この記事を書いている間にも何回も更新されてました。