Load in persisted collections from past sessions on ext host

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