@@ -20,10 +20,13 @@ const localize = nls.loadMessageBundle();
2020export class RunWithoutDebuggingAdapter implements vscode . DebugAdapter {
2121 private readonly sendMessageEmitter = new vscode . EventEmitter < vscode . DebugProtocolMessage > ( ) ;
2222 public readonly onDidSendMessage : vscode . Event < vscode . DebugProtocolMessage > = this . sendMessageEmitter . event ;
23+ private readonly terminalListeners : vscode . Disposable [ ] = [ ] ;
2324
2425 private seq : number = 1 ;
2526 private childProcess ?: cp . ChildProcess ;
2627 private terminal ?: vscode . Terminal ;
28+ private terminalExecution ?: vscode . TerminalShellExecution ;
29+ private hasTerminated : boolean = false ;
2730
2831 public handleMessage ( message : vscode . DebugProtocolMessage ) : void {
2932 const msg = message as { type : string ; command : string ; seq : number ; arguments ?: any ; } ;
@@ -79,7 +82,7 @@ export class RunWithoutDebuggingAdapter implements vscode.DebugAdapter {
7982 this . sendResponse ( request , { } ) ;
8083
8184 if ( consoleMode === 'integratedTerminal' ) {
82- this . launchIntegratedTerminal ( program , args , cwd , env ) ;
85+ await this . launchIntegratedTerminal ( program , args , cwd , env ) ;
8386 } else if ( consoleMode === 'externalTerminal' ) {
8487 this . launchExternalTerminal ( program , args , cwd , env ) ;
8588 } else {
@@ -91,8 +94,7 @@ export class RunWithoutDebuggingAdapter implements vscode.DebugAdapter {
9194 * Launch the program in a VS Code integrated terminal.
9295 * The terminal will remain open after the program exits and be reused for the next session, if applicable.
9396 */
94- private launchIntegratedTerminal ( program : string , args : string [ ] , cwd : string | undefined , env : NodeJS . ProcessEnv ) {
95- const shellArgs : string [ ] = [ program , ...args ] . map ( a => this . quoteArg ( a ) ) ;
97+ private async launchIntegratedTerminal ( program : string , args : string [ ] , cwd : string | undefined , env : NodeJS . ProcessEnv ) : Promise < void > {
9698 const terminalName = path . normalize ( program ) ;
9799 const existingTerminal = vscode . window . terminals . find ( t => t . name === terminalName ) ;
98100 this . terminal = existingTerminal ?? vscode . window . createTerminal ( {
@@ -101,10 +103,21 @@ export class RunWithoutDebuggingAdapter implements vscode.DebugAdapter {
101103 env : env as Record < string , string >
102104 } ) ;
103105 this . terminal . show ( true ) ;
104- this . terminal . sendText ( shellArgs . join ( ' ' ) ) ;
105106
106- // The terminal manages its own lifecycle; notify VS Code the "debug" session is done.
107- this . sendEvent ( 'terminated' ) ;
107+ const shellIntegration : vscode . TerminalShellIntegration | undefined =
108+ this . terminal . shellIntegration ?? await this . waitForShellIntegration ( this . terminal , 3000 ) ;
109+
110+ // Not all terminals support shell integration. If it's not available, we'll just send the command as text though we won't be able to monitor its execution.
111+ if ( shellIntegration ) {
112+ this . monitorIntegratedTerminal ( this . terminal ) ;
113+ this . terminalExecution = shellIntegration . executeCommand ( program , args ) ;
114+ } else {
115+ const shellArgs : string [ ] = [ program , ...args ] . map ( a => this . quoteArg ( a ) ) ;
116+ this . terminal . sendText ( shellArgs . join ( ' ' ) ) ;
117+
118+ // The terminal manages its own lifecycle; notify VS Code the "debug" session is done.
119+ this . sendEvent ( 'terminated' ) ;
120+ }
108121 }
109122
110123 /**
@@ -194,6 +207,65 @@ export class RunWithoutDebuggingAdapter implements vscode.DebugAdapter {
194207 return / \s / . test ( arg ) ? `"${ this . escapeQuotes ( arg ) } "` : arg ;
195208 }
196209
210+ private waitForShellIntegration ( terminal : vscode . Terminal , timeoutMs : number ) : Promise < vscode . TerminalShellIntegration | undefined > {
211+ return new Promise ( resolve => {
212+ let resolved : boolean = false ;
213+ const done = ( shellIntegration : vscode . TerminalShellIntegration | undefined ) : void => {
214+ if ( resolved ) {
215+ return ;
216+ }
217+
218+ resolved = true ;
219+ clearTimeout ( timeout ) ;
220+ shellIntegrationChanged . dispose ( ) ;
221+ terminalClosed . dispose ( ) ;
222+ resolve ( shellIntegration ) ;
223+ } ;
224+
225+ const timeout = setTimeout ( ( ) => done ( undefined ) , timeoutMs ) ;
226+ const shellIntegrationChanged = vscode . window . onDidChangeTerminalShellIntegration ( event => {
227+ if ( event . terminal === terminal ) {
228+ done ( event . shellIntegration ) ;
229+ }
230+ } ) ;
231+ const terminalClosed = vscode . window . onDidCloseTerminal ( closedTerminal => {
232+ if ( closedTerminal === terminal ) {
233+ done ( undefined ) ;
234+ }
235+ } ) ;
236+ } ) ;
237+ }
238+
239+ private monitorIntegratedTerminal ( terminal : vscode . Terminal ) : void {
240+ this . disposeTerminalListeners ( ) ;
241+ this . terminalListeners . push (
242+ vscode . window . onDidEndTerminalShellExecution ( event => {
243+ if ( event . terminal !== terminal || event . execution !== this . terminalExecution || this . hasTerminated ) {
244+ return ;
245+ }
246+
247+ if ( event . exitCode !== undefined ) {
248+ this . sendEvent ( 'exited' , { exitCode : event . exitCode } ) ;
249+ }
250+
251+ this . sendEvent ( 'terminated' ) ;
252+ } ) ,
253+ vscode . window . onDidCloseTerminal ( closedTerminal => {
254+ if ( closedTerminal !== terminal || this . hasTerminated ) {
255+ return ;
256+ }
257+
258+ this . sendEvent ( 'terminated' ) ;
259+ } )
260+ ) ;
261+ }
262+
263+ private disposeTerminalListeners ( ) : void {
264+ while ( this . terminalListeners . length > 0 ) {
265+ this . terminalListeners . pop ( ) ?. dispose ( ) ;
266+ }
267+ }
268+
197269 private sendResponse ( request : { command : string ; seq : number ; } , body : object ) : void {
198270 this . sendMessageEmitter . fire ( {
199271 type : 'response' ,
@@ -206,6 +278,15 @@ export class RunWithoutDebuggingAdapter implements vscode.DebugAdapter {
206278 }
207279
208280 private sendEvent ( event : string , body ?: object ) : void {
281+ if ( event === 'terminated' ) {
282+ if ( this . hasTerminated ) {
283+ return ;
284+ }
285+
286+ this . hasTerminated = true ;
287+ this . disposeTerminalListeners ( ) ;
288+ }
289+
209290 this . sendMessageEmitter . fire ( {
210291 type : 'event' ,
211292 seq : this . seq ++ ,
@@ -216,6 +297,7 @@ export class RunWithoutDebuggingAdapter implements vscode.DebugAdapter {
216297
217298 public dispose ( ) : void {
218299 this . terminateProcess ( ) ;
300+ this . disposeTerminalListeners ( ) ;
219301 this . sendMessageEmitter . dispose ( ) ;
220302 }
221303
0 commit comments