提交 edc71001 编写于 作者: D Daniel Imms

Apply multiple extension mutators, share code with ext host

上级 0db1a5ff
......@@ -5,7 +5,7 @@
import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto, IEnvironmentVariableCollectionDto } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { URI } from 'vs/base/common/uri';
import { StopWatch } from 'vs/base/common/stopwatch';
......@@ -13,8 +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/environmentVariableCollection';
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { deserializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
......@@ -350,9 +350,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
return terminal;
}
$setEnvironmentVariableCollection(extensionIdentifier: string, collection: IEnvironmentVariableCollectionDto | undefined): void {
$setEnvironmentVariableCollection(extensionIdentifier: string, collection: ISerializableEnvironmentVariableCollection | undefined): void {
if (collection) {
const translatedCollection = new EnvironmentVariableCollection(collection.variables, collection.values, collection.types);
const translatedCollection = deserializeEnvironmentVariableCollection(collection);
this._environmentVariableService.set(extensionIdentifier, translatedCollection);
} else {
this._environmentVariableService.delete(extensionIdentifier);
......
......@@ -54,7 +54,7 @@ import { revive } from 'vs/base/common/marshalling';
import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { Dto } from 'vs/base/common/types';
import { EnvironmentVariableMutatorType } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
export interface IEnvironment {
isExtensionDevelopmentDebug: boolean;
......@@ -427,12 +427,6 @@ export interface TerminalLaunchConfig {
isExtensionTerminal?: boolean;
}
export interface IEnvironmentVariableCollectionDto {
variables: string[];
values: string[];
types: EnvironmentVariableMutatorType[];
}
export interface MainThreadTerminalServiceShape extends IDisposable {
$createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string; }>;
$dispose(terminalId: number): void;
......@@ -443,7 +437,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
$stopSendingDataEvents(): void;
$startHandlingLinks(): void;
$stopHandlingLinks(): void;
$setEnvironmentVariableCollection(extensionIdentifier: string, collection: IEnvironmentVariableCollectionDto | undefined): void;
$setEnvironmentVariableCollection(extensionIdentifier: string, collection: ISerializableEnvironmentVariableCollection | undefined): void;
// Process
$sendProcessTitle(terminalId: number, title: string): void;
......
......@@ -648,50 +648,50 @@ export class EnvironmentVariableMutator implements vscode.EnvironmentVariableMut
}
export class EnvironmentVariableCollection implements vscode.EnvironmentVariableCollection {
private _entries: Map<string, EnvironmentVariableMutator> = new Map();
public entries: Map<string, EnvironmentVariableMutator> = new Map();
protected readonly _onDidChangeCollection: Emitter<void> = new Emitter<void>();
get onDidChangeCollection(): Event<void> { return this._onDidChangeCollection && this._onDidChangeCollection.event; }
get size(): number {
return this._entries.size;
return this.entries.size;
}
replace(variable: string, value: string): void {
this._entries.set(variable, new EnvironmentVariableMutator(value, EnvironmentVariableMutatorType.Replace));
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.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.entries.set(variable, new EnvironmentVariableMutator(value, EnvironmentVariableMutatorType.Prepend));
this._onDidChangeCollection.fire();
}
get(variable: string): EnvironmentVariableMutator | undefined {
return this._entries.get(variable);
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));
this.entries.forEach((value, key) => callback(key, value, this));
}
delete(variable: string): void {
this._entries.delete(variable);
this.entries.delete(variable);
this._onDidChangeCollection.fire();
}
clear(): void {
this._entries.clear();
this.entries.clear();
this._onDidChangeCollection.fire();
}
dispose(): void {
this._entries.clear();
this.entries.clear();
this._onDidChangeCollection.fire();
}
}
......
......@@ -9,7 +9,7 @@ import * as os from 'os';
import { URI, UriComponents } from 'vs/base/common/uri';
import * as platform from 'vs/base/common/platform';
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, IEnvironmentVariableCollectionDto } from 'vs/workbench/api/common/extHost.protocol';
import { IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostConfiguration, ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
import { ILogService } from 'vs/platform/log/common/log';
import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal';
......@@ -23,8 +23,8 @@ import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/term
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 { dispose } from 'vs/base/common/lifecycle';
import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
export class ExtHostTerminalService extends BaseExtHostTerminalService {
......@@ -254,27 +254,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
}
private _syncEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void {
this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, this._serializeEnvironmentVariableCollection(collection));
}
private _serializeEnvironmentVariableCollection(collection: EnvironmentVariableCollection): IEnvironmentVariableCollectionDto | undefined {
if (collection.size === 0) {
return undefined;
}
const variables: string[] = [];
const values: string[] = [];
const types: EnvironmentVariableMutatorType[] = [];
collection.forEach((variable, mutator) => {
variables.push(variable);
values.push(mutator.value);
types.push(mutator.type);
});
return {
variables,
values,
types
};
const serialized = serializeEnvironmentVariableCollection(collection.entries);
this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, serialized.length === 0 ? undefined : serialized);
}
}
......@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { env as processEnv } from 'vs/base/common/process';
......@@ -24,8 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { Disposable } from 'vs/base/common/lifecycle';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IEnvironmentVariableService, IEnvironmentVariableCollection, EnvironmentVariableMutatorType } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification';
import { IEnvironmentVariableService, IMergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
/** The amount of time to consider terminal errors to be related to the launch */
const LAUNCHING_DURATION = 500;
......@@ -62,7 +60,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
private _latency: number = -1;
private _latencyLastMeasured: number = 0;
private _initialCwd: string | undefined;
private _extEnvironmentVariableCollection: IEnvironmentVariableCollection | undefined;
private _extEnvironmentVariableCollection: IMergedEnvironmentVariableCollection | undefined;
private readonly _onProcessReady = this._register(new Emitter<void>());
public get onProcessReady(): Event<void> { return this._onProcessReady.event; }
......@@ -92,8 +90,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
@IProductService private readonly _productService: IProductService,
@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService,
@INotificationService private readonly _notificationService: INotificationService
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService
) {
super();
this.ptyProcessReady = new Promise<void>(c => {
......@@ -316,35 +313,35 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
this._onProcessExit.fire(exitCode);
}
private _onEnvironmentVariableCollectionChange(newCollection: IEnvironmentVariableCollection): void {
const newAdditions = this._extEnvironmentVariableCollection!.getNewAdditions(newCollection);
if (newAdditions === undefined) {
return;
}
const promptChoices: IPromptChoice[] = [
{
label: nls.localize('apply', "Apply"),
run: () => {
let text = '';
newAdditions.forEach((mutator, variable) => {
// TODO: Support other common shells
// TODO: Escape the new values properly
switch (mutator.type) {
case EnvironmentVariableMutatorType.Append:
text += `export ${variable}="$${variable}${mutator.value}"\n`;
break;
case EnvironmentVariableMutatorType.Prepend:
text += `export ${variable}="${mutator.value}$${variable}"\n`;
break;
case EnvironmentVariableMutatorType.Replace:
text += `export ${variable}="${mutator.value}"\n`;
break;
}
});
this.write(text);
}
} as IPromptChoice
];
this._notificationService.prompt(Severity.Info, nls.localize('environmentchange', "An extension wants to change the terminal environment, do you want to send commands to set the variables in the terminal? Note if you have an application open in the terminal this may not work."), promptChoices);
private _onEnvironmentVariableCollectionChange(newCollection: IMergedEnvironmentVariableCollection): void {
// const newAdditions = this._extEnvironmentVariableCollection!.getNewAdditions(newCollection);
// if (newAdditions === undefined) {
// return;
// }
// const promptChoices: IPromptChoice[] = [
// {
// label: nls.localize('apply', "Apply"),
// run: () => {
// let text = '';
// newAdditions.forEach((mutator, variable) => {
// // TODO: Support other common shells
// // TODO: Escape the new values properly
// switch (mutator.type) {
// case EnvironmentVariableMutatorType.Append:
// text += `export ${variable}="$${variable}${mutator.value}"\n`;
// break;
// case EnvironmentVariableMutatorType.Prepend:
// text += `export ${variable}="${mutator.value}$${variable}"\n`;
// break;
// case EnvironmentVariableMutatorType.Replace:
// text += `export ${variable}="${mutator.value}"\n`;
// break;
// }
// });
// this.write(text);
// }
// } as IPromptChoice
// ];
// this._notificationService.prompt(Severity.Info, nls.localize('environmentchange', "An extension wants to change the terminal environment, do you want to send commands to set the variables in the terminal? Note if you have an application open in the terminal this may not work."), promptChoices);
}
}
......@@ -20,18 +20,21 @@ export interface IEnvironmentVariableMutator {
readonly type: EnvironmentVariableMutatorType;
}
export interface IEnvironmentVariableCollection {
/**
* All entries in the collection
*/
readonly entries: ReadonlyMap<string, IEnvironmentVariableMutator>;
export type IEnvironmentVariableCollection = ReadonlyMap<string, IEnvironmentVariableMutator>;
/**
* Represents an environment variable collection that results from merging several collections
* together.
*/
export interface IMergedEnvironmentVariableCollection {
readonly entries: ReadonlyMap<string, IEnvironmentVariableMutator[]>;
/**
* Get's additions when compared to another collection. This only gets additions rather than
* doing a full diff because we can only reliably add entries to an environment, not remove
* them.
*/
getNewAdditions(other: IEnvironmentVariableCollection): ReadonlyMap<string, IEnvironmentVariableMutator> | undefined;
// getNewAdditions(other: IEnvironmentVariableCollection): ReadonlyMap<string, IEnvironmentVariableMutator[]> | undefined;
/**
* Applies this collection to a process environment.
......@@ -39,6 +42,25 @@ export interface IEnvironmentVariableCollection {
applyToProcessEnvironment(env: IProcessEnvironment): void;
}
// export interface IEnvironmentVariableCollection {
// /**
// * All entries in the collection
// */
// readonly entries: ReadonlyMap<string, IEnvironmentVariableMutator>;
// /**
// * Get's additions when compared to another collection. This only gets additions rather than
// * doing a full diff because we can only reliably add entries to an environment, not remove
// * them.
// */
// getNewAdditions(other: IEnvironmentVariableCollection): ReadonlyMap<string, IEnvironmentVariableMutator> | undefined;
// /**
// * Applies this collection to a process environment.
// */
// applyToProcessEnvironment(env: IProcessEnvironment): void;
// }
/**
* Tracks and persists environment variable collections as defined by extensions.
*/
......@@ -49,13 +71,13 @@ export interface IEnvironmentVariableService {
* Gets a single collection constructed by merging all environment variable collections into
* one.
*/
readonly mergedCollection: IEnvironmentVariableCollection;
readonly mergedCollection: IMergedEnvironmentVariableCollection;
/**
* An event that is fired when an extension's environment variable collection changes, the event
* provides the new merged collection.
*/
onDidChangeCollections: Event<IEnvironmentVariableCollection>;
onDidChangeCollections: Event<IMergedEnvironmentVariableCollection>;
/**
* Sets an extension's environment variable collection.
......@@ -67,3 +89,10 @@ export interface IEnvironmentVariableService {
*/
delete(extensionIdentifier: string): void;
}
/**
* First: Variable
* Second: Value
* Third: Type
*/
export type ISerializableEnvironmentVariableCollection = [string, IEnvironmentVariableMutator][];
......@@ -3,53 +3,129 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentVariableCollection, IEnvironmentVariableMutator, EnvironmentVariableMutatorType } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IEnvironmentVariableCollection, IEnvironmentVariableMutator, EnvironmentVariableMutatorType, IMergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IProcessEnvironment } from 'vs/base/common/platform';
export class EnvironmentVariableCollection implements IEnvironmentVariableCollection {
readonly entries: Map<string, IEnvironmentVariableMutator>;
constructor(
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] });
}
}
}
export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVariableCollection {
readonly entries: Map<string, IEnvironmentVariableMutator[]> = new Map();
// TODO: Consider doing a full diff, just marking the environment as stale with no action available?
getNewAdditions(other: IEnvironmentVariableCollection): ReadonlyMap<string, IEnvironmentVariableMutator> | undefined {
const result = new Map<string, IEnvironmentVariableMutator>();
other.entries.forEach((newMutator, variable) => {
const currentMutator = this.entries.get(variable);
if (currentMutator?.type !== newMutator.type || currentMutator.value !== newMutator.value) {
result.set(variable, newMutator);
constructor(...extensionCollections: IEnvironmentVariableCollection[]) {
extensionCollections.forEach(collection => {
const it = collection.entries();
let next = it.next();
while (!next.done) {
const variable = next.value[0];
let entry = this.entries.get(variable);
if (!entry) {
entry = [];
this.entries.set(variable, entry);
}
// If the first item in the entry is replace ignore any other entries as they would
// just get replaced by this one.
if (entry.length > 0 && entry[0].type === EnvironmentVariableMutatorType.Replace) {
next = it.next();
continue;
}
// Mutators get applied in the reverse order than they are created
entry.unshift(next.value[1]);
next = it.next();
}
});
return result.size === 0 ? undefined : result;
}
// TODO: Consider doing a full diff, just marking the environment as stale with no action available?
// getNewAdditions(other: IMergedEnvironmentVariableCollection): ReadonlyMap<string, IEnvironmentVariableMutator[]> | undefined {
// const result = new Map<string, IEnvironmentVariableMutator[]>();
// const it = other.entries.entries();
// let next = it.next();
// while (!next.done) {
// // TODO: convert to support multiple w/ iterator
// const variable = next.value[0];
// const currentMutators = this.entries.get(variable);
// const newMutators = next.value[1];
// newMutators.forEach(newMutator => {
// });
// }
// // other.entries.forEach((newMutator, variable) => {
// // const currentMutator = this.entries.get(variable);
// // if (currentMutator?.type !== newMutator.type || currentMutator.value !== newMutator.value) {
// // result.set(variable, newMutator);
// // }
// // });
// return result.size === 0 ? undefined : result;
// }
applyToProcessEnvironment(env: IProcessEnvironment): void {
this.entries.forEach((mutator, variable) => {
switch (mutator.type) {
case EnvironmentVariableMutatorType.Append:
env[variable] = (env[variable] || '') + mutator.value;
break;
case EnvironmentVariableMutatorType.Prepend:
env[variable] = mutator.value + (env[variable] || '');
break;
case EnvironmentVariableMutatorType.Replace:
env[variable] = mutator.value;
break;
}
this.entries.forEach((mutators, variable) => {
mutators.forEach(mutator => {
switch (mutator.type) {
case EnvironmentVariableMutatorType.Append:
env[variable] = (env[variable] || '') + mutator.value;
break;
case EnvironmentVariableMutatorType.Prepend:
env[variable] = mutator.value + (env[variable] || '');
break;
case EnvironmentVariableMutatorType.Replace:
env[variable] = mutator.value;
break;
}
});
});
}
}
// export class EnvironmentVariableCollection implements IEnvironmentVariableCollection {
// readonly entries: Map<string, IEnvironmentVariableMutator>;
// constructor(
// 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: Consider doing a full diff, just marking the environment as stale with no action available?
// getNewAdditions(other: IEnvironmentVariableCollection): ReadonlyMap<string, IEnvironmentVariableMutator> | undefined {
// const result = new Map<string, IEnvironmentVariableMutator>();
// other.entries.forEach((newMutator, variable) => {
// const currentMutator = this.entries.get(variable);
// if (currentMutator?.type !== newMutator.type || currentMutator.value !== newMutator.value) {
// result.set(variable, newMutator);
// }
// });
// return result.size === 0 ? undefined : result;
// }
// applyToProcessEnvironment(env: IProcessEnvironment): void {
// this.entries.forEach((mutator, variable) => {
// switch (mutator.type) {
// case EnvironmentVariableMutatorType.Append:
// env[variable] = (env[variable] || '') + mutator.value;
// break;
// case EnvironmentVariableMutatorType.Prepend:
// env[variable] = mutator.value + (env[variable] || '');
// break;
// case EnvironmentVariableMutatorType.Replace:
// env[variable] = mutator.value;
// break;
// }
// });
// }
// }
......@@ -3,21 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentVariableService, IEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IEnvironmentVariableService, IEnvironmentVariableCollection, IMergedEnvironmentVariableCollection, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { Event, Emitter } from 'vs/base/common/event';
import { debounce, throttle } from 'vs/base/common/decorators';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { EnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
const ENVIRONMENT_VARIABLE_COLLECTIONS_KEY = 'terminal.integrated.environmentVariableCollections';
interface ISerializableEnvironmentVariableCollection {
variables: string[];
values: string[];
types: number[];
}
interface ISerializableExtensionEnvironmentVariableCollection {
extensionIdentifier: string,
collection: ISerializableEnvironmentVariableCollection
......@@ -30,10 +25,10 @@ export class EnvironmentVariableService implements IEnvironmentVariableService {
_serviceBrand: undefined;
private _collections: Map<string, IEnvironmentVariableCollection> = new Map();
private _mergedCollection: IEnvironmentVariableCollection;
private _mergedCollection: IMergedEnvironmentVariableCollection;
private readonly _onDidChangeCollections = new Emitter<IEnvironmentVariableCollection>();
get onDidChangeCollections(): Event<IEnvironmentVariableCollection> { return this._onDidChangeCollections.event; }
private readonly _onDidChangeCollections = new Emitter<IMergedEnvironmentVariableCollection>();
get onDidChangeCollections(): Event<IMergedEnvironmentVariableCollection> { return this._onDidChangeCollections.event; }
constructor(
@IExtensionService private _extensionService: IExtensionService,
......@@ -42,10 +37,7 @@ 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 => {
const extCollection = new EnvironmentVariableCollection(c.collection.variables, c.collection.values, c.collection.types);
this._collections.set(c.extensionIdentifier, extCollection);
});
collectionsJson.forEach(c => this._collections.set(c.extensionIdentifier, deserializeEnvironmentVariableCollection(c.collection)));
console.log('serialized from previous session', this._collections);
// Asynchronously invalidate collections where extensions have been uninstalled, this is
......@@ -59,7 +51,7 @@ export class EnvironmentVariableService implements IEnvironmentVariableService {
this._extensionService.onDidChangeExtensions(() => this._invalidateExtensionCollections());
}
get mergedCollection(): IEnvironmentVariableCollection {
get mergedCollection(): IMergedEnvironmentVariableCollection {
return this._mergedCollection;
}
......@@ -106,17 +98,21 @@ export class EnvironmentVariableService implements IEnvironmentVariableService {
this._onDidChangeCollections.fire(this._mergedCollection);
}
private _resolveMergedCollection(): IEnvironmentVariableCollection {
// TODO: Currently this will replace any entry but it's more complex; we need to apply multiple PATH transformations for example
const result = new EnvironmentVariableCollection();
this._collections.forEach(collection => {
collection.entries.forEach((mutator, variable) => {
if (!result.entries.has(variable)) {
result.entries.set(variable, mutator);
}
});
});
return result;
private _resolveMergedCollection(): IMergedEnvironmentVariableCollection {
return new MergedEnvironmentVariableCollection(...[...this._collections.values()]);
// 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;
}
private async _invalidateExtensionCollections(): Promise<void> {
......@@ -138,13 +134,3 @@ export class EnvironmentVariableService implements IEnvironmentVariableService {
}
}
}
function serializeEnvironmentVariableCollection(collection: IEnvironmentVariableCollection): ISerializableEnvironmentVariableCollection {
const entries = [...collection.entries.entries()];
const result: ISerializableEnvironmentVariableCollection = {
variables: entries.map(e => e[0]),
values: entries.map(e => e[1].value),
types: entries.map(e => e[1].type),
};
return result;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentVariableCollection, IEnvironmentVariableMutator, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
// This file is shared between the renderer and extension host
export function serializeEnvironmentVariableCollection(collection: IEnvironmentVariableCollection): ISerializableEnvironmentVariableCollection {
return [...collection.entries()];
}
export function deserializeEnvironmentVariableCollection(
serializedCollection: ISerializableEnvironmentVariableCollection
): IEnvironmentVariableCollection {
return new Map<string, IEnvironmentVariableMutator>(serializedCollection);
}
......@@ -3,80 +3,61 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
import { strictEqual, deepStrictEqual, throws } from 'assert';
import { deepStrictEqual } from 'assert';
import { EnvironmentVariableMutatorType } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
import { deserializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
suite('EnvironmentVariable - EnvironmentVariableCollection', () => {
test('should construct correctly with no arguments', () => {
const c = new EnvironmentVariableCollection();
strictEqual(c.entries.size, 0);
});
test('should construct correctly with 3 arguments', () => {
const c = new EnvironmentVariableCollection(
['A', 'B', 'C'],
['a', 'b', 'c'],
[1, 2, 3]
);
const keys = [...c.entries.keys()];
deepStrictEqual(keys, ['A', 'B', 'C']);
deepStrictEqual(c.entries.get('A'), { value: 'a', type: EnvironmentVariableMutatorType.Replace });
deepStrictEqual(c.entries.get('B'), { value: 'b', type: EnvironmentVariableMutatorType.Append });
deepStrictEqual(c.entries.get('C'), { value: 'c', type: EnvironmentVariableMutatorType.Prepend });
});
suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
// test('getAdditions should return undefined when there are no new additions', () => {
// const c1 = deserializeEnvironmentVariableCollection({
// variables: ['A', 'B', 'C'],
// values: ['a', 'b', 'c'],
// types: [1, 2, 3]
// }
// );
// const c2 = deserializeEnvironmentVariableCollection({
// variables: ['A', 'B', 'C'],
// values: ['a', 'b', 'c'],
// types: [1, 2, 3]
// }
// );
// const newAdditions = c1.getNewAdditions(c2);
// strictEqual(newAdditions, undefined);
// });
test('should throw when ctor arguments have differing length', () => {
throws(() => new EnvironmentVariableCollection(['A'], ['a'], []));
throws(() => new EnvironmentVariableCollection([], ['a'], [1]));
throws(() => new EnvironmentVariableCollection(['A'], [], []));
});
test('getNewAdditions should return undefined when there are no new additions', () => {
const c1 = new EnvironmentVariableCollection(
['A', 'B', 'C'],
['a', 'b', 'c'],
[1, 2, 3]
);
const c2 = new EnvironmentVariableCollection(
['A', 'B', 'C'],
['a', 'b', 'c'],
[1, 2, 3]
);
const newAdditions = c1.getNewAdditions(c2);
strictEqual(newAdditions, undefined);
});
test('getNewAdditions should return only new additions in another collection', () => {
const c1 = new EnvironmentVariableCollection(
['A', 'B', 'C'],
['a', 'b', 'c'],
[1, 2, 3]
);
const c2 = new EnvironmentVariableCollection(
['B', 'D', 'C'],
['b', 'd', 'c'],
[2, 1, 3]
);
const newAdditions = c1.getNewAdditions(c2)!;
const keys = [...newAdditions.keys()];
deepStrictEqual(keys, ['D']);
deepStrictEqual(newAdditions.get('D'), { value: 'd', type: EnvironmentVariableMutatorType.Replace });
});
// test('getNewAdditions should return only new additions in another collection', () => {
// const c1 = deserializeEnvironmentVariableCollection({
// variables: ['A', 'B', 'C'],
// values: ['a', 'b', 'c'],
// types: [1, 2, 3]
// }
// );
// const c2 = deserializeEnvironmentVariableCollection({
// variables: ['B', 'D', 'C'],
// values: ['b', 'd', 'c'],
// types: [2, 1, 3]
// }
// );
// const newAdditions = c1.getNewAdditions(c2)!;
// const keys = [...newAdditions.keys()];
// deepStrictEqual(keys, ['D']);
// deepStrictEqual(newAdditions.get('D'), { value: 'd', type: EnvironmentVariableMutatorType.Replace });
// });
test('applyToProcessEnvironment should apply the collection to an environment', () => {
const c = new EnvironmentVariableCollection(
['A', 'B', 'C'],
['a', 'b', 'c'],
[1, 2, 3]
);
const merged = new MergedEnvironmentVariableCollection(deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }]
]));
const env: IProcessEnvironment = {
A: 'foo',
B: 'bar',
C: 'baz'
};
c.applyToProcessEnvironment(env);
merged.applyToProcessEnvironment(env);
deepStrictEqual(env, {
A: 'a',
B: 'barb',
......@@ -85,20 +66,17 @@ suite('EnvironmentVariable - EnvironmentVariableCollection', () => {
});
test('applyToProcessEnvironment should apply the collection to environment entries with no values', () => {
const c = new EnvironmentVariableCollection(
['A', 'B', 'C'],
['a', 'b', 'c'],
[1, 2, 3]
);
const env: IProcessEnvironment = {
};
c.applyToProcessEnvironment(env);
const merged = new MergedEnvironmentVariableCollection(deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }]
]));
const env: IProcessEnvironment = {};
merged.applyToProcessEnvironment(env);
deepStrictEqual(env, {
A: 'a',
B: 'b',
C: 'c'
});
});
// TODO: Implement and test multiple mutators applying to one variable
});
......@@ -6,8 +6,7 @@
import { deepStrictEqual } from 'assert';
import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { EnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariableService';
import { EnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
import { EnvironmentVariableMutatorType } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { EnvironmentVariableMutatorType, IEnvironmentVariableMutator } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
......@@ -34,66 +33,74 @@ suite('EnvironmentVariable - EnvironmentVariableService', () => {
instantiationService.stub(IStorageService, storageService);
instantiationService.stub(IExtensionService, TestExtensionService);
instantiationService.stub(IExtensionService, 'onDidChangeExtensions', changeExtensionsEvent.event);
instantiationService.stub(IExtensionService, 'getExtensions', [
{ identifier: { value: 'ext1' } },
{ identifier: { value: 'ext2' } },
{ identifier: { value: 'ext3' } }
]);
environmentVariableService = instantiationService.createInstance(TestEnvironmentVariableService);
});
test('should persist collections to the storage service and be able to restore from them', () => {
const collection = new EnvironmentVariableCollection();
collection.entries.set('A', { value: 'a', type: EnvironmentVariableMutatorType.Replace });
collection.entries.set('B', { value: 'b', type: EnvironmentVariableMutatorType.Append });
collection.entries.set('C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend });
environmentVariableService.set('ext', collection);
const collection = new Map<string, IEnvironmentVariableMutator>();
collection.set('A', { value: 'a', type: EnvironmentVariableMutatorType.Replace });
collection.set('B', { value: 'b', type: EnvironmentVariableMutatorType.Append });
collection.set('C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend });
environmentVariableService.set('ext1', collection);
deepStrictEqual([...environmentVariableService.mergedCollection.entries.entries()], [
['A', { type: EnvironmentVariableMutatorType.Replace, value: 'a' }],
['B', { type: EnvironmentVariableMutatorType.Append, value: 'b' }],
['C', { type: EnvironmentVariableMutatorType.Prepend, value: 'c' }]
['A', [{ type: EnvironmentVariableMutatorType.Replace, value: 'a' }]],
['B', [{ type: EnvironmentVariableMutatorType.Append, value: 'b' }]],
['C', [{ type: EnvironmentVariableMutatorType.Prepend, value: 'c' }]]
]);
// 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.entries.entries()], [
['A', { type: EnvironmentVariableMutatorType.Replace, value: 'a' }],
['B', { type: EnvironmentVariableMutatorType.Append, value: 'b' }],
['C', { type: EnvironmentVariableMutatorType.Prepend, value: 'c' }]
['A', [{ type: EnvironmentVariableMutatorType.Replace, value: 'a' }]],
['B', [{ type: EnvironmentVariableMutatorType.Append, value: 'b' }]],
['C', [{ type: EnvironmentVariableMutatorType.Prepend, value: 'c' }]]
]);
});
suite('Merged collection', () => {
suite('mergedCollection', () => {
test('should overwrite any other variable with the first extension that replaces', () => {
const collection1 = new EnvironmentVariableCollection();
const collection2 = new EnvironmentVariableCollection();
const collection3 = new EnvironmentVariableCollection();
collection1.entries.set('A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace });
collection1.entries.set('B', { value: 'b1', type: EnvironmentVariableMutatorType.Replace });
collection2.entries.set('A', { value: 'a2', type: EnvironmentVariableMutatorType.Replace });
collection2.entries.set('B', { value: 'b2', type: EnvironmentVariableMutatorType.Append });
collection3.entries.set('A', { value: 'a3', type: EnvironmentVariableMutatorType.Prepend });
collection3.entries.set('B', { value: 'b3', type: EnvironmentVariableMutatorType.Replace });
const collection1 = new Map<string, IEnvironmentVariableMutator>();
const collection2 = new Map<string, IEnvironmentVariableMutator>();
const collection3 = new Map<string, IEnvironmentVariableMutator>();
collection1.set('A', { value: 'a1', type: EnvironmentVariableMutatorType.Append });
collection1.set('B', { value: 'b1', type: EnvironmentVariableMutatorType.Replace });
collection2.set('A', { value: 'a2', type: EnvironmentVariableMutatorType.Replace });
collection2.set('B', { value: 'b2', type: EnvironmentVariableMutatorType.Append });
collection3.set('A', { value: 'a3', type: EnvironmentVariableMutatorType.Prepend });
collection3.set('B', { value: 'b3', type: EnvironmentVariableMutatorType.Replace });
environmentVariableService.set('ext1', collection1);
environmentVariableService.set('ext2', collection2);
environmentVariableService.set('ext3', collection3);
deepStrictEqual([...environmentVariableService.mergedCollection.entries.entries()], [
['A', { type: EnvironmentVariableMutatorType.Replace, value: 'a1' }],
['B', { type: EnvironmentVariableMutatorType.Replace, value: 'b1' }]
['A', [
{ type: EnvironmentVariableMutatorType.Replace, value: 'a2' },
{ type: EnvironmentVariableMutatorType.Append, value: 'a1' }
]],
['B', [{ type: EnvironmentVariableMutatorType.Replace, value: 'b1' }]]
]);
});
test('should correctly apply the environment values from multiple extension contributions in the correct order', () => {
const collection1 = new EnvironmentVariableCollection();
const collection2 = new EnvironmentVariableCollection();
const collection3 = new EnvironmentVariableCollection();
collection1.entries.set('PATH', { value: ':a1', type: EnvironmentVariableMutatorType.Append });
collection2.entries.set('PATH', { value: 'a2:', type: EnvironmentVariableMutatorType.Prepend });
collection3.entries.set('PATH', { value: 'a3', type: EnvironmentVariableMutatorType.Replace });
const collection1 = new Map<string, IEnvironmentVariableMutator>();
const collection2 = new Map<string, IEnvironmentVariableMutator>();
const collection3 = new Map<string, IEnvironmentVariableMutator>();
collection1.set('A', { value: ':a1', type: EnvironmentVariableMutatorType.Append });
collection2.set('A', { value: 'a2:', type: EnvironmentVariableMutatorType.Prepend });
collection3.set('A', { value: 'a3', type: EnvironmentVariableMutatorType.Replace });
environmentVariableService.set('ext1', collection1);
environmentVariableService.set('ext2', collection2);
environmentVariableService.set('ext3', collection3);
// The entries should be ordered in the order they are applied
deepStrictEqual([...environmentVariableService.mergedCollection.entries.entries()], [
['PATH', [
['A', [
{ type: EnvironmentVariableMutatorType.Replace, value: 'a3' },
{ type: EnvironmentVariableMutatorType.Prepend, value: 'a2:' },
{ type: EnvironmentVariableMutatorType.Append, value: ':a1' }
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { deepStrictEqual } from 'assert';
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
import { EnvironmentVariableMutatorType, IEnvironmentVariableMutator } from 'vs/workbench/contrib/terminal/common/environmentVariable';
suite('EnvironmentVariable - deserializeEnvironmentVariableCollection', () => {
test('should construct correctly with 3 arguments', () => {
const c = deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }]
]);
const keys = [...c.keys()];
deepStrictEqual(keys, ['A', 'B', 'C']);
deepStrictEqual(c.get('A'), { value: 'a', type: EnvironmentVariableMutatorType.Replace });
deepStrictEqual(c.get('B'), { value: 'b', type: EnvironmentVariableMutatorType.Append });
deepStrictEqual(c.get('C'), { value: 'c', type: EnvironmentVariableMutatorType.Prepend });
});
});
suite('EnvironmentVariable - serializeEnvironmentVariableCollection', () => {
test('should correctly serialize the object', () => {
const collection = new Map<string, IEnvironmentVariableMutator>();
deepStrictEqual(serializeEnvironmentVariableCollection(collection), []);
collection.set('A', { value: 'a', type: EnvironmentVariableMutatorType.Replace });
collection.set('B', { value: 'b', type: EnvironmentVariableMutatorType.Append });
collection.set('C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend });
deepStrictEqual(serializeEnvironmentVariableCollection(collection), [
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }]
]);
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册