move mainThread[L,T], #70319

This commit is contained in:
Johannes Rieken
2019-03-19 17:39:45 +01:00
parent c514d9e783
commit 1c710c65a8
7 changed files with 10 additions and 10 deletions

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } from 'vs/base/common/uri';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { MainThreadLanguagesShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
@extHostNamedCustomer(MainContext.MainThreadLanguages)
export class MainThreadLanguages implements MainThreadLanguagesShape {
constructor(
_extHostContext: IExtHostContext,
@IModeService private readonly _modeService: IModeService,
@IModelService private readonly _modelService: IModelService
) {
}
dispose(): void {
// nothing
}
$getLanguages(): Promise<string[]> {
return Promise.resolve(this._modeService.getRegisteredModes());
}
$changeLanguage(resource: UriComponents, languageId: string): Promise<void> {
const uri = URI.revive(resource);
const model = this._modelService.getModel(uri);
if (!model) {
return Promise.reject(new Error('Invalid uri'));
}
const languageIdentifier = this._modeService.getLanguageIdentifier(languageId);
if (!languageIdentifier || languageIdentifier.language !== languageId) {
return Promise.reject(new Error(`Unknown language id: ${languageId}`));
}
this._modelService.setMode(model, this._modeService.create(languageId));
return Promise.resolve(undefined);
}
}

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ILogService } from 'vs/platform/log/common/log';
import { Disposable } from 'vs/base/common/lifecycle';
import { IExtHostContext, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
@extHostCustomer
export class MainThreadLogService extends Disposable {
constructor(
extHostContext: IExtHostContext,
@ILogService logService: ILogService,
) {
super();
this._register(logService.onDidChangeLogLevel(level => extHostContext.getProxy(ExtHostContext.ExtHostLogService).$setLevel(level)));
}
}

View File

@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { MainThreadTelemetryShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
@extHostNamedCustomer(MainContext.MainThreadTelemetry)
export class MainThreadTelemetry implements MainThreadTelemetryShape {
private static readonly _name = 'pluginHostTelemetry';
constructor(
extHostContext: IExtHostContext,
@ITelemetryService private readonly _telemetryService: ITelemetryService
) {
//
}
dispose(): void {
//
}
$publicLog(eventName: string, data: any = Object.create(null)): void {
// __GDPR__COMMON__ "pluginHostTelemetry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
data[MainThreadTelemetry._name] = true;
this._telemetryService.publicLog(eventName, data);
}
}

View File

@@ -0,0 +1,256 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/contrib/terminal/common/terminal';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { UriComponents, URI } from 'vs/base/common/uri';
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
private _proxy: ExtHostTerminalServiceShape;
private _remoteAuthority: string | null;
private _toDispose: IDisposable[] = [];
private _terminalProcesses: { [id: number]: ITerminalProcessExtHostProxy } = {};
private _terminalOnDidWriteDataListeners: { [id: number]: IDisposable } = {};
private _terminalOnDidAcceptInputListeners: { [id: number]: IDisposable } = {};
constructor(
extHostContext: IExtHostContext,
@ITerminalService private readonly terminalService: ITerminalService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
this._remoteAuthority = extHostContext.remoteAuthority;
this._toDispose.push(terminalService.onInstanceCreated((instance) => {
// Delay this message so the TerminalInstance constructor has a chance to finish and
// return the ID normally to the extension host. The ID that is passed here will be used
// to register non-extension API terminals in the extension host.
setTimeout(() => {
this._onTerminalOpened(instance);
this._onInstanceDimensionsChanged(instance);
}, EXT_HOST_CREATION_DELAY);
}));
this._toDispose.push(terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
this._toDispose.push(terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance)));
this._toDispose.push(terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance)));
this._toDispose.push(terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request)));
this._toDispose.push(terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : null)));
this._toDispose.push(terminalService.onInstanceTitleChanged(instance => this._onTitleChanged(instance.id, instance.title)));
// Set initial ext host state
this.terminalService.terminalInstances.forEach(t => {
this._onTerminalOpened(t);
t.processReady.then(() => this._onTerminalProcessIdReady(t));
});
const activeInstance = this.terminalService.getActiveInstance();
if (activeInstance) {
this._proxy.$acceptActiveTerminalChanged(activeInstance.id);
}
}
public dispose(): void {
this._toDispose = dispose(this._toDispose);
// TODO@Daniel: Should all the previously created terminals be disposed
// when the extension host process goes down ?
}
public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string, cwd?: string | UriComponents, env?: { [key: string]: string }, waitOnExit?: boolean, strictEnv?: boolean): Promise<{ id: number, name: string }> {
const shellLaunchConfig: IShellLaunchConfig = {
name,
executable: shellPath,
args: shellArgs,
cwd: typeof cwd === 'string' ? cwd : URI.revive(cwd),
waitOnExit,
ignoreConfigurationCwd: true,
env,
strictEnv
};
const terminal = this.terminalService.createTerminal(shellLaunchConfig);
return Promise.resolve({
id: terminal.id,
name: terminal.title
});
}
public $createTerminalRenderer(name: string): Promise<number> {
const instance = this.terminalService.createTerminalRenderer(name);
return Promise.resolve(instance.id);
}
public $show(terminalId: number, preserveFocus: boolean): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
this.terminalService.setActiveInstance(terminalInstance);
this.terminalService.showPanel(!preserveFocus);
}
}
public $hide(terminalId: number): void {
const instance = this.terminalService.getActiveInstance();
if (instance && instance.id === terminalId) {
this.terminalService.hidePanel();
}
}
public $dispose(terminalId: number): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.dispose();
}
}
public $terminalRendererWrite(terminalId: number, text: string): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.write(text);
}
}
public $terminalRendererSetName(terminalId: number, name: string): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.setTitle(name, false);
}
}
public $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.setDimensions(dimensions);
}
}
public $terminalRendererRegisterOnInputListener(terminalId: number): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (!terminalInstance) {
return;
}
// Listener already registered
if (this._terminalOnDidAcceptInputListeners.hasOwnProperty(terminalId)) {
return;
}
// Register
this._terminalOnDidAcceptInputListeners[terminalId] = terminalInstance.onRendererInput(data => this._onTerminalRendererInput(terminalId, data));
terminalInstance.addDisposable(this._terminalOnDidAcceptInputListeners[terminalId]);
}
public $sendText(terminalId: number, text: string, addNewLine: boolean): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.sendText(text, addNewLine);
}
}
public $registerOnDataListener(terminalId: number): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (!terminalInstance) {
return;
}
// Listener already registered
if (this._terminalOnDidWriteDataListeners[terminalId]) {
return;
}
// Register
this._terminalOnDidWriteDataListeners[terminalId] = terminalInstance.onData(data => {
this._onTerminalData(terminalId, data);
});
terminalInstance.addDisposable(this._terminalOnDidWriteDataListeners[terminalId]);
}
private _onActiveTerminalChanged(terminalId: number | null): void {
this._proxy.$acceptActiveTerminalChanged(terminalId);
}
private _onTerminalData(terminalId: number, data: string): void {
this._proxy.$acceptTerminalProcessData(terminalId, data);
}
private _onTitleChanged(terminalId: number, name: string): void {
this._proxy.$acceptTerminalTitleChange(terminalId, name);
}
private _onTerminalRendererInput(terminalId: number, data: string): void {
this._proxy.$acceptTerminalRendererInput(terminalId, data);
}
private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
this._proxy.$acceptTerminalClosed(terminalInstance.id);
}
private _onTerminalOpened(terminalInstance: ITerminalInstance): void {
if (terminalInstance.title) {
this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title);
} else {
terminalInstance.waitForTitle().then(title => {
this._proxy.$acceptTerminalOpened(terminalInstance.id, title);
});
}
}
private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void {
if (terminalInstance.processId === undefined) {
return;
}
this._proxy.$acceptTerminalProcessId(terminalInstance.id, terminalInstance.processId);
}
private _onInstanceDimensionsChanged(instance: ITerminalInstance): void {
this._proxy.$acceptTerminalDimensions(instance.id, instance.cols, instance.rows);
}
private _onTerminalRequestExtHostProcess(request: ITerminalProcessExtHostRequest): void {
// Only allow processes on remote ext hosts
if (!this._remoteAuthority) {
return;
}
this._terminalProcesses[request.proxy.terminalId] = request.proxy;
const shellLaunchConfigDto: ShellLaunchConfigDto = {
name: request.shellLaunchConfig.name,
executable: request.shellLaunchConfig.executable,
args: request.shellLaunchConfig.args,
cwd: request.shellLaunchConfig.cwd,
env: request.shellLaunchConfig.env
};
this._proxy.$createProcess(request.proxy.terminalId, shellLaunchConfigDto, request.activeWorkspaceRootUri, request.cols, request.rows);
request.proxy.onInput(data => this._proxy.$acceptProcessInput(request.proxy.terminalId, data));
request.proxy.onResize(dimensions => this._proxy.$acceptProcessResize(request.proxy.terminalId, dimensions.cols, dimensions.rows));
request.proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(request.proxy.terminalId, immediate));
request.proxy.onRequestCwd(() => this._proxy.$acceptProcessRequestCwd(request.proxy.terminalId));
request.proxy.onRequestInitialCwd(() => this._proxy.$acceptProcessRequestInitialCwd(request.proxy.terminalId));
}
public $sendProcessTitle(terminalId: number, title: string): void {
this._terminalProcesses[terminalId].emitTitle(title);
}
public $sendProcessData(terminalId: number, data: string): void {
this._terminalProcesses[terminalId].emitData(data);
}
public $sendProcessPid(terminalId: number, pid: number): void {
this._terminalProcesses[terminalId].emitPid(pid);
}
public $sendProcessExit(terminalId: number, exitCode: number): void {
this._terminalProcesses[terminalId].emitExit(exitCode);
delete this._terminalProcesses[terminalId];
}
public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void {
this._terminalProcesses[terminalId].emitInitialCwd(initialCwd);
}
public $sendProcessCwd(terminalId: number, cwd: string): void {
this._terminalProcesses[terminalId].emitCwd(cwd);
}
}

View File

@@ -0,0 +1,209 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions } from 'vs/workbench/common/views';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { distinct } from 'vs/base/common/arrays';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { isUndefinedOrNull, isNumber } from 'vs/base/common/types';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { Registry } from 'vs/platform/registry/common/platform';
@extHostNamedCustomer(MainContext.MainThreadTreeViews)
export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape {
private readonly _proxy: ExtHostTreeViewsShape;
private readonly _dataProviders: Map<string, TreeViewDataProvider> = new Map<string, TreeViewDataProvider>();
constructor(
extHostContext: IExtHostContext,
@IViewsService private readonly viewsService: IViewsService,
@INotificationService private readonly notificationService: INotificationService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews);
}
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): void {
const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService);
this._dataProviders.set(treeViewId, dataProvider);
const viewer = this.getTreeView(treeViewId);
if (viewer) {
viewer.dataProvider = dataProvider;
viewer.showCollapseAllAction = !!options.showCollapseAll;
this.registerListeners(treeViewId, viewer);
this._proxy.$setVisible(treeViewId, viewer.visible);
} else {
this.notificationService.error('No view is registered with id: ' + treeViewId);
}
}
$reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise<void> {
return this.viewsService.openView(treeViewId, options.focus)
.then(() => {
const viewer = this.getTreeView(treeViewId);
if (viewer) {
return this.reveal(viewer, this._dataProviders.get(treeViewId)!, item, parentChain, options);
}
return undefined;
});
}
$refresh(treeViewId: string, itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): Promise<void> {
const viewer = this.getTreeView(treeViewId);
const dataProvider = this._dataProviders.get(treeViewId);
if (viewer && dataProvider) {
const itemsToRefresh = dataProvider.getItemsToRefresh(itemsToRefreshByHandle);
return viewer.refresh(itemsToRefresh.length ? itemsToRefresh : undefined);
}
return Promise.resolve();
}
$setMessage(treeViewId: string, message: string | IMarkdownString): void {
const viewer = this.getTreeView(treeViewId);
if (viewer) {
viewer.message = message;
}
}
private async reveal(treeView: ITreeView, dataProvider: TreeViewDataProvider, itemIn: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise<void> {
options = options ? options : { select: false, focus: false };
const select = isUndefinedOrNull(options.select) ? false : options.select;
const focus = isUndefinedOrNull(options.focus) ? false : options.focus;
let expand = Math.min(isNumber(options.expand) ? options.expand : options.expand === true ? 1 : 0, 3);
if (dataProvider.isEmpty()) {
// Refresh if empty
await treeView.refresh();
}
for (const parent of parentChain) {
await treeView.expand(parent);
}
const item = dataProvider.getItem(itemIn.handle);
if (item) {
await treeView.reveal(item);
if (select) {
treeView.setSelection([item]);
}
if (focus) {
treeView.setFocus(item);
}
let itemsToExpand = [item];
for (; itemsToExpand.length > 0 && expand > 0; expand--) {
await treeView.expand(itemsToExpand);
itemsToExpand = itemsToExpand.reduce((result, itemValue) => {
const item = dataProvider.getItem(itemValue.handle);
if (item && item.children && item.children.length) {
result.push(...item.children);
}
return result;
}, [] as ITreeItem[]);
}
}
}
private registerListeners(treeViewId: string, treeView: ITreeView): void {
this._register(treeView.onDidExpandItem(item => this._proxy.$setExpanded(treeViewId, item.handle, true)));
this._register(treeView.onDidCollapseItem(item => this._proxy.$setExpanded(treeViewId, item.handle, false)));
this._register(treeView.onDidChangeSelection(items => this._proxy.$setSelection(treeViewId, items.map(({ handle }) => handle))));
this._register(treeView.onDidChangeVisibility(isVisible => this._proxy.$setVisible(treeViewId, isVisible)));
}
private getTreeView(treeViewId: string): ITreeView | null {
const viewDescriptor: ITreeViewDescriptor = <ITreeViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(treeViewId);
return viewDescriptor ? viewDescriptor.treeView : null;
}
dispose(): void {
this._dataProviders.forEach((dataProvider, treeViewId) => {
const treeView = this.getTreeView(treeViewId);
if (treeView) {
treeView.dataProvider = null;
}
});
this._dataProviders.clear();
super.dispose();
}
}
type TreeItemHandle = string;
class TreeViewDataProvider implements ITreeViewDataProvider {
private readonly itemsMap: Map<TreeItemHandle, ITreeItem> = new Map<TreeItemHandle, ITreeItem>();
constructor(private readonly treeViewId: string,
private readonly _proxy: ExtHostTreeViewsShape,
private readonly notificationService: INotificationService
) {
}
getChildren(treeItem?: ITreeItem): Promise<ITreeItem[]> {
return Promise.resolve(this._proxy.$getChildren(this.treeViewId, treeItem ? treeItem.handle : undefined)
.then(
children => this.postGetChildren(children),
err => {
this.notificationService.error(err);
return [];
}));
}
getItemsToRefresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): ITreeItem[] {
const itemsToRefresh: ITreeItem[] = [];
if (itemsToRefreshByHandle) {
for (const treeItemHandle of Object.keys(itemsToRefreshByHandle)) {
const currentTreeItem = this.getItem(treeItemHandle);
if (currentTreeItem) { // Refresh only if the item exists
const treeItem = itemsToRefreshByHandle[treeItemHandle];
// Update the current item with refreshed item
this.updateTreeItem(currentTreeItem, treeItem);
if (treeItemHandle === treeItem.handle) {
itemsToRefresh.push(currentTreeItem);
} else {
// Update maps when handle is changed and refresh parent
this.itemsMap.delete(treeItemHandle);
this.itemsMap.set(currentTreeItem.handle, currentTreeItem);
const parent = treeItem.parentHandle ? this.itemsMap.get(treeItem.parentHandle) : null;
if (parent) {
itemsToRefresh.push(parent);
}
}
}
}
}
return itemsToRefresh;
}
getItem(treeItemHandle: string): ITreeItem | undefined {
return this.itemsMap.get(treeItemHandle);
}
isEmpty(): boolean {
return this.itemsMap.size === 0;
}
private postGetChildren(elements: ITreeItem[]): ITreeItem[] {
const result: ITreeItem[] = [];
if (elements) {
for (const element of elements) {
this.itemsMap.set(element.handle, element);
result.push(element);
}
}
return result;
}
private updateTreeItem(current: ITreeItem, treeItem: ITreeItem): void {
treeItem.children = treeItem.children ? treeItem.children : undefined;
if (current) {
const properties = distinct([...Object.keys(current), ...Object.keys(treeItem)]);
for (const property of properties) {
current[property] = treeItem[property];
}
}
}
}