diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 121735849071ef5d0b0cb1ee936ebeac616ec498..a77d074ebdf29470bb4b039f905e4682bfd537a4 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -400,32 +400,32 @@ declare module 'vscode' { */ env?: { [key: string]: string }; } | { - /** - * The current working directory of the executed shell. - * If omitted VSCode's current workspace root is used. - */ - cwd: string; - - /** - * The additional environment of the executed shell. If omitted - * the parent process' environment is used. If provided it is merged with - * the parent process' environment. - */ - env?: { [key: string]: string }; - } | { - /** - * The current working directory of the executed shell. - * If omitted VSCode's current workspace root is used. - */ - cwd?: string; - - /** - * The additional environment of the executed shell. If omitted - * the parent process' environment is used. If provided it is merged with - * the parent process' environment. - */ - env: { [key: string]: string }; - }; + /** + * The current working directory of the executed shell. + * If omitted VSCode's current workspace root is used. + */ + cwd: string; + + /** + * The additional environment of the executed shell. If omitted + * the parent process' environment is used. If provided it is merged with + * the parent process' environment. + */ + env?: { [key: string]: string }; + } | { + /** + * The current working directory of the executed shell. + * If omitted VSCode's current workspace root is used. + */ + cwd?: string; + + /** + * The additional environment of the executed shell. If omitted + * the parent process' environment is used. If provided it is merged with + * the parent process' environment. + */ + env: { [key: string]: string }; + }; /** * A task that executes a shell command. @@ -545,6 +545,15 @@ declare module 'vscode' { * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ export function registerTreeExplorerNodeProvider(providerId: string, provider: TreeExplorerNodeProvider): Disposable; + + /** + * Register a [TreeNode](#TreeNode). + * + * @param providerId A unique id that identifies the provider. + * @param provider A [TreeNode](#TreeNode). + * @return A [disposable](#Disposable) that unregisters this tree when being disposed. + */ + export function registerTree(id: string, root: TreeNode): Disposable; } /** @@ -578,7 +587,7 @@ declare module 'vscode' { * @param node The node from which the provider resolves children. * @return Children of `node`. */ - resolveChildren(node: T): T[] | Thenable; + resolveChildren?(node: T): T[] | Thenable; /** * Provide a human-readable string that will be used for rendering the node. Default to use @@ -606,7 +615,33 @@ declare module 'vscode' { * @param node The node that the command is associated with. * @return The command to execute when `node` is clicked. */ - getClickCommand?(node: T): string; + getClickCommand?(node: T): Command; + + /** + * Event to be listened for any changes + */ + onChange?: Event; + } + + export interface TreeNode { + /** + * Human-readable string used for rendering the node. + * Label for Root node is not rendered. + */ + readonly label: string; + /** + * Get the children for the node. If not implemented, the node is not expandable. + */ + getChildren?(): Thenable; + /** + * The [command](#Command) which should be run when the node + * is selected in the View. + */ + command?: Command; + /** + * Event to be listened for any changes on this node + */ + onChange?: Event; } /** diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index cd604730034b18bf4e6235248ad67ab72aaf8eb1..5c7ffb52615defd97fd9e8f27de3f1e40a3d2c38 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -19,6 +19,7 @@ import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/node/extHostDoc import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import { ExtHostTreeExplorers } from 'vs/workbench/api/node/extHostTreeExplorers'; +import { ExtHostTree } from 'vs/workbench/api/node/extHostTree'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { ExtHostQuickOpen } from 'vs/workbench/api/node/extHostQuickOpen'; import { ExtHostProgress } from 'vs/workbench/api/node/extHostProgress'; @@ -112,6 +113,7 @@ export function createApiFactory( const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set(new ExtHostEditors(threadService, extHostDocumentsAndEditors)); const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set(new ExtHostCommands(threadService, extHostHeapService)); const extHostExplorers = col.define(ExtHostContext.ExtHostExplorers).set(new ExtHostTreeExplorers(threadService, extHostCommands)); + const extHostTree = col.define(ExtHostContext.ExtHostTree).set(new ExtHostTree(threadService, extHostCommands)); const extHostConfiguration = col.define(ExtHostContext.ExtHostConfiguration).set(new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration), initData.configuration)); const extHostDiagnostics = col.define(ExtHostContext.ExtHostDiagnostics).set(new ExtHostDiagnostics(threadService)); const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics)); @@ -366,6 +368,9 @@ export function createApiFactory( registerTreeExplorerNodeProvider: proposedApiFunction(extension, (providerId: string, provider: vscode.TreeExplorerNodeProvider) => { return extHostExplorers.registerTreeExplorerNodeProvider(providerId, provider); }), + registerTree: proposedApiFunction(extension, (providerId: string, root: vscode.TreeNode) => { + return extHostTree.registerTree(providerId, root); + }) }; // namespace: workspace diff --git a/src/vs/workbench/api/node/extHost.contribution.ts b/src/vs/workbench/api/node/extHost.contribution.ts index 87aca222da86127ef4374d54e052e4b1f5ed5be8..934ed7c998c08d22c9f3208375e0eb89ba476944 100644 --- a/src/vs/workbench/api/node/extHost.contribution.ts +++ b/src/vs/workbench/api/node/extHost.contribution.ts @@ -20,6 +20,7 @@ import { MainThreadDocuments } from './mainThreadDocuments'; import { MainThreadEditors } from './mainThreadEditors'; import { MainThreadErrors } from './mainThreadErrors'; import { MainThreadTreeExplorers } from './mainThreadTreeExplorers'; +import { MainThreadTree } from './mainThreadTree'; import { MainThreadLanguageFeatures } from './mainThreadLanguageFeatures'; import { MainThreadLanguages } from './mainThreadLanguages'; import { MainThreadMessageService } from './mainThreadMessageService'; @@ -75,6 +76,7 @@ export class ExtHostContribution implements IWorkbenchContribution { col.define(MainContext.MainThreadEditors).set(this.instantiationService.createInstance(MainThreadEditors, documentsAndEditors)); col.define(MainContext.MainThreadErrors).set(create(MainThreadErrors)); col.define(MainContext.MainThreadExplorers).set(create(MainThreadTreeExplorers)); + col.define(MainContext.MainThreadTree).set(create(MainThreadTree)); col.define(MainContext.MainThreadLanguageFeatures).set(create(MainThreadLanguageFeatures)); col.define(MainContext.MainThreadLanguages).set(create(MainThreadLanguages)); col.define(MainContext.MainThreadMessageService).set(create(MainThreadMessageService)); diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 9fe2a7715d758267c0ee80e3909f0aaab2f29378..2a324eb9a91010684abd7962a4bd1f45fb20bcdb 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -149,6 +149,12 @@ export abstract class MainThreadEditorsShape { export abstract class MainThreadTreeExplorersShape { $registerTreeExplorerNodeProvider(providerId: string): void { throw ni(); } + $refresh(providerId: string, node: InternalTreeExplorerNodeContent): void { throw ni(); } +} + +export abstract class MainThreadTreeShape { + $registerTreeExplorerNodeProvider(providerId: string, node: InternalTreeExplorerNodeContent): void { throw ni(); } + $refresh(providerId: string, node: InternalTreeExplorerNodeContent): void { throw ni(); } } export abstract class MainThreadErrorsShape { @@ -358,6 +364,11 @@ export abstract class ExtHostTreeExplorersShape { $getInternalCommand(providerId: string, node: InternalTreeExplorerNodeContent): TPromise { throw ni(); } } +export abstract class ExtHostTreeShape { + $resolveChildren(providerId: string, node: InternalTreeExplorerNodeContent): TPromise { throw ni(); } + $getInternalCommand(providerId: string, node: InternalTreeExplorerNodeContent): TPromise { throw ni(); } +} + export abstract class ExtHostExtensionServiceShape { $localShowMessage(severity: Severity, msg: string): void { throw ni(); } $activateExtension(extensionDescription: IExtensionDescription): TPromise { throw ni(); } @@ -447,6 +458,7 @@ export const MainContext = { MainThreadEditors: createMainId('MainThreadEditors', MainThreadEditorsShape), MainThreadErrors: createMainId('MainThreadErrors', MainThreadErrorsShape), MainThreadExplorers: createMainId('MainThreadExplorers', MainThreadTreeExplorersShape), + MainThreadTree: createMainId('MainThreadTree', MainThreadTreeShape), MainThreadLanguageFeatures: createMainId('MainThreadLanguageFeatures', MainThreadLanguageFeaturesShape), MainThreadLanguages: createMainId('MainThreadLanguages', MainThreadLanguagesShape), MainThreadMessageService: createMainId('MainThreadMessageService', MainThreadMessageServiceShape), @@ -479,5 +491,6 @@ export const ExtHostContext = { ExtHostExtensionService: createExtId('ExtHostExtensionService', ExtHostExtensionServiceShape), ExtHostTerminalService: createExtId('ExtHostTerminalService', ExtHostTerminalServiceShape), ExtHostSCM: createExtId('ExtHostSCM', ExtHostSCMShape), + ExtHostTree: createExtId('ExtHostTree', ExtHostTreeShape), ExtHostTask: createExtId('ExtHostTask', ExtHostTaskShape) }; diff --git a/src/vs/workbench/api/node/extHostTree.ts b/src/vs/workbench/api/node/extHostTree.ts new file mode 100644 index 0000000000000000000000000000000000000000..014fa2209a484c8dcc3a17c2f8d76727616b0a8d --- /dev/null +++ b/src/vs/workbench/api/node/extHostTree.ts @@ -0,0 +1,127 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TreeNode } from 'vscode'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { Disposable } from 'vs/workbench/api/node/extHostTypes'; +import { defaultGenerator } from 'vs/base/common/idGenerator'; +import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; +import { MainContext, ExtHostTreeShape, MainThreadTreeShape } from './extHost.protocol'; +import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; +import { asWinJsPromise } from 'vs/base/common/async'; +import { localize } from 'vs/nls'; +import { InternalTreeExplorerNode } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; +import * as modes from 'vs/editor/common/modes'; + +class InternalTreeExplorerNodeImpl implements InternalTreeExplorerNode { + + readonly id: string; + label: string; + hasChildren: boolean; + clickCommand: string; + + constructor(node: TreeNode) { + this.id = defaultGenerator.nextId(); + this.label = node.label; + this.hasChildren = !!node.getChildren; + this.clickCommand = node.command ? node.command.command : null; + } +} + +export class ExtHostTree extends ExtHostTreeShape { + + private _proxy: MainThreadTreeShape; + + private _providers: Map> = new Map>(); + private _disposables: Map = new Map(); + private _nodeDisposables: Map = new Map(); + + constructor( + threadService: IThreadService, + private commands: ExtHostCommands + ) { + super(); + this._proxy = threadService.get(MainContext.MainThreadTree); + } + + registerTree(providerId: string, root: TreeNode): Disposable { + this._providers.set(providerId, new Map()); + this._disposables.set(providerId, []); + + const internalNode = new InternalTreeExplorerNodeImpl(root); + this._providers.get(providerId).set(internalNode.id, root); + + const disposable = root.onChange(() => { + this._proxy.$refresh(providerId, internalNode); + }); + this._disposables.get(providerId).push(new Disposable(() => disposable.dispose())); + + this._proxy.$registerTreeExplorerNodeProvider(providerId, internalNode); + + return new Disposable(() => { + this._providers.delete(providerId); + const disposables = this._disposables.get(providerId); + if (disposables) { + for (const disposable of disposables) { + disposable.dispose(); + } + } + this._disposables.delete(providerId); + }); + } + + $resolveChildren(providerId: string, mainThreadNode: InternalTreeExplorerNode): TPromise { + const provider = this._providers.get(providerId); + if (!provider) { + const errMessage = localize('treeExplorer.notRegistered', 'No TreeExplorerNodeProvider with id \'{0}\' registered.', providerId); + return TPromise.wrapError(errMessage); + } + + const extNode = provider.get(mainThreadNode.id); + const disposables = this._nodeDisposables.get(mainThreadNode.id); + if (disposables) { + for (const disposable of disposables) { + disposable.dispose(); + } + } + this._nodeDisposables.set(mainThreadNode.id, []); + return asWinJsPromise(() => extNode.getChildren()).then(children => { + return children.map(extChild => { + const internalChild = new InternalTreeExplorerNodeImpl(extChild); + provider.set(internalChild.id, extChild); + if (extChild.onChange) { + const disposable = extChild.onChange(() => this._proxy.$refresh(providerId, internalChild)); + this._disposables.get(providerId).push(new Disposable(() => disposable.dispose())); + this._nodeDisposables.get(mainThreadNode.id).push(new Disposable(() => disposable.dispose())); + } + return internalChild; + }); + }, err => { + const errMessage = localize('treeExplorer.failedToResolveChildren', 'TreeExplorerNodeProvider \'{0}\' failed to resolveChildren.', providerId); + return TPromise.wrapError(errMessage); + }); + } + + // Convert the command on the ExtHost side so we can pass the original externalNode to the registered handler + $getInternalCommand(providerId: string, mainThreadNode: InternalTreeExplorerNode): TPromise { + const commandConverter = this.commands.converter; + + if (mainThreadNode.clickCommand) { + const extNode = this._providers.get(providerId).get(mainThreadNode.id); + + const internalCommand = commandConverter.toInternal({ + title: '', + command: mainThreadNode.clickCommand, + arguments: [extNode] + }); + + return TPromise.wrap(internalCommand); + } + + return TPromise.as(null); + } + +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHostTreeExplorers.ts b/src/vs/workbench/api/node/extHostTreeExplorers.ts index 7a7da986d09e7b9292ff7db8ce6f11b08c716df3..5794f3af517f23bc1113dfe6a53e9feb848d1c0f 100644 --- a/src/vs/workbench/api/node/extHostTreeExplorers.ts +++ b/src/vs/workbench/api/node/extHostTreeExplorers.ts @@ -21,13 +21,18 @@ class InternalTreeExplorerNodeImpl implements InternalTreeExplorerNode { readonly id: string; label: string; hasChildren: boolean; - clickCommand: string; + clickCommand: string = null; constructor(node: any, provider: TreeExplorerNodeProvider) { this.id = defaultGenerator.nextId(); this.label = provider.getLabel ? provider.getLabel(node) : node.toString(); this.hasChildren = provider.getHasChildren ? provider.getHasChildren(node) : true; - this.clickCommand = provider.getClickCommand ? provider.getClickCommand(node) : null; + if (provider.getClickCommand) { + const command = provider.getClickCommand(node); + if (command) { + this.clickCommand = command.command; + } + } } } @@ -36,6 +41,8 @@ export class ExtHostTreeExplorers extends ExtHostTreeExplorersShape { private _extNodeProviders: { [providerId: string]: TreeExplorerNodeProvider }; private _extNodeMaps: { [providerId: string]: { [id: string]: InternalTreeExplorerNode } }; + private _mainNodesMap: Map>; + private _childrenNodesMap: Map>; constructor( threadService: IThreadService, @@ -47,15 +54,32 @@ export class ExtHostTreeExplorers extends ExtHostTreeExplorersShape { this._extNodeProviders = Object.create(null); this._extNodeMaps = Object.create(null); + this._mainNodesMap = new Map>(); + this._childrenNodesMap = new Map>(); } registerTreeExplorerNodeProvider(providerId: string, provider: TreeExplorerNodeProvider): Disposable { this._proxy.$registerTreeExplorerNodeProvider(providerId); this._extNodeProviders[providerId] = provider; + this._mainNodesMap.set(providerId, new Map()); + this._childrenNodesMap.set(providerId, new Map()); + + let disposable = null; + if (provider.onChange) { + disposable = provider.onChange(node => { + const mainThreadNode = this._mainNodesMap.get(providerId).get(node); + this._proxy.$refresh(providerId, mainThreadNode); + }); + } return new Disposable(() => { delete this._extNodeProviders[providerId]; delete this._extNodeProviders[providerId]; + this._mainNodesMap.delete(providerId); + this._childrenNodesMap.delete(providerId); + if (disposable) { + disposable.dispose(); + } }); } @@ -73,6 +97,8 @@ export class ExtHostTreeExplorers extends ExtHostTreeExplorersShape { 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); @@ -90,15 +116,20 @@ export class ExtHostTreeExplorers extends ExtHostTreeExplorersShape { 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 InternalTreeExplorerNodeImpl(extChild, provider); extNodeMap[internalChild.id] = extChild; + this._mainNodesMap.get(providerId).set(extChild, internalChild); return internalChild; }); - }, err => { - const errMessage = localize('treeExplorer.failedToResolveChildren', 'TreeExplorerNodeProvider \'{0}\' failed to resolveChildren.', providerId); - return TPromise.wrapError(errMessage); }); } diff --git a/src/vs/workbench/api/node/mainThreadTree.ts b/src/vs/workbench/api/node/mainThreadTree.ts new file mode 100644 index 0000000000000000000000000000000000000000..b89a4c52e7935e31ea6af22748ed32d979287abc --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadTree.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TPromise } from 'vs/base/common/winjs.base'; +import Event, { Emitter } from 'vs/base/common/event'; +import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; +import { ExtHostContext, MainThreadTreeShape, ExtHostTreeShape } from './extHost.protocol'; +import { ITreeExplorerService } from 'vs/workbench/parts/explorers/common/treeExplorerService'; +import { InternalTreeExplorerNodeContent, InternalTreeExplorerNodeProvider } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; +import { IMessageService, Severity } from 'vs/platform/message/common/message'; +import { ICommandService } from 'vs/platform/commands/common/commands'; + +export class MainThreadTree extends MainThreadTreeShape { + private _proxy: ExtHostTreeShape; + + constructor( + @IThreadService threadService: IThreadService, + @ITreeExplorerService private treeExplorerService: ITreeExplorerService, + @IMessageService private messageService: IMessageService, + @ICommandService private commandService: ICommandService + ) { + super(); + + this._proxy = threadService.get(ExtHostContext.ExtHostTree); + } + + $registerTreeExplorerNodeProvider(providerId: string, rootNode: InternalTreeExplorerNodeContent): void { + const provider = new TreeExplorerNodeProvider(providerId, rootNode, this._proxy, this.messageService, this.commandService); + this.treeExplorerService.registerTreeExplorerNodeProvider(providerId, provider); + } + + $refresh(providerId: string, node: InternalTreeExplorerNodeContent): void { + (this.treeExplorerService.getProvider(providerId))._onRefresh.fire(node); + } +} + +class TreeExplorerNodeProvider implements InternalTreeExplorerNodeProvider { + + readonly _onRefresh: Emitter = new Emitter(); + readonly onRefresh: Event = this._onRefresh.event; + + constructor(private providerId: string, private rootNode: InternalTreeExplorerNodeContent, private _proxy: ExtHostTreeShape, + private messageService: IMessageService, + private commandService: ICommandService + ) { + } + + provideRootNode(): TPromise { + return TPromise.as(this.rootNode); + } + + resolveChildren(node: InternalTreeExplorerNodeContent): TPromise { + return this._proxy.$resolveChildren(this.providerId, node).then(children => children, err => this.messageService.show(Severity.Error, err)); + } + + executeCommand(node: InternalTreeExplorerNodeContent): TPromise { + return this._proxy.$getInternalCommand(this.providerId, node).then(command => { + return this.commandService.executeCommand(command.id, ...command.arguments); + }); + } +} diff --git a/src/vs/workbench/api/node/mainThreadTreeExplorers.ts b/src/vs/workbench/api/node/mainThreadTreeExplorers.ts index 178c1e4ac931d61085d5ae9fb0e604da45bd33e3..18c8e81464553d97ff2b42cc29d19ae6c087b126 100644 --- a/src/vs/workbench/api/node/mainThreadTreeExplorers.ts +++ b/src/vs/workbench/api/node/mainThreadTreeExplorers.ts @@ -5,10 +5,11 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; +import Event, { Emitter } from 'vs/base/common/event'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; import { ExtHostContext, MainThreadTreeExplorersShape, ExtHostTreeExplorersShape } from './extHost.protocol'; import { ITreeExplorerService } from 'vs/workbench/parts/explorers/common/treeExplorerService'; -import { InternalTreeExplorerNodeContent } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; +import { InternalTreeExplorerNodeContent, InternalTreeExplorerNodeProvider } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -27,20 +28,37 @@ export class MainThreadTreeExplorers extends MainThreadTreeExplorersShape { } $registerTreeExplorerNodeProvider(providerId: string): void { - const onError = err => { this.messageService.show(Severity.Error, err); }; - - this.treeExplorerService.registerTreeExplorerNodeProvider(providerId, { - provideRootNode: (): TPromise => { - return this._proxy.$provideRootNode(providerId).then(rootNode => rootNode, onError); - }, - resolveChildren: (node: InternalTreeExplorerNodeContent): TPromise => { - return this._proxy.$resolveChildren(providerId, node).then(children => children, onError); - }, - executeCommand: (node: InternalTreeExplorerNodeContent): TPromise => { - return this._proxy.$getInternalCommand(providerId, node).then(command => { - return this.commandService.executeCommand(command.id, ...command.arguments); - }); - } + const provider = new TreeExplorerNodeProvider(providerId, this._proxy, this.messageService, this.commandService); + this.treeExplorerService.registerTreeExplorerNodeProvider(providerId, provider); + } + + $refresh(providerId: string, node: InternalTreeExplorerNodeContent): void { + (this.treeExplorerService.getProvider(providerId))._onRefresh.fire(node); + } +} + +class TreeExplorerNodeProvider implements InternalTreeExplorerNodeProvider { + + readonly _onRefresh: Emitter = new Emitter(); + readonly onRefresh: Event = this._onRefresh.event; + + constructor(private providerId: string, private _proxy: ExtHostTreeExplorersShape, + private messageService: IMessageService, + private commandService: ICommandService + ) { + } + + provideRootNode(): TPromise { + return this._proxy.$provideRootNode(this.providerId).then(rootNode => rootNode, err => this.messageService.show(Severity.Error, err)); + } + + resolveChildren(node: InternalTreeExplorerNodeContent): TPromise { + return this._proxy.$resolveChildren(this.providerId, node).then(children => children, err => this.messageService.show(Severity.Error, err)); + } + + executeCommand(node: InternalTreeExplorerNodeContent): TPromise { + return this._proxy.$getInternalCommand(this.providerId, node).then(command => { + return this.commandService.executeCommand(command.id, ...command.arguments); }); } } diff --git a/src/vs/workbench/parts/explorers/browser/treeExplorerService.ts b/src/vs/workbench/parts/explorers/browser/treeExplorerService.ts index 5046d42c5343320af0fe5001b3fe29bd264358d3..82ee02263c78790573bee820e17caa1eeb5c2741 100644 --- a/src/vs/workbench/parts/explorers/browser/treeExplorerService.ts +++ b/src/vs/workbench/parts/explorers/browser/treeExplorerService.ts @@ -49,7 +49,7 @@ export class TreeExplorerService implements ITreeExplorerService { return TPromise.wrap(provider.executeCommand(node)); } - private getProvider(providerId: string): InternalTreeExplorerNodeProvider { + public getProvider(providerId: string): InternalTreeExplorerNodeProvider { const provider = this._treeExplorerNodeProviders[providerId]; if (!provider) { diff --git a/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts b/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts index 30d2f9acc3cccd69c201a3e1f5827680c2e10a46..dd8fffaebdf2fb52bdfdcd326d026857d9d2c10f 100644 --- a/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts +++ b/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts @@ -11,6 +11,7 @@ import { Builder, $ } from 'vs/base/browser/builder'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { CollapsibleViewletView } from 'vs/workbench/browser/viewlet'; import { IAction, IActionRunner } from 'vs/base/common/actions'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IMessageService } from 'vs/platform/message/common/message'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -25,6 +26,9 @@ import { attachListStyler } from "vs/platform/theme/common/styler"; import { IThemeService } from "vs/platform/theme/common/themeService"; export class TreeExplorerView extends CollapsibleViewletView { + + private providerDisposables = IDisposable[]; + constructor( private viewletState: TreeExplorerViewletState, private treeNodeProviderId: string, @@ -86,9 +90,7 @@ export class TreeExplorerView extends CollapsibleViewletView { public updateInput(): TPromise { if (this.treeExplorerService.hasProvider(this.treeNodeProviderId)) { - return this.treeExplorerService.provideRootNode(this.treeNodeProviderId).then(tree => { - this.tree.setInput(tree); - }); + return this.updateProvider(); } // Provider registration happens independently of the reading of extension's contribution, // which constructs the viewlet, so it's possible the viewlet is constructed before a provider @@ -97,9 +99,7 @@ export class TreeExplorerView extends CollapsibleViewletView { else { this.treeExplorerService.onTreeExplorerNodeProviderRegistered(providerId => { if (this.treeNodeProviderId === providerId) { - return this.treeExplorerService.provideRootNode(this.treeNodeProviderId).then(tree => { - this.tree.setInput(tree); - }); + return this.updateProvider(); } return undefined; }); @@ -114,4 +114,16 @@ export class TreeExplorerView extends CollapsibleViewletView { return DOM.getLargestChildWidth(parentNode, childNodes); } + + private updateProvider(): TPromise { + if (this.providerDisposables) { + dispose(this.providerDisposables); + } + + const provider = this.treeExplorerService.getProvider(this.treeNodeProviderId); + provider.onRefresh(node => this.tree.refresh(node)); + return this.treeExplorerService.provideRootNode(this.treeNodeProviderId).then(tree => { + this.tree.setInput(tree); + }); + } } diff --git a/src/vs/workbench/parts/explorers/common/treeExplorerService.ts b/src/vs/workbench/parts/explorers/common/treeExplorerService.ts index 1ce4896a4788211ed648e80e5a9b4916dac6689b..dc41d8a146868d53452d3e0c58df7a1af7bf50b9 100644 --- a/src/vs/workbench/parts/explorers/common/treeExplorerService.ts +++ b/src/vs/workbench/parts/explorers/common/treeExplorerService.ts @@ -18,6 +18,7 @@ export interface ITreeExplorerService { registerTreeExplorerNodeProvider(providerId: string, provider: InternalTreeExplorerNodeProvider): void; hasProvider(providerId: string): boolean; + getProvider(providerId: string): InternalTreeExplorerNodeProvider; provideRootNode(providerId: string): TPromise; resolveChildren(providerId: string, node: InternalTreeExplorerNode): TPromise; diff --git a/src/vs/workbench/parts/explorers/common/treeExplorerViewModel.ts b/src/vs/workbench/parts/explorers/common/treeExplorerViewModel.ts index 7e6b265faf3a1a7e081e0731e61d2b9e053864f9..e623b1a4295644437fe13ea62c16039d07c172d5 100644 --- a/src/vs/workbench/parts/explorers/common/treeExplorerViewModel.ts +++ b/src/vs/workbench/parts/explorers/common/treeExplorerViewModel.ts @@ -5,6 +5,7 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; +import Event from 'vs/base/common/event'; export interface InternalTreeExplorerNodeContent { label: string; @@ -20,4 +21,5 @@ export interface InternalTreeExplorerNodeProvider { provideRootNode(): Thenable; resolveChildren(node: InternalTreeExplorerNodeContent): Thenable; executeCommand(node: InternalTreeExplorerNodeContent): TPromise; + onRefresh?: Event; }