diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 0f0fb6ae9ea452204b621d61b2ff3de5775a0a86..52a57b17fddeee6221050bdf9de159f013276b60 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -13,6 +13,8 @@ import { ITerminalInstanceService, ITerminalService, ITerminalInstance, ITermina import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; +import { IEnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { EnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableService'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -29,8 +31,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape extHostContext: IExtHostContext, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalInstanceService readonly terminalInstanceService: ITerminalInstanceService, - @IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService, + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService); this._remoteAuthority = extHostContext.remoteAuthority; @@ -347,9 +350,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape return terminal; } - $updateEnvironmentVariableCollections(collections: IEnvironmentVariableCollectionDto[]): void { - // TODO: Pass on to env var service - throw new Error('Method not implemented.'); + $setEnvironmentVariableCollection(extensionIdentifier: string, collection: IEnvironmentVariableCollectionDto | undefined): void { + if (collection) { + const translatedCollection = new EnvironmentVariableCollection(collection.variables, collection.values, collection.types); + this._environmentVariableService.set(extensionIdentifier, translatedCollection); + } else { + this._environmentVariableService.delete(extensionIdentifier); + } } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d34babb11c3e485691feef1381cf6dd1d25e89a3..8e6fa94038ea6eff4c55c4fac6de77a2345135f3 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -428,7 +428,6 @@ export interface TerminalLaunchConfig { } export interface IEnvironmentVariableCollectionDto { - extensionIdentifier: string; variables: string[]; values: string[]; types: EnvironmentVariableMutatorType[]; @@ -444,7 +443,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $stopSendingDataEvents(): void; $startHandlingLinks(): void; $stopHandlingLinks(): void; - $updateEnvironmentVariableCollections(collections: IEnvironmentVariableCollectionDto[]): void; + $setEnvironmentVariableCollection(extensionIdentifier: string, collection: IEnvironmentVariableCollectionDto | undefined): void; // Process $sendProcessTitle(terminalId: number, title: string): void; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 3f150634ab7149fdec3f44b6ab840e411bf8c539..1a99a6b64764e5cf3b79d8598d4335ad192af120 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -14,7 +14,7 @@ import { timeout } from 'vs/base/common/async'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { Disposable as VSCodeDisposable } from './extHostTypes'; +import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType } from './extHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { @@ -640,6 +640,62 @@ 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 { + private _entries: Map = new Map(); + + protected readonly _onDidChangeCollection: Emitter = new Emitter(); + get onDidChangeCollection(): Event { return this._onDidChangeCollection && this._onDidChangeCollection.event; } + + get size(): number { + return this._entries.size; + } + + replace(variable: string, value: string): void { + this._entries.set(variable, new EnvironmentVariableMutator(value, EnvironmentVariableMutatorType.Replace)); + this._onDidChangeCollection.fire(); + } + + append(variable: string, value: string): void { + this._entries.set(variable, new EnvironmentVariableMutator(value, EnvironmentVariableMutatorType.Append)); + this._onDidChangeCollection.fire(); + } + + prepend(variable: string, value: string): void { + this._entries.set(variable, new EnvironmentVariableMutator(value, EnvironmentVariableMutatorType.Prepend)); + this._onDidChangeCollection.fire(); + } + + get(variable: string): EnvironmentVariableMutator | undefined { + return this._entries.get(variable); + } + + forEach(callback: (variable: string, mutator: vscode.EnvironmentVariableMutator, collection: vscode.EnvironmentVariableCollection) => any, thisArg?: any): void { + this._entries.forEach((value, key) => callback(key, value, this)); + } + + delete(variable: string): void { + this._entries.delete(variable); + this._onDidChangeCollection.fire(); + } + + clear(): void { + this._entries.clear(); + this._onDidChangeCollection.fire(); + } + + dispose(): void { + this._entries.clear(); + this._onDidChangeCollection.fire(); + } +} + export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { throw new Error('Not implemented'); diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 86827e746bf40fab1ceac304eb6f10bcaee71361..1dd0a11f1e325d4d2bab8149a496245834e2c93a 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -20,67 +20,12 @@ import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostD import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal'; import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; -import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService'; +import { BaseExtHostTerminalService, ExtHostTerminal, EnvironmentVariableCollection } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { EnvironmentVariableMutatorType } from 'vs/workbench/api/common/extHostTypes'; -import { Emitter, Event } from 'vs/base/common/event'; -import { debounce } from 'vs/base/common/decorators'; import { dispose } from 'vs/base/common/lifecycle'; - -class EnvironmentVariableMutator implements vscode.EnvironmentVariableMutator { - constructor( - public value: string, - public type: vscode.EnvironmentVariableMutatorType - ) { } -} - -class EnvironmentVariableCollection implements vscode.EnvironmentVariableCollection { - private _entries: Map = new Map(); - - protected readonly _onDidChangeCollection: Emitter = new Emitter(); - get onDidChangeCollection(): Event { return this._onDidChangeCollection && this._onDidChangeCollection.event; } - - replace(variable: string, value: string): void { - this._entries.set(variable, new EnvironmentVariableMutator(value, EnvironmentVariableMutatorType.Replace)); - this._onDidChangeCollection.fire(); - } - - append(variable: string, value: string): void { - this._entries.set(variable, new EnvironmentVariableMutator(value, EnvironmentVariableMutatorType.Append)); - this._onDidChangeCollection.fire(); - } - - prepend(variable: string, value: string): void { - this._entries.set(variable, new EnvironmentVariableMutator(value, EnvironmentVariableMutatorType.Prepend)); - this._onDidChangeCollection.fire(); - } - - get(variable: string): EnvironmentVariableMutator | undefined { - return this._entries.get(variable); - } - - forEach(callback: (variable: string, mutator: vscode.EnvironmentVariableMutator, collection: vscode.EnvironmentVariableCollection) => any, thisArg?: any): void { - this._entries.forEach((value, key) => callback(key, value, this)); - } - - delete(variable: string): void { - this._entries.delete(variable); - this._onDidChangeCollection.fire(); - } - - clear(): void { - this._entries.clear(); - this._onDidChangeCollection.fire(); - } - - dispose(): void { - this._entries.clear(); - this._onDidChangeCollection.fire(); - } -} - export class ExtHostTerminalService extends BaseExtHostTerminalService { private _variableResolver: ExtHostVariableResolverService | undefined; @@ -287,21 +232,19 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { } else { collection = new EnvironmentVariableCollection(); } - collection.onDidChangeCollection(() => this._updateEnvironmentVariableCollections()); + collection.onDidChangeCollection(() => this._syncEnvironmentVariableCollection(extension.identifier.value, collection)); this._environmentVariableCollection.set(extension.identifier.value, collection); return collection; } - @debounce(1000) - private _updateEnvironmentVariableCollections(): void { - const collections: IEnvironmentVariableCollectionDto[] = []; - this._environmentVariableCollection.forEach((collection, extensionIdenfitier) => { - collections.push(this._serializeEnvironmentVariableCollection(extensionIdenfitier, collection)); - }); - this._proxy.$updateEnvironmentVariableCollections(collections); + private _syncEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void { + this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, this._serializeEnvironmentVariableCollection(collection)); } - private _serializeEnvironmentVariableCollection(extensionIdentifier: string, collection: vscode.EnvironmentVariableCollection): IEnvironmentVariableCollectionDto { + private _serializeEnvironmentVariableCollection(collection: EnvironmentVariableCollection): IEnvironmentVariableCollectionDto | undefined { + if (collection.size === 0) { + return undefined; + } const variables: string[] = []; const values: string[] = []; const types: EnvironmentVariableMutatorType[] = []; @@ -311,7 +254,6 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { types.push(mutator.type); }); return { - extensionIdentifier, variables, values, types diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariable.contribution.ts b/src/vs/workbench/contrib/terminal/common/environmentVariable.contribution.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ebe68228164a507bf5c63b5f9f6cdf87733c5c5 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/environmentVariable.contribution.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { EnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariableService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +registerSingleton(IEnvironmentVariableService, EnvironmentVariableService, true); diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts index 4cbfcf4d43dd9a04c74d0ed5a0473f6345f061d1..318ac3d0f89df5c17c0274bead6a1d33e9cda5f1 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts @@ -19,13 +19,16 @@ export interface IEnvironmentVariableMutator { } export interface IEnvironmentVariableCollection { - readonly entries: Map; + readonly entries: ReadonlyMap; + equals(other: IEnvironmentVariableCollection): boolean; } /** * Tracks and persists environment variable collections as defined by extensions. */ export interface IEnvironmentVariableService { + _serviceBrand: undefined; + /** * Gets a single collection constructed by merging all collections into one. */ diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts index 933a1fc2b37512be2076304f42d5422a9ec4c58b..1415651616553ea9070e3aac1065cbdce09b9d44 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts @@ -3,15 +3,41 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEnvironmentVariableService, IEnvironmentVariableCollection, IEnvironmentVariableMutator } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { IEnvironmentVariableService, IEnvironmentVariableCollection, IEnvironmentVariableMutator, EnvironmentVariableMutatorType } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { Event, Emitter } from 'vs/base/common/event'; export class EnvironmentVariableCollection implements IEnvironmentVariableCollection { readonly entries: Map; constructor( - // TODO: Init entries via ctor if specified + variables?: string[], + values?: string[], + types?: EnvironmentVariableMutatorType[] ) { this.entries = new Map(); + if (variables && values && types) { + if (variables.length !== values.length || variables.length !== types.length) { + throw new Error('Cannot create environment collection from arrays of differing length'); + } + for (let i = 0; i < variables.length; i++) { + this.entries.set(variables[i], { value: values[i], type: types[i] }); + } + } + } + + // 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; + } + }); + return result; } } @@ -19,14 +45,21 @@ export class EnvironmentVariableCollection implements IEnvironmentVariableCollec * Tracks and persists environment variable collections as defined by extensions. */ export class EnvironmentVariableService implements IEnvironmentVariableService { + _serviceBrand: undefined; + /** * The merged collection, this is set to undefined when it needs to be resolved again and is * evaluated lazily as needed. */ - private _mergedCollection: IEnvironmentVariableCollection | undefined; + private _mergedCollection: IEnvironmentVariableCollection = new EnvironmentVariableCollection(); private _collections: Map = new Map(); + // TODO: Debounce notifying of terminals about onDidChangeCollections + // TODO: Generate a summary of changes inside the terminal component as it needs to be done per-terminal compared to what it started with + protected readonly _onDidChangeCollections = new Emitter(); + public get onDidChangeCollections(): Event { return this._onDidChangeCollections.event; } + // TODO: Load in persisted collections // TODO: Fire an event when collections have changed that the terminal component can listen to @@ -39,12 +72,10 @@ export class EnvironmentVariableService implements IEnvironmentVariableService { set(extensionIdentifier: string, collection: IEnvironmentVariableCollection): void { this._collections.set(extensionIdentifier, collection); - this._mergedCollection = undefined; } delete(extensionIdentifier: string): void { this._collections.delete(extensionIdentifier); - this._mergedCollection = undefined; } private _resolveMergedCollection(): IEnvironmentVariableCollection { diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 7879df84278138beef8d35532994b5b9e87e6ec3..14e06eea0d17b4b0d1681a24d14b01e48d0e671f 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -207,6 +207,7 @@ import 'vs/workbench/contrib/output/browser/output.contribution'; import 'vs/workbench/contrib/output/browser/outputView'; // Terminal +import 'vs/workbench/contrib/terminal/browser/environmentVariable.contribution'; import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; import 'vs/workbench/contrib/terminal/browser/terminalView';