The first Neovim IDE integration for Claude Code β bringing Anthropic's AI coding assistant to your favorite editor with a pure Lua implementation.
π― TL;DR: When Anthropic released Claude Code with VS Code and JetBrains support, I reverse-engineered their extension and built this Neovim plugin. This plugin implements the same WebSocket-based MCP protocol, giving Neovim users the same AI-powered coding experience.
claudecode-nvim.mp4
When Anthropic released Claude Code, they only supported VS Code and JetBrains. As a Neovim user, I wanted the same experience β so I reverse-engineered their extension and built this.
- π Pure Lua, Zero Dependencies β Built entirely with
vim.loopand Neovim built-ins - π 100% Protocol Compatible β Same WebSocket MCP implementation as official extensions
- π Fully Documented Protocol β Learn how to build your own integrations (see PROTOCOL.md)
- β‘ First to Market β Beat Anthropic to releasing Neovim support
- π οΈ Built with AI β Used Claude to reverse-engineer Claude's own protocol
{
"coder/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
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>am", "<cmd>ClaudeCodeSelectModel<cr>", desc = "Select Claude model" },
{ "<leader>ab", "<cmd>ClaudeCodeAdd %<cr>", desc = "Add current buffer" },
{ "<leader>as", "<cmd>ClaudeCodeSend<cr>", mode = "v", desc = "Send to Claude" },
{
"<leader>as",
"<cmd>ClaudeCodeTreeAdd<cr>",
desc = "Add file",
ft = { "NvimTree", "neo-tree", "oil", "minifiles", "netrw" },
},
-- Diff management
{ "<leader>aa", "<cmd>ClaudeCodeDiffAccept<cr>", desc = "Accept diff" },
{ "<leader>ad", "<cmd>ClaudeCodeDiffDeny<cr>", desc = "Deny diff" },
},
}That's it! The plugin will auto-configure everything else.
- Neovim >= 0.8.0
- Claude Code CLI installed
- folke/snacks.nvim for enhanced terminal support
If you've used Claude Code's migrate-installer command to move to a local installation, you'll need to configure the plugin to use the local path.
Claude Code offers a claude migrate-installer command that:
- Moves Claude Code from a global npm installation to
~/.claude/local/ - Avoids permission issues with system directories
- Creates shell aliases but these may not be available to Neovim
Check your installation type:
# Check where claude command points
which claude
# Global installation shows: /usr/local/bin/claude (or similar)
# Local installation shows: alias to ~/.claude/local/claude
# Verify installation health
claude doctorIf you have a local installation, configure the plugin with the direct path:
{
"coder/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
opts = {
terminal_cmd = "~/.claude/local/claude", -- Point to local installation
},
config = true,
keys = {
-- Your keymaps here
},
}Native Binary Installation (Alpha)
Claude Code also offers an experimental native binary installation method currently in alpha testing. This provides a single executable with no Node.js dependencies.
Install the native binary using one of these methods:
# Fresh install (recommended)
curl -fsSL claude.ai/install.sh | bash
# From existing Claude Code installation
claude install- macOS: Full support for Intel and Apple Silicon
- Linux: x64 and arm64 architectures
- Windows: Via WSL (Windows Subsystem for Linux)
- Zero Dependencies: Single executable file with no external requirements
- Cross-Platform: Consistent experience across operating systems
- Secure Installation: Includes checksum verification and automatic cleanup
The exact binary path depends on your shell integration. To find your installation:
# Check where claude command points
which claude
# Verify installation type and health
claude doctorConfigure the plugin with the detected path:
{
"coder/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
opts = {
terminal_cmd = "/path/to/your/claude", -- Use output from 'which claude'
},
config = true,
keys = {
-- Your keymaps here
},
}Note: If Claude Code was installed globally via npm, you can use the default configuration without specifying
terminal_cmd.
" Launch Claude Code in a split
:ClaudeCode
" Claude now sees your current file and selections in real-time!
" Send visual selection as context
:'<,'>ClaudeCodeSend
" Claude can open files, show diffs, and more- Launch Claude: Run
:ClaudeCodeto open Claude in a split terminal - Send context:
- Select text in visual mode and use
<leader>asto send it to Claude - In
nvim-tree/neo-tree/oil.nvim/mini.nvim, press<leader>ason a file to add it to Claude's context
- Select text in visual mode and use
- Let Claude work: Claude can now:
- See your current file and selections in real-time
- Open files in your editor
- Show diffs with proposed changes
- Access diagnostics and workspace info
:ClaudeCode- Toggle the Claude Code terminal window:ClaudeCodeFocus- Smart focus/toggle Claude terminal:ClaudeCodeSelectModel- Select Claude model and open terminal with optional arguments:ClaudeCodeSend- Send current visual selection to Claude:ClaudeCodeAdd <file-path> [start-line] [end-line]- Add specific file to Claude context with optional line range:ClaudeCodeDiffAccept- Accept diff changes:ClaudeCodeDiffDeny- Reject diff changes:ClaudeCodeCloseAllDiffs- Close pending Claude diffs (leaves accepted/saved diffs intact):ClaudeCodeLiveCursor [preview|open|off]- Toggle the live Claude cursor (see Live Claude Cursor):ClaudeCodePlanView [on|off]- Toggle showing Claude's plan-mode plan in an editor split (see Plan View)
When Claude proposes changes, the plugin opens a diff view:
- Accept:
:w(save) or<leader>aa - Reject:
:qor<leader>ad
You can edit Claude's suggestions before accepting them.
diff_opts.provider selects the rendering backend:
| value | behavior |
|---|---|
"auto" (default) |
Use unified.nvim if it is installed; otherwise fall back to native. |
"native" |
Built-in side-by-side vimdiff: original on the left, proposed on the right. |
"unified" |
One buffer containing the proposed content with inline +/- marks for the diff against the on-disk original. Marks auto-refresh as you edit. Requires unified.nvim. |
The unified provider ignores diff_opts.open_in_new_tab β proposals always open in the current tab.
:ClaudeCodeDiffToggleProvider (or require("claudecode.diff").toggle_provider()) flips the active provider for subsequent diffs. Any currently open diff keeps its existing view; the change takes effect on the next proposal. Suggested keymap:
vim.keymap.set("n", "<leader>aD", "<cmd>ClaudeCodeDiffToggleProvider<cr>", { desc = "Claude: toggle diff provider" })If a diff is resolved outside this Neovim (for example via Claude remote control on another device) the diff windows would otherwise stay open. They are now closed automatically when the Claude session that opened them disconnects. If you resolve diffs remotely while the session is still connected, run :ClaudeCodeCloseAllDiffs to clear the leftover pending proposals β it leaves any diff you have already accepted (:w) but whose file has not been written yet untouched, so your saved edits are never discarded.
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.
The protocol uses a WebSocket-based variant of MCP (Model Context Protocol) that:
- Creates a WebSocket server on a random port
- Writes a lock file to
~/.claude/ide/[port].lock(or$CLAUDE_CONFIG_DIR/ide/[port].lockifCLAUDE_CONFIG_DIRis set) with connection info - Sets environment variables that tell Claude where to connect
- Implements MCP tools that Claude can call
π Read the full reverse-engineering story β π§ Complete protocol documentation β
Built with pure Lua and zero external dependencies:
- WebSocket Server - RFC 6455 compliant implementation using
vim.loop - MCP Protocol - Full JSON-RPC 2.0 message handling
- Lock File System - Enables Claude CLI discovery
- Selection Tracking - Real-time context updates
- Native Diff Support - Seamless file comparison
For deep technical details, see ARCHITECTURE.md.
{
"coder/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
opts = {
-- Server Configuration
port_range = { min = 10000, max = 65535 },
auto_start = true,
log_level = "info", -- "trace", "debug", "info", "warn", "error"
terminal_cmd = nil, -- Custom terminal command (default: "claude")
-- For local installations: "~/.claude/local/claude"
-- For native binary: use output from 'which claude'
-- Send/Focus Behavior
-- When true, successful sends will focus the Claude terminal if already connected
focus_after_send = false,
-- Selection Tracking
track_selection = true,
visual_demotion_delay_ms = 50,
-- Terminal Configuration
terminal = {
split_side = "right", -- "left" or "right"
split_width_percentage = 0.30,
provider = "auto", -- "auto", "snacks", "native", "external", "none", or custom provider table
auto_close = true,
snacks_win_opts = {}, -- Opts to pass to `Snacks.terminal.open()` - see Floating Window section below
-- Work around a Neovim core bug (< 0.12.2) that fragments large pastes into
-- the terminal, making Cmd+V appear to truncate ([#161]). true | false | "auto"
-- ("auto", the default, enables it only on affected Neovim versions).
fix_streamed_paste = "auto",
-- Provider-specific options
provider_opts = {
-- Command for external terminal provider. Can be:
-- 1. String with %s placeholder: "alacritty -e %s" (backward compatible)
-- 2. String with two %s placeholders: "alacritty --working-directory %s -e %s" (cwd, command)
-- 3. Function returning command: function(cmd, env) return "alacritty -e " .. cmd end
external_terminal_cmd = nil,
},
},
-- Diff Integration
diff_opts = {
provider = "auto", -- "auto" (unified.nvim if installed, else native), "native", or "unified"
layout = "vertical", -- "vertical" or "horizontal" (native provider only)
open_in_new_tab = false, -- ignored by the unified provider
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,
},
-- Live Claude cursor (opt-in): a real-time view of what Claude is reading/editing
live_cursor = {
enabled = false, -- master switch
mode = nil, -- REQUIRED when enabled: "preview" or "open"
layout = "horizontal", -- "vertical" or "horizontal" split for the preview window
split_size_percentage = 0.5, -- preview split size as a fraction of the screen (0..1): height (horizontal) or width (vertical)
highlight = "ClaudeCodeLiveCursor", -- highlight group (defaults to a link to Visual)
clear_delay_ms = 4000, -- clear the highlight after this much inactivity (0 = never)
diff_suppress_ms = 250, -- delay before painting an edit, to detect a review diff first
preview_winbar = true, -- colored winbar label marking the preview window
preview_divider = true, -- tint the preview window's split divider
preview_label = "β Claude live preview", -- winbar brand text (file name + read/write action are appended)
preview_align = "center", -- winbar alignment: "center" or "left"
preview_highlight = "ClaudeCodeLivePreview", -- marker color (defaults to a link to DiagnosticOk / green)
},
-- Plan view (opt-in): show Claude's plan-mode plan in the editor
plan = {
enabled = false, -- master switch
focus = true, -- move focus into the plan window when it opens
close_on_resolve = true, -- restore the editor when the plan is accepted/rejected
clear_delay_ms = 0, -- inactivity backstop close (0 = rely on accept/reject signals)
label = "β Claude plan", -- winbar brand text for the plan window
highlight = "ClaudeCodePlan", -- winbar color (defaults to a link to DiagnosticInfo)
-- layout / split_size_percentage only apply to the fallback split that is
-- created when there is no editor window to take over (terminal-only layout):
layout = "vertical", -- "vertical" or "horizontal" fallback split
split_size_percentage = 0.5, -- fallback split size as a fraction of the screen (0..1)
},
},
keys = {
-- Your keymaps here
},
}A "ride-along" view: as Claude uses its Read/Edit/Write tools, claudecode.nvim opens or previews the touched file and highlights the exact line range β a live picture of what the agent is doing.
This works by injecting a Claude Code PreToolUse hook at launch via claude --settings (your own settings files are never modified) that reports each tool event back to the running Neovim over its RPC socket. The hook is a small Lua script run through nvim itself, so it works on macOS, Linux, and native Windows alike. It requires the claude CLI's hook support and an nvim on PATH.
enabledβ off by default; set totrueto turn it on.modeβ required when enabled:"preview"β load the file into a single reserved split, leaving your layout and focus untouched (the "live camera"). The split is spawned next to the editor window closest to the Claude terminal. It ishorizontal(below) by default; setlayout = "vertical"for a split beside instead. When Claude goes idle forclear_delay_ms, the preview split auto-closes β unless your cursor is in it, in which case it stays until you leave. Setclear_delay_ms = 0to keep it open."open"β load the file into your current editor window if you're focused in one; otherwise (e.g. focus is in the Claude terminal, the usual case) into the editor window closest to the terminal.
- Focus never moves β the file updates passively while you keep working.
- Reads highlight the read range. Edits are shown only when no review diff is open for that file (i.e. in auto / accept-edits mode); when a normal diff is shown, that already visualizes the change and the live cursor stays out of the way.
- Edits render a real inline diff when unified.nvim is installed: the live cursor reconstructs the pre-edit file (post-edit content with the edit reversed) and shows the precise added/removed lines, rather than a heuristic highlight. Without unified.nvim it falls back to highlighting the changed line range.
- Multi-tab aware: each Claude is stamped with the tab it launched in, and its reads/edits only drive the preview when you are viewing that tab. A Claude running in a background tab never opens previews in the tab you are currently working in.
highlightβ the highlight group used for the range. Define your own group of this (or another) name to customize colors.- In
previewmode the window is marked so you can tell it apart from a normal split: a colored winbar label (preview_winbar) and a tinted split divider (preview_divider), both on by default. The winbar reads<label> Β· <reading|writing> Β· <file>(e.g.β Claude live preview Β· reading Β· config.lua) so you can see at a glance what Claude is doing and to which file.preview_labelsets the leading brand text,preview_aligncenters ("center", default) or left-aligns ("left") it, andpreview_highlightsets the color β it defaults to a link toDiagnosticOk(green); point it at a different group (e.g.Function,Directory) for blue, or defineClaudeCodeLivePreviewyourself. - Neovim splits have no true border, so a window can only recolor the separators it owns (right/bottom edges). The divider tint is therefore most effective with
layout = "vertical"(it colors the separator beside the preview); withlayout = "horizontal"the top divider belongs to the window above and stays uncolored, so the winbar is the reliable marker there. If you run a winbar plugin (dropbar, barbecue, lualine winbar, β¦), the live-preview label intentionally overrides it inside the preview window.
Toggle it at runtime with :ClaudeCodeLiveCursor:
:ClaudeCodeLiveCursorβ flip enabled/disabled (requires amodeto already be set).:ClaudeCodeLiveCursor preview/:ClaudeCodeLiveCursor openβ enable in that mode.:ClaudeCodeLiveCursor offβ disable and clear any highlight.
Because the hook is injected when Claude launches, enabling mid-session takes effect the next time you start Claude; disabling stops the highlighting immediately.
When Claude runs in plan mode (shift+tab) and presents its finished plan, claudecode.nvim can open that plan as a markdown document in the editor β the same idea as the VS Code extension, instead of leaving the plan only in the terminal.
It uses the same launch hook as the live cursor: Claude presents a plan by calling its built-in ExitPlanMode tool, whose input carries the plan markdown. A PreToolUse hook on that tool forwards the plan into Neovim the moment it is ready to read (before you accept or reject), and we render it in the editor. The same launch-time requirements apply (claude CLI hook support; an nvim on PATH; Claude must be started through the plugin).
enabledβ off by default; set totrueto turn it on. Independent oflive_cursor(either can be on without the other).- The plan takes over an existing editor window β the one physically closest to the Claude terminal β rather than opening a new split, so it reads like the VS Code experience. When the plan resolves, that window's previous buffer (and cursor) are restored. If you navigate that window to a different buffer while reading the plan, your choice is left alone.
- If there is no editor window to take over (e.g. only the Claude terminal is visible), it falls back to a dedicated split sized by
layout/split_size_percentage, which is closed on resolve. focusβ whentrue(default), focus moves into the plan window so you can scroll it with normal motions; switch back to the Claude terminal to accept/reject. Setfalseto leave focus where it was.close_on_resolveβ whentrue(default), the plan is dismissed once resolved: accepting it (Claude starts executing) or rejecting it (Claude resumes planning) both restore the editor.- Multi-tab aware: like the live cursor, a Claude running in a background tab never opens its plan in the tab you are currently working in.
label/highlightβ the winbar brand text and its color (defaults to a link toDiagnosticInfo).
Toggle it at runtime with :ClaudeCodePlanView (on / off, or no argument to flip). As with the live cursor, the hook is injected at launch, so enabling mid-session applies the next time you start Claude.
Click a file path in the Claude terminal to open it in your editor β like the VS Code extension, instead of having it open in your OS file explorer (Finder, etc.).
Claude marks file references (the Read/Update/Write headers and inline path:line mentions) as clickable hyperlinks. Inside Neovim's :terminal those clicks would otherwise be handled by Claude/your terminal and opened with the OS, so this feature captures the links Claude emits and opens them in Neovim itself.
enabledβ on by default; set tofalseto turn it off.- Plain click (no modifier) on a file link opens it. The file opens in the editor window closest to the Claude terminal (the same targeting the plan view uses), replacing that window's buffer, and jumps to the
:linewhen one is shown. Clicks that aren't on a file pass straight through to Claude, so its own terminal UI keeps working. It works regardless of how far the conversation has scrolled, and on file names shown by themselves, with a relative path, or split across two lines. keyβ a normal-mode keymap on the terminal buffer (default"gf") that opens the path under the cursor; set""to disable. Handy if your terminal doesn't deliver the click into Neovim.mouse_motionβ on by default; enables Neovim's'mousemoveevent'so mouse motion reaches Claude and Claude's own hover (underlining the link under the pointer) works inside:terminal. Setfalseto leave the global option untouched.clickβ set tofalseto keep thegfkeymap (andmouse_motion) but not intercept mouse clicks at all.
opts = {
terminal_links = {
enabled = true,
click = true,
key = "gf",
mouse_motion = true,
},
}Unlike the live cursor and plan view, this needs no launch hook β it reads the hyperlinks already in the terminal β so toggling the config takes effect on the next Claude terminal you open.
You can fix the Claude terminal's working directory regardless of autochdir and buffer-local cwd changes. Options (precedence order):
cwd_provider(ctx): function that returns a directory string. Receives{ file, file_dir, cwd }.cwd: static path to use as working directory.git_repo_cwd = true: resolves git root from the current file directory (or cwd if no file).
Examples:
require("claudecode").setup({
-- Top-level aliases are supported and forwarded to terminal config
git_repo_cwd = true,
})
require("claudecode").setup({
terminal = {
cwd = vim.fn.expand("~/projects/my-app"),
},
})
require("claudecode").setup({
terminal = {
cwd_provider = function(ctx)
-- Prefer repo root; fallback to file's directory
local cwd = require("claudecode.cwd").git_root(ctx.file_dir or ctx.cwd) or ctx.file_dir or ctx.cwd
return cwd
end,
},
})The snacks_win_opts configuration allows you to create floating Claude Code terminals with custom positioning, sizing, and key bindings. Here are several practical examples:
local toggle_key = "<C-,>"
return {
{
"coder/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
keys = {
{ toggle_key, "<cmd>ClaudeCodeFocus<cr>", desc = "Claude Code", mode = { "n", "x" } },
},
opts = {
terminal = {
---@module "snacks"
---@type snacks.win.Config|{}
snacks_win_opts = {
position = "float",
width = 0.9,
height = 0.9,
keys = {
claude_hide = {
toggle_key,
function(self)
self:hide()
end,
mode = "t",
desc = "Hide",
},
},
},
},
},
},
}Alternative with Meta+, (Alt+,) Toggle
local toggle_key = "<M-,>" -- Alt/Meta + comma
return {
{
"coder/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
keys = {
{ toggle_key, "<cmd>ClaudeCodeFocus<cr>", desc = "Claude Code", mode = { "n", "x" } },
},
opts = {
terminal = {
snacks_win_opts = {
position = "float",
width = 0.8,
height = 0.8,
border = "rounded",
keys = {
claude_hide = { toggle_key, function(self) self:hide() end, mode = "t", desc = "Hide" },
},
},
},
},
},
}Centered Floating Window with Custom Styling
require("claudecode").setup({
terminal = {
snacks_win_opts = {
position = "float",
width = 0.6,
height = 0.6,
border = "double",
backdrop = 80,
keys = {
claude_hide = { "<Esc>", function(self) self:hide() end, mode = "t", desc = "Hide" },
claude_close = { "q", "close", mode = "n", desc = "Close" },
},
},
},
})Multiple Key Binding Options
{
"coder/claudecode.nvim",
dependencies = { "folke/snacks.nvim" },
keys = {
{ "<C-,>", "<cmd>ClaudeCodeFocus<cr>", desc = "Claude Code (Ctrl+,)", mode = { "n", "x" } },
{ "<M-,>", "<cmd>ClaudeCodeFocus<cr>", desc = "Claude Code (Alt+,)", mode = { "n", "x" } },
{ "<leader>tc", "<cmd>ClaudeCodeFocus<cr>", desc = "Toggle Claude", mode = { "n", "x" } },
},
opts = {
terminal = {
snacks_win_opts = {
position = "float",
width = 0.85,
height = 0.85,
border = "rounded",
keys = {
-- Multiple ways to hide from terminal mode
claude_hide_ctrl = { "<C-,>", function(self) self:hide() end, mode = "t", desc = "Hide (Ctrl+,)" },
claude_hide_alt = { "<M-,>", function(self) self:hide() end, mode = "t", desc = "Hide (Alt+,)" },
claude_hide_esc = { "<C-\\><C-n>", function(self) self:hide() end, mode = "t", desc = "Hide (Ctrl+\\)" },
},
},
},
},
}Window Position Variations
-- Bottom floating (like a drawer)
snacks_win_opts = {
position = "bottom",
height = 0.4,
width = 1.0,
border = "single",
}
-- Side floating panel
snacks_win_opts = {
position = "right",
width = 0.4,
height = 1.0,
border = "rounded",
}
-- Small centered popup
snacks_win_opts = {
position = "float",
width = 120, -- Fixed width in columns
height = 30, -- Fixed height in rows
border = "double",
backdrop = 90,
}For complete configuration options, see:
Run Claude Code without any terminal management inside Neovim. This is useful for advanced setups where you manage the CLI externally (tmux, kitty, separate terminal windows) while still using the WebSocket server and tools.
You have to take care of launching CC and connecting it to the IDE yourself. (e.g. claude --ide or launching claude and then selecting the IDE using the /ide command)
{
"coder/claudecode.nvim",
opts = {
terminal = {
provider = "none", -- no UI actions; server + tools remain available
},
},
}Notes:
- No windows/buffers are created.
:ClaudeCodeand related commands will not open anything. - The WebSocket server still starts and broadcasts work as usual. Launch the Claude CLI externally when desired.
Run Claude Code in a separate terminal application outside of Neovim:
-- Using a string template (simple)
{
"coder/claudecode.nvim",
opts = {
terminal = {
provider = "external",
provider_opts = {
external_terminal_cmd = "alacritty -e %s", -- %s is replaced with claude command
-- Or with working directory: "alacritty --working-directory %s -e %s" (first %s = cwd, second %s = command)
},
},
},
}
-- Using a function for dynamic command generation (advanced)
{
"coder/claudecode.nvim",
opts = {
terminal = {
provider = "external",
provider_opts = {
external_terminal_cmd = function(cmd, env)
-- You can build complex commands based on environment or conditions
if vim.fn.has("mac") == 1 then
return { "osascript", "-e", string.format('tell app "Terminal" to do script "%s"', cmd) }
else
return "alacritty -e " .. cmd
end
end,
},
},
},
}You can create custom terminal providers by passing a table with the required functions instead of a string provider name:
require("claudecode").setup({
terminal = {
provider = {
-- Required functions
setup = function(config)
-- Initialize your terminal provider
end,
open = function(cmd_string, env_table, effective_config, focus)
-- Open terminal with command and environment
-- focus parameter controls whether to focus terminal (defaults to true)
end,
close = function()
-- Close the terminal
end,
simple_toggle = function(cmd_string, env_table, effective_config)
-- Simple show/hide toggle
end,
focus_toggle = function(cmd_string, env_table, effective_config)
-- Smart toggle: focus terminal if not focused, hide if focused
end,
get_active_bufnr = function()
-- Return terminal buffer number or nil
return 123 -- example
end,
is_available = function()
-- Return true if provider can be used
return true
end,
-- Optional functions (auto-generated if not provided)
toggle = function(cmd_string, env_table, effective_config)
-- Defaults to calling simple_toggle for backward compatibility
end,
_get_terminal_for_test = function()
-- For testing only, defaults to return nil
return nil
end,
},
},
})Here's a complete example using a hypothetical my_terminal plugin:
local my_terminal_provider = {
setup = function(config)
-- Store config for later use
self.config = config
end,
open = function(cmd_string, env_table, effective_config, focus)
if focus == nil then focus = true end
local my_terminal = require("my_terminal")
my_terminal.open({
cmd = cmd_string,
env = env_table,
width = effective_config.split_width_percentage,
side = effective_config.split_side,
focus = focus,
})
end,
close = function()
require("my_terminal").close()
end,
simple_toggle = function(cmd_string, env_table, effective_config)
require("my_terminal").toggle()
end,
focus_toggle = function(cmd_string, env_table, effective_config)
local my_terminal = require("my_terminal")
if my_terminal.is_focused() then
my_terminal.hide()
else
my_terminal.focus()
end
end,
get_active_bufnr = function()
return require("my_terminal").get_bufnr()
end,
is_available = function()
local ok, _ = pcall(require, "my_terminal")
return ok
end,
}
require("claudecode").setup({
terminal = {
provider = my_terminal_provider,
},
})The custom provider will automatically fall back to the native provider if validation fails or is_available() returns false.
Note: If your command or working directory may contain spaces or special characters, prefer returning a table of args from a function (e.g., { "alacritty", "--working-directory", cwd, "-e", "claude", "--help" }) to avoid shell-quoting issues.
The following are third-party community extensions that complement claudecode.nvim. These extensions are not affiliated with Coder and are maintained independently by community members. We do not ensure that these extensions work correctly or provide support for them.
π claude-fzf.nvim
Integrates fzf-lua's file selection with claudecode.nvim's context management:
- Batch file selection with fzf-lua multi-select
- Smart search integration with grep β Claude
- Tree-sitter based context extraction
- Support for files, buffers, git files
Provides convenient Claude interaction history management and access for enhanced workflow continuity.
Disclaimer: These community extensions are developed and maintained by independent contributors. The authors and their extensions are not affiliated with Coder. Use at your own discretion and refer to their respective repositories for installation instructions, documentation, and support.
Using auto-save plugins can cause diff windows opened by Claude to immediately accept without waiting for input. You can avoid this using a custom condition:
Pocco81/auto-save.nvim
opts = {
-- ... other options
condition = function(buf)
local fn = vim.fn
local utils = require("auto-save.utils.data")
-- First check the default conditions
if not (fn.getbufvar(buf, "&modifiable") == 1 and utils.not_in(fn.getbufvar(buf, "&filetype"), {})) then
return false
end
-- Exclude claudecode diff buffers by buffer name patterns
local bufname = vim.api.nvim_buf_get_name(buf)
if bufname:match("%(proposed%)") or
bufname:match("%(NEW FILE %- proposed%)") or
bufname:match("%(New%)") then
return false
end
-- Exclude by buffer variables (claudecode sets these)
if vim.b[buf].claudecode_diff_tab_name or
vim.b[buf].claudecode_diff_new_win or
vim.b[buf].claudecode_diff_target_win then
return false
end
-- Exclude by buffer type (claudecode diff buffers use "acwrite")
local buftype = fn.getbufvar(buf, "&buftype")
if buftype == "acwrite" then
return false
end
return true -- Safe to auto-save
end,
},okuuva/auto-save.nvim
opts = {
-- ... other options
condition = function(buf)
-- Exclude claudecode diff buffers by buffer name patterns
local bufname = vim.api.nvim_buf_get_name(buf)
if bufname:match('%(proposed%)') or bufname:match('%(NEW FILE %- proposed%)') or bufname:match('%(New%)') then
return false
end
-- Exclude by buffer variables (claudecode sets these)
if
vim.b[buf].claudecode_diff_tab_name
or vim.b[buf].claudecode_diff_new_win
or vim.b[buf].claudecode_diff_target_win
then
return false
end
-- Exclude by buffer type (claudecode diff buffers use "acwrite")
local buftype = vim.fn.getbufvar(buf, '&buftype')
if buftype == 'acwrite' then
return false
end
return true -- Safe to auto-save
end,
},- Claude not connecting? Check
:ClaudeCodeStatusand verify lock file exists in~/.claude/ide/(or$CLAUDE_CONFIG_DIR/ide/ifCLAUDE_CONFIG_DIRis set) - Need debug logs? Set
log_level = "debug"in opts - Terminal issues? Try
provider = "native"if using snacks.nvim - Local installation not working? If you used
claude migrate-installer, setterminal_cmd = "~/.claude/local/claude"in your config. Checkwhich claudevsls ~/.claude/local/claudeto verify your installation type. - Native binary installation not working? If you used the alpha native binary installer, run
claude doctorto verify installation health and usewhich claudeto find the binary path. Setterminal_cmd = "/path/to/claude"with the detected path in your config.
See DEVELOPMENT.md for build instructions and development guidelines. Tests can be run with mise run test.
- Claude Code CLI by Anthropic
- Inspired by analyzing the official VS Code extension
- Built with assistance from AI (how meta!)