From a19dd83f2ce484ebcda2d44807df0e6810be014d Mon Sep 17 00:00:00 2001 From: Arjun Mahishi Date: Sat, 2 Aug 2025 08:15:08 +0530 Subject: [PATCH] feat(terminal): add toggle to enable/disable the built-in terminal Introduce a new `enable` option in the terminal configuration to allow users to enable or disable the built-in terminal functionality entirely. Updated relevant logic to respect this setting, ensuring terminal-related features are only initialized and executed when enabled. - Added `enable` field to terminal config and default settings. - Updated terminal-related functions to check `is_enabled` before execution. - Enhanced documentation with instructions for external Claude Code usage. - Added unit tests to mock and validate `is_enabled` behavior. This change improves flexibility for users who prefer to disable the built-in terminal and use an external claude code instance. --- README.md | 28 +++++++++++++++++++ lua/claudecode/init.lua | 30 ++++++++++++++------- lua/claudecode/terminal.lua | 9 +++++++ tests/unit/claudecode_add_command_spec.lua | 1 + tests/unit/claudecode_send_command_spec.lua | 1 + tests/unit/init_spec.lua | 2 ++ tests/unit/visual_delay_timing_spec.lua | 1 + 7 files changed, 62 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index add7076..e76c1a2 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,7 @@ For deep technical details, see [ARCHITECTURE.md](./ARCHITECTURE.md). split_width_percentage = 0.30, provider = "auto", -- "auto", "snacks", "native", or custom provider table auto_close = true, + enable = true, -- Enable terminal commands (set to false when using external Claude Code) snacks_win_opts = {}, -- Opts to pass to `Snacks.terminal.open()` - see Floating Window section below }, @@ -572,11 +573,38 @@ Provides convenient Claude interaction history management and access for enhance > **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 External Claude Code + +If you prefer to run Claude Code externally (e.g., in a separate terminal), you can disable the plugin's built-in terminal commands: + +```lua +{ + "coder/claudecode.nvim", + opts = { + terminal = { + enable = false, -- Disable ClaudeCode, ClaudeCodeOpen, etc. + }, + }, +} +``` + +> **💡 Tip**: **Disable lazy loading** (`lazy = false`) for the best experience with external Claude Code. This ensures the WebSocket server starts immediately when Neovim opens, allowing instant connection from external terminals. + +Then connect your external Claude Code instance using: + +```bash +# Start claudecode.nvim first: :ClaudeCodeStart in Neovim (or auto-starts if lazy = false) +claude --ide # Connects to the running Neovim WebSocket server +``` + +This approach gives you full control over Claude Code's terminal interface while still providing all the IDE integration features. + ## 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 +- **Want to use external Claude Code?** Set `terminal = { enable = false }`, then use `claude --ide` command - **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. diff --git a/lua/claudecode/init.lua b/lua/claudecode/init.lua index d69abc9..6aa5f14 100644 --- a/lua/claudecode/init.lua +++ b/lua/claudecode/init.lua @@ -44,6 +44,7 @@ M.version = { --- @field connection_timeout number Maximum time to wait for Claude Code to connect (milliseconds). --- @field queue_timeout number Maximum time to keep @ mentions in queue (milliseconds). --- @field diff_opts { auto_close_on_accept: boolean, show_diff_stats: boolean, vertical_split: boolean, open_in_current_tab: boolean } Options for the diff provider. +--- @field enable_terminal boolean Whether to enable terminal commands (ClaudeCode, ClaudeCodeOpen, etc.). --- @type ClaudeCode.Config local default_config = { @@ -63,6 +64,7 @@ local default_config = { vertical_split = true, open_in_current_tab = false, }, + enable_terminal = true, } --- @class ClaudeCode.State @@ -324,22 +326,25 @@ function M.send_at_mention(file_path, start_line, end_line, context) -- Check if Claude Code is connected if M.is_claude_connected() then - -- Claude is connected, send immediately and ensure terminal is visible + -- Claude is connected, send immediately and ensure terminal is visible (if enabled) local success, error_msg = M._broadcast_at_mention(file_path, start_line, end_line) - if success then - local terminal = require("claudecode.terminal") + local terminal_ok, terminal = pcall(require, "claudecode.terminal") + if success and terminal_ok and terminal.is_enabled() then terminal.ensure_visible() end return success, error_msg else - -- Claude not connected, queue the mention and launch terminal + -- Claude not connected, queue the mention and launch terminal (if enabled) queue_mention(file_path, start_line, end_line) - -- Launch terminal with Claude Code - local terminal = require("claudecode.terminal") - terminal.open() - - logger.debug(context, "Queued @ mention and launched Claude Code: " .. file_path) + local terminal_ok, terminal = pcall(require, "claudecode.terminal") + if terminal_ok and terminal.is_enabled() then + -- Launch terminal with Claude Code + terminal.open() + logger.debug(context, "Queued @ mention and launched Claude Code: " .. file_path) + else + logger.debug(context, "Queued @ mention (terminal disabled): " .. file_path) + end return true, nil end @@ -990,7 +995,7 @@ function M._create_commands() }) local terminal_ok, terminal = pcall(require, "claudecode.terminal") - if terminal_ok then + if terminal_ok and terminal.is_enabled() then vim.api.nvim_create_user_command("ClaudeCode", function(opts) local current_mode = vim.fn.mode() if current_mode == "v" or current_mode == "V" or current_mode == "\22" then @@ -1028,6 +1033,11 @@ function M._create_commands() end, { desc = "Close the Claude Code terminal window", }) + elseif terminal_ok and not terminal.is_enabled() then + logger.debug( + "init", + "Terminal commands disabled via terminal.enable = false. Terminal commands (ClaudeCode, ClaudeCodeOpen, ClaudeCodeClose) not registered." + ) else logger.error( "init", diff --git a/lua/claudecode/terminal.lua b/lua/claudecode/terminal.lua index 4d4ee94..e374b94 100644 --- a/lua/claudecode/terminal.lua +++ b/lua/claudecode/terminal.lua @@ -24,6 +24,7 @@ local config = { show_native_term_exit_tip = true, terminal_cmd = nil, auto_close = true, + enable = true, -- Enable terminal commands (ClaudeCode, ClaudeCodeOpen, etc.) env = {}, -- Custom environment variables for Claude terminal snacks_win_opts = {}, } @@ -313,6 +314,8 @@ function M.setup(user_term_config, p_terminal_cmd, p_env) config[k] = v elseif k == "auto_close" and type(v) == "boolean" then config[k] = v + elseif k == "enable" and type(v) == "boolean" then + config[k] = v elseif k == "snacks_win_opts" and type(v) == "table" then config[k] = v else @@ -403,4 +406,10 @@ function M._get_managed_terminal_for_test() return nil end +--- Gets whether terminal commands are enabled +-- @return boolean True if terminal commands are enabled +function M.is_enabled() + return config.enable +end + return M diff --git a/tests/unit/claudecode_add_command_spec.lua b/tests/unit/claudecode_add_command_spec.lua index 5f98f65..b22c3a3 100644 --- a/tests/unit/claudecode_add_command_spec.lua +++ b/tests/unit/claudecode_add_command_spec.lua @@ -90,6 +90,7 @@ describe("ClaudeCodeAdd command", function() return 1 end, simple_toggle = spy.new(function() end), + is_enabled = function() return true end, } elseif mod == "claudecode.visual_commands" then return { diff --git a/tests/unit/claudecode_send_command_spec.lua b/tests/unit/claudecode_send_command_spec.lua index e8c8092..f33b300 100644 --- a/tests/unit/claudecode_send_command_spec.lua +++ b/tests/unit/claudecode_send_command_spec.lua @@ -72,6 +72,7 @@ describe("ClaudeCodeSend Command Range Functionality", function() mock_terminal = { open = spy.new(function() end), ensure_visible = spy.new(function() end), + is_enabled = function() return true end, } -- Mock server diff --git a/tests/unit/init_spec.lua b/tests/unit/init_spec.lua index a6233de..6ceffe7 100644 --- a/tests/unit/init_spec.lua +++ b/tests/unit/init_spec.lua @@ -301,6 +301,7 @@ describe("claudecode.init", function() close = spy.new(function() end), setup = spy.new(function() end), ensure_visible = spy.new(function() end), + is_enabled = function() return true end, } local original_require = _G.require @@ -475,6 +476,7 @@ describe("claudecode.init", function() focus_toggle = spy.new(function() end), open = spy.new(function() end), close = spy.new(function() end), + is_enabled = function() return true end, } -- Mock vim.ui.select to automatically select the first model diff --git a/tests/unit/visual_delay_timing_spec.lua b/tests/unit/visual_delay_timing_spec.lua index be0d1fa..8bbd720 100644 --- a/tests/unit/visual_delay_timing_spec.lua +++ b/tests/unit/visual_delay_timing_spec.lua @@ -22,6 +22,7 @@ describe("Visual Delay Timing Validation", function() get_active_terminal_bufnr = function() return nil -- No active terminal by default end, + is_enabled = function() return true end, } -- Extend the existing vim mock