Skip to content

fix(diff): reuse tabnew window for unified new-tab diff#303

Open
zakissimo wants to merge 1 commit into
coder:mainfrom
zakissimo:fix/unified-new-tab-empty-pane
Open

fix(diff): reuse tabnew window for unified new-tab diff#303
zakissimo wants to merge 1 commit into
coder:mainfrom
zakissimo:fix/unified-new-tab-empty-pane

Conversation

@zakissimo

Copy link
Copy Markdown

Summary

When the unified diff opens in a new tab (diff_opts = { layout = "unified", open_in_new_tab = true }), the tab contained an extra empty [No Name] pane next to the diff, and — with hide_terminal_in_new_tab = true — the diff's tab could also leak on cleanup. Both stem from diff_inline.setup_inline_diff.

Root cause

1. Empty pane. :tabnew (via display_terminal_in_new_tab) creates the tab with an ephemeral [No Name] window. setup_inline_diff then picks that window as editor_win and does rightbelow vsplit to place the diff beside it, so the empty [No Name] window lingers as a blank pane and wincmd = gives it equal width.

2. Dropped new-tab handle. guard_inline_setup captured the helper's return values with:

local results = { pcall(action) }
...
return unpack(results, 2)

Under hide_terminal_in_new_tab = true, display_terminal_in_new_tab returns nil for the terminal window in the middle of its result list (original_tab, nil, had_terminal, new_tab). That nil is a table hole, so #results reports a border before it and unpack(results, 2) truncates — silently dropping the trailing new-tab handle. Result: new_tab_number is nil, so the tab isn't closed in cleanup_inline_diff, and ClaudeCodeDiffOpened fires with tab_number = nil (breaking the documented "own the layout" hook for this path).

Fix

  • Reuse the ephemeral :tabnew window as the diff window when created_new_tab is true (its buffer is bufhidden=wipe, so it self-cleans). The in-current-tab path keeps the rightbelow vsplit so it never replaces the user's own buffer.
  • Add a small pack_results helper that records the exact argument count, and switch the guard to unpack(results, 2, results.n) so nils in the middle no longer truncate the returns.

Reproduction

require("claudecode").setup({
  diff_opts = { layout = "unified", open_in_new_tab = true, hide_terminal_in_new_tab = true },
})

Have Claude Code propose an edit. Before: the diff tab shows an empty pane on the left. After: the diff spans the tab (terminal shown → diff + terminal, no empty pane).

Testing

  • New tests/unit/diff_inline_new_tab_spec.lua asserts the new tab has exactly the expected windows (no empty pane) for hidden/shown terminal, and that new_tab_number is tracked (not nil). It fails on the pre-fix code and passes after.
  • Existing diff_inline_spec.lua and diff_hide_terminal_new_tab_spec.lua still pass.
  • Verified end-to-end in real headless Neovim (0.12.3): hidden → 1 full-width diff window; shown → diff + terminal, no empty pane.

🤖 Generated with Claude Code

The unified (inline) diff opened in a new tab left an empty [No Name]
pane and could leak its tab. Two issues in setup_inline_diff, both hit by
layout = "unified" + open_in_new_tab = true:

1. The diff was opened with `rightbelow vsplit` off the ephemeral window
   created by `:tabnew`, so that empty [No Name] window lingered beside
   the diff and `wincmd =` split the width evenly. Reuse the ephemeral
   window as the diff window instead. The in-current-tab path keeps the
   split so it does not replace the user's buffer.

2. guard_inline_setup captured display_terminal_in_new_tab()'s return
   values with `{ pcall(action) }` + `unpack(results, 2)`. Under
   hide_terminal_in_new_tab that helper returns a nil terminal window in
   the middle of its results; the nil is a table hole, so `#results`
   reports a border before it and the trailing new-tab handle is dropped.
   That left new_tab_number nil, so the tab was not closed on cleanup and
   ClaudeCodeDiffOpened fired with tab_number = nil. Preserve the exact
   return count via pack_results / `unpack(results, 2, results.n)`.

Add tests/unit/diff_inline_new_tab_spec.lua covering the window layout
(no empty pane, terminal hidden or shown) and the preserved tab handle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zakissimo added a commit to zakissimo/nvim that referenced this pull request Jul 9, 2026
Configure claudecode.nvim to open the unified diff in a new tab with the
terminal hidden for full-width review. Pin the plugin to a personal fork
that fixes the empty [No Name] pane and the dropped new-tab handle in that
path (coder/claudecode.nvim#303); revert to coder/claudecode.nvim once it
merges. opts is present so lazy calls setup() directly, replacing the old
config wrapper and its ClaudeCodeDiffOpened workaround autocmd.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant