Skip to content

Commit

Permalink
Add environmentVariableCollection to PluginContext
Browse files Browse the repository at this point in the history
Signed-off-by: Igor Vinokur <ivinokur@redhat.com>
  • Loading branch information
vinokurig committed Sep 18, 2020
1 parent 4739e26 commit 7933b72
Show file tree
Hide file tree
Showing 14 changed files with 537 additions and 14 deletions.
5 changes: 5 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import type {
TimelineChangeEvent,
TimelineProviderDescriptor
} from '@theia/timeline/lib/common/timeline-model';
import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';

export interface PreferenceData {
[scope: number]: any;
Expand Down Expand Up @@ -245,6 +246,8 @@ export interface TerminalServiceExt {
$terminalOnInput(id: string, data: string): void;
$terminalSizeChanged(id: string, cols: number, rows: number): void;
$currentTerminalChanged(id: string | undefined): void;
$initEnvironmentVariableCollections(collections: [string, SerializableEnvironmentVariableCollection][]): void;
getEnvironmentVariableCollection(extensionIdentifier: string): theia.EnvironmentVariableCollection;
}
export interface OutputChannelRegistryExt {
createOutputChannel(name: string, pluginInfo: PluginInfo): theia.OutputChannel
Expand Down Expand Up @@ -312,6 +315,8 @@ export interface TerminalServiceMain {
* @param id - terminal id.
*/
$dispose(id: string): void;

$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined): void;
}

export interface AutoFocus {
Expand Down
6 changes: 4 additions & 2 deletions packages/plugin-ext/src/hosted/browser/worker/worker-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { Emitter } from '@theia/core/lib/common/event';
import { RPCProtocolImpl } from '../../../common/rpc-protocol';
import { PluginManagerExtImpl } from '../../../plugin/plugin-manager';
import { MAIN_RPC_CONTEXT, Plugin, emptyPlugin } from '../../../common/plugin-api-rpc';
import { MAIN_RPC_CONTEXT, Plugin, emptyPlugin, TerminalServiceExt } from '../../../common/plugin-api-rpc';
import { createAPIFactory } from '../../../plugin/plugin-context';
import { getPluginId, PluginMetadata, PluginPackage } from '../../../common/plugin-protocol';
import * as theia from '@theia/plugin';
Expand All @@ -31,6 +31,7 @@ import { WorkerEnvExtImpl } from './worker-env-ext';
import { ClipboardExt } from '../../../plugin/clipboard-ext';
import { KeyValueStorageProxy } from '../../../plugin/plugin-storage';
import { WebviewsExtImpl } from '../../../plugin/webviews';
import { TerminalServiceExtImpl } from '../../../plugin/terminal-ext';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ctx = self as any;
Expand Down Expand Up @@ -61,6 +62,7 @@ const preferenceRegistryExt = new PreferenceRegistryExtImpl(rpc, workspaceExt);
const debugExt = createDebugExtStub(rpc);
const clipboardExt = new ClipboardExt(rpc);
const webviewExt = new WebviewsExtImpl(rpc, workspaceExt);
const terminalService: TerminalServiceExt = new TerminalServiceExtImpl(rpc);

const pluginManager = new PluginManagerExtImpl({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -135,7 +137,7 @@ const pluginManager = new PluginManagerExtImpl({
}
}
}
}, envExt, storageProxy, preferenceRegistryExt, webviewExt, rpc);
}, envExt, terminalService, storageProxy, preferenceRegistryExt, webviewExt, rpc);

const apiFactory = createAPIFactory(
rpc,
Expand Down
8 changes: 5 additions & 3 deletions packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { ClipboardExt } from '../../plugin/clipboard-ext';
import { loadManifest } from './plugin-manifest-loader';
import { KeyValueStorageProxy } from '../../plugin/plugin-storage';
import { WebviewsExtImpl } from '../../plugin/webviews';
import { TerminalServiceExtImpl } from '../../plugin/terminal-ext';

/**
* Handle the RPC calls.
Expand All @@ -54,7 +55,8 @@ export class PluginHostRPC {
const preferenceRegistryExt = new PreferenceRegistryExtImpl(this.rpc, workspaceExt);
const clipboardExt = new ClipboardExt(this.rpc);
const webviewExt = new WebviewsExtImpl(this.rpc, workspaceExt);
this.pluginManager = this.createPluginManager(envExt, storageProxy, preferenceRegistryExt, webviewExt, this.rpc);
const terminalService = new TerminalServiceExtImpl(this.rpc);
this.pluginManager = this.createPluginManager(envExt, terminalService, storageProxy, preferenceRegistryExt, webviewExt, this.rpc);
this.rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, this.pluginManager);
this.rpc.set(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT, editorsAndDocumentsExt);
this.rpc.set(MAIN_RPC_CONTEXT.WORKSPACE_EXT, workspaceExt);
Expand Down Expand Up @@ -93,7 +95,7 @@ export class PluginHostRPC {
}

createPluginManager(
envExt: EnvExtImpl, storageProxy: KeyValueStorageProxy, preferencesManager: PreferenceRegistryExtImpl, webview: WebviewsExtImpl,
envExt: EnvExtImpl, terminalService: TerminalServiceExtImpl, storageProxy: KeyValueStorageProxy, preferencesManager: PreferenceRegistryExtImpl, webview: WebviewsExtImpl,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rpc: any): PluginManagerExtImpl {
const { extensionTestsPath } = process.env;
Expand Down Expand Up @@ -225,7 +227,7 @@ export class PluginHostRPC {
`Path ${extensionTestsPath} does not point to a valid extension test runner.`
);
} : undefined
}, envExt, storageProxy, preferencesManager, webview, rpc);
}, envExt, terminalService, storageProxy, preferencesManager, webview, rpc);
return pluginManager;
}
}
17 changes: 17 additions & 0 deletions packages/plugin-ext/src/main/browser/terminal-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-servi
import { TerminalServiceMain, TerminalServiceExt, MAIN_RPC_CONTEXT } from '../../common/plugin-api-rpc';
import { RPCProtocol } from '../../common/rpc-protocol';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
import { ShellTerminalServerProxy } from '@theia/terminal/lib/common/shell-terminal-protocol';

/**
* Plugin api service allows working with terminal emulator.
Expand All @@ -31,19 +33,34 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, Disposable
private readonly terminals: TerminalService;
private readonly shell: ApplicationShell;
private readonly extProxy: TerminalServiceExt;
private readonly shellTerminalServer: ShellTerminalServerProxy;

private readonly toDispose = new DisposableCollection();

constructor(rpc: RPCProtocol, container: interfaces.Container) {
this.terminals = container.get(TerminalService);
this.shell = container.get(ApplicationShell);
this.shellTerminalServer = container.get(ShellTerminalServerProxy);
this.extProxy = rpc.getProxy(MAIN_RPC_CONTEXT.TERMINAL_EXT);
this.toDispose.push(this.terminals.onDidCreateTerminal(terminal => this.trackTerminal(terminal)));
for (const terminal of this.terminals.all) {
this.trackTerminal(terminal);
}
this.toDispose.push(this.terminals.onDidChangeCurrentTerminal(() => this.updateCurrentTerminal()));
this.updateCurrentTerminal();
if (this.shellTerminalServer.collections.size > 0) {
const collectionAsArray = [...this.shellTerminalServer.collections.entries()];
const serializedCollections: [string, SerializableEnvironmentVariableCollection][] = collectionAsArray.map(e => [e[0], [...e[1].map.entries()]]);
this.extProxy.$initEnvironmentVariableCollections(serializedCollections);
}
}

$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined): void {
if (collection) {
this.shellTerminalServer.setCollection(extensionIdentifier, persistent, collection);
} else {
this.shellTerminalServer.deleteCollection(extensionIdentifier);
}
}

dispose(): void {
Expand Down
6 changes: 4 additions & 2 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ import {
CallHierarchyItem,
CallHierarchyIncomingCall,
CallHierarchyOutgoingCall,
TimelineItem
TimelineItem,
EnvironmentVariableMutatorType
} from './types-impl';
import { AuthenticationExtImpl } from './authentication-ext';
import { SymbolKind } from '../common/plugin-api-rpc-model';
Expand Down Expand Up @@ -911,7 +912,8 @@ export function createAPIFactory(
CallHierarchyItem,
CallHierarchyIncomingCall,
CallHierarchyOutgoingCall,
TimelineItem
TimelineItem,
EnvironmentVariableMutatorType
};
};
}
Expand Down
7 changes: 5 additions & 2 deletions packages/plugin-ext/src/plugin/plugin-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
PluginAPI,
ConfigStorage,
PluginManagerInitializeParams,
PluginManagerStartParams
PluginManagerStartParams,
TerminalServiceExt
} from '../common/plugin-api-rpc';
import { PluginMetadata, PluginJsonValidationContribution } from '../common/plugin-protocol';
import * as theia from '@theia/plugin';
Expand Down Expand Up @@ -106,6 +107,7 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager {
constructor(
private readonly host: PluginHost,
private readonly envExt: EnvExtImpl,
private readonly terminalService: TerminalServiceExt,
private readonly storageProxy: KeyValueStorageProxy,
private readonly preferencesManager: PreferenceRegistryExtImpl,
private readonly webview: WebviewsExtImpl,
Expand Down Expand Up @@ -355,7 +357,8 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager {
asAbsolutePath: asAbsolutePath,
logPath: logPath,
storagePath: storagePath,
globalStoragePath: globalStoragePath
globalStoragePath: globalStoragePath,
environmentVariableCollection: this.terminalService.getEnvironmentVariableCollection(plugin.model.id)
};
this.pluginContextsMap.set(plugin.model.id, pluginContext);

Expand Down
108 changes: 107 additions & 1 deletion packages/plugin-ext/src/plugin/terminal-ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import { UUID } from '@phosphor/coreutils/lib/uuid';
import { Terminal, TerminalOptions, PseudoTerminalOptions } from '@theia/plugin';
import { TerminalServiceExt, TerminalServiceMain, PLUGIN_RPC_CONTEXT } from '../common/plugin-api-rpc';
import { RPCProtocol } from '../common/rpc-protocol';
import { Emitter } from '@theia/core/lib/common/event';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { Deferred } from '@theia/core/lib/common/promise-util';
import * as theia from '@theia/plugin';
import { EnvironmentVariableMutatorType } from './types-impl';
import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';

/**
* Provides high level terminal plugin api to use in the Theia plugins.
Expand All @@ -42,6 +44,8 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
private readonly onDidChangeActiveTerminalEmitter = new Emitter<Terminal | undefined>();
readonly onDidChangeActiveTerminal: theia.Event<Terminal | undefined> = this.onDidChangeActiveTerminalEmitter.event;

protected environmentVariableCollections: Map<string, EnvironmentVariableCollection> = new Map();

constructor(rpc: RPCProtocol) {
this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.TERMINAL_MAIN);
}
Expand Down Expand Up @@ -151,6 +155,108 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
this.onDidChangeActiveTerminalEmitter.fire(this.activeTerminal);
}

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// some code copied and modified from https://github.com/microsoft/vscode/blob/1.49.0/src/vs/workbench/api/common/extHostTerminalService.ts

getEnvironmentVariableCollection(extensionIdentifier: string): theia.EnvironmentVariableCollection {
let collection = this.environmentVariableCollections.get(extensionIdentifier);
if (!collection) {
collection = new EnvironmentVariableCollection();
this.setEnvironmentVariableCollection(extensionIdentifier, collection);
}
return collection;
}

private syncEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void {
const serialized = [...collection.map.entries()];
this.proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized);
}

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);
});
}

$initEnvironmentVariableCollections(collections: [string, SerializableEnvironmentVariableCollection][]): void {
collections.forEach(entry => {
const extensionIdentifier = entry[0];
const collection = new EnvironmentVariableCollection(entry[1]);
this.setEnvironmentVariableCollection(extensionIdentifier, collection);
});
}

}

export class EnvironmentVariableCollection implements theia.EnvironmentVariableCollection {
readonly map: Map<string, theia.EnvironmentVariableMutator> = new Map();
private _persistent: boolean = true;

public get persistent(): boolean { return this._persistent; }
public set persistent(value: boolean) {
this._persistent = value;
this.onDidChangeCollectionEmitter.fire();
}

protected readonly onDidChangeCollectionEmitter: Emitter<void> = new Emitter<void>();
onDidChangeCollection: Event<void> = this.onDidChangeCollectionEmitter.event;

constructor(
serialized?: SerializableEnvironmentVariableCollection
) {
this.map = new Map(serialized);
}

get size(): number {
return this.map.size;
}

replace(variable: string, value: string): void {
this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Replace });
}

append(variable: string, value: string): void {
this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Append });
}

prepend(variable: string, value: string): void {
this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Prepend });
}

private _setIfDiffers(variable: string, mutator: theia.EnvironmentVariableMutator): void {
const current = this.map.get(variable);
if (!current || current.value !== mutator.value || current.type !== mutator.type) {
this.map.set(variable, mutator);
this.onDidChangeCollectionEmitter.fire();
}
}

get(variable: string): theia.EnvironmentVariableMutator | undefined {
return this.map.get(variable);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
forEach(callback: (variable: string, mutator: theia.EnvironmentVariableMutator, collection: theia.EnvironmentVariableCollection) => any, thisArg?: any): void {
this.map.forEach((value, key) => callback.call(thisArg, key, value, this));
}

delete(variable: string): void {
this.map.delete(variable);
this.onDidChangeCollectionEmitter.fire();
}

clear(): void {
this.map.clear();
this.onDidChangeCollectionEmitter.fire();
}
}

export class TerminalExtImpl implements Terminal {
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,12 @@ export enum EndOfLine {
CRLF = 2
}

export enum EnvironmentVariableMutatorType {
Replace = 1,
Append = 2,
Prepend = 3
}

export class SnippetString {

static isSnippetString(thing: {}): thing is SnippetString {
Expand Down
Loading

0 comments on commit 7933b72

Please sign in to comment.