- Initial version of custom views and tree data provider API
This commit is contained in:
Sandeep Somavarapu
2017-05-19 18:22:59 +02:00
parent 6fd1ac9ccc
commit e87e0999d4
32 changed files with 969 additions and 1390 deletions

View File

@@ -18,7 +18,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/node/extHostDocumentSaveParticipant';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics';
import { ExtHostExplorerView } from 'vs/workbench/api/node/extHostExplorerView';
import { ExtHostTreeViews } from 'vs/workbench/api/node/extHostTreeViews';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { ExtHostQuickOpen } from 'vs/workbench/api/node/extHostQuickOpen';
import { ExtHostProgress } from 'vs/workbench/api/node/extHostProgress';
@@ -111,7 +111,7 @@ export function createApiFactory(
const extHostDocumentSaveParticipant = col.define(ExtHostContext.ExtHostDocumentSaveParticipant).set<ExtHostDocumentSaveParticipant>(new ExtHostDocumentSaveParticipant(extHostDocuments, threadService.get(MainContext.MainThreadWorkspace)));
const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set<ExtHostEditors>(new ExtHostEditors(threadService, extHostDocumentsAndEditors));
const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set<ExtHostCommands>(new ExtHostCommands(threadService, extHostHeapService));
const extHostExplorerView = col.define(ExtHostContext.ExtHostExplorerView).set<ExtHostExplorerView>(new ExtHostExplorerView(threadService, extHostCommands));
const extHostTreeViews = col.define(ExtHostContext.ExtHostTreeViews).set<ExtHostTreeViews>(new ExtHostTreeViews(threadService, extHostCommands));
const extHostConfiguration = col.define(ExtHostContext.ExtHostConfiguration).set<ExtHostConfiguration>(new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration), initData.configuration));
const extHostDiagnostics = col.define(ExtHostContext.ExtHostDiagnostics).set<ExtHostDiagnostics>(new ExtHostDiagnostics(threadService));
const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set<ExtHostLanguageFeatures>(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics));
@@ -369,8 +369,8 @@ export function createApiFactory(
sampleFunction: proposedApiFunction(extension, () => {
return extHostMessageService.showMessage(Severity.Info, 'Hello Proposed Api!', {}, []);
}),
createExplorerView: proposedApiFunction(extension, (id: string, name: string, provider: vscode.TreeDataProvider<any>): vscode.View<any> => {
return extHostExplorerView.createExplorerView(id, name, provider);
registerTreeDataProvider: proposedApiFunction(extension, (id: string, treeDataProvider: vscode.TreeDataProvider<any>): vscode.Disposable => {
return extHostTreeViews.registerTreeDataProvider(id, treeDataProvider);
})
};
@@ -533,6 +533,7 @@ export function createApiFactory(
ViewColumn: extHostTypes.ViewColumn,
WorkspaceEdit: extHostTypes.WorkspaceEdit,
ProgressLocation: extHostTypes.ProgressLocation,
TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState,
// functions
FileLocationKind: extHostTypes.FileLocationKind,
ApplyToKind: extHostTypes.ApplyToKind,

View File

@@ -194,17 +194,17 @@ export abstract class MainThreadEditorsShape {
$getDiffInformation(id: string): TPromise<editorCommon.ILineChange[]> { throw ni(); }
}
export interface ITreeNode {
id: string;
label: string;
hasChildren: boolean;
clickCommand: string;
contextKey: string;
export interface TreeItem extends vscode.TreeItem {
handle: number;
commandId?: string;
icon?: string;
iconDark?: string;
children?: TreeItem[];
}
export abstract class MainThreadExplorerViewShape {
$registerView(id: string, name: string): void { throw ni(); }
$refresh(viewId: string, node: ITreeNode): void { throw ni(); }
export abstract class MainThreadTreeViewsShape {
$registerView(treeViewId: string): void { throw ni(); }
$refresh(treeViewId: string, treeItemHandle?: number): void { throw ni(); }
}
export abstract class MainThreadErrorsShape {
@@ -408,11 +408,15 @@ export abstract class ExtHostDocumentsAndEditorsShape {
$acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void { throw ni(); }
}
export type TreeViewCommandArg = {
treeViewId: string,
treeItemHandle: number
};
export abstract class ExtHostExplorerViewShape {
$provideRootNode(viewId: string): TPromise<ITreeNode> { throw ni(); };
$resolveChildren(viewId: string, node: ITreeNode): TPromise<ITreeNode[]> { throw ni(); }
$getInternalCommand(viewId: string, node: ITreeNode): TPromise<modes.Command> { throw ni(); }
export abstract class ExtHostTreeViewsShape {
$getElements(treeViewId: string): TPromise<TreeItem[]> { throw ni(); }
$getChildren(treeViewId: string, treeItemHandle: number): TPromise<TreeItem[]> { throw ni(); }
$restore(treeViewId: string, treeItems: TreeItem[]): TPromise<TreeItem[]> { throw ni(); }
}
export abstract class ExtHostExtensionServiceShape {
@@ -503,7 +507,7 @@ export const MainContext = {
MainThreadDocuments: createMainId<MainThreadDocumentsShape>('MainThreadDocuments', MainThreadDocumentsShape),
MainThreadEditors: createMainId<MainThreadEditorsShape>('MainThreadEditors', MainThreadEditorsShape),
MainThreadErrors: createMainId<MainThreadErrorsShape>('MainThreadErrors', MainThreadErrorsShape),
MainThreadExplorerViews: createMainId<MainThreadExplorerViewShape>('MainThreadExplorerView', MainThreadExplorerViewShape),
MainThreadTreeViews: createMainId<MainThreadTreeViewsShape>('MainThreadTreeViews', MainThreadTreeViewsShape),
MainThreadLanguageFeatures: createMainId<MainThreadLanguageFeaturesShape>('MainThreadLanguageFeatures', MainThreadLanguageFeaturesShape),
MainThreadLanguages: createMainId<MainThreadLanguagesShape>('MainThreadLanguages', MainThreadLanguagesShape),
MainThreadMessageService: createMainId<MainThreadMessageServiceShape>('MainThreadMessageService', MainThreadMessageServiceShape),
@@ -528,7 +532,7 @@ export const ExtHostContext = {
ExtHostDocuments: createExtId<ExtHostDocumentsShape>('ExtHostDocuments', ExtHostDocumentsShape),
ExtHostDocumentSaveParticipant: createExtId<ExtHostDocumentSaveParticipantShape>('ExtHostDocumentSaveParticipant', ExtHostDocumentSaveParticipantShape),
ExtHostEditors: createExtId<ExtHostEditorsShape>('ExtHostEditors', ExtHostEditorsShape),
ExtHostExplorerView: createExtId<ExtHostExplorerViewShape>('ExtHostExplorerView', ExtHostExplorerViewShape),
ExtHostTreeViews: createExtId<ExtHostTreeViewsShape>('ExtHostTreeViews', ExtHostTreeViewsShape),
ExtHostFileSystemEventService: createExtId<ExtHostFileSystemEventServiceShape>('ExtHostFileSystemEventService', ExtHostFileSystemEventServiceShape),
ExtHostHeapService: createExtId<ExtHostHeapServiceShape>('ExtHostHeapMonitor', ExtHostHeapServiceShape),
ExtHostLanguageFeatures: createExtId<ExtHostLanguageFeaturesShape>('ExtHostLanguageFeatures', ExtHostLanguageFeaturesShape),

View File

@@ -1,163 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { localize } from 'vs/nls';
import { View, TreeDataProvider } from 'vscode';
import { defaultGenerator } from 'vs/base/common/idGenerator';
import { TPromise } from 'vs/base/common/winjs.base';
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
import { MainContext, ExtHostExplorerViewShape, MainThreadExplorerViewShape, ITreeNode } from './extHost.protocol';
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
import { asWinJsPromise } from 'vs/base/common/async';
import * as modes from 'vs/editor/common/modes';
class TreeNodeImpl implements ITreeNode {
readonly id: string;
label: string;
hasChildren: boolean;
clickCommand: string = null;
contextKey: string;
constructor(readonly providerId: string, node: any, provider: TreeDataProvider<any>) {
this.id = defaultGenerator.nextId();
this.label = provider.getLabel ? provider.getLabel(node) : node.toString();
this.hasChildren = provider.getHasChildren ? provider.getHasChildren(node) : true;
this.contextKey = provider.getContextKey ? provider.getContextKey(node) : null;
if (provider.getClickCommand) {
const command = provider.getClickCommand(node);
if (command) {
this.clickCommand = command.command;
}
}
}
}
export class ExtHostExplorerView extends ExtHostExplorerViewShape {
private _proxy: MainThreadExplorerViewShape;
private _extNodeProviders: { [providerId: string]: TreeDataProvider<any> };
private _extViews: Map<string, View<any>> = new Map<string, View<any>>();
private _extNodeMaps: { [providerId: string]: { [id: string]: ITreeNode } };
private _mainNodesMap: Map<string, Map<any, ITreeNode>>;
private _childrenNodesMap: Map<string, Map<any, any[]>>;
constructor(
threadService: IThreadService,
private commands: ExtHostCommands
) {
super();
this._proxy = threadService.get(MainContext.MainThreadExplorerViews);
this._extNodeProviders = Object.create(null);
this._extNodeMaps = Object.create(null);
this._mainNodesMap = new Map<string, Map<any, ITreeNode>>();
this._childrenNodesMap = new Map<string, Map<any, any[]>>();
commands.registerArgumentProcessor({
processArgument: arg => {
if (arg && arg.providerId && arg.id) {
const extNodeMap = this._extNodeMaps[arg.providerId];
return extNodeMap[arg.id];
}
return arg;
}
});
}
createExplorerView<T>(viewId: string, viewName: string, provider: TreeDataProvider<T>): View<T> {
this._proxy.$registerView(viewId, viewName);
this._extNodeProviders[viewId] = provider;
this._mainNodesMap.set(viewId, new Map<any, ITreeNode>());
this._childrenNodesMap.set(viewId, new Map<any, any>());
const treeView: View<T> = {
refresh: (node: T) => {
const mainThreadNode = this._mainNodesMap.get(viewId).get(node);
this._proxy.$refresh(viewId, mainThreadNode);
},
dispose: () => {
delete this._extNodeProviders[viewId];
delete this._extNodeProviders[viewId];
this._mainNodesMap.delete(viewId);
this._childrenNodesMap.delete(viewId);
this._extViews.delete(viewId);
}
};
this._extViews.set(viewId, treeView);
return treeView;
}
$provideRootNode(providerId: string): TPromise<ITreeNode> {
const provider = this._extNodeProviders[providerId];
if (!provider) {
const errMessage = localize('treeExplorer.notRegistered', 'No TreeExplorerNodeProvider with id \'{0}\' registered.', providerId);
return TPromise.wrapError<ITreeNode>(errMessage);
}
return asWinJsPromise(() => provider.provideRootNode()).then(extRootNode => {
const extNodeMap: { [id: string]: ITreeNode } = Object.create(null);
const internalRootNode = new TreeNodeImpl(providerId, extRootNode, provider);
extNodeMap[internalRootNode.id] = extRootNode;
this._extNodeMaps[providerId] = extNodeMap;
this._mainNodesMap.get(providerId).set(extRootNode, internalRootNode);
return internalRootNode;
}, err => {
const errMessage = localize('treeExplorer.failedToProvideRootNode', 'TreeExplorerNodeProvider \'{0}\' failed to provide root node.', providerId);
return TPromise.wrapError<ITreeNode>(errMessage);
});
}
$resolveChildren(providerId: string, mainThreadNode: ITreeNode): TPromise<ITreeNode[]> {
const provider = this._extNodeProviders[providerId];
if (!provider) {
const errMessage = localize('treeExplorer.notRegistered', 'No TreeExplorerNodeProvider with id \'{0}\' registered.', providerId);
return TPromise.wrapError<ITreeNode[]>(errMessage);
}
const extNodeMap = this._extNodeMaps[providerId];
const extNode = extNodeMap[mainThreadNode.id];
const currentChildren = this._childrenNodesMap.get(providerId).get(extNode);
if (currentChildren) {
for (const child of currentChildren) {
this._mainNodesMap.get(providerId).delete(child);
}
}
return asWinJsPromise(() => provider.resolveChildren(extNode)).then(children => {
return children.map(extChild => {
const internalChild = new TreeNodeImpl(providerId, extChild, provider);
extNodeMap[internalChild.id] = extChild;
this._mainNodesMap.get(providerId).set(extChild, internalChild);
return internalChild;
});
});
}
// Convert the command on the ExtHost side so we can pass the original externalNode to the registered handler
$getInternalCommand(providerId: string, mainThreadNode: ITreeNode): TPromise<modes.Command> {
const commandConverter = this.commands.converter;
if (mainThreadNode.clickCommand) {
const extNode = this._extNodeMaps[providerId][mainThreadNode.id];
const internalCommand = commandConverter.toInternal({
title: '',
command: mainThreadNode.clickCommand,
arguments: [extNode]
});
return TPromise.wrap(internalCommand);
}
return TPromise.as(null);
}
}

View File

@@ -0,0 +1,210 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { localize } from 'vs/nls';
import * as vscode from 'vscode';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { Disposable } from 'vs/base/common/lifecycle';
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
import { MainContext, ExtHostTreeViewsShape, MainThreadTreeViewsShape, TreeItem, TreeViewCommandArg } from './extHost.protocol';
import { TreeItemCollapsibleState } from './extHostTypes';
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
import { asWinJsPromise } from 'vs/base/common/async';
import * as modes from 'vs/editor/common/modes';
type TreeItemHandle = number;
export class ExtHostTreeViews extends ExtHostTreeViewsShape {
private treeViews: Map<string, ExtHostTreeView<any>> = new Map<string, ExtHostTreeView<any>>();
private _proxy: MainThreadTreeViewsShape;
constructor(
threadService: IThreadService,
private commands: ExtHostCommands
) {
super();
this._proxy = threadService.get(MainContext.MainThreadTreeViews);
commands.registerArgumentProcessor({
processArgument: arg => {
if (arg && arg.treeViewId && arg.treeItemHandle) {
return this.convertArgument(arg);
}
return arg;
}
});
}
registerTreeDataProvider<T>(id: string, treeDataProvider: vscode.TreeDataProvider<T>): vscode.Disposable {
const treeView = new ExtHostTreeView<T>(id, treeDataProvider, this._proxy);
this.treeViews.set(id, treeView);
return {
dispose: () => {
this.treeViews.delete(id);
treeView.dispose();
}
};
}
$getElements(treeViewId: string): TPromise<TreeItem[]> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
return TPromise.wrapError<TreeItem[]>(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
}
return treeView.getTreeItems();
}
$getChildren(treeViewId: string, treeItemHandle?: number): TPromise<TreeItem[]> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
return TPromise.wrapError<TreeItem[]>(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
}
return treeView.getChildren(treeItemHandle);
}
private convertArgument(arg: TreeViewCommandArg): any {
const treeView = this.treeViews.get(arg.treeViewId);
if (!treeView) {
return TPromise.wrapError<modes.Command>(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', arg.treeViewId));
}
return treeView.getExtensionElement(arg.treeItemHandle);
}
}
class ExtHostTreeView<T> extends Disposable {
private _itemHandlePool = 0;
private extElementsMap: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
private itemHandlesMap: Map<T, TreeItemHandle> = new Map<T, TreeItemHandle>();
private extChildrenElementsMap: Map<T, T[]> = new Map<T, T[]>();
constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider<T>, private proxy: MainThreadTreeViewsShape) {
super();
this.proxy.$registerView(viewId);
this._register(dataProvider.onDidChange(element => this._refresh(element)));
}
getTreeItems(): TPromise<TreeItem[]> {
this.extChildrenElementsMap.clear();
this.extElementsMap.clear();
this.itemHandlesMap.clear();
return asWinJsPromise(() => this.dataProvider.getChildren())
.then(elements => this.processAndMapElements(elements));
}
getChildren(treeItemHandle: TreeItemHandle): TPromise<TreeItem[]> {
let extElement = this.getExtensionElement(treeItemHandle);
if (extElement) {
this.clearChildren(extElement);
} else {
return TPromise.wrapError<TreeItem[]>(localize('treeItem.notFound', 'No tree item with id \'{0}\' found.', treeItemHandle));
}
return asWinJsPromise(() => this.dataProvider.getChildren(extElement))
.then(childrenElements => this.processAndMapElements(childrenElements));
}
getExtensionElement(treeItemHandle: TreeItemHandle): T {
return this.extElementsMap.get(treeItemHandle);
}
private _refresh(element: T): void {
if (element) {
const itemHandle = this.itemHandlesMap.get(element);
if (itemHandle) {
this.proxy.$refresh(this.viewId, itemHandle);
}
} else {
this.proxy.$refresh(this.viewId);
}
}
private processAndMapElements(elements: T[]): TPromise<TreeItem[]> {
const treeItemsPromises: TPromise<TreeItem>[] = [];
for (const element of elements) {
if (this.extChildrenElementsMap.has(element)) {
return TPromise.wrapError<TreeItem[]>(localize('treeView.duplicateElement', 'Element {0} is already registered', element));
}
const treeItem = this.massageTreeItem(this.dataProvider.getTreeItem(element));
this.itemHandlesMap.set(element, treeItem.handle);
this.extElementsMap.set(treeItem.handle, element);
if (treeItem.collapsibleState === TreeItemCollapsibleState.Expanded) {
treeItemsPromises.push(this.getChildren(treeItem.handle).then(children => {
treeItem.children = children;
return treeItem;
}));
} else {
treeItemsPromises.push(TPromise.as(treeItem));
}
}
return TPromise.join(treeItemsPromises);
}
private massageTreeItem(extensionTreeItem: vscode.TreeItem): TreeItem {
return {
handle: ++this._itemHandlePool,
label: extensionTreeItem.label,
commandId: extensionTreeItem.command ? extensionTreeItem.command.command : void 0,
contextValue: extensionTreeItem.contextValue,
icon: this.getLightIconPath(extensionTreeItem),
iconDark: this.getDarkIconPath(extensionTreeItem),
collapsibleState: extensionTreeItem.collapsibleState,
};
}
private getLightIconPath(extensionTreeItem: vscode.TreeItem) {
if (extensionTreeItem.iconPath) {
if (typeof extensionTreeItem.iconPath === 'string' || extensionTreeItem.iconPath instanceof URI) {
return this.getIconPath(extensionTreeItem.iconPath);
}
return this.getIconPath(extensionTreeItem.iconPath['light']);
}
return void 0;
}
private getDarkIconPath(extensionTreeItem: vscode.TreeItem) {
if (extensionTreeItem.iconPath && extensionTreeItem.iconPath['dark']) {
return this.getIconPath(extensionTreeItem.iconPath['dark']);
}
return void 0;
}
private getIconPath(iconPath: string | URI): string {
if (iconPath instanceof URI) {
return iconPath.toString();
}
return URI.file(iconPath).toString();
}
private clearChildren(extElement: T): void {
const children = this.extChildrenElementsMap.get(extElement);
if (children) {
for (const child of children) {
this.clearElement(child);
}
this.extChildrenElementsMap.delete(extElement);
}
}
private clearElement(extElement: T): void {
this.clearChildren(extElement);
const treeItemhandle = this.itemHandlesMap.get(extElement);
this.itemHandlesMap.delete(extElement);
if (treeItemhandle) {
this.extElementsMap.delete(treeItemhandle);
}
}
dispose() {
this.extElementsMap.clear();
this.itemHandlesMap.clear();
this.extChildrenElementsMap.clear();
}
}

View File

@@ -1265,3 +1265,8 @@ export enum ProgressLocation {
SourceControl = 1,
Window = 10,
}
export enum TreeItemCollapsibleState {
Collapsed = 1,
Expanded = 2
}