Skip to content

Commit d1e6a5c

Browse files
committed
feat: add auto-shutdown on Neovim exit
Change-Id: Ie9f85fd800bbc86c7b1f0905b947fa758d6bb70c Signed-off-by: Thomas Kosiewski <[email protected]>
1 parent 482b4b0 commit d1e6a5c

File tree

4 files changed

+144
-0
lines changed

4 files changed

+144
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- Selection tracking for editor context
1616
- MCP tool implementations
1717
- Basic commands (:ClaudeCodeStart, :ClaudeCodeStop, :ClaudeCodeStatus, :ClaudeCodeSend)
18+
- Automatic shutdown and cleanup on Neovim exit
1819
- Testing framework with Busted
1920
- Development environment with Nix flakes
2021

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ A Neovim plugin that integrates with Claude Code CLI to provide a seamless AI co
1313
- 🛠️ Integration with Neovim's buffer and window management
1414
- 📝 Support for file operations and diagnostics
1515
- 🖥️ Terminal integration for launching Claude with proper environment
16+
- 🔒 Automatic cleanup on exit - server shutdown and lockfile removal
1617

1718
## Requirements
1819

lua/claudecode/init.lua

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ function M.setup(opts)
6868
-- Set up commands
6969
M._create_commands()
7070

71+
-- Set up auto-shutdown on Neovim exit
72+
vim.api.nvim_create_autocmd("VimLeavePre", {
73+
group = vim.api.nvim_create_augroup("ClaudeCodeShutdown", { clear = true }),
74+
callback = function()
75+
if M.state.server then
76+
M.stop()
77+
end
78+
end,
79+
desc = "Automatically stop Claude Code integration when exiting Neovim",
80+
})
81+
7182
M.state.initialized = true
7283
return M
7384
end

tests/unit/init_spec.lua

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
describe("claudecode.init", function()
2+
local mock_api = {
3+
nvim_create_autocmd = function() end,
4+
nvim_create_augroup = function()
5+
return 1
6+
end,
7+
}
8+
9+
local mock_server = {
10+
start = function()
11+
return true, 12345
12+
end,
13+
stop = function()
14+
return true
15+
end,
16+
}
17+
18+
local mock_lockfile = {
19+
create = function()
20+
return true, "/mock/path"
21+
end,
22+
remove = function()
23+
return true
24+
end,
25+
}
26+
27+
before_each(function()
28+
-- Save original modules
29+
_G._saved_vim = _G.vim
30+
_G._saved_require = _G.require
31+
32+
-- Set up mocks
33+
_G.vim = {
34+
deepcopy = function(t)
35+
return vim.deepcopy(t)
36+
end,
37+
tbl_deep_extend = function(_, default, override)
38+
return vim.tbl_deep_extend("force", default, override)
39+
end,
40+
notify = function() end,
41+
api = mock_api,
42+
fn = {
43+
getpid = function()
44+
return 123
45+
end,
46+
expand = function()
47+
return "/mock/path"
48+
end,
49+
},
50+
log = {
51+
levels = {
52+
INFO = 2,
53+
WARN = 3,
54+
ERROR = 4,
55+
},
56+
},
57+
}
58+
59+
-- Mock require function
60+
_G.require = function(mod)
61+
if mod == "claudecode.server" then
62+
return mock_server
63+
elseif mod == "claudecode.lockfile" then
64+
return mock_lockfile
65+
elseif mod == "claudecode.selection" then
66+
return {
67+
enable = function() end,
68+
disable = function() end,
69+
}
70+
else
71+
return _G._saved_require(mod)
72+
end
73+
end
74+
75+
-- Spy on functions
76+
spy.on(mock_api, "nvim_create_autocmd")
77+
spy.on(mock_api, "nvim_create_augroup")
78+
spy.on(mock_server, "stop")
79+
spy.on(mock_lockfile, "remove")
80+
end)
81+
82+
after_each(function()
83+
-- Restore original modules
84+
_G.vim = _G._saved_vim
85+
_G.require = _G._saved_require
86+
end)
87+
88+
describe("setup", function()
89+
it("should register VimLeavePre autocmd for auto-shutdown", function()
90+
local claudecode = require("claudecode")
91+
claudecode.setup()
92+
93+
assert.spy(mock_api.nvim_create_augroup).was_called(1)
94+
assert.spy(mock_api.nvim_create_autocmd).was_called(1)
95+
assert.spy(mock_api.nvim_create_autocmd).was_called_with("VimLeavePre", match.is_table())
96+
end)
97+
end)
98+
99+
describe("auto-shutdown", function()
100+
it("should stop the server and remove lockfile when Neovim exits", function()
101+
local claudecode = require("claudecode")
102+
claudecode.setup()
103+
claudecode.start()
104+
105+
-- Get the callback function from the autocmd call
106+
local callback_fn = mock_api.nvim_create_autocmd.calls[1].vals[2].callback
107+
108+
-- Call the callback function to simulate VimLeavePre event
109+
callback_fn()
110+
111+
-- Verify that stop was called
112+
assert.spy(mock_server.stop).was_called(1)
113+
assert.spy(mock_lockfile.remove).was_called(1)
114+
end)
115+
116+
it("should do nothing if the server is not running", function()
117+
local claudecode = require("claudecode")
118+
claudecode.setup()
119+
120+
-- Get the callback function from the autocmd call
121+
local callback_fn = mock_api.nvim_create_autocmd.calls[1].vals[2].callback
122+
123+
-- Call the callback function to simulate VimLeavePre event
124+
callback_fn()
125+
126+
-- Verify that stop was not called
127+
assert.spy(mock_server.stop).was_not_called()
128+
assert.spy(mock_lockfile.remove).was_not_called()
129+
end)
130+
end)
131+
end)

0 commit comments

Comments
 (0)