@@ -5,8 +5,10 @@ import * as fse from 'fs-extra';
55import * as path from 'path' ;
66import {
77 Disposable ,
8+ EnvironmentVariableCollection ,
89 EnvironmentVariableScope ,
910 GlobalEnvironmentVariableCollection ,
11+ Uri ,
1012 window ,
1113 workspace ,
1214 WorkspaceFolder ,
@@ -23,6 +25,13 @@ import { EnvVarManager } from '../execution/envVariableManager';
2325export class TerminalEnvVarInjector implements Disposable {
2426 private disposables : Disposable [ ] = [ ] ;
2527
28+ /**
29+ * Track which environment variables we've set per workspace to properly handle
30+ * variables that are removed/commented out in .env files.
31+ * Key: workspace fsPath, Value: Set of env var keys we've set for that workspace
32+ */
33+ private readonly trackedEnvVars : Map < string , Set < string > > = new Map ( ) ;
34+
2635 constructor (
2736 private readonly envVarCollection : GlobalEnvironmentVariableCollection ,
2837 private readonly envVarManager : EnvVarManager ,
@@ -134,48 +143,22 @@ export class TerminalEnvVarInjector implements Disposable {
134143 */
135144 private async injectEnvironmentVariablesForWorkspace ( workspaceFolder : WorkspaceFolder ) : Promise < void > {
136145 const workspaceUri = workspaceFolder . uri ;
137- try {
138- const envVars = await this . envVarManager . getEnvironmentVariables ( workspaceUri ) ;
146+ const workspaceKey = workspaceUri . fsPath ;
139147
140- // use scoped environment variable collection
148+ try {
141149 const envVarScope = this . getEnvironmentVariableCollectionScoped ( { workspaceFolder } ) ;
142150
143- // Check if env file injection is enabled
144- const config = getConfiguration ( 'python' , workspaceUri ) ;
145- const useEnvFile = config . get < boolean > ( 'terminal.useEnvFile' , false ) ;
146- const envFilePath = config . get < string > ( 'envFile' ) ;
147-
148- if ( ! useEnvFile ) {
149- traceVerbose (
150- `TerminalEnvVarInjector: Env file injection disabled for workspace: ${ workspaceUri . fsPath } ` ,
151- ) ;
151+ // Check if we should inject and get the env file path
152+ const shouldInject = await this . shouldInjectEnvVars ( workspaceUri , envVarScope , workspaceKey ) ;
153+ if ( ! shouldInject ) {
152154 return ;
153155 }
154156
155- // Track which .env file is being used for logging
156- const resolvedEnvFilePath : string | undefined = envFilePath
157- ? path . resolve ( resolveVariables ( envFilePath , workspaceUri ) )
158- : undefined ;
159- const defaultEnvFilePath : string = path . join ( workspaceUri . fsPath , '.env' ) ;
160-
161- let activeEnvFilePath : string = resolvedEnvFilePath || defaultEnvFilePath ;
162- if ( activeEnvFilePath && ( await fse . pathExists ( activeEnvFilePath ) ) ) {
163- traceVerbose ( `TerminalEnvVarInjector: Using env file: ${ activeEnvFilePath } ` ) ;
164- } else {
165- traceVerbose (
166- `TerminalEnvVarInjector: No .env file found for workspace: ${ workspaceUri . fsPath } , not injecting environment variables.` ,
167- ) ;
168- return ; // No .env file to inject
169- }
157+ // Get environment variables from the .env file
158+ const envVars = await this . envVarManager . getEnvironmentVariables ( workspaceUri ) ;
170159
171- for ( const [ key , value ] of Object . entries ( envVars ) ) {
172- if ( value === undefined ) {
173- // Remove the environment variable if the value is undefined
174- envVarScope . delete ( key ) ;
175- } else {
176- envVarScope . replace ( key , value ) ;
177- }
178- }
160+ // Apply the environment variable changes
161+ this . applyEnvVarChanges ( envVarScope , envVars , workspaceKey ) ;
179162 } catch ( error ) {
180163 traceError (
181164 `TerminalEnvVarInjector: Error injecting environment variables for workspace ${ workspaceUri . fsPath } :` ,
@@ -184,6 +167,91 @@ export class TerminalEnvVarInjector implements Disposable {
184167 }
185168 }
186169
170+ /**
171+ * Check if environment variables should be injected for the workspace.
172+ * Returns true if injection should proceed, false otherwise.
173+ */
174+ private async shouldInjectEnvVars (
175+ workspaceUri : Uri ,
176+ envVarScope : EnvironmentVariableCollection ,
177+ workspaceKey : string ,
178+ ) : Promise < boolean > {
179+ const config = getConfiguration ( 'python' , workspaceUri ) ;
180+ const useEnvFile = config . get < boolean > ( 'terminal.useEnvFile' , false ) ;
181+ const envFilePath = config . get < string > ( 'envFile' ) ;
182+
183+ if ( ! useEnvFile ) {
184+ traceVerbose ( `TerminalEnvVarInjector: Env file injection disabled for workspace: ${ workspaceUri . fsPath } ` ) ;
185+ this . cleanupTrackedVars ( envVarScope , workspaceKey ) ;
186+ return false ;
187+ }
188+
189+ // Determine which .env file to use
190+ const resolvedEnvFilePath : string | undefined = envFilePath
191+ ? path . resolve ( resolveVariables ( envFilePath , workspaceUri ) )
192+ : undefined ;
193+ const defaultEnvFilePath : string = path . join ( workspaceUri . fsPath , '.env' ) ;
194+ const activeEnvFilePath : string = resolvedEnvFilePath || defaultEnvFilePath ;
195+
196+ if ( activeEnvFilePath && ( await fse . pathExists ( activeEnvFilePath ) ) ) {
197+ traceVerbose ( `TerminalEnvVarInjector: Using env file: ${ activeEnvFilePath } ` ) ;
198+ return true ;
199+ } else {
200+ traceVerbose (
201+ `TerminalEnvVarInjector: No .env file found for workspace: ${ workspaceUri . fsPath } , not injecting environment variables.` ,
202+ ) ;
203+ this . cleanupTrackedVars ( envVarScope , workspaceKey ) ;
204+ return false ;
205+ }
206+ }
207+
208+ /**
209+ * Apply environment variable changes by comparing current and previous state.
210+ */
211+ private applyEnvVarChanges (
212+ envVarScope : EnvironmentVariableCollection ,
213+ envVars : { [ key : string ] : string | undefined } ,
214+ workspaceKey : string ,
215+ ) : void {
216+ const previousKeys = this . trackedEnvVars . get ( workspaceKey ) || new Set < string > ( ) ;
217+ const currentKeys = new Set < string > ( ) ;
218+
219+ // Update/add current env vars from .env file
220+ for ( const [ key , value ] of Object . entries ( envVars ) ) {
221+ if ( value === undefined || value === '' ) {
222+ // Variable explicitly unset in .env (e.g., VAR=)
223+ envVarScope . delete ( key ) ;
224+ } else {
225+ envVarScope . replace ( key , value ) ;
226+ currentKeys . add ( key ) ;
227+ }
228+ }
229+
230+ // Delete keys that were previously set but are now gone from .env
231+ for ( const oldKey of previousKeys ) {
232+ if ( ! currentKeys . has ( oldKey ) ) {
233+ traceVerbose (
234+ `TerminalEnvVarInjector: Removing previously set env var '${ oldKey } ' from workspace ${ workspaceKey } ` ,
235+ ) ;
236+ envVarScope . delete ( oldKey ) ;
237+ }
238+ }
239+
240+ // Update our tracking for this workspace
241+ this . trackedEnvVars . set ( workspaceKey , currentKeys ) ;
242+ }
243+
244+ /**
245+ * Clean up previously tracked environment variables for a workspace.
246+ */
247+ private cleanupTrackedVars ( envVarScope : EnvironmentVariableCollection , workspaceKey : string ) : void {
248+ const previousKeys = this . trackedEnvVars . get ( workspaceKey ) ;
249+ if ( previousKeys ) {
250+ previousKeys . forEach ( ( key ) => envVarScope . delete ( key ) ) ;
251+ this . trackedEnvVars . delete ( workspaceKey ) ;
252+ }
253+ }
254+
187255 /**
188256 * Dispose of the injector and clean up resources.
189257 */
@@ -192,8 +260,20 @@ export class TerminalEnvVarInjector implements Disposable {
192260 this . disposables . forEach ( ( disposable ) => disposable . dispose ( ) ) ;
193261 this . disposables = [ ] ;
194262
195- // Clear all environment variables from the collection
196- this . envVarCollection . clear ( ) ;
263+ // Clear only the environment variables that we've set, preserving others (e.g., BASH_ENV)
264+ for ( const [ workspaceKey , trackedKeys ] of this . trackedEnvVars . entries ( ) ) {
265+ try {
266+ // Try to find the workspace folder for proper scoping
267+ const workspaceFolder = workspace . workspaceFolders ?. find ( ( wf ) => wf . uri . fsPath === workspaceKey ) ;
268+ if ( workspaceFolder ) {
269+ const scope = this . getEnvironmentVariableCollectionScoped ( { workspaceFolder } ) ;
270+ trackedKeys . forEach ( ( key ) => scope . delete ( key ) ) ;
271+ }
272+ } catch ( error ) {
273+ traceError ( `Failed to clean up environment variables for workspace ${ workspaceKey } :` , error ) ;
274+ }
275+ }
276+ this . trackedEnvVars . clear ( ) ;
197277 }
198278
199279 private getEnvironmentVariableCollectionScoped ( scope : EnvironmentVariableScope = { } ) {
@@ -205,9 +285,16 @@ export class TerminalEnvVarInjector implements Disposable {
205285 * Clear all environment variables for a workspace.
206286 */
207287 private clearWorkspaceVariables ( workspaceFolder : WorkspaceFolder ) : void {
288+ const workspaceKey = workspaceFolder . uri . fsPath ;
208289 try {
209290 const scope = this . getEnvironmentVariableCollectionScoped ( { workspaceFolder } ) ;
210- scope . clear ( ) ;
291+
292+ // Only delete env vars that we've set, not ones set by other managers (e.g., BASH_ENV)
293+ const trackedKeys = this . trackedEnvVars . get ( workspaceKey ) ;
294+ if ( trackedKeys ) {
295+ trackedKeys . forEach ( ( key ) => scope . delete ( key ) ) ;
296+ this . trackedEnvVars . delete ( workspaceKey ) ;
297+ }
211298 } catch ( error ) {
212299 traceError ( `Failed to clear environment variables for workspace ${ workspaceFolder . uri . fsPath } :` , error ) ;
213300 }
0 commit comments