My nvim configuration
idk
5 files changed, +98 -9
+9-0claude-nvim/lua/claude/init.lua
| @@ -20,6 +20,15 @@ local defaults = { | ||
| 20 | 20 | --- Max number of history entries surfaced by the prompts/sessions pickers. |
| 21 | 21 | --- nil or 0 means unlimited (default: nil). |
| 22 | 22 | history_limit = nil, |
| 23 | + --- Floating prompt window size. Each value is either an absolute cell count | |
| 24 | + --- (> 1) or a fraction of the editor (<= 1). | |
| 25 | + prompt = { | |
| 26 | + width = 100, | |
| 27 | + height = 16, | |
| 28 | + }, | |
| 29 | + --- Context-window size (tokens) used to compute the usage % shown in the | |
| 30 | + --- prompt title and panel winbar. Default 200k. | |
| 31 | + context_window = 200000, | |
| 23 | 32 | } |
| 24 | 33 | |
| 25 | 34 | --- Setup the Claude CLI wrapper plugin. |
+9-0claude-nvim/lua/claude/runner.lua
| @@ -133,6 +133,7 @@ local function handle_event(sid, evt) | ||
| 133 | 133 | end |
| 134 | 134 | elseif t == "assistant" and evt.message then |
| 135 | 135 | local msg = evt.message |
| 136 | + if msg.usage then st.last_usage = msg.usage end | |
| 136 | 137 | if msg.content then |
| 137 | 138 | for _, block in ipairs(msg.content) do |
| 138 | 139 | if block.type == "text" and block.text then |
| @@ -199,8 +200,10 @@ summary = input.pattern or "" | ||
| 199 | 200 | elseif t == "user" and evt.message then |
| 200 | 201 | -- tool results, ignore content for now |
| 201 | 202 | elseif t == "result" then |
| 203 | + if evt.usage and not st.last_usage then st.last_usage = evt.usage end | |
| 202 | 204 | flush_prompt(sid) |
| 203 | 205 | ui.append_separator(sid) |
| 206 | + ui.update_context(sid, st.last_usage) | |
| 204 | 207 | elseif t == "stream_event" then |
| 205 | 208 | local e = evt.event |
| 206 | 209 | if e and e.delta and e.delta.text then |
| @@ -378,6 +381,12 @@ function M.get_session(sid) | ||
| 378 | 381 | return st and st.session or nil |
| 379 | 382 | end |
| 380 | 383 | |
| 384 | +--- Latest usage block (input/cache token counts) seen for a session, if any. | |
| 385 | +function M.get_usage(sid) | |
| 386 | + local st = active[sid] | |
| 387 | + return st and st.last_usage or nil | |
| 388 | +end | |
| 389 | + | |
| 381 | 390 | function M.cancel(sid) |
| 382 | 391 | local st = active[sid] |
| 383 | 392 | if not st or not st.job then return false end |
+61-4claude-nvim/lua/claude/ui.lua
| @@ -2,6 +2,35 @@ local M = {} | ||
| 2 | 2 | |
| 3 | 3 | local default_width = 80 |
| 4 | 4 | |
| 5 | +-- prompt window size + context-window size, overridden in M.setup | |
| 6 | +local config = { | |
| 7 | + prompt = { width = 100, height = 16 }, | |
| 8 | + context_window = 200000, | |
| 9 | +} | |
| 10 | + | |
| 11 | +-- Resolve a dimension that is either absolute (> 1) or a fraction (<= 1). | |
| 12 | +local function resolve_dim(val, total, lo, hi) | |
| 13 | + local n | |
| 14 | + if val and val <= 1 then | |
| 15 | + n = math.floor(total * val) | |
| 16 | + else | |
| 17 | + n = math.floor(val or 0) | |
| 18 | + end | |
| 19 | + return math.max(lo, math.min(n, hi)) | |
| 20 | +end | |
| 21 | + | |
| 22 | +-- "ctx 45.2k/200k (23%)" from a Claude usage block, or nil if unknown. | |
| 23 | +local function format_ctx(usage) | |
| 24 | + if not usage then return nil end | |
| 25 | + local used = (usage.input_tokens or 0) | |
| 26 | + + (usage.cache_read_input_tokens or 0) | |
| 27 | + + (usage.cache_creation_input_tokens or 0) | |
| 28 | + if used <= 0 then return nil end | |
| 29 | + local win = config.context_window | |
| 30 | + local pct = math.floor(used / win * 100 + 0.5) | |
| 31 | + return string.format("ctx %.1fk/%dk (%d%%)", used / 1000, math.floor(win / 1000), pct) | |
| 32 | +end | |
| 33 | + | |
| 5 | 34 | -- panels keyed by tabpage handle |
| 6 | 35 | local panels = {} |
| 7 | 36 | |
| @@ -49,6 +78,17 @@ local function spinner_text(p) | ||
| 49 | 78 | return SPINNER_FRAMES[p.spinner.frame] .. " thinking…" |
| 50 | 79 | end |
| 51 | 80 | |
| 81 | +local function update_winbar(p) | |
| 82 | + if not p.win or not vim.api.nvim_win_is_valid(p.win) then return end | |
| 83 | + local ctx = format_ctx(p.ctx_usage) | |
| 84 | + if ctx then | |
| 85 | + -- escape % for winbar's statusline syntax | |
| 86 | + vim.wo[p.win].winbar = " Claude %=" .. ctx:gsub("%%", "%%%%") .. " " | |
| 87 | + else | |
| 88 | + vim.wo[p.win].winbar = " Claude " | |
| 89 | + end | |
| 90 | +end | |
| 91 | + | |
| 52 | 92 | local function scroll_to_bottom_now(p) |
| 53 | 93 | if not p.autoscroll then return end |
| 54 | 94 | if p.win and vim.api.nvim_win_is_valid(p.win) then |
| @@ -181,6 +221,11 @@ end | ||
| 181 | 221 | |
| 182 | 222 | function M.setup(opts) |
| 183 | 223 | default_width = opts.width or 80 |
| 224 | + if opts.prompt then | |
| 225 | + config.prompt.width = opts.prompt.width or config.prompt.width | |
| 226 | + config.prompt.height = opts.prompt.height or config.prompt.height | |
| 227 | + end | |
| 228 | + config.context_window = opts.context_window or config.context_window | |
| 184 | 229 | |
| 185 | 230 | vim.api.nvim_create_autocmd("TabClosed", { |
| 186 | 231 | callback = function(ev) |
| @@ -210,6 +255,7 @@ function M.open_panel() | ||
| 210 | 255 | vim.wo[p.win].linebreak = true |
| 211 | 256 | vim.wo[p.win].number = false |
| 212 | 257 | vim.wo[p.win].relativenumber = false |
| 258 | + update_winbar(p) | |
| 213 | 259 | p.autoscroll = true |
| 214 | 260 | |
| 215 | 261 | vim.api.nvim_create_autocmd("WinScrolled", { |
| @@ -445,8 +491,11 @@ function M.open_input(opts) | ||
| 445 | 491 | local context = opts.context |
| 446 | 492 | local input_buf = ensure_prompt_buf() |
| 447 | 493 | |
| 448 | - local width = math.min(80, math.max(40, math.floor(vim.o.columns * 0.6))) | |
| 449 | - local height = 8 | |
| 494 | + local width = resolve_dim(config.prompt.width, vim.o.columns, 40, vim.o.columns - 4) | |
| 495 | + local height = resolve_dim(config.prompt.height, vim.o.lines, 6, vim.o.lines - 4) | |
| 496 | + local ctx = format_ctx(require("claude.runner").get_usage(sid)) | |
| 497 | + local base = context and "Claude prompt (with context)" or "Claude prompt" | |
| 498 | + local title = " " .. base .. (ctx and (" · " .. ctx) or "") .. " — <C-s> send <C-c> cancel " | |
| 450 | 499 | local win = vim.api.nvim_open_win(input_buf, true, { |
| 451 | 500 | relative = "editor", |
| 452 | 501 | row = math.floor((vim.o.lines - height) / 2), |
| @@ -455,8 +504,7 @@ function M.open_input(opts) | ||
| 455 | 504 | height = height, |
| 456 | 505 | style = "minimal", |
| 457 | 506 | border = "rounded", |
| 458 | - title = context and " Claude prompt (with context) — <C-s> send <C-c> cancel " | |
| 459 | - or " Claude prompt — <C-s> send <C-c> cancel ", | |
| 507 | + title = title, | |
| 460 | 508 | title_pos = "center", |
| 461 | 509 | }) |
| 462 | 510 | local lc = vim.api.nvim_buf_line_count(input_buf) |
| @@ -704,6 +752,15 @@ function M.on_prompt_complete(sid, prompt, had_changes) | ||
| 704 | 752 | end) |
| 705 | 753 | end |
| 706 | 754 | |
| 755 | +function M.update_context(sid, usage) | |
| 756 | + vim.schedule(function() | |
| 757 | + local p = find_panel_by_sid(sid) | |
| 758 | + if not p then return end | |
| 759 | + p.ctx_usage = usage | |
| 760 | + update_winbar(p) | |
| 761 | + end) | |
| 762 | +end | |
| 763 | + | |
| 707 | 764 | function M.current_sid() |
| 708 | 765 | return get_panel().sid |
| 709 | 766 | end |
+1-0lazy-lock.json
| @@ -3,6 +3,7 @@ | ||
| 3 | 3 | "blink.cmp": { "branch": "main", "commit": "5ee69ea52492fa038b3a1cd8de55edffe205c77d" }, |
| 4 | 4 | "blink.lib": { "branch": "main", "commit": "2cb62890cee636fe46722d03b7b2331e54fb3755" }, |
| 5 | 5 | "bruno.nvim": { "branch": "main", "commit": "54bdebc62aa6ffc7906464a20d182c0a660be836" }, |
| 6 | + "claudecode.nvim": { "branch": "main", "commit": "8ec321279d3d9eddb0ce5f52d4b0d051dafb5743" }, | |
| 6 | 7 | "crates.nvim": { "branch": "main", "commit": "afcd1cc3eeceb5783676fc8464389b9216a29d05" }, |
| 7 | 8 | "dotenv.nvim": { "branch": "main", "commit": "7d516e9293c6e3ac21830fb10a4e8674c02747c6" }, |
| 8 | 9 | "flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" }, |
+18-5lua/plugins/treesitter.lua
| @@ -1,19 +1,32 @@ | ||
| 1 | 1 | ---@type LazySpec |
| 2 | 2 | return { |
| 3 | 3 | "nvim-treesitter/nvim-treesitter", |
| 4 | - branch = 'main', | |
| 4 | + branch = "main", | |
| 5 | 5 | lazy = false, |
| 6 | 6 | build = ":TSUpdate", |
| 7 | 7 | config = function() |
| 8 | 8 | require("nvim-treesitter.config").setup({ |
| 9 | - ensure_installed = { "c", "lua", "vim", "vimdoc", "json", "python", "rust", "typescript", "markdown", "bash", "http", "astro" }, | |
| 9 | + ensure_installed = { | |
| 10 | + "c", | |
| 11 | + "lua", | |
| 12 | + "vim", | |
| 13 | + "vimdoc", | |
| 14 | + "json", | |
| 15 | + "python", | |
| 16 | + "rust", | |
| 17 | + "typescript", | |
| 18 | + "markdown", | |
| 19 | + "bash", | |
| 20 | + "http", | |
| 21 | + "astro", | |
| 22 | + }, | |
| 10 | 23 | highlight = { |
| 11 | - enable = true | |
| 24 | + enable = true, | |
| 12 | 25 | }, |
| 13 | 26 | sync_install = false, |
| 14 | 27 | auto_install = true, |
| 15 | 28 | ignore_install = {}, |
| 16 | - modules = {} | |
| 29 | + modules = {}, | |
| 17 | 30 | }) |
| 18 | - end | |
| 31 | + end, | |
| 19 | 32 | } |