Skip to content
Open
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
39 changes: 35 additions & 4 deletions lua/claudecode/diff.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1442,28 +1442,59 @@ function M.reload_file_buffers_manual(file_path, original_cursor_pos)
return reload_file_buffers(file_path, original_cursor_pos)
end

---Find a diff buffer in the current tabpage by searching all windows.
---@return integer? buf The buffer number with an active diff, or nil if not found
local function find_diff_buf_in_current_tab()
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
local buf = vim.api.nvim_win_get_buf(win)
if vim.b[buf].claudecode_diff_tab_name then
return buf
end
end
return nil
end

---Accept the current diff (user command version)
---This function reads the diff context from buffer variables
---This function reads the diff context from buffer variables.
---If the current buffer is not a diff buffer, falls back to searching
---all windows in the current tab for an active diff.
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)
local diff_buf = find_diff_buf_in_current_tab()
if diff_buf then
tab_name = vim.b[diff_buf].claudecode_diff_tab_name
current_buffer = diff_buf
end
end

if not tab_name then
vim.notify("No active diff found in current tab", 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
---This function reads the diff context from buffer variables.
---If the current buffer is not a diff buffer, falls back to searching
---all windows in the current tab for an active diff.
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

if not tab_name then
vim.notify("No active diff found in current buffer", vim.log.levels.WARN)
local diff_buf = find_diff_buf_in_current_tab()
if diff_buf then
tab_name = vim.b[diff_buf].claudecode_diff_tab_name
end
end

if not tab_name then
vim.notify("No active diff found in current tab", vim.log.levels.WARN)
return
end

Expand Down
17 changes: 17 additions & 0 deletions tests/mocks/vim.lua
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,23 @@ local vim = {
return vim._tabs[tab] == true
end,

nvim_tabpage_list_wins = function(tabpage)
if tabpage == 0 then
tabpage = vim._current_tabpage
end
local wins = {}
local list = vim._tab_windows[tabpage] or {}
for _, winid in ipairs(list) do
if vim._windows[winid] then
table.insert(wins, winid)
end
end
if #wins == 0 then
table.insert(wins, vim._current_window)
end
return wins
end,

nvim_tabpage_get_number = function(tab)
return tab
end,
Expand Down
181 changes: 181 additions & 0 deletions tests/unit/diff_accept_deny_tab_search_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
-- luacheck: globals expect
-- Tests for accept_current_diff / deny_current_diff tab-wide search fallback
require("tests.busted_setup")

describe("diff accept/deny tab-wide search", function()
local diff

before_each(function()
package.loaded["claudecode.diff"] = nil
package.loaded["claudecode.config"] = nil
diff = require("claudecode.diff")
diff.setup({})

-- Reset mock state: one tab, one window showing buffer 1
_G.vim._buffers = {
[1] = { name = "/project/file.lua", lines = { "hello" }, options = {}, b_vars = {} },
}
_G.vim._windows = { [1000] = { buf = 1, width = 80 } }
_G.vim._win_tab = { [1000] = 1 }
_G.vim._tab_windows = { [1] = { 1000 } }
_G.vim._tabs = { [1] = true }
_G.vim._current_tabpage = 1
_G.vim._current_window = 1000
_G.vim._next_winid = 1001
end)

after_each(function()
diff._cleanup_all_active_diffs("test_teardown")
end)

-- Helper: create a second buffer in the current tab with claudecode_diff_tab_name set
local function add_diff_buffer_to_current_tab(tab_name)
local diff_buf = #_G.vim._buffers + 1
_G.vim._buffers[diff_buf] = {
name = "/tmp/claudecode_diff_test.lua.new",
lines = { "new content" },
options = { eol = true },
b_vars = { claudecode_diff_tab_name = tab_name },
}
local diff_win = _G.vim._next_winid
_G.vim._next_winid = _G.vim._next_winid + 1
_G.vim._windows[diff_win] = { buf = diff_buf, width = 80 }
_G.vim._win_tab[diff_win] = 1
table.insert(_G.vim._tab_windows[1], diff_win)
return diff_buf, diff_win
end

describe("accept_current_diff", function()
it("works when cursor is in the diff buffer", function()
local tab_name = "test_accept_direct"
local diff_buf, diff_win = add_diff_buffer_to_current_tab(tab_name)

-- Simulate an active diff with a resolution callback
-- Put cursor in diff buffer
_G.vim._current_window = diff_win
_G.vim.api.nvim_get_current_buf = function()
return diff_buf
end

local found_buf = nil
local orig_resolve = diff._resolve_diff_as_saved
diff._resolve_diff_as_saved = function(tn, buf)
found_buf = buf
-- don't actually resolve, just capture
end

diff.accept_current_diff()

diff._resolve_diff_as_saved = orig_resolve

assert.equal(diff_buf, found_buf)
end)

it("falls back to searching the current tab when cursor is not in a diff buffer", function()
local tab_name = "test_accept_fallback"
local diff_buf, _ = add_diff_buffer_to_current_tab(tab_name)

-- Cursor stays in the non-diff buffer (window 1000, buf 1)
_G.vim._current_window = 1000
_G.vim.api.nvim_get_current_buf = function()
return 1 -- no claudecode_diff_tab_name
end

local found_buf = nil
local orig_resolve = diff._resolve_diff_as_saved
diff._resolve_diff_as_saved = function(tn, buf)
found_buf = buf
end

diff.accept_current_diff()

diff._resolve_diff_as_saved = orig_resolve

assert.equal(diff_buf, found_buf)
end)

it("notifies when no diff buffer found in current tab", function()
-- No diff buffer in the tab
_G.vim._current_window = 1000
_G.vim.api.nvim_get_current_buf = function()
return 1
end

local notified = false
local orig_notify = _G.vim.notify
_G.vim.notify = function(msg, level)
notified = true
assert.is_not_nil(msg:find("No active diff"))
end

diff.accept_current_diff()

_G.vim.notify = orig_notify
assert.is_true(notified)
end)
end)

describe("deny_current_diff", function()
it("works when cursor is in the diff buffer", function()
local tab_name = "test_deny_direct"
local diff_buf, diff_win = add_diff_buffer_to_current_tab(tab_name)

_G.vim._current_window = diff_win
_G.vim.api.nvim_get_current_buf = function()
return diff_buf
end

local resolved_name = nil
local orig_resolve = diff._resolve_diff_as_rejected
diff._resolve_diff_as_rejected = function(tn)
resolved_name = tn
end

diff.deny_current_diff()

diff._resolve_diff_as_rejected = orig_resolve
assert.equal(tab_name, resolved_name)
end)

it("falls back to searching the current tab when cursor is not in a diff buffer", function()
local tab_name = "test_deny_fallback"
add_diff_buffer_to_current_tab(tab_name)

-- Cursor in non-diff buffer
_G.vim._current_window = 1000
_G.vim.api.nvim_get_current_buf = function()
return 1
end

local resolved_name = nil
local orig_resolve = diff._resolve_diff_as_rejected
diff._resolve_diff_as_rejected = function(tn)
resolved_name = tn
end

diff.deny_current_diff()

diff._resolve_diff_as_rejected = orig_resolve
assert.equal(tab_name, resolved_name)
end)

it("notifies when no diff buffer found in current tab", function()
_G.vim._current_window = 1000
_G.vim.api.nvim_get_current_buf = function()
return 1
end

local notified = false
local orig_notify = _G.vim.notify
_G.vim.notify = function(msg, level)
notified = true
assert.is_not_nil(msg:find("No active diff"))
end

diff.deny_current_diff()

_G.vim.notify = orig_notify
assert.is_true(notified)
end)
end)
end)