From 91c24b34a6b9de8e91291b31fdefda181cedf53e Mon Sep 17 00:00:00 2001 From: zolinthecow Date: Thu, 10 Jul 2025 11:18:10 -0700 Subject: [PATCH] claude-baseline-1752171490 --- LICENSE.md | 3 + lua/nvim-claude/diff-review.lua | 424 ++++++++++---------- lua/nvim-claude/hooks.lua | 182 +++------ lua/nvim-claude/inline-diff-persistence.lua | 266 ++++++++++++ lua/nvim-claude/inline-diff.lua | 170 ++------ 5 files changed, 581 insertions(+), 464 deletions(-) create mode 100644 lua/nvim-claude/inline-diff-persistence.lua diff --git a/LICENSE.md b/LICENSE.md index 98e7438f..12dc00d1 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -98,3 +98,6 @@ SEPARATED HUNK #4: Final test with better hunk separation! MULTI-EDIT TEST #4: Testing hunk navigation with ]h and [h! +## PERSISTENCE TEST +This tests our new stash-based persistence system that doesn't create git commits! + diff --git a/lua/nvim-claude/diff-review.lua b/lua/nvim-claude/diff-review.lua index f19d0452..6981e4d9 100644 --- a/lua/nvim-claude/diff-review.lua +++ b/lua/nvim-claude/diff-review.lua @@ -7,7 +7,7 @@ M.current_review = M.current_review or nil function M.setup() -- Set up keybindings M.setup_keybindings() - + vim.notify('Diff review system loaded (using diffview.nvim)', vim.log.levels.DEBUG) end @@ -17,33 +17,29 @@ function M.handle_claude_edit(stash_ref, pre_edit_ref) vim.notify('No stash reference provided for diff review', vim.log.levels.ERROR) return end - + vim.notify('Processing Claude edit with stash: ' .. stash_ref, vim.log.levels.INFO) - + -- Get list of changed files local changed_files = M.get_changed_files(stash_ref) if not changed_files or #changed_files == 0 then vim.notify('No changes detected from Claude edit', vim.log.levels.INFO) return end - + -- Initialize review session M.current_review = { stash_ref = stash_ref, - pre_edit_ref = pre_edit_ref, -- Store the pre-edit commit reference + pre_edit_ref = pre_edit_ref, -- Store the pre-edit commit reference timestamp = os.time(), changed_files = changed_files, } - + -- Notify user about changes - vim.notify(string.format( - 'Claude made changes to %d file(s): %s', - #changed_files, - table.concat(changed_files, ', ') - ), vim.log.levels.INFO) - + vim.notify(string.format('Claude made changes to %d file(s): %s', #changed_files, table.concat(changed_files, ', ')), vim.log.levels.INFO) + vim.notify('Use dd to open diffview, df for fugitive, dc to clear review', vim.log.levels.INFO) - + -- Automatically open diffview M.open_diffview() end @@ -54,31 +50,28 @@ function M.handle_claude_stashes(baseline_ref) vim.notify('No baseline reference provided for Claude stashes', vim.log.levels.ERROR) return end - + vim.notify('Showing Claude stashes against baseline: ' .. baseline_ref, vim.log.levels.INFO) - + -- Get Claude stashes local claude_stashes = M.get_claude_stashes() if not claude_stashes or #claude_stashes == 0 then vim.notify('No Claude stashes found', vim.log.levels.INFO) return end - + -- Initialize review session for Claude stashes M.current_review = { baseline_ref = baseline_ref, timestamp = os.time(), claude_stashes = claude_stashes, current_stash_index = 0, -- Show cumulative view by default - is_stash_based = true + is_stash_based = true, } - + -- Notify user about changes - vim.notify(string.format( - 'Found %d Claude stash(es). Use dd for cumulative view, dh to browse.', - #claude_stashes - ), vim.log.levels.INFO) - + vim.notify(string.format('Found %d Claude stash(es). Use dd for cumulative view, dh to browse.', #claude_stashes), vim.log.levels.INFO) + -- Automatically open cumulative stash view M.open_cumulative_stash_view() end @@ -91,16 +84,16 @@ end -- Get list of files changed in the stash function M.get_changed_files(stash_ref) - local utils = require('nvim-claude.utils') + local utils = require 'nvim-claude.utils' local cmd = string.format('git stash show %s --name-only', stash_ref) local result = utils.exec(cmd) - + if not result or result == '' then return {} end - + local files = {} - for line in result:gmatch('[^\n]+') do + for line in result:gmatch '[^\n]+' do if line ~= '' then table.insert(files, line) end @@ -110,16 +103,16 @@ end -- Get list of files changed since baseline function M.get_changed_files_since_baseline(baseline_ref) - local utils = require('nvim-claude.utils') + local utils = require 'nvim-claude.utils' local cmd = string.format('git diff --name-only %s', baseline_ref) local result = utils.exec(cmd) - + if not result or result == '' then return {} end - + local files = {} - for line in result:gmatch('[^\n]+') do + for line in result:gmatch '[^\n]+' do if line ~= '' then table.insert(files, line) end @@ -129,22 +122,22 @@ end -- Get Claude stashes (only stashes with [claude-edit] messages) function M.get_claude_stashes() - local utils = require('nvim-claude.utils') + local utils = require 'nvim-claude.utils' local cmd = 'git stash list' local result = utils.exec(cmd) - + if not result or result == '' then return {} end - + local stashes = {} - for line in result:gmatch('[^\n]+') do - if line ~= '' and line:match('%[claude%-edit%]') then - local stash_ref = line:match('^(stash@{%d+})') + for line in result:gmatch '[^\n]+' do + if line ~= '' and line:match '%[claude%-edit%]' then + local stash_ref = line:match '^(stash@{%d+})' if stash_ref then table.insert(stashes, { ref = stash_ref, - message = line:match(': (.+)$') or line + message = line:match ': (.+)$' or line, }) end end @@ -161,15 +154,15 @@ function M.setup_keybindings() vim.keymap.set('n', 'dl', M.list_changes, { desc = 'List Claude changed files' }) vim.keymap.set('n', 'da', M.accept_changes, { desc = 'Accept all Claude changes' }) vim.keymap.set('n', 'dr', M.decline_changes, { desc = 'Decline all Claude changes' }) - + -- Stash browsing vim.keymap.set('n', 'dh', M.browse_claude_stashes, { desc = 'Browse Claude stash history' }) vim.keymap.set('n', 'dp', M.previous_stash, { desc = 'View previous Claude stash' }) vim.keymap.set('n', 'dn', M.next_stash, { desc = 'View next Claude stash' }) - + -- Unified view vim.keymap.set('n', 'du', M.open_unified_view, { desc = 'Open Claude diff in unified view' }) - + -- Hunk operations vim.keymap.set('n', 'dka', M.accept_hunk_at_cursor, { desc = 'Accept Claude hunk at cursor' }) vim.keymap.set('n', 'dkr', M.reject_hunk_at_cursor, { desc = 'Reject Claude hunk at cursor' }) @@ -179,9 +172,9 @@ end function M.open_diffview() if not M.current_review then -- Try to recover stash-based session from baseline - local utils = require('nvim-claude.utils') - local baseline_ref = utils.read_file('/tmp/claude-baseline-commit') - + local utils = require 'nvim-claude.utils' + local baseline_ref = utils.read_file '/tmp/claude-baseline-commit' + -- If no baseline file, but we have Claude stashes, use HEAD as baseline local claude_stashes = M.get_claude_stashes() if claude_stashes and #claude_stashes > 0 then @@ -191,35 +184,35 @@ function M.open_diffview() else baseline_ref = baseline_ref:gsub('%s+', '') end - + M.current_review = { baseline_ref = baseline_ref, timestamp = os.time(), claude_stashes = claude_stashes, current_stash_index = 0, -- Show cumulative view by default - is_stash_based = true + is_stash_based = true, } vim.notify(string.format('Recovered Claude stash session with %d stashes', #claude_stashes), vim.log.levels.INFO) end - + if not M.current_review then vim.notify('No active review session', vim.log.levels.INFO) return end end - + -- Use stash-based diff if available if M.current_review.is_stash_based then M.open_cumulative_stash_view() return end - + -- Legacy: Use cumulative diff if available if M.current_review.is_cumulative then M.open_cumulative_diffview() return end - + -- Check if diffview is available local ok, diffview = pcall(require, 'diffview') if not ok then @@ -227,7 +220,7 @@ function M.open_diffview() M.open_fugitive() return end - + -- Use the pre-edit reference if available if M.current_review.pre_edit_ref then local cmd = 'DiffviewOpen ' .. M.current_review.pre_edit_ref @@ -248,14 +241,14 @@ function M.open_cumulative_stash_view() vim.notify('No active review session', vim.log.levels.INFO) return end - + -- Check if diffview is available local ok, diffview = pcall(require, 'diffview') if not ok then vim.notify('diffview.nvim not available', vim.log.levels.WARN) return end - + if M.current_review.is_stash_based and M.current_review.claude_stashes then -- Show cumulative diff of all Claude stashes against baseline local cmd = 'DiffviewOpen ' .. M.current_review.baseline_ref @@ -280,7 +273,7 @@ function M.open_fugitive() vim.notify('No active review session', vim.log.levels.INFO) return end - + -- Use fugitive to show diff local cmd = 'Gdiffsplit ' .. M.current_review.stash_ref vim.notify('Opening fugitive: ' .. cmd, vim.log.levels.INFO) @@ -293,13 +286,13 @@ function M.list_changes() vim.notify('No active review session', vim.log.levels.INFO) return end - + local files = M.current_review.changed_files if #files == 0 then vim.notify('No changes found', vim.log.levels.INFO) return end - + -- Create a telescope picker if available, otherwise just notify local ok, telescope = pcall(require, 'telescope.pickers') if ok then @@ -314,38 +307,40 @@ end -- Telescope picker for changed files function M.telescope_changed_files() - local pickers = require('telescope.pickers') - local finders = require('telescope.finders') + local pickers = require 'telescope.pickers' + local finders = require 'telescope.finders' local conf = require('telescope.config').values - - pickers.new({}, { - prompt_title = 'Claude Changed Files', - finder = finders.new_table({ - results = M.current_review.changed_files, - }), - sorter = conf.generic_sorter({}), - attach_mappings = function(_, map) - map('i', '', function(prompt_bufnr) - local selection = require('telescope.actions.state').get_selected_entry() - require('telescope.actions').close(prompt_bufnr) - vim.cmd('edit ' .. selection[1]) - M.open_diffview() - end) - return true - end, - }):find() + + pickers + .new({}, { + prompt_title = 'Claude Changed Files', + finder = finders.new_table { + results = M.current_review.changed_files, + }, + sorter = conf.generic_sorter {}, + attach_mappings = function(_, map) + map('i', '', function(prompt_bufnr) + local selection = require('telescope.actions.state').get_selected_entry() + require('telescope.actions').close(prompt_bufnr) + vim.cmd('edit ' .. selection[1]) + M.open_diffview() + end) + return true + end, + }) + :find() end -- Clear review session function M.clear_review() if M.current_review then M.current_review = nil - + -- Close diffview if it's open pcall(function() - vim.cmd('DiffviewClose') + vim.cmd 'DiffviewClose' end) - + vim.notify('Claude review session cleared', vim.log.levels.INFO) else vim.notify('No active Claude review session', vim.log.levels.INFO) @@ -354,94 +349,94 @@ end -- Accept all Claude changes (update baseline) function M.accept_changes() - local utils = require('nvim-claude.utils') - + local utils = require 'nvim-claude.utils' + -- Get project root local git_root = utils.get_project_root() if not git_root then vim.notify('Not in a git repository', vim.log.levels.ERROR) return end - + -- Create new baseline commit with current state local timestamp = os.time() local commit_msg = string.format('claude-baseline-%d', timestamp) - + -- Stage all changes local add_cmd = string.format('cd "%s" && git add -A', git_root) local add_result, add_err = utils.exec(add_cmd) - + if add_err then vim.notify('Failed to stage changes: ' .. add_err, vim.log.levels.ERROR) return end - + -- Create new baseline commit local commit_cmd = string.format('cd "%s" && git commit -m "%s" --allow-empty', git_root, commit_msg) local commit_result, commit_err = utils.exec(commit_cmd) - - if commit_err and not commit_err:match('nothing to commit') then + + if commit_err and not commit_err:match 'nothing to commit' then vim.notify('Failed to create new baseline: ' .. commit_err, vim.log.levels.ERROR) return end - + -- Update baseline reference local baseline_file = '/tmp/claude-baseline-commit' utils.write_file(baseline_file, 'HEAD') - + -- Clear review session M.current_review = nil - + -- Close diffview pcall(function() - vim.cmd('DiffviewClose') + vim.cmd 'DiffviewClose' end) - + vim.notify('All Claude changes accepted! New baseline created.', vim.log.levels.INFO) end -- Decline all Claude changes (reset to baseline) function M.decline_changes() - local utils = require('nvim-claude.utils') - + local utils = require 'nvim-claude.utils' + -- Get baseline commit local baseline_file = '/tmp/claude-baseline-commit' local baseline_ref = utils.read_file(baseline_file) - + if not baseline_ref or baseline_ref == '' then vim.notify('No baseline commit found', vim.log.levels.ERROR) return end - + baseline_ref = baseline_ref:gsub('%s+', '') - + -- Get project root local git_root = utils.get_project_root() if not git_root then vim.notify('Not in a git repository', vim.log.levels.ERROR) return end - + -- Reset to baseline (hard reset) local reset_cmd = string.format('cd "%s" && git reset --hard %s', git_root, baseline_ref) local reset_result, reset_err = utils.exec(reset_cmd) - + if reset_err then vim.notify('Failed to reset to baseline: ' .. reset_err, vim.log.levels.ERROR) return end - + -- Clear review session M.current_review = nil - + -- Close diffview pcall(function() - vim.cmd('DiffviewClose') + vim.cmd 'DiffviewClose' end) - + -- Refresh buffers - vim.cmd('checktime') - + vim.cmd 'checktime' + vim.notify('All Claude changes declined! Reset to baseline.', vim.log.levels.INFO) end @@ -451,13 +446,13 @@ function M.browse_claude_stashes() vim.notify('No Claude stash session active', vim.log.levels.INFO) return end - + local stashes = M.current_review.claude_stashes if not stashes or #stashes == 0 then vim.notify('No Claude stashes found', vim.log.levels.INFO) return end - + -- Create a telescope picker if available, otherwise just notify local ok, telescope = pcall(require, 'telescope.pickers') if ok then @@ -477,19 +472,19 @@ function M.previous_stash() vim.notify('No Claude stash session active', vim.log.levels.INFO) return end - + local stashes = M.current_review.claude_stashes if not stashes or #stashes == 0 then vim.notify('No Claude stashes found', vim.log.levels.INFO) return end - + local current_index = M.current_review.current_stash_index or 0 if current_index <= 1 then vim.notify('Already at first stash', vim.log.levels.INFO) return end - + M.current_review.current_stash_index = current_index - 1 M.view_specific_stash(M.current_review.current_stash_index) end @@ -500,19 +495,19 @@ function M.next_stash() vim.notify('No Claude stash session active', vim.log.levels.INFO) return end - + local stashes = M.current_review.claude_stashes if not stashes or #stashes == 0 then vim.notify('No Claude stashes found', vim.log.levels.INFO) return end - + local current_index = M.current_review.current_stash_index or 0 if current_index >= #stashes then vim.notify('Already at last stash', vim.log.levels.INFO) return end - + M.current_review.current_stash_index = current_index + 1 M.view_specific_stash(M.current_review.current_stash_index) end @@ -523,22 +518,22 @@ function M.view_specific_stash(index) vim.notify('No Claude stash session active', vim.log.levels.INFO) return end - + local stashes = M.current_review.claude_stashes if not stashes or index < 1 or index > #stashes then vim.notify('Invalid stash index', vim.log.levels.ERROR) return end - + local stash = stashes[index] - + -- Check if diffview is available local ok, diffview = pcall(require, 'diffview') if not ok then vim.notify('diffview.nvim not available', vim.log.levels.WARN) return end - + -- Open diffview for this specific stash local cmd = string.format('DiffviewOpen %s^..%s', stash.ref, stash.ref) vim.notify(string.format('Opening stash %d: %s', index, stash.message), vim.log.levels.INFO) @@ -547,13 +542,13 @@ end -- Telescope picker for Claude stashes function M.telescope_claude_stashes() - local pickers = require('telescope.pickers') - local finders = require('telescope.finders') + local pickers = require 'telescope.pickers' + local finders = require 'telescope.finders' local conf = require('telescope.config').values - + local stashes = M.current_review.claude_stashes local stash_entries = {} - + for i, stash in ipairs(stashes) do table.insert(stash_entries, { value = i, @@ -561,30 +556,32 @@ function M.telescope_claude_stashes() ordinal = stash.message, }) end - - pickers.new({}, { - prompt_title = 'Claude Stash History', - finder = finders.new_table({ - results = stash_entries, - entry_maker = function(entry) - return { - value = entry.value, - display = entry.display, - ordinal = entry.ordinal, - } + + pickers + .new({}, { + prompt_title = 'Claude Stash History', + finder = finders.new_table { + results = stash_entries, + entry_maker = function(entry) + return { + value = entry.value, + display = entry.display, + ordinal = entry.ordinal, + } + end, + }, + sorter = conf.generic_sorter {}, + attach_mappings = function(_, map) + map('i', '', function(prompt_bufnr) + local selection = require('telescope.actions.state').get_selected_entry() + require('telescope.actions').close(prompt_bufnr) + M.current_review.current_stash_index = selection.value + M.view_specific_stash(selection.value) + end) + return true end, - }), - sorter = conf.generic_sorter({}), - attach_mappings = function(_, map) - map('i', '', function(prompt_bufnr) - local selection = require('telescope.actions.state').get_selected_entry() - require('telescope.actions').close(prompt_bufnr) - M.current_review.current_stash_index = selection.value - M.view_specific_stash(selection.value) - end) - return true - end, - }):find() + }) + :find() end -- Generate combined patch from all Claude stashes @@ -593,19 +590,19 @@ function M.generate_claude_patch() vim.notify('No Claude stash session active', vim.log.levels.ERROR) return nil end - - local utils = require('nvim-claude.utils') + + local utils = require 'nvim-claude.utils' local baseline_ref = M.current_review.baseline_ref - + -- Generate diff from baseline to current working directory local cmd = string.format('git diff %s', baseline_ref) local patch, err = utils.exec(cmd) - + if err then vim.notify('Failed to generate patch: ' .. err, vim.log.levels.ERROR) return nil end - + return patch end @@ -613,9 +610,9 @@ end function M.open_unified_view() if not M.current_review then -- Try to recover stash-based session from baseline - local utils = require('nvim-claude.utils') - local baseline_ref = utils.read_file('/tmp/claude-baseline-commit') - + local utils = require 'nvim-claude.utils' + local baseline_ref = utils.read_file '/tmp/claude-baseline-commit' + -- If no baseline file, but we have Claude stashes, use HEAD as baseline local claude_stashes = M.get_claude_stashes() if claude_stashes and #claude_stashes > 0 then @@ -625,23 +622,23 @@ function M.open_unified_view() else baseline_ref = baseline_ref:gsub('%s+', '') end - + M.current_review = { baseline_ref = baseline_ref, timestamp = os.time(), claude_stashes = claude_stashes, current_stash_index = 0, - is_stash_based = true + is_stash_based = true, } vim.notify(string.format('Recovered Claude stash session with %d stashes', #claude_stashes), vim.log.levels.INFO) end - + if not M.current_review then vim.notify('No active review session', vim.log.levels.INFO) return end end - + -- Check if unified.nvim is available and load it local ok, unified = pcall(require, 'unified') if not ok then @@ -649,56 +646,52 @@ function M.open_unified_view() M.open_diffview() return end - + -- Ensure unified.nvim is set up pcall(unified.setup, {}) - + -- Use unified.nvim to show diff against baseline local baseline_ref = M.current_review.baseline_ref - + -- Try the command with pcall to catch errors local cmd_ok, cmd_err = pcall(function() vim.cmd('Unified ' .. baseline_ref) end) - + if not cmd_ok then vim.notify('Unified command failed: ' .. tostring(cmd_err) .. ', falling back to diffview', vim.log.levels.WARN) M.open_diffview() return end - + vim.notify('Claude unified diff opened. Use ]h/[h to navigate hunks', vim.log.levels.INFO) end - -- Accept hunk at cursor position function M.accept_hunk_at_cursor() -- Get current buffer and check if we're in a diff view local bufname = vim.api.nvim_buf_get_name(0) local filetype = vim.bo.filetype - + -- Check for various diff view types - local is_diff_view = bufname:match('diffview://') or - bufname:match('Claude Unified Diff') or - filetype == 'diff' or - filetype == 'git' - + local is_diff_view = bufname:match 'diffview://' or bufname:match 'Claude Unified Diff' or filetype == 'diff' or filetype == 'git' + if not is_diff_view then vim.notify('This command only works in diff views', vim.log.levels.WARN) return end - + -- Get current file and line from cursor position local cursor_line = vim.api.nvim_win_get_cursor(0)[1] local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) - + -- Parse diff to find current hunk local hunk_info = M.find_hunk_at_line(lines, cursor_line) if not hunk_info then vim.notify('No hunk found at cursor position', vim.log.levels.WARN) return end - + -- Apply the hunk M.apply_hunk(hunk_info) end @@ -708,30 +701,30 @@ function M.reject_hunk_at_cursor() -- Get current buffer and check if we're in a diff view local bufname = vim.api.nvim_buf_get_name(0) local filetype = vim.bo.filetype - + -- Check for various diff view types - local is_diff_view = bufname:match('diffview://') or - bufname:match('Claude Unified Diff') or - filetype == 'diff' or - filetype == 'git' - + local is_diff_view = bufname:match 'diffview://' or bufname:match 'Claude Unified Diff' or filetype == 'diff' or filetype == 'git' + if not is_diff_view then vim.notify('This command only works in diff views', vim.log.levels.WARN) return end - + -- Get current file and line from cursor position local cursor_line = vim.api.nvim_win_get_cursor(0)[1] local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) - + -- Parse diff to find current hunk local hunk_info = M.find_hunk_at_line(lines, cursor_line) if not hunk_info then vim.notify('No hunk found at cursor position', vim.log.levels.WARN) return end - - vim.notify(string.format('Rejected hunk in %s at lines %d-%d', hunk_info.file, hunk_info.old_start, hunk_info.old_start + hunk_info.old_count - 1), vim.log.levels.INFO) + + vim.notify( + string.format('Rejected hunk in %s at lines %d-%d', hunk_info.file, hunk_info.old_start, hunk_info.old_start + hunk_info.old_count - 1), + vim.log.levels.INFO + ) end -- Find hunk information at given line in diff buffer @@ -740,29 +733,29 @@ function M.find_hunk_at_line(lines, target_line) local in_hunk = false local hunk_start_line = nil local hunk_lines = {} - + for i, line in ipairs(lines) do -- File header - if line:match('^diff %-%-git') or line:match('^diff %-%-cc') then - current_file = line:match('b/(.+)$') - elseif line:match('^%+%+%+ b/(.+)') then - current_file = line:match('^%+%+%+ b/(.+)') + if line:match '^diff %-%-git' or line:match '^diff %-%-cc' then + current_file = line:match 'b/(.+)$' + elseif line:match '^%+%+%+ b/(.+)' then + current_file = line:match '^%+%+%+ b/(.+)' end - + -- Hunk header - if line:match('^@@') then + if line:match '^@@' then -- If we were in a hunk that included target line, return it if in_hunk and hunk_start_line and target_line >= hunk_start_line and target_line < i then return M.parse_hunk_info(hunk_lines, current_file, hunk_start_line) end - + -- Start new hunk in_hunk = true hunk_start_line = i - hunk_lines = {line} + hunk_lines = { line } elseif in_hunk then -- Collect hunk lines - if line:match('^[%+%-%s]') then + if line:match '^[%+%-%s]' then table.insert(hunk_lines, line) else -- End of hunk @@ -773,24 +766,28 @@ function M.find_hunk_at_line(lines, target_line) end end end - + -- Check last hunk if in_hunk and hunk_start_line and target_line >= hunk_start_line then return M.parse_hunk_info(hunk_lines, current_file, hunk_start_line) end - + return nil end -- Parse hunk information from diff lines function M.parse_hunk_info(hunk_lines, file, start_line) - if #hunk_lines == 0 then return nil end - + if #hunk_lines == 0 then + return nil + end + local header = hunk_lines[1] - local old_start, old_count, new_start, new_count = header:match('^@@ %-(%d+),?(%d*) %+(%d+),?(%d*) @@') - - if not old_start then return nil end - + local old_start, old_count, new_start, new_count = header:match '^@@ %-(%d+),?(%d*) %+(%d+),?(%d*) @@' + + if not old_start then + return nil + end + return { file = file, old_start = tonumber(old_start), @@ -798,58 +795,59 @@ function M.parse_hunk_info(hunk_lines, file, start_line) new_start = tonumber(new_start), new_count = tonumber(new_count) or 1, lines = hunk_lines, - buffer_start_line = start_line + buffer_start_line = start_line, } end -- Apply a specific hunk to the working directory function M.apply_hunk(hunk_info) - local utils = require('nvim-claude.utils') - + local utils = require 'nvim-claude.utils' + -- Create a patch with just this hunk local patch_lines = { 'diff --git a/' .. hunk_info.file .. ' b/' .. hunk_info.file, 'index 0000000..0000000 100644', '--- a/' .. hunk_info.file, - '+++ b/' .. hunk_info.file + '+++ b/' .. hunk_info.file, } - + -- Add hunk lines for _, line in ipairs(hunk_info.lines) do table.insert(patch_lines, line) end - + local patch_content = table.concat(patch_lines, '\n') - + -- Write patch to temp file local temp_patch = '/tmp/claude-hunk-patch.diff' utils.write_file(temp_patch, patch_content) - + -- Apply the patch local git_root = utils.get_project_root() if not git_root then vim.notify('Not in a git repository', vim.log.levels.ERROR) return end - + local cmd = string.format('cd "%s" && git apply --cached "%s"', git_root, temp_patch) local result, err = utils.exec(cmd) - + if err then -- Try without --cached cmd = string.format('cd "%s" && git apply "%s"', git_root, temp_patch) result, err = utils.exec(cmd) - + if err then vim.notify('Failed to apply hunk: ' .. err, vim.log.levels.ERROR) return end end - + vim.notify(string.format('Applied hunk to %s', hunk_info.file), vim.log.levels.INFO) - + -- Refresh the buffer if it's open - vim.cmd('checktime') + vim.cmd 'checktime' end -return M \ No newline at end of file +return M + diff --git a/lua/nvim-claude/hooks.lua b/lua/nvim-claude/hooks.lua index ab1fc993..6ad18b8e 100644 --- a/lua/nvim-claude/hooks.lua +++ b/lua/nvim-claude/hooks.lua @@ -5,12 +5,7 @@ local M = {} M.pre_edit_commit = nil function M.setup() - -- Auto-cleanup old Claude commits on startup - vim.defer_fn(function() - M.cleanup_old_commits() - end, 200) - - -- Create baseline on startup if we're in a git repo + -- Setup persistence layer on startup vim.defer_fn(function() M.create_startup_baseline() end, 500) @@ -27,6 +22,7 @@ end function M.post_tool_use_hook() -- Run directly without vim.schedule for testing local utils = require 'nvim-claude.utils' + local persistence = require 'nvim-claude.inline-diff-persistence' -- Refresh all buffers to show Claude's changes vim.cmd 'checktime' @@ -53,26 +49,15 @@ function M.post_tool_use_hook() end end - -- Get the baseline commit reference first - local baseline_ref = utils.read_file '/tmp/claude-baseline-commit' - if baseline_ref then - baseline_ref = baseline_ref:gsub('%s+', '') -- trim whitespace + -- Get the stash reference from pre-hook + local stash_ref = persistence.current_stash_ref + if not stash_ref then + -- If no pre-hook stash, create one now + stash_ref = persistence.create_stash('nvim-claude: changes detected ' .. os.date('%Y-%m-%d %H:%M:%S')) + persistence.current_stash_ref = stash_ref end - -- Create a stash of Claude's changes (but keep them in working directory) - local timestamp = os.date '%Y-%m-%d %H:%M:%S' - local stash_msg = string.format('[claude-edit] %s', timestamp) - - -- Use git stash create to create stash without removing changes - local stash_cmd = string.format('cd "%s" && git stash create -u', git_root) - local stash_hash, stash_err = utils.exec(stash_cmd) - - if not stash_err and stash_hash and stash_hash ~= '' then - -- Store the stash with a message - stash_hash = stash_hash:gsub('%s+', '') -- trim whitespace - local store_cmd = string.format('cd "%s" && git stash store -m "%s" %s', git_root, stash_msg, stash_hash) - utils.exec(store_cmd) - + if stash_ref then -- Check if any modified files are currently open in buffers local inline_diff = require 'nvim-claude.inline-diff' local opened_inline = false @@ -85,9 +70,9 @@ function M.post_tool_use_hook() if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_is_loaded(buf) then local buf_name = vim.api.nvim_buf_get_name(buf) if buf_name == full_path or buf_name:match('/' .. file:gsub('([^%w])', '%%%1') .. '$') then - -- Get the original content (from baseline) - local baseline_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, baseline_ref or 'HEAD', file) - local original_content, orig_err = utils.exec(baseline_cmd) + -- Get the original content from stash + local stash_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, stash_ref, file) + local original_content, orig_err = utils.exec(stash_cmd) if not orig_err and original_content then -- Get current content @@ -116,14 +101,12 @@ function M.post_tool_use_hook() -- If no inline diff was shown, fall back to regular diff review if not opened_inline then - -- Trigger diff review - show Claude stashes against baseline + -- Trigger diff review with stash reference local ok, diff_review = pcall(require, 'nvim-claude.diff-review') if ok then - diff_review.handle_claude_stashes(baseline_ref) - else + diff_review.handle_claude_edit(stash_ref, nil) end end - else end end @@ -132,6 +115,7 @@ function M.test_inline_diff() vim.notify('Testing inline diff manually...', vim.log.levels.INFO) local utils = require 'nvim-claude.utils' + local persistence = require 'nvim-claude.inline-diff-persistence' local git_root = utils.get_project_root() if not git_root then @@ -163,12 +147,20 @@ function M.test_inline_diff() 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+', '') + elseif persistence.current_stash_ref then + -- Try to get from stash + local stash_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, persistence.current_stash_ref, relative_path) + local git_err + original_content, git_err = utils.exec(stash_cmd) - local baseline_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, baseline_ref, relative_path) + if git_err then + vim.notify('Failed to get stash content: ' .. git_err, vim.log.levels.ERROR) + return + end + vim.notify('Using stash baseline: ' .. persistence.current_stash_ref, vim.log.levels.INFO) + else + -- Fall back to HEAD + local baseline_cmd = string.format('cd "%s" && git show HEAD:%s 2>/dev/null', git_root, relative_path) local git_err original_content, git_err = utils.exec(baseline_cmd) @@ -176,7 +168,7 @@ function M.test_inline_diff() vim.notify('Failed to get baseline content: ' .. git_err, vim.log.levels.ERROR) return end - vim.notify('Using git baseline', vim.log.levels.INFO) + vim.notify('Using HEAD as baseline', vim.log.levels.INFO) end -- Get current content @@ -187,65 +179,33 @@ function M.test_inline_diff() inline_diff.show_inline_diff(bufnr, original_content, current_content) end --- Create baseline on Neovim startup +-- Create baseline on Neovim startup (now just sets up persistence) function M.create_startup_baseline() - local utils = require 'nvim-claude.utils' - - -- Check if we're in a git repository - local git_root = utils.get_project_root() - if not git_root then - return - end - - -- Check if we already have a baseline - local baseline_file = '/tmp/claude-baseline-commit' - local existing_baseline = utils.read_file(baseline_file) - - -- If we have a valid baseline, keep it - if existing_baseline and existing_baseline ~= '' then - existing_baseline = existing_baseline:gsub('%s+', '') - local check_cmd = string.format('cd "%s" && git rev-parse --verify %s^{commit} 2>/dev/null', git_root, existing_baseline) - local check_result, check_err = utils.exec(check_cmd) - - if check_result and not check_err and check_result:match '^%x+' then - -- Baseline is valid - vim.notify('Claude baseline loaded: ' .. existing_baseline:sub(1, 7), vim.log.levels.INFO) - return - end - end - - -- Create new baseline of current state - local timestamp = os.time() - local commit_msg = string.format('claude-baseline-%d (startup)', timestamp) - - -- Stage all current changes - local add_cmd = string.format('cd "%s" && git add -A', git_root) - utils.exec(add_cmd) - - -- Create baseline commit - local commit_cmd = string.format('cd "%s" && git commit -m "%s" --allow-empty', git_root, commit_msg) - local commit_result, commit_err = utils.exec(commit_cmd) - - if not commit_err or commit_err:match 'nothing to commit' then - -- Get the commit hash - local hash_cmd = string.format('cd "%s" && git rev-parse HEAD', git_root) - local commit_hash, hash_err = utils.exec(hash_cmd) - - if not hash_err and commit_hash then - commit_hash = commit_hash:gsub('%s+', '') - utils.write_file(baseline_file, commit_hash) - vim.notify('Claude baseline created: ' .. commit_hash:sub(1, 7), vim.log.levels.INFO) - end - end + local persistence = require 'nvim-claude.inline-diff-persistence' + + -- Setup persistence autocmds + persistence.setup_autocmds() + + -- Try to restore any saved diffs + persistence.restore_diffs() end -- Manual hook testing function M.test_hooks() vim.notify('=== Testing nvim-claude hooks ===', vim.log.levels.INFO) - - -- Test pre-tool-use hook - vim.notify('1. Testing pre-tool-use hook (creating snapshot)...', vim.log.levels.INFO) - M.pre_tool_use_hook() + + local persistence = require 'nvim-claude.inline-diff-persistence' + + -- Test creating a stash + vim.notify('1. Creating test stash...', vim.log.levels.INFO) + local stash_ref = persistence.create_stash('nvim-claude: test stash') + + if stash_ref then + persistence.current_stash_ref = stash_ref + vim.notify('Stash created: ' .. stash_ref, vim.log.levels.INFO) + else + vim.notify('Failed to create stash', vim.log.levels.ERROR) + end -- Simulate making a change vim.notify('2. Make some changes to test files now...', vim.log.levels.INFO) @@ -386,6 +346,12 @@ function M.setup_commands() local inline_diff = require 'nvim-claude.inline-diff' inline_diff.original_content[bufnr] = current_content + -- Save updated state + local persistence = require 'nvim-claude.inline-diff-persistence' + if persistence.current_stash_ref then + persistence.save_state({ stash_ref = persistence.current_stash_ref }) + end + vim.notify('Baseline updated to current buffer state', vim.log.levels.INFO) end, { desc = 'Update Claude baseline to current buffer state', @@ -448,15 +414,8 @@ function M.setup_commands() }) end --- Cleanup old Claude commits and temp files -function M.cleanup_old_commits() - local utils = require 'nvim-claude.utils' - - local git_root = utils.get_project_root() - if not git_root then - return - end - +-- Cleanup old temp files (no longer cleans up commits) +function M.cleanup_old_files() -- Clean up old temp files local temp_files = { '/tmp/claude-pre-edit-commit', @@ -470,31 +429,6 @@ function M.cleanup_old_commits() vim.fn.delete(file) end end - - -- Clean up old Claude commits (keep only the last 5) - local log_cmd = - string.format('cd "%s" && git log --oneline --grep="claude-" --grep="claude-baseline" --grep="claude-pre-edit" --all --max-count=10', git_root) - local log_result = utils.exec(log_cmd) - - if log_result and log_result ~= '' then - local commits = {} - for line in log_result:gmatch '[^\n]+' do - local hash = line:match '^(%w+)' - if hash then - table.insert(commits, hash) - end - end - - -- Keep only the last 5 Claude commits, remove the rest - -- DISABLED: This was causing rebases that broke the workflow - -- if #commits > 5 then - -- for i = 6, #commits do - -- local reset_cmd = string.format('cd "%s" && git rebase --onto %s^ %s', git_root, commits[i], commits[i]) - -- utils.exec(reset_cmd) - -- end - -- vim.notify('Cleaned up old Claude commits', vim.log.levels.DEBUG) - -- end - end end return M diff --git a/lua/nvim-claude/inline-diff-persistence.lua b/lua/nvim-claude/inline-diff-persistence.lua new file mode 100644 index 00000000..fae64ef2 --- /dev/null +++ b/lua/nvim-claude/inline-diff-persistence.lua @@ -0,0 +1,266 @@ +-- Persistence layer for inline diffs +-- Manages saving/loading diff state across neovim sessions without polluting git history + +local M = {} +local utils = require('nvim-claude.utils') + +-- State file location +M.state_file = vim.fn.stdpath('data') .. '/nvim-claude-inline-diff-state.json' + +-- Save current diff state +function M.save_state(diff_data) + -- Structure: + -- { + -- version: 1, + -- timestamp: , + -- stash_ref: "stash@{0}", + -- files: { + -- "/path/to/file": { + -- original_content: "...", + -- hunks: [...], + -- applied_hunks: {...} + -- } + -- } + -- } + + local state = { + version = 1, + timestamp = os.time(), + stash_ref = diff_data.stash_ref, + files = {} + } + + -- Collect state from all buffers with active diffs + local inline_diff = require('nvim-claude.inline-diff') + for file_path, bufnr in pairs(inline_diff.diff_files) do + if inline_diff.active_diffs[bufnr] then + local diff = inline_diff.active_diffs[bufnr] + state.files[file_path] = { + original_content = inline_diff.original_content[bufnr], + hunks = diff.hunks, + applied_hunks = diff.applied_hunks or {}, + new_content = diff.new_content + } + end + end + + -- Save to file + local success, err = utils.write_json(M.state_file, state) + if not success then + vim.notify('Failed to save inline diff state: ' .. err, vim.log.levels.ERROR) + return false + end + + return true +end + +-- Load saved diff state +function M.load_state() + if not utils.file_exists(M.state_file) then + return nil + end + + local state, err = utils.read_json(M.state_file) + if not state then + vim.notify('Failed to load inline diff state: ' .. err, vim.log.levels.ERROR) + return nil + end + + -- Validate version + if state.version ~= 1 then + vim.notify('Incompatible inline diff state version', vim.log.levels.WARN) + return nil + end + + -- Check if stash still exists + if state.stash_ref then + local cmd = string.format('git stash list | grep -q "%s"', state.stash_ref:gsub("{", "\\{"):gsub("}", "\\}")) + local result = os.execute(cmd) + if result ~= 0 then + vim.notify('Saved stash no longer exists: ' .. state.stash_ref, vim.log.levels.WARN) + M.clear_state() + return nil + end + end + + return state +end + +-- Clear saved state +function M.clear_state() + if utils.file_exists(M.state_file) then + os.remove(M.state_file) + end +end + +-- Restore diffs from saved state +function M.restore_diffs() + local state = M.load_state() + if not state then + return false + end + + local inline_diff = require('nvim-claude.inline-diff') + local restored_count = 0 + + -- Restore diffs for each file + for file_path, file_state in pairs(state.files) do + -- Check if file exists and hasn't changed since the diff was created + if utils.file_exists(file_path) then + -- Read current content + local current_content = utils.read_file(file_path) + + -- Check if the file matches what we expect (either original or with applied changes) + -- This handles the case where some hunks were accepted + if current_content then + -- Find or create buffer for this file + local bufnr = vim.fn.bufnr(file_path) + if bufnr == -1 then + -- File not loaded, we'll restore when it's opened + -- Store in a pending restores table + M.pending_restores = M.pending_restores or {} + M.pending_restores[file_path] = file_state + else + -- Restore the diff visualization + inline_diff.original_content[bufnr] = file_state.original_content + inline_diff.diff_files[file_path] = bufnr + inline_diff.active_diffs[bufnr] = { + hunks = file_state.hunks, + new_content = file_state.new_content, + current_hunk = 1, + applied_hunks = file_state.applied_hunks or {} + } + + -- Apply visualization + inline_diff.apply_diff_visualization(bufnr) + inline_diff.setup_inline_keymaps(bufnr) + + restored_count = restored_count + 1 + end + end + end + end + + if restored_count > 0 then + vim.notify(string.format('Restored inline diffs for %d file(s)', restored_count), vim.log.levels.INFO) + end + + -- Store the stash reference for future operations + M.current_stash_ref = state.stash_ref + + return true +end + +-- Check for pending restores when a buffer is loaded +function M.check_pending_restore(bufnr) + if not M.pending_restores then + return + end + + local file_path = vim.api.nvim_buf_get_name(bufnr) + local file_state = M.pending_restores[file_path] + + if file_state then + local inline_diff = require('nvim-claude.inline-diff') + + -- Restore the diff for this buffer + inline_diff.original_content[bufnr] = file_state.original_content + inline_diff.diff_files[file_path] = bufnr + inline_diff.active_diffs[bufnr] = { + hunks = file_state.hunks, + new_content = file_state.new_content, + current_hunk = 1, + applied_hunks = file_state.applied_hunks or {} + } + + -- Apply visualization + inline_diff.apply_diff_visualization(bufnr) + inline_diff.setup_inline_keymaps(bufnr) + + -- Remove from pending + M.pending_restores[file_path] = nil + + vim.notify('Restored inline diff for ' .. vim.fn.fnamemodify(file_path, ':~:.'), vim.log.levels.INFO) + end +end + +-- Create a stash of current changes (instead of baseline commit) +function M.create_stash(message) + message = message or 'nvim-claude: pre-edit state' + + -- Check if there are changes to stash + local status = utils.exec('git status --porcelain') + if not status or status == '' then + -- No changes, but we still need to track current state + -- Create an empty stash by making a tiny change + local temp_file = '.nvim-claude-temp' + utils.write_file(temp_file, 'temp') + utils.exec('git add ' .. temp_file) + utils.exec(string.format('git stash push -m "%s" -- %s', message, temp_file)) + os.remove(temp_file) + else + -- Stash all current changes + local cmd = string.format('git stash push -m "%s" --include-untracked', message) + local result, err = utils.exec(cmd) + if err and not err:match('Saved working directory') then + vim.notify('Failed to create stash: ' .. err, vim.log.levels.ERROR) + return nil + end + end + + -- Get the stash reference + local stash_list = utils.exec('git stash list -n 1') + if stash_list then + local stash_ref = stash_list:match('^(stash@{%d+})') + return stash_ref + end + + return nil +end + +-- Setup autocmds for persistence +function M.setup_autocmds() + local group = vim.api.nvim_create_augroup('NvimClaudeInlineDiffPersistence', { clear = true }) + + -- Save state before exiting vim + vim.api.nvim_create_autocmd('VimLeavePre', { + group = group, + callback = function() + local inline_diff = require('nvim-claude.inline-diff') + -- Only save if there are active diffs + local has_active_diffs = false + for _, diff in pairs(inline_diff.active_diffs) do + if diff then + has_active_diffs = true + break + end + end + + if has_active_diffs and M.current_stash_ref then + M.save_state({ stash_ref = M.current_stash_ref }) + end + end + }) + + -- Check for pending restores when buffers are loaded + vim.api.nvim_create_autocmd('BufReadPost', { + group = group, + callback = function(ev) + M.check_pending_restore(ev.buf) + end + }) + + -- Auto-restore on VimEnter + vim.api.nvim_create_autocmd('VimEnter', { + group = group, + once = true, + callback = function() + -- Delay slightly to ensure everything is loaded + vim.defer_fn(function() + M.restore_diffs() + end, 100) + end + }) +end + +return M \ No newline at end of file diff --git a/lua/nvim-claude/inline-diff.lua b/lua/nvim-claude/inline-diff.lua index 04c28a02..d24efae0 100644 --- a/lua/nvim-claude/inline-diff.lua +++ b/lua/nvim-claude/inline-diff.lua @@ -349,12 +349,14 @@ function M.accept_current_hunk(bufnr) local hunk = diff_data.hunks[diff_data.current_hunk] if not hunk then return end - -- Update the baseline to include this accepted change - M.update_baseline_after_accept(bufnr, hunk) - -- Mark as applied (the changes are already in the buffer) diff_data.applied_hunks[diff_data.current_hunk] = true + -- Update in-memory baseline to current state + 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 + -- Remove this hunk from the diff data since it's accepted table.remove(diff_data.hunks, diff_data.current_hunk) @@ -365,6 +367,10 @@ function M.accept_current_hunk(bufnr) vim.notify(string.format('Accepted hunk - %d hunks remaining', #diff_data.hunks), vim.log.levels.INFO) + -- Save state for persistence + local persistence = require('nvim-claude.inline-diff-persistence') + persistence.save_state({ stash_ref = persistence.current_stash_ref }) + if #diff_data.hunks == 0 then -- No more hunks to review vim.notify('All hunks processed! Closing inline diff.', vim.log.levels.INFO) @@ -387,67 +393,17 @@ function M.reject_current_hunk(bufnr) -- Revert the hunk by applying original content M.revert_hunk_changes(bufnr, hunk) - -- Create a new baseline commit with the rejected changes reverted - local utils = require('nvim-claude.utils') - local git_root = utils.get_project_root() - - if git_root then - -- Save the buffer to ensure changes are on disk - vim.api.nvim_buf_call(bufnr, function() - if vim.bo.modified then - vim.cmd('write') - vim.notify('Buffer saved', vim.log.levels.INFO) - else - vim.notify('Buffer already saved', vim.log.levels.INFO) - end - end) - - -- Stage only the current file (now with hunk reverted) - local file_path = vim.api.nvim_buf_get_name(bufnr) - local relative_path = file_path:gsub('^' .. git_root .. '/', '') - - -- Create a new baseline commit with only this file - local timestamp = os.time() - local commit_msg = string.format('claude-baseline-%d (rejected changes)', timestamp) - - -- Use git commit with only the specific file - local commit_cmd = string.format('cd "%s" && git add "%s" && git commit -m "%s" -- "%s"', - git_root, relative_path, commit_msg, relative_path) - local commit_result, commit_err = utils.exec(commit_cmd) - - vim.notify('Commit command: ' .. commit_cmd, vim.log.levels.DEBUG) - vim.notify('Commit result: ' .. (commit_result or 'nil'), vim.log.levels.DEBUG) - - if commit_result and (commit_result:match('1 file changed') or commit_result:match('create mode') or commit_result:match('nothing to commit')) then - -- Get the new commit hash - local hash_cmd = string.format('cd "%s" && git rev-parse HEAD', git_root) - local commit_hash, hash_err = utils.exec(hash_cmd) - - if not hash_err and commit_hash then - commit_hash = commit_hash:gsub('%s+', '') - -- Update the baseline file - utils.write_file('/tmp/claude-baseline-commit', commit_hash) - - -- Update in-memory baseline - 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 - - if commit_result:match('nothing to commit') then - vim.notify('No changes to commit after rejection, baseline updated', vim.log.levels.INFO) - else - vim.notify('Baseline commit created after rejection: ' .. commit_hash:sub(1, 7), vim.log.levels.INFO) - end - else - vim.notify('Failed to get commit hash: ' .. (hash_err or 'unknown'), vim.log.levels.ERROR) - end - else - vim.notify('Failed to create baseline commit for rejection', vim.log.levels.ERROR) - if commit_err then - vim.notify('Error: ' .. commit_err, vim.log.levels.ERROR) - end + -- Save the buffer to ensure changes are on disk + vim.api.nvim_buf_call(bufnr, function() + if vim.bo.modified then + vim.cmd('write') end - end + end) + + -- Update in-memory baseline to current state (with rejected changes) + 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 -- Remove this hunk from the diff data since it's rejected table.remove(diff_data.hunks, diff_data.current_hunk) @@ -459,6 +415,10 @@ function M.reject_current_hunk(bufnr) vim.notify(string.format('Rejected hunk - %d hunks remaining', #diff_data.hunks), vim.log.levels.INFO) + -- Save state for persistence + local persistence = require('nvim-claude.inline-diff-persistence') + persistence.save_state({ stash_ref = persistence.current_stash_ref }) + if #diff_data.hunks == 0 then -- No more hunks to review vim.notify('All hunks processed! Closing inline diff.', vim.log.levels.INFO) @@ -612,6 +572,22 @@ function M.close_inline_diff(bufnr, keep_baseline) M.original_content[bufnr] = nil end + -- Check if all diffs are closed + local has_active_diffs = false + for _, diff in pairs(M.active_diffs) do + if diff then + has_active_diffs = true + break + end + end + + -- If no more active diffs, clear persistence state + if not has_active_diffs then + local persistence = require('nvim-claude.inline-diff-persistence') + persistence.clear_state() + persistence.current_stash_ref = nil + end + vim.notify('Inline diff closed', vim.log.levels.INFO) end @@ -620,71 +596,11 @@ function M.has_active_diff(bufnr) return M.active_diffs[bufnr] ~= nil end --- Update baseline content after accepting a hunk +-- Update baseline content after accepting a hunk (deprecated - no longer creates commits) function M.update_baseline_after_accept(bufnr, hunk) - local utils = require('nvim-claude.utils') - local git_root = utils.get_project_root() - - if not git_root then - vim.notify('Not in a git repository', vim.log.levels.ERROR) - return - end - - -- Save the buffer to ensure changes are on disk - vim.api.nvim_buf_call(bufnr, function() - if vim.bo.modified then - vim.cmd('write') - vim.notify('Buffer saved', vim.log.levels.INFO) - else - vim.notify('Buffer already saved', vim.log.levels.INFO) - end - end) - - -- Stage only the current file - local file_path = vim.api.nvim_buf_get_name(bufnr) - local relative_path = file_path:gsub('^' .. git_root .. '/', '') - - -- Create a new baseline commit with only this file - local timestamp = os.time() - local commit_msg = string.format('claude-baseline-%d (accepted changes)', timestamp) - - -- Use git commit with only the specific file - local commit_cmd = string.format('cd "%s" && git add "%s" && git commit -m "%s" -- "%s"', - git_root, relative_path, commit_msg, relative_path) - local commit_result, commit_err = utils.exec(commit_cmd) - - vim.notify('Commit command: ' .. commit_cmd, vim.log.levels.INFO) - vim.notify('Commit result: ' .. (commit_result or 'nil'), vim.log.levels.INFO) - - if commit_result and (commit_result:match('1 file changed') or commit_result:match('create mode') or commit_result:match('nothing to commit')) then - -- Commit was successful or there was nothing to commit (file already at desired state) - local hash_cmd = string.format('cd "%s" && git rev-parse HEAD', git_root) - local commit_hash, hash_err = utils.exec(hash_cmd) - - if not hash_err and commit_hash then - commit_hash = commit_hash:gsub('%s+', '') - -- Update the baseline file - utils.write_file('/tmp/claude-baseline-commit', commit_hash) - - -- Update in-memory baseline - 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 - - if commit_result:match('nothing to commit') then - vim.notify('No changes to commit, baseline updated to current state', vim.log.levels.INFO) - else - vim.notify('Baseline commit created: ' .. commit_hash:sub(1, 7), vim.log.levels.INFO) - end - else - vim.notify('Failed to get commit hash: ' .. (hash_err or 'unknown'), vim.log.levels.ERROR) - end - else - vim.notify('Failed to create baseline commit', vim.log.levels.ERROR) - if commit_err then - vim.notify('Error: ' .. commit_err, vim.log.levels.ERROR) - end - end + -- This function is deprecated but kept for compatibility + -- The baseline update is now handled directly in accept_current_hunk + vim.notify('update_baseline_after_accept is deprecated', vim.log.levels.DEBUG) end -- Test keymap functionality