multi file edit
This commit is contained in:
parent
720778cd2c
commit
dac46b9a93
111
LICENSE.md
111
LICENSE.md
|
@ -1,114 +1,19 @@
|
||||||
DEBUG LOGGING: Check /tmp/claude-python-hook.log for debug output!
|
MIT License
|
||||||
MIT License - ACCEPT TEST 1: Try accepting this first hunk
|
|
||||||
|
|
||||||
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
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
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:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in all
|
||||||
copies or substantial portions of the Software.
|
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
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
ACCEPT TEST 3: Third hunk should still be visible
|
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
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
TEST 3: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
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
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
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
|
# kickstart.nvim
|
||||||
|
|
||||||
|
> Enhanced with nvim-claude integration for AI-powered development
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
|
@ -228,3 +229,4 @@ sudo pacman -S --noconfirm --needed gcc make git ripgrep fd unzip neovim
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
# Test manual edit
|
||||||
|
|
|
@ -25,10 +25,13 @@ What is Kickstart?
|
||||||
Kickstart.nvim is *not* a distribution.
|
Kickstart.nvim is *not* a distribution.
|
||||||
|
|
||||||
Kickstart.nvim is a starting point for your own configuration.
|
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
|
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.
|
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
|
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
|
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!
|
or immediately breaking it into modular pieces. It's up to you!
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,40 @@ local M = {}
|
||||||
-- Track hook state
|
-- Track hook state
|
||||||
M.pre_edit_commit = nil
|
M.pre_edit_commit = nil
|
||||||
M.stable_baseline_ref = nil -- The stable baseline to compare all changes against
|
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()
|
function M.setup()
|
||||||
-- Setup persistence layer on startup
|
-- Setup persistence layer on startup
|
||||||
vim.defer_fn(function()
|
vim.defer_fn(function()
|
||||||
M.create_startup_baseline()
|
M.setup_persistence()
|
||||||
end, 500)
|
end, 500)
|
||||||
|
|
||||||
|
-- Set up autocmd for opening files
|
||||||
|
M.setup_file_open_autocmd()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Pre-tool-use hook: Create baseline stash if we don't have one
|
-- 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
|
-- Get list of modified files
|
||||||
local modified_files = {}
|
local modified_files = {}
|
||||||
|
local inline_diff = require 'nvim-claude.inline-diff'
|
||||||
|
|
||||||
for line in status_result:gmatch '[^\n]+' do
|
for line in status_result:gmatch '[^\n]+' do
|
||||||
local file = line:match '^.M (.+)$' or line:match '^M. (.+)$' or line:match '^.. (.+)$'
|
local file = line:match '^.M (.+)$' or line:match '^M. (.+)$' or line:match '^.. (.+)$'
|
||||||
if file then
|
if file then
|
||||||
table.insert(modified_files, file)
|
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
|
||||||
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
|
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 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'))
|
stash_ref = persistence.create_stash('nvim-claude: baseline ' .. os.date('%Y-%m-%d %H:%M:%S'))
|
||||||
M.stable_baseline_ref = stash_ref
|
M.stable_baseline_ref = stash_ref
|
||||||
persistence.current_stash_ref = stash_ref
|
persistence.current_stash_ref = stash_ref
|
||||||
end
|
end
|
||||||
|
|
||||||
if stash_ref or has_baselines then
|
if stash_ref then
|
||||||
-- Check if any modified files are currently open in buffers
|
-- Process inline diffs for currently open buffers
|
||||||
local opened_inline = false
|
local opened_inline = false
|
||||||
|
|
||||||
for _, file in ipairs(modified_files) do
|
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
|
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)
|
local buf_name = vim.api.nvim_buf_get_name(buf)
|
||||||
if buf_name == full_path or buf_name:match('/' .. file:gsub('([^%w])', '%%%1') .. '$') then
|
if buf_name == full_path or buf_name:match('/' .. file:gsub('([^%w])', '%%%1') .. '$') then
|
||||||
-- Get the original content - prefer in-memory baseline if available
|
-- Show inline diff for this open buffer
|
||||||
local original_content = nil
|
M.show_inline_diff_for_file(buf, file, git_root, stash_ref)
|
||||||
|
opened_inline = true
|
||||||
|
|
||||||
-- Check for in-memory baseline first
|
-- Switch to that buffer if it's not the current one
|
||||||
if inline_diff.original_content[buf] then
|
if buf ~= vim.api.nvim_get_current_buf() then
|
||||||
original_content = inline_diff.original_content[buf]
|
vim.api.nvim_set_current_buf(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
|
end
|
||||||
|
|
||||||
if original_content then
|
break -- Only show inline diff for first matching buffer
|
||||||
-- 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
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -145,6 +149,33 @@ function M.post_tool_use_hook()
|
||||||
end
|
end
|
||||||
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
|
-- Test inline diff manually
|
||||||
function M.test_inline_diff()
|
function M.test_inline_diff()
|
||||||
vim.notify('Testing inline diff manually...', vim.log.levels.INFO)
|
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)
|
inline_diff.show_inline_diff(bufnr, original_content, current_content)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Create baseline on Neovim startup (now just sets up persistence)
|
-- Set up autocmd to check for diffs when opening files
|
||||||
function M.create_startup_baseline()
|
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'
|
local persistence = require 'nvim-claude.inline-diff-persistence'
|
||||||
|
|
||||||
-- Setup persistence autocmds
|
-- Setup persistence autocmds
|
||||||
|
@ -224,14 +285,12 @@ function M.create_startup_baseline()
|
||||||
-- Try to restore any saved diffs
|
-- Try to restore any saved diffs
|
||||||
local restored = persistence.restore_diffs()
|
local restored = persistence.restore_diffs()
|
||||||
|
|
||||||
-- If no diffs were restored and we don't have a baseline, create one now
|
-- Also restore the baseline reference from persistence if it exists
|
||||||
if not restored and not M.stable_baseline_ref then
|
if persistence.current_stash_ref then
|
||||||
local stash_ref = persistence.create_stash('nvim-claude: startup baseline ' .. os.date('%Y-%m-%d %H:%M:%S'))
|
M.stable_baseline_ref = persistence.current_stash_ref
|
||||||
if stash_ref then
|
|
||||||
M.stable_baseline_ref = stash_ref
|
|
||||||
persistence.current_stash_ref = stash_ref
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Don't create a startup baseline - only create baselines when Claude makes edits
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Manual hook testing
|
-- Manual hook testing
|
||||||
|
@ -474,6 +533,7 @@ function M.setup_commands()
|
||||||
-- Clear stable baseline reference
|
-- Clear stable baseline reference
|
||||||
M.stable_baseline_ref = nil
|
M.stable_baseline_ref = nil
|
||||||
persistence.current_stash_ref = nil
|
persistence.current_stash_ref = nil
|
||||||
|
M.claude_edited_files = {}
|
||||||
|
|
||||||
-- Clear persistence state
|
-- Clear persistence state
|
||||||
persistence.clear_state()
|
persistence.clear_state()
|
||||||
|
@ -482,6 +542,52 @@ function M.setup_commands()
|
||||||
end, {
|
end, {
|
||||||
desc = 'Reset Claude baseline for cumulative diffs',
|
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
|
end
|
||||||
|
|
||||||
-- Cleanup old temp files (no longer cleans up commits)
|
-- 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 = {
|
local state = {
|
||||||
version = 1,
|
version = 1,
|
||||||
timestamp = os.time(),
|
timestamp = os.time(),
|
||||||
stash_ref = diff_data.stash_ref,
|
stash_ref = diff_data.stash_ref,
|
||||||
|
claude_edited_files = hooks.claude_edited_files or {},
|
||||||
files = {}
|
files = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +151,12 @@ function M.restore_diffs()
|
||||||
-- Store the stash reference for future operations
|
-- Store the stash reference for future operations
|
||||||
M.current_stash_ref = state.stash_ref
|
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
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -230,6 +239,12 @@ function M.setup_autocmds()
|
||||||
|
|
||||||
if has_active_diffs and M.current_stash_ref then
|
if has_active_diffs and M.current_stash_ref then
|
||||||
M.save_state({ stash_ref = M.current_stash_ref })
|
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
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
|
@ -73,6 +73,9 @@ function M.compute_diff(old_text, new_text)
|
||||||
)
|
)
|
||||||
local diff_output = utils.exec(cmd)
|
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
|
-- Parse diff into hunks
|
||||||
local hunks = M.parse_diff(diff_output)
|
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.keymap.set('n', '[h', function() M.prev_hunk(bufnr) end,
|
||||||
vim.tbl_extend('force', opts, { desc = 'Previous Claude hunk' }))
|
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
|
-- Accept/Reject
|
||||||
vim.keymap.set('n', '<leader>ia', function() M.accept_current_hunk(bufnr) end,
|
vim.keymap.set('n', '<leader>ia', function() M.accept_current_hunk(bufnr) end,
|
||||||
vim.tbl_extend('force', opts, { desc = 'Accept Claude hunk' }))
|
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
|
jump_line = hunk.new_start
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Move cursor to the actual changed line
|
-- Move cursor to the actual changed line (only if we have a valid window)
|
||||||
vim.api.nvim_win_set_cursor(0, {jump_line, 0})
|
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
|
-- Update status
|
||||||
vim.notify(string.format('Hunk %d/%d', hunk_idx, #diff_data.hunks), vim.log.levels.INFO)
|
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)
|
M.jump_to_hunk(bufnr, prev_idx)
|
||||||
end
|
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
|
-- Accept current hunk
|
||||||
function M.accept_current_hunk(bufnr)
|
function M.accept_current_hunk(bufnr)
|
||||||
local diff_data = M.active_diffs[bufnr]
|
local diff_data = M.active_diffs[bufnr]
|
||||||
if not diff_data then return end
|
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
|
if not hunk then return end
|
||||||
|
|
||||||
-- Mark this hunk as processed
|
vim.notify(string.format('Accepting hunk %d/%d', hunk_idx, #diff_data.hunks), vim.log.levels.INFO)
|
||||||
vim.notify(string.format('Accepted hunk %d/%d', diff_data.current_hunk, #diff_data.hunks), vim.log.levels.INFO)
|
|
||||||
|
|
||||||
-- For single hunk case
|
-- Debug: Show current baseline
|
||||||
if #diff_data.hunks == 1 then
|
local hooks = require('nvim-claude.hooks')
|
||||||
vim.notify('All changes accepted. Closing inline diff.', vim.log.levels.INFO)
|
local persistence = require('nvim-claude.inline-diff-persistence')
|
||||||
M.close_inline_diff(bufnr, true) -- Keep new baseline
|
vim.notify('Current baseline: ' .. (hooks.stable_baseline_ref or persistence.current_stash_ref or 'none'), vim.log.levels.INFO)
|
||||||
|
|
||||||
|
-- Accept = keep current state, update baseline
|
||||||
|
M.apply_hunk_to_baseline(bufnr, hunk_idx, 'accept')
|
||||||
|
|
||||||
|
-- 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
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Multiple hunks: remove the accepted hunk and continue
|
local baseline_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, stash_ref, relative_path)
|
||||||
table.remove(diff_data.hunks, diff_data.current_hunk)
|
local new_baseline = utils.exec(baseline_cmd)
|
||||||
|
|
||||||
-- Adjust current hunk index
|
if new_baseline then
|
||||||
if diff_data.current_hunk > #diff_data.hunks then
|
M.original_content[bufnr] = new_baseline
|
||||||
diff_data.current_hunk = #diff_data.hunks
|
|
||||||
end
|
|
||||||
|
|
||||||
if #diff_data.hunks == 0 then
|
-- Get current content
|
||||||
-- No more hunks
|
local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||||
vim.notify('All changes accepted. Closing inline diff.', vim.log.levels.INFO)
|
local current_content = table.concat(current_lines, '\n')
|
||||||
M.close_inline_diff(bufnr, true)
|
|
||||||
else
|
-- Recalculate diff
|
||||||
-- Refresh visualization to show remaining hunks
|
local new_diff_data = M.compute_diff(new_baseline, current_content)
|
||||||
M.apply_diff_visualization(bufnr)
|
|
||||||
M.jump_to_hunk(bufnr, diff_data.current_hunk)
|
if not new_diff_data or #new_diff_data.hunks == 0 then
|
||||||
vim.notify(string.format('%d hunks remaining', #diff_data.hunks), vim.log.levels.INFO)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -396,47 +573,84 @@ function M.reject_current_hunk(bufnr)
|
||||||
return
|
return
|
||||||
end
|
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
|
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
|
return
|
||||||
end
|
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)
|
||||||
|
|
||||||
-- Revert the hunk by applying original content
|
-- For reject, apply the patch in reverse to the current file
|
||||||
M.revert_hunk_changes(bufnr, hunk)
|
-- 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 .. '/', '')
|
||||||
|
|
||||||
-- Save the buffer to ensure changes are on disk
|
-- 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
|
||||||
|
|
||||||
|
vim.fn.delete(patch_file)
|
||||||
|
|
||||||
|
-- Reload the buffer
|
||||||
vim.api.nvim_buf_call(bufnr, function()
|
vim.api.nvim_buf_call(bufnr, function()
|
||||||
if vim.bo.modified then
|
vim.cmd('checktime')
|
||||||
vim.cmd('write')
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Get current content after rejection
|
-- Recalculate diff against unchanged baseline
|
||||||
local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
local hooks = require('nvim-claude.hooks')
|
||||||
local current_content = table.concat(current_lines, '\n')
|
|
||||||
|
|
||||||
-- Recalculate diff between current state (with rejected hunk) and original baseline
|
-- Get the new baseline content
|
||||||
local new_diff_data = M.compute_diff(M.original_content[bufnr], current_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
|
if new_baseline then
|
||||||
-- No more changes from baseline - close the diff
|
M.original_content[bufnr] = new_baseline
|
||||||
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
|
|
||||||
|
|
||||||
-- The new_content should remain as Claude's original suggestion
|
-- Get current content
|
||||||
-- so we can continue to accept remaining hunks if desired
|
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
|
-- Recalculate diff
|
||||||
M.apply_diff_visualization(bufnr)
|
local new_diff_data = M.compute_diff(new_baseline, current_content)
|
||||||
M.jump_to_hunk(bufnr, 1)
|
|
||||||
vim.notify(string.format('%d hunks remaining', #diff_data.hunks), vim.log.levels.INFO)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -633,8 +847,6 @@ function M.close_inline_diff(bufnr, keep_baseline)
|
||||||
-- Remove buffer-local keymaps
|
-- 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', '[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>ia', { buffer = bufnr })
|
||||||
pcall(vim.keymap.del, 'n', '<leader>ir', { buffer = bufnr })
|
pcall(vim.keymap.del, 'n', '<leader>ir', { buffer = bufnr })
|
||||||
pcall(vim.keymap.del, 'n', '<leader>iA', { 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
|
M.diff_files[file_path] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Only clear baseline if not explicitly told to keep it
|
-- Clear the in-memory baseline
|
||||||
if not keep_baseline then
|
M.original_content[bufnr] = nil
|
||||||
M.original_content[bufnr] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check if all diffs are closed
|
-- Check if all diffs are closed
|
||||||
local has_active_diffs = false
|
local has_active_diffs = false
|
||||||
|
@ -674,6 +884,7 @@ function M.close_inline_diff(bufnr, keep_baseline)
|
||||||
-- Reset the stable baseline in hooks
|
-- Reset the stable baseline in hooks
|
||||||
local hooks = require('nvim-claude.hooks')
|
local hooks = require('nvim-claude.hooks')
|
||||||
hooks.stable_baseline_ref = nil
|
hooks.stable_baseline_ref = nil
|
||||||
|
hooks.claude_edited_files = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.notify('Inline diff closed', vim.log.levels.INFO)
|
vim.notify('Inline diff closed', vim.log.levels.INFO)
|
||||||
|
@ -709,10 +920,15 @@ end
|
||||||
function M.next_diff_file()
|
function M.next_diff_file()
|
||||||
local current_file = vim.api.nvim_buf_get_name(0)
|
local current_file = vim.api.nvim_buf_get_name(0)
|
||||||
local files_with_diffs = {}
|
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
|
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)
|
table.insert(files_with_diffs, file_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -749,10 +965,15 @@ end
|
||||||
function M.prev_diff_file()
|
function M.prev_diff_file()
|
||||||
local current_file = vim.api.nvim_buf_get_name(0)
|
local current_file = vim.api.nvim_buf_get_name(0)
|
||||||
local files_with_diffs = {}
|
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
|
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)
|
table.insert(files_with_diffs, file_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -788,15 +1009,27 @@ end
|
||||||
-- List all files with active diffs
|
-- List all files with active diffs
|
||||||
function M.list_diff_files()
|
function M.list_diff_files()
|
||||||
local files_with_diffs = {}
|
local files_with_diffs = {}
|
||||||
|
local hooks = require('nvim-claude.hooks')
|
||||||
|
|
||||||
for file_path, bufnr in pairs(M.diff_files) do
|
for file_path, bufnr in pairs(M.diff_files) do
|
||||||
if M.active_diffs[bufnr] then
|
-- Check if we have active diffs for this buffer, or if it's a tracked file not yet opened
|
||||||
local diff_data = M.active_diffs[bufnr]
|
if (bufnr > 0 and M.active_diffs[bufnr]) or bufnr == -1 then
|
||||||
table.insert(files_with_diffs, {
|
local diff_data = bufnr > 0 and M.active_diffs[bufnr] or nil
|
||||||
path = file_path,
|
local relative_path = vim.fn.fnamemodify(file_path, ':~:.')
|
||||||
hunks = #diff_data.hunks,
|
|
||||||
name = vim.fn.fnamemodify(file_path, ':t')
|
-- 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -808,11 +1041,35 @@ function M.list_diff_files()
|
||||||
-- Sort by filename
|
-- Sort by filename
|
||||||
table.sort(files_with_diffs, function(a, b) return a.name < b.name end)
|
table.sort(files_with_diffs, function(a, b) return a.name < b.name end)
|
||||||
|
|
||||||
-- Display list
|
-- Create items for vim.ui.select
|
||||||
vim.notify('Files with active diffs:', vim.log.levels.INFO)
|
local items = {}
|
||||||
|
local display_items = {}
|
||||||
|
|
||||||
for i, file_info in ipairs(files_with_diffs) do
|
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
|
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
|
end
|
||||||
|
|
||||||
return M
|
return M
|
|
@ -59,9 +59,36 @@ function M.setup(config, commands)
|
||||||
l = { 'List Agents' },
|
l = { 'List Agents' },
|
||||||
k = { 'Kill Agent' },
|
k = { 'Kill Agent' },
|
||||||
x = { 'Clean Old Agents' },
|
x = { 'Clean Old Agents' },
|
||||||
|
i = { 'List files with diffs' },
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
return M
|
return M
|
Loading…
Reference in New Issue