irongit

My nvim configuration

feat(claude): insert similar to edit with ci

huncholanehuncholaneauthored
parent f4458b3commit f870716e7b40f06e824eb96f577d5049cbdc3e3cBrowse files

2 files changed, +113 -5

+112-5claude-nvim/lua/claude/edit.lua
@@ -24,17 +24,18 @@ local function strip_fences(text)
2424 return lines
2525 end
2626
27-local function start_spinner(buf, row)
27+local function start_spinner(buf, row, label)
28+ label = label and label ~= "" and (" " .. label) or ""
2829 local idx = 1
2930 local function virt(i)
30- return { { " " .. spinner_frames[i] .. " claude…", "Comment" } }
31+ return { { " " .. spinner_frames[i] .. " claude…" .. label, "Comment" } }
3132 end
3233 local ok, mark = pcall(vim.api.nvim_buf_set_extmark, buf, ns, row, 0, {
3334 virt_text = virt(idx),
3435 virt_text_pos = "eol",
3536 hl_mode = "combine",
3637 })
37- if not ok then return function() end end
38+ if not ok then return function() end, nil end
3839
3940 local timer = vim.uv.new_timer()
4041 timer:start(80, 80, vim.schedule_wrap(function()
@@ -51,12 +52,13 @@ local function start_spinner(buf, row)
5152 })
5253 end))
5354
54- return function()
55+ local stop = function()
5556 if not timer:is_closing() then
5657 timer:stop(); timer:close()
5758 end
5859 pcall(vim.api.nvim_buf_del_extmark, buf, ns, mark)
5960 end
61+ return stop, mark
6062 end
6163
6264 local function run_claude(prompt, selection, on_done)
@@ -138,7 +140,8 @@ function M.prompt_visual()
138140 end
139141
140142 if not vim.api.nvim_buf_is_valid(target_buf) then return end
141- local stop_spin = start_spinner(target_buf, start_row)
143+ local label = (prompt:gsub("%s+", " ")):sub(1, 60)
144+ local stop_spin = start_spinner(target_buf, start_row, label)
142145
143146 run_claude(prompt, selection, function(stdout, err)
144147 stop_spin()
@@ -169,4 +172,108 @@ function M.prompt_visual()
169172 })
170173 end
171174
175+local function run_claude_insert(prompt, on_done)
176+ local full = string.format(
177+ "Generate code for the following instruction. Output ONLY the code to be inserted, with no explanation, no commentary, no markdown code fences.\n\nInstruction:\n%s",
178+ prompt
179+ )
180+ local cmd = { "claude", "-p", full }
181+ if skip_permissions then
182+ table.insert(cmd, "--dangerously-skip-permissions")
183+ end
184+ vim.system(cmd, { text = true }, vim.schedule_wrap(function(out)
185+ if out.code ~= 0 then
186+ on_done(nil, (out.stderr ~= "" and out.stderr) or ("claude exited " .. tostring(out.code)))
187+ else
188+ on_done(out.stdout or "", nil)
189+ end
190+ end))
191+end
192+
193+function M.prompt_insert()
194+ local target_buf = vim.api.nvim_get_current_buf()
195+ local cur = vim.api.nvim_win_get_cursor(0)
196+ local row = cur[1] - 1
197+
198+ local pop_buf = vim.api.nvim_create_buf(false, true)
199+ vim.bo[pop_buf].bufhidden = "wipe"
200+ vim.bo[pop_buf].filetype = "markdown"
201+
202+ local width = math.min(80, math.max(40, math.floor(vim.o.columns * 0.6)))
203+ local height = 8
204+ local pop_win = vim.api.nvim_open_win(pop_buf, true, {
205+ relative = "editor",
206+ row = math.floor((vim.o.lines - height) / 2),
207+ col = math.floor((vim.o.columns - width) / 2),
208+ width = width,
209+ height = height,
210+ style = "minimal",
211+ border = "rounded",
212+ title = " Claude insert — <C-s> apply <C-c> cancel ",
213+ title_pos = "center",
214+ })
215+
216+ vim.cmd("startinsert")
217+
218+ local closed = false
219+ local function close_popup()
220+ if closed then return end
221+ closed = true
222+ pcall(vim.cmd, "stopinsert")
223+ if vim.api.nvim_win_is_valid(pop_win) then
224+ vim.api.nvim_win_close(pop_win, true)
225+ end
226+ end
227+
228+ local function apply()
229+ if closed then return end
230+ local lines = vim.api.nvim_buf_get_lines(pop_buf, 0, -1, false)
231+ local prompt = table.concat(lines, "\n")
232+ close_popup()
233+ if prompt:match("^%s*$") then
234+ vim.notify("claude: empty prompt", vim.log.levels.WARN)
235+ return
236+ end
237+
238+ if not vim.api.nvim_buf_is_valid(target_buf) then return end
239+
240+ -- Place a tracking extmark at the insertion row so position survives upstream edits
241+ local anchor = vim.api.nvim_buf_set_extmark(target_buf, ns, row, 0, {})
242+ local label = (prompt:gsub("%s+", " ")):sub(1, 60)
243+ local stop_spin = start_spinner(target_buf, row, label)
244+
245+ run_claude_insert(prompt, function(stdout, err)
246+ stop_spin()
247+ if err then
248+ pcall(vim.api.nvim_buf_del_extmark, target_buf, ns, anchor)
249+ vim.notify("claude: " .. err, vim.log.levels.ERROR)
250+ return
251+ end
252+ if not vim.api.nvim_buf_is_valid(target_buf) then return end
253+ local out_lines = strip_fences(stdout or "")
254+ if #out_lines == 0 then
255+ pcall(vim.api.nvim_buf_del_extmark, target_buf, ns, anchor)
256+ vim.notify("claude: empty response", vim.log.levels.WARN)
257+ return
258+ end
259+ local pos = vim.api.nvim_buf_get_extmark_by_id(target_buf, ns, anchor, {})
260+ pcall(vim.api.nvim_buf_del_extmark, target_buf, ns, anchor)
261+ local insert_at = (pos and pos[1] or row) + 1
262+ pcall(vim.api.nvim_buf_set_lines, target_buf, insert_at, insert_at, false, out_lines)
263+ end)
264+ end
265+
266+ local km = { buffer = pop_buf, nowait = true, silent = true }
267+ vim.keymap.set({ "n", "i" }, "<C-s>", apply, km)
268+ vim.keymap.set({ "n", "i" }, "<C-c>", close_popup, km)
269+ vim.keymap.set("n", "q", close_popup, km)
270+ vim.keymap.set("n", "<Esc>", close_popup, km)
271+
272+ vim.api.nvim_create_autocmd("BufLeave", {
273+ buffer = pop_buf,
274+ once = true,
275+ callback = close_popup,
276+ })
277+end
278+
172279 return M
+1-0claude-nvim/lua/claude/init.lua
@@ -32,6 +32,7 @@ function M.setup(opts)
3232 vim.keymap.set("n", "<leader>c", "", { desc = "Claude" })
3333
3434 vim.keymap.set("n", "<leader>cc", ui.cancel_current, { desc = "Claude: Cancel current prompt" })
35+ vim.keymap.set("n", "<leader>ci", edit.prompt_insert, { desc = "Claude: Insert at cursor with prompt" })
3536 vim.keymap.set("n", "<leader>ct", ui.toggle, { desc = "Claude: Toggle panel" })
3637 vim.keymap.set("n", "<leader>cn", ui.start_new, { desc = "Claude: New session" })
3738 vim.keymap.set("n", "<leader>cr", ui.resume_last, { desc = "Claude: Resume last session" })