From a373afb48ed1e0a6b9781a2cbf2a314ea15312d4 Mon Sep 17 00:00:00 2001 From: Itzamna Date: Tue, 20 May 2025 12:57:02 -0600 Subject: [PATCH 1/6] Add multiple instance per git root --- CLAUDE.md | 19 ++++++++++ lua/claude-code/config.lua | 6 ++++ lua/claude-code/init.lua | 17 +++++++-- lua/claude-code/terminal.lua | 67 ++++++++++++++++++++++++++++++------ 4 files changed, 96 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3874650..37cb73e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,6 +26,25 @@ Claude Code Plugin provides seamless integration between the Claude Code AI assi - Implementing better context synchronization - Adding buffer-specific context management +## Multi-Instance Support +The plugin supports running multiple Claude Code instances, one per git repository root: + +- Each git repository maintains its own Claude instance +- Works across multiple Neovim tabs with different projects +- Allows working on multiple projects in parallel +- Configurable via `git.multi_instance` option (defaults to `true`) +- Instances remain in their own directory context when switching between tabs +- Buffer names include the git root path for easy identification + +Example configuration to disable multi-instance mode: +```lua +require('claude-code').setup({ + git = { + multi_instance = false -- Use a single global Claude instance + } +}) +``` + ## Documentation Links - Tasks: `/home/gregg/Projects/docs-projects/neovim-ecosystem-docs/tasks/claude-code-tasks.md` - Project Status: `/home/gregg/Projects/docs-projects/neovim-ecosystem-docs/project-status.md` \ No newline at end of file diff --git a/lua/claude-code/config.lua b/lua/claude-code/config.lua index c350660..20569f3 100644 --- a/lua/claude-code/config.lua +++ b/lua/claude-code/config.lua @@ -25,6 +25,7 @@ local M = {} --- ClaudeCodeGit class for git integration configuration -- @table ClaudeCodeGit -- @field use_git_root boolean Set CWD to git root when opening Claude Code (if in git project) +-- @field multi_instance boolean Use multiple Claude instances (one per git root) --- ClaudeCodeKeymapsToggle class for toggle keymap configuration -- @table ClaudeCodeKeymapsToggle @@ -78,6 +79,7 @@ M.default_config = { -- Git integration settings git = { use_git_root = true, -- Set CWD to git root when opening Claude Code (if in git project) + multi_instance = true, -- Use multiple Claude instances (one per git root) }, -- Command settings command = 'claude', -- Command used to launch Claude Code @@ -172,6 +174,10 @@ local function validate_config(config) if type(config.git.use_git_root) ~= 'boolean' then return false, 'git.use_git_root must be a boolean' end + + if type(config.git.multi_instance) ~= 'boolean' then + return false, 'git.multi_instance must be a boolean' + end -- Validate command settings if type(config.command) ~= 'string' then diff --git a/lua/claude-code/init.lua b/lua/claude-code/init.lua index 0f64912..1b09e53 100644 --- a/lua/claude-code/init.lua +++ b/lua/claude-code/init.lua @@ -44,13 +44,25 @@ function M.force_insert_mode() terminal.force_insert_mode(M, M.config) end +--- Get the current active buffer number +--- @return number|nil bufnr Current Claude instance buffer number or nil +local function get_current_buffer_number() + -- Get current instance from the instances table + local current_instance = M.claude_code.current_instance + if current_instance then + return M.claude_code.instances[current_instance] + end + return nil +end + --- Toggle the Claude Code terminal window --- This is a public function used by commands function M.toggle() terminal.toggle(M, M.config, git) -- Set up terminal navigation keymaps after toggling - if M.claude_code.bufnr and vim.api.nvim_buf_is_valid(M.claude_code.bufnr) then + local bufnr = get_current_buffer_number() + if bufnr and vim.api.nvim_buf_is_valid(bufnr) then keymaps.setup_terminal_navigation(M, M.config) end end @@ -73,7 +85,8 @@ function M.toggle_with_variant(variant_name) terminal.toggle(M, M.config, git) -- Set up terminal navigation keymaps after toggling - if M.claude_code.bufnr and vim.api.nvim_buf_is_valid(M.claude_code.bufnr) then + local bufnr = get_current_buffer_number() + if bufnr and vim.api.nvim_buf_is_valid(bufnr) then keymaps.setup_terminal_navigation(M, M.config) end diff --git a/lua/claude-code/terminal.lua b/lua/claude-code/terminal.lua index e2f1d4f..6f835f2 100644 --- a/lua/claude-code/terminal.lua +++ b/lua/claude-code/terminal.lua @@ -8,13 +8,28 @@ local M = {} --- Terminal buffer and window management -- @table ClaudeCodeTerminal --- @field bufnr number|nil Buffer number of the Claude Code terminal +-- @field instances table Key-value store of git root to buffer number -- @field saved_updatetime number|nil Original updatetime before Claude Code was opened +-- @field current_instance string|nil Current git root path for active instance M.terminal = { - bufnr = nil, + instances = {}, saved_updatetime = nil, + current_instance = nil, } +--- Get the current git root or a fallback identifier +--- @param git table The git module +--- @return string identifier Git root path or fallback identifier +local function get_instance_identifier(git) + local git_root = git.get_git_root() + if git_root then + return git_root + else + -- Fallback to current working directory if not in a git repo + return vim.fn.getcwd() + end +end + --- Create a split window according to the specified position configuration --- @param position string Window position configuration --- @param config table Plugin configuration containing window settings @@ -49,8 +64,18 @@ end --- @param claude_code table The main plugin module --- @param config table The plugin configuration function M.force_insert_mode(claude_code, config) - local bufnr = claude_code.claude_code.bufnr - if bufnr and vim.api.nvim_buf_is_valid(bufnr) and vim.fn.bufnr '%' == bufnr then + local current_bufnr = vim.fn.bufnr('%') + + -- Check if current buffer is any of our Claude instances + local is_claude_instance = false + for _, bufnr in pairs(claude_code.claude_code.instances) do + if bufnr == current_bufnr and vim.api.nvim_buf_is_valid(bufnr) then + is_claude_instance = true + break + end + end + + if is_claude_instance then -- Only enter insert mode if we're in the terminal buffer and not already in insert mode -- and not configured to stay in normal mode if config.window.start_in_normal_mode then @@ -72,10 +97,22 @@ end --- @param config table The plugin configuration --- @param git table The git module function M.toggle(claude_code, config, git) - -- Check if Claude Code is already running - local bufnr = claude_code.claude_code.bufnr + -- Determine instance ID based on config + local instance_id + if config.git.multi_instance then + -- Use git root or current directory as instance identifier + instance_id = get_instance_identifier(git) + else + -- Use a fixed ID for single instance mode + instance_id = "global" + end + + claude_code.claude_code.current_instance = instance_id + + -- Check if this Claude Code instance is already running + local bufnr = claude_code.claude_code.instances[instance_id] if bufnr and vim.api.nvim_buf_is_valid(bufnr) then - -- Check if there's a window displaying Claude Code buffer + -- Check if there's a window displaying this Claude Code buffer local win_ids = vim.fn.win_findbuf(bufnr) if #win_ids > 0 then -- Claude Code is visible, close the window @@ -93,7 +130,7 @@ function M.toggle(claude_code, config, git) end end else - -- Claude Code is not running, start it in a new split + -- This Claude Code instance is not running, start it in a new split create_split(config.window.position, config) -- Determine if we should use the git root directory @@ -108,7 +145,15 @@ function M.toggle(claude_code, config, git) vim.cmd(cmd) vim.cmd 'setlocal bufhidden=hide' - vim.cmd 'file claude-code' + + -- Create a unique buffer name (or a standard one in single instance mode) + local buffer_name + if config.git.multi_instance then + buffer_name = 'claude-code-' .. instance_id:gsub('[/\\\\]', '-') + else + buffer_name = 'claude-code' + end + vim.cmd('file ' .. buffer_name) if config.window.hide_numbers then vim.cmd 'setlocal nonumber norelativenumber' @@ -118,8 +163,8 @@ function M.toggle(claude_code, config, git) vim.cmd 'setlocal signcolumn=no' end - -- Store buffer number for future reference - claude_code.claude_code.bufnr = vim.fn.bufnr '%' + -- Store buffer number for this instance + claude_code.claude_code.instances[instance_id] = vim.fn.bufnr('%') -- Automatically enter insert mode in terminal unless configured to start in normal mode if config.window.enter_insert and not config.window.start_in_normal_mode then From 30d8be70a03773ef8760ac845610187a960e67e3 Mon Sep 17 00:00:00 2001 From: Itzamna Date: Wed, 21 May 2025 18:22:09 -0600 Subject: [PATCH 2/6] Fix CodeRabbit review comments - Remove whitespace-only lines throughout codebase - Add nil-safe buffer validity checks - Add guard for instances table access - Add buffer cleanup logic to prune invalid entries --- lua/claude-code/config.lua | 2 +- lua/claude-code/init.lua | 2 +- lua/claude-code/terminal.lua | 19 +++++++++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lua/claude-code/config.lua b/lua/claude-code/config.lua index 20569f3..ab1d618 100644 --- a/lua/claude-code/config.lua +++ b/lua/claude-code/config.lua @@ -174,7 +174,7 @@ local function validate_config(config) if type(config.git.use_git_root) ~= 'boolean' then return false, 'git.use_git_root must be a boolean' end - + if type(config.git.multi_instance) ~= 'boolean' then return false, 'git.multi_instance must be a boolean' end diff --git a/lua/claude-code/init.lua b/lua/claude-code/init.lua index 1b09e53..56dee80 100644 --- a/lua/claude-code/init.lua +++ b/lua/claude-code/init.lua @@ -49,7 +49,7 @@ end local function get_current_buffer_number() -- Get current instance from the instances table local current_instance = M.claude_code.current_instance - if current_instance then + if current_instance and type(M.claude_code.instances) == 'table' then return M.claude_code.instances[current_instance] end return nil diff --git a/lua/claude-code/terminal.lua b/lua/claude-code/terminal.lua index 6f835f2..c643917 100644 --- a/lua/claude-code/terminal.lua +++ b/lua/claude-code/terminal.lua @@ -65,16 +65,19 @@ end --- @param config table The plugin configuration function M.force_insert_mode(claude_code, config) local current_bufnr = vim.fn.bufnr('%') - + -- Check if current buffer is any of our Claude instances local is_claude_instance = false for _, bufnr in pairs(claude_code.claude_code.instances) do - if bufnr == current_bufnr and vim.api.nvim_buf_is_valid(bufnr) then + if bufnr + and bufnr == current_bufnr + and vim.api.nvim_buf_is_valid(bufnr) + then is_claude_instance = true break end end - + if is_claude_instance then -- Only enter insert mode if we're in the terminal buffer and not already in insert mode -- and not configured to stay in normal mode @@ -106,9 +109,9 @@ function M.toggle(claude_code, config, git) -- Use a fixed ID for single instance mode instance_id = "global" end - + claude_code.claude_code.current_instance = instance_id - + -- Check if this Claude Code instance is already running local bufnr = claude_code.claude_code.instances[instance_id] if bufnr and vim.api.nvim_buf_is_valid(bufnr) then @@ -130,6 +133,10 @@ function M.toggle(claude_code, config, git) end end else + -- Prune invalid buffer entries + if bufnr and not vim.api.nvim_buf_is_valid(bufnr) then + claude_code.claude_code.instances[instance_id] = nil + end -- This Claude Code instance is not running, start it in a new split create_split(config.window.position, config) @@ -145,7 +152,7 @@ function M.toggle(claude_code, config, git) vim.cmd(cmd) vim.cmd 'setlocal bufhidden=hide' - + -- Create a unique buffer name (or a standard one in single instance mode) local buffer_name if config.git.multi_instance then From a9efdf1f5f6b08f323d2aa1cb3c25a62d3543a9d Mon Sep 17 00:00:00 2001 From: Itzamna Date: Wed, 21 May 2025 18:33:38 -0600 Subject: [PATCH 3/6] Respect git.use_git_root config for instance ID derivation When multi_instance is enabled, now checks use_git_root config: - If true: uses git root as instance identifier - If false: uses current working directory as instance identifier This ensures instance ID respects user configuration preferences. --- lua/claude-code/terminal.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lua/claude-code/terminal.lua b/lua/claude-code/terminal.lua index c643917..5f3eb5f 100644 --- a/lua/claude-code/terminal.lua +++ b/lua/claude-code/terminal.lua @@ -103,8 +103,11 @@ function M.toggle(claude_code, config, git) -- Determine instance ID based on config local instance_id if config.git.multi_instance then - -- Use git root or current directory as instance identifier - instance_id = get_instance_identifier(git) + if config.git.use_git_root then + instance_id = get_instance_identifier(git) + else + instance_id = vim.fn.getcwd() + end else -- Use a fixed ID for single instance mode instance_id = "global" From d5e3c62ff6fa7655d7a5c6ba09b15cbd88048238 Mon Sep 17 00:00:00 2001 From: Itzamna Date: Wed, 21 May 2025 18:45:37 -0600 Subject: [PATCH 4/6] Complete multi-instance migration fixes - Sanitize buffer names to avoid special characters that break :buffer commands - Update keymaps.lua to use instances table instead of single bufnr - Update file_refresh.lua to use instances table instead of single bufnr Note: Tests in terminal_spec.lua still need updating to work with instances table --- lua/claude-code/file_refresh.lua | 3 ++- lua/claude-code/keymaps.lua | 4 +++- lua/claude-code/terminal.lua | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lua/claude-code/file_refresh.lua b/lua/claude-code/file_refresh.lua index 5dd584b..9bbb085 100644 --- a/lua/claude-code/file_refresh.lua +++ b/lua/claude-code/file_refresh.lua @@ -57,7 +57,8 @@ function M.setup(claude_code, config) config.refresh.timer_interval, vim.schedule_wrap(function() -- Only check time if there's an active Claude Code terminal - local bufnr = claude_code.claude_code.bufnr + local current_instance = claude_code.claude_code.current_instance + local bufnr = current_instance and claude_code.claude_code.instances[current_instance] if bufnr and vim.api.nvim_buf_is_valid(bufnr) and #vim.fn.win_findbuf(bufnr) > 0 then vim.cmd 'silent! checktime' end diff --git a/lua/claude-code/keymaps.lua b/lua/claude-code/keymaps.lua index f47586f..8749f7f 100644 --- a/lua/claude-code/keymaps.lua +++ b/lua/claude-code/keymaps.lua @@ -89,7 +89,9 @@ end --- @param claude_code table The main plugin module --- @param config table The plugin configuration function M.setup_terminal_navigation(claude_code, config) - local buf = claude_code.claude_code.bufnr + -- Get current active Claude instance buffer + local current_instance = claude_code.claude_code.current_instance + local buf = current_instance and claude_code.claude_code.instances[current_instance] if buf and vim.api.nvim_buf_is_valid(buf) then -- Create autocommand to enter insert mode when the terminal window gets focus local augroup = vim.api.nvim_create_augroup('ClaudeCodeTerminalFocus', { clear = true }) diff --git a/lua/claude-code/terminal.lua b/lua/claude-code/terminal.lua index 5f3eb5f..6adaf16 100644 --- a/lua/claude-code/terminal.lua +++ b/lua/claude-code/terminal.lua @@ -159,7 +159,7 @@ function M.toggle(claude_code, config, git) -- Create a unique buffer name (or a standard one in single instance mode) local buffer_name if config.git.multi_instance then - buffer_name = 'claude-code-' .. instance_id:gsub('[/\\\\]', '-') + buffer_name = 'claude-code-' .. instance_id:gsub('[^%w%-_]', '-') else buffer_name = 'claude-code' end From 5dd219695cc6ccf29a2f1cf108c0790176b7403e Mon Sep 17 00:00:00 2001 From: Itzamna Date: Wed, 21 May 2025 19:03:21 -0600 Subject: [PATCH 5/6] Update terminal tests for multi-instance support - Completely rewrite terminal_spec.lua to test instances table instead of single bufnr - Add comprehensive tests for multi-instance functionality: - Instance creation and management - Git root vs current directory as instance identifier - Buffer name sanitization for special characters - Invalid buffer cleanup from instances table - Add tests for single-instance mode using 'global' key - Update force_insert_mode tests to work with instances table - All tests validate both single and multi-instance behavior --- tests/spec/terminal_spec.lua | 227 ++++++++++++++++++++++++++--------- 1 file changed, 171 insertions(+), 56 deletions(-) diff --git a/tests/spec/terminal_spec.lua b/tests/spec/terminal_spec.lua index 5909fdd..330e944 100644 --- a/tests/spec/terminal_spec.lua +++ b/tests/spec/terminal_spec.lua @@ -48,6 +48,11 @@ describe('terminal module', function() return 42 end + -- Mock vim.fn.getcwd + _G.vim.fn.getcwd = function() + return '/test/current/dir' + end + -- Mock vim.api.nvim_win_close _G.vim.api.nvim_win_close = function(win_id, force) -- Remove the window from win_ids @@ -78,12 +83,14 @@ describe('terminal module', function() }, git = { use_git_root = true, + multi_instance = true, }, } claude_code = { claude_code = { - bufnr = nil, + instances = {}, + current_instance = nil, saved_updatetime = nil, }, } @@ -95,10 +102,11 @@ describe('terminal module', function() } end) - describe('toggle', function() - it('should open terminal window when Claude Code is not running', function() - -- Claude Code is not running (bufnr is nil) - claude_code.claude_code.bufnr = nil + describe('toggle with multi-instance enabled', function() + it('should create new instance when none exists', function() + -- No instances exist + claude_code.claude_code.instances = {} + claude_code.claude_code.current_instance = nil -- Call toggle terminal.toggle(claude_code, config, git) @@ -122,36 +130,43 @@ describe('terminal module', function() assert.is_true(resize_cmd_found, 'Resize command should be called') assert.is_true(terminal_cmd_found, 'Terminal command should be called') - -- Buffer number should be set - assert.is_not_nil(claude_code.claude_code.bufnr, 'Claude Code buffer number should be set') + -- Current instance should be set + assert.is_not_nil(claude_code.claude_code.current_instance, 'Current instance should be set') + + -- Instance should be created in instances table + local current_instance = claude_code.claude_code.current_instance + assert.is_not_nil(claude_code.claude_code.instances[current_instance], 'Instance buffer should be set') end) - it('should use git root when configured', function() - -- Claude Code is not running (bufnr is nil) - claude_code.claude_code.bufnr = nil - - -- Set git config to use root + it('should use git root as instance identifier when use_git_root is true', function() + -- Configure to use git root config.git.use_git_root = true + config.git.multi_instance = true -- Call toggle terminal.toggle(claude_code, config, git) - -- Check that git root was used in terminal command - local git_root_cmd_found = false + -- Current instance should be git root + assert.are.equal('/test/git/root', claude_code.claude_code.current_instance) + end) - for _, cmd in ipairs(vim_cmd_calls) do - if cmd:match('terminal pushd /test/git/root && ' .. config.command .. ' && popd') then - git_root_cmd_found = true - break - end - end + it('should use current directory as instance identifier when use_git_root is false', function() + -- Configure to use current directory + config.git.use_git_root = false + config.git.multi_instance = true - assert.is_true(git_root_cmd_found, 'Terminal command should include git root') + -- Call toggle + terminal.toggle(claude_code, config, git) + + -- Current instance should be current directory + assert.are.equal('/test/current/dir', claude_code.claude_code.current_instance) end) - it('should close window when Claude Code is visible', function() - -- Claude Code is running and visible - claude_code.claude_code.bufnr = 42 + it('should close window when instance is visible', function() + -- Setup existing instance + local instance_id = '/test/git/root' + claude_code.claude_code.instances[instance_id] = 42 + claude_code.claude_code.current_instance = instance_id win_ids = { 100, 101 } -- Windows displaying the buffer -- Create a function to clear the win_ids array @@ -168,9 +183,11 @@ describe('terminal module', function() assert.are.equal(0, #win_ids, 'Windows should be closed') end) - it('should reopen window when Claude Code exists but is hidden', function() - -- Claude Code is running but not visible - claude_code.claude_code.bufnr = 42 + it('should reopen window when instance exists but is hidden', function() + -- Setup existing instance that's not visible + local instance_id = '/test/git/root' + claude_code.claude_code.instances[instance_id] = 42 + claude_code.claude_code.current_instance = instance_id win_ids = {} -- No windows displaying the buffer -- Call toggle @@ -195,13 +212,98 @@ describe('terminal module', function() assert.is_true(resize_cmd_found, 'Resize command should be called') assert.is_true(buffer_cmd_found, 'Buffer command should be called with correct buffer number') end) + + it('should create buffer with sanitized name for multi-instance', function() + -- Use an instance ID with special characters + config.git.use_git_root = false + config.git.multi_instance = true + + -- Mock getcwd to return path with special characters + _G.vim.fn.getcwd = function() + return '/test/path with spaces/and-symbols!' + end + + -- Call toggle + terminal.toggle(claude_code, config, git) + + -- Check that file command was called with sanitized name + local file_cmd_found = false + for _, cmd in ipairs(vim_cmd_calls) do + if cmd:match('file claude%-code%-.*') then + file_cmd_found = true + -- Ensure no special characters remain + assert.is_false(cmd:match('[^%w%-_]'), 'Buffer name should not contain special characters') + break + end + end + + assert.is_true(file_cmd_found, 'File command should be called with sanitized buffer name') + end) + + it('should clean up invalid buffers from instances table', function() + -- Setup invalid buffer in instances + local instance_id = '/test/git/root' + claude_code.claude_code.instances[instance_id] = 999 -- Invalid buffer number + + -- Mock nvim_buf_is_valid to return false for this buffer + _G.vim.api.nvim_buf_is_valid = function(bufnr) + return bufnr ~= 999 + end + + -- Call toggle + terminal.toggle(claude_code, config, git) + + -- Invalid buffer should be cleaned up + assert.is_nil(claude_code.claude_code.instances[instance_id], 'Invalid buffer should be cleaned up') + end) + end) + + describe('toggle with multi-instance disabled', function() + before_each(function() + config.git.multi_instance = false + end) + + it('should use global instance when multi-instance is disabled', function() + -- Call toggle + terminal.toggle(claude_code, config, git) + + -- Current instance should be "global" + assert.are.equal('global', claude_code.claude_code.current_instance) + end) + + it('should create single global instance', function() + -- Call toggle + terminal.toggle(claude_code, config, git) + + -- Check that global instance is created + assert.is_not_nil(claude_code.claude_code.instances['global'], 'Global instance should be created') + end) + end) + + describe('git root usage', function() + it('should use git root when configured', function() + -- Set git config to use root + config.git.use_git_root = true + + -- Call toggle + terminal.toggle(claude_code, config, git) + + -- Check that git root was used in terminal command + local git_root_cmd_found = false + + for _, cmd in ipairs(vim_cmd_calls) do + if cmd:match('terminal pushd /test/git/root && ' .. config.command .. ' && popd') then + git_root_cmd_found = true + break + end + end + + assert.is_true(git_root_cmd_found, 'Terminal command should include git root') + end) end) describe('start_in_normal_mode option', function() it('should not enter insert mode when start_in_normal_mode is true', function() - -- Claude Code is not running (bufnr is nil) - claude_code.claude_code.bufnr = nil - -- Set start_in_normal_mode to true config.window.start_in_normal_mode = true @@ -224,9 +326,6 @@ describe('terminal module', function() end) it('should enter insert mode when start_in_normal_mode is false', function() - -- Claude Code is not running (bufnr is nil) - claude_code.claude_code.bufnr = nil - -- Set start_in_normal_mode to false config.window.start_in_normal_mode = false @@ -251,19 +350,23 @@ describe('terminal module', function() describe('force_insert_mode', function() it('should check insert mode conditions in terminal buffer', function() + -- Setup mock with instances table + local mock_claude_code = { + claude_code = { + instances = { + ['/test/instance'] = 1, + }, + current_instance = '/test/instance', + }, + } + local mock_config = { + window = { + start_in_normal_mode = false, + }, + } + -- For this test, we'll just verify that the function can be called without error local success, _ = pcall(function() - -- Setup minimal mock - local mock_claude_code = { - claude_code = { - bufnr = 1, - }, - } - local mock_config = { - window = { - start_in_normal_mode = false, - }, - } terminal.force_insert_mode(mock_claude_code, mock_config) end) @@ -271,23 +374,35 @@ describe('terminal module', function() end) it('should handle non-terminal buffers correctly', function() + -- Setup mock with instances table but different current buffer + local mock_claude_code = { + claude_code = { + instances = { + ['/test/instance'] = 2, + }, + current_instance = '/test/instance', + }, + } + local mock_config = { + window = { + start_in_normal_mode = false, + }, + } + + -- Mock bufnr to return different buffer + _G.vim.fn.bufnr = function(pattern) + if pattern == '%' then + return 1 -- Different from instances buffer + end + return 1 + end + -- For this test, we'll just verify that the function can be called without error local success, _ = pcall(function() - -- Setup minimal mock that's different from terminal buffer - local mock_claude_code = { - claude_code = { - bufnr = 2, - }, - } - local mock_config = { - window = { - start_in_normal_mode = false, - }, - } terminal.force_insert_mode(mock_claude_code, mock_config) end) assert.is_true(success, 'Force insert mode function should run without error') end) end) -end) +end) \ No newline at end of file From 083682e9fbce39c9d6978a7a70ee0faac21a3c38 Mon Sep 17 00:00:00 2001 From: Itzamna Date: Wed, 21 May 2025 19:34:10 -0600 Subject: [PATCH 6/6] Address latest CodeRabbit review comments - Fix test assertion: use assert.is_nil instead of assert.is_false for pattern matching - Fix autocommand collision: use unique group names per buffer instance - Remove trailing whitespace from test file These changes ensure multi-instance autocommands don't interfere with each other and fix the test assertion logic for buffer name sanitization validation. --- lua/claude-code/keymaps.lua | 2 +- tests/spec/terminal_spec.lua | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/claude-code/keymaps.lua b/lua/claude-code/keymaps.lua index 8749f7f..5441bd1 100644 --- a/lua/claude-code/keymaps.lua +++ b/lua/claude-code/keymaps.lua @@ -94,7 +94,7 @@ function M.setup_terminal_navigation(claude_code, config) local buf = current_instance and claude_code.claude_code.instances[current_instance] if buf and vim.api.nvim_buf_is_valid(buf) then -- Create autocommand to enter insert mode when the terminal window gets focus - local augroup = vim.api.nvim_create_augroup('ClaudeCodeTerminalFocus', { clear = true }) + local augroup = vim.api.nvim_create_augroup('ClaudeCodeTerminalFocus_' .. buf, { clear = true }) -- Set up multiple events for more reliable focus detection vim.api.nvim_create_autocmd( diff --git a/tests/spec/terminal_spec.lua b/tests/spec/terminal_spec.lua index 330e944..d861c4a 100644 --- a/tests/spec/terminal_spec.lua +++ b/tests/spec/terminal_spec.lua @@ -132,7 +132,7 @@ describe('terminal module', function() -- Current instance should be set assert.is_not_nil(claude_code.claude_code.current_instance, 'Current instance should be set') - + -- Instance should be created in instances table local current_instance = claude_code.claude_code.current_instance assert.is_not_nil(claude_code.claude_code.instances[current_instance], 'Instance buffer should be set') @@ -217,7 +217,7 @@ describe('terminal module', function() -- Use an instance ID with special characters config.git.use_git_root = false config.git.multi_instance = true - + -- Mock getcwd to return path with special characters _G.vim.fn.getcwd = function() return '/test/path with spaces/and-symbols!' @@ -232,7 +232,7 @@ describe('terminal module', function() if cmd:match('file claude%-code%-.*') then file_cmd_found = true -- Ensure no special characters remain - assert.is_false(cmd:match('[^%w%-_]'), 'Buffer name should not contain special characters') + assert.is_nil(cmd:match('[^%w%-_]'), 'Buffer name should not contain special characters') break end end @@ -244,7 +244,7 @@ describe('terminal module', function() -- Setup invalid buffer in instances local instance_id = '/test/git/root' claude_code.claude_code.instances[instance_id] = 999 -- Invalid buffer number - + -- Mock nvim_buf_is_valid to return false for this buffer _G.vim.api.nvim_buf_is_valid = function(bufnr) return bufnr ~= 999