Skip to content

Commit 21f984b

Browse files
authored
Merge pull request #39 from coder/feat/native-terminal-bufhidden-fix
2 parents 83061d9 + e1def67 commit 21f984b

File tree

2 files changed

+505
-25
lines changed

2 files changed

+505
-25
lines changed

lua/claudecode/terminal/native.lua

Lines changed: 112 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
--- @type TerminalProvider
55
local M = {}
66

7+
local logger = require("claudecode.logger")
8+
79
local bufnr = nil
810
local winid = nil
911
local jobid = nil
@@ -31,13 +33,12 @@ local function is_valid()
3133
if vim.api.nvim_win_get_buf(win) == bufnr then
3234
-- Found a window displaying our terminal buffer, update the tracked window ID
3335
winid = win
34-
require("claudecode.logger").debug("terminal", "Recovered terminal window ID:", win)
36+
logger.debug("terminal", "Recovered terminal window ID:", win)
3537
return true
3638
end
3739
end
38-
-- Buffer exists but no window displays it
39-
cleanup_state()
40-
return false
40+
-- Buffer exists but no window displays it - this is normal for hidden terminals
41+
return true -- Buffer is valid even though not visible
4142
end
4243

4344
-- Both buffer and window are valid
@@ -82,6 +83,8 @@ local function open_terminal(cmd_string, env_table, effective_config)
8283
on_exit = function(job_id, _, _)
8384
vim.schedule(function()
8485
if job_id == jobid then
86+
logger.debug("terminal", "Terminal process exited, cleaning up")
87+
8588
-- Ensure we are operating on the correct window and buffer before closing
8689
local current_winid_for_job = winid
8790
local current_bufnr_for_job = bufnr
@@ -135,7 +138,7 @@ local function close_terminal()
135138
-- If the job already exited, on_exit would have cleaned up.
136139
-- This direct close is for user-initiated close.
137140
vim.api.nvim_win_close(winid, true)
138-
cleanup_state() -- Ensure cleanup if on_exit doesn't fire (e.g. job already dead)
141+
cleanup_state() -- Cleanup after explicit close
139142
end
140143
end
141144

@@ -146,6 +149,78 @@ local function focus_terminal()
146149
end
147150
end
148151

152+
local function is_terminal_visible()
153+
-- Check if our terminal buffer exists and is displayed in any window
154+
if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then
155+
return false
156+
end
157+
158+
local windows = vim.api.nvim_list_wins()
159+
for _, win in ipairs(windows) do
160+
if vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_buf(win) == bufnr then
161+
-- Update our tracked window ID if we find the buffer in a different window
162+
winid = win
163+
return true
164+
end
165+
end
166+
167+
-- Buffer exists but no window displays it
168+
winid = nil
169+
return false
170+
end
171+
172+
local function hide_terminal()
173+
-- Hide the terminal window but keep the buffer and job alive
174+
if bufnr and vim.api.nvim_buf_is_valid(bufnr) and winid and vim.api.nvim_win_is_valid(winid) then
175+
-- Set buffer to hide instead of being wiped when window closes
176+
vim.api.nvim_buf_set_option(bufnr, "bufhidden", "hide")
177+
178+
-- Close the window - this preserves the buffer and job
179+
vim.api.nvim_win_close(winid, false)
180+
winid = nil -- Clear window reference
181+
182+
logger.debug("terminal", "Terminal window hidden, process preserved")
183+
end
184+
end
185+
186+
local function show_hidden_terminal(effective_config)
187+
-- Show an existing hidden terminal buffer in a new window
188+
if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then
189+
return false
190+
end
191+
192+
-- Check if it's already visible
193+
if is_terminal_visible() then
194+
focus_terminal()
195+
return true
196+
end
197+
198+
-- Create a new window for the existing buffer
199+
local width = math.floor(vim.o.columns * effective_config.split_width_percentage)
200+
local full_height = vim.o.lines
201+
local placement_modifier
202+
203+
if effective_config.split_side == "left" then
204+
placement_modifier = "topleft "
205+
else
206+
placement_modifier = "botright "
207+
end
208+
209+
vim.cmd(placement_modifier .. width .. "vsplit")
210+
local new_winid = vim.api.nvim_get_current_win()
211+
vim.api.nvim_win_set_height(new_winid, full_height)
212+
213+
-- Set the existing buffer in the new window
214+
vim.api.nvim_win_set_buf(new_winid, bufnr)
215+
winid = new_winid
216+
217+
vim.api.nvim_set_current_win(winid)
218+
vim.cmd("startinsert")
219+
220+
logger.debug("terminal", "Showed hidden terminal in new window")
221+
return true
222+
end
223+
149224
local function find_existing_claude_terminal()
150225
local buffers = vim.api.nvim_list_bufs()
151226
for _, buf in ipairs(buffers) do
@@ -158,13 +233,7 @@ local function find_existing_claude_terminal()
158233
local windows = vim.api.nvim_list_wins()
159234
for _, win in ipairs(windows) do
160235
if vim.api.nvim_win_get_buf(win) == buf then
161-
require("claudecode.logger").debug(
162-
"terminal",
163-
"Found existing Claude terminal in buffer",
164-
buf,
165-
"window",
166-
win
167-
)
236+
logger.debug("terminal", "Found existing Claude terminal in buffer", buf, "window", win)
168237
return buf, win
169238
end
170239
end
@@ -193,7 +262,7 @@ function M.open(cmd_string, env_table, effective_config)
193262
bufnr = existing_buf
194263
winid = existing_win
195264
-- Note: We can't recover the job ID easily, but it's less critical
196-
require("claudecode.logger").debug("terminal", "Recovered existing Claude terminal")
265+
logger.debug("terminal", "Recovered existing Claude terminal")
197266
focus_terminal()
198267
else
199268
if not open_terminal(cmd_string, env_table, effective_config) then
@@ -211,32 +280,50 @@ end
211280
--- @param env_table table
212281
--- @param effective_config table
213282
function M.toggle(cmd_string, env_table, effective_config)
214-
if is_valid() then
215-
local claude_term_neovim_win_id = winid
216-
local current_neovim_win_id = vim.api.nvim_get_current_win()
283+
-- Check if we have a valid terminal buffer (process running)
284+
local has_buffer = bufnr and vim.api.nvim_buf_is_valid(bufnr)
285+
local is_visible = has_buffer and is_terminal_visible()
217286

218-
if claude_term_neovim_win_id == current_neovim_win_id then
219-
close_terminal()
287+
if has_buffer then
288+
-- Terminal process exists
289+
if is_visible then
290+
-- Terminal is visible - check if we're currently in it
291+
local current_win_id = vim.api.nvim_get_current_win()
292+
if winid == current_win_id then
293+
-- We're in the terminal window, hide it (but keep process running)
294+
hide_terminal()
295+
else
296+
-- Terminal is visible but we're not in it, focus it
297+
focus_terminal()
298+
end
220299
else
221-
focus_terminal() -- This already calls startinsert
300+
-- Terminal process exists but is hidden, show it
301+
if show_hidden_terminal(effective_config) then
302+
logger.debug("terminal", "Showing hidden terminal")
303+
else
304+
logger.error("terminal", "Failed to show hidden terminal")
305+
end
222306
end
223307
else
224-
-- Check if there's an existing Claude terminal we lost track of
308+
-- No terminal process exists, check if there's an existing one we lost track of
225309
local existing_buf, existing_win = find_existing_claude_terminal()
226310
if existing_buf and existing_win then
227311
-- Recover the existing terminal
228312
bufnr = existing_buf
229313
winid = existing_win
230-
require("claudecode.logger").debug("terminal", "Recovered existing Claude terminal in toggle")
314+
logger.debug("terminal", "Recovered existing Claude terminal")
231315

232-
-- Check if we're currently in this terminal
233-
local current_neovim_win_id = vim.api.nvim_get_current_win()
234-
if existing_win == current_neovim_win_id then
235-
close_terminal()
316+
-- Check if we're currently in this recovered terminal
317+
local current_win_id = vim.api.nvim_get_current_win()
318+
if existing_win == current_win_id then
319+
-- We're in the recovered terminal, hide it
320+
hide_terminal()
236321
else
322+
-- Focus the recovered terminal
237323
focus_terminal()
238324
end
239325
else
326+
-- No existing terminal found, create a new one
240327
if not open_terminal(cmd_string, env_table, effective_config) then
241328
vim.notify("Failed to open Claude terminal using native fallback (toggle).", vim.log.levels.ERROR)
242329
end

0 commit comments

Comments
 (0)