diff --git a/README.md b/README.md index 3eceda0..6cbf0bd 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,9 @@ Using [lazy.nvim](https://github.com/folke/lazy.nvim): desc = "Add file", ft = { "NvimTree", "neo-tree", "oil" }, }, + -- Diff management + { "da", "ClaudeCodeDiffAccept", desc = "Accept diff" }, + { "dq", "ClaudeCodeDiffDeny", desc = "Deny diff" }, }, } ``` @@ -88,6 +91,8 @@ That's it! For more configuration options, see [Advanced Setup](#advanced-setup) - `:ClaudeCodeSend` - Send current visual selection to Claude, or add files from tree explorer - `:ClaudeCodeTreeAdd` - Add selected file(s) from tree explorer to Claude context (also available via ClaudeCodeSend) - `:ClaudeCodeAdd [start-line] [end-line]` - Add a specific file or directory to Claude context by path with optional line range +- `:ClaudeCodeDiffAccept` - Accept the current diff changes (equivalent to `da`) +- `:ClaudeCodeDiffDeny` - Deny/reject the current diff changes (equivalent to `dq`) ### Toggle Behavior @@ -137,7 +142,7 @@ When Claude proposes changes to your files, the plugin opens a native Neovim dif ### Accepting Changes - **`:w` (save)** - Accept the changes and apply them to your file -- **`da`** - Accept the changes using the dedicated keymap +- **`da`** - Accept the changes using the dedicated keymap (configured in LazyVim spec) You can edit the proposed changes in the right-hand diff buffer before accepting them. This allows you to modify Claude's suggestions or make additional tweaks before applying the final version to your file. @@ -146,7 +151,7 @@ Both methods signal Claude Code to apply the changes to your file, after which t ### Rejecting Changes - **`:q` or `:close`** - Close the diff view to reject the changes -- **`dq`** - Reject changes using the dedicated keymap +- **`dq`** - Reject changes using the dedicated keymap (configured in LazyVim spec) - **`:bdelete` or `:bwipeout`** - Delete the diff buffer to reject changes When you reject changes, the diff view closes and the original file remains unchanged. @@ -155,6 +160,28 @@ When you reject changes, the diff view closes and the original file remains unch You can also navigate to the Claude Code terminal window and accept or reject diffs directly from within Claude's interface. This provides an alternative way to manage diffs without using the Neovim-specific keymaps. +### Customizing Diff Keymaps + +The diff keymaps are configured in the LazyVim spec and can be customized by modifying the `keys` table: + +```lua +{ + "coder/claudecode.nvim", + config = true, + keys = { + -- ... other keymaps ... + + -- Customize diff keymaps to avoid conflicts (e.g., with debugger) + { "ya", "ClaudeCodeDiffAccept", desc = "Accept diff" }, + { "yn", "ClaudeCodeDiffDeny", desc = "Deny diff" }, + + -- Or disable them entirely by omitting them from the keys table + }, +} +``` + +The commands `ClaudeCodeDiffAccept` and `ClaudeCodeDiffDeny` work only in diff buffers created by the plugin and will show a warning if used elsewhere. + ### How It Works The plugin uses a signal-based approach where accepting or rejecting a diff sends a message to Claude Code rather than directly modifying files. This ensures consistency and allows Claude Code to handle the actual file operations while the plugin manages the user interface and buffer reloading. diff --git a/dev-config.lua b/dev-config.lua index 525e61e..5e6dde6 100644 --- a/dev-config.lua +++ b/dev-config.lua @@ -31,6 +31,10 @@ return { { "ai", "ClaudeCodeStatus", desc = "Claude Status" }, { "aS", "ClaudeCodeStart", desc = "Start Claude Server" }, { "aQ", "ClaudeCodeStop", desc = "Stop Claude Server" }, + + -- Diff management (buffer-local, only active in diff buffers) + { "aa", "ClaudeCodeDiffAccept", desc = "Accept diff" }, + { "ad", "ClaudeCodeDiffDeny", desc = "Deny diff" }, }, -- Development configuration - all options shown with defaults commented out diff --git a/lua/claudecode/diff.lua b/lua/claudecode/diff.lua index 90cf4c7..852e8d8 100644 --- a/lua/claudecode/diff.lua +++ b/lua/claudecode/diff.lua @@ -578,23 +578,10 @@ function M._create_diff_view_from_window(target_window, old_file_path, new_buffe vim.cmd("wincmd =") vim.api.nvim_set_current_win(new_win) - local keymap_opts = { buffer = new_buffer, silent = true } - - vim.keymap.set("n", "da", function() - M._resolve_diff_as_saved(tab_name, new_buffer) - end, keymap_opts) - - vim.keymap.set("n", "dq", function() - if vim.api.nvim_win_is_valid(new_win) then - vim.api.nvim_win_close(new_win, true) - end - if vim.api.nvim_win_is_valid(target_window) then - vim.api.nvim_set_current_win(target_window) - vim.cmd("diffoff") - end - - M._resolve_diff_as_rejected(tab_name) - end, keymap_opts) + -- Store diff context in buffer variables for user commands + vim.b[new_buffer].claudecode_diff_tab_name = tab_name + vim.b[new_buffer].claudecode_diff_new_win = new_win + vim.b[new_buffer].claudecode_diff_target_win = target_window -- Return window information for later storage return { @@ -899,4 +886,43 @@ function M.reload_file_buffers_manual(file_path, original_cursor_pos) return reload_file_buffers(file_path, original_cursor_pos) end +--- Accept the current diff (user command version) +-- This function reads the diff context from buffer variables +function M.accept_current_diff() + local current_buffer = vim.api.nvim_get_current_buf() + local tab_name = vim.b[current_buffer].claudecode_diff_tab_name + + if not tab_name then + vim.notify("No active diff found in current buffer", vim.log.levels.WARN) + return + end + + M._resolve_diff_as_saved(tab_name, current_buffer) +end + +--- Deny/reject the current diff (user command version) +-- This function reads the diff context from buffer variables +function M.deny_current_diff() + local current_buffer = vim.api.nvim_get_current_buf() + local tab_name = vim.b[current_buffer].claudecode_diff_tab_name + local new_win = vim.b[current_buffer].claudecode_diff_new_win + local target_window = vim.b[current_buffer].claudecode_diff_target_win + + if not tab_name then + vim.notify("No active diff found in current buffer", vim.log.levels.WARN) + return + end + + -- Close windows and clean up (same logic as the original keymap) + if new_win and vim.api.nvim_win_is_valid(new_win) then + vim.api.nvim_win_close(new_win, true) + end + if target_window and vim.api.nvim_win_is_valid(target_window) then + vim.api.nvim_set_current_win(target_window) + vim.cmd("diffoff") + end + + M._resolve_diff_as_rejected(tab_name) +end + return M diff --git a/lua/claudecode/init.lua b/lua/claudecode/init.lua index 9547ef6..1e5ad34 100644 --- a/lua/claudecode/init.lua +++ b/lua/claudecode/init.lua @@ -880,6 +880,21 @@ function M._create_commands() "Terminal module not found. Terminal commands (ClaudeCode, ClaudeCodeOpen, ClaudeCodeClose) not registered." ) end + + -- Diff management commands + vim.api.nvim_create_user_command("ClaudeCodeDiffAccept", function() + local diff = require("claudecode.diff") + diff.accept_current_diff() + end, { + desc = "Accept the current diff changes", + }) + + vim.api.nvim_create_user_command("ClaudeCodeDiffDeny", function() + local diff = require("claudecode.diff") + diff.deny_current_diff() + end, { + desc = "Deny/reject the current diff changes", + }) end --- Get version information diff --git a/tests/mocks/vim.lua b/tests/mocks/vim.lua index 7041997..39141c7 100644 --- a/tests/mocks/vim.lua +++ b/tests/mocks/vim.lua @@ -582,6 +582,25 @@ local vim = { end, }), + b = setmetatable({}, { + __index = function(_, bufnr) + -- Return buffer-local variables for the given buffer + if vim._buffers[bufnr] then + if not vim._buffers[bufnr].b_vars then + vim._buffers[bufnr].b_vars = {} + end + return vim._buffers[bufnr].b_vars + end + return {} + end, + __newindex = function(_, bufnr, vars) + -- Set buffer-local variables for the given buffer + if vim._buffers[bufnr] then + vim._buffers[bufnr].b_vars = vars + end + end, + }), + deepcopy = function(tbl) if type(tbl) ~= "table" then return tbl