diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 9ca6d26134c..8f6e137a10c 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -14,7 +14,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { deserializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; +import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -74,6 +74,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape if (activeInstance) { this._proxy.$acceptActiveTerminalChanged(activeInstance.id); } + if (this._environmentVariableService.collections.size > 0) { + const collectionAsArray = [...this._environmentVariableService.collections.entries()]; + const serializedCollections: [string, ISerializableEnvironmentVariableCollection][] = collectionAsArray.map(e => { + return [e[0], serializeEnvironmentVariableCollection(e[1])]; + }); + this._proxy.$initEnvironmentVariableCollections(serializedCollections); + } this._terminalService.extHostReady(extHostContext.remoteAuthority); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 672d51edfd4..685062d87e0 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1388,6 +1388,7 @@ export interface ExtHostTerminalServiceShape { $getAvailableShells(): Promise; $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; $handleLink(id: number, link: string): Promise; + $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void; } export interface ExtHostSCMShape { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 8c35512636b..81afe27e730 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -16,6 +16,7 @@ import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/termi import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType } from './extHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { @@ -336,6 +337,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ public abstract $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; public abstract getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection; + public abstract $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void; public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, options, options.name); @@ -640,39 +642,36 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } } -export class EnvironmentVariableMutator implements vscode.EnvironmentVariableMutator { - constructor( - public value: string, - public type: vscode.EnvironmentVariableMutatorType - ) { } -} - export class EnvironmentVariableCollection implements vscode.EnvironmentVariableCollection { - public map: Map = new Map(); + public readonly map: Map = new Map(); protected readonly _onDidChangeCollection: Emitter = new Emitter(); get onDidChangeCollection(): Event { return this._onDidChangeCollection && this._onDidChangeCollection.event; } + constructor(serialized?: ISerializableEnvironmentVariableCollection) { + this.map = new Map(serialized); + } + get size(): number { return this.map.size; } replace(variable: string, value: string): void { - this.map.set(variable, new EnvironmentVariableMutator(value, EnvironmentVariableMutatorType.Replace)); + this.map.set(variable, { value, type: EnvironmentVariableMutatorType.Replace }); this._onDidChangeCollection.fire(); } append(variable: string, value: string): void { - this.map.set(variable, new EnvironmentVariableMutator(value, EnvironmentVariableMutatorType.Append)); + this.map.set(variable, { value, type: EnvironmentVariableMutatorType.Append }); this._onDidChangeCollection.fire(); } prepend(variable: string, value: string): void { - this.map.set(variable, new EnvironmentVariableMutator(value, EnvironmentVariableMutatorType.Prepend)); + this.map.set(variable, { value, type: EnvironmentVariableMutatorType.Prepend }); this._onDidChangeCollection.fire(); } - get(variable: string): EnvironmentVariableMutator | undefined { + get(variable: string): vscode.EnvironmentVariableMutator | undefined { return this.map.get(variable); } @@ -733,4 +732,8 @@ export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { // This is not implemented so worker ext host extensions cannot influence terminal envs throw new Error('Not implemented'); } + + public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void { + // No-op for web worker ext host as collections aren't used + } } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 7d5244dccbf..078710ebe34 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -25,13 +25,14 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { dispose } from 'vs/base/common/lifecycle'; import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; +import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; export class ExtHostTerminalService extends BaseExtHostTerminalService { private _variableResolver: ExtHostVariableResolverService | undefined; private _lastActiveWorkspace: IWorkspaceFolder | undefined; - private _environmentVariableCollection: Map = new Map(); + private _environmentVariableCollections: Map = new Map(); // TODO: Pull this from main side private _isWorkspaceShellAllowed: boolean = false; @@ -48,13 +49,14 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { this._updateVariableResolver(); this._registerListeners(); - const c = this.getEnvironmentVariableCollection({ identifier: { value: 'test' } } as any, true); setTimeout(() => { + const c = this.getEnvironmentVariableCollection({ identifier: { value: 'test' } } as any, true); c.prepend('a', 'b'); c.replace('c', 'd'); c.append('e', 'f'); }, 2000); setTimeout(() => { + const c = this.getEnvironmentVariableCollection({ identifier: { value: 'test' } } as any, true); c.prepend('a', 'late addition'); }, 4000); } @@ -232,24 +234,19 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { } public getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection { - dispose(this._environmentVariableCollection.get(extension.identifier.value)); - - let collection: EnvironmentVariableCollection; + let collection: EnvironmentVariableCollection | undefined; if (persistent) { - // TODO: Persist when persistent is set - // TODO: Load collection for this extension when persistent is set - collection = new EnvironmentVariableCollection(); - } else { - collection = new EnvironmentVariableCollection(); + // If persistent is specified, return the current collection if it exists + collection = this._environmentVariableCollections.get(extension.identifier.value); } - collection.onDidChangeCollection(() => { - // When any collection value changes send this immediately, this is done to ensure - // following calls to createTerminal will be created with the new environment. It will - // result in more noise by sending multiple updates when called but collections are - // expected to be small. - this._syncEnvironmentVariableCollection(extension.identifier.value, collection); - }); - this._environmentVariableCollection.set(extension.identifier.value, collection); + + if (!collection) { + // If not persistent, clear out the current collection and create a new one + dispose(this._environmentVariableCollections.get(extension.identifier.value)); + collection = new EnvironmentVariableCollection(); + this._setEnvironmentVariableCollection(extension.identifier.value, collection); + } + return collection; } @@ -257,4 +254,23 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { const serialized = serializeEnvironmentVariableCollection(collection.map); this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, serialized.length === 0 ? undefined : serialized); } + + public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void { + collections.forEach(entry => { + const extensionIdentifier = entry[0]; + const collection = new EnvironmentVariableCollection(entry[1]); + this._setEnvironmentVariableCollection(extensionIdentifier, collection); + }); + } + + private _setEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void { + this._environmentVariableCollections.set(extensionIdentifier, collection); + collection.onDidChangeCollection(() => { + // When any collection value changes send this immediately, this is done to ensure + // following calls to createTerminal will be created with the new environment. It will + // result in more noise by sending multiple updates when called but collections are + // expected to be small. + this._syncEnvironmentVariableCollection(extensionIdentifier, collection!); + }); + } } diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts index 8ae25aa6d76..6c126a077f5 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts @@ -75,6 +75,12 @@ export interface IMergedEnvironmentVariableCollection { export interface IEnvironmentVariableService { _serviceBrand: undefined; + /** + * Gets a single collection constructed by merging all environment variable collections into + * one. + */ + readonly collections: ReadonlyMap; + /** * Gets a single collection constructed by merging all environment variable collections into * one. diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts index 9e97d31e883..65517358411 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts @@ -24,8 +24,8 @@ interface ISerializableExtensionEnvironmentVariableCollection { export class EnvironmentVariableService implements IEnvironmentVariableService { _serviceBrand: undefined; - private _collections: Map = new Map(); - private _mergedCollection: IMergedEnvironmentVariableCollection; + collections: Map = new Map(); + mergedCollection: IMergedEnvironmentVariableCollection; private readonly _onDidChangeCollections = new Emitter(); get onDidChangeCollections(): Event { return this._onDidChangeCollections.event; } @@ -37,36 +37,32 @@ export class EnvironmentVariableService implements IEnvironmentVariableService { const serializedPersistedCollections = this._storageService.get(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, StorageScope.WORKSPACE); if (serializedPersistedCollections) { const collectionsJson: ISerializableExtensionEnvironmentVariableCollection[] = JSON.parse(serializedPersistedCollections); - collectionsJson.forEach(c => this._collections.set(c.extensionIdentifier, deserializeEnvironmentVariableCollection(c.collection))); + collectionsJson.forEach(c => this.collections.set(c.extensionIdentifier, deserializeEnvironmentVariableCollection(c.collection))); // Asynchronously invalidate collections where extensions have been uninstalled, this is // async to avoid making all functions on the service synchronous and because extensions // being uninstalled is rare. this._invalidateExtensionCollections(); } - this._mergedCollection = this._resolveMergedCollection(); + this.mergedCollection = this._resolveMergedCollection(); // Listen for uninstalled/disabled extensions this._extensionService.onDidChangeExtensions(() => this._invalidateExtensionCollections()); } - get mergedCollection(): IMergedEnvironmentVariableCollection { - return this._mergedCollection; - } - set(extensionIdentifier: string, collection: IEnvironmentVariableCollection): void { - this._collections.set(extensionIdentifier, collection); + this.collections.set(extensionIdentifier, collection); this._updateCollections(); } delete(extensionIdentifier: string): void { - this._collections.delete(extensionIdentifier); + this.collections.delete(extensionIdentifier); this._updateCollections(); } private _updateCollections(): void { this._persistCollectionsEventually(); - this._mergedCollection = this._resolveMergedCollection(); + this.mergedCollection = this._resolveMergedCollection(); this._notifyCollectionUpdatesEventually(); } @@ -76,11 +72,11 @@ export class EnvironmentVariableService implements IEnvironmentVariableService { } protected _persistCollections(): void { - const keys = [...this._collections.keys()]; + const keys = [...this.collections.keys()]; const collectionsJson: ISerializableExtensionEnvironmentVariableCollection[] = keys.map(extensionIdentifier => { return { extensionIdentifier, - collection: serializeEnvironmentVariableCollection(this._collections.get(extensionIdentifier)!) + collection: serializeEnvironmentVariableCollection(this.collections.get(extensionIdentifier)!) }; }); const stringifiedJson = JSON.stringify(collectionsJson); @@ -93,24 +89,11 @@ export class EnvironmentVariableService implements IEnvironmentVariableService { } protected _notifyCollectionUpdates(): void { - this._onDidChangeCollections.fire(this._mergedCollection); + this._onDidChangeCollections.fire(this.mergedCollection); } private _resolveMergedCollection(): IMergedEnvironmentVariableCollection { - return new MergedEnvironmentVariableCollection(this._collections); - // const result = new EnvironmentVariableCollection(); - // this._collections.forEach(collection => { - // const it = collection.entries(); - // let next = it.next(); - // while (!next.done) { - // const variable = next.value[0]; - // if (!result.entries.has(variable)) { - // result.entries.set(variable, next.value[1]); - // } - // next = it.next(); - // } - // }); - // return result; + return new MergedEnvironmentVariableCollection(this.collections); } private async _invalidateExtensionCollections(): Promise { @@ -118,10 +101,10 @@ export class EnvironmentVariableService implements IEnvironmentVariableService { const registeredExtensions = await this._extensionService.getExtensions(); let changes = false; - this._collections.forEach((_, extensionIdentifier) => { + this.collections.forEach((_, extensionIdentifier) => { const isExtensionRegistered = registeredExtensions.some(r => r.identifier.value === extensionIdentifier); if (!isExtensionRegistered) { - this._collections.delete(extensionIdentifier); + this.collections.delete(extensionIdentifier); changes = true; } });