Skip to content

Commit 7b8dde8

Browse files
committed
feat(terminal): add execution marker for command execution tracking
1 parent a5ea602 commit 7b8dde8

File tree

1 file changed

+45
-6
lines changed

1 file changed

+45
-6
lines changed

src/integrations/terminal/TerminalProcess.ts

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Terminal } from "./Terminal"
1616

1717
export class TerminalProcess extends BaseTerminalProcess {
1818
private terminalRef: WeakRef<Terminal>
19+
private executionMarker?: string
1920

2021
constructor(terminal: Terminal) {
2122
super()
@@ -72,6 +73,10 @@ export class TerminalProcess extends BaseTerminalProcess {
7273
return
7374
}
7475

76+
// Generate a unique marker for this command execution
77+
const executionId = `ROO_CMD_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
78+
this.executionMarker = executionId
79+
7580
// Create a promise that resolves when the stream becomes available
7681
const streamAvailable = new Promise<AsyncIterable<string>>((resolve, reject) => {
7782
const timeoutId = setTimeout(() => {
@@ -104,7 +109,7 @@ export class TerminalProcess extends BaseTerminalProcess {
104109
this.once("shell_execution_complete", (details: ExitCodeDetails) => resolve(details))
105110
})
106111

107-
// Execute command
112+
// Execute command with our marker
108113
const defaultWindowsShellProfile = vscode.workspace
109114
.getConfiguration("terminal.integrated.defaultProfile")
110115
.get("windows")
@@ -114,8 +119,12 @@ export class TerminalProcess extends BaseTerminalProcess {
114119
(defaultWindowsShellProfile === null ||
115120
(defaultWindowsShellProfile as string)?.toLowerCase().includes("powershell"))
116121

122+
let commandToExecute = command
123+
124+
// Add our execution marker at the end
117125
if (isPowerShell) {
118-
let commandToExecute = command
126+
// PowerShell: Always echo the marker regardless of command success/failure
127+
commandToExecute = `try { ${command} } finally { Write-Host "${executionId}" -NoNewline }`
119128

120129
// Only add the PowerShell counter workaround if enabled
121130
if (Terminal.getPowershellCounter()) {
@@ -126,12 +135,14 @@ export class TerminalProcess extends BaseTerminalProcess {
126135
if (Terminal.getCommandDelay() > 0) {
127136
commandToExecute += ` ; start-sleep -milliseconds ${Terminal.getCommandDelay()}`
128137
}
129-
130-
terminal.shellIntegration.executeCommand(commandToExecute)
131138
} else {
132-
terminal.shellIntegration.executeCommand(command)
139+
// Bash/Zsh/other shells: Always echo the marker regardless of command success/failure
140+
// Using ; instead of && ensures the marker is printed even if the command fails
141+
commandToExecute += ` ; echo -n "${executionId}"`
133142
}
134143

144+
terminal.shellIntegration.executeCommand(commandToExecute)
145+
135146
this.isHot = true
136147

137148
// Wait for stream to be available
@@ -170,6 +181,7 @@ export class TerminalProcess extends BaseTerminalProcess {
170181
*/
171182

172183
// Process stream data
184+
let markerDetected = false
173185
for await (let data of stream) {
174186
// Check for command output start marker
175187
if (!commandOutputStarted) {
@@ -192,6 +204,15 @@ export class TerminalProcess extends BaseTerminalProcess {
192204
// and chunks may not be complete so you cannot rely on detecting or removing escape sequences mid-stream.
193205
this.fullOutput += data
194206

207+
// Check if our execution marker is in the output
208+
if (this.executionMarker && this.fullOutput.includes(this.executionMarker)) {
209+
markerDetected = true
210+
// Remove the marker from the output
211+
this.fullOutput = this.fullOutput.replace(this.executionMarker, "")
212+
// Break out of the loop as command has completed
213+
break
214+
}
215+
195216
// For non-immediately returning commands we want to show loading spinner
196217
// right away but this wouldn't happen until it emits a line break, so
197218
// as soon as we get any output we emit to let webview know to show spinner
@@ -208,7 +229,25 @@ export class TerminalProcess extends BaseTerminalProcess {
208229
// Set streamClosed immediately after stream ends.
209230
this.terminal.setActiveStream(undefined)
210231

211-
// Wait for shell execution to complete.
232+
// If we detected our marker, we know the command completed
233+
if (markerDetected) {
234+
this.isHot = false
235+
236+
// Emit any remaining output before completing
237+
this.emitRemainingBufferIfListening()
238+
239+
// Emit completion with success exit code
240+
const exitDetails: ExitCodeDetails = { exitCode: 0 }
241+
this.emit("shell_execution_complete", exitDetails)
242+
243+
// Clean up output and emit completion
244+
this.stopHotTimer()
245+
this.emit("completed", this.removeEscapeSequences(this.fullOutput))
246+
this.emit("continue")
247+
return
248+
}
249+
250+
// Wait for shell execution to complete (fallback to VSCode's event)
212251
await shellExecutionComplete
213252

214253
this.isHot = false

0 commit comments

Comments
 (0)