My nvim configuration
fix(claude): scroll to see spinner
2 files changed, +37 -20
+3-2claude-nvim/lua/claude/runner.lua
| @@ -240,7 +240,8 @@ function M.is_busy(sid) | ||
| 240 | 240 | return st and st.job ~= nil |
| 241 | 241 | end |
| 242 | 242 | |
| 243 | -function M.send(sid, prompt_text) | |
| 243 | +function M.send(sid, prompt_text, opts) | |
| 244 | + opts = opts or {} | |
| 244 | 245 | local st = active[sid] |
| 245 | 246 | if not st then return false, "no session" end |
| 246 | 247 | if st.job then return false, "previous prompt still running" end |
| @@ -260,7 +261,7 @@ function M.send(sid, prompt_text) | ||
| 260 | 261 | table.insert(cmd, prompt_text) |
| 261 | 262 | |
| 262 | 263 | local ok_u, ui = pcall(require, "claude.ui") |
| 263 | - if ok_u then ui.append_user_prompt(sid, prompt_text) end | |
| 264 | + if ok_u and not opts.skip_ui_prompt then ui.append_user_prompt(sid, prompt_text) end | |
| 264 | 265 | |
| 265 | 266 | log("CMD", table.concat(cmd, " | ")) |
| 266 | 267 |
+34-18claude-nvim/lua/claude/ui.lua
| @@ -8,23 +8,30 @@ local panel = { | ||
| 8 | 8 | } |
| 9 | 9 | |
| 10 | 10 | local spinner = { |
| 11 | - ns = vim.api.nvim_create_namespace("claude_spinner"), | |
| 12 | 11 | timer = nil, |
| 13 | - mark_id = nil, | |
| 12 | + line = nil, | |
| 14 | 13 | frame = 1, |
| 15 | 14 | frames = { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }, |
| 16 | 15 | } |
| 17 | 16 | |
| 18 | 17 | local function spinner_render() |
| 19 | 18 | 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 | 19 | 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 | - }) | |
| 20 | + vim.bo[panel.buf].modifiable = true | |
| 21 | + local appended = false | |
| 22 | + if spinner.line ~= nil and spinner.line < vim.api.nvim_buf_line_count(panel.buf) then | |
| 23 | + pcall(vim.api.nvim_buf_set_lines, panel.buf, spinner.line, spinner.line + 1, false, { txt }) | |
| 24 | + else | |
| 25 | + local lc = vim.api.nvim_buf_line_count(panel.buf) | |
| 26 | + pcall(vim.api.nvim_buf_set_lines, panel.buf, lc, lc, false, { txt }) | |
| 27 | + spinner.line = lc | |
| 28 | + appended = true | |
| 29 | + end | |
| 30 | + vim.bo[panel.buf].modifiable = false | |
| 31 | + if appended and panel.win and vim.api.nvim_win_is_valid(panel.win) then | |
| 32 | + local lc = vim.api.nvim_buf_line_count(panel.buf) | |
| 33 | + pcall(vim.api.nvim_win_set_cursor, panel.win, { lc, 0 }) | |
| 34 | + end | |
| 28 | 35 | end |
| 29 | 36 | |
| 30 | 37 | local function spinner_stop() |
| @@ -33,10 +40,14 @@ local function spinner_stop() | ||
| 33 | 40 | if not spinner.timer:is_closing() then spinner.timer:close() end |
| 34 | 41 | spinner.timer = nil |
| 35 | 42 | 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) | |
| 43 | + if spinner.line ~= nil and panel.buf and vim.api.nvim_buf_is_valid(panel.buf) then | |
| 44 | + if spinner.line < vim.api.nvim_buf_line_count(panel.buf) then | |
| 45 | + vim.bo[panel.buf].modifiable = true | |
| 46 | + pcall(vim.api.nvim_buf_set_lines, panel.buf, spinner.line, spinner.line + 1, false, {}) | |
| 47 | + vim.bo[panel.buf].modifiable = false | |
| 48 | + end | |
| 38 | 49 | end |
| 39 | - spinner.mark_id = nil | |
| 50 | + spinner.line = nil | |
| 40 | 51 | end |
| 41 | 52 | |
| 42 | 53 | local function spinner_start() |
| @@ -44,7 +55,7 @@ local function spinner_start() | ||
| 44 | 55 | spinner.frame = 1 |
| 45 | 56 | spinner_render() |
| 46 | 57 | spinner.timer = vim.uv.new_timer() |
| 47 | - spinner.timer:start(0, 90, vim.schedule_wrap(function() | |
| 58 | + spinner.timer:start(90, 90, vim.schedule_wrap(function() | |
| 48 | 59 | spinner.frame = (spinner.frame % #spinner.frames) + 1 |
| 49 | 60 | spinner_render() |
| 50 | 61 | end)) |
| @@ -307,13 +318,14 @@ function M.open_input(opts) | ||
| 307 | 318 | if closed then return end |
| 308 | 319 | local lines = vim.api.nvim_buf_get_lines(input_buf, 0, -1, false) |
| 309 | 320 | local text = table.concat(lines, "\n") |
| 310 | - close() | |
| 311 | - if text:match("^%s*$") then return end | |
| 321 | + if text:match("^%s*$") then close(); return end | |
| 312 | 322 | if context and context ~= "" then |
| 313 | 323 | text = context .. "\n\n" .. text |
| 314 | 324 | end |
| 325 | + M.append_user_prompt(sid, text) | |
| 326 | + close() | |
| 315 | 327 | local runner = require("claude.runner") |
| 316 | - local ok, err = runner.send(sid, text) | |
| 328 | + local ok, err = runner.send(sid, text, { skip_ui_prompt = true }) | |
| 317 | 329 | if not ok then |
| 318 | 330 | vim.notify("claude: " .. tostring(err), vim.log.levels.WARN) |
| 319 | 331 | end |
| @@ -385,17 +397,21 @@ function M.append_user_prompt(sid, text) | ||
| 385 | 397 | table.insert(lines, "") |
| 386 | 398 | append_lines(lines) |
| 387 | 399 | spinner_start() |
| 388 | - vim.cmd("redraw") | |
| 400 | + pcall(vim.cmd, "redraw!") | |
| 389 | 401 | end |
| 390 | 402 | |
| 391 | 403 | function M.append_assistant_text(sid, text) |
| 392 | 404 | if sid ~= panel.sid then return end |
| 393 | - vim.schedule(function() append_text_streaming(text) end) | |
| 405 | + vim.schedule(function() | |
| 406 | + spinner_stop() | |
| 407 | + append_text_streaming(text) | |
| 408 | + end) | |
| 394 | 409 | end |
| 395 | 410 | |
| 396 | 411 | function M.append_tool_use(sid, name, path) |
| 397 | 412 | if sid ~= panel.sid then return end |
| 398 | 413 | vim.schedule(function() |
| 414 | + spinner_stop() | |
| 399 | 415 | append_lines({ "", "> **" .. name .. "** `" .. path .. "`", "" }) |
| 400 | 416 | end) |
| 401 | 417 | end |