diff --git a/lua/claudecode/init.lua b/lua/claudecode/init.lua index c4b7744e..0393d172 100644 --- a/lua/claudecode/init.lua +++ b/lua/claudecode/init.lua @@ -390,12 +390,22 @@ end ---Start the Claude Code integration ---@param show_startup_notification? boolean Whether to show a notification upon successful startup (defaults to true) +---@param force_new_terminal? boolean If true, creates a new terminal instance even if server is running ---@return boolean success Whether the operation was successful ---@return number|string port_or_error The WebSocket port if successful, or error message if failed -function M.start(show_startup_notification) +function M.start(show_startup_notification, force_new_terminal) if show_startup_notification == nil then show_startup_notification = true end + + -- If server is already running and we're forcing a new terminal, just create it + if M.state.server and force_new_terminal then + logger.info("init", "Creating new Claude Code terminal on existing port " .. tostring(M.state.port)) + local terminal = require("claudecode.terminal") + terminal.create_new_instance() + return true, M.state.port + end + if M.state.server then local msg = "Claude Code integration is already running on port " .. tostring(M.state.port) logger.warn("init", msg) @@ -525,10 +535,11 @@ end ---Set up user commands ---@private function M._create_commands() - vim.api.nvim_create_user_command("ClaudeCodeStart", function() - M.start() + vim.api.nvim_create_user_command("ClaudeCodeStart", function(opts) + M.start(nil, opts.bang) end, { - desc = "Start Claude Code integration", + bang = true, + desc = "Start Claude Code integration (use ! to create multiple instances)", }) vim.api.nvim_create_user_command("ClaudeCodeStop", function() diff --git a/lua/claudecode/terminal.lua b/lua/claudecode/terminal.lua index fae0b30f..73c16d04 100644 --- a/lua/claudecode/terminal.lua +++ b/lua/claudecode/terminal.lua @@ -569,4 +569,21 @@ function M._get_managed_terminal_for_test() return nil end +---Creates a new Claude Code terminal instance. +---This always creates a new terminal, regardless of existing ones. +---@param opts_override table? Overrides for terminal appearance (split_side, split_width_percentage). +---@param cmd_args string? Arguments to append to the claude command. +function M.create_new_instance(opts_override, cmd_args) + local effective_config = build_config(opts_override) + local cmd_string, claude_env_table = get_claude_command_and_env(cmd_args) + + local provider = get_provider() + if provider.create_new_instance then + provider.create_new_instance(cmd_string, claude_env_table, effective_config) + else + -- Fallback: just call open, which may reuse existing terminal + provider.open(cmd_string, claude_env_table, effective_config) + end +end + return M diff --git a/lua/claudecode/terminal/native.lua b/lua/claudecode/terminal/native.lua index 7cd24dd5..c9f0e441 100644 --- a/lua/claudecode/terminal/native.lua +++ b/lua/claudecode/terminal/native.lua @@ -435,5 +435,33 @@ function M.is_available() return true -- Native provider is always available end +---Creates a new terminal instance, ignoring any existing terminals +---@param cmd_string string +---@param env_table table +---@param effective_config table +function M.create_new_instance(cmd_string, env_table, effective_config) + -- Temporarily save the current state + local old_bufnr = bufnr + local old_winid = winid + local old_jobid = jobid + + -- Clear state to force creation of new terminal + bufnr = nil + winid = nil + jobid = nil + + -- Create new terminal + local success = open_terminal(cmd_string, env_table, effective_config, true) + + -- If creation failed, restore old state + if not success then + bufnr = old_bufnr + winid = old_winid + jobid = old_jobid + end + + return success +end + --- @type ClaudeCodeTerminalProvider return M diff --git a/lua/claudecode/terminal/snacks.lua b/lua/claudecode/terminal/snacks.lua index 2b4c7c98..1e3ea766 100644 --- a/lua/claudecode/terminal/snacks.lua +++ b/lua/claudecode/terminal/snacks.lua @@ -272,5 +272,36 @@ function M._get_terminal_for_test() return terminal end +---Creates a new terminal instance, ignoring any existing terminals +---@param cmd_string string +---@param env_table table +---@param config table +function M.create_new_instance(cmd_string, env_table, config) + if not is_available() then + vim.notify("Snacks.nvim terminal provider selected but Snacks.terminal not available.", vim.log.levels.ERROR) + return + end + + -- Save existing terminal + local old_terminal = terminal + + -- Clear terminal state to force creation + terminal = nil + + -- Create new terminal (M.open will create a new one since terminal is nil) + local opts = build_opts(config, env_table, true) + local term_instance = Snacks.terminal.open(cmd_string, opts) + + if term_instance and term_instance:buf_valid() then + setup_terminal_events(term_instance, config) + -- Don't set terminal = term_instance, keep it nil or use a separate tracking if needed + -- This allows the new terminal to exist independently + else + terminal = old_terminal -- Restore if creation failed + local logger = require("claudecode.logger") + logger.error("terminal", "Failed to create new terminal instance") + end +end + ---@type ClaudeCodeTerminalProvider return M