Skip to content

fix: address terminal focus error when buffer is hidden #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 17, 2025
224 changes: 186 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Using [lazy.nvim](https://github.com/folke/lazy.nvim):
"<leader>as",
"<cmd>ClaudeCodeTreeAdd<cr>",
desc = "Add file",
ft = { "NvimTree", "neo-tree" },
ft = { "NvimTree", "neo-tree", "oil" },
},
},
}
Expand Down Expand Up @@ -214,77 +214,225 @@ For deep technical details, see [ARCHITECTURE.md](./ARCHITECTURE.md).

See [DEVELOPMENT.md](./DEVELOPMENT.md) for build instructions and development guidelines. Tests can be run with `make test`.

## Advanced Setup
## Configuration

### Quick Setup

For most users, the default configuration is sufficient:

```lua
{
"coder/claudecode.nvim",
dependencies = {
"folke/snacks.nvim", -- optional
},
config = true,
keys = {
{ "<leader>a", nil, desc = "AI/Claude Code" },
{ "<leader>ac", "<cmd>ClaudeCode<cr>", desc = "Toggle Claude" },
{ "<leader>af", "<cmd>ClaudeCodeFocus<cr>", desc = "Focus Claude" },
{ "<leader>ar", "<cmd>ClaudeCode --resume<cr>", desc = "Resume Claude" },
{ "<leader>aC", "<cmd>ClaudeCode --continue<cr>", desc = "Continue Claude" },
{ "<leader>as", "<cmd>ClaudeCodeSend<cr>", mode = "v", desc = "Send to Claude" },
{
"<leader>as",
"<cmd>ClaudeCodeTreeAdd<cr>",
desc = "Add file",
ft = { "NvimTree", "neo-tree", "oil" },
},
},
}
```

### Advanced Configuration

<details>
<summary>Full configuration with all options</summary>
<summary>Complete configuration options</summary>

```lua
{
"coder/claudecode.nvim",
dependencies = {
"folke/snacks.nvim", -- Optional for enhanced terminal
},
keys = {
{ "<leader>a", nil, desc = "AI/Claude Code" },
{ "<leader>ac", "<cmd>ClaudeCode<cr>", desc = "Toggle Claude" },
{ "<leader>af", "<cmd>ClaudeCodeFocus<cr>", desc = "Focus Claude" },
{ "<leader>ar", "<cmd>ClaudeCode --resume<cr>", desc = "Resume Claude" },
{ "<leader>aC", "<cmd>ClaudeCode --continue<cr>", desc = "Continue Claude" },
{ "<leader>as", "<cmd>ClaudeCodeSend<cr>", mode = "v", desc = "Send to Claude" },
{
"<leader>as",
"<cmd>ClaudeCodeTreeAdd<cr>",
desc = "Add file",
ft = { "NvimTree", "neo-tree", "oil" },
},
},
opts = {
-- Server options
port_range = { min = 10000, max = 65535 },
auto_start = true,
log_level = "info",

-- Terminal options
-- Server Configuration
port_range = { min = 10000, max = 65535 }, -- WebSocket server port range
auto_start = true, -- Auto-start server on Neovim startup
log_level = "info", -- "trace", "debug", "info", "warn", "error"
terminal_cmd = nil, -- Custom terminal command (default: "claude")

-- Selection Tracking
track_selection = true, -- Enable real-time selection tracking
visual_demotion_delay_ms = 50, -- Delay before demoting visual selection (ms)

-- Connection Management
connection_wait_delay = 200, -- Wait time after connection before sending queued @ mentions (ms)
connection_timeout = 10000, -- Max time to wait for Claude Code connection (ms)
queue_timeout = 5000, -- Max time to keep @ mentions in queue (ms)

-- Terminal Configuration
terminal = {
split_side = "right",
split_width_percentage = 0.3,
provider = "auto", -- "auto" (default), "snacks", or "native"
auto_close = true, -- Auto-close terminal after command completion
split_side = "right", -- "left" or "right"
split_width_percentage = 0.30, -- Width as percentage (0.0 to 1.0)
provider = "auto", -- "auto", "snacks", or "native"
show_native_term_exit_tip = true, -- Show exit tip for native terminal
auto_close = true, -- Auto-close terminal after command completion
},

-- Diff options
-- Diff Integration
diff_opts = {
auto_close_on_accept = true,
vertical_split = true,
auto_close_on_accept = true, -- Close diff view after accepting changes
show_diff_stats = true, -- Show diff statistics
vertical_split = true, -- Use vertical split for diffs
open_in_current_tab = true, -- Open diffs in current tab vs new tab
},
},
config = true,
}
```

</details>

### Configuration Options Explained

#### Server Options

- **`port_range`**: Port range for the WebSocket server that Claude connects to
- **`auto_start`**: Whether to automatically start the integration when Neovim starts
- **`terminal_cmd`**: Override the default "claude" command (useful for custom Claude installations)
- **`log_level`**: Controls verbosity of plugin logs

#### Selection Tracking

- **`track_selection`**: Enables real-time selection updates sent to Claude
- **`visual_demotion_delay_ms`**: Time to wait before switching from visual selection to cursor position tracking

#### Connection Management

- **`connection_wait_delay`**: Prevents overwhelming Claude with rapid @ mentions after connection
- **`connection_timeout`**: How long to wait for Claude to connect before giving up
- **`queue_timeout`**: How long to keep queued @ mentions before discarding them

#### Terminal Configuration

- **`split_side`**: Which side to open the terminal split (`"left"` or `"right"`)
- **`split_width_percentage`**: Terminal width as a fraction of screen width (0.1 = 10%, 0.5 = 50%)
- **`provider`**: Terminal implementation to use:
- `"auto"`: Try snacks.nvim, fallback to native
- `"snacks"`: Force snacks.nvim (requires folke/snacks.nvim)
- `"native"`: Use built-in Neovim terminal
- **`show_native_term_exit_tip`**: Show help text for exiting native terminal
- **`auto_close`**: Automatically close terminal when commands finish

#### Diff Options

- **`auto_close_on_accept`**: Close diff view after accepting changes with `:w` or `<leader>da`
- **`show_diff_stats`**: Display diff statistics (lines added/removed)
- **`vertical_split`**: Use vertical split layout for diffs
- **`open_in_current_tab`**: Open diffs in current tab instead of creating new tabs

### Example Configurations

#### Minimal Configuration

```lua
{
"coder/claudecode.nvim",
keys = {
{ "<leader>a", nil, desc = "AI/Claude Code" },
{ "<leader>ac", "<cmd>ClaudeCode<cr>", desc = "Toggle Claude" },
{ "<leader>af", "<cmd>ClaudeCodeFocus<cr>", desc = "Focus Claude" },
{ "<leader>ar", "<cmd>ClaudeCode --resume<cr>", desc = "Resume Claude" },
{ "<leader>aC", "<cmd>ClaudeCode --continue<cr>", desc = "Continue Claude" },
{ "<leader>as", "<cmd>ClaudeCodeSend<cr>", mode = "v", desc = "Send to Claude" },
{
"<leader>as",
"<cmd>ClaudeCodeTreeAdd<cr>",
desc = "Add file",
ft = { "NvimTree", "neo-tree" },
ft = { "NvimTree", "neo-tree", "oil" },
},
{ "<leader>ao", "<cmd>ClaudeCodeOpen<cr>", desc = "Open Claude" },
{ "<leader>ax", "<cmd>ClaudeCodeClose<cr>", desc = "Close Claude" },
},
opts = {
log_level = "warn", -- Reduce log verbosity
auto_start = false, -- Manual startup only
},
}
```

</details>

### Terminal Auto-Close Behavior

The `auto_close` option controls what happens when Claude commands finish:

**When `auto_close = true` (default):**

- Terminal automatically closes after command completion
- Error notifications shown for failed commands (non-zero exit codes)
- Clean workflow for quick command execution
#### Power User Configuration

**When `auto_close = false`:**
```lua
{
"coder/claudecode.nvim",
keys = {
{ "<leader>a", nil, desc = "AI/Claude Code" },
{ "<leader>ac", "<cmd>ClaudeCode<cr>", desc = "Toggle Claude" },
{ "<leader>af", "<cmd>ClaudeCodeFocus<cr>", desc = "Focus Claude" },
{ "<leader>ar", "<cmd>ClaudeCode --resume<cr>", desc = "Resume Claude" },
{ "<leader>aC", "<cmd>ClaudeCode --continue<cr>", desc = "Continue Claude" },
{ "<leader>as", "<cmd>ClaudeCodeSend<cr>", mode = "v", desc = "Send to Claude" },
{
"<leader>as",
"<cmd>ClaudeCodeTreeAdd<cr>",
desc = "Add file",
ft = { "NvimTree", "neo-tree", "oil" },
},
},
opts = {
log_level = "debug",
visual_demotion_delay_ms = 100, -- Slower selection demotion
connection_wait_delay = 500, -- Longer delay for @ mention batching
terminal = {
split_side = "left",
split_width_percentage = 0.4, -- Wider terminal
provider = "snacks",
auto_close = false, -- Keep terminal open to review output
},
diff_opts = {
vertical_split = false, -- Horizontal diffs
open_in_current_tab = false, -- New tabs for diffs
},
},
}
```

- Terminal stays open after command completion
- Allows reviewing command output and any error messages
- Useful for debugging or when you want to see detailed output
#### Custom Claude Installation

```lua
terminal = {
provider = "snacks",
auto_close = false, -- Keep terminal open to review output
{
"coder/claudecode.nvim",
keys = {
{ "<leader>a", nil, desc = "AI/Claude Code" },
{ "<leader>ac", "<cmd>ClaudeCode<cr>", desc = "Toggle Claude" },
{ "<leader>af", "<cmd>ClaudeCodeFocus<cr>", desc = "Focus Claude" },
{ "<leader>ar", "<cmd>ClaudeCode --resume<cr>", desc = "Resume Claude" },
{ "<leader>aC", "<cmd>ClaudeCode --continue<cr>", desc = "Continue Claude" },
{ "<leader>as", "<cmd>ClaudeCodeSend<cr>", mode = "v", desc = "Send to Claude" },
{
"<leader>as",
"<cmd>ClaudeCodeTreeAdd<cr>",
desc = "Add file",
ft = { "NvimTree", "neo-tree", "oil" },
},
},
opts = {
terminal_cmd = "/opt/claude/bin/claude", -- Custom Claude path
port_range = { min = 20000, max = 25000 }, -- Different port range
},
}
```

Expand Down
4 changes: 2 additions & 2 deletions STORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

While browsing Reddit at DevOpsCon in London, I stumbled upon a post that caught my eye: someone mentioned finding .vsix files in Anthropic's npm package for their Claude Code VS Code extension.

Link to the Reddit post: https://www.reddit.com/r/ClaudeAI/comments/1klpzvl/hidden_jetbrains_vs_code_plugin_in_todays_release/
Link to the Reddit post: <https://www.reddit.com/r/ClaudeAI/comments/1klpzvl/hidden_jetbrains_vs_code_plugin_in_todays_release/>

My first thought? "No way, they wouldn't ship the source like that."

Expand Down Expand Up @@ -45,7 +45,7 @@ What I discovered was fascinating:

Armed with this knowledge, I faced a new challenge: I wanted this in Neovim, but I didn't know Lua.

So I did what any reasonable person would do in 2024 — I used AI to help me build it. Using Roo Code with Gemini 2.5 Pro, I scaffolded a Neovim plugin that implements the same protocol.
So I did what any reasonable person would do in 2025 — I used AI to help me build it. Using Roo Code with Gemini 2.5 Pro, I scaffolded a Neovim plugin that implements the same protocol. (Note: Claude 4 models were not publicly available at the time of writing the extension.)

The irony isn't lost on me: I used AI to reverse-engineer an AI tool, then used AI to build a plugin for AI.

Expand Down
42 changes: 36 additions & 6 deletions dev-config.lua
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
-- Development configuration for claudecode.nvim
-- This is Thomas's personal config for developing claudecode.nvim
-- Symlink this to your personal Neovim config:
-- ln -s ~/GitHub/claudecode.nvim/dev-config.lua ~/.config/nvim/lua/plugins/dev-claudecode.lua
-- ln -s ~/projects/claudecode.nvim/dev-config.lua ~/.config/nvim/lua/plugins/dev-claudecode.lua

return {
"coder/claudecode.nvim",
dev = true, -- Use local development version
dir = "~/GitHub/claudecode.nvim", -- Adjust path as needed
keys = {
-- AI/Claude Code prefix
{ "<leader>a", nil, desc = "AI/Claude Code" },
Expand All @@ -23,7 +22,7 @@ return {
"<leader>as",
"<cmd>ClaudeCodeTreeAdd<cr>",
desc = "Add file from tree",
ft = { "NvimTree", "neo-tree" },
ft = { "NvimTree", "neo-tree", "oil" },
},

-- Development helpers
Expand All @@ -34,11 +33,42 @@ return {
{ "<leader>aQ", "<cmd>ClaudeCodeStop<cr>", desc = "Stop Claude Server" },
},

-- Development configuration
-- Development configuration - all options shown with defaults commented out
opts = {
-- auto_start = true,
-- Server Configuration
-- port_range = { min = 10000, max = 65535 }, -- WebSocket server port range
-- auto_start = true, -- Auto-start server on Neovim startup
-- log_level = "info", -- "trace", "debug", "info", "warn", "error"
-- terminal_cmd = nil, -- Custom terminal command (default: "claude")

-- Selection Tracking
-- track_selection = true, -- Enable real-time selection tracking
-- visual_demotion_delay_ms = 50, -- Delay before demoting visual selection (ms)

-- Connection Management
-- connection_wait_delay = 200, -- Wait time after connection before sending queued @ mentions (ms)
-- connection_timeout = 10000, -- Max time to wait for Claude Code connection (ms)
-- queue_timeout = 5000, -- Max time to keep @ mentions in queue (ms)

-- Diff Integration
-- diff_opts = {
-- auto_close_on_accept = true, -- Close diff view after accepting changes
-- show_diff_stats = true, -- Show diff statistics
-- vertical_split = true, -- Use vertical split for diffs
-- open_in_current_tab = true, -- Open diffs in current tab vs new tab
-- },

-- Terminal Configuration
-- terminal = {
-- split_side = "right", -- "left" or "right"
-- split_width_percentage = 0.30, -- Width as percentage (0.0 to 1.0)
-- provider = "auto", -- "auto", "snacks", or "native"
-- show_native_term_exit_tip = true, -- Show exit tip for native terminal
-- auto_close = true, -- Auto-close terminal after command completion
-- },

-- Development overrides (uncomment as needed)
-- log_level = "debug",
-- terminal_cmd = "claude --debug",
-- terminal = {
-- provider = "native",
-- auto_close = false, -- Keep terminals open to see output
Expand Down
15 changes: 15 additions & 0 deletions lua/claudecode/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ M.defaults = {
log_level = "info",
track_selection = true,
visual_demotion_delay_ms = 50, -- Milliseconds to wait before demoting a visual selection
connection_wait_delay = 200, -- Milliseconds to wait after connection before sending queued @ mentions
connection_timeout = 10000, -- Maximum time to wait for Claude Code to connect (milliseconds)
queue_timeout = 5000, -- Maximum time to keep @ mentions in queue (milliseconds)
diff_opts = {
auto_close_on_accept = true,
show_diff_stats = true,
Expand Down Expand Up @@ -53,6 +56,18 @@ function M.validate(config)
"visual_demotion_delay_ms must be a non-negative number"
)

assert(
type(config.connection_wait_delay) == "number" and config.connection_wait_delay >= 0,
"connection_wait_delay must be a non-negative number"
)

assert(
type(config.connection_timeout) == "number" and config.connection_timeout > 0,
"connection_timeout must be a positive number"
)

assert(type(config.queue_timeout) == "number" and config.queue_timeout > 0, "queue_timeout must be a positive number")

assert(type(config.diff_opts) == "table", "diff_opts must be a table")
assert(type(config.diff_opts.auto_close_on_accept) == "boolean", "diff_opts.auto_close_on_accept must be a boolean")
assert(type(config.diff_opts.show_diff_stats) == "boolean", "diff_opts.show_diff_stats must be a boolean")
Expand Down
Loading