@@ -149,12 +149,20 @@ export class ExtHostTerminalShellIntegration extends Disposable implements IExtH
149149 }
150150}
151151
152- class InternalTerminalShellIntegration extends Disposable {
152+ interface IExecutionProperties {
153+ isMultiLine : boolean ;
154+ unresolvedCommandLines : string [ ] | undefined ;
155+ }
156+
157+ export class InternalTerminalShellIntegration extends Disposable {
153158 private _pendingExecutions : InternalTerminalShellExecution [ ] = [ ] ;
159+ private _pendingEndingExecution : InternalTerminalShellExecution | undefined ;
154160
161+ private _currentExecutionProperties : IExecutionProperties | undefined ;
155162 private _currentExecution : InternalTerminalShellExecution | undefined ;
156163 get currentExecution ( ) : InternalTerminalShellExecution | undefined { return this . _currentExecution ; }
157164
165+
158166 private _env : vscode . TerminalShellIntegrationEnvironment | undefined ;
159167 private _cwd : URI | undefined ;
160168 private _hasRichCommandDetection : boolean = false ;
@@ -226,27 +234,70 @@ class InternalTerminalShellIntegration extends Disposable {
226234
227235 requestNewShellExecution ( commandLine : vscode . TerminalShellExecutionCommandLine , cwd : URI | undefined ) {
228236 const execution = new InternalTerminalShellExecution ( commandLine , cwd ?? this . _cwd ) ;
237+ const unresolvedCommandLines = splitAndSanitizeCommandLine ( commandLine . value ) ;
238+ if ( unresolvedCommandLines . length > 1 ) {
239+ this . _currentExecutionProperties = {
240+ isMultiLine : true ,
241+ unresolvedCommandLines : splitAndSanitizeCommandLine ( commandLine . value ) ,
242+ } ;
243+ }
229244 this . _pendingExecutions . push ( execution ) ;
230245 this . _onDidRequestNewExecution . fire ( commandLine . value ) ;
231246 return execution ;
232247 }
233248
234- startShellExecution ( commandLine : vscode . TerminalShellExecutionCommandLine , cwd : URI | undefined ) : InternalTerminalShellExecution {
249+ startShellExecution ( commandLine : vscode . TerminalShellExecutionCommandLine , cwd : URI | undefined ) : undefined {
250+ // Since an execution is starting, fire the end event for any execution that is awaiting to
251+ // end. When this happens it means that the data stream may not be flushed and therefore may
252+ // fire events after the end event.
253+ if ( this . _pendingEndingExecution ) {
254+ this . _onDidRequestEndExecution . fire ( { terminal : this . _terminal , shellIntegration : this . value , execution : this . _pendingEndingExecution . value , exitCode : undefined } ) ;
255+ this . _pendingEndingExecution = undefined ;
256+ }
257+
235258 if ( this . _currentExecution ) {
259+ // If the current execution is multi-line, check if this command line is part of it.
260+ if ( this . _currentExecutionProperties ?. isMultiLine && this . _currentExecutionProperties . unresolvedCommandLines ) {
261+ const subExecutionResult = isSubExecution ( this . _currentExecutionProperties . unresolvedCommandLines , commandLine ) ;
262+ if ( subExecutionResult ) {
263+ this . _currentExecutionProperties . unresolvedCommandLines = subExecutionResult . unresolvedCommandLines ;
264+ return ;
265+ }
266+ }
236267 if ( this . _hasRichCommandDetection ) {
237268 console . warn ( 'Rich command detection is enabled but an execution started before the last ended' ) ;
238269 }
239270 this . _currentExecution . endExecution ( undefined ) ;
271+ this . _currentExecution . flush ( ) ;
240272 this . _onDidRequestEndExecution . fire ( { terminal : this . _terminal , shellIntegration : this . value , execution : this . _currentExecution . value , exitCode : undefined } ) ;
241273 }
242274
243275 // Get the matching pending execution, how strict this is depends on the confidence of the
244276 // command line
245277 let currentExecution : InternalTerminalShellExecution | undefined ;
246278 if ( commandLine . confidence === TerminalShellExecutionCommandLineConfidence . High ) {
247- const index = this . _pendingExecutions . findIndex ( e => e . value . commandLine . value === commandLine . value ) ;
248- if ( index !== - 1 ) {
249- currentExecution = this . _pendingExecutions . splice ( index , 1 ) [ 0 ] ;
279+ for ( const [ i , execution ] of this . _pendingExecutions . entries ( ) ) {
280+ if ( execution . value . commandLine . value === commandLine . value ) {
281+ currentExecution = execution ;
282+ this . _currentExecutionProperties = {
283+ isMultiLine : false ,
284+ unresolvedCommandLines : undefined ,
285+ } ;
286+ currentExecution = execution ;
287+ this . _pendingExecutions . splice ( i , 1 ) ;
288+ break ;
289+ } else {
290+ const subExecutionResult = isSubExecution ( splitAndSanitizeCommandLine ( execution . value . commandLine . value ) , commandLine ) ;
291+ if ( subExecutionResult ) {
292+ this . _currentExecutionProperties = {
293+ isMultiLine : true ,
294+ unresolvedCommandLines : subExecutionResult . unresolvedCommandLines ,
295+ } ;
296+ currentExecution = execution ;
297+ this . _pendingExecutions . splice ( i , 1 ) ;
298+ break ;
299+ }
300+ }
250301 }
251302 } else {
252303 currentExecution = this . _pendingExecutions . shift ( ) ;
@@ -259,27 +310,36 @@ class InternalTerminalShellIntegration extends Disposable {
259310 }
260311
261312 this . _currentExecution = currentExecution ;
262-
263313 this . _onDidStartTerminalShellExecution . fire ( { terminal : this . _terminal , shellIntegration : this . value , execution : this . _currentExecution . value } ) ;
264- return this . _currentExecution ;
265314 }
266315
267316 emitData ( data : string ) : void {
268317 this . currentExecution ?. emitData ( data ) ;
269318 }
270319
271320 endShellExecution ( commandLine : vscode . TerminalShellExecutionCommandLine | undefined , exitCode : number | undefined ) : void {
321+ // If the current execution is multi-line, don't end it until the next command line is
322+ // confirmed to not be a part of it.
323+ if ( this . _currentExecutionProperties ?. isMultiLine ) {
324+ if ( this . _currentExecutionProperties . unresolvedCommandLines && this . _currentExecutionProperties . unresolvedCommandLines . length > 0 ) {
325+ return ;
326+ }
327+ }
328+
272329 if ( this . _currentExecution ) {
273- this . _currentExecution . endExecution ( commandLine ) ;
330+ const commandLineForEvent = this . _currentExecutionProperties ?. isMultiLine ? this . _currentExecution . value . commandLine : commandLine ;
331+ this . _currentExecution . endExecution ( commandLineForEvent ) ;
274332 const currentExecution = this . _currentExecution ;
333+ this . _pendingEndingExecution = currentExecution ;
334+ this . _currentExecution = undefined ;
275335 // IMPORTANT: Ensure the current execution's data events are flushed in order to
276336 // prevent data events firing after the end event fires.
277337 currentExecution . flush ( ) . then ( ( ) => {
278338 // Only fire if it's still the same execution, if it's changed it would have already
279339 // been fired.
280- if ( this . _currentExecution === currentExecution ) {
340+ if ( this . _pendingEndingExecution === currentExecution ) {
281341 this . _onDidRequestEndExecution . fire ( { terminal : this . _terminal , shellIntegration : this . value , execution : currentExecution . value , exitCode } ) ;
282- this . _currentExecution = undefined ;
342+ this . _pendingEndingExecution = undefined ;
283343 }
284344 } ) ;
285345 }
@@ -320,12 +380,11 @@ class InternalTerminalShellIntegration extends Disposable {
320380}
321381
322382class InternalTerminalShellExecution {
323- private _dataStream : ShellExecutionDataStream | undefined ;
324-
325- private _ended : boolean = false ;
326-
327383 readonly value : vscode . TerminalShellExecution ;
328384
385+ private _dataStream : ShellExecutionDataStream | undefined ;
386+ private _isEnded : boolean = false ;
387+
329388 constructor (
330389 private _commandLine : vscode . TerminalShellExecutionCommandLine ,
331390 readonly cwd : URI | undefined ,
@@ -346,7 +405,7 @@ class InternalTerminalShellExecution {
346405
347406 private _createDataStream ( ) : AsyncIterable < string > {
348407 if ( ! this . _dataStream ) {
349- if ( this . _ended ) {
408+ if ( this . _isEnded ) {
350409 return AsyncIterableObject . EMPTY ;
351410 }
352411 this . _dataStream = new ShellExecutionDataStream ( ) ;
@@ -355,20 +414,25 @@ class InternalTerminalShellExecution {
355414 }
356415
357416 emitData ( data : string ) : void {
358- this . _dataStream ?. emitData ( data ) ;
417+ if ( ! this . _isEnded ) {
418+ this . _dataStream ?. emitData ( data ) ;
419+ }
359420 }
360421
361422 endExecution ( commandLine : vscode . TerminalShellExecutionCommandLine | undefined ) : void {
362423 if ( commandLine ) {
363424 this . _commandLine = commandLine ;
364425 }
365426 this . _dataStream ?. endExecution ( ) ;
366- this . _dataStream = undefined ;
367- this . _ended = true ;
427+ this . _isEnded = true ;
368428 }
369429
370430 async flush ( ) : Promise < void > {
371- await this . _dataStream ?. flush ( ) ;
431+ if ( this . _dataStream ) {
432+ await this . _dataStream . flush ( ) ;
433+ this . _dataStream . dispose ( ) ;
434+ this . _dataStream = undefined ;
435+ }
372436 }
373437}
374438
@@ -405,3 +469,39 @@ class ShellExecutionDataStream extends Disposable {
405469 await Promise . all ( this . _iterables . map ( e => e . toPromise ( ) ) ) ;
406470 }
407471}
472+
473+ function splitAndSanitizeCommandLine ( commandLine : string ) : string [ ] {
474+ return commandLine
475+ . split ( '\n' )
476+ . map ( line => line . trim ( ) )
477+ . filter ( line => line . length > 0 ) ;
478+ }
479+
480+ /**
481+ * When executing something that the shell considers multiple commands, such as
482+ * a comment followed by a command, this needs to all be tracked under a single
483+ * execution.
484+ */
485+ function isSubExecution ( unresolvedCommandLines : string [ ] , commandLine : vscode . TerminalShellExecutionCommandLine ) : { unresolvedCommandLines : string [ ] } | false {
486+ if ( unresolvedCommandLines . length === 0 ) {
487+ return false ;
488+ }
489+ const newUnresolvedCommandLines = [ ...unresolvedCommandLines ] ;
490+ const subExecutionLines = splitAndSanitizeCommandLine ( commandLine . value ) ;
491+ if ( newUnresolvedCommandLines && newUnresolvedCommandLines . length > 0 ) {
492+ // If all sub-execution lines are in the command line, this is part of the
493+ // multi-line execution.
494+ for ( let i = 0 ; i < newUnresolvedCommandLines . length ; i ++ ) {
495+ if ( newUnresolvedCommandLines [ i ] !== subExecutionLines [ i ] ) {
496+ break ;
497+ }
498+ newUnresolvedCommandLines . shift ( ) ;
499+ subExecutionLines . shift ( ) ;
500+ }
501+
502+ if ( subExecutionLines . length === 0 ) {
503+ return { unresolvedCommandLines : newUnresolvedCommandLines } ;
504+ }
505+ }
506+ return false ;
507+ }
0 commit comments