inline diffs are pretty good now

pull/1635/head
zolinthecow 2 days ago
parent 91c24b34a6
commit 720778cd2c

@ -1,19 +1,22 @@
MIT License - SEPARATED HUNK #1
DEBUG LOGGING: Check /tmp/claude-python-hook.log for debug output!
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
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
HUNK TEST #2: copies of the Software, and to permit persons to whom the Software is
ACCEPT TEST 2: This should remain after accepting the first
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
SEPARATED HUNK #2: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
HUNK TEST #3: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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
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.
@ -94,10 +97,18 @@ MULTI-EDIT TEST #3: Testing multiple hunks and navigation!
TEST EDIT #2: Checking consistency of diff display!
SEPARATED HUNK #4: Final test with better hunk separation!
EDIT #2: Middle section replacement - this should be the second hunk!
MULTI-EDIT TEST #4: Testing hunk navigation with ]h and [h!
## PERSISTENCE TEST
This tests our new stash-based persistence system that doesn't create git commits!
## 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!

@ -3,6 +3,7 @@ local M = {}
-- Track hook state
M.pre_edit_commit = nil
M.stable_baseline_ref = nil -- The stable baseline to compare all changes against
function M.setup()
-- Setup persistence layer on startup
@ -11,11 +12,22 @@ function M.setup()
end, 500)
end
-- Pre-tool-use hook: Now just validates existing baseline
-- Pre-tool-use hook: Create baseline stash if we don't have one
function M.pre_tool_use_hook()
-- Pre-hook no longer creates baselines
-- Baselines are created on startup or through accept/reject
return
local persistence = require 'nvim-claude.inline-diff-persistence'
-- Only create a baseline if we don't have one yet
if not M.stable_baseline_ref then
-- Create baseline stash synchronously
local stash_ref = persistence.create_stash('nvim-claude: 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
end
-- Return success to allow the tool to proceed
return true
end
-- Post-tool-use hook: Create stash of Claude's changes and trigger diff review
@ -49,17 +61,28 @@ function M.post_tool_use_hook()
end
end
-- Get the stash reference from pre-hook
local stash_ref = persistence.current_stash_ref
if not stash_ref then
-- If no pre-hook stash, create one now
stash_ref = persistence.create_stash('nvim-claude: changes detected ' .. os.date('%Y-%m-%d %H:%M:%S'))
-- 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
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 then
if stash_ref or has_baselines then
-- Check if any modified files are currently open in buffers
local inline_diff = require 'nvim-claude.inline-diff'
local opened_inline = false
for _, file in ipairs(modified_files) do
@ -70,15 +93,27 @@ function M.post_tool_use_hook()
if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_is_loaded(buf) then
local buf_name = vim.api.nvim_buf_get_name(buf)
if buf_name == full_path or buf_name:match('/' .. file:gsub('([^%w])', '%%%1') .. '$') then
-- Get the original content from stash
local stash_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, stash_ref, file)
local original_content, orig_err = utils.exec(stash_cmd)
-- 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
if not orig_err and original_content then
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
@ -187,7 +222,16 @@ function M.create_startup_baseline()
persistence.setup_autocmds()
-- Try to restore any saved diffs
persistence.restore_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
end
end
-- Manual hook testing
@ -337,6 +381,12 @@ function M.setup_commands()
end, {
desc = 'Test Claude keymap functionality',
})
vim.api.nvim_create_user_command('ClaudeDebugInlineDiff', function()
require('nvim-claude.inline-diff-debug').debug_inline_diff()
end, {
desc = 'Debug Claude inline diff state',
})
vim.api.nvim_create_user_command('ClaudeUpdateBaseline', function()
local bufnr = vim.api.nvim_get_current_buf()
@ -412,6 +462,26 @@ function M.setup_commands()
end, {
desc = 'Uninstall Claude Code hooks for this project',
})
vim.api.nvim_create_user_command('ClaudeResetBaseline', function()
-- Clear all baselines and force new baseline on next edit
local inline_diff = require 'nvim-claude.inline-diff'
local persistence = require 'nvim-claude.inline-diff-persistence'
-- Clear in-memory baselines
inline_diff.original_content = {}
-- Clear stable baseline reference
M.stable_baseline_ref = nil
persistence.current_stash_ref = nil
-- Clear persistence state
persistence.clear_state()
vim.notify('Baseline reset. Next edit will create a new baseline.', vim.log.levels.INFO)
end, {
desc = 'Reset Claude baseline for cumulative diffs',
})
end
-- Cleanup old temp files (no longer cleans up commits)

@ -121,6 +121,7 @@ function M.setup(user_config)
M.registry = require('nvim-claude.registry')
M.hooks = require('nvim-claude.hooks')
M.diff_review = require('nvim-claude.diff-review')
M.settings_updater = require('nvim-claude.settings-updater')
-- Initialize submodules with config
M.tmux.setup(M.config.tmux)
@ -128,6 +129,7 @@ function M.setup(user_config)
M.registry.setup(M.config.agents)
M.hooks.setup()
M.diff_review.setup()
M.settings_updater.setup()
-- Set up commands
M.commands.setup(M)
@ -137,7 +139,6 @@ function M.setup(user_config)
vim.defer_fn(function()
if M.utils.get_project_root() then
M.hooks.install_hooks()
vim.notify('Claude Code hooks auto-installed', vim.log.levels.INFO)
end
end, 100)

@ -0,0 +1,57 @@
local M = {}
-- Debug function to check inline diff state
function M.debug_inline_diff()
local inline_diff = require('nvim-claude.inline-diff')
local bufnr = vim.api.nvim_get_current_buf()
vim.notify('=== Inline Diff Debug Info ===', vim.log.levels.INFO)
-- Check if inline diff is active for current buffer
local diff_data = inline_diff.active_diffs[bufnr]
if diff_data then
vim.notify(string.format('✓ Inline diff ACTIVE for buffer %d', bufnr), vim.log.levels.INFO)
vim.notify(string.format(' - Hunks: %d', #diff_data.hunks), vim.log.levels.INFO)
vim.notify(string.format(' - Current hunk: %d', diff_data.current_hunk or 0), vim.log.levels.INFO)
vim.notify(string.format(' - Original content length: %d', #(inline_diff.original_content[bufnr] or '')), vim.log.levels.INFO)
vim.notify(string.format(' - New content length: %d', #(diff_data.new_content or '')), vim.log.levels.INFO)
else
vim.notify(string.format('✗ No inline diff for buffer %d', bufnr), vim.log.levels.WARN)
end
-- Check all active diffs
local count = 0
for buf, _ in pairs(inline_diff.active_diffs) do
count = count + 1
end
vim.notify(string.format('Total active inline diffs: %d', count), vim.log.levels.INFO)
-- Check keymaps
local keymaps = vim.api.nvim_buf_get_keymap(bufnr, 'n')
local found_ir = false
local leader = vim.g.mapleader or '\\'
local ir_pattern = leader .. 'ir'
vim.notify(string.format('Looking for keymap: %s', ir_pattern), vim.log.levels.INFO)
for _, map in ipairs(keymaps) do
if map.lhs == ir_pattern or map.lhs == '<leader>ir' then
found_ir = true
vim.notify(string.format('✓ Found keymap: %s -> %s', map.lhs, map.desc or 'no desc'), vim.log.levels.INFO)
break
end
end
if not found_ir then
vim.notify('✗ <leader>ir keymap not found', vim.log.levels.WARN)
-- List all keymaps that start with leader
vim.notify('Buffer keymaps starting with leader:', vim.log.levels.INFO)
for _, map in ipairs(keymaps) do
if map.lhs:match('^' .. vim.pesc(leader)) or map.lhs:match('^<leader>') then
vim.notify(string.format(' %s -> %s', map.lhs, map.desc or 'no desc'), vim.log.levels.INFO)
end
end
end
end
return M

@ -142,7 +142,7 @@ function M.restore_diffs()
end
if restored_count > 0 then
vim.notify(string.format('Restored inline diffs for %d file(s)', restored_count), vim.log.levels.INFO)
-- Silent restore - no notification
end
-- Store the stash reference for future operations
@ -180,39 +180,31 @@ function M.check_pending_restore(bufnr)
-- Remove from pending
M.pending_restores[file_path] = nil
vim.notify('Restored inline diff for ' .. vim.fn.fnamemodify(file_path, ':~:.'), vim.log.levels.INFO)
-- Silent restore - no notification
end
end
-- Create a stash of current changes (instead of baseline commit)
function M.create_stash(message)
local utils = require('nvim-claude.utils')
message = message or 'nvim-claude: pre-edit state'
-- Check if there are changes to stash
local status = utils.exec('git status --porcelain')
if not status or status == '' then
-- No changes, but we still need to track current state
-- Create an empty stash by making a tiny change
local temp_file = '.nvim-claude-temp'
utils.write_file(temp_file, 'temp')
utils.exec('git add ' .. temp_file)
utils.exec(string.format('git stash push -m "%s" -- %s', message, temp_file))
os.remove(temp_file)
else
-- Stash all current changes
local cmd = string.format('git stash push -m "%s" --include-untracked', message)
local result, err = utils.exec(cmd)
if err and not err:match('Saved working directory') then
vim.notify('Failed to create stash: ' .. err, vim.log.levels.ERROR)
return nil
end
end
-- Create a stash object without removing changes from working directory
local stash_cmd = 'git stash create'
local stash_hash, err = utils.exec(stash_cmd)
-- Get the stash reference
local stash_list = utils.exec('git stash list -n 1')
if stash_list then
local stash_ref = stash_list:match('^(stash@{%d+})')
return stash_ref
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)
-- Get the stash reference
local stash_list = utils.exec('git stash list -n 1')
if stash_list then
local stash_ref = stash_list:match('^(stash@{%d+})')
return stash_ref
end
end
return nil

@ -40,6 +40,9 @@ function M.show_inline_diff(bufnr, old_content, new_content)
applied_hunks = {}
}
-- Debug: Log target content length
-- vim.notify(string.format('DEBUG: Stored target content with %d chars', #new_content), vim.log.levels.WARN)
-- Apply visual indicators
M.apply_diff_visualization(bufnr)
@ -49,7 +52,7 @@ function M.show_inline_diff(bufnr, old_content, new_content)
-- Jump to first hunk
M.jump_to_hunk(bufnr, 1)
vim.notify('Inline diff active. Use [h/]h to navigate, <leader>ia/<leader>ir to accept/reject hunks', vim.log.levels.INFO)
-- Silent activation - no notification
end
-- Compute diff between two texts
@ -63,8 +66,11 @@ function M.compute_diff(old_text, new_text)
utils.write_file(old_file, old_text)
utils.write_file(new_file, new_text)
-- Generate unified diff with minimal context to avoid grouping nearby changes
local cmd = string.format('diff -U1 "%s" "%s" || true', old_file, new_file)
-- Use git diff with histogram algorithm for better code diffs
local cmd = string.format(
'git diff --no-index --no-prefix --unified=1 --diff-algorithm=histogram "%s" "%s" 2>/dev/null || true',
old_file, new_file
)
local diff_output = utils.exec(cmd)
-- Parse diff into hunks
@ -101,6 +107,9 @@ function M.parse_diff(diff_text)
elseif in_hunk and (line:match('^[%+%-]') or line:match('^%s')) then
-- Diff line
table.insert(current_hunk.lines, line)
elseif line:match('^diff %-%-git') or line:match('^index ') or line:match('^%+%+%+ ') or line:match('^%-%-%-') then
-- Skip git diff headers
in_hunk = false
end
end
@ -349,46 +358,51 @@ function M.accept_current_hunk(bufnr)
local hunk = diff_data.hunks[diff_data.current_hunk]
if not hunk then return end
-- Mark as applied (the changes are already in the buffer)
diff_data.applied_hunks[diff_data.current_hunk] = true
-- Mark this hunk as processed
vim.notify(string.format('Accepted hunk %d/%d', diff_data.current_hunk, #diff_data.hunks), vim.log.levels.INFO)
-- Update in-memory baseline to current state
local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local current_content = table.concat(current_lines, '\n')
M.original_content[bufnr] = current_content
-- 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
-- Remove this hunk from the diff data since it's accepted
-- Multiple hunks: remove the accepted hunk and continue
table.remove(diff_data.hunks, diff_data.current_hunk)
-- Adjust current hunk index
if diff_data.current_hunk > #diff_data.hunks then
diff_data.current_hunk = math.max(1, #diff_data.hunks)
diff_data.current_hunk = #diff_data.hunks
end
vim.notify(string.format('Accepted hunk - %d hunks remaining', #diff_data.hunks), vim.log.levels.INFO)
-- Save state for persistence
local persistence = require('nvim-claude.inline-diff-persistence')
persistence.save_state({ stash_ref = persistence.current_stash_ref })
if #diff_data.hunks == 0 then
-- No more hunks to review
vim.notify('All hunks processed! Closing inline diff.', vim.log.levels.INFO)
M.close_inline_diff(bufnr, true) -- Keep baseline for future diffs
-- No more hunks
vim.notify('All changes accepted. Closing inline diff.', vim.log.levels.INFO)
M.close_inline_diff(bufnr, true)
else
-- Refresh visualization and move to current hunk
-- 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)
end
end
-- Reject current hunk
function M.reject_current_hunk(bufnr)
local diff_data = M.active_diffs[bufnr]
if not diff_data then return end
if not diff_data then
vim.notify('No diff data for buffer', vim.log.levels.ERROR)
return
end
local hunk = diff_data.hunks[diff_data.current_hunk]
if not hunk then return end
if not hunk then
vim.notify('No hunk at index ' .. tostring(diff_data.current_hunk), 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)
-- Revert the hunk by applying original content
M.revert_hunk_changes(bufnr, hunk)
@ -400,33 +414,29 @@ function M.reject_current_hunk(bufnr)
end
end)
-- Update in-memory baseline to current state (with rejected changes)
-- 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')
M.original_content[bufnr] = current_content
-- Remove this hunk from the diff data since it's rejected
table.remove(diff_data.hunks, diff_data.current_hunk)
-- Adjust current hunk index
if diff_data.current_hunk > #diff_data.hunks then
diff_data.current_hunk = math.max(1, #diff_data.hunks)
end
vim.notify(string.format('Rejected hunk - %d hunks remaining', #diff_data.hunks), vim.log.levels.INFO)
-- Recalculate diff between current state (with rejected hunk) and original baseline
local new_diff_data = M.compute_diff(M.original_content[bufnr], current_content)
-- Save state for persistence
local persistence = require('nvim-claude.inline-diff-persistence')
persistence.save_state({ stash_ref = persistence.current_stash_ref })
if #diff_data.hunks == 0 then
-- No more hunks to review
vim.notify('All hunks processed! Closing inline diff.', vim.log.levels.INFO)
M.close_inline_diff(bufnr, true) -- Keep baseline after rejection too
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
-- Refresh visualization and move to current hunk
-- 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
-- so we can continue to accept remaining hunks if desired
-- Refresh visualization and jump to first remaining hunk
M.apply_diff_visualization(bufnr)
M.jump_to_hunk(bufnr, diff_data.current_hunk)
M.jump_to_hunk(bufnr, 1)
vim.notify(string.format('%d hunks remaining', #diff_data.hunks), vim.log.levels.INFO)
end
end
@ -434,45 +444,119 @@ end
function M.revert_hunk_changes(bufnr, hunk)
-- Get current buffer lines
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local original_content = M.original_content[bufnr]
if not original_content then
vim.notify('No original content available for rejection', vim.log.levels.ERROR)
return
end
-- Extract the expected content from the hunk
local expected_lines = {}
local original_lines = {}
-- Split original content into lines
local original_lines = vim.split(original_content, '\n')
for _, diff_line in ipairs(hunk.lines) do
if diff_line:match('^%+') then
-- Lines that were added (these should be in current buffer)
table.insert(expected_lines, diff_line:sub(2))
elseif diff_line:match('^%-') then
-- Lines that were removed (these should be restored)
table.insert(original_lines, diff_line:sub(2))
elseif diff_line:match('^%s') then
-- Context lines (should be in both)
table.insert(expected_lines, diff_line:sub(2))
table.insert(original_lines, diff_line:sub(2))
end
end
-- Build new lines by reverting this hunk
local new_lines = {}
local buffer_line = 1
local applied = false
-- Find where this hunk actually is in the current buffer
-- We'll look for the best match by checking context lines too
local hunk_start = nil
local hunk_end = nil
local best_score = -1
local best_start = nil
-- Include some context before and after for better matching
local context_before = {}
local context_after = {}
-- Extract context from the diff
local in_changes = false
for i, diff_line in ipairs(hunk.lines) do
if diff_line:match('^[%+%-]') then
in_changes = true
elseif diff_line:match('^%s') and not in_changes then
-- Context before changes
table.insert(context_before, diff_line:sub(2))
elseif diff_line:match('^%s') and in_changes then
-- Context after changes
table.insert(context_after, diff_line:sub(2))
end
end
while buffer_line <= #lines do
if buffer_line >= hunk.new_start and buffer_line < hunk.new_start + hunk.new_count and not applied then
-- Revert this section by using original lines
local orig_start = hunk.old_start
local orig_end = hunk.old_start + hunk.old_count - 1
-- Search for the hunk by matching content with context
for i = 1, #lines - #expected_lines + 1 do
local score = 0
local matches = true
-- Check the main content
for j = 1, #expected_lines do
if lines[i + j - 1] == expected_lines[j] then
score = score + 1
else
matches = false
end
end
if matches then
-- Bonus points for matching context before
local before_start = i - #context_before
if before_start > 0 then
for j = 1, #context_before do
if lines[before_start + j - 1] == context_before[j] then
score = score + 2 -- Context is worth more
end
end
end
for orig_line = orig_start, orig_end do
if orig_line <= #original_lines then
table.insert(new_lines, original_lines[orig_line])
-- Bonus points for matching context after
local after_start = i + #expected_lines
if after_start + #context_after - 1 <= #lines then
for j = 1, #context_after do
if lines[after_start + j - 1] == context_after[j] then
score = score + 2 -- Context is worth more
end
end
end
-- Skip the modified lines in current buffer
buffer_line = hunk.new_start + hunk.new_count
applied = true
else
-- Copy unchanged line
if buffer_line <= #lines then
table.insert(new_lines, lines[buffer_line])
-- Keep the best match
if score > best_score then
best_score = score
best_start = i
end
buffer_line = buffer_line + 1
end
end
if best_start then
hunk_start = best_start
hunk_end = best_start + #expected_lines - 1
else
vim.notify('Could not find hunk in current buffer - content may have changed', vim.log.levels.ERROR)
return
end
-- Build new buffer content
local new_lines = {}
-- Copy lines before the hunk
for i = 1, hunk_start - 1 do
table.insert(new_lines, lines[i])
end
-- Insert the original lines
for _, line in ipairs(original_lines) do
table.insert(new_lines, line)
end
-- Copy lines after the hunk
for i = hunk_end + 1, #lines do
table.insert(new_lines, lines[i])
end
-- Update buffer
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, new_lines)
end
@ -581,11 +665,15 @@ function M.close_inline_diff(bufnr, keep_baseline)
end
end
-- If no more active diffs, clear persistence state
-- If no more active diffs, clear persistence state and reset baseline
if not has_active_diffs then
local persistence = require('nvim-claude.inline-diff-persistence')
persistence.clear_state()
persistence.current_stash_ref = nil
-- Reset the stable baseline in hooks
local hooks = require('nvim-claude.hooks')
hooks.stable_baseline_ref = nil
end
vim.notify('Inline diff closed', vim.log.levels.INFO)

@ -0,0 +1,112 @@
local M = {}
local utils = require('nvim-claude.utils')
-- Update Claude settings with current Neovim server address
function M.update_claude_settings()
local project_root = utils.get_project_root()
if not project_root then
return
end
local settings_path = project_root .. '/.claude/settings.json'
local settings_dir = project_root .. '/.claude'
-- Get current Neovim server address
local server_addr = vim.v.servername
if not server_addr or server_addr == '' then
-- If no servername, we can't communicate
return
end
-- Ensure .claude directory exists
if vim.fn.isdirectory(settings_dir) == 0 then
vim.fn.mkdir(settings_dir, 'p')
end
-- Read existing settings or create new
local settings = {}
if vim.fn.filereadable(settings_path) == 1 then
local ok, content = pcall(vim.fn.readfile, settings_path)
if ok and #content > 0 then
local decode_ok, decoded = pcall(vim.json.decode, table.concat(content, '\n'))
if decode_ok then
settings = decoded
end
end
end
-- Ensure hooks structure exists
if not settings.hooks then
settings.hooks = {}
end
if not settings.hooks.PreToolUse then
settings.hooks.PreToolUse = {}
end
if not settings.hooks.PostToolUse then
settings.hooks.PostToolUse = {}
end
-- Update hook commands with current server address
local pre_hook_cmd = string.format(
'nvr --servername "%s" --remote-expr \'luaeval("require(\\"nvim-claude.hooks\\").pre_tool_use_hook()")\'',
server_addr
)
local post_hook_cmd = string.format(
'nvr --servername "%s" --remote-send "<C-\\\\><C-N>:lua require(\'nvim-claude.hooks\').post_tool_use_hook()<CR>"',
server_addr
)
-- Update PreToolUse hooks
local pre_hook_found = false
for _, hook_group in ipairs(settings.hooks.PreToolUse) do
if hook_group.matcher == "Edit|Write|MultiEdit" then
hook_group.hooks = {{type = "command", command = pre_hook_cmd}}
pre_hook_found = true
break
end
end
if not pre_hook_found then
table.insert(settings.hooks.PreToolUse, {
matcher = "Edit|Write|MultiEdit",
hooks = {{type = "command", command = pre_hook_cmd}}
})
end
-- Update PostToolUse hooks
local post_hook_found = false
for _, hook_group in ipairs(settings.hooks.PostToolUse) do
if hook_group.matcher == "Edit|Write|MultiEdit" then
hook_group.hooks = {{type = "command", command = post_hook_cmd}}
post_hook_found = true
break
end
end
if not post_hook_found then
table.insert(settings.hooks.PostToolUse, {
matcher = "Edit|Write|MultiEdit",
hooks = {{type = "command", command = post_hook_cmd}}
})
end
-- Write updated settings
local encoded = vim.json.encode(settings)
vim.fn.writefile({encoded}, settings_path)
end
-- Setup autocmds to update settings
function M.setup()
vim.api.nvim_create_autocmd({"VimEnter", "DirChanged"}, {
group = vim.api.nvim_create_augroup("NvimClaudeSettingsUpdater", { clear = true }),
callback = function()
-- Defer to ensure servername is available
vim.defer_fn(function()
M.update_claude_settings()
end, 100)
end,
})
end
return M
Loading…
Cancel
Save