@@ -97,6 +97,14 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
9797
9898 public async activate ( resource : Resource ) : Promise < void > {
9999 try {
100+ // Check shellIntegration.activate first - this should work regardless of
101+ // env extension or terminalEnvVar experiment
102+ const settings = this . configurationService . getSettings ( resource ) ;
103+ if ( settings . terminal . shellIntegration . activate ) {
104+ await this . activateUsingEnvVar ( resource ) ;
105+ return ;
106+ }
107+
100108 if ( useEnvExtension ( ) ) {
101109 traceVerbose ( 'Ignoring environment variable experiment since env extension is being used' ) ;
102110 this . context . environmentVariableCollection . clear ( ) ;
@@ -170,6 +178,50 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
170178 }
171179 }
172180
181+ /**
182+ * Activates environments using shell-specific environment variables (e.g., VSCODE_PYTHON_BASH_ACTIVATE).
183+ * This method works independently of the env extension or terminalEnvVar experiment.
184+ */
185+ private async activateUsingEnvVar ( resource : Resource ) : Promise < void > {
186+ if ( ! this . registeredOnce ) {
187+ this . interpreterService . onDidChangeInterpreter (
188+ async ( r ) => {
189+ const settings = this . configurationService . getSettings ( r ) ;
190+ if ( settings . terminal . shellIntegration . activate ) {
191+ await this . applyActivateEnvVarForResource ( r ) . ignoreErrors ( ) ;
192+ }
193+ } ,
194+ this ,
195+ this . disposables ,
196+ ) ;
197+ this . applicationEnvironment . onDidChangeShell (
198+ async ( shell : string ) => {
199+ const settings = this . configurationService . getSettings ( resource ) ;
200+ if ( settings . terminal . shellIntegration . activate ) {
201+ await this . applyActivateEnvVarForResource ( resource , shell ) . ignoreErrors ( ) ;
202+ }
203+ } ,
204+ this ,
205+ this . disposables ,
206+ ) ;
207+ this . registeredOnce = true ;
208+ }
209+ await this . applyActivateEnvVarForResource ( resource ) ;
210+ await registerPythonStartup ( this . context ) ;
211+ }
212+
213+ /**
214+ * Applies the shell-specific activate environment variable for a given resource.
215+ */
216+ private async applyActivateEnvVarForResource (
217+ resource : Resource ,
218+ shell = this . applicationEnvironment . shell ,
219+ ) : Promise < void > {
220+ const workspaceFolder = this . getWorkspaceFolder ( resource ) ;
221+ const envVarCollection = this . getEnvironmentVariableCollection ( { workspaceFolder } ) ;
222+ await this . applyActivateEnvVar ( resource , shell , envVarCollection ) ;
223+ }
224+
173225 public async _applyCollection ( resource : Resource , shell ?: string ) : Promise < void > {
174226 this . progressService . showProgress ( {
175227 location : ProgressLocation . Window ,
@@ -197,6 +249,7 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
197249 traceVerbose ( 'Activating environments in terminal is disabled for' , resource ?. fsPath ) ;
198250 return ;
199251 }
252+
200253 const activatedEnv = await this . environmentActivationService . getActivatedEnvironmentVariables (
201254 resource ,
202255 undefined ,
@@ -308,6 +361,183 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ
308361 } ) ;
309362 }
310363
364+ /**
365+ * Applies the VSCODE_PYTHON_*_ACTIVATE environment variables to enable activation
366+ * by contributing the full activation command via environment variable
367+ * instead of sending activation commands directly to the terminal.
368+ * Sets ALL shell-specific variables at once so any shell can be activated.
369+ * Supports bash, zsh, fish, PowerShell, and Command Prompt.
370+ */
371+ private async applyActivateEnvVar (
372+ resource : Resource ,
373+ _shell : string ,
374+ envVarCollection : ReturnType < typeof this . getEnvironmentVariableCollection > ,
375+ ) : Promise < void > {
376+ const interpreter = await this . interpreterService . getActiveInterpreter ( resource ) ;
377+ if ( ! interpreter ) {
378+ traceVerbose ( 'No interpreter found for shell integration activation' ) ;
379+ envVarCollection . clear ( ) ;
380+ return ;
381+ }
382+
383+ // For virtual environments, get the bin directory
384+ if ( interpreter . envType !== EnvironmentType . Venv && interpreter . type !== PythonEnvType . Virtual ) {
385+ traceVerbose ( 'Shell integration activation only supports virtual environments' ) ;
386+ envVarCollection . clear ( ) ;
387+ return ;
388+ }
389+
390+ const binDir = path . dirname ( interpreter . path ) ;
391+
392+ // Clear any previously set env vars
393+ envVarCollection . clear ( ) ;
394+
395+ const options = {
396+ applyAtShellIntegration : true ,
397+ applyAtProcessCreation : true ,
398+ } ;
399+
400+ // Set ALL shell-specific environment variables at once
401+ // Bash
402+ const bashActivate = path . join ( binDir , 'activate' ) ;
403+ if ( await pathExists ( bashActivate ) ) {
404+ const bashCommand = `source ${ bashActivate } ` ;
405+ traceLog ( `Setting VSCODE_PYTHON_BASH_ACTIVATE to ${ bashCommand } ` ) ;
406+ envVarCollection . replace ( 'VSCODE_PYTHON_BASH_ACTIVATE' , bashCommand , options ) ;
407+
408+ // ZSH uses the same activate script
409+ traceLog ( `Setting VSCODE_PYTHON_ZSH_ACTIVATE to ${ bashCommand } ` ) ;
410+ envVarCollection . replace ( 'VSCODE_PYTHON_ZSH_ACTIVATE' , bashCommand , options ) ;
411+ }
412+
413+ // Fish
414+ const fishActivate = path . join ( binDir , 'activate.fish' ) ;
415+ if ( await pathExists ( fishActivate ) ) {
416+ const fishCommand = `source ${ fishActivate } ` ;
417+ traceLog ( `Setting VSCODE_PYTHON_FISH_ACTIVATE to ${ fishCommand } ` ) ;
418+ envVarCollection . replace ( 'VSCODE_PYTHON_FISH_ACTIVATE' , fishCommand , options ) ;
419+ }
420+
421+ // PowerShell
422+ const pwshActivate = path . join ( binDir , 'Activate.ps1' ) ;
423+ if ( await pathExists ( pwshActivate ) ) {
424+ const pwshCommand = `& ${ pwshActivate } ` ;
425+ traceLog ( `Setting VSCODE_PYTHON_PWSH_ACTIVATE to ${ pwshCommand } ` ) ;
426+ envVarCollection . replace ( 'VSCODE_PYTHON_PWSH_ACTIVATE' , pwshCommand , options ) ;
427+ }
428+
429+ // Command Prompt
430+ const cmdActivate = path . join ( binDir , 'activate.bat' ) ;
431+ if ( await pathExists ( cmdActivate ) ) {
432+ traceLog ( `Setting VSCODE_PYTHON_CMD_ACTIVATE to ${ cmdActivate } ` ) ;
433+ envVarCollection . replace ( 'VSCODE_PYTHON_CMD_ACTIVATE' , cmdActivate , options ) ;
434+ }
435+
436+ const workspaceFolder = this . getWorkspaceFolder ( resource ) ;
437+ const settings = this . configurationService . getSettings ( resource ) ;
438+ const displayPath = this . pathUtils . getDisplayName ( settings . pythonPath , workspaceFolder ?. uri . fsPath ) ;
439+ const description = new MarkdownString (
440+ `${ Interpreters . activateTerminalDescription } \`${ displayPath } \` (via shell integration)` ,
441+ ) ;
442+ envVarCollection . description = description ;
443+ }
444+
445+ /**
446+ * Builds the full activation command for the given shell type and script path.
447+ */
448+ private buildActivateCommand ( shellType : TerminalShellType , scriptPath : string ) : string {
449+ switch ( shellType ) {
450+ case TerminalShellType . bash :
451+ case TerminalShellType . gitbash :
452+ case TerminalShellType . wsl :
453+ case TerminalShellType . zsh :
454+ case TerminalShellType . fish :
455+ return `source ${ scriptPath } ` ;
456+ case TerminalShellType . powershell :
457+ case TerminalShellType . powershellCore :
458+ return `& ${ scriptPath } ` ;
459+ case TerminalShellType . commandPrompt :
460+ return scriptPath ;
461+ default :
462+ return `source ${ scriptPath } ` ;
463+ }
464+ }
465+
466+ /**
467+ * Returns the environment variable name for shell integration activation based on shell type.
468+ * Only supports bash, fish, PowerShell, and Command Prompt.
469+ */
470+ private getShellActivateEnvVarName ( shellType : TerminalShellType ) : string | undefined {
471+ switch ( shellType ) {
472+ case TerminalShellType . bash :
473+ case TerminalShellType . gitbash :
474+ case TerminalShellType . wsl :
475+ return 'VSCODE_PYTHON_BASH_ACTIVATE' ;
476+ case TerminalShellType . zsh :
477+ return 'VSCODE_PYTHON_ZSH_ACTIVATE' ;
478+ case TerminalShellType . fish :
479+ return 'VSCODE_PYTHON_FISH_ACTIVATE' ;
480+ case TerminalShellType . powershell :
481+ case TerminalShellType . powershellCore :
482+ return 'VSCODE_PYTHON_PS1_ACTIVATE' ;
483+ case TerminalShellType . commandPrompt :
484+ return 'VSCODE_PYTHON_CMD_ACTIVATE' ;
485+ default :
486+ return undefined ;
487+ }
488+ }
489+
490+ /**
491+ * Gets the path to the activate script for the given interpreter.
492+ */
493+ private async getActivateScriptPath ( interpreter : PythonEnvironment , shell : string ) : Promise < string | undefined > {
494+ const shellType = identifyShellFromShellPath ( shell ) ;
495+
496+ // For virtual environments, look for activate script in bin directory
497+ if ( interpreter . envType === EnvironmentType . Venv || interpreter . type === PythonEnvType . Virtual ) {
498+ const binDir = path . dirname ( interpreter . path ) ;
499+ const activateScripts = this . getActivateScriptNames ( shellType ) ;
500+
501+ if ( ! activateScripts ) {
502+ return undefined ;
503+ }
504+
505+ for ( const scriptName of activateScripts ) {
506+ const scriptPath = path . join ( binDir , scriptName ) ;
507+ if ( await pathExists ( scriptPath ) ) {
508+ return scriptPath ;
509+ }
510+ }
511+ }
512+
513+ // For conda environments, we would need a different approach
514+ // For now, return undefined for unsupported environment types
515+ return undefined ;
516+ }
517+
518+ /**
519+ * Returns the activate script names to look for based on shell type.
520+ * Only supports bash, fish, PowerShell, and Command Prompt.
521+ */
522+ private getActivateScriptNames ( shellType : TerminalShellType ) : string [ ] | undefined {
523+ switch ( shellType ) {
524+ case TerminalShellType . bash :
525+ case TerminalShellType . gitbash :
526+ case TerminalShellType . zsh :
527+ case TerminalShellType . wsl :
528+ return [ 'activate' , 'activate.sh' ] ;
529+ case TerminalShellType . fish :
530+ return [ 'activate.fish' ] ;
531+ case TerminalShellType . powershell :
532+ case TerminalShellType . powershellCore :
533+ return [ 'Activate.ps1' ] ;
534+ case TerminalShellType . commandPrompt :
535+ return [ 'activate.bat' ] ;
536+ default :
537+ return undefined ;
538+ }
539+ }
540+
311541 private isPromptSet = new Map < number | undefined , boolean > ( ) ;
312542
313543 // eslint-disable-next-line class-methods-use-this
0 commit comments