Skip to content

coder/claudecode.nvim

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

Working with Diffs

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

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

You can edit Claude's suggestions before accepting them.

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'

    -- 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", or custom provider table
      auto_close = true,
      snacks_win_opts = {}, -- Opts to pass to `Snacks.terminal.open()` - see Floating Window section below
    },

    -- Diff Integration
    diff_opts = {
      auto_close_on_accept = true,
      vertical_split = true,
      open_in_current_tab = true,
    },
  },
  keys = {
    -- Your keymaps here
  },
}

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:

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.

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.

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 make 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

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Languages