@@ -9,7 +9,7 @@ import { IServiceContainer } from '../../ioc/types';
99import { captureTelemetry } from '../../telemetry' ;
1010import { EventName } from '../../telemetry/constants' ;
1111import { ITerminalAutoActivation } from '../../terminals/types' ;
12- import { ITerminalManager } from '../application/types' ;
12+ import { IApplicationShell , ITerminalManager } from '../application/types' ;
1313import { _SCRIPTS_DIR } from '../process/internal/scripts/constants' ;
1414import { IConfigurationService , IDisposableRegistry } from '../types' ;
1515import {
@@ -20,9 +20,9 @@ import {
2020 TerminalShellType ,
2121} from './types' ;
2222import { traceVerbose } from '../../logging' ;
23+ import { sleep } from '../utils/async' ;
2324import { useEnvExtension } from '../../envExt/api.internal' ;
2425import { ensureTerminalLegacy } from '../../envExt/api.legacy' ;
25- import { sleep } from '../utils/async' ;
2626
2727@injectable ( )
2828export class TerminalService implements ITerminalService , Disposable {
@@ -33,8 +33,13 @@ export class TerminalService implements ITerminalService, Disposable {
3333 private terminalHelper : ITerminalHelper ;
3434 private terminalActivator : ITerminalActivator ;
3535 private terminalAutoActivator : ITerminalAutoActivation ;
36+ private applicationShell : IApplicationShell ;
3637 private readonly executeCommandListeners : Set < Disposable > = new Set ( ) ;
3738 private _terminalFirstLaunched : boolean = true ;
39+ private pythonReplCommandQueue : string [ ] = [ ] ;
40+ private isReplReady : boolean = false ;
41+ private replPromptListener ?: Disposable ;
42+ private replShellTypeListener ?: Disposable ;
3843 public get onDidCloseTerminal ( ) : Event < void > {
3944 return this . terminalClosed . event . bind ( this . terminalClosed ) ;
4045 }
@@ -48,11 +53,13 @@ export class TerminalService implements ITerminalService, Disposable {
4853 this . terminalHelper = this . serviceContainer . get < ITerminalHelper > ( ITerminalHelper ) ;
4954 this . terminalManager = this . serviceContainer . get < ITerminalManager > ( ITerminalManager ) ;
5055 this . terminalAutoActivator = this . serviceContainer . get < ITerminalAutoActivation > ( ITerminalAutoActivation ) ;
56+ this . applicationShell = this . serviceContainer . get < IApplicationShell > ( IApplicationShell ) ;
5157 this . terminalManager . onDidCloseTerminal ( this . terminalCloseHandler , this , disposableRegistry ) ;
5258 this . terminalActivator = this . serviceContainer . get < ITerminalActivator > ( ITerminalActivator ) ;
5359 }
5460 public dispose ( ) {
5561 this . terminal ?. dispose ( ) ;
62+ this . disposeReplListener ( ) ;
5663
5764 if ( this . executeCommandListeners && this . executeCommandListeners . size > 0 ) {
5865 this . executeCommandListeners . forEach ( ( d ) => {
@@ -81,7 +88,86 @@ export class TerminalService implements ITerminalService, Disposable {
8188 commandLine : string ,
8289 isPythonShell : boolean ,
8390 ) : Promise < TerminalShellExecution | undefined > {
84- const terminal = this . terminal ! ;
91+ if ( isPythonShell ) {
92+ if ( this . isReplReady ) {
93+ this . terminal ?. sendText ( commandLine ) ;
94+ traceVerbose ( `Python REPL sendText: ${ commandLine } ` ) ;
95+ } else {
96+ // Queue command to run once REPL is ready.
97+ this . pythonReplCommandQueue . push ( commandLine ) ;
98+ traceVerbose ( `Python REPL queued command: ${ commandLine } ` ) ;
99+ this . startReplListener ( ) ;
100+ }
101+ return undefined ;
102+ }
103+
104+ // Non-REPL code execution
105+ return this . executeCommandInternal ( commandLine ) ;
106+ }
107+
108+ private startReplListener ( ) : void {
109+ if ( this . replPromptListener || this . replShellTypeListener ) {
110+ return ;
111+ }
112+
113+ this . replShellTypeListener = this . terminalManager . onDidChangeTerminalState ( ( terminal ) => {
114+ if ( this . terminal && terminal === this . terminal ) {
115+ if ( terminal . state . shell == 'python' ) {
116+ traceVerbose ( 'Python REPL ready from terminal shell api' ) ;
117+ this . onReplReady ( ) ;
118+ }
119+ }
120+ } ) ;
121+
122+ let terminalData = '' ;
123+ this . replPromptListener = this . applicationShell . onDidWriteTerminalData ( ( e ) => {
124+ if ( this . terminal && e . terminal === this . terminal ) {
125+ terminalData += e . data ;
126+ if ( / > > > \s * $ / . test ( terminalData ) ) {
127+ traceVerbose ( 'Python REPL ready, from >>> prompt detection' ) ;
128+ this . onReplReady ( ) ;
129+ }
130+ }
131+ } ) ;
132+ }
133+
134+ private onReplReady ( ) : void {
135+ if ( this . isReplReady ) {
136+ return ;
137+ }
138+ this . isReplReady = true ;
139+ this . flushReplQueue ( ) ;
140+ this . disposeReplListener ( ) ;
141+ }
142+
143+ private disposeReplListener ( ) : void {
144+ if ( this . replPromptListener ) {
145+ this . replPromptListener . dispose ( ) ;
146+ this . replPromptListener = undefined ;
147+ }
148+ if ( this . replShellTypeListener ) {
149+ this . replShellTypeListener . dispose ( ) ;
150+ this . replShellTypeListener = undefined ;
151+ }
152+ }
153+
154+ private flushReplQueue ( ) : void {
155+ while ( this . pythonReplCommandQueue . length > 0 ) {
156+ const commandLine = this . pythonReplCommandQueue . shift ( ) ;
157+ if ( commandLine ) {
158+ traceVerbose ( `Executing queued REPL command: ${ commandLine } ` ) ;
159+ this . terminal ?. sendText ( commandLine ) ;
160+ }
161+ }
162+ }
163+
164+ private async executeCommandInternal ( commandLine : string ) : Promise < TerminalShellExecution | undefined > {
165+ const terminal = this . terminal ;
166+ if ( ! terminal ) {
167+ traceVerbose ( 'Terminal not available, cannot execute command' ) ;
168+ return undefined ;
169+ }
170+
85171 if ( ! this . options ?. hideFromUser ) {
86172 terminal . show ( true ) ;
87173 }
@@ -105,11 +191,7 @@ export class TerminalService implements ITerminalService, Disposable {
105191 await promise ;
106192 }
107193
108- if ( isPythonShell ) {
109- // Prevent KeyboardInterrupt in Python REPL: https://github.com/microsoft/vscode-python/issues/25468
110- terminal . sendText ( commandLine ) ;
111- traceVerbose ( `Python REPL detected, sendText: ${ commandLine } ` ) ;
112- } else if ( terminal . shellIntegration ) {
194+ if ( terminal . shellIntegration ) {
113195 const execution = terminal . shellIntegration . executeCommand ( commandLine ) ;
114196 traceVerbose ( `Shell Integration is enabled, executeCommand: ${ commandLine } ` ) ;
115197 return execution ;
@@ -138,6 +220,7 @@ export class TerminalService implements ITerminalService, Disposable {
138220 name : this . options ?. title || 'Python' ,
139221 hideFromUser : this . options ?. hideFromUser ,
140222 } ) ;
223+ return ;
141224 } else {
142225 this . terminalShellType = this . terminalHelper . identifyTerminalShell ( this . terminal ) ;
143226 this . terminal = this . terminalManager . createTerminal ( {
@@ -167,6 +250,9 @@ export class TerminalService implements ITerminalService, Disposable {
167250 if ( terminal === this . terminal ) {
168251 this . terminalClosed . fire ( ) ;
169252 this . terminal = undefined ;
253+ this . isReplReady = false ;
254+ this . disposeReplListener ( ) ;
255+ this . pythonReplCommandQueue = [ ] ;
170256 }
171257 }
172258
0 commit comments