Skip to content

papricasix/claudecode.nvim

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

158 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

claudecode.nvim

Tests Neovim version Status

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

What Makes This Special

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.loop and 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

Installation

{
  "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.

Requirements

Local Installation Configuration

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.

What is a Local Installation?

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

Detecting Your Installation Type

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 doctor

Configuring for Local Installation

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

Installation Methods

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

Platform Support

  • macOS: Full support for Intel and Apple Silicon
  • Linux: x64 and arm64 architectures
  • Windows: Via WSL (Windows Subsystem for Linux)

Benefits

  • Zero Dependencies: Single executable file with no external requirements
  • Cross-Platform: Consistent experience across operating systems
  • Secure Installation: Includes checksum verification and automatic cleanup

Configuring for Native Binary

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 doctor

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

Quick Demo

" 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

Usage

  1. Launch Claude: Run :ClaudeCode to open Claude in a split terminal
  2. Send context:
    • Select text in visual mode and use <leader>as to send it to Claude
    • In nvim-tree/neo-tree/oil.nvim/mini.nvim, press <leader>as on a file to add it to Claude's context
  3. 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

Key Commands

  • :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)

Working with Diffs

When Claude proposes changes, the plugin opens a diff view:

  • Accept: :w (save) or <leader>aa
  • Reject: :q or <leader>ad

You can edit Claude's suggestions before accepting them.

Diff providers

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.

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.

The protocol uses a WebSocket-based variant of MCP (Model Context Protocol) that:

  1. Creates a WebSocket server on a random port
  2. Writes a lock file to ~/.claude/ide/[port].lock (or $CLAUDE_CONFIG_DIR/ide/[port].lock if CLAUDE_CONFIG_DIR is set) with connection info
  3. Sets environment variables that tell Claude where to connect
  4. Implements MCP tools that Claude can call

πŸ“– Read the full reverse-engineering story β†’ πŸ”§ Complete protocol documentation β†’

Architecture

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.

Advanced Configuration

{
  "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
  },
}

Live Claude Cursor

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 to true to 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 is horizontal (below) by default; set layout = "vertical" for a split beside instead. When Claude goes idle for clear_delay_ms, the preview split auto-closes β€” unless your cursor is in it, in which case it stays until you leave. Set clear_delay_ms = 0 to 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 preview mode 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_label sets the leading brand text, preview_align centers ("center", default) or left-aligns ("left") it, and preview_highlight sets the color β€” it defaults to a link to DiagnosticOk (green); point it at a different group (e.g. Function, Directory) for blue, or define ClaudeCodeLivePreview yourself.
  • 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); with layout = "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 a mode to 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.

Plan View

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 to true to turn it on. Independent of live_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 β€” when true (default), focus moves into the plan window so you can scroll it with normal motions; switch back to the Claude terminal to accept/reject. Set false to leave focus where it was.
  • close_on_resolve β€” when true (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 to DiagnosticInfo).

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.

Terminal Links

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 to false to 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 :line when 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. Set false to leave the global option untouched.
  • click β€” set to false to keep the gf keymap (and mouse_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.

Working Directory Control

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,
  },
})

Floating Window Configuration

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:

Basic Floating Window with Ctrl+, Toggle

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:

Terminal Providers

None (No-Op) Provider

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. :ClaudeCode and related commands will not open anything.
  • The WebSocket server still starts and broadcasts work as usual. Launch the Claude CLI externally when desired.

External Terminal Provider

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,
      },
    },
  },
}

Custom Terminal Providers

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,
    },
  },
})

Custom Provider Example

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.

Community Extensions

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.

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.

Auto-Save Plugin Issues

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,
},

Troubleshooting

  • Claude not connecting? Check :ClaudeCodeStatus and verify lock file exists in ~/.claude/ide/ (or $CLAUDE_CONFIG_DIR/ide/ if CLAUDE_CONFIG_DIR is 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, set terminal_cmd = "~/.claude/local/claude" in your config. Check which claude vs ls ~/.claude/local/claude to verify your installation type.
  • Native binary installation not working? If you used the alpha native binary installer, run claude doctor to verify installation health and use which claude to find the binary path. Set terminal_cmd = "/path/to/claude" with the detected path in your config.

Contributing

See DEVELOPMENT.md for build instructions and development guidelines. Tests can be run with mise run test.

License

MIT

Acknowledgements

  • Claude Code CLI by Anthropic
  • Inspired by analyzing the official VS Code extension
  • Built with assistance from AI (how meta!)

About

🧩 Claude Code Neovim IDE Extension

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages

  • Lua 91.4%
  • Shell 8.5%
  • Python 0.1%