Skip to content

Commit ca9251c

Browse files
committed
fix(snacks): terminate opencode process on VimLeavePre
Track the terminal's PID at toggle/start time and use shell 'kill' to terminate it during VimLeavePre. This is more reliable than jobstop() because snacks.terminal cleanup can invalidate the job before our stop() is called.
1 parent 0571a56 commit ca9251c

3 files changed

Lines changed: 49 additions & 3 deletions

File tree

lua/opencode/provider/snacks.lua

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
---@class opencode.provider.Snacks : opencode.Provider
55
---
66
---@field opts snacks.terminal.Opts
7+
---@field private _job_id number|nil
8+
---@field private _pid number|nil
79
local Snacks = {}
810
Snacks.__index = Snacks
911
Snacks.name = "snacks"
@@ -15,6 +17,8 @@ Snacks.name = "snacks"
1517
function Snacks.new(opts)
1618
local self = setmetatable({}, Snacks)
1719
self.opts = opts or {}
20+
self._job_id = nil
21+
self._pid = nil
1822
return self
1923
end
2024

@@ -44,19 +48,55 @@ end
4448

4549
function Snacks:toggle()
4650
require("snacks.terminal").toggle(self.cmd, self.opts)
51+
-- Capture the job ID and PID after terminal opens (may be a new terminal)
52+
vim.defer_fn(function()
53+
local win = self:get()
54+
if win and win.buf and vim.api.nvim_buf_is_valid(win.buf) then
55+
self._job_id = vim.b[win.buf].terminal_job_id
56+
if self._job_id then
57+
pcall(function()
58+
self._pid = vim.fn.jobpid(self._job_id)
59+
end)
60+
end
61+
end
62+
end, 100)
4763
end
4864

4965
function Snacks:start()
5066
if not self:get() then
5167
require("snacks.terminal").open(self.cmd, self.opts)
68+
-- Capture the job ID and PID after terminal opens
69+
vim.defer_fn(function()
70+
local win = self:get()
71+
if win and win.buf and vim.api.nvim_buf_is_valid(win.buf) then
72+
self._job_id = vim.b[win.buf].terminal_job_id
73+
if self._job_id then
74+
pcall(function()
75+
self._pid = vim.fn.jobpid(self._job_id)
76+
end)
77+
end
78+
end
79+
end, 100)
5280
end
5381
end
5482

5583
function Snacks:stop()
84+
-- Kill via PID using shell kill (most reliable during VimLeavePre,
85+
-- as vim.uv.kill and jobstop may not work when Neovim is shutting down)
86+
if self._pid then
87+
vim.fn.system("kill -TERM " .. self._pid .. " 2>/dev/null")
88+
self._pid = nil
89+
end
90+
91+
-- Also try jobstop as a fallback
92+
if self._job_id then
93+
pcall(vim.fn.jobstop, self._job_id)
94+
self._job_id = nil
95+
end
96+
97+
-- Close the window via snacks
5698
local win = self:get()
5799
if win then
58-
-- TODO: Stop the job first so we don't get error exit code.
59-
-- Not sure how to get the job ID from snacks API though.
60100
win:close()
61101
end
62102
end

lua/opencode/provider/terminal.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ end
8888

8989
---Close the window, delete the buffer.
9090
function Terminal:stop()
91+
if self.bufnr ~= nil and vim.api.nvim_buf_is_valid(self.bufnr) then
92+
local job_id = vim.b[self.bufnr].terminal_job_id
93+
if job_id then
94+
vim.fn.jobstop(job_id)
95+
end
96+
end
9197
if self.winid ~= nil and vim.api.nvim_win_is_valid(self.winid) then
9298
vim.api.nvim_win_close(self.winid, true)
9399
self.winid = nil

plugin/provider.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
vim.api.nvim_create_autocmd("VimLeave", {
1+
vim.api.nvim_create_autocmd("VimLeavePre", {
22
group = vim.api.nvim_create_augroup("OpencodeProvider", { clear = true }),
33
pattern = "*",
44
callback = function()

0 commit comments

Comments
 (0)