diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 36c1e66c85249..e902bcd04a7cc 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -850,17 +850,17 @@ import { assertNoRpc, poll } from '../utils'; collection.prepend('C', '~c2~'); // Verify get - deepStrictEqual(collection.get('A'), { value: '~a2~', type: EnvironmentVariableMutatorType.Replace, scope: undefined }); - deepStrictEqual(collection.get('B'), { value: '~b2~', type: EnvironmentVariableMutatorType.Append, scope: undefined }); - deepStrictEqual(collection.get('C'), { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend, scope: undefined }); + deepStrictEqual(collection.get('A'), { value: '~a2~', type: EnvironmentVariableMutatorType.Replace, scope: undefined, options: {} }); + deepStrictEqual(collection.get('B'), { value: '~b2~', type: EnvironmentVariableMutatorType.Append, scope: undefined, options: {} }); + deepStrictEqual(collection.get('C'), { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend, scope: undefined, options: {} }); // Verify forEach const entries: [string, EnvironmentVariableMutator][] = []; collection.forEach((v, m) => entries.push([v, m])); deepStrictEqual(entries, [ - ['A', { value: '~a2~', type: EnvironmentVariableMutatorType.Replace, scope: undefined }], - ['B', { value: '~b2~', type: EnvironmentVariableMutatorType.Append, scope: undefined }], - ['C', { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend, scope: undefined }] + ['A', { value: '~a2~', type: EnvironmentVariableMutatorType.Replace, scope: undefined, options: {} }], + ['B', { value: '~b2~', type: EnvironmentVariableMutatorType.Append, scope: undefined, options: {} }], + ['C', { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend, scope: undefined, options: {} }] ]); }); }); diff --git a/src/vs/platform/terminal/common/environmentVariable.ts b/src/vs/platform/terminal/common/environmentVariable.ts index 3df82accaae63..8ff5a08abc495 100644 --- a/src/vs/platform/terminal/common/environmentVariable.ts +++ b/src/vs/platform/terminal/common/environmentVariable.ts @@ -11,17 +11,12 @@ export enum EnvironmentVariableMutatorType { Append = 2, Prepend = 3 } -// export enum EnvironmentVariableMutatorTiming { -// AtSpawn = 1, -// AfterShellIntegration = 2 -// // TODO: Do we need a both? -// } export interface IEnvironmentVariableMutator { readonly variable: string; readonly value: string; readonly type: EnvironmentVariableMutatorType; readonly scope?: EnvironmentVariableScope; - // readonly timing?: EnvironmentVariableMutatorTiming; + readonly options?: IEnvironmentVariableMutatorOptions; } export interface IEnvironmentDescriptionMutator { @@ -29,6 +24,11 @@ export interface IEnvironmentDescriptionMutator { readonly scope?: EnvironmentVariableScope; } +export interface IEnvironmentVariableMutatorOptions { + applyAtProcessCreation?: boolean; + applyAtShellIntegration?: boolean; +} + export type EnvironmentVariableScope = { workspaceFolder?: IWorkspaceFolderData; }; diff --git a/src/vs/platform/terminal/common/environmentVariableCollection.ts b/src/vs/platform/terminal/common/environmentVariableCollection.ts index 3c223347ceb33..67b0e2a534beb 100644 --- a/src/vs/platform/terminal/common/environmentVariableCollection.ts +++ b/src/vs/platform/terminal/common/environmentVariableCollection.ts @@ -8,11 +8,11 @@ import { EnvironmentVariableMutatorType, EnvironmentVariableScope, IEnvironmentV type VariableResolver = (str: string) => Promise; -// const mutatorTypeToLabelMap: Map = new Map([ -// [EnvironmentVariableMutatorType.Append, 'APPEND'], -// [EnvironmentVariableMutatorType.Prepend, 'PREPEND'], -// [EnvironmentVariableMutatorType.Replace, 'REPLACE'] -// ]); +const mutatorTypeToLabelMap: Map = new Map([ + [EnvironmentVariableMutatorType.Append, 'APPEND'], + [EnvironmentVariableMutatorType.Prepend, 'PREPEND'], + [EnvironmentVariableMutatorType.Replace, 'REPLACE'] +]); export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVariableCollection { private readonly map: Map = new Map(); @@ -46,7 +46,8 @@ export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVa value: mutator.value, type: mutator.type, scope: mutator.scope, - variable: mutator.variable + variable: mutator.variable, + options: mutator.options }; if (!extensionMutator.scope) { delete extensionMutator.scope; // Convenient for tests @@ -69,26 +70,33 @@ export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVa const actualVariable = isWindows ? lowerToActualVariableNames![variable.toLowerCase()] || variable : variable; for (const mutator of mutators) { const value = variableResolver ? await variableResolver(mutator.value) : mutator.value; - // if (mutator.timing === EnvironmentVariableMutatorTiming.AfterShellIntegration) { - // const key = `VSCODE_ENV_${mutatorTypeToLabelMap.get(mutator.type)!}`; - // env[key] = (env[key] ? env[key] + ':' : '') + variable + '=' + value; - // continue; - // } - switch (mutator.type) { - case EnvironmentVariableMutatorType.Append: - env[actualVariable] = (env[actualVariable] || '') + value; - break; - case EnvironmentVariableMutatorType.Prepend: - env[actualVariable] = value + (env[actualVariable] || ''); - break; - case EnvironmentVariableMutatorType.Replace: - env[actualVariable] = value; - break; + // Default: true + if (mutator.options?.applyAtProcessCreation ?? true) { + switch (mutator.type) { + case EnvironmentVariableMutatorType.Append: + env[actualVariable] = (env[actualVariable] || '') + value; + break; + case EnvironmentVariableMutatorType.Prepend: + env[actualVariable] = value + (env[actualVariable] || ''); + break; + case EnvironmentVariableMutatorType.Replace: + env[actualVariable] = value; + break; + } + } + // Default: false + if (mutator.options?.applyAtShellIntegration ?? false) { + const key = `VSCODE_ENV_${mutatorTypeToLabelMap.get(mutator.type)!}`; + env[key] = (env[key] ? env[key] + ':' : '') + variable + '=' + this._encodeColons(value); } } } } + private _encodeColons(value: string): string { + return value.replaceAll(':', '\\x3a'); + } + diff(other: IMergedEnvironmentVariableCollection, scope: EnvironmentVariableScope | undefined): IMergedEnvironmentVariableCollectionDiff | undefined { const added: Map = new Map(); const changed: Map = new Map(); diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index e20dfbd98e907..78a1d4a52919f 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -25,6 +25,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { Promises } from 'vs/base/common/async'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { ViewColumn } from 'vs/workbench/api/common/extHostTypeConverters'; +import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable { @@ -826,7 +827,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I public getEnvironmentVariableCollection(extension: IExtensionDescription): IEnvironmentVariableCollection { let collection = this._environmentVariableCollections.get(extension.identifier.value); if (!collection) { - collection = new EnvironmentVariableCollection(); + collection = new EnvironmentVariableCollection(extension); this._setEnvironmentVariableCollection(extension.identifier.value, collection); } return collection.getScopedEnvironmentVariableCollection(undefined); @@ -841,7 +842,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void { collections.forEach(entry => { const extensionIdentifier = entry[0]; - const collection = new EnvironmentVariableCollection(entry[1]); + const collection = new EnvironmentVariableCollection(undefined, entry[1]); this._setEnvironmentVariableCollection(extensionIdentifier, collection); }); } @@ -883,6 +884,11 @@ class EnvironmentVariableCollection { get onDidChangeCollection(): Event { return this._onDidChangeCollection && this._onDidChangeCollection.event; } constructor( + // HACK: Only check proposed options if extension is set (when the collection is not + // restored by serialization). This saves us from getting the extension details and + // shouldn't ever happen since you can only set them initially via the proposed check. + // TODO: This should be removed when the env var extension API(s) are stabilized + private readonly _extension: IExtensionDescription | undefined, serialized?: ISerializableEnvironmentVariableCollection ) { this.map = new Map(serialized); @@ -899,19 +905,31 @@ class EnvironmentVariableCollection { return scopedCollection; } - replace(variable: string, value: string, scope: vscode.EnvironmentVariableScope | undefined): void { - this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Replace, scope }); + replace(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void { + if (this._extension && options) { + isProposedApiEnabled(this._extension, 'envCollectionOptions'); + } + this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Replace, options: options ?? {}, scope }); } - append(variable: string, value: string, scope: vscode.EnvironmentVariableScope | undefined): void { - this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Append, scope }); + append(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void { + if (this._extension && options) { + isProposedApiEnabled(this._extension, 'envCollectionOptions'); + } + this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Append, options: options ?? {}, scope }); } - prepend(variable: string, value: string, scope: vscode.EnvironmentVariableScope | undefined): void { - this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Prepend, scope }); + prepend(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void { + if (this._extension && options) { + isProposedApiEnabled(this._extension, 'envCollectionOptions'); + } + this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Prepend, options: options ?? {}, scope }); } private _setIfDiffers(variable: string, mutator: vscode.EnvironmentVariableMutator & { scope: vscode.EnvironmentVariableScope | undefined }): void { + if (mutator.options && mutator.options.applyAtProcessCreation === false && !mutator.options.applyAtShellIntegration) { + throw new Error('EnvironmentVariableMutatorOptions must apply at either process creation or shell integration'); + } if (!mutator.scope) { delete (mutator as any).scope; // Convenient for tests } @@ -928,6 +946,7 @@ class EnvironmentVariableCollection { get(variable: string, scope: vscode.EnvironmentVariableScope | undefined): vscode.EnvironmentVariableMutator | undefined { const key = this.getKey(variable, scope); const value = this.map.get(key); + // TODO: Set options to defaults if needed return value ? convertMutator(value) : undefined; } @@ -944,11 +963,11 @@ class EnvironmentVariableCollection { return workspaceFolder ? workspaceFolder.uri.toString() : undefined; } - public getVariableMap(scope: vscode.EnvironmentVariableScope | undefined): Map { - const map = new Map(); + public getVariableMap(scope: vscode.EnvironmentVariableScope | undefined): Map { + const map = new Map(); for (const [key, value] of this.map) { if (this.getScopeKey(value.scope) === this.getScopeKey(scope)) { - map.set(key, value); + map.set(key, convertMutator(value)); } } return map; @@ -1022,16 +1041,16 @@ class ScopedEnvironmentVariableCollection implements vscode.EnvironmentVariableC return this.collection.getScopedEnvironmentVariableCollection(this.scope); } - replace(variable: string, value: string): void { - this.collection.replace(variable, value, this.scope); + replace(variable: string, value: string, options?: vscode.EnvironmentVariableMutatorOptions | undefined): void { + this.collection.replace(variable, value, options, this.scope); } - append(variable: string, value: string): void { - this.collection.append(variable, value, this.scope); + append(variable: string, value: string, options?: vscode.EnvironmentVariableMutatorOptions | undefined): void { + this.collection.append(variable, value, options, this.scope); } - prepend(variable: string, value: string): void { - this.collection.prepend(variable, value, this.scope); + prepend(variable: string, value: string, options?: vscode.EnvironmentVariableMutatorOptions | undefined): void { + this.collection.prepend(variable, value, options, this.scope); } get(variable: string): vscode.EnvironmentVariableMutator | undefined { @@ -1039,7 +1058,7 @@ class ScopedEnvironmentVariableCollection implements vscode.EnvironmentVariableC } forEach(callback: (variable: string, mutator: vscode.EnvironmentVariableMutator, collection: vscode.EnvironmentVariableCollection) => any, thisArg?: any): void { - this.collection.getVariableMap(this.scope).forEach((value, variable) => callback.call(thisArg, variable, convertMutator(value), this), this.scope); + this.collection.getVariableMap(this.scope).forEach((value, variable) => callback.call(thisArg, variable, value, this), this.scope); } [Symbol.iterator](): IterableIterator<[variable: string, mutator: vscode.EnvironmentVariableMutator]> { @@ -1102,6 +1121,7 @@ function asTerminalColor(color?: vscode.ThemeColor): ThemeColor | undefined { function convertMutator(mutator: IEnvironmentVariableMutator): vscode.EnvironmentVariableMutator { const newMutator = { ...mutator }; newMutator.scope = newMutator.scope ?? undefined; + newMutator.options = newMutator.options ?? undefined; delete (newMutator as any).variable; return newMutator as vscode.EnvironmentVariableMutator; } 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 40d0e4c339e92..cffe7c45081c1 100644 --- a/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts +++ b/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts @@ -31,16 +31,16 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { }], ['ext4', { map: deserializeEnvironmentVariableCollection([ - ['A-key', { value: 'a4', type: EnvironmentVariableMutatorType.Append, variable: 'A' }] + ['A-key', { value: 'a4', type: EnvironmentVariableMutatorType.Append, variable: 'A', options: { applyAtProcessCreation: true, applyAtShellIntegration: true } }] ]) }] ])); deepStrictEqual([...merged.getVariableMap(undefined).entries()], [ ['A', [ - { extensionIdentifier: 'ext4', type: EnvironmentVariableMutatorType.Append, value: 'a4', variable: 'A' }, - { extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Prepend, value: 'a3', variable: 'A' }, - { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2', variable: 'A' }, - { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'a1', variable: 'A' } + { extensionIdentifier: 'ext4', type: EnvironmentVariableMutatorType.Append, value: 'a4', variable: 'A', options: { applyAtProcessCreation: true, applyAtShellIntegration: true } }, + { extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Prepend, value: 'a3', variable: 'A', options: undefined }, + { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2', variable: 'A', options: undefined }, + { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'a1', variable: 'A', options: undefined } ]] ]); }); @@ -70,9 +70,9 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { ])); deepStrictEqual([...merged.getVariableMap(undefined).entries()], [ ['A', [ - { extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Replace, value: 'a3', variable: 'A' }, - { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2', variable: 'A' }, - { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'a1', variable: 'A' } + { extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Replace, value: 'a3', variable: 'A', options: undefined }, + { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2', variable: 'A', options: undefined }, + { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'a1', variable: 'A', options: undefined } ]] ], 'The ext4 entry should be removed as it comes after a Replace'); }); @@ -104,9 +104,9 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { ])); deepStrictEqual([...merged.getVariableMap(scope2).entries()], [ ['A', [ - { extensionIdentifier: 'ext4', type: EnvironmentVariableMutatorType.Append, value: 'a4', variable: 'A' }, - { extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Prepend, value: 'a3', scope: scope2, variable: 'A' }, - { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2', variable: 'A' }, + { extensionIdentifier: 'ext4', type: EnvironmentVariableMutatorType.Append, value: 'a4', variable: 'A', options: undefined }, + { extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Prepend, value: 'a3', scope: scope2, variable: 'A', options: undefined }, + { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2', variable: 'A', options: undefined }, ]] ]); }); @@ -138,8 +138,8 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { ])); deepStrictEqual([...merged.getVariableMap(undefined).entries()], [ ['A', [ - { extensionIdentifier: 'ext4', type: EnvironmentVariableMutatorType.Append, value: 'a4', variable: 'A' }, - { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2', variable: 'A' }, + { extensionIdentifier: 'ext4', type: EnvironmentVariableMutatorType.Append, value: 'a4', variable: 'A', options: undefined }, + { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2', variable: 'A', options: undefined }, ]] ]); }); @@ -322,7 +322,7 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { strictEqual(diff.removed.size, 0); const entries = [...diff.added.entries()]; deepStrictEqual(entries, [ - ['A', [{ extensionIdentifier: 'ext1', value: 'a', type: EnvironmentVariableMutatorType.Replace, variable: 'A' }]] + ['A', [{ extensionIdentifier: 'ext1', value: 'a', type: EnvironmentVariableMutatorType.Replace, variable: 'A', options: undefined }]] ]); }); @@ -347,7 +347,7 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { strictEqual(diff.removed.size, 0); const entries = [...diff.added.entries()]; deepStrictEqual(entries, [ - ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Append, variable: 'B' }]] + ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Append, variable: 'B', options: undefined }]] ]); }); @@ -376,7 +376,7 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { strictEqual(diff.changed.size, 0); strictEqual(diff.removed.size, 0); deepStrictEqual([...diff.added.entries()], [ - ['A', [{ extensionIdentifier: 'ext2', value: 'a2', type: EnvironmentVariableMutatorType.Append, variable: 'A' }]] + ['A', [{ extensionIdentifier: 'ext2', value: 'a2', type: EnvironmentVariableMutatorType.Append, variable: 'A', options: undefined }]] ]); const merged3 = new MergedEnvironmentVariableCollection(new Map([ @@ -443,7 +443,7 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { strictEqual(diff.changed.size, 0); strictEqual(diff.added.size, 0); deepStrictEqual([...diff.removed.entries()], [ - ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Replace, variable: 'B' }]] + ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Replace, variable: 'B', options: undefined }]] ]); }); @@ -468,8 +468,8 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { strictEqual(diff.added.size, 0); strictEqual(diff.removed.size, 0); deepStrictEqual([...diff.changed.entries()], [ - ['A', [{ extensionIdentifier: 'ext1', value: 'a2', type: EnvironmentVariableMutatorType.Replace, variable: 'A' }]], - ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Append, variable: 'B' }]] + ['A', [{ extensionIdentifier: 'ext1', value: 'a2', type: EnvironmentVariableMutatorType.Replace, variable: 'A', options: undefined }]], + ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Append, variable: 'B', options: undefined }]] ]); }); @@ -492,13 +492,13 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { ])); const diff = merged1.diff(merged2, undefined)!; deepStrictEqual([...diff.added.entries()], [ - ['C', [{ extensionIdentifier: 'ext1', value: 'c', type: EnvironmentVariableMutatorType.Append, variable: 'C' }]], + ['C', [{ extensionIdentifier: 'ext1', value: 'c', type: EnvironmentVariableMutatorType.Append, variable: 'C', options: undefined }]], ]); deepStrictEqual([...diff.removed.entries()], [ - ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Prepend, variable: 'B' }]] + ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Prepend, variable: 'B', options: undefined }]] ]); deepStrictEqual([...diff.changed.entries()], [ - ['A', [{ extensionIdentifier: 'ext1', value: 'a2', type: EnvironmentVariableMutatorType.Replace, variable: 'A' }]] + ['A', [{ extensionIdentifier: 'ext1', value: 'a2', type: EnvironmentVariableMutatorType.Replace, variable: 'A', options: undefined }]] ]); }); @@ -524,10 +524,10 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { const diff = merged1.diff(merged2, scope1)!; strictEqual(diff.added.size, 0); deepStrictEqual([...diff.removed.entries()], [ - ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Prepend, variable: 'B' }]] + ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Prepend, variable: 'B', options: undefined }]] ]); deepStrictEqual([...diff.changed.entries()], [ - ['A', [{ extensionIdentifier: 'ext1', value: 'a2', type: EnvironmentVariableMutatorType.Replace, scope: scope1, variable: 'A' }]] + ['A', [{ extensionIdentifier: 'ext1', value: 'a2', type: EnvironmentVariableMutatorType.Replace, scope: scope1, variable: 'A', options: undefined }]] ]); }); }); diff --git a/src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts b/src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts index c1651e86f9ea1..4d3d7a840c362 100644 --- a/src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts +++ b/src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts @@ -51,21 +51,21 @@ suite('EnvironmentVariable - EnvironmentVariableService', () => { const collection = new Map(); collection.set('A-key', { value: 'a', type: EnvironmentVariableMutatorType.Replace, variable: 'A' }); collection.set('B-key', { value: 'b', type: EnvironmentVariableMutatorType.Append, variable: 'B' }); - collection.set('C-key', { value: 'c', type: EnvironmentVariableMutatorType.Prepend, variable: 'C' }); + collection.set('C-key', { value: 'c', type: EnvironmentVariableMutatorType.Prepend, variable: 'C', options: { applyAtProcessCreation: true, applyAtShellIntegration: true } }); environmentVariableService.set('ext1', { map: collection, persistent: true }); deepStrictEqual([...environmentVariableService.mergedCollection.getVariableMap(undefined).entries()], [ - ['A', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'a', variable: 'A' }]], - ['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'b', variable: 'B' }]], - ['C', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'c', variable: 'C' }]] + ['A', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'a', variable: 'A', options: undefined }]], + ['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'b', variable: 'B', options: undefined }]], + ['C', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'c', variable: 'C', options: { applyAtProcessCreation: true, applyAtShellIntegration: true } }]] ]); // Persist with old service, create a new service with the same storage service to verify restore environmentVariableService.persistCollections(); const service2: TestEnvironmentVariableService = instantiationService.createInstance(TestEnvironmentVariableService); deepStrictEqual([...service2.mergedCollection.getVariableMap(undefined).entries()], [ - ['A', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'a', variable: 'A' }]], - ['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'b', variable: 'B' }]], - ['C', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'c', variable: 'C' }]] + ['A', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'a', variable: 'A', options: undefined }]], + ['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'b', variable: 'B', options: undefined }]], + ['C', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'c', variable: 'C', options: { applyAtProcessCreation: true, applyAtShellIntegration: true } }]] ]); }); @@ -85,10 +85,10 @@ suite('EnvironmentVariable - EnvironmentVariableService', () => { environmentVariableService.set('ext3', { map: collection3, persistent: true }); deepStrictEqual([...environmentVariableService.mergedCollection.getVariableMap(undefined).entries()], [ ['A', [ - { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Replace, value: 'a2', variable: 'A' }, - { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'a1', variable: 'A' } + { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Replace, value: 'a2', variable: 'A', options: undefined }, + { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'a1', variable: 'A', options: undefined } ]], - ['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'b1', variable: 'B' }]] + ['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'b1', variable: 'B', options: undefined }]] ]); }); @@ -106,9 +106,9 @@ suite('EnvironmentVariable - EnvironmentVariableService', () => { // The entries should be ordered in the order they are applied deepStrictEqual([...environmentVariableService.mergedCollection.getVariableMap(undefined).entries()], [ ['A', [ - { extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Replace, value: 'a3', variable: 'A' }, - { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Prepend, value: 'a2:', variable: 'A' }, - { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: ':a1', variable: 'A' } + { extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Replace, value: 'a3', variable: 'A', options: undefined }, + { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Prepend, value: 'a2:', variable: 'A', options: undefined }, + { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: ':a1', variable: 'A', options: undefined } ]] ]); @@ -134,8 +134,8 @@ suite('EnvironmentVariable - EnvironmentVariableService', () => { // The entries should be ordered in the order they are applied deepStrictEqual([...environmentVariableService.mergedCollection.getVariableMap(scope1).entries()], [ ['A', [ - { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Prepend, value: 'a2:', variable: 'A' }, - { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: ':a1', scope: scope1, variable: 'A' } + { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Prepend, value: 'a2:', variable: 'A', options: undefined }, + { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: ':a1', scope: scope1, variable: 'A', options: undefined } ]] ]); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 58dac72b165b8..f1c61165b4c1a 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -34,6 +34,7 @@ export const allApiProposals = Object.freeze({ dropMetadata: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.dropMetadata.d.ts', editSessionIdentityProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts', editorInsets: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorInsets.d.ts', + envCollectionOptions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.envCollectionOptions.d.ts', envCollectionWorkspace: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts', envShellEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.envShellEvent.d.ts', extensionRuntime: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionRuntime.d.ts', diff --git a/src/vscode-dts/vscode.proposed.envCollectionOptions.d.ts b/src/vscode-dts/vscode.proposed.envCollectionOptions.d.ts new file mode 100644 index 0000000000000..d25a92725a4dd --- /dev/null +++ b/src/vscode-dts/vscode.proposed.envCollectionOptions.d.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/179476 + + /** + * Options applied to the mutator. + */ + export interface EnvironmentVariableMutatorOptions { + /** + * Apply to the environment just before the process is created. + * + * Defaults to true. + */ + applyAtProcessCreation?: boolean; + + /** + * Apply to the environment in the shell integration script. Note that this _will not_ apply + * the mutator if shell integration is disabled or not working for some reason. + * + * Defaults to false. + */ + applyAtShellIntegration?: boolean; + } + + /** + * A type of mutation and its value to be applied to an environment variable. + */ + export interface EnvironmentVariableMutator { + /** + * Options applied to the mutator. + */ + readonly options: EnvironmentVariableMutatorOptions; + } + + export interface EnvironmentVariableCollection extends Iterable<[variable: string, mutator: EnvironmentVariableMutator]> { + /** + * @param options Options applied to the mutator. + */ + replace(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void; + + /** + * @param options Options applied to the mutator. + */ + append(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void; + + /** + * @param options Options applied to the mutator. + */ + prepend(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void; + } +}