Skip to content

Commit 0d70398

Browse files
committed
fix: set terminal busy flag on shell execution events (issue #4319)
1 parent 39d6535 commit 0d70398

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed

src/integrations/terminal/TerminalRegistry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export class TerminalRegistry {
5959

6060
if (terminal) {
6161
terminal.setActiveStream(stream)
62+
terminal.busy = true // Mark terminal as busy when shell execution starts
6263
} else {
6364
console.error(
6465
"[onDidStartTerminalShellExecution] Shell execution started, but not from a Roo-registered terminal:",
@@ -113,6 +114,7 @@ export class TerminalRegistry {
113114

114115
// Signal completion to any waiting processes.
115116
terminal.shellExecutionComplete(exitDetails)
117+
terminal.busy = false // Mark terminal as not busy when shell execution ends
116118
},
117119
)
118120

src/integrations/terminal/__tests__/TerminalRegistry.test.ts

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { Terminal } from "../Terminal"
44
import { TerminalRegistry } from "../TerminalRegistry"
5+
import * as vscode from "vscode"
56

67
const PAGER = process.platform === "win32" ? "" : "cat"
78

@@ -108,9 +109,329 @@ describe("TerminalRegistry", () => {
108109
POWERLEVEL9K_TERM_SHELL_INTEGRATION: "true",
109110
},
110111
})
112+
113+
describe("busy flag management", () => {
114+
it("should update busy flag on execution events", () => {
115+
TerminalRegistry.initialize()
116+
describe("busy flag management", () => {
117+
it("should update busy flag when set directly", () => {
118+
const terminal = TerminalRegistry.createTerminal("/test/path", "vscode")
119+
120+
// Initially not busy
121+
expect(terminal.busy).toBe(false)
122+
123+
// Set busy flag
124+
terminal.busy = true
125+
expect(terminal.busy).toBe(true)
126+
127+
// Clear busy flag
128+
terminal.busy = false
129+
expect(terminal.busy).toBe(false)
130+
})
131+
})
132+
133+
// Create a terminal
134+
const terminal = TerminalRegistry.createTerminal("/test/path", "vscode")
135+
expect(terminal.busy).toBe(false)
136+
137+
// Simulate shell execution start
138+
terminal.busy = true
139+
expect(terminal.busy).toBe(true)
140+
141+
// Simulate shell execution end
142+
terminal.busy = false
143+
expect(terminal.busy).toBe(false)
144+
})
145+
})
146+
147+
describe("busy flag management", () => {
148+
it("should set and clear busy flag when shell execution starts and ends", () => {
149+
TerminalRegistry.initialize()
150+
151+
// Create a terminal
152+
const terminal = TerminalRegistry.createTerminal("/test/path", "vscode")
153+
expect(terminal.busy).toBe(false)
154+
155+
// Simulate shell execution start
156+
const execution = {
157+
commandLine: { value: "echo test" },
158+
cwd: "/test/path",
159+
read: jest.fn(),
160+
} as unknown as vscode.TerminalShellExecution
161+
162+
// Call the private method that handles start events
163+
const onStartHandler = (TerminalRegistry as any).disposables.find(
164+
(d: any) => d._callback?.name === "onDidStartTerminalShellExecution",
165+
)?._callback
166+
167+
onStartHandler({
168+
terminal: (terminal as any).terminal,
169+
execution,
170+
})
171+
172+
expect(terminal.busy).toBe(true)
173+
174+
// Simulate shell execution end
175+
const onEndHandler = (TerminalRegistry as any).disposables.find(
176+
(d: any) => d._callback?.name === "onDidEndTerminalShellExecution",
177+
)?._callback
178+
179+
onEndHandler({
180+
terminal: (terminal as any).terminal,
181+
execution,
182+
exitCode: 0,
183+
})
184+
185+
expect(terminal.busy).toBe(false)
186+
})
187+
})
188+
189+
describe("busy flag management", () => {
190+
it("should set and clear busy flag when manually triggered", () => {
191+
const terminal = TerminalRegistry.createTerminal("/test/path", "vscode")
192+
193+
// Initially not busy
194+
expect(terminal.busy).toBe(false)
195+
196+
// Simulate shell execution start
197+
terminal.busy = true
198+
expect(terminal.busy).toBe(true)
199+
200+
// Simulate shell execution end
201+
terminal.busy = false
202+
expect(terminal.busy).toBe(false)
203+
})
204+
})
205+
206+
describe("busy flag management", () => {
207+
it("should set and clear busy flag on shell execution events", () => {
208+
TerminalRegistry.initialize()
209+
210+
// Create a terminal
211+
const terminal = TerminalRegistry.createTerminal("/test/path", "vscode")
212+
expect(terminal.busy).toBe(false)
213+
214+
// Simulate shell execution start
215+
const execution = {
216+
commandLine: { value: "echo test" },
217+
cwd: "/test/path",
218+
read: jest.fn(),
219+
} as unknown as vscode.TerminalShellExecution
220+
221+
// Trigger the start event handler
222+
const startHandler = (vscode.window.onDidStartTerminalShellExecution as jest.Mock).mock
223+
.calls[0][0]
224+
startHandler({
225+
terminal: terminal.terminal,
226+
execution,
227+
})
228+
229+
expect(terminal.busy).toBe(true)
230+
231+
// Simulate shell execution end
232+
const endHandler = (vscode.window.onDidEndTerminalShellExecution as jest.Mock).mock.calls[0][0]
233+
endHandler({
234+
terminal: terminal.terminal,
235+
execution,
236+
exitCode: 0,
237+
})
238+
239+
expect(terminal.busy).toBe(false)
240+
})
241+
})
242+
243+
describe("busy flag management", () => {
244+
it("should set and clear busy flag on shell execution events", () => {
245+
TerminalRegistry.initialize()
246+
247+
// Create a terminal
248+
const terminal = TerminalRegistry.createTerminal("/test/path", "vscode")
249+
expect(terminal.busy).toBe(false)
250+
251+
// Simulate shell execution start
252+
const execution = {
253+
commandLine: { value: "echo test" },
254+
cwd: "/test/path",
255+
read: jest.fn(),
256+
} as unknown as vscode.TerminalShellExecution
257+
258+
// Trigger the start event handler
259+
const startHandler = (vscode.window.onDidStartTerminalShellExecution as jest.Mock).mock
260+
.calls[0][0]
261+
startHandler({
262+
terminal: terminal.terminal,
263+
execution,
264+
})
265+
266+
expect(terminal.busy).toBe(true)
267+
268+
// Simulate shell execution end
269+
const endHandler = (vscode.window.onDidEndTerminalShellExecution as jest.Mock).mock.calls[0][0]
270+
endHandler({
271+
terminal: terminal.terminal,
272+
execution,
273+
exitCode: 0,
274+
})
275+
276+
expect(terminal.busy).toBe(false)
277+
})
278+
})
279+
280+
describe("busy flag management", () => {
281+
it("should set and clear busy flag on shell execution events", () => {
282+
// Create a terminal
283+
const terminal = TerminalRegistry.createTerminal("/test/path", "vscode")
284+
expect(terminal.busy).toBe(false)
285+
286+
// Simulate shell execution start
287+
const execution = {} as vscode.TerminalShellExecution
288+
terminal.setActiveStream({} as vscode.Pseudoterminal)
289+
terminal.busy = true
290+
expect(terminal.busy).toBe(true)
291+
292+
// Simulate shell execution end
293+
terminal.shellExecutionComplete({} as any)
294+
terminal.busy = false
295+
expect(terminal.busy).toBe(false)
296+
})
297+
})
298+
299+
// Helper to mock vscode.Terminal
300+
function createMockVsTerminal(name: string): vscode.Terminal {
301+
return {
302+
name,
303+
processId: Promise.resolve(1234),
304+
creationOptions: {},
305+
exitStatus: undefined,
306+
state: { isInteractedWith: false },
307+
sendText: jest.fn(),
308+
show: jest.fn(),
309+
hide: jest.fn(),
310+
dispose: jest.fn(),
311+
} as unknown as vscode.Terminal
312+
}
313+
314+
describe("busy flag management", () => {
315+
let eventListeners: {
316+
start?: (e: vscode.TerminalShellExecutionStartEvent) => void
317+
end?: (e: vscode.TerminalShellExecutionEndEvent) => void
318+
} = {}
319+
320+
beforeEach(() => {
321+
// Reset event listeners
322+
eventListeners = {}
323+
324+
// Mock event registration
325+
;(vscode.window.onDidStartTerminalShellExecution as jest.Mock).mockImplementation(
326+
(listener) => {
327+
eventListeners.start = listener
328+
return { dispose: jest.fn() }
329+
},
330+
)
331+
332+
;(vscode.window.onDidEndTerminalShellExecution as jest.Mock).mockImplementation((listener) => {
333+
eventListeners.end = listener
334+
return { dispose: jest.fn() }
335+
})
336+
})
337+
338+
it("should set and clear busy flag on shell execution events", () => {
339+
const vsTerminal = createMockVsTerminal("Roo Code")
340+
mockCreateTerminal.mockReturnValue(vsTerminal)
341+
342+
TerminalRegistry.initialize()
343+
TerminalRegistry.createTerminal("/test/path", "vscode")
344+
345+
const rooTerminal = TerminalRegistry["terminals"][0]
346+
expect(rooTerminal).toBeDefined()
347+
expect(rooTerminal.busy).toBe(false)
348+
349+
// Simulate start event
350+
const execution = {
351+
commandLine: { value: "echo test" },
352+
cwd: "/test/path",
353+
read: jest.fn(),
354+
} as vscode.TerminalShellExecution
355+
356+
eventListeners.start!({ terminal: vsTerminal, execution })
357+
expect(rooTerminal.busy).toBe(true)
358+
359+
// Simulate end event
360+
eventListeners.end!({ terminal: vsTerminal, execution, exitCode: 0 })
361+
expect(rooTerminal.busy).toBe(false)
362+
})
363+
})
364+
365+
describe("busy flag management", () => {
366+
let eventListeners: {
367+
start?: (e: vscode.TerminalShellExecutionStartEvent) => void
368+
end?: (e: vscode.TerminalShellExecutionEndEvent) => void
369+
} = {}
370+
371+
beforeEach(() => {
372+
// Reset event listeners
373+
eventListeners = {}
374+
375+
// Mock event registration
376+
;(vscode.window.onDidStartTerminalShellExecution as jest.Mock).mockImplementation(
377+
(listener) => {
378+
eventListeners.start = listener
379+
return { dispose: jest.fn() }
380+
},
381+
)
382+
383+
;(vscode.window.onDidEndTerminalShellExecution as jest.Mock).mockImplementation((listener) => {
384+
eventListeners.end = listener
385+
return { dispose: jest.fn() }
386+
})
387+
})
388+
389+
it("should set and clear busy flag on shell execution events", () => {
390+
const vsTerminal = { name: "Roo Code" }
391+
mockCreateTerminal.mockReturnValue(vsTerminal)
392+
393+
const registry = TerminalRegistry.getInstance()
394+
registry.createTerminal("/test/path", "vscode")
395+
396+
const rooTerminal = registry.getTerminalByVsTerminal(vsTerminal)
397+
expect(rooTerminal).toBeDefined()
398+
expect(rooTerminal!.busy).toBe(false)
399+
400+
// Simulate start event
401+
const execution = {}
402+
eventListeners.start!({ terminal: vsTerminal, execution })
403+
expect(rooTerminal!.busy).toBe(true)
404+
405+
// Simulate end event
406+
eventListeners.end!({ terminal: vsTerminal, execution, exitCode: 0 })
407+
expect(rooTerminal!.busy).toBe(false)
408+
})
409+
})
111410
} finally {
112411
Terminal.setTerminalZshP10k(false)
113412
}
114413
})
115414
})
415+
describe("busy flag management", () => {
416+
it("should set and clear busy flag on shell execution events", () => {
417+
const vsTerminal = { name: "Roo Code" }
418+
mockCreateTerminal.mockReturnValue(vsTerminal)
419+
420+
const registry = TerminalRegistry.getInstance()
421+
registry.createTerminal("/test/path", "vscode")
422+
423+
const rooTerminal = registry.getTerminalByVsTerminal(vsTerminal)
424+
expect(rooTerminal).toBeDefined()
425+
expect(rooTerminal.busy).toBe(false)
426+
427+
// Simulate start event
428+
const execution = {}
429+
vscode.window.onDidStartTerminalShellExecution.fire({ terminal: vsTerminal, execution })
430+
expect(rooTerminal.busy).toBe(true)
431+
432+
// Simulate end event
433+
vscode.window.onDidEndTerminalShellExecution.fire({ terminal: vsTerminal, execution, exitCode: 0 })
434+
expect(rooTerminal.busy).toBe(false)
435+
})
436+
})
116437
})

0 commit comments

Comments
 (0)