···11+local helpers = require('utils.claude_helpers')
22+local diagnostic_helpers = require('utils.diagnostic_helpers')
33+44+local M = {}
55+66+-- Display Claude response in new buffer
77+local function show_claude_response(response)
88+ -- Create new buffer and switch to it
99+ vim.cmd('vnew')
1010+ local buf = vim.api.nvim_get_current_buf()
1111+1212+ -- Set buffer content
1313+ vim.api.nvim_buf_set_lines(buf, 0, -1, false, vim.split(response, '\n'))
1414+1515+ -- Set buffer options
1616+ vim.bo[buf].modifiable = false
1717+ vim.bo[buf].filetype = 'markdown'
1818+ vim.bo[buf].buftype = '' -- Make it a normal buffer
1919+ vim.bo[buf].bufhidden = 'hide' -- Hide instead of wipe
2020+2121+ -- Set buffer name
2222+ vim.api.nvim_buf_set_name(buf, 'Claude Response')
2323+end
2424+2525+-- Main Claude command handler
2626+local function claude_command(opts)
2727+ if not helpers.claude_available() then
2828+ vim.notify("Claude binary not found. Please install claude-code CLI.", vim.log.levels.ERROR)
2929+ return
3030+ end
3131+3232+ local user_input = table.concat(opts.fargs, ' ')
3333+ if user_input == '' then
3434+ vim.notify("Please provide a request for Claude", vim.log.levels.WARN)
3535+ return
3636+ end
3737+3838+ local file_context = helpers.get_file_context()
3939+ local selection = helpers.get_selection_info(opts.range > 0, opts.line1, opts.line2)
4040+ local prompt = helpers.build_prompt(user_input, file_context, selection, false, nil)
4141+4242+ vim.notify("Asking Claude...", vim.log.levels.INFO)
4343+4444+ helpers.execute_claude(prompt, function(response)
4545+ vim.schedule(function()
4646+ show_claude_response(response)
4747+ end)
4848+ end)
4949+end
5050+5151+-- Claude edit command handler
5252+local function claude_edit_command(opts)
5353+ if not helpers.claude_available() then
5454+ vim.notify("Claude binary not found. Please install claude-code CLI.", vim.log.levels.ERROR)
5555+ return
5656+ end
5757+5858+ local user_input = table.concat(opts.fargs, ' ')
5959+ if user_input == '' then
6060+ vim.notify("Please provide a request for Claude", vim.log.levels.WARN)
6161+ return
6262+ end
6363+6464+ local file_context = helpers.get_file_context()
6565+ local selection = helpers.get_selection_info(opts.range > 0, opts.line1, opts.line2)
6666+6767+ if not selection then
6868+ vim.notify("ClaudeEdit requires a selection. Select text first or use a range.", vim.log.levels.WARN)
6969+ return
7070+ end
7171+7272+ local prompt = helpers.build_prompt(user_input, file_context, selection, true, nil)
7373+7474+ vim.notify("Claude is editing your selection...", vim.log.levels.INFO)
7575+7676+ helpers.execute_claude(prompt, function(response)
7777+ vim.schedule(function()
7878+ helpers.replace_selection(selection, response)
7979+ vim.notify("Selection replaced by Claude", vim.log.levels.INFO)
8080+ end)
8181+ end)
8282+end
8383+8484+-- Claude fix diagnostics command handler
8585+local function claude_fix_command(opts)
8686+ if not helpers.claude_available() then
8787+ vim.notify("Claude binary not found. Please install claude-code CLI.", vim.log.levels.ERROR)
8888+ return
8989+ end
9090+9191+ local file_context = helpers.get_file_context()
9292+ local selection = helpers.get_selection_info(opts.range > 0, opts.line1, opts.line2)
9393+9494+ if not selection then
9595+ vim.notify("ClaudeFix requires a selection. Select text first or use a range.", vim.log.levels.WARN)
9696+ return
9797+ end
9898+9999+ -- Get diagnostics for the selected range
100100+ local diagnostics = diagnostic_helpers.get_range_diagnostics(0, selection.start_line, selection.end_line)
101101+102102+ if not diagnostic_helpers.has_diagnostics(0, selection.start_line, selection.end_line) then
103103+ vim.notify("No diagnostic issues found in selected range.", vim.log.levels.INFO)
104104+ return
105105+ end
106106+107107+ local diagnostic_info = diagnostic_helpers.format_diagnostics_for_prompt(diagnostics)
108108+ local user_input = table.concat(opts.fargs, ' ')
109109+ if user_input == '' then
110110+ user_input = "Fix the diagnostic issues listed above"
111111+ end
112112+113113+ local prompt = helpers.build_prompt(user_input, file_context, selection, false, diagnostic_info)
114114+115115+ vim.notify("Claude is analyzing and fixing diagnostic issues...", vim.log.levels.INFO)
116116+117117+ helpers.execute_claude_with_tools(prompt, function(response)
118118+ vim.schedule(function()
119119+ vim.notify("Claude diagnostic fix completed. Check the file for changes.", vim.log.levels.INFO)
120120+ end)
121121+ end)
122122+end
123123+124124+-- Setup function to register commands
125125+function M.setup()
126126+ -- Register :Claude command
127127+ vim.api.nvim_create_user_command('Claude', claude_command, {
128128+ nargs = '*',
129129+ range = true,
130130+ desc = 'Ask Claude for help with code context'
131131+ })
132132+133133+ -- Register :ClaudeEdit command
134134+ vim.api.nvim_create_user_command('ClaudeEdit', claude_edit_command, {
135135+ nargs = '*',
136136+ range = true,
137137+ desc = 'Ask Claude to edit selected code'
138138+ })
139139+140140+ -- Register :ClaudeFix command
141141+ vim.api.nvim_create_user_command('ClaudeFix', claude_fix_command, {
142142+ nargs = '*',
143143+ range = true,
144144+ desc = 'Ask Claude to fix diagnostic issues in selected code'
145145+ })
146146+end
147147+148148+return M
···11+local M = {}
22+33+-- Get current file context
44+function M.get_file_context()
55+ return {
66+ filepath = vim.fn.expand('%:p'),
77+ filename = vim.fn.expand('%:t'),
88+ filetype = vim.bo.filetype,
99+ line_count = vim.api.nvim_buf_line_count(0)
1010+ }
1111+end
1212+1313+-- Detect and extract visual selection
1414+function M.get_selection_info(range_given, line1, line2)
1515+ local selection = nil
1616+ if range_given then
1717+ -- Command called with range
1818+ selection = {
1919+ start_line = line1,
2020+ end_line = line2,
2121+ text = table.concat(vim.api.nvim_buf_get_lines(0, line1 - 1, line2, false), '\n')
2222+ }
2323+ elseif vim.fn.mode() == 'v' or vim.fn.mode() == 'V' then
2424+ -- Visual mode selection
2525+ local start_pos = vim.fn.getpos("'<")
2626+ local end_pos = vim.fn.getpos("'>")
2727+ selection = {
2828+ start_line = start_pos[2],
2929+ end_line = end_pos[2],
3030+ text = table.concat(vim.api.nvim_buf_get_lines(0, start_pos[2] - 1, end_pos[2], false), '\n')
3131+ }
3232+ end
3333+ return selection
3434+end
3535+3636+-- Build prompt with context
3737+function M.build_prompt(user_input, file_context, selection, is_edit_mode, diagnostic_info)
3838+ local prompt_parts = {}
3939+4040+ -- Diagnostic fix mode instruction
4141+ if diagnostic_info then
4242+ table.insert(prompt_parts,
4343+ "DIAGNOSTIC FIX MODE: Use your Edit tool to fix the following diagnostic issues in the code. Make precise fixes to resolve the specific problems listed.")
4444+ table.insert(prompt_parts, diagnostic_info)
4545+ elseif is_edit_mode then
4646+ -- Edit mode instruction
4747+ table.insert(prompt_parts,
4848+ "EDIT MODE: Return ONLY the modified code with no explanations, markdown formatting, or additional text. Any explanations should be included as code comments within the code itself. Do not surround the code with backticks.")
4949+ end
5050+5151+ -- File context
5252+ table.insert(prompt_parts, string.format("File: %s (%s)", file_context.filename, file_context.filetype))
5353+5454+ -- Selection context if present
5555+ if selection then
5656+ table.insert(prompt_parts, string.format("Selected lines %d-%d:", selection.start_line, selection.end_line))
5757+ table.insert(prompt_parts, "```" .. file_context.filetype)
5858+ table.insert(prompt_parts, selection.text)
5959+ table.insert(prompt_parts, "```")
6060+ end
6161+6262+ -- User request
6363+ table.insert(prompt_parts, "Request: " .. user_input)
6464+6565+ return table.concat(prompt_parts, '\n\n')
6666+end
6767+6868+-- Execute claude command
6969+function M.execute_claude(prompt, callback)
7070+ -- Alternative 1: Use vim.system (Neovim 0.10+) - cleaner, no shell escaping needed
7171+ vim.system({ 'claude', '-p', prompt }, {
7272+ text = true
7373+ }, function(result)
7474+ vim.schedule(function()
7575+ if result.code == 0 then
7676+ if callback then callback(result.stdout) end
7777+ else
7878+ vim.notify("Claude error: " .. (result.stderr or "Unknown error"), vim.log.levels.ERROR)
7979+ end
8080+ end)
8181+ end)
8282+end
8383+8484+-- Execute claude command with tool permissions for diagnostic fixing
8585+function M.execute_claude_with_tools(prompt, callback)
8686+ vim.system({ 'claude', '--allowedTools=Edit,Read', '-p', prompt }, {
8787+ text = true
8888+ }, function(result)
8989+ vim.schedule(function()
9090+ if result.code == 0 then
9191+ if callback then callback(result.stdout) end
9292+ else
9393+ vim.notify("Claude error: " .. (result.stderr or "Unknown error"), vim.log.levels.ERROR)
9494+ end
9595+ end)
9696+ end)
9797+end
9898+9999+-- Replace selection with new content
100100+function M.replace_selection(selection, new_content)
101101+ local lines = vim.split(new_content, '\n')
102102+ vim.api.nvim_buf_set_lines(0, selection.start_line - 1, selection.end_line, false, lines)
103103+end
104104+105105+-- Check if claude binary exists
106106+function M.claude_available()
107107+ return vim.fn.executable('claude') == 1
108108+end
109109+110110+return M
+52
nvim/lua/utils/diagnostic_helpers.lua
···11+local M = {}
22+33+-- Get diagnostics for a specific line range
44+function M.get_range_diagnostics(bufnr, start_line, end_line)
55+ local all_diagnostics = vim.diagnostic.get(bufnr or 0)
66+ local range_diagnostics = {}
77+88+ for _, diagnostic in ipairs(all_diagnostics) do
99+ -- Convert from 0-indexed to 1-indexed for user-facing line numbers
1010+ local diag_line = diagnostic.lnum + 1
1111+ if diag_line >= start_line and diag_line <= end_line then
1212+ table.insert(range_diagnostics, diagnostic)
1313+ end
1414+ end
1515+1616+ return range_diagnostics
1717+end
1818+1919+-- Format diagnostics for Claude prompt
2020+function M.format_diagnostics_for_prompt(diagnostics)
2121+ if #diagnostics == 0 then
2222+ return "No diagnostics found in the selected range."
2323+ end
2424+2525+ local severity_names = {
2626+ [vim.diagnostic.severity.ERROR] = "ERROR",
2727+ [vim.diagnostic.severity.WARN] = "WARNING",
2828+ [vim.diagnostic.severity.INFO] = "INFO",
2929+ [vim.diagnostic.severity.HINT] = "HINT"
3030+ }
3131+3232+ local formatted_lines = {}
3333+ table.insert(formatted_lines, "Diagnostic Issues:")
3434+3535+ for _, diagnostic in ipairs(diagnostics) do
3636+ local line_num = diagnostic.lnum + 1 -- Convert to 1-indexed
3737+ local severity = severity_names[diagnostic.severity] or "UNKNOWN"
3838+ local message = diagnostic.message:gsub("\n", " ") -- Remove newlines from message
3939+4040+ table.insert(formatted_lines, string.format("Line %d: [%s] %s", line_num, severity, message))
4141+ end
4242+4343+ return table.concat(formatted_lines, "\n")
4444+end
4545+4646+-- Quick check if any diagnostics exist in range
4747+function M.has_diagnostics(bufnr, start_line, end_line)
4848+ local diagnostics = M.get_range_diagnostics(bufnr, start_line, end_line)
4949+ return #diagnostics > 0
5050+end
5151+5252+return M