My nvim configuration
feat(claude): thinking animation in chat window
1 file changed, +49 -1
+49-1claude-nvim/lua/claude/ui.lua
| @@ -7,6 +7,49 @@ local panel = { | ||
| 7 | 7 | width = 80, |
| 8 | 8 | } |
| 9 | 9 | |
| 10 | +local spinner = { | |
| 11 | + ns = vim.api.nvim_create_namespace("claude_spinner"), | |
| 12 | + timer = nil, | |
| 13 | + mark_id = nil, | |
| 14 | + frame = 1, | |
| 15 | + frames = { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }, | |
| 16 | +} | |
| 17 | + | |
| 18 | +local function spinner_render() | |
| 19 | + if not panel.buf or not vim.api.nvim_buf_is_valid(panel.buf) then return end | |
| 20 | + local lc = vim.api.nvim_buf_line_count(panel.buf) | |
| 21 | + local line = math.max(0, lc - 1) | |
| 22 | + local txt = spinner.frames[spinner.frame] .. " thinking…" | |
| 23 | + spinner.mark_id = vim.api.nvim_buf_set_extmark(panel.buf, spinner.ns, line, 0, { | |
| 24 | + id = spinner.mark_id, | |
| 25 | + virt_lines = { { { txt, "Comment" } } }, | |
| 26 | + virt_lines_above = false, | |
| 27 | + }) | |
| 28 | +end | |
| 29 | + | |
| 30 | +local function spinner_stop() | |
| 31 | + if spinner.timer then | |
| 32 | + spinner.timer:stop() | |
| 33 | + if not spinner.timer:is_closing() then spinner.timer:close() end | |
| 34 | + spinner.timer = nil | |
| 35 | + end | |
| 36 | + if spinner.mark_id and panel.buf and vim.api.nvim_buf_is_valid(panel.buf) then | |
| 37 | + pcall(vim.api.nvim_buf_del_extmark, panel.buf, spinner.ns, spinner.mark_id) | |
| 38 | + end | |
| 39 | + spinner.mark_id = nil | |
| 40 | +end | |
| 41 | + | |
| 42 | +local function spinner_start() | |
| 43 | + spinner_stop() | |
| 44 | + spinner.frame = 1 | |
| 45 | + spinner_render() | |
| 46 | + spinner.timer = vim.uv.new_timer() | |
| 47 | + spinner.timer:start(0, 90, vim.schedule_wrap(function() | |
| 48 | + spinner.frame = (spinner.frame % #spinner.frames) + 1 | |
| 49 | + spinner_render() | |
| 50 | + end)) | |
| 51 | +end | |
| 52 | + | |
| 10 | 53 | local function ensure_buf() |
| 11 | 54 | if panel.buf and vim.api.nvim_buf_is_valid(panel.buf) then |
| 12 | 55 | return panel.buf |
| @@ -302,6 +345,7 @@ function M.append_user_prompt(sid, text) | ||
| 302 | 345 | table.insert(lines, "## Claude") |
| 303 | 346 | table.insert(lines, "") |
| 304 | 347 | append_lines(lines) |
| 348 | + spinner_start() | |
| 305 | 349 | end) |
| 306 | 350 | end |
| 307 | 351 | |
| @@ -319,7 +363,10 @@ end | ||
| 319 | 363 | |
| 320 | 364 | function M.append_separator(sid) |
| 321 | 365 | if sid ~= panel.sid then return end |
| 322 | - vim.schedule(function() append_lines({ "", "---", "" }) end) | |
| 366 | + vim.schedule(function() | |
| 367 | + spinner_stop() | |
| 368 | + append_lines({ "", "---", "" }) | |
| 369 | + end) | |
| 323 | 370 | end |
| 324 | 371 | |
| 325 | 372 | function M.append_stderr(sid, line) |
| @@ -329,6 +376,7 @@ end | ||
| 329 | 376 | |
| 330 | 377 | function M.on_prompt_complete(sid, prompt, had_changes) |
| 331 | 378 | if sid ~= panel.sid then return end |
| 379 | + vim.schedule(function() spinner_stop() end) | |
| 332 | 380 | if had_changes then |
| 333 | 381 | vim.schedule(function() |
| 334 | 382 | append_lines({ "_recorded — " .. #(prompt.files or {}) .. " file(s) changed_", "" }) |