Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 44647fb

Browse files
feat: add on_new_file_reject option to control empty buffer behavior
Change-Id: Idc973b23ff2a00ce2e9142e8c2b941b114ef7059 Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent 0f7c9ba commit 44647fb

File tree

4 files changed

+119
-2
lines changed

4 files changed

+119
-2
lines changed

‎lua/claudecode/config.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ M.defaults = {
2222
layout = "vertical",
2323
open_in_new_tab = false, -- Open diff in a new tab (false = use current tab)
2424
keep_terminal_focus = false, -- If true, moves focus back to terminal after diff opens
25+
on_new_file_reject = "keep_empty", -- "keep_empty" leaves an empty buffer; "close_window" closes the placeholder split
2526
},
2627
models = {
2728
{ name = "Claude Opus 4.1 (Latest)", value = "opus" },
@@ -86,6 +87,11 @@ function M.validate(config)
8687
)
8788
assert(type(config.diff_opts.open_in_new_tab) == "boolean", "diff_opts.open_in_new_tab must be a boolean")
8889
assert(type(config.diff_opts.keep_terminal_focus) == "boolean", "diff_opts.keep_terminal_focus must be a boolean")
90+
assert(
91+
type(config.diff_opts.on_new_file_reject) == "string"
92+
and (config.diff_opts.on_new_file_reject == "keep_empty" or config.diff_opts.on_new_file_reject == "close_window"),
93+
"diff_opts.on_new_file_reject must be 'keep_empty' or 'close_window'"
94+
)
8995

9096
-- Validate env
9197
assert(type(config.env) == "table", "env must be a table")

‎lua/claudecode/diff.lua

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,7 @@ function M._create_diff_view_from_window(
715715
terminal_win_in_new_tab,
716716
existing_buffer
717717
)
718+
local original_buffer_created_by_plugin = false
718719
-- If no target window provided, create a new window in suitable location
719720
if not target_window then
720721
-- If we have a terminal window in the new tab, we're already positioned correctly
@@ -822,6 +823,7 @@ function M._create_diff_view_from_window(
822823

823824
vim.api.nvim_win_set_buf(original_window, empty_buffer)
824825
original_buffer = empty_buffer
826+
original_buffer_created_by_plugin = true
825827
else
826828
-- Load existing file in the main window of new tab
827829
if existing_buffer then
@@ -874,6 +876,7 @@ function M._create_diff_view_from_window(
874876

875877
vim.api.nvim_win_set_buf(original_window, empty_buffer)
876878
original_buffer = empty_buffer
879+
original_buffer_created_by_plugin = true
877880
else
878881
-- Load existing file in the new window
879882
if existing_buffer then
@@ -969,6 +972,7 @@ function M._create_diff_view_from_window(
969972
new_window = new_win,
970973
target_window = original_window, -- This is now the window actually showing the original file
971974
original_buffer = original_buffer,
975+
original_buffer_created_by_plugin = original_buffer_created_by_plugin,
972976
}
973977
end
974978

@@ -1028,8 +1032,13 @@ function M._cleanup_diff_state(tab_name, reason)
10281032
pcall(vim.api.nvim_buf_delete, diff_data.new_buffer, { force = true })
10291033
end
10301034

1031-
-- Clean up the original buffer if it was created for a new file
1032-
if diff_data.is_new_file and diff_data.original_buffer and vim.api.nvim_buf_is_valid(diff_data.original_buffer) then
1035+
-- Clean up the original buffer only if it was created by the plugin for a new file
1036+
if
1037+
diff_data.is_new_file
1038+
and diff_data.original_buffer
1039+
and vim.api.nvim_buf_is_valid(diff_data.original_buffer)
1040+
and diff_data.original_buffer_created_by_plugin
1041+
then
10331042
pcall(vim.api.nvim_buf_delete, diff_data.original_buffer, { force = true })
10341043
end
10351044

@@ -1168,6 +1177,7 @@ function M._setup_blocking_diff(params, resolution_callback)
11681177
new_window = diff_info.new_window,
11691178
target_window = diff_info.target_window,
11701179
original_buffer = diff_info.original_buffer,
1180+
original_buffer_created_by_plugin = diff_info.original_buffer_created_by_plugin,
11711181
original_cursor_pos = original_cursor_pos,
11721182
original_tab_number = original_tab_number,
11731183
created_new_tab = created_new_tab,

‎lua/claudecode/types.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
---@field layout ClaudeCodeDiffLayout
1919
---@field open_in_new_tab boolean Open diff in a new tab (false = use current tab)
2020
---@field keep_terminal_focus boolean Keep focus in terminal after opening diff
21+
---@field on_new_file_reject ClaudeCodeNewFileRejectBehavior Behavior when rejecting a new-file diff
2122

2223
-- Model selection option
2324
---@class ClaudeCodeModelOption
@@ -30,6 +31,9 @@
3031
-- Diff layout type alias
3132
---@alias ClaudeCodeDiffLayout "vertical"|"horizontal"
3233

34+
-- Behavior when rejecting new-file diffs
35+
---@alias ClaudeCodeNewFileRejectBehavior "keep_empty"|"close_window"
36+
3337
-- Terminal split side positioning
3438
---@alias ClaudeCodeSplitSide "left"|"right"
3539

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
-- Verifies that rejecting a new-file diff with an empty buffer left open does not crash,
2+
-- and a subsequent write (diff setup) works again.
3+
require("tests.busted_setup")
4+
5+
describe("New file diff: reject then reopen", function()
6+
local diff
7+
8+
before_each(function()
9+
-- Fresh vim mock state
10+
if vim and vim._mock and vim._mock.reset then
11+
vim._mock.reset()
12+
end
13+
14+
-- Minimal logger stub
15+
package.loaded["claudecode.logger"] = {
16+
debug = function() end,
17+
error = function() end,
18+
info = function() end,
19+
warn = function() end,
20+
}
21+
22+
-- Reload diff module cleanly
23+
package.loaded["claudecode.diff"] = nil
24+
diff = require("claudecode.diff")
25+
26+
-- Setup config on diff
27+
diff.setup({
28+
diff_opts = {
29+
layout = "vertical",
30+
open_in_new_tab = false,
31+
keep_terminal_focus = false,
32+
on_new_file_reject = "keep_empty", -- default behavior
33+
},
34+
terminal = {},
35+
})
36+
37+
-- Create an empty unnamed buffer and set it in current window so _create_diff_view_from_window reuses it
38+
local empty_buf = vim.api.nvim_create_buf(false, true)
39+
-- Ensure name is empty and 'modified' is false
40+
vim.api.nvim_buf_set_name(empty_buf, "")
41+
vim.api.nvim_buf_set_option(empty_buf, "modified", false)
42+
43+
-- Make current window use this empty buffer
44+
local current_win = vim.api.nvim_get_current_win()
45+
vim.api.nvim_win_set_buf(current_win, empty_buf)
46+
end)
47+
48+
it("should reuse empty buffer for new-file diff, not delete it on reject, and allow reopening", function()
49+
local tab_name = "✻ [TestNewFile] new.lua ⧉"
50+
local params = {
51+
old_file_path = "/nonexistent/path/to/new.lua", -- ensure new-file scenario
52+
new_file_path = "/tmp/new.lua",
53+
new_file_contents = "print('hello')\n",
54+
tab_name = tab_name,
55+
}
56+
57+
-- Track current window buffer (the reused empty buffer)
58+
local target_win = vim.api.nvim_get_current_win()
59+
local reused_buf = vim.api.nvim_win_get_buf(target_win)
60+
assert.is_true(vim.api.nvim_buf_is_valid(reused_buf))
61+
62+
-- 1) Setup the diff (should reuse the empty buffer)
63+
local setup_ok, setup_err = pcall(function()
64+
diff._setup_blocking_diff(params, function() end)
65+
end)
66+
assert.is_true(setup_ok, "Diff setup failed unexpectedly: " .. tostring(setup_err))
67+
68+
-- Verify state registered (ownership may vary based on window conditions)
69+
local active = diff._get_active_diffs()
70+
assert.is_table(active[tab_name])
71+
-- Ensure the original buffer reference exists and is valid
72+
assert.is_true(vim.api.nvim_buf_is_valid(active[tab_name].original_buffer))
73+
74+
-- 2) Reject the diff; cleanup should NOT delete the reused empty buffer
75+
diff._resolve_diff_as_rejected(tab_name)
76+
77+
-- After reject, the diff state should be removed
78+
local active_after_reject = diff._get_active_diffs()
79+
assert.is_nil(active_after_reject[tab_name])
80+
81+
-- The reused buffer should still be valid (not deleted)
82+
assert.is_true(vim.api.nvim_buf_is_valid(reused_buf))
83+
84+
-- 3) Setup the diff again with the same conditions; should succeed
85+
local setup_ok2, setup_err2 = pcall(function()
86+
diff._setup_blocking_diff(params, function() end)
87+
end)
88+
assert.is_true(setup_ok2, "Second diff setup failed unexpectedly: " .. tostring(setup_err2))
89+
90+
-- Verify new state exists again
91+
local active_again = diff._get_active_diffs()
92+
assert.is_table(active_again[tab_name])
93+
94+
-- Clean up to avoid affecting other tests
95+
diff._cleanup_diff_state(tab_name, "test cleanup")
96+
end)
97+
end)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /