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
17 changes: 11 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,22 +288,27 @@ require("claudecode").setup({

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

- `layout` ("vertical"|"horizontal", default: `"vertical"`) - Whether the diff panes open in a vertical or horizontal split.
- `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.
- `open_in_new_tab` (boolean, default: `false`) - Open diffs in a new tab instead of the current tab.
- `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.
- `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).
- Legacy aliases (still supported): `vertical_split` (maps to `layout`) and `open_in_current_tab` (inverse of `open_in_new_tab`).

**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.

```lua
require("claudecode").setup({
diff_opts = {
keep_terminal_focus = true, -- If true, moves focus back to terminal after diff opens
open_in_new_tab = true, -- Open diff in a separate tab
layout = "vertical", -- "vertical" or "horizontal"
keep_terminal_focus = true, -- If true, moves focus back to terminal after diff opens
open_in_new_tab = true, -- Open diff in a separate tab
hide_terminal_in_new_tab = true, -- In the new tab, do not show Claude terminal
auto_close_on_accept = true,
show_diff_stats = true,
vertical_split = true,
open_in_current_tab = true,
on_new_file_reject = "keep_empty", -- "keep_empty" or "close_window"

-- Legacy aliases (still supported):
-- vertical_split = true,
-- open_in_current_tab = true,
},
})
```
Expand Down
3 changes: 3 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ vv netrw # Start Neovim with built-in netrw configuration

# List available configurations
list-configs

# Minimal repro environment (copies fixtures/repro/example into /tmp)
repro
```

**Example fixture structure** (`fixtures/my-integration/`):
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,15 @@ For deep technical details, see [ARCHITECTURE.md](./ARCHITECTURE.md).

-- Diff Integration
diff_opts = {
auto_close_on_accept = true,
vertical_split = true,
open_in_current_tab = true,
keep_terminal_focus = false, -- If true, moves focus back to terminal after diff opens (including floating terminals)
layout = "vertical", -- "vertical" or "horizontal"
open_in_new_tab = false,
keep_terminal_focus = false, -- If true, moves focus back to terminal after diff opens
hide_terminal_in_new_tab = false,
-- on_new_file_reject = "keep_empty", -- "keep_empty" or "close_window"

-- Legacy aliases (still supported):
-- vertical_split = true,
-- open_in_current_tab = true,
},
},
keys = {
Expand Down
89 changes: 89 additions & 0 deletions fixtures/bin/repro
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/bin/bash

set -euo pipefail

# repro - Launch Neovim with the minimal "repro" fixture + a temp workspace.
#
# This is intended to provide a low-noise environment for reproducing issues in claudecode.nvim.

FIXTURES_DIR="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)"

# Source common functions
source "$FIXTURES_DIR/bin/common.sh"

config="repro"
if ! validate_config "$FIXTURES_DIR" "$config"; then
exit 1
fi

keep_workspace=false

# Parse repro-specific flags.
# Everything else is passed to nvim.
while [[ $# -gt 0 ]]; do
case "$1" in
--keep|--no-reset)
keep_workspace=true
shift
;;
--help|-h)
cat <<'EOF'
repro - Launch Neovim with the minimal "repro" fixture + a temp workspace.

Usage:
repro [--keep] [nvim args...]

Options:
--keep, --no-reset Reuse the existing repro workspace instead of resetting it

Examples:
repro
repro --keep
repro --headless "+qall!"
EOF
exit 0
;;
--)
shift
break
;;
*)
break
;;
esac
done

nvim_args=("$@")

template_dir="$FIXTURES_DIR/$config/example"
if [[ ! -d "$template_dir" ]]; then
echo "Error: repro template directory not found: $template_dir" >&2
exit 1
fi

workspace_dir="${TMPDIR:-/tmp}/claudecode.nvim-repro"

# Safety check before deleting
if [[ -z "$workspace_dir" || "$workspace_dir" == "/" ]]; then
echo "Error: unsafe workspace_dir: '$workspace_dir'" >&2
exit 1
fi

if [[ "$keep_workspace" == "true" && -d "$workspace_dir" ]]; then
echo "Repro workspace (kept): $workspace_dir"
else
rm -rf "$workspace_dir"
mkdir -p "$workspace_dir"
cp -R "$template_dir/." "$workspace_dir/"
echo "Repro workspace (reset): $workspace_dir"
fi

echo "Repro config: $FIXTURES_DIR/$config"
echo "Tip: :e README.md in the repro workspace for steps."

# If no args are provided, open the default file to keep the window non-empty.
if [[ ${#nvim_args[@]} -eq 0 ]]; then
nvim_args=("a.txt")
fi

(cd "$workspace_dir" && NVIM_APPNAME="$config" XDG_CONFIG_HOME="$FIXTURES_DIR" nvim "${nvim_args[@]}")
3 changes: 3 additions & 0 deletions fixtures/nvim-aliases.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ alias vv="$BIN_DIR/vv"
alias vve="$BIN_DIR/vve"
# shellcheck disable=SC2139
alias list-configs="$BIN_DIR/list-configs"
# shellcheck disable=SC2139
alias repro="$BIN_DIR/repro"

echo "Neovim configuration aliases loaded!"
echo "Use 'vv <config>' or 'vve <config>' to test configurations"
echo "Use 'repro' for a minimal claudecode.nvim repro environment"
echo "Use 'list-configs' to see available options"
56 changes: 56 additions & 0 deletions fixtures/repro/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# claudecode.nvim repro workspace

This directory is copied into a temp workspace when you run `repro`.

## Quick start

From the repo root:

```sh
source fixtures/nvim-aliases.sh
repro
```

That will:

- create `/tmp/claudecode.nvim-repro` (reset on every run; use `repro --keep` to reuse)
- open Neovim with the **minimal** `fixtures/repro` config
- open `a.txt` so your current window is non-empty

## Iterating on the config

The Neovim config lives at `fixtures/repro/init.lua`.

- Edit it from another terminal:

```sh
vve repro
```

Then restart the running `repro` Neovim instance to pick up changes.

- Or edit it from inside the running `repro` session:

```vim
:ReproEditConfig
```

> Note: config changes generally require restarting Neovim (this fixture avoids a plugin manager / hot-reload).

## Example flow (sanity check)

A basic end-to-end diff flow you can use to sanity-check the environment:

1. Start Claude:

- press `<leader>ac` (starts the server if needed, then opens the terminal), **or**
- run `:ClaudeCodeStart` then `:ClaudeCode`

2. Ask Claude to edit `b.txt` (do not open it in a window first)
3. Accept the diff with `:w` (or `<leader>aa`)
4. Confirm you didn’t get any extra leftover windows: `:echo winnr('$')`

## Notes

- This fixture uses the **native** terminal provider to avoid depending on external plugins.
- To tweak the config, edit `fixtures/repro/init.lua` (or run `vve repro`).
4 changes: 4 additions & 0 deletions fixtures/repro/example/a.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
This is file A.

Keep this file open.
Ask Claude to edit b.txt (without opening b.txt in a window first).
2 changes: 2 additions & 0 deletions fixtures/repro/example/b.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
line1
line2
95 changes: 95 additions & 0 deletions fixtures/repro/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
-- Minimal repro config for claudecode.nvim issues.
--
-- This fixture intentionally avoids a plugin manager so it's easy to run and reason about.
--
-- Usage (from repo root):
-- source fixtures/nvim-aliases.sh
-- repro
--
-- To edit this config:
-- vve repro

-- Ensure this repo is on the runtimepath so `plugin/claudecode.lua` is loaded.
local config_dir = vim.fn.stdpath("config")
local repo_root = vim.fn.fnamemodify(config_dir, ":h:h")

vim.opt.rtp:prepend(repo_root)

-- Basic editor settings
vim.g.mapleader = " "
vim.g.maplocalleader = "\\"

local ok, claudecode = pcall(require, "claudecode")
assert(ok, "Failed to load claudecode.nvim from repo root: " .. tostring(claudecode))

claudecode.setup({
auto_start = false, -- avoid noisy startup + make restarts deterministic
log_level = "debug",
terminal = {
provider = "native",
auto_close = false,
},
diff_opts = {
layout = "vertical",
open_in_new_tab = false,
keep_terminal_focus = false,
},
})

local function ensure_claudecode_started()
local ok_start, started_or_err, port_or_err = pcall(function()
return claudecode.start(false)
end)

if not ok_start then
vim.notify("ClaudeCode start crashed: " .. tostring(started_or_err), vim.log.levels.ERROR)
return false
end

local started = started_or_err
if started then
return true
end

-- start() returns false + "Already running" when running.
if port_or_err == "Already running" then
return true
end

vim.notify("ClaudeCode failed to start: " .. tostring(port_or_err), vim.log.levels.ERROR)
return false
end

-- Keymaps (kept small on purpose)
vim.keymap.set("n", "<leader>ac", function()
if ensure_claudecode_started() then
local terminal = require("claudecode.terminal")
terminal.simple_toggle({}, nil)
end
end, { desc = "Toggle Claude" })

vim.keymap.set("n", "<leader>af", function()
if ensure_claudecode_started() then
local terminal = require("claudecode.terminal")
terminal.focus_toggle({}, nil)
end
end, { desc = "Focus Claude" })

vim.keymap.set("n", "<leader>aa", "<cmd>ClaudeCodeDiffAccept<cr>", { desc = "Accept diff" })
vim.keymap.set("n", "<leader>ad", "<cmd>ClaudeCodeDiffDeny<cr>", { desc = "Deny diff" })

-- Convenience helpers for iterating on this fixture.
vim.api.nvim_create_user_command("ReproEditConfig", function()
local config_path = vim.fn.stdpath("config") .. "/init.lua"

-- Open the config file without `:edit` / `vim.cmd(...)` so we don't trigger
-- Treesitter "vim" language injections (which can be noisy if parsers/queries mismatch).
local bufnr = vim.fn.bufadd(config_path)
vim.fn.bufload(bufnr)
vim.api.nvim_set_current_buf(bufnr)
end, { desc = "Edit the repro Neovim config" })

vim.keymap.set("n", "<leader>ae", "<cmd>ReproEditConfig<cr>", { desc = "Edit repro config" })
vim.keymap.set("n", "<leader>aw", function()
vim.notify(("windows in tab: %d"):format(vim.fn.winnr("$")))
end, { desc = "Claude: show window count" })
Loading