irongit

My nvim configuration

feat(claude): add AI-powered code search to quickfix

Adds `<leader>cf` find command that prompts Claude to search the codebase and populate the quickfix list with results. Also wires up `<leader>gx` to stage all and run Claude commit in one step.
huncholanehuncholaneauthored
parent 3334820commit 0190a9d96fa06975aab55c7ec1e57b689f47b33aBrowse files

3 files changed, +121 -0

+114-0claude-nvim/lua/claude/find.lua
@@ -0,0 +1,114 @@
1+local M = {}
2+
3+local skip_permissions = false
4+
5+function M.setup(opts)
6+ if opts and opts.skip_permissions then skip_permissions = true end
7+end
8+
9+local function strip_fences(text)
10+ local lines = vim.split(text or "", "\n", { plain = true })
11+ while #lines > 0 and lines[#lines] == "" do table.remove(lines) end
12+ if #lines >= 2 and lines[1]:match("^```") then
13+ table.remove(lines, 1)
14+ if lines[#lines] and lines[#lines]:match("^```%s*$") then
15+ table.remove(lines)
16+ end
17+ end
18+ return table.concat(lines, "\n")
19+end
20+
21+local function extract_json_array(text)
22+ local s = text:find("%[")
23+ local e = text:reverse():find("%]")
24+ if not s or not e then return text end
25+ return text:sub(s, #text - e + 1)
26+end
27+
28+local function open_popup(on_submit)
29+ local buf = vim.api.nvim_create_buf(false, true)
30+ vim.bo[buf].bufhidden = "wipe"
31+ vim.bo[buf].filetype = "markdown"
32+ local width = math.min(80, math.max(40, math.floor(vim.o.columns * 0.6)))
33+ local height = 6
34+ local win = vim.api.nvim_open_win(buf, true, {
35+ relative = "editor",
36+ row = math.floor((vim.o.lines - height) / 2),
37+ col = math.floor((vim.o.columns - width) / 2),
38+ width = width,
39+ height = height,
40+ style = "minimal",
41+ border = "rounded",
42+ title = " Claude find — <C-s> search <C-c> cancel ",
43+ title_pos = "center",
44+ })
45+ vim.cmd("startinsert")
46+ local closed = false
47+ local function close()
48+ if closed then return end
49+ closed = true
50+ pcall(vim.cmd, "stopinsert")
51+ if vim.api.nvim_win_is_valid(win) then
52+ vim.api.nvim_win_close(win, true)
53+ end
54+ end
55+ local function submit()
56+ if closed then return end
57+ local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
58+ local q = table.concat(lines, "\n"):gsub("^%s+", ""):gsub("%s+$", "")
59+ close()
60+ if q == "" then return end
61+ on_submit(q)
62+ end
63+ local km = { buffer = buf, nowait = true, silent = true }
64+ vim.keymap.set({ "n", "i" }, "<C-s>", submit, km)
65+ vim.keymap.set({ "n", "i" }, "<C-c>", close, km)
66+ vim.keymap.set("n", "q", close, km)
67+ vim.keymap.set("n", "<Esc>", close, km)
68+end
69+
70+function M.find()
71+ open_popup(function(query)
72+ vim.notify("claude: searching for '" .. query .. "'…")
73+ local prompt = "You are searching this codebase for snippets relevant to the user's query. "
74+ .. "Use the Grep, Glob, and Read tools as needed to find them. Return up to 20 results. "
75+ .. "Output ONLY a JSON array (no markdown fences, no commentary, no prose) of objects with keys: "
76+ .. 'path (absolute file path string), lnum (1-indexed integer line number), snippet (the matching line content, trimmed of leading/trailing whitespace).\n\n'
77+ .. "Query: " .. query
78+ local cmd = { "claude", "-p", prompt }
79+ if skip_permissions then table.insert(cmd, "--dangerously-skip-permissions") end
80+ vim.system(cmd, { text = true }, vim.schedule_wrap(function(out)
81+ if out.code ~= 0 then
82+ vim.notify("claude failed: " .. (out.stderr or ""), vim.log.levels.ERROR)
83+ return
84+ end
85+ local raw = strip_fences(out.stdout or "")
86+ local json_text = extract_json_array(raw)
87+ local ok, results = pcall(vim.json.decode, json_text)
88+ if not ok or type(results) ~= "table" then
89+ vim.notify("claude: could not parse JSON response", vim.log.levels.ERROR)
90+ return
91+ end
92+ local items = { { text = "▌ " .. query, valid = 0 } }
93+ for _, r in ipairs(results) do
94+ if type(r) == "table" and r.path and r.lnum then
95+ table.insert(items, {
96+ filename = tostring(r.path),
97+ lnum = tonumber(r.lnum) or 1,
98+ text = ((r.snippet or "")):gsub("^%s+", ""):gsub("%s+$", ""),
99+ })
100+ end
101+ end
102+ if #items <= 1 then
103+ vim.notify("claude: no results for '" .. query .. "'", vim.log.levels.WARN)
104+ return
105+ end
106+ vim.fn.setqflist({}, " ", { title = query, items = items })
107+ vim.cmd("copen")
108+ pcall(vim.cmd, "cc 2")
109+ pcall(function() require("claude.highlights").refresh_all() end)
110+ end))
111+ end)
112+end
113+
114+return M
+3-0claude-nvim/lua/claude/init.lua
@@ -7,6 +7,7 @@ local pickers = require("claude.pickers")
77 local edit = require("claude.edit")
88 local timetravel = require("claude.timetravel")
99 local commit = require("claude.commit")
10+local find = require("claude.find")
1011
1112 ---@class ClaudeConfig
1213 local defaults = {
@@ -31,6 +32,7 @@ function M.setup(opts)
3132 highlights.setup(opts)
3233 edit.setup(opts)
3334 commit.setup(opts)
35+ find.setup(opts)
3436
3537 vim.keymap.set("n", "<leader>c", "", { desc = "Claude" })
3638
@@ -39,6 +41,7 @@ function M.setup(opts)
3941 vim.keymap.set("n", "<leader>cu", timetravel.undo, { desc = "Claude: Undo last claude change" })
4042 vim.keymap.set("n", "<leader>cU", timetravel.redo, { desc = "Claude: Redo claude change" })
4143 vim.keymap.set("n", "<leader>cg", commit.commit, { desc = "Claude: Commit staged with generated message" })
44+ vim.keymap.set("n", "<leader>cf", find.find, { desc = "Claude: Find code snippets to quickfix" })
4245 vim.keymap.set("n", "<leader>ct", ui.toggle, { desc = "Claude: Toggle panel" })
4346 vim.keymap.set("n", "<leader>cn", ui.start_new, { desc = "Claude: New session" })
4447 vim.keymap.set("n", "<leader>cr", ui.resume_last, { desc = "Claude: Resume last session" })
+4-0lua/extensions/git-extensions.lua
@@ -141,6 +141,10 @@ M.setup = function(opts)
141141 M.register_keymap("c", ":Gitsigns setqflist<cr>", "Quickfix Current Buffer Hunks")
142142 M.register_keymap("a", ":Git add -A<cr>", "Add All Files")
143143 M.register_keymap("p", ":Git push<cr>", "Push")
144+ M.register_keymap("x", function()
145+ vim.fn.system("git add -A")
146+ require("claude.commit").commit()
147+ end, "Add All + Claude Commit")
144148
145149 -- Jump maps
146150 vim.keymap.set("n", "]h", ":Gitsigns next_hunk<cr>", { desc = "Next Hunk" })