@@ -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 {
@@ -24,6 +24,11 @@ import { sleep } from '../utils/async';
2424import { useEnvExtension } from '../../envExt/api.internal' ;
2525import { ensureTerminalLegacy } from '../../envExt/api.legacy' ;
2626
27+ interface QueuedCommand {
28+ commandLine : string ;
29+ resolve : ( value : TerminalShellExecution | undefined ) => void ;
30+ }
31+
2732@injectable ( )
2833export class TerminalService implements ITerminalService , Disposable {
2934 private terminal ?: Terminal ;
@@ -33,8 +38,12 @@ export class TerminalService implements ITerminalService, Disposable {
3338 private terminalHelper : ITerminalHelper ;
3439 private terminalActivator : ITerminalActivator ;
3540 private terminalAutoActivator : ITerminalAutoActivation ;
41+ private applicationShell : IApplicationShell ;
3642 private readonly executeCommandListeners : Set < Disposable > = new Set ( ) ;
3743 private _terminalFirstLaunched : boolean = true ;
44+ private pythonReplCommandQueue : QueuedCommand [ ] = [ ] ;
45+ private isReplReady : boolean = false ;
46+ private replDataListener ?: Disposable ;
3847 public get onDidCloseTerminal ( ) : Event < void > {
3948 return this . terminalClosed . event . bind ( this . terminalClosed ) ;
4049 }
@@ -48,11 +57,13 @@ export class TerminalService implements ITerminalService, Disposable {
4857 this . terminalHelper = this . serviceContainer . get < ITerminalHelper > ( ITerminalHelper ) ;
4958 this . terminalManager = this . serviceContainer . get < ITerminalManager > ( ITerminalManager ) ;
5059 this . terminalAutoActivator = this . serviceContainer . get < ITerminalAutoActivation > ( ITerminalAutoActivation ) ;
60+ this . applicationShell = this . serviceContainer . get < IApplicationShell > ( IApplicationShell ) ;
5161 this . terminalManager . onDidCloseTerminal ( this . terminalCloseHandler , this , disposableRegistry ) ;
5262 this . terminalActivator = this . serviceContainer . get < ITerminalActivator > ( ITerminalActivator ) ;
5363 }
5464 public dispose ( ) {
5565 this . terminal ?. dispose ( ) ;
66+ this . disposeReplListener ( ) ;
5667
5768 if ( this . executeCommandListeners && this . executeCommandListeners . size > 0 ) {
5869 this . executeCommandListeners . forEach ( ( d ) => {
@@ -81,14 +92,76 @@ export class TerminalService implements ITerminalService, Disposable {
8192 commandLine : string ,
8293 isPythonShell : boolean ,
8394 ) : Promise < TerminalShellExecution | undefined > {
84- // TODO: First execution of shift+enter may get ignored when using sendText.
85- // Prevent Cannot read properties of undefined: https://github.com/microsoft/vscode-python-environments/issues/958
86- if ( ! this . terminal ) {
87- traceVerbose ( 'Terminal not available yet, cannot execute command' ) ;
88- return undefined ;
95+ if ( isPythonShell ) {
96+ if ( this . isReplReady ) {
97+ return this . executeCommandInternal ( commandLine , true ) ;
98+ }
99+
100+ // Queue command and start listening for REPL prompt if not already
101+ return new Promise < TerminalShellExecution | undefined > ( ( resolve ) => {
102+ this . pythonReplCommandQueue . push ( { commandLine, resolve } ) ;
103+ traceVerbose ( `Queued Python REPL command: ${ commandLine } ` ) ;
104+ this . startReplListener ( ) ;
105+ } ) ;
106+ }
107+
108+ // For non-Python shell commands, execute directly
109+ return this . executeCommandInternal ( commandLine , isPythonShell ) ;
110+ }
111+
112+ /**
113+ * Starts listening for the Python REPL prompt (>>>).
114+ * When detected, processes all queued commands.
115+ */
116+ private startReplListener ( ) : void {
117+ if ( this . replDataListener ) {
118+ return ;
89119 }
90120
121+ let terminalData = '' ;
122+
123+ this . replDataListener = this . applicationShell . onDidWriteTerminalData ( ( e ) => {
124+ if ( this . terminal && e . terminal === this . terminal ) {
125+ terminalData += e . data ;
126+ // Check for Python REPL prompt (>>>)
127+ if ( / > > > \s * $ / . test ( terminalData ) ) {
128+ traceVerbose ( 'Python REPL ready, detected >>> prompt' ) ;
129+ this . isReplReady = true ;
130+ this . disposeReplListener ( ) ;
131+ this . flushReplQueue ( ) ;
132+ }
133+ }
134+ } ) ;
135+ }
136+
137+ private disposeReplListener ( ) : void {
138+ if ( this . replDataListener ) {
139+ this . replDataListener . dispose ( ) ;
140+ this . replDataListener = undefined ;
141+ }
142+ }
143+
144+ private async flushReplQueue ( ) : Promise < void > {
145+ while ( this . pythonReplCommandQueue . length > 0 ) {
146+ const cmd = this . pythonReplCommandQueue . shift ( ) ;
147+ if ( cmd ) {
148+ traceVerbose ( `Executing queued REPL command: ${ cmd . commandLine } ` ) ;
149+ const result = await this . executeCommandInternal ( cmd . commandLine , true ) ;
150+ cmd . resolve ( result ) ;
151+ }
152+ }
153+ }
154+
155+ private async executeCommandInternal (
156+ commandLine : string ,
157+ isPythonShell : boolean ,
158+ ) : Promise < TerminalShellExecution | undefined > {
91159 const terminal = this . terminal ;
160+ if ( ! terminal ) {
161+ traceVerbose ( 'Terminal not available, cannot execute command' ) ;
162+ return undefined ;
163+ }
164+
92165 if ( ! this . options ?. hideFromUser ) {
93166 terminal . show ( true ) ;
94167 }
@@ -175,6 +248,14 @@ export class TerminalService implements ITerminalService, Disposable {
175248 if ( terminal === this . terminal ) {
176249 this . terminalClosed . fire ( ) ;
177250 this . terminal = undefined ;
251+ // Reset REPL state when terminal closes
252+ this . isReplReady = false ;
253+ this . disposeReplListener ( ) ;
254+ // Clear any pending commands
255+ while ( this . pythonReplCommandQueue . length > 0 ) {
256+ const cmd = this . pythonReplCommandQueue . shift ( ) ;
257+ cmd ?. resolve ( undefined ) ;
258+ }
178259 }
179260 }
180261
0 commit comments