Skip to content
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