@@ -16,6 +16,7 @@ import { Terminal } from "./Terminal"
1616
1717export 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