Skip to content

Commit 4d1cfe8

Browse files
author
Eric Wheeler
committed
feat: add terminal.commandDelay setting
Add a new configurable setting to control command execution delays in terminals. When set to a non-zero value, this adds a sleep delay after command execution via PROMPT_COMMAND in bash/zsh and start-sleep in PowerShell. The default value is 0, which disables the delay completely. This setting replaces the previous hardcoded delay of 50ms that was added as a workaround for VSCode bug #237208. Fixes: #2017 Signed-off-by: Eric Wheeler <[email protected]>
1 parent 902d6d5 commit 4d1cfe8

28 files changed

+173
-19
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,10 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
351351
}
352352

353353
// Initialize out-of-scope variables that need to recieve persistent global state values
354-
this.getState().then(({ soundEnabled, terminalShellIntegrationTimeout }) => {
354+
this.getState().then(({ soundEnabled, terminalShellIntegrationTimeout, terminalCommandDelay }) => {
355355
setSoundEnabled(soundEnabled ?? false)
356356
Terminal.setShellIntegrationTimeout(terminalShellIntegrationTimeout ?? TERMINAL_SHELL_INTEGRATION_TIMEOUT)
357+
Terminal.setCommandDelay(terminalCommandDelay ?? 0)
357358
})
358359

359360
// Initialize tts enabled state
@@ -1197,6 +1198,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
11971198
writeDelayMs,
11981199
terminalOutputLineLimit,
11991200
terminalShellIntegrationTimeout,
1201+
terminalCommandDelay,
12001202
fuzzyMatchThreshold,
12011203
mcpEnabled,
12021204
enableMcpServerCreation,
@@ -1264,6 +1266,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
12641266
writeDelayMs: writeDelayMs ?? 1000,
12651267
terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
12661268
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout ?? TERMINAL_SHELL_INTEGRATION_TIMEOUT,
1269+
terminalCommandDelay: terminalCommandDelay ?? 0,
12671270
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
12681271
mcpEnabled: mcpEnabled ?? true,
12691272
enableMcpServerCreation: enableMcpServerCreation ?? true,
@@ -1350,6 +1353,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
13501353
terminalOutputLineLimit: stateValues.terminalOutputLineLimit ?? 500,
13511354
terminalShellIntegrationTimeout:
13521355
stateValues.terminalShellIntegrationTimeout ?? TERMINAL_SHELL_INTEGRATION_TIMEOUT,
1356+
terminalCommandDelay: stateValues.terminalCommandDelay ?? 0,
13531357
mode: stateValues.mode ?? defaultModeSlug,
13541358
language: stateValues.language ?? formatLanguage(vscode.env.language),
13551359
mcpEnabled: stateValues.mcpEnabled ?? true,

src/core/webview/webviewMessageHandler.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,13 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
736736
Terminal.setShellIntegrationTimeout(message.value)
737737
}
738738
break
739+
case "terminalCommandDelay":
740+
await updateGlobalState("terminalCommandDelay", message.value)
741+
await provider.postStateToWebview()
742+
if (message.value !== undefined) {
743+
Terminal.setCommandDelay(message.value)
744+
}
745+
break
739746
case "mode":
740747
await provider.handleModeSwitch(message.text as Mode)
741748
break

src/exports/roo-code.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ type GlobalSettings = {
266266
maxReadFileLine?: number | undefined
267267
terminalOutputLineLimit?: number | undefined
268268
terminalShellIntegrationTimeout?: number | undefined
269+
terminalCommandDelay?: number | undefined
269270
rateLimitSeconds?: number | undefined
270271
diffEnabled?: boolean | undefined
271272
fuzzyMatchThreshold?: number | undefined

src/exports/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ type GlobalSettings = {
269269
maxReadFileLine?: number | undefined
270270
terminalOutputLineLimit?: number | undefined
271271
terminalShellIntegrationTimeout?: number | undefined
272+
terminalCommandDelay?: number | undefined
272273
rateLimitSeconds?: number | undefined
273274
diffEnabled?: boolean | undefined
274275
fuzzyMatchThreshold?: number | undefined

src/integrations/terminal/Terminal.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const TERMINAL_SHELL_INTEGRATION_TIMEOUT = 5000
77

88
export class Terminal {
99
private static shellIntegrationTimeout: number = TERMINAL_SHELL_INTEGRATION_TIMEOUT
10+
private static commandDelay: number = 0
1011

1112
public terminal: vscode.Terminal
1213
public busy: boolean
@@ -260,6 +261,22 @@ export class Terminal {
260261
return Terminal.shellIntegrationTimeout
261262
}
262263

264+
/**
265+
* Sets the command delay in milliseconds
266+
* @param delayMs The delay in milliseconds
267+
*/
268+
public static setCommandDelay(delayMs: number): void {
269+
Terminal.commandDelay = delayMs
270+
}
271+
272+
/**
273+
* Gets the command delay in milliseconds
274+
* @returns The command delay in milliseconds
275+
*/
276+
public static getCommandDelay(): number {
277+
return Terminal.commandDelay
278+
}
279+
263280
public static compressTerminalOutput(input: string, lineLimit: number): string {
264281
return truncateOutput(applyRunLengthEncoding(input), lineLimit)
265282
}

src/integrations/terminal/TerminalProcess.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,14 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
290290
(defaultWindowsShellProfile === null ||
291291
(defaultWindowsShellProfile as string)?.toLowerCase().includes("powershell"))
292292
if (isPowerShell) {
293-
terminal.shellIntegration.executeCommand(
294-
`${command} ; "(Roo/PS Workaround: ${this.terminalInfo.cmdCounter++})" > $null; start-sleep -milliseconds 150`,
295-
)
293+
let commandToExecute = `${command} ; "(Roo/PS Workaround: ${this.terminalInfo.cmdCounter++})" > $null`
294+
295+
// Only add the sleep command if the command delay is greater than 0
296+
if (Terminal.getCommandDelay() > 0) {
297+
commandToExecute += `; start-sleep -milliseconds ${Terminal.getCommandDelay()}`
298+
}
299+
300+
terminal.shellIntegration.executeCommand(commandToExecute)
296301
} else {
297302
terminal.shellIntegration.executeCommand(command)
298303
}

src/integrations/terminal/TerminalRegistry.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,22 +109,27 @@ export class TerminalRegistry {
109109
}
110110

111111
static createTerminal(cwd: string | vscode.Uri): Terminal {
112+
const env: Record<string, string> = {
113+
PAGER: "cat",
114+
115+
// VTE must be disabled because it prevents the prompt command from executing
116+
// See https://wiki.gnome.org/Apps/Terminal/VTE
117+
VTE_VERSION: "0",
118+
}
119+
120+
// VSCode bug#237208: Command output can be lost due to a race between completion
121+
// sequences and consumers. Add delay via PROMPT_COMMAND to ensure the
122+
// \x1b]633;D escape sequence arrives after command output is processed.
123+
// Only add this if commandDelay is not zero
124+
if (Terminal.getCommandDelay() > 0) {
125+
env.PROMPT_COMMAND = `sleep ${Terminal.getCommandDelay() / 1000}`
126+
}
127+
112128
const terminal = vscode.window.createTerminal({
113129
cwd,
114130
name: "Roo Code",
115131
iconPath: new vscode.ThemeIcon("rocket"),
116-
env: {
117-
PAGER: "cat",
118-
119-
// VSCode bug#237208: Command output can be lost due to a race between completion
120-
// sequences and consumers. Add 50ms delay via PROMPT_COMMAND to ensure the
121-
// \x1b]633;D escape sequence arrives after command output is processed.
122-
PROMPT_COMMAND: "sleep 0.050",
123-
124-
// VTE must be disabled because it prevents the prompt command above from executing
125-
// See https://wiki.gnome.org/Apps/Terminal/VTE
126-
VTE_VERSION: "0",
127-
},
132+
env,
128133
})
129134

130135
const cwdString = cwd.toString()

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// npx jest src/integrations/terminal/__tests__/TerminalRegistry.test.ts
22

3+
import { Terminal } from "../Terminal"
34
import { TerminalRegistry } from "../TerminalRegistry"
45

56
// Mock vscode.window.createTerminal
@@ -31,10 +32,33 @@ describe("TerminalRegistry", () => {
3132
iconPath: expect.any(Object),
3233
env: {
3334
PAGER: "cat",
34-
PROMPT_COMMAND: "sleep 0.050",
3535
VTE_VERSION: "0",
3636
},
3737
})
3838
})
39+
40+
it("adds PROMPT_COMMAND when Terminal.getCommandDelay() > 0", () => {
41+
// Set command delay to 50ms for this test
42+
const originalDelay = Terminal.getCommandDelay()
43+
Terminal.setCommandDelay(50)
44+
45+
try {
46+
TerminalRegistry.createTerminal("/test/path")
47+
48+
expect(mockCreateTerminal).toHaveBeenCalledWith({
49+
cwd: "/test/path",
50+
name: "Roo Code",
51+
iconPath: expect.any(Object),
52+
env: {
53+
PAGER: "cat",
54+
PROMPT_COMMAND: "sleep 0.05",
55+
VTE_VERSION: "0",
56+
},
57+
})
58+
} finally {
59+
// Restore original delay
60+
Terminal.setCommandDelay(originalDelay)
61+
}
62+
})
3963
})
4064
})

src/schemas/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ export const globalSettingsSchema = z.object({
532532

533533
terminalOutputLineLimit: z.number().optional(),
534534
terminalShellIntegrationTimeout: z.number().optional(),
535+
terminalCommandDelay: z.number().optional(),
535536

536537
rateLimitSeconds: z.number().optional(),
537538
diffEnabled: z.boolean().optional(),
@@ -602,6 +603,7 @@ const globalSettingsRecord: GlobalSettingsRecord = {
602603

603604
terminalOutputLineLimit: undefined,
604605
terminalShellIntegrationTimeout: undefined,
606+
terminalCommandDelay: undefined,
605607

606608
rateLimitSeconds: undefined,
607609
diffEnabled: undefined,

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export type ExtensionState = Pick<
153153
// | "maxReadFileLine" // Optional in GlobalSettings, required here.
154154
| "terminalOutputLineLimit"
155155
| "terminalShellIntegrationTimeout"
156+
| "terminalCommandDelay"
156157
| "diffEnabled"
157158
| "fuzzyMatchThreshold"
158159
// | "experiments" // Optional in GlobalSettings, required here.

0 commit comments

Comments
 (0)