Skip to content

Commit 432121f

Browse files
authored
fix(diff): close plugin-created split on cleanup (#175)
1 parent aa9a5ce commit 432121f

11 files changed

Lines changed: 427 additions & 16 deletions

File tree

CLAUDE.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,22 +288,27 @@ require("claudecode").setup({
288288

289289
The `diff_opts` configuration allows you to customize diff behavior:
290290

291+
- `layout` ("vertical"|"horizontal", default: `"vertical"`) - Whether the diff panes open in a vertical or horizontal split.
291292
- `keep_terminal_focus` (boolean, default: `false`) - When enabled, keeps focus in the Claude Code terminal when a diff opens instead of moving focus to the diff buffer. This allows you to continue using terminal keybindings like `<CR>` for accepting/rejecting diffs without accidentally triggering other mappings.
292293
- `open_in_new_tab` (boolean, default: `false`) - Open diffs in a new tab instead of the current tab.
293294
- `hide_terminal_in_new_tab` (boolean, default: `false`) - When opening diffs in a new tab, do not show the Claude terminal split in that new tab. The terminal remains in the original tab, giving maximum screen estate for reviewing the diff.
295+
- `on_new_file_reject` ("keep_empty"|"close_window", default: `"keep_empty"`) - Behavior when rejecting a diff for a new file (where the old file did not exist).
296+
- Legacy aliases (still supported): `vertical_split` (maps to `layout`) and `open_in_current_tab` (inverse of `open_in_new_tab`).
294297

295298
**Example use case**: If you frequently use `<CR>` or arrow keys in the Claude Code terminal to accept/reject diffs, enable this option to prevent focus from moving to the diff buffer where `<CR>` might trigger unintended actions.
296299

297300
```lua
298301
require("claudecode").setup({
299302
diff_opts = {
300-
keep_terminal_focus = true, -- If true, moves focus back to terminal after diff opens
301-
open_in_new_tab = true, -- Open diff in a separate tab
303+
layout = "vertical", -- "vertical" or "horizontal"
304+
keep_terminal_focus = true, -- If true, moves focus back to terminal after diff opens
305+
open_in_new_tab = true, -- Open diff in a separate tab
302306
hide_terminal_in_new_tab = true, -- In the new tab, do not show Claude terminal
303-
auto_close_on_accept = true,
304-
show_diff_stats = true,
305-
vertical_split = true,
306-
open_in_current_tab = true,
307+
on_new_file_reject = "keep_empty", -- "keep_empty" or "close_window"
308+
309+
-- Legacy aliases (still supported):
310+
-- vertical_split = true,
311+
-- open_in_current_tab = true,
307312
},
308313
})
309314
```

DEVELOPMENT.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ vv netrw # Start Neovim with built-in netrw configuration
153153

154154
# List available configurations
155155
list-configs
156+
157+
# Minimal repro environment (copies fixtures/repro/example into /tmp)
158+
repro
156159
```
157160

158161
**Example fixture structure** (`fixtures/my-integration/`):

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,15 @@ For deep technical details, see [ARCHITECTURE.md](./ARCHITECTURE.md).
277277

278278
-- Diff Integration
279279
diff_opts = {
280-
auto_close_on_accept = true,
281-
vertical_split = true,
282-
open_in_current_tab = true,
283-
keep_terminal_focus = false, -- If true, moves focus back to terminal after diff opens (including floating terminals)
280+
layout = "vertical", -- "vertical" or "horizontal"
281+
open_in_new_tab = false,
282+
keep_terminal_focus = false, -- If true, moves focus back to terminal after diff opens
283+
hide_terminal_in_new_tab = false,
284+
-- on_new_file_reject = "keep_empty", -- "keep_empty" or "close_window"
285+
286+
-- Legacy aliases (still supported):
287+
-- vertical_split = true,
288+
-- open_in_current_tab = true,
284289
},
285290
},
286291
keys = {

fixtures/bin/repro

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
# repro - Launch Neovim with the minimal "repro" fixture + a temp workspace.
6+
#
7+
# This is intended to provide a low-noise environment for reproducing issues in claudecode.nvim.
8+
9+
FIXTURES_DIR="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)"
10+
11+
# Source common functions
12+
source "$FIXTURES_DIR/bin/common.sh"
13+
14+
config="repro"
15+
if ! validate_config "$FIXTURES_DIR" "$config"; then
16+
exit 1
17+
fi
18+
19+
keep_workspace=false
20+
21+
# Parse repro-specific flags.
22+
# Everything else is passed to nvim.
23+
while [[ $# -gt 0 ]]; do
24+
case "$1" in
25+
--keep|--no-reset)
26+
keep_workspace=true
27+
shift
28+
;;
29+
--help|-h)
30+
cat <<'EOF'
31+
repro - Launch Neovim with the minimal "repro" fixture + a temp workspace.
32+
33+
Usage:
34+
repro [--keep] [nvim args...]
35+
36+
Options:
37+
--keep, --no-reset Reuse the existing repro workspace instead of resetting it
38+
39+
Examples:
40+
repro
41+
repro --keep
42+
repro --headless "+qall!"
43+
EOF
44+
exit 0
45+
;;
46+
--)
47+
shift
48+
break
49+
;;
50+
*)
51+
break
52+
;;
53+
esac
54+
done
55+
56+
nvim_args=("$@")
57+
58+
template_dir="$FIXTURES_DIR/$config/example"
59+
if [[ ! -d "$template_dir" ]]; then
60+
echo "Error: repro template directory not found: $template_dir" >&2
61+
exit 1
62+
fi
63+
64+
workspace_dir="${TMPDIR:-/tmp}/claudecode.nvim-repro"
65+
66+
# Safety check before deleting
67+
if [[ -z "$workspace_dir" || "$workspace_dir" == "/" ]]; then
68+
echo "Error: unsafe workspace_dir: '$workspace_dir'" >&2
69+
exit 1
70+
fi
71+
72+
if [[ "$keep_workspace" == "true" && -d "$workspace_dir" ]]; then
73+
echo "Repro workspace (kept): $workspace_dir"
74+
else
75+
rm -rf "$workspace_dir"
76+
mkdir -p "$workspace_dir"
77+
cp -R "$template_dir/." "$workspace_dir/"
78+
echo "Repro workspace (reset): $workspace_dir"
79+
fi
80+
81+
echo "Repro config: $FIXTURES_DIR/$config"
82+
echo "Tip: :e README.md in the repro workspace for steps."
83+
84+
# If no args are provided, open the default file to keep the window non-empty.
85+
if [[ ${#nvim_args[@]} -eq 0 ]]; then
86+
nvim_args=("a.txt")
87+
fi
88+
89+
(cd "$workspace_dir" && NVIM_APPNAME="$config" XDG_CONFIG_HOME="$FIXTURES_DIR" nvim "${nvim_args[@]}")

fixtures/nvim-aliases.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ alias vv="$BIN_DIR/vv"
1414
alias vve="$BIN_DIR/vve"
1515
# shellcheck disable=SC2139
1616
alias list-configs="$BIN_DIR/list-configs"
17+
# shellcheck disable=SC2139
18+
alias repro="$BIN_DIR/repro"
1719

1820
echo "Neovim configuration aliases loaded!"
1921
echo "Use 'vv <config>' or 'vve <config>' to test configurations"
22+
echo "Use 'repro' for a minimal claudecode.nvim repro environment"
2023
echo "Use 'list-configs' to see available options"

fixtures/repro/example/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# claudecode.nvim repro workspace
2+
3+
This directory is copied into a temp workspace when you run `repro`.
4+
5+
## Quick start
6+
7+
From the repo root:
8+
9+
```sh
10+
source fixtures/nvim-aliases.sh
11+
repro
12+
```
13+
14+
That will:
15+
16+
- create `/tmp/claudecode.nvim-repro` (reset on every run; use `repro --keep` to reuse)
17+
- open Neovim with the **minimal** `fixtures/repro` config
18+
- open `a.txt` so your current window is non-empty
19+
20+
## Iterating on the config
21+
22+
The Neovim config lives at `fixtures/repro/init.lua`.
23+
24+
- Edit it from another terminal:
25+
26+
```sh
27+
vve repro
28+
```
29+
30+
Then restart the running `repro` Neovim instance to pick up changes.
31+
32+
- Or edit it from inside the running `repro` session:
33+
34+
```vim
35+
:ReproEditConfig
36+
```
37+
38+
> Note: config changes generally require restarting Neovim (this fixture avoids a plugin manager / hot-reload).
39+
40+
## Example flow (sanity check)
41+
42+
A basic end-to-end diff flow you can use to sanity-check the environment:
43+
44+
1. Start Claude:
45+
46+
- press `<leader>ac` (starts the server if needed, then opens the terminal), **or**
47+
- run `:ClaudeCodeStart` then `:ClaudeCode`
48+
49+
2. Ask Claude to edit `b.txt` (do not open it in a window first)
50+
3. Accept the diff with `:w` (or `<leader>aa`)
51+
4. Confirm you didn’t get any extra leftover windows: `:echo winnr('$')`
52+
53+
## Notes
54+
55+
- This fixture uses the **native** terminal provider to avoid depending on external plugins.
56+
- To tweak the config, edit `fixtures/repro/init.lua` (or run `vve repro`).

fixtures/repro/example/a.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
This is file A.
2+
3+
Keep this file open.
4+
Ask Claude to edit b.txt (without opening b.txt in a window first).

fixtures/repro/example/b.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
line1
2+
line2

fixtures/repro/init.lua

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
-- Minimal repro config for claudecode.nvim issues.
2+
--
3+
-- This fixture intentionally avoids a plugin manager so it's easy to run and reason about.
4+
--
5+
-- Usage (from repo root):
6+
-- source fixtures/nvim-aliases.sh
7+
-- repro
8+
--
9+
-- To edit this config:
10+
-- vve repro
11+
12+
-- Ensure this repo is on the runtimepath so `plugin/claudecode.lua` is loaded.
13+
local config_dir = vim.fn.stdpath("config")
14+
local repo_root = vim.fn.fnamemodify(config_dir, ":h:h")
15+
16+
vim.opt.rtp:prepend(repo_root)
17+
18+
-- Basic editor settings
19+
vim.g.mapleader = " "
20+
vim.g.maplocalleader = "\\"
21+
22+
local ok, claudecode = pcall(require, "claudecode")
23+
assert(ok, "Failed to load claudecode.nvim from repo root: " .. tostring(claudecode))
24+
25+
claudecode.setup({
26+
auto_start = false, -- avoid noisy startup + make restarts deterministic
27+
log_level = "debug",
28+
terminal = {
29+
provider = "native",
30+
auto_close = false,
31+
},
32+
diff_opts = {
33+
layout = "vertical",
34+
open_in_new_tab = false,
35+
keep_terminal_focus = false,
36+
},
37+
})
38+
39+
local function ensure_claudecode_started()
40+
local ok_start, started_or_err, port_or_err = pcall(function()
41+
return claudecode.start(false)
42+
end)
43+
44+
if not ok_start then
45+
vim.notify("ClaudeCode start crashed: " .. tostring(started_or_err), vim.log.levels.ERROR)
46+
return false
47+
end
48+
49+
local started = started_or_err
50+
if started then
51+
return true
52+
end
53+
54+
-- start() returns false + "Already running" when running.
55+
if port_or_err == "Already running" then
56+
return true
57+
end
58+
59+
vim.notify("ClaudeCode failed to start: " .. tostring(port_or_err), vim.log.levels.ERROR)
60+
return false
61+
end
62+
63+
-- Keymaps (kept small on purpose)
64+
vim.keymap.set("n", "<leader>ac", function()
65+
if ensure_claudecode_started() then
66+
local terminal = require("claudecode.terminal")
67+
terminal.simple_toggle({}, nil)
68+
end
69+
end, { desc = "Toggle Claude" })
70+
71+
vim.keymap.set("n", "<leader>af", function()
72+
if ensure_claudecode_started() then
73+
local terminal = require("claudecode.terminal")
74+
terminal.focus_toggle({}, nil)
75+
end
76+
end, { desc = "Focus Claude" })
77+
78+
vim.keymap.set("n", "<leader>aa", "<cmd>ClaudeCodeDiffAccept<cr>", { desc = "Accept diff" })
79+
vim.keymap.set("n", "<leader>ad", "<cmd>ClaudeCodeDiffDeny<cr>", { desc = "Deny diff" })
80+
81+
-- Convenience helpers for iterating on this fixture.
82+
vim.api.nvim_create_user_command("ReproEditConfig", function()
83+
local config_path = vim.fn.stdpath("config") .. "/init.lua"
84+
85+
-- Open the config file without `:edit` / `vim.cmd(...)` so we don't trigger
86+
-- Treesitter "vim" language injections (which can be noisy if parsers/queries mismatch).
87+
local bufnr = vim.fn.bufadd(config_path)
88+
vim.fn.bufload(bufnr)
89+
vim.api.nvim_set_current_buf(bufnr)
90+
end, { desc = "Edit the repro Neovim config" })
91+
92+
vim.keymap.set("n", "<leader>ae", "<cmd>ReproEditConfig<cr>", { desc = "Edit repro config" })
93+
vim.keymap.set("n", "<leader>aw", function()
94+
vim.notify(("windows in tab: %d"):format(vim.fn.winnr("$")))
95+
end, { desc = "Claude: show window count" })

0 commit comments

Comments
 (0)