codecompanion.nvimでチャット履歴を保存する方法

NeovimのAIプラグインcodecompanion.nvimでチャットの履歴を保存する方法を解説します。

CodeCompanion全体の使い方は別の記事NeovimのAIプラグインcodecompanion.nvimの使い方に書きました。まだ使ったこと無い人はまずはこちらをご覧ください。

プラグインとしては実装されていない

codecompanion.nvimではチャットの履歴は実装されていません。実は以前は実装されていましたが廃止されました。他の機能を実装する時間が削られてしまうからだそうです。
詳しくはDiscussion#139 Save and restore chatsをご覧ください。

実装が紹介されている!

上記のDiscussionにて、コマンドの実装が紹介されていました。itsfrankさんのcodecompanion-save.luaです。
チャット履歴をMarkdownとして保存し、選択UIでそのMarkdownファイルをすぐに表示できる、というものです。

元の実装ではtelescope.nvimを選択UIとして使っていました。
筆者はこれを参考にsnacks.nvimで動くようにしてみました。更新日が新しい順にソートしてます。

チャット履歴の保存

:CodeCompanionSave fooと実行するとfoo.mdとして保存されます。
保存先は~/.local/share/nvim/cc_saves/です。

local Path = require("plenary.path")
local data_path = vim.fn.stdpath("data")
local save_folder = Path:new(data_path, "cc_saves")
if not save_folder:exists() then
save_folder:mkdir({ parents = true })
end
vim.api.nvim_create_user_command("CodeCompanionSave", function(opts)
local codecompanion = require("codecompanion")
local success, chat = pcall(function()
return codecompanion.buf_get_chat(0)
end)
if not success or chat == nil then
vim.notify("CodeCompanionSave should only be called from CodeCompanion chat buffers", vim.log.levels.ERROR)
return
end
if #opts.fargs == 0 then
vim.notify("CodeCompanionSave requires at least 1 arg to make a file name", vim.log.levels.ERROR)
end
local save_name = table.concat(opts.fargs, "-") .. ".md"
local save_path = Path:new(save_folder, save_name)
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
save_path:write(table.concat(lines, "\n"), "w")
end, { nargs = "*" })

「保存先を変えたい」「ファイル名を自動で付けたい」という人はハイライト箇所を変更してください。

チャット履歴の閲覧

続いて「チャット履歴ファイルを開くコマンド」の実装です。

要となるのがハイライトの部分です。

vim.api.nvim_create_user_command("CodeCompanionLoad", function()
local cwd = save_folder:absolute()
require("snacks.picker").files({
cwd = cwd,
transform = function(item)
item.cwd = cwd
item.file = item.text
item.mtime = Path:new(save_folder, item.text):_stat().mtime.sec
item.title = item.text .. " | 更新日: " .. os.date("%Y-%m-%d %H:%M", item.mtime)
end,
matcher = { sort_empty = true },
sort = { fields = { "mtime:desc" } },
prompt_title = "Saved CodeCompanion Chats",
win = {
input = {
keys = {
["dd"] = { "delete_file", mode = { "n" } },
},
},
},
actions = {
delete_file = function(the_picker)
the_picker.preview:reset()
for _, item in ipairs(the_picker:selected({ fallback = true })) do
if item.file then
os.remove(item.cwd .. "/" .. item.file)
end
end
the_picker.list:set_selected()
the_picker.list:set_target()
the_picker:find()
end,
},
})
end, {})

やっていることは意外と単純です。ざっくりと次のとおりです。

  • 保存ファイルを更新日でソートして読み込む
  • ddで削除できるようにキーマップを設定

チャットを継続させたい場合

:CodeCompanionLoadはあくまでも保存した履歴ファイルを開くためのコマンドです。そのままチャットとしては継続できません
代わりに、そのファイルのバッファをスラッシュコマンド/bufferで読み込めば、AIに共有できます。

スラッシュコマンドは別の記事NeovimのAIプラグインcodecompanion.nvimの使い方で解説しています。

遅延読み込みに設定

せっかくなのでプラグインの遅延読み込みで:CodeCompanionLoadを指定しておきます。

次のコードはlazy.nvimでの設定例です。

---@module "lazy"
---@type LazyPluginSpec
return {
"olimorris/codecompanion.nvim",
cmd = {
"CodeCompanion",
"CodeCompanionActions",
"CodeCompanionChat",
"CodeCompanionCmd",
"CodeCompanionLoad"
},
-- ...
}

nbに保存しておくといいかも

メモ管理のCLIであるnbで保存しておくものいいでしょう。

nbを知らない人はエンジニアのためのメモ管理CLIツールnbの使い方をご覧ください。

保存先をnbにするのもありです。何でもかんでも保存したいわけではないため、筆者は次のようによく見る履歴だけをnbに入れています。

Terminal window
cat ~/.local/share/nvim/cc_saves/create-divided-openapi-yaml.md | nb a

ファイルの命名は困るのでnb aを使ってタイムスタンプにしています。

ファイル名がそのままでいい人はnb importを使いましょう。

Terminal window
nb import ~/.local/share/nvim/cc_saves/create-divided-openapi-yaml.md

ファイル名のコピーが面倒な人は次のようなキーマップを仕込んでおきましょう。

vim.keymap.set(
"n",
"sgf",
"<Cmd>let @+=expand('%')<CR>:echo 'Clipboard << ' . @+<CR>",
{ desc = "現在のファイル名をyank", silent = true }
)

以上、codecompanion.nvimでのチャット履歴の実装と解説でした。