Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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
```
9 changes: 7 additions & 2 deletions lua/codediff/ui/auto_refresh.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 5 additions & 14 deletions lua/codediff/ui/explorer/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion lua/codediff/ui/explorer/render.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 21 additions & 22 deletions lua/codediff/ui/view/keymaps.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions lua/codediff/ui/view/navigation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
13 changes: 6 additions & 7 deletions tests/ui/explorer/hunk_operations_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down