diff --git a/src/vs/platform/terminal/common/environmentVariable.ts b/src/vs/platform/terminal/common/environmentVariable.ts index f207a85a9874b..3df82accaae63 100644 --- a/src/vs/platform/terminal/common/environmentVariable.ts +++ b/src/vs/platform/terminal/common/environmentVariable.ts @@ -24,19 +24,30 @@ export interface IEnvironmentVariableMutator { // readonly timing?: EnvironmentVariableMutatorTiming; } +export interface IEnvironmentDescriptionMutator { + readonly description: string | undefined; + readonly scope?: EnvironmentVariableScope; +} + export type EnvironmentVariableScope = { workspaceFolder?: IWorkspaceFolderData; }; export interface IEnvironmentVariableCollection { readonly map: ReadonlyMap; + readonly descriptionMap?: ReadonlyMap; } /** [variable, mutator] */ export type ISerializableEnvironmentVariableCollection = [string, IEnvironmentVariableMutator][]; -/** [extension, collection] */ -export type ISerializableEnvironmentVariableCollections = [string, ISerializableEnvironmentVariableCollection][]; +export type ISerializableEnvironmentDescriptionMap = [string, IEnvironmentDescriptionMutator][]; +export interface IExtensionOwnedEnvironmentDescriptionMutator extends IEnvironmentDescriptionMutator { + readonly extensionIdentifier: string; +} + +/** [extension, collection, description] */ +export type ISerializableEnvironmentVariableCollections = [string, ISerializableEnvironmentVariableCollection, ISerializableEnvironmentDescriptionMap][]; export interface IExtensionOwnedEnvironmentVariableMutator extends IEnvironmentVariableMutator { readonly extensionIdentifier: string; @@ -61,6 +72,11 @@ export interface IMergedEnvironmentVariableCollection { * @param scope The scope to get the variable map for. If undefined, the global scope is used. */ getVariableMap(scope: EnvironmentVariableScope | undefined): Map; + /** + * Gets the description map for a given scope. + * @param scope The scope to get the description map for. If undefined, the global scope is used. + */ + getDescriptionMap(scope: EnvironmentVariableScope | undefined): Map; /** * Applies this collection to a process environment. * @param variableResolver An optional function to use to resolve variables within the diff --git a/src/vs/platform/terminal/common/environmentVariableCollection.ts b/src/vs/platform/terminal/common/environmentVariableCollection.ts index c9858f8a49407..3c223347ceb33 100644 --- a/src/vs/platform/terminal/common/environmentVariableCollection.ts +++ b/src/vs/platform/terminal/common/environmentVariableCollection.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; -import { EnvironmentVariableMutatorType, EnvironmentVariableScope, IEnvironmentVariableCollection, IExtensionOwnedEnvironmentVariableMutator, IMergedEnvironmentVariableCollection, IMergedEnvironmentVariableCollectionDiff } from 'vs/platform/terminal/common/environmentVariable'; +import { EnvironmentVariableMutatorType, EnvironmentVariableScope, IEnvironmentVariableCollection, IExtensionOwnedEnvironmentDescriptionMutator, IExtensionOwnedEnvironmentVariableMutator, IMergedEnvironmentVariableCollection, IMergedEnvironmentVariableCollectionDiff } from 'vs/platform/terminal/common/environmentVariable'; type VariableResolver = (str: string) => Promise; @@ -16,11 +16,13 @@ type VariableResolver = (str: string) => Promise; export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVariableCollection { private readonly map: Map = new Map(); + private readonly descriptionMap: Map = new Map(); constructor( readonly collections: ReadonlyMap, ) { collections.forEach((collection, extensionIdentifier) => { + this.populateDescriptionMap(collection, extensionIdentifier); const it = collection.map.entries(); let next = it.next(); while (!next.done) { @@ -137,10 +139,51 @@ export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVa }); return result; } + + getDescriptionMap(scope: EnvironmentVariableScope | undefined): Map { + const result = new Map(); + this.descriptionMap.forEach((mutators, _key) => { + const filteredMutators = mutators.filter(m => filterScope(m, scope)); + if (filteredMutators.length > 0) { + // There should be exactly one description per extension per scope. + result.set(filteredMutators[0].extensionIdentifier, filteredMutators[0].description); + } + }); + return result; + } + + private populateDescriptionMap(collection: IEnvironmentVariableCollection, extensionIdentifier: string): void { + if (!collection.descriptionMap) { + return; + } + const it = collection.descriptionMap.entries(); + let next = it.next(); + while (!next.done) { + const mutator = next.value[1]; + const key = next.value[0]; + let entry = this.descriptionMap.get(key); + if (!entry) { + entry = []; + this.descriptionMap.set(key, entry); + } + const extensionMutator = { + extensionIdentifier, + scope: mutator.scope, + description: mutator.description + }; + if (!extensionMutator.scope) { + delete extensionMutator.scope; // Convenient for tests + } + entry.push(extensionMutator); + + next = it.next(); + } + + } } function filterScope( - mutator: IExtensionOwnedEnvironmentVariableMutator, + mutator: IExtensionOwnedEnvironmentVariableMutator | IExtensionOwnedEnvironmentDescriptionMutator, scope: EnvironmentVariableScope | undefined ): boolean { if (!mutator.scope) { diff --git a/src/vs/platform/terminal/common/environmentVariableShared.ts b/src/vs/platform/terminal/common/environmentVariableShared.ts index 7587473953766..5fbc5f363963f 100644 --- a/src/vs/platform/terminal/common/environmentVariableShared.ts +++ b/src/vs/platform/terminal/common/environmentVariableShared.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEnvironmentVariableCollection, IEnvironmentVariableMutator, ISerializableEnvironmentVariableCollection, ISerializableEnvironmentVariableCollections } from 'vs/platform/terminal/common/environmentVariable'; +import { IEnvironmentDescriptionMutator, IEnvironmentVariableCollection, IEnvironmentVariableMutator, ISerializableEnvironmentDescriptionMap as ISerializableEnvironmentDescriptionMap, ISerializableEnvironmentVariableCollection, ISerializableEnvironmentVariableCollections } from 'vs/platform/terminal/common/environmentVariable'; // This file is shared between the renderer and extension host @@ -11,15 +11,25 @@ export function serializeEnvironmentVariableCollection(collection: ReadonlyMap | undefined): ISerializableEnvironmentDescriptionMap { + return descriptionMap ? [...descriptionMap.entries()] : []; +} + export function deserializeEnvironmentVariableCollection( serializedCollection: ISerializableEnvironmentVariableCollection ): Map { return new Map(serializedCollection); } +export function deserializeEnvironmentDescriptionMap( + serializableEnvironmentDescription: ISerializableEnvironmentDescriptionMap | undefined +): Map { + return new Map(serializableEnvironmentDescription ?? []); +} + export function serializeEnvironmentVariableCollections(collections: ReadonlyMap): ISerializableEnvironmentVariableCollections { return Array.from(collections.entries()).map(e => { - return [e[0], serializeEnvironmentVariableCollection(e[1].map)]; + return [e[0], serializeEnvironmentVariableCollection(e[1].map), serializeEnvironmentDescriptionMap(e[1].descriptionMap)]; }); } @@ -27,6 +37,6 @@ export function deserializeEnvironmentVariableCollections( serializedCollection: ISerializableEnvironmentVariableCollections ): Map { return new Map(serializedCollection.map(e => { - return [e[0], { map: deserializeEnvironmentVariableCollection(e[1]) }]; + return [e[0], { map: deserializeEnvironmentVariableCollection(e[1]), descriptionMap: deserializeEnvironmentDescriptionMap(e[2]) }]; })); } diff --git a/src/vs/server/node/remoteTerminalChannel.ts b/src/vs/server/node/remoteTerminalChannel.ts index fddf20d605dfe..a77ba85cb6f55 100644 --- a/src/vs/server/node/remoteTerminalChannel.ts +++ b/src/vs/server/node/remoteTerminalChannel.ts @@ -22,7 +22,7 @@ import { createURITransformer } from 'vs/workbench/api/node/uriTransformer'; import { CLIServerBase, ICommandsExecuter } from 'vs/workbench/api/node/extHostCLIServer'; import { IEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; import { MergedEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableCollection'; -import { deserializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; +import { deserializeEnvironmentDescriptionMap, deserializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; import { ICreateTerminalProcessArguments, ICreateTerminalProcessResult, IWorkspaceFolderData } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; @@ -233,8 +233,8 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel< // Apply extension environment variable collections to the environment if (!shellLaunchConfig.strictEnv) { const entries: [string, IEnvironmentVariableCollection][] = []; - for (const [k, v] of args.envVariableCollections) { - entries.push([k, { map: deserializeEnvironmentVariableCollection(v) }]); + for (const [k, v, d] of args.envVariableCollections) { + entries.push([k, { map: deserializeEnvironmentVariableCollection(v), descriptionMap: deserializeEnvironmentDescriptionMap(d) }]); } const envVariableCollections = new Map(entries); const mergedCollection = new MergedEnvironmentVariableCollection(envVariableCollections); diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 93d4cd269d7b2..f43a403ff42a0 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -15,7 +15,7 @@ import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBu import { ITerminalEditorService, ITerminalExternalLinkProvider, ITerminalGroupService, ITerminalInstance, ITerminalLink, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy'; import { IEnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; +import { deserializeEnvironmentDescriptionMap, deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; import { IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { withNullAsUndefined } from 'vs/base/common/types'; @@ -25,7 +25,7 @@ import { Promises } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; import { TerminalQuickFixType } from 'vs/workbench/api/common/extHostTypes'; -import { ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; +import { ISerializableEnvironmentDescriptionMap, ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; import { ITerminalLinkProviderService } from 'vs/workbench/contrib/terminalContrib/links/browser/links'; import { ITerminalQuickFixService, ITerminalQuickFixOptions, ITerminalQuickFix } from 'vs/workbench/contrib/terminalContrib/quickFix/browser/quickFix'; @@ -400,11 +400,12 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape return terminal; } - $setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void { + $setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined, descriptionMap: ISerializableEnvironmentDescriptionMap): void { if (collection) { const translatedCollection = { persistent, - map: deserializeEnvironmentVariableCollection(collection) + map: deserializeEnvironmentVariableCollection(collection), + descriptionMap: deserializeEnvironmentDescriptionMap(descriptionMap) }; this._environmentVariableService.set(extensionIdentifier, translatedCollection); } else { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f95e8c929b21f..6012741412b7a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -41,7 +41,7 @@ import * as quickInput from 'vs/platform/quickinput/common/quickInput'; import { IRemoteConnectionData, TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import { ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; +import { ISerializableEnvironmentDescriptionMap, ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; import { ICreateContributedTerminalProfileOptions, IProcessProperty, IShellLaunchConfigDto, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, TerminalExitReason, TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { ProvidedPortAttributes, TunnelCreationOptions, TunnelOptions, TunnelPrivacyId, TunnelProviderFeatures } from 'vs/platform/tunnel/common/tunnel'; import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions'; @@ -492,7 +492,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $unregisterProfileProvider(id: string): void; $registerQuickFixProvider(id: string, extensionIdentifier: string): void; $unregisterQuickFixProvider(id: string): void; - $setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void; + $setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined, descriptionMap: ISerializableEnvironmentDescriptionMap): void; // Process $sendProcessData(terminalId: number, data: string): void; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 522db6055836d..c703131984652 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -14,10 +14,10 @@ import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType, Termina import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { localize } from 'vs/nls'; import { NotSupportedError } from 'vs/base/common/errors'; -import { serializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; +import { serializeEnvironmentDescriptionMap, serializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { generateUuid } from 'vs/base/common/uuid'; -import { IEnvironmentVariableMutator, ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; +import { IEnvironmentDescriptionMutator, IEnvironmentVariableMutator, ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, IProcessProperty, ProcessPropertyType, IProcessPropertyMap } from 'vs/platform/terminal/common/terminal'; import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; import { ThemeColor } from 'vs/base/common/themables'; @@ -834,7 +834,8 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I private _syncEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void { const serialized = serializeEnvironmentVariableCollection(collection.map); - this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized); + const serializedDescription = serializeEnvironmentDescriptionMap(collection.descriptionMap); + this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized, serializedDescription); } public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void { @@ -868,6 +869,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I class EnvironmentVariableCollection implements vscode.EnvironmentVariableCollection { readonly map: Map = new Map(); + readonly descriptionMap: Map = new Map(); private _persistent: boolean = true; public get persistent(): boolean { return this._persistent; } @@ -959,11 +961,35 @@ class EnvironmentVariableCollection implements vscode.EnvironmentVariableCollect this.map.delete(key); } } + this.clearDescription(scope); } else { this.map.clear(); + this.descriptionMap.clear(); } this._onDidChangeCollection.fire(); } + + setDescription(description: string | vscode.MarkdownString | undefined, scope?: vscode.EnvironmentVariableScope): void { + const key = this.getKey('', scope); + const current = this.descriptionMap.get(key); + if (!current || current.description !== description) { + let descriptionStr: string | undefined; + if (typeof description === 'string') { + descriptionStr = description; + } else { + // Only take the description before the first `\n\n`, so that the description doesn't mess up the UI + descriptionStr = description?.value.split('\n\n')[0]; + } + const value: IEnvironmentDescriptionMutator = { description: descriptionStr, scope }; + this.descriptionMap.set(key, value); + this._onDidChangeCollection.fire(); + } + } + + private clearDescription(scope?: vscode.EnvironmentVariableScope): void { + const key = this.getKey('', scope); + this.descriptionMap.delete(key); + } } export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { diff --git a/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts b/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts index 66e0ad68d4b4f..242e6c16ffdec 100644 --- a/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts +++ b/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts @@ -20,12 +20,13 @@ export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo { constructor( private readonly _diff: IMergedEnvironmentVariableCollectionDiff, private readonly _terminalId: number, + private readonly _collection: IMergedEnvironmentVariableCollection, @ITerminalService private readonly _terminalService: ITerminalService, @IExtensionService private readonly _extensionService: IExtensionService ) { } - private _getInfo(): string { + private _getInfo(scope: EnvironmentVariableScope | undefined): string { const extSet: Set = new Set(); addExtensionIdentifiers(extSet, this._diff.added.values()); addExtensionIdentifiers(extSet, this._diff.removed.values()); @@ -33,8 +34,13 @@ export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo { let message = localize('extensionEnvironmentContributionInfoStale', "The following extensions want to relaunch the terminal to contribute to its environment:"); message += '\n'; + const descriptionMap = this._collection.getDescriptionMap(scope); for (const ext of extSet) { message += `\n- \`${getExtensionName(ext, this._extensionService)}\``; + const description = descriptionMap.get(ext); + if (description) { + message += `: ${description}`; + } } return message; } @@ -47,12 +53,12 @@ export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo { }]; } - getStatus(): ITerminalStatus { + getStatus(scope: EnvironmentVariableScope | undefined): ITerminalStatus { return { id: TerminalStatus.RelaunchNeeded, severity: Severity.Warning, icon: Codicon.warning, - tooltip: this._getInfo(), + tooltip: this._getInfo(scope), hoverActions: this._getActions() }; } @@ -74,8 +80,13 @@ export class EnvironmentVariableInfoChangesActive implements IEnvironmentVariabl let message = localize('extensionEnvironmentContributionInfoActive', "The following extensions have contributed to this terminal's environment:"); message += '\n'; + const descriptionMap = this._collection.getDescriptionMap(scope); for (const ext of extSet) { message += `\n- \`${getExtensionName(ext, this._extensionService)}\``; + const description = descriptionMap.get(ext); + if (description) { + message += `: ${description}`; + } } return message; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 8b45521762b23..36e4acf2b21b5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -658,7 +658,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce } return; } - this.environmentVariableInfo = this._instantiationService.createInstance(EnvironmentVariableInfoStale, diff, this._instanceId); + this.environmentVariableInfo = this._instantiationService.createInstance(EnvironmentVariableInfoStale, diff, this._instanceId, newCollection); this._onEnvironmentVariableInfoChange.fire(this.environmentVariableInfo); } } diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts index dbd39153b54e1..8ae63dab66473 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts @@ -8,14 +8,15 @@ import { debounce, throttle } from 'vs/base/common/decorators'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { MergedEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableCollection'; -import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; +import { deserializeEnvironmentDescriptionMap, deserializeEnvironmentVariableCollection, serializeEnvironmentDescriptionMap, serializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; import { IEnvironmentVariableCollectionWithPersistence, IEnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/terminalStorageKeys'; -import { IMergedEnvironmentVariableCollection, ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; +import { IMergedEnvironmentVariableCollection, ISerializableEnvironmentDescriptionMap, ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; interface ISerializableExtensionEnvironmentVariableCollection { extensionIdentifier: string; collection: ISerializableEnvironmentVariableCollection; + description?: ISerializableEnvironmentDescriptionMap; } /** @@ -40,7 +41,8 @@ export class EnvironmentVariableService implements IEnvironmentVariableService { const collectionsJson: ISerializableExtensionEnvironmentVariableCollection[] = JSON.parse(serializedPersistedCollections); collectionsJson.forEach(c => this.collections.set(c.extensionIdentifier, { persistent: true, - map: deserializeEnvironmentVariableCollection(c.collection) + map: deserializeEnvironmentVariableCollection(c.collection), + descriptionMap: deserializeEnvironmentDescriptionMap(c.description) })); // Asynchronously invalidate collections where extensions have been uninstalled, this is @@ -81,7 +83,8 @@ export class EnvironmentVariableService implements IEnvironmentVariableService { if (collection.persistent) { collectionsJson.push({ extensionIdentifier, - collection: serializeEnvironmentVariableCollection(this.collections.get(extensionIdentifier)!.map) + collection: serializeEnvironmentVariableCollection(this.collections.get(extensionIdentifier)!.map), + description: serializeEnvironmentDescriptionMap(collection.descriptionMap) }); } }); diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index f725cd971b95e..5b128c3361dd8 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -11,7 +11,7 @@ import { IWorkbenchConfigurationService } from 'vs/workbench/services/configurat import { ILogService } from 'vs/platform/log/common/log'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { serializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; +import { serializeEnvironmentDescriptionMap, serializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -23,11 +23,11 @@ import { IGetTerminalLayoutInfoArgs, IProcessDetails, ISetTerminalLayoutInfoArgs import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform'; import { ICompleteTerminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminal'; import { IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; +import { ISerializableEnvironmentDescriptionMap as ISerializableEnvironmentDescriptionMap, ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; export const REMOTE_TERMINAL_CHANNEL_NAME = 'remoteterminal'; -export type ITerminalEnvironmentVariableCollections = [string, ISerializableEnvironmentVariableCollection][]; +export type ITerminalEnvironmentVariableCollections = [string, ISerializableEnvironmentVariableCollection, ISerializableEnvironmentDescriptionMap][]; export interface IWorkspaceFolderData { uri: UriComponents; @@ -152,7 +152,7 @@ export class RemoteTerminalChannelClient implements IPtyHostController { const envVariableCollections: ITerminalEnvironmentVariableCollections = []; for (const [k, v] of this._environmentVariableService.collections.entries()) { - envVariableCollections.push([k, serializeEnvironmentVariableCollection(v.map)]); + envVariableCollections.push([k, serializeEnvironmentVariableCollection(v.map), serializeEnvironmentDescriptionMap(v.descriptionMap)]); } const resolverResult = await this._remoteAuthorityResolverService.resolveAuthority(this._remoteAuthority); diff --git a/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts b/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts index 528fb86363130..40d0e4c339e92 100644 --- a/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts +++ b/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts @@ -7,7 +7,7 @@ import { deepStrictEqual, strictEqual } from 'assert'; import { EnvironmentVariableMutatorType } from 'vs/platform/terminal/common/environmentVariable'; import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; import { MergedEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableCollection'; -import { deserializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; +import { deserializeEnvironmentDescriptionMap, deserializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; import { URI } from 'vs/base/common/uri'; suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { @@ -144,6 +144,46 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { ]); }); + test('Workspace scoped description entries are properly filtered for each extension', () => { + const scope1 = { workspaceFolder: { uri: URI.file('workspace1'), name: 'workspace1', index: 0 } }; + const scope2 = { workspaceFolder: { uri: URI.file('workspace2'), name: 'workspace2', index: 3 } }; + const merged = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A-key', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend, scope: scope1, variable: 'A' }] + ]), + descriptionMap: deserializeEnvironmentDescriptionMap([ + ['A-key-scope1', { description: 'ext1 scope1 description', scope: scope1 }], + ['A-key-scope2', { description: 'ext1 scope2 description', scope: scope2 }], + ]) + }], + ['ext2', { + map: deserializeEnvironmentVariableCollection([ + ['A-key', { value: 'a2', type: EnvironmentVariableMutatorType.Append, variable: 'A' }] + ]), + descriptionMap: deserializeEnvironmentDescriptionMap([ + ['A-key', { description: 'ext2 global description' }], + ]) + }], + ['ext3', { + map: deserializeEnvironmentVariableCollection([ + ['A-key', { value: 'a3', type: EnvironmentVariableMutatorType.Prepend, scope: scope2, variable: 'A' }] + ]), + descriptionMap: deserializeEnvironmentDescriptionMap([ + ['A-key', { description: 'ext3 scope2 description', scope: scope2 }], + ]) + }], + ['ext4', { + map: deserializeEnvironmentVariableCollection([ + ['A-key', { value: 'a4', type: EnvironmentVariableMutatorType.Append, variable: 'A' }] + ]) + }] + ])); + deepStrictEqual([...merged.getDescriptionMap(scope1).entries()], [ + ['ext1', 'ext1 scope1 description'], + ['ext2', 'ext2 global description'], + ]); + }); }); suite('applyToProcessEnvironment', () => { diff --git a/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts b/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts index 202782232fbd8..b97e4afb28a4e 100644 --- a/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts +++ b/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts @@ -14,6 +14,12 @@ declare module 'vscode' { } export interface EnvironmentVariableCollection extends Iterable<[variable: string, mutator: EnvironmentVariableMutator]> { + /** + * Sets a description for the environment variable collection, this will be used to describe the changes in the UI. + * @param description A description for the environment variable collection. + * @param scope Specific scope to which this description applies to. + */ + setDescription(description: string | MarkdownString | undefined, scope?: EnvironmentVariableScope): void; replace(variable: string, value: string, scope?: EnvironmentVariableScope): void; append(variable: string, value: string, scope?: EnvironmentVariableScope): void; prepend(variable: string, value: string, scope?: EnvironmentVariableScope): void;