## 経緯
[[CodeCompanion]]を実行中、思考が長いモデルについては結果が表示されるまで中身が更新されないので不安になる。また、[[Azure OpenAI Service]]の[[o1]]モデルはstreamに対応していなかったので、表示までに時間がかかる。絶賛処理中であることを明示的に分かるようにしたい。
## スピナーコードを書く
[[CodeCompanion]]のドキュメントにサンプルコードが載っている。
<div class="link-card-v2">
<div class="link-card-v2-site">
<span class="link-card-v2-site-name">codecompanion.olimorris.dev</span>
</div>
<div class="link-card-v2-title">
User Interface | CodeCompanion
</div>
<div class="link-card-v2-content">
AI-powered coding, seamlessly in Neovim
</div>
<a href="https://codecompanion.olimorris.dev/usage/ui"></a>
</div>
[[Fidget]]を使う方法と[[lualine.nvim]]を使う方法を紹介する。
### Fidgetを使う
サンプルコードをそのまま使う。
`nvim/lua/codecompanion/fidget-spinner.lua`
```lua
local progress = require("fidget.progress")
local M = {}
function M:init()
local group = vim.api.nvim_create_augroup("CodeCompanionFidgetHooks", {})
vim.api.nvim_create_autocmd({ "User" }, {
pattern = "CodeCompanionRequestStarted",
group = group,
callback = function(request)
local handle = M:create_progress_handle(request)
M:store_progress_handle(request.data.id, handle)
end,
})
vim.api.nvim_create_autocmd({ "User" }, {
pattern = "CodeCompanionRequestFinished",
group = group,
callback = function(request)
local handle = M:pop_progress_handle(request.data.id)
if handle then
M:report_exit_status(handle, request)
handle:finish()
end
end,
})
end
M.handles = {}
function M:store_progress_handle(id, handle)
M.handles[id] = handle
end
function M:pop_progress_handle(id)
local handle = M.handles[id]
M.handles[id] = nil
return handle
end
function M:create_progress_handle(request)
return progress.handle.create({
title = " Requesting assistance (" .. request.data.strategy .. ")",
message = "In progress...",
lsp_client = {
name = M:llm_role_title(request.data.adapter),
},
})
end
function M:llm_role_title(adapter)
local parts = {}
table.insert(parts, adapter.formatted_name)
if adapter.model and adapter.model ~= "" then
table.insert(parts, "(" .. adapter.model .. ")")
end
return table.concat(parts, " ")
end
function M:report_exit_status(handle, request)
if request.data.status == "success" then
handle.message = "Completed"
elseif request.data.status == "error" then
handle.message = " Error"
else
handle.message = " Cancelled"
end
end
return M
```
[[CodeCompanion]]の設定に以下を追加。
```diff
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-treesitter/nvim-treesitter",
+ "j-hui/fidget.nvim",
},
+ config = function()
+ require("codecompanion.fidget-spinner"):init()
+ end,
```
> [!caution]
> 実際に設定例では `config` ではなく `init` を使っていたので、そうでないと動かない可能性がある。しかし、`init` に設定するとlazy loadができなくなり 5ms ほど起動が遅くなるので `config` に設定した。
### lualine.nvimを使う
少し改造する。
`nvim/lua/lualine/cc-component.lua`
```lua
local M = require("lualine.component"):extend()
M.active_requests = {} -- リクエストIDを追跡するテーブル
M.spinner_index = 1
local spinner_symbols = {
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
}
local spinner_symbols_len = #spinner_symbols
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
if request.data and request.data.id then
-- リクエストIDを保存
self.active_requests[request.data.id] = true
end
elseif request.match == "CodeCompanionRequestFinished" then
if request.data and request.data.id then
-- 完了したリクエストをリストから削除
self.active_requests[request.data.id] = nil
end
end
end,
})
end
function M:update_status()
-- アクティブなリクエストがあればスピナーを回す
local has_active_requests = false
for _, _ in pairs(self.active_requests) do
has_active_requests = true
break
end
if has_active_requests then
self.spinner_index = (self.spinner_index % spinner_symbols_len) + 1
return spinner_symbols[self.spinner_index]
else
return nil
end
end
return M
```
具体的な変更点は以下。
- スピナーのシンボル変更
- [[ステータスライン (Neovim)|ステータスライン]]の左端に表示
- **[[インラインアシスタント (CodeCompanion)|インラインアシスタント]]から[[チャットバッファ (CodeCompanion)|チャットバッファ]]を開いた場合** でも動作する
- 公式のサンプルコードでは動作しない
#### lualine.nvimに設定する
[[lualine.nvim]]の設定に含める。設定が長いので差分だけdiff形式で差分を表示。
```diff
return {
"nvim-lualine/lualine.nvim",
dependencies = { "nvim-web-devicons", opt = true },
event = { "BufNewFile", "BufRead" },
opts = function()
local theme_base = {
a = { fg = "#1b1d2b", bg = "#82aaff", gui = "bold" },
b = { fg = "#82aaff", bg = "#3b4261" },
c = { fg = "#828bb8", bg = "#1e2030" },
}
local theme_base_active = {
a = { fg = "#efef33", bg = "#888888", gui = "bold" },
b = { fg = "#82aaff", bg = "#3b4261" },
c = { fg = "#828bb8", bg = "#1e2030" },
}
local custom_theme = {
normal = theme_base_active,
insert = theme_base_active,
visual = theme_base_active,
replace = theme_base,
command = theme_base,
inactive = theme_base,
}
return {
options = {
theme = custom_theme,
component_separators = {},
section_separators = {},
disabled_filetypes = {
statusline = { "no-neck-pain" },
winbar = { "no-neck-pain", "Avante", "AvanteInput" },
},
},
winbar = {
lualine_a = {},
lualine_b = {
{ "filename", file_status = false, newfile_status = false, path = 1 },
},
lualine_c = {
{ "diff", symbols = { added = " ", modified = " ", removed = " " } },
},
lualine_x = { { "diagnostics", sources = { "nvim_lsp" } } },
lualine_y = {
{ "filetype", icon_only = true },
},
lualine_z = {
{
"filename",
newfile_status = true,
symbols = {
modified = " ",
readonly = " ",
},
},
},
},
inactive_winbar = {
lualine_a = {},
lualine_b = {
{ "filename", file_status = false, newfile_status = false, path = 1 },
},
lualine_c = {
{ "diff", symbols = { added = " ", modified = " ", removed = " " } },
},
lualine_x = { { "diagnostics", sources = { "nvim_lsp" } } },
lualine_y = {
{ "filetype", icon_only = true },
},
lualine_z = {
{
"filename",
newfile_status = true,
symbols = {
modified = " ",
readonly = " ",
},
},
},
},
sections = {
lualine_a = {},
lualine_b = {},
- lualine_c = { require("lualine/cc-component") },
+ lualine_c = { require("lualine/cc-component") },
lualine_x = { { "filename", path = 3 } },
lualine_y = { "encoding", "fileformat" },
lualine_z = {},
},
}
end,
}
```
[[lualine.nvim]]のこの設定は他の用途でも使えそう。
## 参考
- [NeovimでLLMを動かすcodecompanion.nvimの使い方](https://eiji.page/blog/neovim-codecompanion-intro/#%E5%AE%9F%E8%A1%8C%E4%B8%AD%E3%81%AB%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%B3%E3%81%B8%E5%8F%8D%E6%98%A0)