@@ -8,30 +8,26 @@ import {
88 Terminal ,
99 TerminalShellExecutionEndEvent ,
1010 TerminalShellExecutionStartEvent ,
11- TerminalShellIntegrationChangeEvent ,
1211 Uri ,
12+ TerminalOptions ,
1313} from 'vscode' ;
1414import {
1515 createTerminal ,
16- onDidChangeTerminalShellIntegration ,
1716 onDidCloseTerminal ,
1817 onDidEndTerminalShellExecution ,
1918 onDidOpenTerminal ,
2019 onDidStartTerminalShellExecution ,
2120 terminals ,
2221 withProgress ,
2322} from '../../common/window.apis' ;
24- import { PythonEnvironment , PythonProject , PythonTerminalOptions } from '../../api' ;
23+ import { PythonEnvironment , PythonProject , PythonTerminalCreateOptions } from '../../api' ;
2524import { getActivationCommand , getDeactivationCommand , isActivatableEnvironment } from '../common/activation' ;
26- import { showErrorMessage } from '../../common/errors/utils' ;
2725import { quoteArgs } from '../execution/execUtils' ;
2826import { createDeferred } from '../../common/utils/deferred' ;
2927import { traceError , traceVerbose } from '../../common/logging' ;
3028import { getConfiguration } from '../../common/workspace.apis' ;
31- import { EnvironmentManagers } from '../../internal.api' ;
32-
33- const SHELL_INTEGRATION_TIMEOUT = 500 ; // 0.5 seconds
34- const SHELL_INTEGRATION_POLL_INTERVAL = 100 ; // 0.1 seconds
29+ import { EnvironmentManagers , PythonProjectManager } from '../../internal.api' ;
30+ import { waitForShellIntegration } from './utils' ;
3531
3632export interface TerminalActivation {
3733 isActivated ( terminal : Terminal , environment ?: PythonEnvironment ) : boolean ;
@@ -40,7 +36,7 @@ export interface TerminalActivation {
4036}
4137
4238export interface TerminalCreation {
43- create ( environment : PythonEnvironment , options : PythonTerminalOptions ) : Promise < Terminal > ;
39+ create ( environment : PythonEnvironment , options : PythonTerminalCreateOptions ) : Promise < Terminal > ;
4440}
4541
4642export interface TerminalGetters {
@@ -62,7 +58,7 @@ export interface TerminalEnvironment {
6258}
6359
6460export interface TerminalInit {
65- initialize ( projects : PythonProject [ ] , em : EnvironmentManagers ) : Promise < void > ;
61+ initialize ( ) : Promise < void > ;
6662}
6763
6864export interface TerminalManager
@@ -78,33 +74,28 @@ export class TerminalManagerImpl implements TerminalManager {
7874 private activatedTerminals = new Map < Terminal , PythonEnvironment > ( ) ;
7975 private activatingTerminals = new Map < Terminal , Promise < void > > ( ) ;
8076 private deactivatingTerminals = new Map < Terminal , Promise < void > > ( ) ;
77+ private skipActivationOnOpen = new Set < Terminal > ( ) ;
8178
8279 private onTerminalOpenedEmitter = new EventEmitter < Terminal > ( ) ;
8380 private onTerminalOpened = this . onTerminalOpenedEmitter . event ;
8481
8582 private onTerminalClosedEmitter = new EventEmitter < Terminal > ( ) ;
8683 private onTerminalClosed = this . onTerminalClosedEmitter . event ;
8784
88- private onTerminalShellIntegrationChangedEmitter = new EventEmitter < TerminalShellIntegrationChangeEvent > ( ) ;
89- private onTerminalShellIntegrationChanged = this . onTerminalShellIntegrationChangedEmitter . event ;
90-
9185 private onTerminalShellExecutionStartEmitter = new EventEmitter < TerminalShellExecutionStartEvent > ( ) ;
9286 private onTerminalShellExecutionStart = this . onTerminalShellExecutionStartEmitter . event ;
9387
9488 private onTerminalShellExecutionEndEmitter = new EventEmitter < TerminalShellExecutionEndEvent > ( ) ;
9589 private onTerminalShellExecutionEnd = this . onTerminalShellExecutionEndEmitter . event ;
9690
97- constructor ( ) {
91+ constructor ( private readonly projectManager : PythonProjectManager , private readonly em : EnvironmentManagers ) {
9892 this . disposables . push (
9993 onDidOpenTerminal ( ( t : Terminal ) => {
10094 this . onTerminalOpenedEmitter . fire ( t ) ;
10195 } ) ,
10296 onDidCloseTerminal ( ( t : Terminal ) => {
10397 this . onTerminalClosedEmitter . fire ( t ) ;
10498 } ) ,
105- onDidChangeTerminalShellIntegration ( ( e : TerminalShellIntegrationChangeEvent ) => {
106- this . onTerminalShellIntegrationChangedEmitter . fire ( e ) ;
107- } ) ,
10899 onDidStartTerminalShellExecution ( ( e : TerminalShellExecutionStartEvent ) => {
109100 this . onTerminalShellExecutionStartEmitter . fire ( e ) ;
110101 } ) ,
@@ -113,9 +104,20 @@ export class TerminalManagerImpl implements TerminalManager {
113104 } ) ,
114105 this . onTerminalOpenedEmitter ,
115106 this . onTerminalClosedEmitter ,
116- this . onTerminalShellIntegrationChangedEmitter ,
117107 this . onTerminalShellExecutionStartEmitter ,
118108 this . onTerminalShellExecutionEndEmitter ,
109+ this . onTerminalOpened ( async ( t ) => {
110+ if ( this . skipActivationOnOpen . has ( t ) || ( t . creationOptions as TerminalOptions ) ?. hideFromUser ) {
111+ return ;
112+ }
113+ await this . autoActivateOnTerminalOpen ( t ) ;
114+ } ) ,
115+ this . onTerminalClosed ( ( t ) => {
116+ this . activatedTerminals . delete ( t ) ;
117+ this . activatingTerminals . delete ( t ) ;
118+ this . deactivatingTerminals . delete ( t ) ;
119+ this . skipActivationOnOpen . delete ( t ) ;
120+ } ) ,
119121 ) ;
120122 }
121123
@@ -212,75 +214,36 @@ export class TerminalManagerImpl implements TerminalManager {
212214 }
213215 }
214216
215- private async activateEnvironmentOnCreation ( terminal : Terminal , environment : PythonEnvironment ) : Promise < void > {
216- const deferred = createDeferred < void > ( ) ;
217- const disposables : Disposable [ ] = [ ] ;
218- let disposeTimer : Disposable | undefined ;
219- let activated = false ;
220- this . activatingTerminals . set ( terminal , deferred . promise ) ;
217+ private async getActivationEnvironment ( ) : Promise < PythonEnvironment | undefined > {
218+ const projects = this . projectManager . getProjects ( ) ;
219+ const uri = projects . length === 0 ? undefined : projects [ 0 ] . uri ;
220+ const manager = this . em . getEnvironmentManager ( uri ) ;
221+ const env = await manager ?. get ( uri ) ;
222+ return env ;
223+ }
221224
222- try {
223- disposables . push (
224- new Disposable ( ( ) => {
225- this . activatingTerminals . delete ( terminal ) ;
226- } ) ,
227- this . onTerminalOpened ( async ( t : Terminal ) => {
228- if ( t === terminal ) {
229- if ( terminal . shellIntegration ) {
230- // Shell integration is available when the terminal is opened.
231- activated = true ;
232- await this . activateUsingShellIntegration ( terminal . shellIntegration , terminal , environment ) ;
233- deferred . resolve ( ) ;
234- } else {
235- let seconds = 0 ;
236- const timer = setInterval ( ( ) => {
237- seconds += SHELL_INTEGRATION_POLL_INTERVAL ;
238- if ( terminal . shellIntegration || activated ) {
239- disposeTimer ?. dispose ( ) ;
240- return ;
241- }
242-
243- if ( seconds >= SHELL_INTEGRATION_TIMEOUT ) {
244- disposeTimer ?. dispose ( ) ;
245- activated = true ;
246- this . activateLegacy ( terminal , environment ) ;
247- deferred . resolve ( ) ;
248- }
249- } , 100 ) ;
250-
251- disposeTimer = new Disposable ( ( ) => {
252- clearInterval ( timer ) ;
253- disposeTimer = undefined ;
254- } ) ;
255- }
256- }
257- } ) ,
258- this . onTerminalShellIntegrationChanged ( async ( e : TerminalShellIntegrationChangeEvent ) => {
259- if ( terminal === e . terminal && ! activated ) {
260- disposeTimer ?. dispose ( ) ;
261- activated = true ;
262- await this . activateUsingShellIntegration ( e . shellIntegration , terminal , environment ) ;
263- deferred . resolve ( ) ;
264- }
265- } ) ,
266- this . onTerminalClosed ( ( t ) => {
267- if ( terminal === t && ! deferred . completed ) {
268- deferred . reject ( new Error ( 'Terminal closed before activation' ) ) ;
269- }
270- } ) ,
271- new Disposable ( ( ) => {
272- disposeTimer ?. dispose ( ) ;
273- } ) ,
225+ private async autoActivateOnTerminalOpen ( terminal : Terminal , environment ?: PythonEnvironment ) : Promise < void > {
226+ const config = getConfiguration ( 'python' ) ;
227+ if ( ! config . get < boolean > ( 'terminal.activateEnvironment' , false ) ) {
228+ return ;
229+ }
230+
231+ const env = environment ?? ( await this . getActivationEnvironment ( ) ) ;
232+ if ( env && isActivatableEnvironment ( env ) ) {
233+ await withProgress (
234+ {
235+ location : ProgressLocation . Window ,
236+ title : `Activating environment: ${ env . displayName } ` ,
237+ } ,
238+ async ( ) => {
239+ await waitForShellIntegration ( terminal ) ;
240+ await this . activate ( terminal , env ) ;
241+ } ,
274242 ) ;
275- await deferred . promise ;
276- } catch ( ex ) {
277- traceError ( 'Failed to activate environment:\r\n' , ex ) ;
278- } finally {
279- disposables . forEach ( ( d ) => d . dispose ( ) ) ;
280243 }
281244 }
282245
283- public async create ( environment : PythonEnvironment , options : PythonTerminalOptions ) : Promise < Terminal > {
246+ public async create ( environment : PythonEnvironment , options : PythonTerminalCreateOptions ) : Promise < Terminal > {
284247 // const name = options.name ?? `Python: ${environment.displayName}`;
285248 const newTerminal = createTerminal ( {
286249 name : options . name ,
@@ -296,25 +259,17 @@ export class TerminalManagerImpl implements TerminalManager {
296259 location : options . location ,
297260 isTransient : options . isTransient ,
298261 } ) ;
299- const activatable = ! options . disableActivation && isActivatableEnvironment ( environment ) ;
300262
301- if ( activatable ) {
302- try {
303- await withProgress (
304- {
305- location : ProgressLocation . Window ,
306- title : `Activating ${ environment . displayName } ` ,
307- } ,
308- async ( ) => {
309- await this . activateEnvironmentOnCreation ( newTerminal , environment ) ;
310- } ,
311- ) ;
312- } catch ( e ) {
313- traceError ( 'Failed to activate environment:\r\n' , e ) ;
314- showErrorMessage ( `Failed to activate ${ environment . displayName } ` ) ;
315- }
263+ if ( options . disableActivation ) {
264+ this . skipActivationOnOpen . add ( newTerminal ) ;
265+ return newTerminal ;
316266 }
317267
268+ // We add it to skip activation on open to prevent double activation.
269+ // We can activate it ourselves since we are creating it.
270+ this . skipActivationOnOpen . add ( newTerminal ) ;
271+ await this . autoActivateOnTerminalOpen ( newTerminal , environment ) ;
272+
318273 return newTerminal ;
319274 }
320275
@@ -462,25 +417,15 @@ export class TerminalManagerImpl implements TerminalManager {
462417 }
463418 }
464419
465- public async initialize ( projects : PythonProject [ ] , em : EnvironmentManagers ) : Promise < void > {
420+ public async initialize ( ) : Promise < void > {
466421 const config = getConfiguration ( 'python' ) ;
467422 if ( config . get < boolean > ( 'terminal.activateEnvInCurrentTerminal' , false ) ) {
468423 await Promise . all (
469424 terminals ( ) . map ( async ( t ) => {
470- if ( projects . length === 0 ) {
471- const manager = em . getEnvironmentManager ( undefined ) ;
472- const env = await manager ?. get ( undefined ) ;
473- if ( env ) {
474- return this . activate ( t , env ) ;
475- }
476- } else if ( projects . length === 1 ) {
477- const manager = em . getEnvironmentManager ( projects [ 0 ] . uri ) ;
478- const env = await manager ?. get ( projects [ 0 ] . uri ) ;
479- if ( env ) {
480- return this . activate ( t , env ) ;
481- }
482- } else {
483- // TODO: handle multi project case
425+ this . skipActivationOnOpen . add ( t ) ;
426+ const env = await this . getActivationEnvironment ( ) ;
427+ if ( env && isActivatableEnvironment ( env ) ) {
428+ await this . activate ( t , env ) ;
484429 }
485430 } ) ,
486431 ) ;
0 commit comments