44import * as fse from 'fs-extra' ;
55import * as path from 'path' ;
66import {
7+ ConfigurationChangeEvent ,
78 Disposable ,
89 EnvironmentVariableScope ,
910 GlobalEnvironmentVariableCollection ,
@@ -12,7 +13,7 @@ import {
1213} from 'vscode' ;
1314import { traceError , traceVerbose } from '../../common/logging' ;
1415import { resolveVariables } from '../../common/utils/internalVariables' ;
15- import { getConfiguration , getWorkspaceFolder } from '../../common/workspace.apis' ;
16+ import { getConfiguration , getWorkspaceFolder , onDidChangeConfiguration } from '../../common/workspace.apis' ;
1617import { showInformationMessage } from '../../common/window.apis' ;
1718import { EnvVarManager } from '../execution/envVariableManager' ;
1819
@@ -22,6 +23,7 @@ import { EnvVarManager } from '../execution/envVariableManager';
2223 */
2324export class TerminalEnvVarInjector implements Disposable {
2425 private disposables : Disposable [ ] = [ ] ;
26+ private readonly previousSettingsState = new Map < string , { useEnvFile : boolean | undefined ; envFile : string | undefined } > ( ) ;
2527
2628 constructor (
2729 private readonly envVarCollection : GlobalEnvironmentVariableCollection ,
@@ -36,6 +38,21 @@ export class TerminalEnvVarInjector implements Disposable {
3638 private async initialize ( ) : Promise < void > {
3739 traceVerbose ( 'TerminalEnvVarInjector: Initializing environment variable injection' ) ;
3840
41+ // Initialize previous settings state for all workspaces
42+ const workspaceFolders = workspace . workspaceFolders ;
43+ if ( workspaceFolders ) {
44+ for ( const folder of workspaceFolders ) {
45+ this . updatePreviousSettingsState ( folder ) ;
46+ }
47+ }
48+
49+ // Listen for configuration changes to show notifications when settings change
50+ this . disposables . push (
51+ onDidChangeConfiguration ( ( event : ConfigurationChangeEvent ) => {
52+ this . handleConfigurationChange ( event ) ;
53+ } ) ,
54+ ) ;
55+
3956 // Listen for environment variable changes from the manager
4057 this . disposables . push (
4158 this . envVarManager . onDidChangeEnvironmentVariables ( ( args ) => {
@@ -71,6 +88,109 @@ export class TerminalEnvVarInjector implements Disposable {
7188 await this . updateEnvironmentVariables ( ) ;
7289 }
7390
91+ /**
92+ * Handle configuration changes and show notifications when relevant settings change.
93+ */
94+ private handleConfigurationChange ( event : ConfigurationChangeEvent ) : void {
95+ const workspaceFolders = workspace . workspaceFolders ;
96+ if ( ! workspaceFolders ) {
97+ return ;
98+ }
99+
100+ for ( const folder of workspaceFolders ) {
101+ if ( event . affectsConfiguration ( 'python.terminal.useEnvFile' , folder . uri ) ||
102+ event . affectsConfiguration ( 'python.envFile' , folder . uri ) ) {
103+
104+ const folderKey = folder . uri . toString ( ) ;
105+ const previousState = this . previousSettingsState . get ( folderKey ) ;
106+ const currentState = this . getCurrentSettingsState ( folder ) ;
107+
108+ if ( previousState && this . shouldShowNotification ( previousState , currentState ) ) {
109+ this . showSettingsChangeNotification ( currentState ) ;
110+ }
111+
112+ this . previousSettingsState . set ( folderKey , currentState ) ;
113+
114+ // Update environment variables for this workspace
115+ this . updateEnvironmentVariables ( folder ) . catch ( ( error ) => {
116+ traceError ( 'Failed to update environment variables after configuration change:' , error ) ;
117+ } ) ;
118+ }
119+ }
120+ }
121+
122+ /**
123+ * Get current settings state for a workspace.
124+ */
125+ private getCurrentSettingsState ( workspaceFolder : WorkspaceFolder ) : { useEnvFile : boolean | undefined ; envFile : string | undefined } {
126+ const config = getConfiguration ( 'python' , workspaceFolder . uri ) ;
127+ return {
128+ useEnvFile : config . get < boolean > ( 'terminal.useEnvFile' ) ,
129+ envFile : config . get < string > ( 'envFile' )
130+ } ;
131+ }
132+
133+ /**
134+ * Update the previous settings state for a workspace.
135+ */
136+ private updatePreviousSettingsState ( workspaceFolder : WorkspaceFolder ) : void {
137+ const folderKey = workspaceFolder . uri . toString ( ) ;
138+ this . previousSettingsState . set ( folderKey , this . getCurrentSettingsState ( workspaceFolder ) ) ;
139+ }
140+
141+ /**
142+ * Determine if we should show a notification based on settings changes.
143+ */
144+ private shouldShowNotification (
145+ previousState : { useEnvFile : boolean | undefined ; envFile : string | undefined } ,
146+ currentState : { useEnvFile : boolean | undefined ; envFile : string | undefined }
147+ ) : boolean {
148+ // Show notification if:
149+ // 1. useEnvFile changed from undefined/false to true
150+ // 2. envFile changed from undefined to set (any string value)
151+ // And ensure both settings are configured correctly
152+
153+ const useEnvFileChanged = ! previousState . useEnvFile && currentState . useEnvFile ;
154+ const envFileChanged = ! previousState . envFile && ! ! currentState . envFile ;
155+
156+ if ( useEnvFileChanged || envFileChanged ) {
157+ // If one is set but the other isn't, show notification
158+ if ( ( currentState . envFile && ! currentState . useEnvFile ) ||
159+ ( currentState . useEnvFile && ! currentState . envFile ) ) {
160+ return true ;
161+ }
162+ }
163+
164+ return false ;
165+ }
166+
167+ /**
168+ * Show notification about settings configuration.
169+ */
170+ private showSettingsChangeNotification ( currentState : { useEnvFile : boolean | undefined ; envFile : string | undefined } ) : void {
171+ let message : string ;
172+
173+ if ( currentState . envFile && ! currentState . useEnvFile ) {
174+ message = 'The python.envFile setting is configured but will not take effect in terminals. Enable the "python.terminal.useEnvFile" setting to use environment variables from .env files in terminals.' ;
175+ } else if ( currentState . useEnvFile && ! currentState . envFile ) {
176+ message = 'The python.terminal.useEnvFile setting is enabled. Consider setting "python.envFile" to specify a custom .env file path, or ensure a .env file exists in your workspace root.' ;
177+ } else {
178+ return ; // Both are properly configured, no notification needed
179+ }
180+
181+ showInformationMessage ( message , 'Open Settings' ) . then ( ( selection ) => {
182+ if ( selection === 'Open Settings' ) {
183+ // Open VS Code settings to the relevant setting
184+ import ( 'vscode' ) . then ( vscode => {
185+ const settingToOpen = currentState . envFile && ! currentState . useEnvFile
186+ ? 'python.terminal.useEnvFile'
187+ : 'python.envFile' ;
188+ vscode . commands . executeCommand ( 'workbench.action.openSettings' , settingToOpen ) ;
189+ } ) ;
190+ }
191+ } ) ;
192+ }
193+
74194 /**
75195 * Update environment variables in the terminal collection.
76196 */
@@ -113,64 +233,47 @@ export class TerminalEnvVarInjector implements Disposable {
113233 // Check if environment variable injection is enabled
114234 const config = getConfiguration ( 'python' , workspaceUri ) ;
115235 const useEnvFile = config . get < boolean > ( 'terminal.useEnvFile' , false ) ;
116- const envFilePath = config . get < string > ( 'envFile' ) ;
117236
118237 // use scoped environment variable collection
119238 const envVarScope = this . getEnvironmentVariableCollectionScoped ( { workspaceFolder } ) ;
120239 envVarScope . clear ( ) ; // Clear existing variables for this workspace
121240
122- // Check if python.envFile is set but useEnvFile is false (default) and show notification
123- if ( ! useEnvFile && envFilePath ) {
241+ // Only inject if useEnvFile is true
242+ if ( useEnvFile ) {
124243 traceVerbose (
125- `TerminalEnvVarInjector: python.envFile is set but python.terminal.useEnvFile is false for workspace: ${ workspaceUri . fsPath } ` ,
244+ `TerminalEnvVarInjector: Environment variable injection enabled for workspace: ${ workspaceUri . fsPath } ` ,
126245 ) ;
127-
128- // Show information message to user
129- showInformationMessage (
130- 'The python.envFile setting is configured but will not take effect in terminals. Enable the "python.terminal.useEnvFile" setting to use environment variables from .env files in terminals.' ,
131- 'Open Settings'
132- ) . then ( ( selection ) => {
133- if ( selection === 'Open Settings' ) {
134- // Open VS Code settings to the python.terminal.useEnvFile setting
135- import ( 'vscode' ) . then ( vscode => {
136- vscode . commands . executeCommand ( 'workbench.action.openSettings' , 'python.terminal.useEnvFile' ) ;
137- } ) ;
138- }
139- } ) ;
140- }
141-
142- if ( ! useEnvFile ) {
143- traceVerbose (
144- `TerminalEnvVarInjector: Environment variable injection disabled for workspace: ${ workspaceUri . fsPath } ` ,
145- ) ;
146- return ; // Injection is disabled
147- }
148246
149- const envVars = await this . envVarManager . getEnvironmentVariables ( workspaceUri ) ;
247+ const envVars = await this . envVarManager . getEnvironmentVariables ( workspaceUri ) ;
150248
151- // Track which .env file is being used for logging
152- const resolvedEnvFilePath : string | undefined = envFilePath
153- ? path . resolve ( resolveVariables ( envFilePath , workspaceUri ) )
154- : undefined ;
155- const defaultEnvFilePath : string = path . join ( workspaceUri . fsPath , '.env' ) ;
249+ // Track which .env file is being used for logging
250+ const envFilePath = config . get < string > ( 'envFile' ) ;
251+ const resolvedEnvFilePath : string | undefined = envFilePath
252+ ? path . resolve ( resolveVariables ( envFilePath , workspaceUri ) )
253+ : undefined ;
254+ const defaultEnvFilePath : string = path . join ( workspaceUri . fsPath , '.env' ) ;
156255
157- let activeEnvFilePath : string = resolvedEnvFilePath || defaultEnvFilePath ;
158- if ( activeEnvFilePath && ( await fse . pathExists ( activeEnvFilePath ) ) ) {
159- traceVerbose ( `TerminalEnvVarInjector: Using env file: ${ activeEnvFilePath } ` ) ;
256+ let activeEnvFilePath : string = resolvedEnvFilePath || defaultEnvFilePath ;
257+ if ( activeEnvFilePath && ( await fse . pathExists ( activeEnvFilePath ) ) ) {
258+ traceVerbose ( `TerminalEnvVarInjector: Using env file: ${ activeEnvFilePath } ` ) ;
259+
260+ for ( const [ key , value ] of Object . entries ( envVars ) ) {
261+ if ( value === undefined ) {
262+ // Remove the environment variable if the value is undefined
263+ envVarScope . delete ( key ) ;
264+ } else {
265+ envVarScope . replace ( key , value ) ;
266+ }
267+ }
268+ } else {
269+ traceVerbose (
270+ `TerminalEnvVarInjector: No .env file found for workspace: ${ workspaceUri . fsPath } , not injecting environment variables.` ,
271+ ) ;
272+ }
160273 } else {
161274 traceVerbose (
162- `TerminalEnvVarInjector: No .env file found for workspace: ${ workspaceUri . fsPath } , not injecting environment variables. ` ,
275+ `TerminalEnvVarInjector: Environment variable injection disabled for workspace: ${ workspaceUri . fsPath } ` ,
163276 ) ;
164- return ; // No .env file to inject
165- }
166-
167- for ( const [ key , value ] of Object . entries ( envVars ) ) {
168- if ( value === undefined ) {
169- // Remove the environment variable if the value is undefined
170- envVarScope . delete ( key ) ;
171- } else {
172- envVarScope . replace ( key , value ) ;
173- }
174277 }
175278 } catch ( error ) {
176279 traceError (
@@ -185,7 +288,7 @@ export class TerminalEnvVarInjector implements Disposable {
185288 */
186289 dispose ( ) : void {
187290 traceVerbose ( 'TerminalEnvVarInjector: Disposing' ) ;
188- this . disposables . forEach ( ( disposable ) => disposable . dispose ( ) ) ;
291+ this . disposables . forEach ( ( disposable ) => disposable ? .dispose ( ) ) ;
189292 this . disposables = [ ] ;
190293
191294 // Clear all environment variables from the collection
0 commit comments