Skip to content

Commit bd1bd4c

Browse files
Copiloteleanorjboyd
andcommitted
Implement environment variable injection into terminal using GlobalEnvironmentVariableCollection
Co-authored-by: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com>
1 parent 48cfa90 commit bd1bd4c

4 files changed

Lines changed: 463 additions & 0 deletions

File tree

src/extension.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import { ShellStartupActivationVariablesManagerImpl } from './features/terminal/
5858
import { cleanupStartupScripts } from './features/terminal/shellStartupSetupHandlers';
5959
import { TerminalActivationImpl } from './features/terminal/terminalActivationState';
6060
import { TerminalManager, TerminalManagerImpl } from './features/terminal/terminalManager';
61+
import { TerminalEnvVarInjector } from './features/terminal/terminalEnvVarInjector';
6162
import { getAutoActivationType, getEnvironmentForTerminal } from './features/terminal/utils';
6263
import { EnvManagerView } from './features/views/envManagersView';
6364
import { ProjectView } from './features/views/projectView';
@@ -239,6 +240,13 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
239240
api,
240241
);
241242

243+
// Initialize terminal environment variable injection
244+
const terminalEnvVarInjector = new TerminalEnvVarInjector(
245+
context.environmentVariableCollection,
246+
envVarManager,
247+
);
248+
context.subscriptions.push(terminalEnvVarInjector);
249+
242250
context.subscriptions.push(
243251
shellStartupVarsMgr,
244252
registerCompletionProvider(envManagers),
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import * as path from 'path';
5+
import * as fsapi from 'fs-extra';
6+
import { Disposable, Uri, workspace, GlobalEnvironmentVariableCollection } from 'vscode';
7+
import { traceVerbose, traceError } from '../../common/logging';
8+
import { getConfiguration, onDidChangeConfiguration } from '../../common/workspace.apis';
9+
import { EnvVarManager } from '../execution/envVariableManager';
10+
11+
/**
12+
* Manages injection of workspace-specific environment variables into VS Code terminals
13+
* using the GlobalEnvironmentVariableCollection API.
14+
*/
15+
export class TerminalEnvVarInjector implements Disposable {
16+
private disposables: Disposable[] = [];
17+
18+
constructor(
19+
private readonly envVarCollection: GlobalEnvironmentVariableCollection,
20+
private readonly envVarManager: EnvVarManager,
21+
) {
22+
this.initialize();
23+
}
24+
25+
/**
26+
* Initialize the injector by setting up watchers and injecting initial environment variables.
27+
*/
28+
private async initialize(): Promise<void> {
29+
traceVerbose('TerminalEnvVarInjector: Initializing environment variable injection');
30+
31+
// Listen for configuration changes to python.envFile setting
32+
this.disposables.push(
33+
onDidChangeConfiguration((e) => {
34+
if (e.affectsConfiguration('python.envFile')) {
35+
traceVerbose('TerminalEnvVarInjector: python.envFile setting changed, reloading env vars');
36+
this.updateEnvironmentVariables().catch((error) => {
37+
traceError('TerminalEnvVarInjector: Error updating env vars after setting change:', error);
38+
});
39+
}
40+
}),
41+
);
42+
43+
// Listen for environment variable changes from the manager
44+
this.disposables.push(
45+
this.envVarManager.onDidChangeEnvironmentVariables(() => {
46+
traceVerbose('TerminalEnvVarInjector: Environment variables changed, reloading');
47+
this.updateEnvironmentVariables().catch((error) => {
48+
traceError('TerminalEnvVarInjector: Error updating env vars after change event:', error);
49+
});
50+
}),
51+
);
52+
53+
// Initial load of environment variables
54+
await this.updateEnvironmentVariables();
55+
}
56+
57+
/**
58+
* Update environment variables in the terminal collection.
59+
*/
60+
private async updateEnvironmentVariables(): Promise<void> {
61+
try {
62+
// Clear existing environment variables
63+
traceVerbose('TerminalEnvVarInjector: Clearing existing environment variables');
64+
this.envVarCollection.clear();
65+
66+
// Get environment variables for all workspace folders
67+
const workspaceFolders = workspace.workspaceFolders;
68+
if (!workspaceFolders || workspaceFolders.length === 0) {
69+
traceVerbose('TerminalEnvVarInjector: No workspace folders found, skipping env var injection');
70+
return;
71+
}
72+
73+
// Process environment variables for each workspace folder
74+
for (const folder of workspaceFolders) {
75+
await this.injectEnvironmentVariablesForWorkspace(folder.uri);
76+
}
77+
78+
traceVerbose('TerminalEnvVarInjector: Environment variable injection completed');
79+
} catch (error) {
80+
traceError('TerminalEnvVarInjector: Error updating environment variables:', error);
81+
}
82+
}
83+
84+
/**
85+
* Inject environment variables for a specific workspace.
86+
*/
87+
private async injectEnvironmentVariablesForWorkspace(workspaceUri: Uri): Promise<void> {
88+
try {
89+
traceVerbose(`TerminalEnvVarInjector: Processing workspace: ${workspaceUri.fsPath}`);
90+
91+
// Get environment variables for this workspace
92+
const envVars = await this.envVarManager.getEnvironmentVariables(workspaceUri);
93+
94+
// Track which .env file is being used for logging
95+
const config = getConfiguration('python', workspaceUri);
96+
const envFilePath = config.get<string>('envFile');
97+
const resolvedEnvFilePath = envFilePath ? path.resolve(envFilePath) : undefined;
98+
const defaultEnvFilePath = path.join(workspaceUri.fsPath, '.env');
99+
100+
let activeEnvFilePath: string | undefined;
101+
if (resolvedEnvFilePath && (await fsapi.pathExists(resolvedEnvFilePath))) {
102+
activeEnvFilePath = resolvedEnvFilePath;
103+
traceVerbose(`TerminalEnvVarInjector: Using python.envFile setting: ${activeEnvFilePath}`);
104+
} else if (await fsapi.pathExists(defaultEnvFilePath)) {
105+
activeEnvFilePath = defaultEnvFilePath;
106+
traceVerbose(`TerminalEnvVarInjector: Using default .env file: ${activeEnvFilePath}`);
107+
} else {
108+
traceVerbose(`TerminalEnvVarInjector: No .env file found for workspace: ${workspaceUri.fsPath}`);
109+
}
110+
111+
// Inject environment variables into the collection
112+
let injectedCount = 0;
113+
for (const [key, value] of Object.entries(envVars)) {
114+
if (value !== undefined && value !== process.env[key]) {
115+
// Only inject if the value is different from the current process environment
116+
this.envVarCollection.replace(key, value);
117+
injectedCount++;
118+
traceVerbose(`TerminalEnvVarInjector: Injected ${key}=${value}`);
119+
}
120+
}
121+
122+
if (injectedCount > 0) {
123+
traceVerbose(
124+
`TerminalEnvVarInjector: Injected ${injectedCount} environment variables for workspace: ${workspaceUri.fsPath}`,
125+
);
126+
} else {
127+
traceVerbose(
128+
`TerminalEnvVarInjector: No environment variables to inject for workspace: ${workspaceUri.fsPath}`,
129+
);
130+
}
131+
} catch (error) {
132+
traceError(
133+
`TerminalEnvVarInjector: Error injecting environment variables for workspace ${workspaceUri.fsPath}:`,
134+
error,
135+
);
136+
}
137+
}
138+
139+
/**
140+
* Dispose of the injector and clean up resources.
141+
*/
142+
dispose(): void {
143+
traceVerbose('TerminalEnvVarInjector: Disposing');
144+
this.disposables.forEach((disposable) => disposable.dispose());
145+
this.disposables = [];
146+
147+
// Clear all environment variables from the collection
148+
this.envVarCollection.clear();
149+
}
150+
}

0 commit comments

Comments
 (0)