From 4d82324eca2c1312848f9b357851662c7a69bad1 Mon Sep 17 00:00:00 2001 From: zolinthecow Date: Wed, 9 Jul 2025 23:26:07 -0700 Subject: [PATCH] claude-baseline-1752128767 --- LICENSE.md | 4 + lua/nvim-claude/hooks.lua | 46 ++++++-- lua/nvim-claude/inline-diff.lua | 188 ++++++++++++++++++++++++-------- 3 files changed, 182 insertions(+), 56 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 9cf10627..b90f8244 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -17,3 +17,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +test line added by user + +TEST: This line should trigger inline diff automatically via hooks! diff --git a/lua/nvim-claude/hooks.lua b/lua/nvim-claude/hooks.lua index 20a391a0..d5719bf5 100644 --- a/lua/nvim-claude/hooks.lua +++ b/lua/nvim-claude/hooks.lua @@ -227,16 +227,31 @@ function M.test_inline_diff() local relative_path = buf_name:gsub(git_root .. '/', '') vim.notify('Testing inline diff for: ' .. relative_path, vim.log.levels.INFO) - -- Get baseline content - local baseline_ref = utils.read_file '/tmp/claude-baseline-commit' or 'HEAD' - baseline_ref = baseline_ref:gsub('%s+', '') + -- Get baseline content - check for updated baseline first + local inline_diff = require 'nvim-claude.inline-diff' + local original_content = nil - local baseline_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, baseline_ref, relative_path) - local original_content, orig_err = utils.exec(baseline_cmd) + -- Check if we have an updated baseline in memory + vim.notify('DEBUG: Checking for baseline in buffer ' .. bufnr, vim.log.levels.INFO) + vim.notify('DEBUG: Available baselines: ' .. vim.inspect(vim.tbl_keys(inline_diff.original_content)), vim.log.levels.INFO) - if orig_err then - vim.notify('Failed to get baseline content: ' .. orig_err, vim.log.levels.ERROR) - return + if inline_diff.original_content[bufnr] then + original_content = inline_diff.original_content[bufnr] + vim.notify('Using updated baseline from memory (length: ' .. #original_content .. ')', vim.log.levels.INFO) + else + -- Fall back to git baseline + local baseline_ref = utils.read_file '/tmp/claude-baseline-commit' or 'HEAD' + baseline_ref = baseline_ref:gsub('%s+', '') + + local baseline_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, baseline_ref, relative_path) + local git_err + original_content, git_err = utils.exec(baseline_cmd) + + if git_err then + vim.notify('Failed to get baseline content: ' .. git_err, vim.log.levels.ERROR) + return + end + vim.notify('Using git baseline', vim.log.levels.INFO) end -- Get current content @@ -244,7 +259,6 @@ function M.test_inline_diff() local current_content = table.concat(current_lines, '\n') -- Show inline diff - local inline_diff = require 'nvim-claude.inline-diff' inline_diff.show_inline_diff(bufnr, original_content, current_content) end @@ -387,6 +401,19 @@ function M.setup_commands() desc = 'Test Claude keymap functionality', }) + vim.api.nvim_create_user_command('ClaudeUpdateBaseline', function() + local bufnr = vim.api.nvim_get_current_buf() + local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local current_content = table.concat(current_lines, '\n') + + local inline_diff = require 'nvim-claude.inline-diff' + inline_diff.original_content[bufnr] = current_content + + vim.notify('Baseline updated to current buffer state', vim.log.levels.INFO) + end, { + desc = 'Update Claude baseline to current buffer state', + }) + vim.api.nvim_create_user_command('ClaudeTestDiff', function() local utils = require 'nvim-claude.utils' @@ -494,4 +521,3 @@ function M.cleanup_old_commits() end return M - diff --git a/lua/nvim-claude/inline-diff.lua b/lua/nvim-claude/inline-diff.lua index 5426bc95..49e0a4bf 100644 --- a/lua/nvim-claude/inline-diff.lua +++ b/lua/nvim-claude/inline-diff.lua @@ -118,34 +118,45 @@ function M.apply_diff_visualization(bufnr) -- Apply highlights for each hunk for i, hunk in ipairs(diff_data.hunks) do - -- Track additions and deletions separately with proper line mapping - local current_new_line = hunk.new_start - 1 -- 0-indexed, tracks position in current buffer - local current_old_line = hunk.old_start - 1 -- 0-indexed, tracks position in old content + + -- Track which lines in the current buffer correspond to additions/deletions + local additions = {} local deletions = {} + -- Start from the beginning of the hunk and track line numbers + local new_line_num = hunk.new_start -- 1-indexed line number in new file + local old_line_num = hunk.old_start -- 1-indexed line number in old file + for _, diff_line in ipairs(hunk.lines) do if diff_line:match('^%+') then - -- This is an added line - highlight it in the current buffer - if current_new_line >= 0 and current_new_line < #buf_lines then - vim.api.nvim_buf_set_extmark(bufnr, ns_id, current_new_line, 0, { - line_hl_group = 'DiffAdd', - id = 4000 + i * 1000 + current_new_line - }) - end - current_new_line = current_new_line + 1 - -- Don't advance old_line for additions + -- This is an added line - it exists in the current buffer at new_line_num + table.insert(additions, new_line_num - 1) -- Convert to 0-indexed for extmarks + new_line_num = new_line_num + 1 + -- Don't advance old_line_num for additions elseif diff_line:match('^%-') then -- This is a deleted line - show as virtual text above current position table.insert(deletions, { - line = current_new_line, -- Show deletion above current position + line = new_line_num - 1, -- 0-indexed, show above current position text = diff_line:sub(2), }) - current_old_line = current_old_line + 1 - -- Don't advance new_line for deletions - else + old_line_num = old_line_num + 1 + -- Don't advance new_line_num for deletions + elseif diff_line:match('^%s') then -- Context line - advance both - current_new_line = current_new_line + 1 - current_old_line = current_old_line + 1 + new_line_num = new_line_num + 1 + old_line_num = old_line_num + 1 + end + end + + -- Apply highlighting for additions + for _, line_idx in ipairs(additions) do + if line_idx >= 0 and line_idx < #buf_lines then + vim.api.nvim_buf_set_extmark(bufnr, ns_id, line_idx, 0, { + line_hl_group = 'DiffAdd', + id = 4000 + i * 1000 + line_idx + }) + else + vim.notify('Line ' .. line_idx .. ' out of range (buf has ' .. #buf_lines .. ' lines)', vim.log.levels.WARN) end end @@ -167,8 +178,16 @@ function M.apply_diff_visualization(bufnr) end end - -- Add sign in gutter for hunk - local sign_line = hunk.new_start - 1 + -- Add sign in gutter for hunk (use first addition or deletion line) + local sign_line = nil + if #additions > 0 then + sign_line = additions[1] + elseif #deletions > 0 then + sign_line = deletions[1].line + else + sign_line = hunk.new_start - 1 + end + local sign_text = '>' local sign_hl = 'DiffAdd' @@ -178,7 +197,7 @@ function M.apply_diff_visualization(bufnr) sign_hl = 'DiffChange' end - if sign_line >= 0 and sign_line < #buf_lines then + if sign_line and sign_line >= 0 and sign_line < #buf_lines then vim.api.nvim_buf_set_extmark(bufnr, ns_id, sign_line, 0, { sign_text = sign_text, sign_hl_group = sign_hl, @@ -186,9 +205,9 @@ function M.apply_diff_visualization(bufnr) }) end - -- Add subtle hunk info at end of first line - local info_line = hunk.new_start - 1 - if info_line >= 0 and info_line < #buf_lines then + -- Add subtle hunk info at end of first changed line + local info_line = sign_line + if info_line and info_line >= 0 and info_line < #buf_lines then vim.api.nvim_buf_set_extmark(bufnr, ns_id, info_line, 0, { virt_text = {{' [Hunk ' .. i .. '/' .. #diff_data.hunks .. ']', 'Comment'}}, virt_text_pos = 'eol', @@ -233,8 +252,32 @@ function M.jump_to_hunk(bufnr, hunk_idx) local hunk = diff_data.hunks[hunk_idx] diff_data.current_hunk = hunk_idx - -- Move cursor to hunk start - vim.api.nvim_win_set_cursor(0, {hunk.old_start, 0}) + -- Find the first actual changed line (addition or deletion) in this hunk + local jump_line = nil + local new_line_num = hunk.new_start -- 1-indexed line number in new file + + for _, diff_line in ipairs(hunk.lines) do + if diff_line:match('^%+') then + -- Found an addition - jump here + jump_line = new_line_num + break + elseif diff_line:match('^%-') then + -- Found a deletion - jump here + jump_line = new_line_num + break + elseif diff_line:match('^%s') then + -- Context line - advance + new_line_num = new_line_num + 1 + end + end + + -- Fallback to hunk start if no changes found + if not jump_line then + jump_line = hunk.new_start + end + + -- Move cursor to the actual changed line + vim.api.nvim_win_set_cursor(0, {jump_line, 0}) -- Update status vim.notify(string.format('Hunk %d/%d', hunk_idx, #diff_data.hunks), vim.log.levels.INFO) @@ -274,19 +317,31 @@ function M.accept_current_hunk(bufnr) local hunk = diff_data.hunks[diff_data.current_hunk] if not hunk then return end - -- Apply the hunk changes - M.apply_hunk_changes(bufnr, hunk) + -- Update the baseline to include this accepted change + M.update_baseline_after_accept(bufnr, hunk) - -- Mark as applied + -- Mark as applied (the changes are already in the buffer) diff_data.applied_hunks[diff_data.current_hunk] = true - -- Refresh visualization - M.apply_diff_visualization(bufnr) + -- Remove this hunk from the diff data since it's accepted + table.remove(diff_data.hunks, diff_data.current_hunk) - vim.notify(string.format('Accepted hunk %d/%d', diff_data.current_hunk, #diff_data.hunks), vim.log.levels.INFO) + -- Adjust current hunk index + if diff_data.current_hunk > #diff_data.hunks then + diff_data.current_hunk = math.max(1, #diff_data.hunks) + end + + vim.notify(string.format('Accepted hunk - %d hunks remaining', #diff_data.hunks), vim.log.levels.INFO) - -- Move to next hunk - M.next_hunk(bufnr) + if #diff_data.hunks == 0 then + -- No more hunks to review + vim.notify('All hunks processed! Closing inline diff.', vim.log.levels.INFO) + M.close_inline_diff(bufnr, true) -- Keep baseline for future diffs + else + -- Refresh visualization and move to current hunk + M.apply_diff_visualization(bufnr) + M.jump_to_hunk(bufnr, diff_data.current_hunk) + end end -- Reject current hunk @@ -300,10 +355,31 @@ function M.reject_current_hunk(bufnr) -- Revert the hunk by applying original content M.revert_hunk_changes(bufnr, hunk) - vim.notify(string.format('Rejected hunk %d/%d', diff_data.current_hunk, #diff_data.hunks), vim.log.levels.INFO) + -- Update baseline to the state after rejection + local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local current_content = table.concat(current_lines, '\n') + M.original_content[bufnr] = current_content + vim.notify('Baseline updated after rejection', vim.log.levels.INFO) + + -- Remove this hunk from the diff data since it's rejected + table.remove(diff_data.hunks, diff_data.current_hunk) - -- Move to next hunk - M.next_hunk(bufnr) + -- Adjust current hunk index + if diff_data.current_hunk > #diff_data.hunks then + diff_data.current_hunk = math.max(1, #diff_data.hunks) + end + + vim.notify(string.format('Rejected hunk - %d hunks remaining', #diff_data.hunks), vim.log.levels.INFO) + + if #diff_data.hunks == 0 then + -- No more hunks to review + vim.notify('All hunks processed! Closing inline diff.', vim.log.levels.INFO) + M.close_inline_diff(bufnr, true) -- Keep baseline after rejection too + else + -- Refresh visualization and move to current hunk + M.apply_diff_visualization(bufnr) + M.jump_to_hunk(bufnr, diff_data.current_hunk) + end end -- Revert hunk changes (restore original content) @@ -418,22 +494,26 @@ function M.reject_all_hunks(bufnr) end -- Close inline diff mode -function M.close_inline_diff(bufnr) +function M.close_inline_diff(bufnr, keep_baseline) -- Clear highlights and virtual text vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1) -- Remove buffer-local keymaps - vim.keymap.del('n', ']h', { buffer = bufnr }) - vim.keymap.del('n', '[h', { buffer = bufnr }) - vim.keymap.del('n', 'ia', { buffer = bufnr }) - vim.keymap.del('n', 'ir', { buffer = bufnr }) - vim.keymap.del('n', 'iA', { buffer = bufnr }) - vim.keymap.del('n', 'iR', { buffer = bufnr }) - vim.keymap.del('n', 'iq', { buffer = bufnr }) + pcall(vim.keymap.del, 'n', ']h', { buffer = bufnr }) + pcall(vim.keymap.del, 'n', '[h', { buffer = bufnr }) + pcall(vim.keymap.del, 'n', 'ia', { buffer = bufnr }) + pcall(vim.keymap.del, 'n', 'ir', { buffer = bufnr }) + pcall(vim.keymap.del, 'n', 'iA', { buffer = bufnr }) + pcall(vim.keymap.del, 'n', 'iR', { buffer = bufnr }) + pcall(vim.keymap.del, 'n', 'iq', { buffer = bufnr }) -- Clean up state M.active_diffs[bufnr] = nil - M.original_content[bufnr] = nil + + -- Only clear baseline if not explicitly told to keep it + if not keep_baseline then + M.original_content[bufnr] = nil + end vim.notify('Inline diff closed', vim.log.levels.INFO) end @@ -443,6 +523,22 @@ function M.has_active_diff(bufnr) return M.active_diffs[bufnr] ~= nil end +-- Update baseline content after accepting a hunk +function M.update_baseline_after_accept(bufnr, hunk) + -- Get current content (which now includes the accepted change) + local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local current_content = table.concat(current_lines, '\n') + + vim.notify('DEBUG: Updating baseline for buffer ' .. bufnr, vim.log.levels.INFO) + vim.notify('DEBUG: New baseline content length: ' .. #current_content, vim.log.levels.INFO) + + -- Update the stored original content to match current content + -- This way, future diffs will compare against this new baseline + M.original_content[bufnr] = current_content + + vim.notify('Baseline updated to include accepted changes', vim.log.levels.INFO) +end + -- Test keymap functionality function M.test_keymap() local bufnr = vim.api.nvim_get_current_buf()