## 経緯 [[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)