4
4
--- @type TerminalProvider
5
5
local M = {}
6
6
7
+ local logger = require (" claudecode.logger" )
8
+
7
9
local bufnr = nil
8
10
local winid = nil
9
11
local jobid = nil
@@ -31,13 +33,12 @@ local function is_valid()
31
33
if vim .api .nvim_win_get_buf (win ) == bufnr then
32
34
-- Found a window displaying our terminal buffer, update the tracked window ID
33
35
winid = win
34
- require ( " claudecode. logger" ) .debug (" terminal" , " Recovered terminal window ID:" , win )
36
+ logger .debug (" terminal" , " Recovered terminal window ID:" , win )
35
37
return true
36
38
end
37
39
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
41
42
end
42
43
43
44
-- Both buffer and window are valid
@@ -82,6 +83,8 @@ local function open_terminal(cmd_string, env_table, effective_config)
82
83
on_exit = function (job_id , _ , _ )
83
84
vim .schedule (function ()
84
85
if job_id == jobid then
86
+ logger .debug (" terminal" , " Terminal process exited, cleaning up" )
87
+
85
88
-- Ensure we are operating on the correct window and buffer before closing
86
89
local current_winid_for_job = winid
87
90
local current_bufnr_for_job = bufnr
@@ -135,7 +138,7 @@ local function close_terminal()
135
138
-- If the job already exited, on_exit would have cleaned up.
136
139
-- This direct close is for user-initiated close.
137
140
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
139
142
end
140
143
end
141
144
@@ -146,6 +149,78 @@ local function focus_terminal()
146
149
end
147
150
end
148
151
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
+
149
224
local function find_existing_claude_terminal ()
150
225
local buffers = vim .api .nvim_list_bufs ()
151
226
for _ , buf in ipairs (buffers ) do
@@ -158,13 +233,7 @@ local function find_existing_claude_terminal()
158
233
local windows = vim .api .nvim_list_wins ()
159
234
for _ , win in ipairs (windows ) do
160
235
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 )
168
237
return buf , win
169
238
end
170
239
end
@@ -193,7 +262,7 @@ function M.open(cmd_string, env_table, effective_config)
193
262
bufnr = existing_buf
194
263
winid = existing_win
195
264
-- 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" )
197
266
focus_terminal ()
198
267
else
199
268
if not open_terminal (cmd_string , env_table , effective_config ) then
@@ -211,32 +280,50 @@ end
211
280
--- @param env_table table
212
281
--- @param effective_config table
213
282
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 ()
217
286
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
220
299
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
222
306
end
223
307
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
225
309
local existing_buf , existing_win = find_existing_claude_terminal ()
226
310
if existing_buf and existing_win then
227
311
-- Recover the existing terminal
228
312
bufnr = existing_buf
229
313
winid = existing_win
230
- require ( " claudecode. logger" ) .debug (" terminal" , " Recovered existing Claude terminal in toggle " )
314
+ logger .debug (" terminal" , " Recovered existing Claude terminal" )
231
315
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 ()
236
321
else
322
+ -- Focus the recovered terminal
237
323
focus_terminal ()
238
324
end
239
325
else
326
+ -- No existing terminal found, create a new one
240
327
if not open_terminal (cmd_string , env_table , effective_config ) then
241
328
vim .notify (" Failed to open Claude terminal using native fallback (toggle)." , vim .log .levels .ERROR )
242
329
end
0 commit comments