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 990fe27

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 678a582 commit 990fe27

File tree

4 files changed

+127
-3
lines changed

4 files changed

+127
-3
lines changed

‎lua/claudecode/config.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ M.defaults = {
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
2525
hide_terminal_in_new_tab = false, -- If true and opening in a new tab, do not show Claude terminal there
26+
on_new_file_reject = "keep_empty", -- "keep_empty" leaves an empty buffer; "close_window" closes the placeholder split
2627
},
2728
models = {
2829
{ name = "Claude Opus 4.1 (Latest)", value = "opus" },
@@ -124,6 +125,13 @@ function M.validate(config)
124125
"diff_opts.hide_terminal_in_new_tab must be a boolean"
125126
)
126127
end
128+
if config.diff_opts.on_new_file_reject ~= nil then
129+
assert(
130+
type(config.diff_opts.on_new_file_reject) == "string"
131+
and (config.diff_opts.on_new_file_reject == "keep_empty" or config.diff_opts.on_new_file_reject == "close_window"),
132+
"diff_opts.on_new_file_reject must be 'keep_empty' or 'close_window'"
133+
)
134+
end
127135

128136
-- Legacy diff options (accept if present to avoid breaking old configs)
129137
if config.diff_opts.auto_close_on_accept ~= nil then

‎lua/claudecode/diff.lua

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -892,9 +892,10 @@ function M._create_diff_view_from_window(
892892
terminal_win_in_new_tab,
893893
existing_buffer
894894
)
895+
local original_buffer_created_by_plugin = false
896+
895897
-- If no target window provided, create a new window in suitable location
896898
if not target_window then
897-
-- If we have a terminal window in the new tab, we're already positioned correctly
898899
if terminal_win_in_new_tab then
899900
-- We're already in the main area after display_terminal_in_new_tab
900901
target_window = vim.api.nvim_get_current_win()
@@ -929,8 +930,15 @@ function M._create_diff_view_from_window(
929930
original_window = choice.original_win
930931
end
931932

933+
-- For new files, we create an empty buffer for the original side
934+
if is_new_file then
935+
original_buffer_created_by_plugin = true
936+
end
937+
938+
-- Load the original-side buffer into the chosen window
932939
local original_buffer = load_original_buffer(original_window, old_file_path, is_new_file, existing_buffer)
933940

941+
-- Set up the proposed buffer and finalize the diff layout
934942
local new_win = setup_new_buffer(
935943
original_window,
936944
original_buffer,
@@ -945,6 +953,7 @@ function M._create_diff_view_from_window(
945953
new_window = new_win,
946954
target_window = original_window,
947955
original_buffer = original_buffer,
956+
original_buffer_created_by_plugin = original_buffer_created_by_plugin,
948957
}
949958
end
950959

@@ -1030,8 +1039,13 @@ function M._cleanup_diff_state(tab_name, reason)
10301039
pcall(vim.api.nvim_buf_delete, diff_data.new_buffer, { force = true })
10311040
end
10321041

1033-
-- Clean up the original buffer if it was created for a new file
1034-
if diff_data.is_new_file and diff_data.original_buffer and vim.api.nvim_buf_is_valid(diff_data.original_buffer) then
1042+
-- Clean up the original buffer only if it was created by the plugin for a new file
1043+
if
1044+
diff_data.is_new_file
1045+
and diff_data.original_buffer
1046+
and vim.api.nvim_buf_is_valid(diff_data.original_buffer)
1047+
and diff_data.original_buffer_created_by_plugin
1048+
then
10351049
pcall(vim.api.nvim_buf_delete, diff_data.original_buffer, { force = true })
10361050
end
10371051

@@ -1177,6 +1191,7 @@ function M._setup_blocking_diff(params, resolution_callback)
11771191
new_window = diff_info.new_window,
11781192
target_window = diff_info.target_window,
11791193
original_buffer = diff_info.original_buffer,
1194+
original_buffer_created_by_plugin = diff_info.original_buffer_created_by_plugin,
11801195
original_cursor_pos = original_cursor_pos,
11811196
original_tab_number = original_tab_number,
11821197
created_new_tab = created_new_tab,

‎lua/claudecode/types.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
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
2121
---@field hide_terminal_in_new_tab boolean Hide Claude terminal in newly created diff tab
22+
---@field on_new_file_reject ClaudeCodeNewFileRejectBehavior Behavior when rejecting a new-file diff
2223

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

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

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 によって変換されたページ (->オリジナル) /