Skip to content

Commit c17a07d

Browse files
Fix: Reset terminal busy state after manual commands complete (#4583)
fix: reset terminal busy state after manual commands complete (#4319) - Add terminal.busy = true when shell execution starts - Add terminal.busy = false when shell execution ends for both Roo and non-Roo terminals - Add comprehensive tests for busy flag management - Fixes issue where terminals got stuck in busy state after manual commands
1 parent bad9b0e commit c17a07d

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

src/integrations/terminal/TerminalRegistry.ts

Lines changed: 3 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:",
@@ -99,6 +100,7 @@ export class TerminalRegistry {
99100
{ terminalId: terminal?.id, command: process?.command, exitCode: e.exitCode },
100101
)
101102

103+
terminal.busy = false
102104
return
103105
}
104106

@@ -113,6 +115,7 @@ export class TerminalRegistry {
113115

114116
// Signal completion to any waiting processes.
115117
terminal.shellExecutionComplete(exitDetails)
118+
terminal.busy = false // Mark terminal as not busy when shell execution ends
116119
},
117120
)
118121

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

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,39 @@
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

89
// Mock vscode.window.createTerminal
910
const mockCreateTerminal = jest.fn()
1011

12+
// Event handlers for testing
13+
let mockStartHandler: any = null
14+
let mockEndHandler: any = null
15+
1116
jest.mock("vscode", () => ({
1217
window: {
1318
createTerminal: (...args: any[]) => {
1419
mockCreateTerminal(...args)
1520
return {
21+
name: "Roo Code",
1622
exitStatus: undefined,
23+
dispose: jest.fn(),
24+
show: jest.fn(),
25+
hide: jest.fn(),
26+
sendText: jest.fn(),
1727
}
1828
},
1929
onDidCloseTerminal: jest.fn().mockReturnValue({ dispose: jest.fn() }),
30+
onDidStartTerminalShellExecution: jest.fn().mockImplementation((handler) => {
31+
mockStartHandler = handler
32+
return { dispose: jest.fn() }
33+
}),
34+
onDidEndTerminalShellExecution: jest.fn().mockImplementation((handler) => {
35+
mockEndHandler = handler
36+
return { dispose: jest.fn() }
37+
}),
2038
},
2139
ThemeIcon: jest.fn(),
2240
}))
@@ -28,6 +46,15 @@ jest.mock("execa", () => ({
2846
describe("TerminalRegistry", () => {
2947
beforeEach(() => {
3048
mockCreateTerminal.mockClear()
49+
50+
// Reset event handlers
51+
mockStartHandler = null
52+
mockEndHandler = null
53+
54+
// Clear terminals array for each test
55+
;(TerminalRegistry as any).terminals = []
56+
;(TerminalRegistry as any).nextTerminalId = 1
57+
;(TerminalRegistry as any).isInitialized = false
3158
})
3259

3360
describe("createTerminal", () => {
@@ -113,4 +140,188 @@ describe("TerminalRegistry", () => {
113140
}
114141
})
115142
})
143+
144+
describe("busy flag management", () => {
145+
let mockVsTerminal: any
146+
147+
beforeEach(() => {
148+
mockVsTerminal = {
149+
name: "Roo Code",
150+
exitStatus: undefined,
151+
dispose: jest.fn(),
152+
show: jest.fn(),
153+
hide: jest.fn(),
154+
sendText: jest.fn(),
155+
}
156+
mockCreateTerminal.mockReturnValue(mockVsTerminal)
157+
})
158+
159+
// Helper function to get the created Roo terminal and its underlying VSCode terminal
160+
const createTerminalAndGetVsTerminal = (path: string = "/test/path") => {
161+
const rooTerminal = TerminalRegistry.createTerminal(path, "vscode")
162+
// Get the actual VSCode terminal that was created and stored
163+
const vsTerminal = (rooTerminal as any).terminal
164+
return { rooTerminal, vsTerminal }
165+
}
166+
167+
it("should initialize terminal with busy = false", () => {
168+
const terminal = TerminalRegistry.createTerminal("/test/path", "vscode")
169+
expect(terminal.busy).toBe(false)
170+
})
171+
172+
it("should set busy = true when shell execution starts", () => {
173+
// Initialize the registry to set up event handlers
174+
TerminalRegistry.initialize()
175+
176+
// Create a terminal and get the actual VSCode terminal
177+
const { rooTerminal, vsTerminal } = createTerminalAndGetVsTerminal()
178+
expect(rooTerminal.busy).toBe(false)
179+
180+
// Simulate shell execution start event
181+
const execution = {
182+
commandLine: { value: "echo test" },
183+
read: jest.fn().mockReturnValue({}),
184+
} as any
185+
186+
if (mockStartHandler) {
187+
mockStartHandler({
188+
terminal: vsTerminal,
189+
execution,
190+
})
191+
}
192+
193+
expect(rooTerminal.busy).toBe(true)
194+
})
195+
196+
it("should set busy = false when shell execution ends for Roo terminals", () => {
197+
// Initialize the registry to set up event handlers
198+
TerminalRegistry.initialize()
199+
200+
// Create a terminal and get the actual VSCode terminal
201+
const { rooTerminal, vsTerminal } = createTerminalAndGetVsTerminal()
202+
rooTerminal.busy = true
203+
204+
// Set up a mock process to simulate running state
205+
const mockProcess = {
206+
command: "echo test",
207+
isHot: false,
208+
hasUnretrievedOutput: () => false,
209+
}
210+
rooTerminal.process = mockProcess as any
211+
212+
// Simulate shell execution end event
213+
const execution = {
214+
commandLine: { value: "echo test" },
215+
} as any
216+
217+
if (mockEndHandler) {
218+
mockEndHandler({
219+
terminal: vsTerminal,
220+
execution,
221+
exitCode: 0,
222+
})
223+
}
224+
225+
expect(rooTerminal.busy).toBe(false)
226+
})
227+
228+
it("should set busy = false when shell execution ends for non-Roo terminals (manual commands)", () => {
229+
// Initialize the registry to set up event handlers
230+
TerminalRegistry.initialize()
231+
232+
// Simulate a shell execution end event for a terminal not in our registry
233+
const unknownVsTerminal = {
234+
name: "Unknown Terminal",
235+
}
236+
237+
const execution = {
238+
commandLine: { value: "sleep 30" },
239+
} as any
240+
241+
// This should not throw an error and should handle the case gracefully
242+
expect(() => {
243+
if (mockEndHandler) {
244+
mockEndHandler({
245+
terminal: unknownVsTerminal,
246+
execution,
247+
exitCode: 0,
248+
})
249+
}
250+
}).not.toThrow()
251+
})
252+
253+
it("should handle busy flag reset when terminal process is not running", () => {
254+
// Initialize the registry to set up event handlers
255+
TerminalRegistry.initialize()
256+
257+
// Create a terminal and get the actual VSCode terminal
258+
const { rooTerminal, vsTerminal } = createTerminalAndGetVsTerminal()
259+
rooTerminal.busy = true
260+
261+
// Ensure terminal.running returns false (no active process)
262+
Object.defineProperty(rooTerminal, "running", {
263+
get: () => false,
264+
configurable: true,
265+
})
266+
267+
// Simulate shell execution end event
268+
const execution = {
269+
commandLine: { value: "echo test" },
270+
} as any
271+
272+
if (mockEndHandler) {
273+
mockEndHandler({
274+
terminal: vsTerminal,
275+
execution,
276+
exitCode: 0,
277+
})
278+
}
279+
280+
// Should reset busy flag even when not running
281+
expect(rooTerminal.busy).toBe(false)
282+
})
283+
284+
it("should maintain busy state during command execution lifecycle", () => {
285+
// Initialize the registry to set up event handlers
286+
TerminalRegistry.initialize()
287+
288+
// Create a terminal and get the actual VSCode terminal
289+
const { rooTerminal, vsTerminal } = createTerminalAndGetVsTerminal()
290+
expect(rooTerminal.busy).toBe(false)
291+
292+
// Start execution
293+
const execution = {
294+
commandLine: { value: "npm test" },
295+
read: jest.fn().mockReturnValue({}),
296+
} as any
297+
298+
if (mockStartHandler) {
299+
mockStartHandler({
300+
terminal: vsTerminal,
301+
execution,
302+
})
303+
}
304+
305+
expect(rooTerminal.busy).toBe(true)
306+
307+
// Set up mock process for running state
308+
const mockProcess = {
309+
command: "npm test",
310+
isHot: true,
311+
hasUnretrievedOutput: () => true,
312+
}
313+
rooTerminal.process = mockProcess as any
314+
315+
// End execution
316+
if (mockEndHandler) {
317+
mockEndHandler({
318+
terminal: vsTerminal,
319+
execution,
320+
exitCode: 0,
321+
})
322+
}
323+
324+
expect(rooTerminal.busy).toBe(false)
325+
})
326+
})
116327
})

0 commit comments

Comments
 (0)