multi file edit

pull/1635/head
zolinthecow 1 day ago
parent 720778cd2c
commit dac46b9a93

@ -1,114 +1,19 @@
DEBUG LOGGING: Check /tmp/claude-python-hook.log for debug output!
MIT License - ACCEPT TEST 1: Try accepting this first hunk
MIT License
MULTI-EDIT TEST #1: Permission is hereby granted, free of charge, to any person obtaining a copy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
ACCEPT TEST 2: This should remain after accepting the first
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
hi
MULTI-EDIT TEST #2: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
ACCEPT TEST 3: Third hunk should still be visible
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. [EDIT 2: Middle change] IN NO EVENT SHALL THE
TEST 3: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. [EDIT 2: Middle change] IN NO EVENT SHALL THE
TEST 2: Middle section edit - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
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.
HUNK TEST #4: test line modified by Claude
SEPARATED HUNK #3: This line should trigger inline diff automatically via hooks!
MANUAL HOOK TEST: This line was added after you ran pre_tool_use_hook!
AUTOMATIC HOOK TEST: This should trigger hooks automatically!
RESTART TEST: This edit is after Neovim restart - hooks should work now!
HOOKS FIXED: This edit should trigger the pre/post hooks and show inline diff!
TEST AFTER REBASE: This line should trigger the inline diff automatically!
HOOK TEST: Testing with corrected settings.json!
RESTART COMPLETE: Testing hooks after Neovim restart!
MANUAL HOOK TEST: Testing with manual hook execution!
DEBUG HOOK TEST: Testing if Claude Code calls the hook script!
POST-HOOK DEBUG: Testing with enhanced debugging!
FRESH NEOVIM: Testing inline diff after restart!
DEBUG LOGGING: Testing with detailed debug logging!
NOTIFICATION TEST: Testing with enhanced notifications after restart!
CLEAR LOG TEST: Testing after clearing debug log!
ESCAPE FIX: Testing with fixed escape sequence in settings.json!
SCOPE FIX: Testing with fixed baseline_ref scope!
INLINE DIFF TEST: Testing if inline diff finally works!
SCRIPT APPROACH: Testing with the hook script instead of hardcoded server!
HOOK EXECUTION TEST: Checking if Claude Code executes the hook script!
SIMPLE HOOK TEST: Testing with a very simple hook script!
VERBOSE HOOK TEST: Testing with detailed logging to debug hook execution!
TOUCH HOOK TEST: Testing with simplest possible hook that just touches a file!
PROPER HOOK TEST: Testing with a hook that follows Claude Code's expected format!
HOOKS WORKING: The hooks are executing! Let's see if inline diff appears!
DEBUG LOGS REMOVED: Testing inline diff without debug messages blocking execution!
WORKING INLINE DIFF: This edit should trigger the inline diff automatically!
FINAL TEST: With improved hook script - inline diff should appear now!
AUTOMATIC INLINE DIFF: This should appear automatically without any prompts!
NEW EDIT: This line is different from the baseline and should trigger inline diff!
RESTART TEST: Testing if inline diff works after Neovim restart!
BASELINE FIX TEST: This should properly update baseline when accepted!
DEBUG BASELINE: This edit includes debugging to see why baseline commits fail!
AFTER RESTART: Testing baseline commit creation with debug output!
BASELINE ISSUE: The pre-hook runs after edits, so baseline includes changes!
MULTI-EDIT TEST #3: Testing multiple hunks and navigation!
TEST EDIT #2: Checking consistency of diff display!
EDIT #2: Middle section replacement - this should be the second hunk!
MULTI-EDIT TEST #4: Testing hunk navigation with ]h and [h!
## INLINE DIFF SYSTEM V2
Our enhanced inline diff now uses git's histogram algorithm and proper stash-based baselines!
## TESTING HOOKS WITHOUT NOTIFICATIONS
The hooks now run silently without vim.notify interruptions!
EDIT #3: Bottom addition - this is the third hunk for testing!
EDIT 3: Final test line at the very bottom of the file!
ACCEPT TEST 4: Fourth and final hunk for testing!

@ -1,5 +1,6 @@
# kickstart.nvim
> Enhanced with nvim-claude integration for AI-powered development
## Introduction
@ -228,3 +229,4 @@ sudo pacman -S --noconfirm --needed gcc make git ripgrep fd unzip neovim
```
</details>
# Test manual edit

@ -25,10 +25,13 @@ What is Kickstart?
Kickstart.nvim is *not* a distribution.
Kickstart.nvim is a starting point for your own configuration.
NOW ENHANCED: This configuration includes nvim-claude integration!
The goal is that you can read every line of code, top-to-bottom, understand
what your configuration is doing, and modify it to suit your needs.
Once you've done that, you can start exploring, configuring and tinkering to
make Neovim your own! That might mean leaving Kickstart just the way it is for now
make Neovim your own! That might mean leaving Kickstart just the way it is for a while
or immediately breaking it into modular pieces. It's up to you!

@ -4,12 +4,40 @@ local M = {}
-- Track hook state
M.pre_edit_commit = nil
M.stable_baseline_ref = nil -- The stable baseline to compare all changes against
M.claude_edited_files = {} -- Track which files Claude has edited
-- Update stable baseline after accepting changes
function M.update_stable_baseline()
local utils = require('nvim-claude.utils')
local persistence = require('nvim-claude.inline-diff-persistence')
-- Create a new stash with current state as the new baseline
local message = 'nvim-claude-baseline-accepted-' .. os.time()
-- Create a stash object without removing changes from working directory
local stash_cmd = 'git stash create'
local stash_hash, err = utils.exec(stash_cmd)
if not 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('git stash store -m "%s" %s', message, stash_hash)
utils.exec(store_cmd)
-- Update our stable baseline reference
M.stable_baseline_ref = stash_hash
persistence.current_stash_ref = stash_hash
end
end
function M.setup()
-- Setup persistence layer on startup
vim.defer_fn(function()
M.create_startup_baseline()
M.setup_persistence()
end, 500)
-- Set up autocmd for opening files
M.setup_file_open_autocmd()
end
-- Pre-tool-use hook: Create baseline stash if we don't have one
@ -54,35 +82,33 @@ function M.post_tool_use_hook()
-- Get list of modified files
local modified_files = {}
local inline_diff = require 'nvim-claude.inline-diff'
for line in status_result:gmatch '[^\n]+' do
local file = line:match '^.M (.+)$' or line:match '^M. (.+)$' or line:match '^.. (.+)$'
if file then
table.insert(modified_files, file)
-- Track that Claude edited this file
M.claude_edited_files[file] = true
-- Track this file in the diff files list immediately
local full_path = git_root .. '/' .. file
inline_diff.diff_files[full_path] = -1 -- Use -1 to indicate no buffer yet
end
end
-- Use the stable baseline reference for comparison
-- Always use the stable baseline reference for comparison
local stash_ref = M.stable_baseline_ref or persistence.current_stash_ref
-- Check for in-memory baselines first
local inline_diff = require 'nvim-claude.inline-diff'
local has_baselines = false
for _, content in pairs(inline_diff.original_content) do
if content then
has_baselines = true
break
end
end
-- If no baseline exists at all, create one now (shouldn't happen normally)
if not stash_ref and not has_baselines then
if not stash_ref then
stash_ref = persistence.create_stash('nvim-claude: baseline ' .. os.date('%Y-%m-%d %H:%M:%S'))
M.stable_baseline_ref = stash_ref
persistence.current_stash_ref = stash_ref
end
if stash_ref or has_baselines then
-- Check if any modified files are currently open in buffers
if stash_ref then
-- Process inline diffs for currently open buffers
local opened_inline = false
for _, file in ipairs(modified_files) do
@ -93,38 +119,16 @@ 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 - prefer in-memory baseline if available
local original_content = nil
-- Check for in-memory baseline first
if inline_diff.original_content[buf] then
original_content = inline_diff.original_content[buf]
elseif stash_ref then
-- Fall back to stash baseline
local stash_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, stash_ref, file)
original_content = utils.exec(stash_cmd)
end
-- Show inline diff for this open buffer
M.show_inline_diff_for_file(buf, file, git_root, stash_ref)
opened_inline = true
if original_content then
-- Get current content
local current_lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
local current_content = table.concat(current_lines, '\n')
-- Debug: Log content lengths
-- vim.notify(string.format('DEBUG: Baseline has %d chars, current has %d chars',
-- #(original_content or ''), #current_content), vim.log.levels.WARN)
-- Show inline diff
inline_diff.show_inline_diff(buf, original_content, current_content)
opened_inline = true
-- Switch to that buffer if it's not the current one
if buf ~= vim.api.nvim_get_current_buf() then
vim.api.nvim_set_current_buf(buf)
end
break -- Only show inline diff for first matching buffer
-- Switch to that buffer if it's not the current one
if buf ~= vim.api.nvim_get_current_buf() then
vim.api.nvim_set_current_buf(buf)
end
break -- Only show inline diff for first matching buffer
end
end
end
@ -145,6 +149,33 @@ function M.post_tool_use_hook()
end
end
-- Helper function to show inline diff for a file
function M.show_inline_diff_for_file(buf, file, git_root, stash_ref)
local utils = require 'nvim-claude.utils'
local inline_diff = require 'nvim-claude.inline-diff'
-- Only show inline diff if Claude edited this file
if not M.claude_edited_files[file] then
return false
end
-- Get baseline from git stash
local stash_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, stash_ref, file)
local original_content = utils.exec(stash_cmd)
if original_content then
-- Get current content
local current_lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
local current_content = table.concat(current_lines, '\n')
-- Show inline diff
inline_diff.show_inline_diff(buf, original_content, current_content)
return true
end
return false
end
-- Test inline diff manually
function M.test_inline_diff()
vim.notify('Testing inline diff manually...', vim.log.levels.INFO)
@ -214,8 +245,38 @@ function M.test_inline_diff()
inline_diff.show_inline_diff(bufnr, original_content, current_content)
end
-- Create baseline on Neovim startup (now just sets up persistence)
function M.create_startup_baseline()
-- Set up autocmd to check for diffs when opening files
function M.setup_file_open_autocmd()
vim.api.nvim_create_autocmd({"BufRead", "BufNewFile"}, {
pattern = "*",
callback = function(args)
local bufnr = args.buf
local file_path = vim.api.nvim_buf_get_name(bufnr)
if file_path == '' then return end
local utils = require 'nvim-claude.utils'
local git_root = utils.get_project_root()
if not git_root then return end
-- Get relative path
local relative_path = file_path:gsub(git_root .. '/', '')
-- Check if this file was edited by Claude
if M.claude_edited_files[relative_path] and M.stable_baseline_ref then
-- Show inline diff for this file
vim.defer_fn(function()
M.show_inline_diff_for_file(bufnr, relative_path, git_root, M.stable_baseline_ref)
end, 50) -- Small delay to ensure buffer is fully loaded
end
end,
group = vim.api.nvim_create_augroup('NvimClaudeFileOpen', { clear = true })
})
end
-- Setup persistence and restore saved state on Neovim startup
function M.setup_persistence()
local persistence = require 'nvim-claude.inline-diff-persistence'
-- Setup persistence autocmds
@ -224,14 +285,12 @@ function M.create_startup_baseline()
-- Try to restore any saved diffs
local restored = persistence.restore_diffs()
-- If no diffs were restored and we don't have a baseline, create one now
if not restored and not M.stable_baseline_ref then
local stash_ref = persistence.create_stash('nvim-claude: startup baseline ' .. os.date('%Y-%m-%d %H:%M:%S'))
if stash_ref then
M.stable_baseline_ref = stash_ref
persistence.current_stash_ref = stash_ref
end
-- Also restore the baseline reference from persistence if it exists
if persistence.current_stash_ref then
M.stable_baseline_ref = persistence.current_stash_ref
end
-- Don't create a startup baseline - only create baselines when Claude makes edits
end
-- Manual hook testing
@ -474,6 +533,7 @@ function M.setup_commands()
-- Clear stable baseline reference
M.stable_baseline_ref = nil
persistence.current_stash_ref = nil
M.claude_edited_files = {}
-- Clear persistence state
persistence.clear_state()
@ -482,6 +542,52 @@ function M.setup_commands()
end, {
desc = 'Reset Claude baseline for cumulative diffs',
})
vim.api.nvim_create_user_command('ClaudeTrackModified', function()
-- Manually track all modified files as Claude-edited
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
local status_cmd = string.format('cd "%s" && git status --porcelain', git_root)
local status_result = utils.exec(status_cmd)
if not status_result or status_result == '' then
vim.notify('No modified files found', vim.log.levels.INFO)
return
end
local count = 0
for line in status_result:gmatch '[^\n]+' do
local file = line:match '^.M (.+)$' or line:match '^M. (.+)$'
if file then
M.claude_edited_files[file] = true
count = count + 1
end
end
vim.notify(string.format('Tracked %d modified files as Claude-edited', count), vim.log.levels.INFO)
-- Also ensure we have a baseline
if not M.stable_baseline_ref then
local persistence = require 'nvim-claude.inline-diff-persistence'
local stash_list = utils.exec('git stash list | grep "nvim-claude: baseline" | head -1')
if stash_list and stash_list ~= '' then
local stash_ref = stash_list:match('^(stash@{%d+})')
if stash_ref then
M.stable_baseline_ref = stash_ref
persistence.current_stash_ref = stash_ref
vim.notify('Using baseline: ' .. stash_ref, vim.log.levels.INFO)
end
end
end
end, {
desc = 'Track all modified files as Claude-edited (for debugging)',
})
end
-- Cleanup old temp files (no longer cleans up commits)

@ -23,10 +23,13 @@ function M.save_state(diff_data)
-- }
-- }
local hooks = require('nvim-claude.hooks')
local state = {
version = 1,
timestamp = os.time(),
stash_ref = diff_data.stash_ref,
claude_edited_files = hooks.claude_edited_files or {},
files = {}
}
@ -148,6 +151,12 @@ function M.restore_diffs()
-- Store the stash reference for future operations
M.current_stash_ref = state.stash_ref
-- Restore Claude edited files tracking
if state.claude_edited_files then
local hooks = require('nvim-claude.hooks')
hooks.claude_edited_files = state.claude_edited_files
end
return true
end
@ -230,6 +239,12 @@ function M.setup_autocmds()
if has_active_diffs and M.current_stash_ref then
M.save_state({ stash_ref = M.current_stash_ref })
else
-- Save just the Claude edited files tracking even if no active diffs
local hooks = require('nvim-claude.hooks')
if hooks.claude_edited_files and next(hooks.claude_edited_files) then
M.save_state({ stash_ref = M.current_stash_ref or '' })
end
end
end
})

@ -73,6 +73,9 @@ function M.compute_diff(old_text, new_text)
)
local diff_output = utils.exec(cmd)
-- Debug: save raw diff
utils.write_file('/tmp/nvim-claude-raw-diff.txt', diff_output)
-- Parse diff into hunks
local hunks = M.parse_diff(diff_output)
@ -258,12 +261,6 @@ function M.setup_inline_keymaps(bufnr)
vim.keymap.set('n', '[h', function() M.prev_hunk(bufnr) end,
vim.tbl_extend('force', opts, { desc = 'Previous Claude hunk' }))
-- Navigation between files
vim.keymap.set('n', ']f', function() M.next_diff_file() end,
vim.tbl_extend('force', opts, { desc = 'Next file with Claude diff' }))
vim.keymap.set('n', '[f', function() M.prev_diff_file() end,
vim.tbl_extend('force', opts, { desc = 'Previous file with Claude diff' }))
-- Accept/Reject
vim.keymap.set('n', '<leader>ia', function() M.accept_current_hunk(bufnr) end,
vim.tbl_extend('force', opts, { desc = 'Accept Claude hunk' }))
@ -317,8 +314,11 @@ function M.jump_to_hunk(bufnr, hunk_idx)
jump_line = hunk.new_start
end
-- Move cursor to the actual changed line
vim.api.nvim_win_set_cursor(0, {jump_line, 0})
-- Move cursor to the actual changed line (only if we have a valid window)
local win = vim.api.nvim_get_current_win()
if vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_buf(win) == bufnr then
vim.api.nvim_win_set_cursor(win, {jump_line, 0})
end
-- Update status
vim.notify(string.format('Hunk %d/%d', hunk_idx, #diff_data.hunks), vim.log.levels.INFO)
@ -350,41 +350,218 @@ function M.prev_hunk(bufnr)
M.jump_to_hunk(bufnr, prev_idx)
end
-- Generate a patch for a single hunk
function M.generate_hunk_patch(hunk, file_path)
local patch_lines = {
string.format("--- a/%s", file_path),
string.format("+++ b/%s", file_path),
hunk.header
}
-- Add the hunk lines
for _, line in ipairs(hunk.lines) do
table.insert(patch_lines, line)
end
-- Ensure patch ends with newline
table.insert(patch_lines, "")
return table.concat(patch_lines, '\n')
end
-- Apply a hunk to the baseline using git patches
function M.apply_hunk_to_baseline(bufnr, hunk_idx, action)
local utils = require('nvim-claude.utils')
local hooks = require('nvim-claude.hooks')
local diff_data = M.active_diffs[bufnr]
local hunk = diff_data.hunks[hunk_idx]
-- Get file paths
local git_root = utils.get_project_root()
local file_path = vim.api.nvim_buf_get_name(bufnr)
local relative_path = file_path:gsub(git_root .. '/', '')
-- Get current baseline
local persistence = require('nvim-claude.inline-diff-persistence')
local stash_ref = hooks.stable_baseline_ref or persistence.current_stash_ref
-- If still no baseline, try to get the most recent nvim-claude baseline from stash list
if not stash_ref then
local stash_list = utils.exec('git stash list | grep "nvim-claude: baseline" | head -1')
if stash_list and stash_list ~= '' then
stash_ref = stash_list:match('^(stash@{%d+})')
if stash_ref then
-- Update both references
hooks.stable_baseline_ref = stash_ref
persistence.current_stash_ref = stash_ref
vim.notify('Using baseline: ' .. stash_ref, vim.log.levels.INFO)
end
end
end
if not stash_ref then
vim.notify('No baseline stash found', vim.log.levels.ERROR)
return false
end
-- Create temp directory
local temp_dir = vim.fn.tempname()
vim.fn.mkdir(temp_dir, 'p')
-- Extract just the file we need from the stash
local extract_cmd = string.format('cd "%s" && git show %s:%s > "%s/%s"',
git_root, stash_ref, relative_path, temp_dir, relative_path)
-- Create directory structure in temp
local file_dir = vim.fn.fnamemodify(temp_dir .. '/' .. relative_path, ':h')
vim.fn.mkdir(file_dir, 'p')
local _, extract_err = utils.exec(extract_cmd)
if extract_err then
vim.notify('Failed to extract file from baseline: ' .. extract_err, vim.log.levels.ERROR)
vim.fn.delete(temp_dir, 'rf')
return false
end
-- For accept: apply the hunk as-is
-- For reject: apply the hunk in reverse
local patch = M.generate_hunk_patch(hunk, relative_path)
local patch_file = temp_dir .. '/hunk.patch'
utils.write_file(patch_file, patch)
-- Debug: save patch content for inspection
local debug_file = '/tmp/nvim-claude-debug-patch.txt'
utils.write_file(debug_file, patch)
vim.notify('Patch saved to: ' .. debug_file, vim.log.levels.INFO)
-- Apply the patch
local apply_flags = action == 'reject' and '--reverse' or ''
local apply_cmd = string.format('cd "%s" && git apply --verbose %s "%s" 2>&1',
temp_dir, apply_flags, patch_file)
local result, apply_err = utils.exec(apply_cmd)
if apply_err or (result and result:match('error:')) then
vim.notify('Failed to apply patch: ' .. (apply_err or result), vim.log.levels.ERROR)
vim.fn.delete(temp_dir, 'rf')
return false
end
-- Now we need to create a new stash with this modified file
-- First, checkout the baseline into a temp git repo
local work_dir = vim.fn.tempname()
vim.fn.mkdir(work_dir, 'p')
-- Create a work tree from the stash
local worktree_cmd = string.format('cd "%s" && git worktree add --detach "%s" %s 2>&1',
git_root, work_dir, stash_ref)
local _, worktree_err = utils.exec(worktree_cmd)
if worktree_err then
vim.notify('Failed to create worktree: ' .. worktree_err, vim.log.levels.ERROR)
vim.fn.delete(temp_dir, 'rf')
vim.fn.delete(work_dir, 'rf')
return false
end
-- Copy the patched file to the worktree
local copy_cmd = string.format('cp "%s/%s" "%s/%s"', temp_dir, relative_path, work_dir, relative_path)
utils.exec(copy_cmd)
-- Stage and create a new stash
local stage_cmd = string.format('cd "%s" && git add "%s"', work_dir, relative_path)
utils.exec(stage_cmd)
-- Create a new stash
local stash_cmd = string.format('cd "%s" && git stash create', work_dir)
local new_stash, stash_err = utils.exec(stash_cmd)
if stash_err or not new_stash or new_stash == '' then
vim.notify('Failed to create new stash', vim.log.levels.ERROR)
else
new_stash = new_stash:gsub('%s+', '')
-- Store the new stash
local store_cmd = string.format('cd "%s" && git stash store -m "nvim-claude-baseline-hunk-%s" %s',
git_root, action, new_stash)
utils.exec(store_cmd)
-- Update the baseline reference
hooks.stable_baseline_ref = new_stash
persistence.current_stash_ref = new_stash
vim.notify('Updated baseline to: ' .. new_stash, vim.log.levels.INFO)
end
-- Clean up worktree
local cleanup_cmd = string.format('cd "%s" && git worktree remove --force "%s"', git_root, work_dir)
utils.exec(cleanup_cmd)
-- Clean up temp files
vim.fn.delete(temp_dir, 'rf')
vim.fn.delete(work_dir, 'rf')
return true
end
-- Accept current hunk
function M.accept_current_hunk(bufnr)
local diff_data = M.active_diffs[bufnr]
if not diff_data then return end
local hunk = diff_data.hunks[diff_data.current_hunk]
local hunk_idx = diff_data.current_hunk
local hunk = diff_data.hunks[hunk_idx]
if not hunk then return end
-- Mark this hunk as processed
vim.notify(string.format('Accepted hunk %d/%d', diff_data.current_hunk, #diff_data.hunks), vim.log.levels.INFO)
vim.notify(string.format('Accepting hunk %d/%d', hunk_idx, #diff_data.hunks), vim.log.levels.INFO)
-- For single hunk case
if #diff_data.hunks == 1 then
vim.notify('All changes accepted. Closing inline diff.', vim.log.levels.INFO)
M.close_inline_diff(bufnr, true) -- Keep new baseline
return
end
-- Debug: Show current baseline
local hooks = require('nvim-claude.hooks')
local persistence = require('nvim-claude.inline-diff-persistence')
vim.notify('Current baseline: ' .. (hooks.stable_baseline_ref or persistence.current_stash_ref or 'none'), vim.log.levels.INFO)
-- Multiple hunks: remove the accepted hunk and continue
table.remove(diff_data.hunks, diff_data.current_hunk)
-- Accept = keep current state, update baseline
M.apply_hunk_to_baseline(bufnr, hunk_idx, 'accept')
-- Adjust current hunk index
if diff_data.current_hunk > #diff_data.hunks then
diff_data.current_hunk = #diff_data.hunks
-- Recalculate diff against new baseline
local utils = require('nvim-claude.utils')
local hooks = require('nvim-claude.hooks')
local git_root = utils.get_project_root()
local file_path = vim.api.nvim_buf_get_name(bufnr)
local relative_path = file_path:gsub(git_root .. '/', '')
-- Get the new baseline content
local persistence = require('nvim-claude.inline-diff-persistence')
local stash_ref = hooks.stable_baseline_ref or persistence.current_stash_ref
if not stash_ref then
vim.notify('No baseline found for recalculation', vim.log.levels.ERROR)
return
end
if #diff_data.hunks == 0 then
-- No more hunks
vim.notify('All changes accepted. Closing inline diff.', vim.log.levels.INFO)
M.close_inline_diff(bufnr, true)
else
-- Refresh visualization to show remaining hunks
M.apply_diff_visualization(bufnr)
M.jump_to_hunk(bufnr, diff_data.current_hunk)
vim.notify(string.format('%d hunks remaining', #diff_data.hunks), vim.log.levels.INFO)
local baseline_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, stash_ref, relative_path)
local new_baseline = utils.exec(baseline_cmd)
if new_baseline then
M.original_content[bufnr] = new_baseline
-- Get current content
local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local current_content = table.concat(current_lines, '\n')
-- Recalculate diff
local new_diff_data = M.compute_diff(new_baseline, current_content)
if not new_diff_data or #new_diff_data.hunks == 0 then
vim.notify('All changes accepted. Closing inline diff.', vim.log.levels.INFO)
M.close_inline_diff(bufnr, true)
else
-- Update diff data
diff_data.hunks = new_diff_data.hunks
diff_data.current_hunk = 1
-- Refresh visualization
M.apply_diff_visualization(bufnr)
M.jump_to_hunk(bufnr, 1)
vim.notify(string.format('%d hunks remaining', #new_diff_data.hunks), vim.log.levels.INFO)
end
end
end
@ -396,47 +573,84 @@ function M.reject_current_hunk(bufnr)
return
end
local hunk = diff_data.hunks[diff_data.current_hunk]
local hunk_idx = diff_data.current_hunk
local hunk = diff_data.hunks[hunk_idx]
if not hunk then
vim.notify('No hunk at index ' .. tostring(diff_data.current_hunk), vim.log.levels.ERROR)
vim.notify('No hunk at index ' .. tostring(hunk_idx), vim.log.levels.ERROR)
return
end
-- vim.notify(string.format('Rejecting hunk %d/%d', diff_data.current_hunk, #diff_data.hunks), vim.log.levels.INFO)
vim.notify(string.format('Rejecting hunk %d/%d', hunk_idx, #diff_data.hunks), vim.log.levels.INFO)
-- For reject, apply the patch in reverse to the current file
-- The baseline stays unchanged
local utils = require('nvim-claude.utils')
local git_root = utils.get_project_root()
local file_path = vim.api.nvim_buf_get_name(bufnr)
local relative_path = file_path:gsub(git_root .. '/', '')
-- Generate patch for this hunk
local patch = M.generate_hunk_patch(hunk, relative_path)
local patch_file = vim.fn.tempname() .. '.patch'
utils.write_file(patch_file, patch)
-- Debug: Show hunk details
vim.notify(string.format('Hunk %d: old_start=%d, new_start=%d, lines=%d',
hunk_idx, hunk.old_start, hunk.new_start, #hunk.lines), vim.log.levels.INFO)
-- Debug: save patch for inspection
local debug_file = '/tmp/nvim-claude-reject-patch.txt'
utils.write_file(debug_file, patch)
-- Apply reverse patch to the working directory
local apply_cmd = string.format('cd "%s" && git apply --reverse --verbose "%s" 2>&1', git_root, patch_file)
local result, err = utils.exec(apply_cmd)
if err or (result and result:match('error:')) then
vim.notify('Failed to reject hunk: ' .. (err or result), vim.log.levels.ERROR)
vim.notify('Patch saved to: ' .. debug_file, vim.log.levels.INFO)
vim.fn.delete(patch_file)
return
end
-- Revert the hunk by applying original content
M.revert_hunk_changes(bufnr, hunk)
vim.fn.delete(patch_file)
-- Save the buffer to ensure changes are on disk
-- Reload the buffer
vim.api.nvim_buf_call(bufnr, function()
if vim.bo.modified then
vim.cmd('write')
end
vim.cmd('checktime')
end)
-- Get current content after rejection
local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local current_content = table.concat(current_lines, '\n')
-- Recalculate diff against unchanged baseline
local hooks = require('nvim-claude.hooks')
-- Recalculate diff between current state (with rejected hunk) and original baseline
local new_diff_data = M.compute_diff(M.original_content[bufnr], current_content)
-- Get the new baseline content
local stash_ref = hooks.stable_baseline_ref
local baseline_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, stash_ref, relative_path)
local new_baseline = utils.exec(baseline_cmd)
if not new_diff_data or #new_diff_data.hunks == 0 then
-- No more changes from baseline - close the diff
vim.notify('All changes processed. Closing inline diff.', vim.log.levels.INFO)
M.close_inline_diff(bufnr, false)
else
-- Update diff data with remaining hunks
diff_data.hunks = new_diff_data.hunks
diff_data.current_hunk = 1
if new_baseline then
M.original_content[bufnr] = new_baseline
-- The new_content should remain as Claude's original suggestion
-- so we can continue to accept remaining hunks if desired
-- Get current content
local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local current_content = table.concat(current_lines, '\n')
-- Refresh visualization and jump to first remaining hunk
M.apply_diff_visualization(bufnr)
M.jump_to_hunk(bufnr, 1)
vim.notify(string.format('%d hunks remaining', #diff_data.hunks), vim.log.levels.INFO)
-- Recalculate diff
local new_diff_data = M.compute_diff(new_baseline, current_content)
if not new_diff_data or #new_diff_data.hunks == 0 then
vim.notify('All changes processed. Closing inline diff.', vim.log.levels.INFO)
M.close_inline_diff(bufnr, false)
else
-- Update diff data
diff_data.hunks = new_diff_data.hunks
diff_data.current_hunk = 1
-- Refresh visualization
M.apply_diff_visualization(bufnr)
M.jump_to_hunk(bufnr, 1)
vim.notify(string.format('%d hunks remaining', #new_diff_data.hunks), vim.log.levels.INFO)
end
end
end
@ -633,8 +847,6 @@ function M.close_inline_diff(bufnr, keep_baseline)
-- Remove buffer-local keymaps
pcall(vim.keymap.del, 'n', ']h', { buffer = bufnr })
pcall(vim.keymap.del, 'n', '[h', { buffer = bufnr })
pcall(vim.keymap.del, 'n', ']f', { buffer = bufnr })
pcall(vim.keymap.del, 'n', '[f', { buffer = bufnr })
pcall(vim.keymap.del, 'n', '<leader>ia', { buffer = bufnr })
pcall(vim.keymap.del, 'n', '<leader>ir', { buffer = bufnr })
pcall(vim.keymap.del, 'n', '<leader>iA', { buffer = bufnr })
@ -651,10 +863,8 @@ function M.close_inline_diff(bufnr, keep_baseline)
M.diff_files[file_path] = nil
end
-- Only clear baseline if not explicitly told to keep it
if not keep_baseline then
M.original_content[bufnr] = nil
end
-- Clear the in-memory baseline
M.original_content[bufnr] = nil
-- Check if all diffs are closed
local has_active_diffs = false
@ -674,6 +884,7 @@ function M.close_inline_diff(bufnr, keep_baseline)
-- Reset the stable baseline in hooks
local hooks = require('nvim-claude.hooks')
hooks.stable_baseline_ref = nil
hooks.claude_edited_files = {}
end
vim.notify('Inline diff closed', vim.log.levels.INFO)
@ -709,10 +920,15 @@ end
function M.next_diff_file()
local current_file = vim.api.nvim_buf_get_name(0)
local files_with_diffs = {}
local hooks = require('nvim-claude.hooks')
-- Collect all files with diffs (both opened and unopened)
local utils = require('nvim-claude.utils')
local git_root = utils.get_project_root()
-- Collect all files with active diffs
for file_path, bufnr in pairs(M.diff_files) do
if M.active_diffs[bufnr] then
local git_relative = file_path:gsub('^' .. vim.pesc(git_root) .. '/', '')
if hooks.claude_edited_files[git_relative] then
table.insert(files_with_diffs, file_path)
end
end
@ -749,10 +965,15 @@ end
function M.prev_diff_file()
local current_file = vim.api.nvim_buf_get_name(0)
local files_with_diffs = {}
local hooks = require('nvim-claude.hooks')
-- Collect all files with diffs (both opened and unopened)
local utils = require('nvim-claude.utils')
local git_root = utils.get_project_root()
-- Collect all files with active diffs
for file_path, bufnr in pairs(M.diff_files) do
if M.active_diffs[bufnr] then
local git_relative = file_path:gsub('^' .. vim.pesc(git_root) .. '/', '')
if hooks.claude_edited_files[git_relative] then
table.insert(files_with_diffs, file_path)
end
end
@ -788,15 +1009,27 @@ end
-- List all files with active diffs
function M.list_diff_files()
local files_with_diffs = {}
local hooks = require('nvim-claude.hooks')
for file_path, bufnr in pairs(M.diff_files) do
if M.active_diffs[bufnr] then
local diff_data = M.active_diffs[bufnr]
table.insert(files_with_diffs, {
path = file_path,
hunks = #diff_data.hunks,
name = vim.fn.fnamemodify(file_path, ':t')
})
-- Check if we have active diffs for this buffer, or if it's a tracked file not yet opened
if (bufnr > 0 and M.active_diffs[bufnr]) or bufnr == -1 then
local diff_data = bufnr > 0 and M.active_diffs[bufnr] or nil
local relative_path = vim.fn.fnamemodify(file_path, ':~:.')
-- Check if this file is still tracked as Claude-edited
local utils = require('nvim-claude.utils')
local git_root = utils.get_project_root()
local git_relative = file_path:gsub('^' .. vim.pesc(git_root) .. '/', '')
if hooks.claude_edited_files[git_relative] then
table.insert(files_with_diffs, {
path = file_path,
hunks = diff_data and #diff_data.hunks or '?',
name = vim.fn.fnamemodify(file_path, ':t'),
relative_path = relative_path,
current_hunk = diff_data and diff_data.current_hunk or 1
})
end
end
end
@ -808,11 +1041,35 @@ function M.list_diff_files()
-- Sort by filename
table.sort(files_with_diffs, function(a, b) return a.name < b.name end)
-- Display list
vim.notify('Files with active diffs:', vim.log.levels.INFO)
-- Create items for vim.ui.select
local items = {}
local display_items = {}
for i, file_info in ipairs(files_with_diffs) do
vim.notify(string.format(' %d. %s (%d hunks)', i, file_info.name, file_info.hunks), vim.log.levels.INFO)
table.insert(items, file_info)
local hunk_info = type(file_info.hunks) == 'number'
and string.format('%d hunks, on hunk %d', file_info.hunks, file_info.current_hunk)
or 'not opened yet'
table.insert(display_items, string.format('%s (%s)',
file_info.relative_path, hunk_info))
end
-- Use vim.ui.select for a telescope-like experience
vim.ui.select(display_items, {
prompt = 'Select file with Claude edits:',
format_item = function(item) return item end,
}, function(choice, idx)
if choice and idx then
local selected_file = items[idx]
vim.cmd('edit ' .. vim.fn.fnameescape(selected_file.path))
-- Jump to the current hunk in the selected file
local bufnr = M.diff_files[selected_file.path]
if bufnr and M.active_diffs[bufnr] then
M.jump_to_hunk(bufnr, M.active_diffs[bufnr].current_hunk)
end
end
end)
end
return M

@ -59,9 +59,36 @@ function M.setup(config, commands)
l = { 'List Agents' },
k = { 'Kill Agent' },
x = { 'Clean Old Agents' },
i = { 'List files with diffs' },
}
})
end
-- Global keymaps for navigating between files with Claude diffs
vim.keymap.set('n', ']f', function()
local inline_diff = require('nvim-claude.inline-diff')
inline_diff.next_diff_file()
end, {
desc = 'Next file with Claude diff',
silent = true
})
vim.keymap.set('n', '[f', function()
local inline_diff = require('nvim-claude.inline-diff')
inline_diff.prev_diff_file()
end, {
desc = 'Previous file with Claude diff',
silent = true
})
-- Global keymap for listing files with diffs
vim.keymap.set('n', prefix .. 'i', function()
local inline_diff = require('nvim-claude.inline-diff')
inline_diff.list_diff_files()
end, {
desc = 'List files with Claude diffs',
silent = true
})
end
return M

@ -1 +0,0 @@
hihi
Loading…
Cancel
Save