diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..60700514 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,8 @@ +See AGENTS.md for project conventions. + +On first use, create symlinks so Claude Code discovers existing skills and agents: +``` +mkdir -p .claude/skills/nvim-e2e-workflow .claude/skills/codediff-developer +ln -sf ../../.github/skills/nvim-e2e-workflow/SKILL.md .claude/skills/nvim-e2e-workflow/SKILL.md +ln -sf ../../.github/agents/codediff-developer.agent.md .claude/skills/codediff-developer/SKILL.md +``` diff --git a/lua/codediff/ui/auto_refresh.lua b/lua/codediff/ui/auto_refresh.lua index 15a55880..192225e7 100644 --- a/lua/codediff/ui/auto_refresh.lua +++ b/lua/codediff/ui/auto_refresh.lua @@ -406,9 +406,14 @@ function M.sync_mutable_buffers(tabpage) if #current_lines == #lines then local same = true for i = 1, #lines do - if current_lines[i] ~= lines[i] then same = false; break end + if current_lines[i] ~= lines[i] then + same = false + break + end + end + if same then + return end - if same then return end end local was_modifiable = vim.bo[bufnr].modifiable diff --git a/lua/codediff/ui/explorer/actions.lua b/lua/codediff/ui/explorer/actions.lua index 6c9b3fa3..acb7acce 100644 --- a/lua/codediff/ui/explorer/actions.lua +++ b/lua/codediff/ui/explorer/actions.lua @@ -362,21 +362,12 @@ function M.restore_entry(explorer, tree) local is_untracked = not is_directory and status == "??" local display_name = entry_path .. (is_directory and "/" or "") - -- Two-line confirmation prompt + -- Confirmation prompt local action_word = is_directory and "Discard all changes in " or (is_untracked and "Delete " or "Discard changes to ") - vim.api.nvim_echo({ - { action_word, "WarningMsg" }, - { display_name, "WarningMsg" }, - { "?\n", "WarningMsg" }, - { "(D)", "WarningMsg" }, - { is_untracked and "elete, " or "iscard, ", "WarningMsg" }, - { "[C]", "WarningMsg" }, - { "ancel: ", "WarningMsg" }, - }, false, {}) - - local char = vim.fn.getcharstr():lower() - - if char == "d" then + local prompt = action_word .. display_name .. "?" + local choice = vim.fn.confirm(prompt, "&Discard\n&Cancel", 2, "Warning") + + if choice == 1 then if is_untracked then -- Delete untracked file/directory git.delete_untracked(explorer.git_root, entry_path, function(err) diff --git a/lua/codediff/ui/explorer/render.lua b/lua/codediff/ui/explorer/render.lua index f208cdde..3007f444 100644 --- a/lua/codediff/ui/explorer/render.lua +++ b/lua/codediff/ui/explorer/render.lua @@ -339,7 +339,10 @@ function M.create(status_result, git_root, tabpage, width, base_revision, target if current_status then local file_has_staged = false for _, sf in ipairs(current_status.staged or {}) do - if sf.path == file_path then file_has_staged = true; break end + if sf.path == file_path then + file_has_staged = true + break + end end local current_is_mutable = session.original_revision and session.original_revision:match("^:[0-3]$") if file_has_staged ~= (current_is_mutable and true or false) then diff --git a/lua/codediff/ui/view/keymaps.lua b/lua/codediff/ui/view/keymaps.lua index 544ca80c..546bef0c 100644 --- a/lua/codediff/ui/view/keymaps.lua +++ b/lua/codediff/ui/view/keymaps.lua @@ -520,32 +520,31 @@ function M.setup_all_keymaps(tabpage, original_bufnr, modified_bufnr, is_explore end -- Prompt for confirmation before discarding (destructive operation) - local prompt = string.format("Discard hunk %d? This cannot be undone.", hunk_idx) - vim.ui.select({ "Yes", "No" }, { prompt = prompt }, function(choice) - if choice ~= "Yes" then - return - end + local prompt = string.format("Discard hunk %d?", hunk_idx) + local choice = vim.fn.confirm(prompt, "&Discard\n&Cancel", 2, "Warning") + if choice ~= 1 then + return + end - local discard_orig_buf, discard_mod_buf = lifecycle.get_buffers(tabpage) - if not discard_orig_buf or not discard_mod_buf or not vim.api.nvim_buf_is_valid(discard_orig_buf) or not vim.api.nvim_buf_is_valid(discard_mod_buf) then - vim.notify("Diff buffers are no longer available", vim.log.levels.WARN) - return - end + local discard_orig_buf, discard_mod_buf = lifecycle.get_buffers(tabpage) + if not discard_orig_buf or not discard_mod_buf or not vim.api.nvim_buf_is_valid(discard_orig_buf) or not vim.api.nvim_buf_is_valid(discard_mod_buf) then + vim.notify("Diff buffers are no longer available", vim.log.levels.WARN) + return + end - -- Read lines from both buffers for this hunk - local orig_lines = vim.api.nvim_buf_get_lines(discard_orig_buf, hunk.original.start_line - 1, hunk.original.end_line - 1, false) - local mod_lines = vim.api.nvim_buf_get_lines(discard_mod_buf, hunk.modified.start_line - 1, hunk.modified.end_line - 1, false) + -- Read lines from both buffers for this hunk + local orig_lines = vim.api.nvim_buf_get_lines(discard_orig_buf, hunk.original.start_line - 1, hunk.original.end_line - 1, false) + local mod_lines = vim.api.nvim_buf_get_lines(discard_mod_buf, hunk.modified.start_line - 1, hunk.modified.end_line - 1, false) - local patch = build_hunk_patch(file_path, orig_lines, mod_lines, hunk.original.start_line, hunk.modified.start_line) + local patch = build_hunk_patch(file_path, orig_lines, mod_lines, hunk.original.start_line, hunk.modified.start_line) - local git = require("codediff.core.git") - git.discard_hunk_patch(session.git_root, patch, function(err) - if err then - vim.notify("Failed to discard hunk: " .. err, vim.log.levels.ERROR) - return - end - vim.notify(string.format("Discarded hunk %d", hunk_idx), vim.log.levels.INFO) - end) + local git = require("codediff.core.git") + git.discard_hunk_patch(session.git_root, patch, function(err) + if err then + vim.notify("Failed to discard hunk: " .. err, vim.log.levels.ERROR) + return + end + vim.notify(string.format("Discarded hunk %d", hunk_idx), vim.log.levels.INFO) end) end diff --git a/lua/codediff/ui/view/navigation.lua b/lua/codediff/ui/view/navigation.lua index 1b84c51f..745b20c3 100644 --- a/lua/codediff/ui/view/navigation.lua +++ b/lua/codediff/ui/view/navigation.lua @@ -52,6 +52,7 @@ function M.next_hunk() local target_line = is_original and mapping.original.start_line or mapping.modified.start_line if target_line > current_line then pcall(vim.api.nvim_win_set_cursor, 0, { target_line, 0 }) + vim.cmd("normal! zz") vim.api.nvim_echo({ { string.format("Hunk %d of %d", i, #diff_result.changes), "None" } }, false, {}) return true end @@ -62,6 +63,7 @@ function M.next_hunk() local first_hunk = diff_result.changes[1] local target_line = is_original and first_hunk.original.start_line or first_hunk.modified.start_line pcall(vim.api.nvim_win_set_cursor, 0, { target_line, 0 }) + vim.cmd("normal! zz") vim.api.nvim_echo({ { string.format("Hunk 1 of %d", #diff_result.changes), "None" } }, false, {}) return true else @@ -119,6 +121,7 @@ function M.prev_hunk() local target_line = is_original and mapping.original.start_line or mapping.modified.start_line if target_line < current_line then pcall(vim.api.nvim_win_set_cursor, 0, { target_line, 0 }) + vim.cmd("normal! zz") vim.api.nvim_echo({ { string.format("Hunk %d of %d", i, #diff_result.changes), "None" } }, false, {}) return true end @@ -129,6 +132,7 @@ function M.prev_hunk() local last_hunk = diff_result.changes[#diff_result.changes] local target_line = is_original and last_hunk.original.start_line or last_hunk.modified.start_line pcall(vim.api.nvim_win_set_cursor, 0, { target_line, 0 }) + vim.cmd("normal! zz") vim.api.nvim_echo({ { string.format("Hunk %d of %d", #diff_result.changes, #diff_result.changes), "None" } }, false, {}) return true else diff --git a/tests/ui/explorer/hunk_operations_spec.lua b/tests/ui/explorer/hunk_operations_spec.lua index 08120af9..d98496d9 100644 --- a/tests/ui/explorer/hunk_operations_spec.lua +++ b/tests/ui/explorer/hunk_operations_spec.lua @@ -393,11 +393,10 @@ describe("Hunk operations (side-by-side)", function() local discard_fn = get_keymap_fn(mod_buf, "Discard hunk") assert.is_truthy(discard_fn, "discard_hunk keymap callback should be set") - -- Mock vim.ui.select to auto-confirm "Yes" - local original_ui_select = vim.ui.select - vim.ui.select = function(items, opts, on_choice) - -- Auto-confirm with "Yes" - on_choice("Yes") + -- Mock vim.fn.confirm to auto-confirm "Discard" (choice 1) + local original_confirm = vim.fn.confirm + vim.fn.confirm = function(_, _, _, _) + return 1 end -- Position cursor on hunk 1 (line 1) @@ -406,8 +405,8 @@ describe("Hunk operations (side-by-side)", function() -- Invoke discard_hunk discard_fn() - -- Restore original vim.ui.select - vim.ui.select = original_ui_select + -- Restore original vim.fn.confirm + vim.fn.confirm = original_confirm -- Wait for the async git operation to complete vim.wait(2000, function() return false end, 100)