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
46 changes: 34 additions & 12 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,22 +129,36 @@ vim.api.nvim_create_autocmd("CursorMoved", {
-- Preserves selection context when switching to terminal
```

### 6. Terminal Integration (`terminal.lua`)
### 6. Terminal Integration (`terminal/`)

Flexible terminal management with provider pattern:
Flexible terminal management with provider pattern and centralized window management:

```lua
-- Snacks.nvim provider (preferred)
if has_snacks then
Snacks.terminal.open(cmd, {
win = { position = "right", width = 0.3 }
})
else
-- Native fallback
vim.cmd("vsplit | terminal " .. cmd)
end
-- Window manager singleton owns THE terminal window
-- Providers only create buffers, window_manager displays them
local window_manager = require("claudecode.terminal.window_manager")

-- Display a buffer in the managed window (preserves user resizing)
window_manager.display_buffer(bufnr, focus)

-- Snacks.nvim provider creates buffer, delegates window to manager
local term = Snacks.terminal.open(cmd, opts)
vim.api.nvim_win_close(term.win, false) -- Close snacks' window
window_manager.display_buffer(term.buf, true) -- Use our window

-- Native provider creates buffer without window
local bufnr = vim.api.nvim_create_buf(false, true)
vim.fn.termopen(cmd, { env = env })
window_manager.display_buffer(bufnr, true)
```

Key features:

- **Single window**: All sessions share one terminal window
- **Buffer switching**: `nvim_win_set_buf()` preserves window size
- **Session management**: Multiple Claude sessions with tab-like switching
- **Window preservation**: User resizing persists across session switches

## Key Implementation Patterns

### Thread Safety
Expand Down Expand Up @@ -199,7 +213,15 @@ lua/claudecode/
├── tools/init.lua # MCP tool registry
├── diff.lua # Native diff support
├── selection.lua # Selection tracking
├── terminal.lua # Terminal management
├── session.lua # Multi-session state management
├── terminal.lua # Terminal orchestration
├── terminal/ # Terminal providers
│ ├── window_manager.lua # Singleton window management
│ ├── snacks.lua # Snacks.nvim provider
│ ├── native.lua # Native Neovim terminal
│ ├── external.lua # External terminal apps
│ ├── tabbar.lua # Session tab bar UI
│ └── osc_handler.lua # Terminal title detection
└── lockfile.lua # Discovery files
```

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Features

- Multi-session terminal support with `:ClaudeCodeNew`, `:ClaudeCodeSessions`, `:ClaudeCodeSwitch`, `:ClaudeCodeCloseSession` commands
- Tab bar UI with mouse support for session management (`terminal.tabs.*` configuration)
- Smart ESC handling - double-tap ESC to exit terminal mode (`esc_timeout`, `keymaps.exit_terminal`)
- External terminal provider to run Claude in a separate terminal ([#102](https://github.com/coder/claudecode.nvim/pull/102))
- Terminal provider APIs: implement `ensure_visible` for reliability ([#103](https://github.com/coder/claudecode.nvim/pull/103))
- Working directory control for Claude terminal ([#117](https://github.com/coder/claudecode.nvim/pull/117))
Expand Down Expand Up @@ -32,6 +35,10 @@

### Bug Fixes

- Preserve terminal window size across session operations
- Keep terminal window open when session exits with other sessions available
- Fix cursor position when switching terminal sessions
- Send selection updates on BufEnter event
- Wrap ERROR/WARN logging in `vim.schedule` to avoid fast-event context errors ([#54](https://github.com/coder/claudecode.nvim/pull/54))
- Native terminal: do not wipe Claude buffer on window close ([#60](https://github.com/coder/claudecode.nvim/pull/60))
- Native terminal: respect `auto_close` behavior ([#63](https://github.com/coder/claudecode.nvim/pull/63))
Expand Down
181 changes: 170 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# claudecode.nvim

[![Tests](https://github.com/coder/claudecode.nvim/actions/workflows/test.yml/badge.svg)](https://github.com/coder/claudecode.nvim/actions/workflows/test.yml)
[![Tests](https://github.com/snirt/claudecode.nvim/actions/workflows/test.yml/badge.svg)](https://github.com/snirt/claudecode.nvim/actions/workflows/test.yml)
![Neovim version](https://img.shields.io/badge/Neovim-0.8%2B-green)
![Status](https://img.shields.io/badge/Status-beta-blue)

Expand All @@ -20,11 +20,54 @@ When Anthropic released Claude Code, they only supported VS Code and JetBrains.
- ⚡ **First to Market** — Beat Anthropic to releasing Neovim support
- 🛠️ **Built with AI** — Used Claude to reverse-engineer Claude's own protocol

## Fork Features

This fork adds features not available in the original [coder/claudecode.nvim](https://github.com/coder/claudecode.nvim):

### Multi-Session Support

Run multiple Claude Code sessions simultaneously, each with isolated state:

- **Independent sessions** — Each session has its own terminal, WebSocket connection, and context
- **Easy switching** — Use `:ClaudeCodeSessions` for a picker or `:ClaudeCodeSwitch 2` to switch directly
- **Session lifecycle** — Create with `:ClaudeCodeNew`, close with `:ClaudeCodeCloseSession`

Perfect for working on multiple features, comparing approaches, or keeping separate contexts for different parts of a project.

### Visual Tab Bar

A clickable tab bar for managing sessions visually:

```
┌─────────────────────────────────────────────────────┐
│ [1*] ✕ | [2] ✕ | [3] ✕ | [+] │
├─────────────────────────────────────────────────────┤
│ │
│ Claude Code Terminal │
│ │
└─────────────────────────────────────────────────────┘
```

- **Mouse support** — Click tabs to switch, click ✕ to close, click + for new session
- **Keyboard navigation** — `Alt+Tab` / `Alt+Shift+Tab` to cycle sessions
- **Active indicator** — Current session marked with `*`

Enable with:

```lua
terminal = {
tabs = {
enabled = true,
mouse_enabled = true,
},
}
```

## Installation

```lua
{
"coder/claudecode.nvim",
"snirt/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
config = true,
keys = {
Expand All @@ -45,6 +88,9 @@ When Anthropic released Claude Code, they only supported VS Code and JetBrains.
-- Diff management
{ "<leader>aa", "<cmd>ClaudeCodeDiffAccept<cr>", desc = "Accept diff" },
{ "<leader>ad", "<cmd>ClaudeCodeDiffDeny<cr>", desc = "Deny diff" },
-- Multi-session management
{ "<leader>an", "<cmd>ClaudeCodeNew<cr>", desc = "New Claude session" },
{ "<leader>al", "<cmd>ClaudeCodeSessions<cr>", desc = "List Claude sessions" },
},
}
```
Expand Down Expand Up @@ -90,7 +136,7 @@ If you have a local installation, configure the plugin with the direct path:

```lua
{
"coder/claudecode.nvim",
"snirt/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
opts = {
terminal_cmd = "~/.claude/local/claude", -- Point to local installation
Expand Down Expand Up @@ -147,7 +193,7 @@ Configure the plugin with the detected path:

```lua
{
"coder/claudecode.nvim",
"snirt/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
opts = {
terminal_cmd = "/path/to/your/claude", -- Use output from 'which claude'
Expand All @@ -163,6 +209,52 @@ Configure the plugin with the detected path:

> **Note**: If Claude Code was installed globally via npm, you can use the default configuration without specifying `terminal_cmd`.

## Recommended Configuration

A practical configuration with the most useful options:

```lua
{
"snirt/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
opts = {
-- Terminal as floating window (recommended)
terminal = {
provider = "snacks",
split_side = "right",
split_width_percentage = 0.30,
snacks_win_opts = {
style = "float",
width = 0.8,
height = 0.8,
border = "rounded",
},
-- Tab bar for multiple sessions
tabs = {
enabled = true,
mouse_enabled = true,
},
},
-- Diff behavior
diff_opts = {
auto_close_on_accept = true,
},
},
keys = {
{ "<leader>a", group = "Claude" },
{ "<leader>ac", "<cmd>ClaudeCode<cr>", desc = "Toggle Claude" },
{ "<leader>af", "<cmd>ClaudeCodeFocus<cr>", desc = "Focus Claude" },
{ "<leader>as", "<cmd>ClaudeCodeSend<cr>", mode = "v", desc = "Send selection" },
{ "<leader>as", "<cmd>ClaudeCodeTreeAdd<cr>", desc = "Add file", ft = { "NvimTree", "neo-tree", "oil", "minifiles", "netrw" } },
{ "<leader>aa", "<cmd>ClaudeCodeDiffAccept<cr>", desc = "Accept diff" },
{ "<leader>ad", "<cmd>ClaudeCodeDiffDeny<cr>", desc = "Deny diff" },
-- Multi-session
{ "<leader>an", "<cmd>ClaudeCodeNew<cr>", desc = "New session" },
{ "<leader>al", "<cmd>ClaudeCodeSessions<cr>", desc = "List sessions" },
},
}
```

## Quick Demo

```vim
Expand Down Expand Up @@ -199,6 +291,13 @@ Configure the plugin with the detected path:
- `:ClaudeCodeDiffAccept` - Accept diff changes
- `:ClaudeCodeDiffDeny` - Reject diff changes

**Multi-Session Commands:**

- `:ClaudeCodeNew` - Create a new Claude terminal session
- `:ClaudeCodeSessions` - Show session picker (fzf-lua if available)
- `:ClaudeCodeSwitch <number>` - Switch to session by number
- `:ClaudeCodeCloseSession [number]` - Close a session (active session if no number)

## Working with Diffs

When Claude proposes changes, the plugin opens a native Neovim diff view:
Expand All @@ -208,6 +307,41 @@ When Claude proposes changes, the plugin opens a native Neovim diff view:

You can edit Claude's suggestions before accepting them.

## Multi-Session Support

Run multiple Claude Code sessions simultaneously:

- **Create sessions**: `:ClaudeCodeNew` opens a new terminal session
- **Switch sessions**: Use `:ClaudeCodeSessions` to pick from a list, or `:ClaudeCodeSwitch 2` to switch directly
- **Close sessions**: `:ClaudeCodeCloseSession` closes the active session, or `:ClaudeCodeCloseSession 2` to close a specific one

Each session has isolated:

- Terminal buffer and process
- WebSocket client connection
- Selection tracking context
- @ mention queue

### Tab Bar for Sessions

Enable a visual tab bar for managing multiple sessions:

```lua
terminal = {
tabs = {
enabled = true,
mouse_enabled = true, -- Click tabs to switch, middle-click to close
},
}
```

The tab bar shows:

- Numbered tabs for each session (active marked with `*`)
- Close button (x) on each tab
- New session button (+)
- Supports keyboard navigation (Alt+Tab, Alt+Shift+Tab) and mouse interactions

## How It Works

This plugin creates a WebSocket server that Claude Code CLI connects to, implementing the same protocol as the official VS Code extension. When you launch Claude, it automatically detects Neovim and gains full access to your editor.
Expand Down Expand Up @@ -238,7 +372,7 @@ For deep technical details, see [ARCHITECTURE.md](./ARCHITECTURE.md).

```lua
{
"coder/claudecode.nvim",
"snirt/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
opts = {
-- Server Configuration
Expand All @@ -265,6 +399,31 @@ For deep technical details, see [ARCHITECTURE.md](./ARCHITECTURE.md).
auto_close = true,
snacks_win_opts = {}, -- Opts to pass to `Snacks.terminal.open()` - see Floating Window section below

-- Smart ESC handling: double-tap ESC to exit terminal mode
esc_timeout = 200, -- Timeout in ms (0 or nil to disable smart ESC)

-- Terminal keymaps
keymaps = {
exit_terminal = "<Esc><Esc>", -- Key to exit terminal mode (set to false to disable)
},

-- Tab bar for multi-session management
tabs = {
enabled = false, -- Enable tab bar (default: false)
height = 1, -- Height in lines
show_close_button = true, -- Show [x] close button on tabs
show_new_button = true, -- Show [+] button for new session
separator = " | ", -- Separator between tabs
active_indicator = "*", -- Indicator for active tab
mouse_enabled = false, -- Enable mouse clicks on tabs
keymaps = {
next_tab = "<A-Tab>", -- Switch to next session
prev_tab = "<A-S-Tab>", -- Switch to previous session
close_tab = "<A-w>", -- Close current session
new_tab = "<A-+>", -- Create new session
},
},

-- Provider-specific options
provider_opts = {
-- Command for external terminal provider. Can be:
Expand Down Expand Up @@ -332,7 +491,7 @@ The `snacks_win_opts` configuration allows you to create floating Claude Code te
local toggle_key = "<C-,>"
return {
{
"coder/claudecode.nvim",
"snirt/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
keys = {
{ toggle_key, "<cmd>ClaudeCodeFocus<cr>", desc = "Claude Code", mode = { "n", "x" } },
Expand Down Expand Up @@ -369,7 +528,7 @@ return {
local toggle_key = "<M-,>" -- Alt/Meta + comma
return {
{
"coder/claudecode.nvim",
"snirt/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
keys = {
{ toggle_key, "<cmd>ClaudeCodeFocus<cr>", desc = "Claude Code", mode = { "n", "x" } },
Expand Down Expand Up @@ -421,7 +580,7 @@ require("claudecode").setup({

```lua
{
"coder/claudecode.nvim",
"snirt/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
keys = {
{ "<C-,>", "<cmd>ClaudeCodeFocus<cr>", desc = "Claude Code (Ctrl+,)", mode = { "n", "x" } },
Expand Down Expand Up @@ -496,7 +655,7 @@ You have to take care of launching CC and connecting it to the IDE yourself. (e.

```lua
{
"coder/claudecode.nvim",
"snirt/claudecode.nvim",
opts = {
terminal = {
provider = "none", -- no UI actions; server + tools remain available
Expand All @@ -517,7 +676,7 @@ Run Claude Code in a separate terminal application outside of Neovim:
```lua
-- Using a string template (simple)
{
"coder/claudecode.nvim",
"snirt/claudecode.nvim",
opts = {
terminal = {
provider = "external",
Expand All @@ -531,7 +690,7 @@ Run Claude Code in a separate terminal application outside of Neovim:

-- Using a function for dynamic command generation (advanced)
{
"coder/claudecode.nvim",
"snirt/claudecode.nvim",
opts = {
terminal = {
provider = "external",
Expand Down
Loading