@ -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 <leader>dd to open diffview, <leader>df for fugitive, <leader>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 <leader>dd for cumulative view, <leader>dh to browse. ' ,
# claude_stashes
) , vim.log . levels.INFO )
vim.notify ( string.format ( ' Found %d Claude stash(es). Use <leader>dd for cumulative view, <leader>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 ' , ' <leader>dl ' , M.list_changes , { desc = ' List Claude changed files ' } )
vim.keymap . set ( ' n ' , ' <leader>da ' , M.accept_changes , { desc = ' Accept all Claude changes ' } )
vim.keymap . set ( ' n ' , ' <leader>dr ' , M.decline_changes , { desc = ' Decline all Claude changes ' } )
-- Stash browsing
vim.keymap . set ( ' n ' , ' <leader>dh ' , M.browse_claude_stashes , { desc = ' Browse Claude stash history ' } )
vim.keymap . set ( ' n ' , ' <leader>dp ' , M.previous_stash , { desc = ' View previous Claude stash ' } )
vim.keymap . set ( ' n ' , ' <leader>dn ' , M.next_stash , { desc = ' View next Claude stash ' } )
-- Unified view
vim.keymap . set ( ' n ' , ' <leader>du ' , M.open_unified_view , { desc = ' Open Claude diff in unified view ' } )
-- Hunk operations
vim.keymap . set ( ' n ' , ' <leader>dka ' , M.accept_hunk_at_cursor , { desc = ' Accept Claude hunk at cursor ' } )
vim.keymap . set ( ' n ' , ' <leader>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 ' , ' <CR> ' , 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 ' , ' <CR> ' , 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 ' , ' <CR> ' , 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 ' , ' <CR> ' , 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
return M