diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index a05364639c7..9323f3be841 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -48,13 +48,15 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { this._updateVariableResolver(); this._registerListeners(); + const c = this.getEnvironmentVariableCollection({ identifier: { value: 'test' } } as any, true); setTimeout(() => { - this._logService.info('get and set'); - const c = this.getEnvironmentVariableCollection({ identifier: { value: 'test' } } as any, true); c.prepend('a', 'b'); c.replace('c', 'd'); c.append('e', 'f'); }, 2000); + setTimeout(() => { + c.prepend('a', 'late addition'); + }, 4000); } public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index e267fc21c4a..16c01430f94 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -23,6 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { Disposable } from 'vs/base/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IEnvironmentVariableService, IEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; @@ -59,6 +60,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce private _latency: number = -1; private _latencyLastMeasured: number = 0; private _initialCwd: string | undefined; + private _extEnvironmentVariableCollection: IEnvironmentVariableCollection | undefined; private readonly _onProcessReady = this._register(new Emitter()); public get onProcessReady(): Event { return this._onProcessReady.event; } @@ -87,7 +89,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @IProductService private readonly _productService: IProductService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, - @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + @IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService ) { super(); this.ptyProcessReady = new Promise(c => { @@ -230,6 +233,15 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce const baseEnv = this._configHelper.config.inheritEnv ? processEnv : await this._terminalInstanceService.getMainProcessParentEnv(); const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.detectLocale, baseEnv); + // Fetch any extension environment additions and apply them + this._extEnvironmentVariableCollection = this._environmentVariableService.mergedCollection; + this._environmentVariableService.onDidChangeCollections((newCollection) => { + const newAdditions = this._extEnvironmentVariableCollection!.getNewAdditions(newCollection); + // TODO: React to event + console.log('new env additions!', newAdditions); + }); + this._extEnvironmentVariableCollection.applyToProcessEnvironment(env); + const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled; return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty); } diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts index 318ac3d0f89..a5227baf178 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export const IEnvironmentVariableService = createDecorator('environmentVariableService'); @@ -20,7 +22,17 @@ export interface IEnvironmentVariableMutator { export interface IEnvironmentVariableCollection { readonly entries: ReadonlyMap; - equals(other: IEnvironmentVariableCollection): boolean; + // TODO: Remove equals? + // equals(other: IEnvironmentVariableCollection): boolean; + + /** + * Get's additions when compared to another collection. This only gets additions rather than + * doing a full diff because we can only reliably add entries to an environment, not remove + * them. + */ + getNewAdditions(other: IEnvironmentVariableCollection): ReadonlyMap | undefined; + + applyToProcessEnvironment(env: IProcessEnvironment): void; } /** @@ -34,6 +46,8 @@ export interface IEnvironmentVariableService { */ readonly mergedCollection: IEnvironmentVariableCollection; + onDidChangeCollections: Event; + set(extensionIdentifier: string, collection: IEnvironmentVariableCollection): void; delete(extensionIdentifier: string): void; } diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts index a5b0ae30a51..1567e5118e3 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts @@ -6,6 +6,7 @@ import { IEnvironmentVariableService, IEnvironmentVariableCollection, IEnvironmentVariableMutator, EnvironmentVariableMutatorType } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { Event, Emitter } from 'vs/base/common/event'; import { debounce } from 'vs/base/common/decorators'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export class EnvironmentVariableCollection implements IEnvironmentVariableCollection { readonly entries: Map; @@ -26,19 +27,47 @@ export class EnvironmentVariableCollection implements IEnvironmentVariableCollec } } - // TODO: Implement diff method? - equals(other: IEnvironmentVariableCollection): boolean { - if (this.entries.size !== other.entries.size) { - return false; - } - let result = true; - this.entries.forEach((mutator, variable) => { - const otherMutator = other.entries.get(variable); - if (otherMutator !== mutator) { - result = false; + // TODO: Remove? Does diff do the job? + // equals(other: IEnvironmentVariableCollection): boolean { + // if (this.entries.size !== other.entries.size) { + // return false; + // } + // let result = true; + // this.entries.forEach((mutator, variable) => { + // const otherMutator = other.entries.get(variable); + // if (otherMutator !== mutator) { + // result = false; + // } + // }); + // return result; + // } + + // TODO: Consider doing a full diff, just marking the environment as stale with no action available? + getNewAdditions(other: IEnvironmentVariableCollection): ReadonlyMap | undefined { + const result = new Map(); + other.entries.forEach((newMutator, variable) => { + const currentMutator = this.entries.get(variable); + if (currentMutator?.type !== newMutator.type || currentMutator.value !== newMutator.value) { + result.set(variable, newMutator); + } + }); + return result.size === 0 ? undefined : result; + } + + applyToProcessEnvironment(env: IProcessEnvironment): void { + this.entries.forEach((mutator, variable) => { + switch (mutator.type) { + case EnvironmentVariableMutatorType.Append: + env[variable] = (env[variable] || '') + mutator.value; + break; + case EnvironmentVariableMutatorType.Prepend: + env[variable] = mutator.value + (env[variable] || ''); + break; + case EnvironmentVariableMutatorType.Replace: + env[variable] = mutator.value; + break; } }); - return result; } } @@ -52,8 +81,8 @@ export class EnvironmentVariableService implements IEnvironmentVariableService { private _mergedCollection: IEnvironmentVariableCollection = new EnvironmentVariableCollection(); // TODO: Generate a summary of changes inside the terminal component as it needs to be done per-terminal compared to what it started with - private readonly _onDidChangeCollections = new Emitter(); - get onDidChangeCollections(): Event { return this._onDidChangeCollections.event; } + private readonly _onDidChangeCollections = new Emitter(); + get onDidChangeCollections(): Event { return this._onDidChangeCollections.event; } constructor() { // TODO: Load in persisted collections @@ -78,7 +107,7 @@ export class EnvironmentVariableService implements IEnvironmentVariableService { @debounce(1000) private _notifyCollectionUpdates(): void { - this._onDidChangeCollections.fire(); + this._onDidChangeCollections.fire(this._mergedCollection); } private _resolveMergedCollection(): IEnvironmentVariableCollection {