diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index f72a9baa873..60d118eefc6 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -530,20 +530,20 @@ declare module 'vscode' { export namespace window { /** - * Create a new [TreeView](#TreeView) instance. + * Create a new explorer view. * - * @param viewId A unique id that identifies the view. - * @param provider A [TreeDataProvider](#TreeDataProvider). - * @return An instance of [TreeView](#TreeView). + * @param id View id. + * @param name View name. + * @param dataProvider A [TreeDataProvider](#TreeDataProvider). + * @return An instance of [View](#View). */ - export function createTreeView(viewId: string, provider: TreeDataProvider): TreeView; + export function createExplorerView(id: string, name: string, dataProvider: TreeDataProvider): View; } /** - * An source control is able to provide [resource states](#SourceControlResourceState) - * to the editor and interact with the editor in several source control related ways. + * A view to interact with nodes */ - export interface TreeView { + export interface View { /** * Refresh the given nodes @@ -602,6 +602,14 @@ declare module 'vscode' { */ getHasChildren?(node: T): boolean; + /** + * Provider a context key to be set for the node. This can be used to describe actions for each node. + * + * @param node The node from which the provider computes context key. + * @return A context key. + */ + getContextKey?(node: T): string; + /** * Get the command to execute when `node` is clicked. * diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 4b84e46809a..b1e56a9c7e1 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -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 { ExtHostTreeView } from 'vs/workbench/api/node/extHostTreeView'; +import { ExtHostExplorerView } from 'vs/workbench/api/node/extHostExplorerView'; 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(new ExtHostDocumentSaveParticipant(extHostDocuments, threadService.get(MainContext.MainThreadWorkspace))); const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set(new ExtHostEditors(threadService, extHostDocumentsAndEditors)); const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set(new ExtHostCommands(threadService, extHostHeapService)); - const extHostTreeView = col.define(ExtHostContext.ExtHostTreeView).set(new ExtHostTreeView(threadService, extHostCommands)); + const extHostExplorerView = col.define(ExtHostContext.ExtHostExplorerView).set(new ExtHostExplorerView(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)); @@ -369,8 +369,8 @@ export function createApiFactory( sampleFunction: proposedApiFunction(extension, () => { return extHostMessageService.showMessage(Severity.Info, 'Hello Proposed Api!', {}, []); }), - createTreeView: proposedApiFunction(extension, (providerId: string, provider: vscode.TreeDataProvider): vscode.TreeView => { - return extHostTreeView.createTreeView(providerId, provider); + createExplorerView: proposedApiFunction(extension, (id: string, name: string, provider: vscode.TreeDataProvider): vscode.View => { + return extHostExplorerView.createExplorerView(id, name, provider); }) }; diff --git a/src/vs/workbench/api/node/extHost.contribution.ts b/src/vs/workbench/api/node/extHost.contribution.ts index 2a0e9f87486..648e50ea3c1 100644 --- a/src/vs/workbench/api/node/extHost.contribution.ts +++ b/src/vs/workbench/api/node/extHost.contribution.ts @@ -19,7 +19,7 @@ import { MainThreadDiagnostics } from './mainThreadDiagnostics'; import { MainThreadDocuments } from './mainThreadDocuments'; import { MainThreadEditors } from './mainThreadEditors'; import { MainThreadErrors } from './mainThreadErrors'; -import { MainThreadTreeView } from './mainThreadTreeView'; +import { MainThreadExplorerView } from './mainThreadExplorerView'; import { MainThreadLanguageFeatures } from './mainThreadLanguageFeatures'; import { MainThreadLanguages } from './mainThreadLanguages'; import { MainThreadMessageService } from './mainThreadMessageService'; @@ -74,7 +74,7 @@ export class ExtHostContribution implements IWorkbenchContribution { col.define(MainContext.MainThreadDocuments).set(this.instantiationService.createInstance(MainThreadDocuments, documentsAndEditors)); col.define(MainContext.MainThreadEditors).set(this.instantiationService.createInstance(MainThreadEditors, documentsAndEditors)); col.define(MainContext.MainThreadErrors).set(create(MainThreadErrors)); - col.define(MainContext.MainThreadExplorers).set(create(MainThreadTreeView)); + col.define(MainContext.MainThreadExplorerViews).set(create(MainThreadExplorerView)); 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 d4cd004550a..8a40eb6f740 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -37,7 +37,6 @@ import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quick import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import { IApplyEditsOptions, IUndoStopOptions, TextEditorRevealType, ITextEditorConfigurationUpdate, IResolvedTextEditorConfiguration, ISelectionChangeEvent } from './mainThreadEditor'; -import { InternalTreeNodeContent } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks'; import { IModelChangedEvent } from 'vs/editor/common/model/mirrorModel'; import { IPosition } from 'vs/editor/common/core/position'; @@ -155,14 +154,17 @@ export abstract class MainThreadEditorsShape { $getDiffInformation(id: string): TPromise { throw ni(); } } -export abstract class MainThreadTreeViewShape { - $registerTreeDataProvider(providerId: string): void { throw ni(); } - $refresh(providerId: string, node: InternalTreeNodeContent): void { throw ni(); } +export interface ITreeNode { + id: string; + label: string; + hasChildren: boolean; + clickCommand: string; + contextKey: string; } -export abstract class MainThreadTreeShape { - $registerTreeExplorerNodeProvider(providerId: string, node: InternalTreeNodeContent): void { throw ni(); } - $refresh(providerId: string, node: InternalTreeNodeContent): void { throw ni(); } +export abstract class MainThreadExplorerViewShape { + $registerView(id: string, name: string): void { throw ni(); } + $refresh(viewId: string, node: ITreeNode): void { throw ni(); } } export abstract class MainThreadErrorsShape { @@ -366,15 +368,10 @@ export abstract class ExtHostDocumentsAndEditorsShape { } -export abstract class ExtHostTreeViewShape { - $provideRootNode(providerId: string): TPromise { throw ni(); }; - $resolveChildren(providerId: string, node: InternalTreeNodeContent): TPromise { throw ni(); } - $getInternalCommand(providerId: string, node: InternalTreeNodeContent): TPromise { throw ni(); } -} - -export abstract class ExtHostTreeShape { - $resolveChildren(providerId: string, node: InternalTreeNodeContent): TPromise { throw ni(); } - $getInternalCommand(providerId: string, node: InternalTreeNodeContent): TPromise { throw ni(); } +export abstract class ExtHostExplorerViewShape { + $provideRootNode(viewId: string): TPromise { throw ni(); }; + $resolveChildren(viewId: string, node: ITreeNode): TPromise { throw ni(); } + $getInternalCommand(viewId: string, node: ITreeNode): TPromise { throw ni(); } } export abstract class ExtHostExtensionServiceShape { @@ -465,7 +462,7 @@ export const MainContext = { MainThreadDocuments: createMainId('MainThreadDocuments', MainThreadDocumentsShape), MainThreadEditors: createMainId('MainThreadEditors', MainThreadEditorsShape), MainThreadErrors: createMainId('MainThreadErrors', MainThreadErrorsShape), - MainThreadExplorers: createMainId('MainThreadTreeView', MainThreadTreeViewShape), + MainThreadExplorerViews: createMainId('MainThreadExplorerView', MainThreadExplorerViewShape), MainThreadLanguageFeatures: createMainId('MainThreadLanguageFeatures', MainThreadLanguageFeaturesShape), MainThreadLanguages: createMainId('MainThreadLanguages', MainThreadLanguagesShape), MainThreadMessageService: createMainId('MainThreadMessageService', MainThreadMessageServiceShape), @@ -490,7 +487,7 @@ export const ExtHostContext = { ExtHostDocuments: createExtId('ExtHostDocuments', ExtHostDocumentsShape), ExtHostDocumentSaveParticipant: createExtId('ExtHostDocumentSaveParticipant', ExtHostDocumentSaveParticipantShape), ExtHostEditors: createExtId('ExtHostEditors', ExtHostEditorsShape), - ExtHostTreeView: createExtId('ExtHostTreeView', ExtHostTreeViewShape), + ExtHostExplorerView: createExtId('ExtHostExplorerView', ExtHostExplorerViewShape), ExtHostFileSystemEventService: createExtId('ExtHostFileSystemEventService', ExtHostFileSystemEventServiceShape), ExtHostHeapService: createExtId('ExtHostHeapMonitor', ExtHostHeapServiceShape), ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures', ExtHostLanguageFeaturesShape), diff --git a/src/vs/workbench/api/node/extHostTreeView.ts b/src/vs/workbench/api/node/extHostExplorerView.ts similarity index 66% rename from src/vs/workbench/api/node/extHostTreeView.ts rename to src/vs/workbench/api/node/extHostExplorerView.ts index ec6318eec3a..cb2bd09645b 100644 --- a/src/vs/workbench/api/node/extHostTreeView.ts +++ b/src/vs/workbench/api/node/extHostExplorerView.ts @@ -5,27 +5,28 @@ 'use strict'; import { localize } from 'vs/nls'; -import { TreeView, TreeDataProvider } from 'vscode'; +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, ExtHostTreeViewShape, MainThreadTreeViewShape } from './extHost.protocol'; -import { InternalTreeNode } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; +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 InternalTreeNodeImpl implements InternalTreeNode { +class TreeNodeImpl implements ITreeNode { readonly id: string; label: string; hasChildren: boolean; clickCommand: string = null; + contextKey: string; constructor(readonly providerId: string, node: any, provider: TreeDataProvider) { 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) { @@ -35,13 +36,13 @@ class InternalTreeNodeImpl implements InternalTreeNode { } } -export class ExtHostTreeView extends ExtHostTreeViewShape { - private _proxy: MainThreadTreeViewShape; +export class ExtHostExplorerView extends ExtHostExplorerViewShape { + private _proxy: MainThreadExplorerViewShape; private _extNodeProviders: { [providerId: string]: TreeDataProvider }; - private _extViews: Map> = new Map>(); - private _extNodeMaps: { [providerId: string]: { [id: string]: InternalTreeNode } }; - private _mainNodesMap: Map>; + private _extViews: Map> = new Map>(); + private _extNodeMaps: { [providerId: string]: { [id: string]: ITreeNode } }; + private _mainNodesMap: Map>; private _childrenNodesMap: Map>; constructor( @@ -50,11 +51,11 @@ export class ExtHostTreeView extends ExtHostTreeViewShape { ) { super(); - this._proxy = threadService.get(MainContext.MainThreadExplorers); + this._proxy = threadService.get(MainContext.MainThreadExplorerViews); this._extNodeProviders = Object.create(null); this._extNodeMaps = Object.create(null); - this._mainNodesMap = new Map>(); + this._mainNodesMap = new Map>(); this._childrenNodesMap = new Map>(); commands.registerArgumentProcessor({ @@ -68,39 +69,39 @@ export class ExtHostTreeView extends ExtHostTreeViewShape { }); } - createTreeView(providerId: string, provider: TreeDataProvider): TreeView { - this._proxy.$registerTreeDataProvider(providerId); - this._extNodeProviders[providerId] = provider; - this._mainNodesMap.set(providerId, new Map()); - this._childrenNodesMap.set(providerId, new Map()); + createExplorerView(viewId: string, viewName: string, provider: TreeDataProvider): View { + this._proxy.$registerView(viewId, viewName); + this._extNodeProviders[viewId] = provider; + this._mainNodesMap.set(viewId, new Map()); + this._childrenNodesMap.set(viewId, new Map()); - const treeView: TreeView = { + const treeView: View = { refresh: (node: T) => { - const mainThreadNode = this._mainNodesMap.get(providerId).get(node); - this._proxy.$refresh(providerId, mainThreadNode); + const mainThreadNode = this._mainNodesMap.get(viewId).get(node); + this._proxy.$refresh(viewId, mainThreadNode); }, dispose: () => { - delete this._extNodeProviders[providerId]; - delete this._extNodeProviders[providerId]; - this._mainNodesMap.delete(providerId); - this._childrenNodesMap.delete(providerId); - this._extViews.delete(providerId); + delete this._extNodeProviders[viewId]; + delete this._extNodeProviders[viewId]; + this._mainNodesMap.delete(viewId); + this._childrenNodesMap.delete(viewId); + this._extViews.delete(viewId); } }; - this._extViews.set(providerId, treeView); + this._extViews.set(viewId, treeView); return treeView; } - $provideRootNode(providerId: string): TPromise { + $provideRootNode(providerId: string): TPromise { const provider = this._extNodeProviders[providerId]; if (!provider) { const errMessage = localize('treeExplorer.notRegistered', 'No TreeExplorerNodeProvider with id \'{0}\' registered.', providerId); - return TPromise.wrapError(errMessage); + return TPromise.wrapError(errMessage); } return asWinJsPromise(() => provider.provideRootNode()).then(extRootNode => { - const extNodeMap: { [id: string]: InternalTreeNode } = Object.create(null); - const internalRootNode = new InternalTreeNodeImpl(providerId, extRootNode, provider); + const extNodeMap: { [id: string]: ITreeNode } = Object.create(null); + const internalRootNode = new TreeNodeImpl(providerId, extRootNode, provider); extNodeMap[internalRootNode.id] = extRootNode; this._extNodeMaps[providerId] = extNodeMap; @@ -110,15 +111,15 @@ export class ExtHostTreeView extends ExtHostTreeViewShape { return internalRootNode; }, err => { const errMessage = localize('treeExplorer.failedToProvideRootNode', 'TreeExplorerNodeProvider \'{0}\' failed to provide root node.', providerId); - return TPromise.wrapError(errMessage); + return TPromise.wrapError(errMessage); }); } - $resolveChildren(providerId: string, mainThreadNode: InternalTreeNode): TPromise { + $resolveChildren(providerId: string, mainThreadNode: ITreeNode): TPromise { const provider = this._extNodeProviders[providerId]; if (!provider) { const errMessage = localize('treeExplorer.notRegistered', 'No TreeExplorerNodeProvider with id \'{0}\' registered.', providerId); - return TPromise.wrapError(errMessage); + return TPromise.wrapError(errMessage); } const extNodeMap = this._extNodeMaps[providerId]; @@ -133,7 +134,7 @@ export class ExtHostTreeView extends ExtHostTreeViewShape { return asWinJsPromise(() => provider.resolveChildren(extNode)).then(children => { return children.map(extChild => { - const internalChild = new InternalTreeNodeImpl(providerId, extChild, provider); + const internalChild = new TreeNodeImpl(providerId, extChild, provider); extNodeMap[internalChild.id] = extChild; this._mainNodesMap.get(providerId).set(extChild, internalChild); return internalChild; @@ -142,7 +143,7 @@ export class ExtHostTreeView extends ExtHostTreeViewShape { } // Convert the command on the ExtHost side so we can pass the original externalNode to the registered handler - $getInternalCommand(providerId: string, mainThreadNode: InternalTreeNode): TPromise { + $getInternalCommand(providerId: string, mainThreadNode: ITreeNode): TPromise { const commandConverter = this.commands.converter; if (mainThreadNode.clickCommand) { diff --git a/src/vs/workbench/api/node/mainThreadTreeView.ts b/src/vs/workbench/api/node/mainThreadExplorerView.ts similarity index 51% rename from src/vs/workbench/api/node/mainThreadTreeView.ts rename to src/vs/workbench/api/node/mainThreadExplorerView.ts index 54168aecf5c..4ffaa3977c2 100644 --- a/src/vs/workbench/api/node/mainThreadTreeView.ts +++ b/src/vs/workbench/api/node/mainThreadExplorerView.ts @@ -7,56 +7,73 @@ 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, MainThreadTreeViewShape, ExtHostTreeViewShape } from './extHost.protocol'; -import { ITreeExplorerService } from 'vs/workbench/parts/explorers/common/treeExplorerService'; -import { InternalTreeNodeContent, InternalTreeNodeProvider } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; +import { ExtHostContext, MainThreadExplorerViewShape, ExtHostExplorerViewShape, ITreeNode } from './extHost.protocol'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IExplorerViewsService, IExplorerViewDataProvider, IExplorerView } from 'vs/workbench/parts/explorers/common/explorer'; -export class MainThreadTreeView extends MainThreadTreeViewShape { - private _proxy: ExtHostTreeViewShape; +export class MainThreadExplorerView extends MainThreadExplorerViewShape { + + private _proxy: ExtHostExplorerViewShape; + private _views: Map> = new Map>(); constructor( @IThreadService threadService: IThreadService, - @ITreeExplorerService private treeExplorerService: ITreeExplorerService, + @IExplorerViewsService private explorerViewsService: IExplorerViewsService, @IMessageService private messageService: IMessageService, @ICommandService private commandService: ICommandService ) { super(); - - this._proxy = threadService.get(ExtHostContext.ExtHostTreeView); + this._proxy = threadService.get(ExtHostContext.ExtHostExplorerView); } - $registerTreeDataProvider(providerId: string): void { + $registerView(providerId: string, name: string): void { const provider = new TreeExplorerNodeProvider(providerId, this._proxy, this.messageService, this.commandService); - this.treeExplorerService.registerTreeExplorerNodeProvider(providerId, provider); + const view = this.explorerViewsService.createView(providerId, name, provider); + this._views.set(providerId, view); } - $refresh(providerId: string, node: InternalTreeNodeContent): void { - (this.treeExplorerService.getProvider(providerId))._onRefresh.fire(node); + $refresh(providerId: string, node: ITreeNode): void { + this._views.get(providerId).refresh(node); } } -class TreeExplorerNodeProvider implements InternalTreeNodeProvider { +class TreeExplorerNodeProvider implements IExplorerViewDataProvider { - readonly _onRefresh: Emitter = new Emitter(); - readonly onRefresh: Event = this._onRefresh.event; + readonly _onRefresh: Emitter = new Emitter(); + readonly onRefresh: Event = this._onRefresh.event; - constructor(public readonly id: string, private _proxy: ExtHostTreeViewShape, + constructor(public readonly id: string, private _proxy: ExtHostExplorerViewShape, private messageService: IMessageService, private commandService: ICommandService ) { } - provideRootNode(): TPromise { + provideRoot(): TPromise { return this._proxy.$provideRootNode(this.id).then(rootNode => rootNode, err => this.messageService.show(Severity.Error, err)); } - resolveChildren(node: InternalTreeNodeContent): TPromise { + resolveChildren(node: ITreeNode): TPromise { return this._proxy.$resolveChildren(this.id, node).then(children => children, err => this.messageService.show(Severity.Error, err)); } - executeCommand(node: InternalTreeNodeContent): TPromise { + hasChildren(node: ITreeNode): boolean { + return node.hasChildren; + } + + getLabel(node: ITreeNode): string { + return node.label; + } + + getId(node: ITreeNode): string { + return node.id; + } + + getContextKey(node: ITreeNode): string { + return node.contextKey; + } + + executeCommand(node: ITreeNode): TPromise { return this._proxy.$getInternalCommand(this.id, node).then(command => { return this.commandService.executeCommand(command.id, ...command.arguments); }); diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 1ad83a799ae..b6b84f4b4c8 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -24,6 +24,7 @@ import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IMessageService } from 'vs/platform/message/common/message'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IThemable } from 'vs/platform/theme/common/styler'; export abstract class Viewlet extends Composite implements IViewlet { @@ -288,7 +289,7 @@ export class CollapseAction extends Action { } } -export interface IViewletView extends IView { +export interface IViewletView extends IView, IThemable { create(): TPromise; setVisible(visible: boolean): TPromise; getActions(): IAction[]; diff --git a/src/vs/workbench/electron-browser/workbench.main.ts b/src/vs/workbench/electron-browser/workbench.main.ts index 1845a488d23..8b287038985 100644 --- a/src/vs/workbench/electron-browser/workbench.main.ts +++ b/src/vs/workbench/electron-browser/workbench.main.ts @@ -69,8 +69,7 @@ import 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet'; // ca import 'vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution'; -import 'vs/workbench/parts/explorers/browser/treeExplorer.contribution'; -import 'vs/workbench/parts/explorers/browser/treeExplorerViewlet'; // can be packaged separately +import 'vs/workbench/parts/explorers/browser/explorer.contribution'; import 'vs/workbench/parts/output/browser/output.contribution'; import 'vs/workbench/parts/output/browser/outputPanel'; // can be packaged separately diff --git a/src/vs/workbench/parts/explorers/browser/explorer.contribution.ts b/src/vs/workbench/parts/explorers/browser/explorer.contribution.ts new file mode 100644 index 00000000000..2c3a9eecbf0 --- /dev/null +++ b/src/vs/workbench/parts/explorers/browser/explorer.contribution.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IExplorerViewsService } from 'vs/workbench/parts/explorers/common/explorer'; +import { ExplorerViewsService } from 'vs/workbench/parts/explorers/browser/explorerView'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +registerSingleton(IExplorerViewsService, ExplorerViewsService); \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/browser/explorerView.ts b/src/vs/workbench/parts/explorers/browser/explorerView.ts new file mode 100644 index 00000000000..7e11e5fca63 --- /dev/null +++ b/src/vs/workbench/parts/explorers/browser/explorerView.ts @@ -0,0 +1,407 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Event, { Emitter } from 'vs/base/common/event'; +import { IDisposable, Disposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IViewletView, CollapsibleViewletView } from 'vs/workbench/browser/viewlet'; +import { IExplorerViewsService, IExplorerViewDataProvider, IExplorerView } from 'vs/workbench/parts/explorers/common/explorer'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TPromise } from 'vs/base/common/winjs.base'; +import * as DOM from 'vs/base/browser/dom'; +import { Builder, $ } from 'vs/base/browser/builder'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IAction, IActionRunner, IActionItem, ActionRunner } from 'vs/base/common/actions'; +import { IMessageService } from 'vs/platform/message/common/message'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IListService } from 'vs/platform/list/browser/listService'; +import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; +import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults'; +import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ITree, IDataSource, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; + +export interface IViewInstantiator { + instantiate(actionRunner: IActionRunner, viewletSetings: any, instantiationService: IInstantiationService): IViewletView; +} + +export class ExplorerViewsService implements IExplorerViewsService { + + public _serviceBrand: any; + + private explorerViews: Map> = new Map>(); + + private _onViewCreated: Emitter> = new Emitter>(); + public readonly onViewCreated: Event> = this._onViewCreated.event; + + private _onDataProviderRegistered: Emitter> = new Emitter>(); + public readonly onDataProviderRegistered: Event> = this._onDataProviderRegistered.event; + + createView(id: string, name: string, dataProvider: IExplorerViewDataProvider): IExplorerView { + const view = new ExplorerView(id, name, dataProvider); + this.explorerViews.set(id, view); + this._onViewCreated.fire(view); + return view; + } + + public getViews(): IExplorerView[] { + const views = []; + this.explorerViews.forEach(view => { + views.push(view); + }); + return views; + } +} + +class ExplorerView extends Disposable implements IExplorerView, IViewInstantiator { + + private view: TreeExplorerView; + + constructor(private id: string, private name: string, private dataProvider: IExplorerViewDataProvider) { + super(); + } + + refresh(element: T): void { + if (this.view) { + this.view.refresh(element); + } + } + + instantiate(actionRunner: IActionRunner, viewletSettings: any, instantiationService: IInstantiationService): IViewletView { + if (!this.view) { + this.view = instantiationService.createInstance(TreeExplorerView, this.id, this.name, this.dataProvider, actionRunner); + } + return this.view; + } +} + +class TreeExplorerView extends CollapsibleViewletView { + + private menus: Menus; + private viewFocusContext: IContextKey; + + constructor( + private id: string, + private name: string, + private dataProvider: IExplorerViewDataProvider, + actionRunner: IActionRunner, + @IMessageService messageService: IMessageService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IInstantiationService private instantiationService: IInstantiationService, + @IListService private listService: IListService, + @IThemeService private themeService: IThemeService, + @IContextKeyService private contextKeyService: IContextKeyService, + @IExplorerViewsService private explorerViewsService: IExplorerViewsService + ) { + super(actionRunner, false, name, messageService, keybindingService, contextMenuService); + this.menus = this.instantiationService.createInstance(Menus, this.id, this.dataProvider); + this.viewFocusContext = this.contextKeyService.createKey(this.id, void 0); + } + + public renderHeader(container: HTMLElement): void { + const titleDiv = $('div.title').appendTo(container); + $('span').text(this.name).appendTo(titleDiv); + super.renderHeader(container); + } + + public renderBody(container: HTMLElement): void { + this.treeContainer = super.renderViewTree(container); + DOM.addClass(this.treeContainer, 'tree-explorer-viewlet-tree-view'); + + this.tree = this.createViewer($(this.treeContainer)); + } + + public createViewer(container: Builder): ITree { + const dataSource = this.instantiationService.createInstance(TreeDataSource, this.dataProvider); + const renderer = this.instantiationService.createInstance(TreeRenderer, this.dataProvider); + const controller = this.instantiationService.createInstance(TreeController, this.menus); + const tree = new Tree(container.getHTMLElement(), { + dataSource, + renderer, + controller + }, { + keyboardSupport: false + }); + + this.toDispose.push(attachListStyler(tree, this.themeService)); + this.toDispose.push(this.listService.register(tree, [this.viewFocusContext])); + + return tree; + } + + getActions(): IAction[] { + return [...this.menus.getTitleActions()]; + } + + getSecondaryActions(): IAction[] { + return this.menus.getTitleSecondaryActions(); + } + + getActionItem(action: IAction): IActionItem { + return createActionItem(action, this.keybindingService, this.messageService); + } + + public create(): TPromise { + return this.updateInput(); + } + + public setVisible(visible: boolean): TPromise { + return super.setVisible(visible); + } + + public updateInput(): TPromise { + return this.dataProvider.provideRoot() + .then(root => this.tree.setInput(root)); + } + + public getOptimalWidth(): number { + const parentNode = this.tree.getHTMLElement(); + const childNodes = [].slice.call(parentNode.querySelectorAll('.outline-item-label > a')); + + return DOM.getLargestChildWidth(parentNode, childNodes); + } + + refresh(element: any) { + this.tree.refresh(element); + } +} + +class TreeDataSource implements IDataSource { + + constructor( + private dataProvider: IExplorerViewDataProvider, + @IProgressService private progressService: IProgressService, + @IExplorerViewsService private explorerViewsService: IExplorerViewsService + ) { + } + + public getId(tree: ITree, node: any): string { + return this.dataProvider.getId(node); + } + + public hasChildren(tree: ITree, node: any): boolean { + return this.dataProvider.hasChildren(node); + } + + public getChildren(tree: ITree, node: any): TPromise { + const promise = this.dataProvider.resolveChildren(node); + + this.progressService.showWhile(promise, 800); + + return promise; + } + + public getParent(tree: ITree, node: any): TPromise { + return TPromise.as(null); + } +} + +interface ITreeExplorerTemplateData { + label: Builder; +} + +class TreeRenderer implements IRenderer { + + private static ITEM_HEIGHT = 22; + private static TREE_TEMPLATE_ID = 'treeExplorer'; + + constructor( + private dataProvider: IExplorerViewDataProvider, + @IExplorerViewsService private explorerViewsService: IExplorerViewsService + ) { + } + + public getHeight(tree: ITree, element: any): number { + return TreeRenderer.ITEM_HEIGHT; + } + + public getTemplateId(tree: ITree, element: any): string { + return TreeRenderer.TREE_TEMPLATE_ID; + } + + public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): ITreeExplorerTemplateData { + const el = $(container); + const item = $('.custom-viewlet-tree-node-item'); + item.appendTo(el); + + const label = $('.custom-viewlet-tree-node-item-label').appendTo(item); + const link = $('a.plain').appendTo(label); + + return { label: link }; + } + + public renderElement(tree: ITree, node: any, templateId: string, templateData: ITreeExplorerTemplateData): void { + const label = this.dataProvider.getLabel(node); + templateData.label.text(label).title(label); + } + + public disposeTemplate(tree: ITree, templateId: string, templateData: ITreeExplorerTemplateData): void { + } +} + +class TreeController extends DefaultController { + + constructor( + private menus: Menus, + @IContextMenuService private contextMenuService: IContextMenuService, + @IKeybindingService private _keybindingService: IKeybindingService + ) { + super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false }); + } + + public onContextMenu(tree: ITree, node: any, event: ContextMenuEvent): boolean { + tree.setFocus(node); + const actions = this.menus.getResourceContextActions(node); + if (!actions.length) { + return true; + } + const anchor = { x: event.posx + 1, y: event.posy }; + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + + getActions: () => { + return TPromise.as(actions); + }, + + getActionItem: (action) => { + const keybinding = this._keybindingFor(action); + if (keybinding) { + return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + } + return null; + }, + + getKeyBinding: (action): ResolvedKeybinding => { + return this._keybindingFor(action); + }, + + onHide: (wasCancelled?: boolean) => { + if (wasCancelled) { + tree.DOMFocus(); + } + }, + + getActionsContext: () => node, + + actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection()) + }); + + return true; + } + + private _keybindingFor(action: IAction): ResolvedKeybinding { + return this._keybindingService.lookupKeybinding(action.id); + } +} + +class MultipleSelectionActionRunner extends ActionRunner { + + constructor(private getSelectedResources: () => any[]) { + super(); + } + + runAction(action: IAction, context: any): TPromise { + if (action instanceof MenuItemAction) { + const selection = this.getSelectedResources(); + const filteredSelection = selection.filter(s => s !== context); + + if (selection.length === filteredSelection.length || selection.length === 1) { + return action.run(context); + } + + return action.run(context, ...filteredSelection); + } + + return super.runAction(action, context); + } +} + +class Menus implements IDisposable { + + private disposables: IDisposable[] = []; + private titleDisposable: IDisposable = EmptyDisposable; + private titleActions: IAction[] = []; + private titleSecondaryActions: IAction[] = []; + + private _onDidChangeTitle = new Emitter(); + get onDidChangeTitle(): Event { return this._onDidChangeTitle.event; } + + constructor( + private viewId: string, + private dataProvider: IExplorerViewDataProvider, + @IContextKeyService private contextKeyService: IContextKeyService, + @IMenuService private menuService: IMenuService, + @IExplorerViewsService private explorerViewsService: IExplorerViewsService + ) { + if (this.titleDisposable) { + this.titleDisposable.dispose(); + this.titleDisposable = EmptyDisposable; + } + + const _contextKeyService = this.contextKeyService.createScoped(); + contextKeyService.createKey('view', viewId); + + const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService); + const updateActions = () => { + this.titleActions = []; + this.titleSecondaryActions = []; + fillInActions(titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions }); + this._onDidChangeTitle.fire(); + }; + + const listener = titleMenu.onDidChange(updateActions); + updateActions(); + + + this.titleDisposable = toDisposable(() => { + listener.dispose(); + titleMenu.dispose(); + _contextKeyService.dispose(); + this.titleActions = []; + this.titleSecondaryActions = []; + }); + } + + getTitleActions(): IAction[] { + return this.titleActions; + } + + getTitleSecondaryActions(): IAction[] { + return this.titleSecondaryActions; + } + + getResourceContextActions(element: any): IAction[] { + return this.getActions(MenuId.ViewResource, { key: 'resource', value: this.dataProvider.getContextKey(element) }).secondary; + } + + private getActions(menuId: MenuId, context: { key: string, value: string }): { primary: IAction[]; secondary: IAction[]; } { + const contextKeyService = this.contextKeyService.createScoped(); + contextKeyService.createKey('view', this.viewId); + contextKeyService.createKey(context.key, context.value); + + const menu = this.menuService.createMenu(menuId, contextKeyService); + const primary = []; + const secondary = []; + const result = { primary, secondary }; + fillInActions(menu, { shouldForwardArgs: true }, result, g => g === 'inline'); + + menu.dispose(); + contextKeyService.dispose(); + + return result; + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} diff --git a/src/vs/workbench/parts/explorers/common/explorer.ts b/src/vs/workbench/parts/explorers/common/explorer.ts new file mode 100644 index 00000000000..b6a1731fb9b --- /dev/null +++ b/src/vs/workbench/parts/explorers/common/explorer.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * 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 Event from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IActionRunner } from 'vs/base/common/actions'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +export const IExplorerViewsService = createDecorator('explorerViewsService'); + +export interface IExplorerViewsService { + _serviceBrand: any; + + readonly onViewCreated: Event>; + + createView(id: string, name: string, dataProvider: IExplorerViewDataProvider): IExplorerView; + getViews(): IExplorerView[]; +} + +export interface IExplorerView extends IDisposable { + refresh(element: T): void; + instantiate(actionRunner: IActionRunner, viewletSetings: any, instantiationService: IInstantiationService): any; +} + +export interface IExplorerViewDataProvider { + provideRoot(): TPromise; + resolveChildren(element: T): TPromise; + getId(element: T): string; + getLabel(element: T): string; + getContextKey(element: T): string; + hasChildren(element: T): boolean; +} diff --git a/src/vs/workbench/parts/files/browser/explorerViewlet.ts b/src/vs/workbench/parts/files/browser/explorerViewlet.ts index e32cb7f8843..5aa2a30f535 100644 --- a/src/vs/workbench/parts/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/parts/files/browser/explorerViewlet.ts @@ -13,7 +13,7 @@ import { Dimension, Builder } from 'vs/base/browser/builder'; import { Scope } from 'vs/workbench/common/memento'; import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration } from 'vs/workbench/parts/files/common/files'; import { IViewletView, Viewlet } from 'vs/workbench/browser/viewlet'; -import { SplitView, Orientation } from 'vs/base/browser/ui/splitview/splitview'; +import { SplitView } from 'vs/base/browser/ui/splitview/splitview'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ActionRunner, FileViewletState } from 'vs/workbench/parts/files/browser/views/explorerViewer'; import { ExplorerView } from 'vs/workbench/parts/files/browser/views/explorerView'; @@ -32,6 +32,7 @@ import { IEditorGroupService } from 'vs/workbench/services/group/common/groupSer import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachHeaderViewStyler } from 'vs/platform/theme/common/styler'; +import { IExplorerViewsService } from 'vs/workbench/parts/explorers/common/explorer'; export class ExplorerViewlet extends Viewlet { private viewletContainer: Builder; @@ -43,7 +44,7 @@ export class ExplorerViewlet extends Viewlet { private emptyView: EmptyView; private openEditorsVisible: boolean; - private lastFocusedView: ExplorerView | OpenEditorsView | EmptyView; + private lastFocusedView: IViewletView; private focusListener: IDisposable; private delayEditorOpeningInOpenedEditors: boolean; @@ -62,7 +63,8 @@ export class ExplorerViewlet extends Viewlet { @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IExplorerViewsService private explorerViewsService: IExplorerViewsService ) { super(VIEWLET_ID, telemetryService, themeService); @@ -73,6 +75,7 @@ export class ExplorerViewlet extends Viewlet { this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE); this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(e.config)); + this.explorerViewsService.onViewCreated(view => this.render()); } public create(parent: Builder): TPromise { @@ -80,78 +83,79 @@ export class ExplorerViewlet extends Viewlet { this.viewletContainer = parent.div().addClass('explorer-viewlet'); - const settings = this.configurationService.getConfiguration(); - - return this.onConfigurationUpdated(settings); + return this.render(); } public getActions(): IAction[] { - if (this.openEditorsVisible) { - return []; + if (this.views.length === 1) { + return this.views[0].getActions(); } - - if (this.explorerView) { - return this.explorerView.getActions(); - } - return []; } - private onConfigurationUpdated(config: IFilesConfiguration): TPromise { + private render(): TPromise { + const config = this.configurationService.getConfiguration(); // No need to delay if preview is disabled this.delayEditorOpeningInOpenedEditors = !!config.workbench.editor.enablePreview; // Open editors view should always be visible in no folder workspace. - const openEditorsVisible = !this.contextService.hasWorkspace() || config.explorer.openEditors.visible !== 0; + this.openEditorsVisible = !this.contextService.hasWorkspace() || config.explorer.openEditors.visible !== 0; - // Create views on startup and if open editors visibility has changed #6919 - if (this.openEditorsVisible !== openEditorsVisible) { - this.dispose(); - this.openEditorsVisible = openEditorsVisible; - this.views = []; - this.viewletContainer.clearChildren(); + this.dispose(); + this.views = []; + this.viewletContainer.clearChildren(); - if (this.openEditorsVisible) { - this.splitView = new SplitView(this.viewletContainer.getHTMLElement()); + this.splitView = new SplitView(this.viewletContainer.getHTMLElement()); + // Track focus + this.focusListener = this.splitView.onFocus((view: IViewletView) => { + this.lastFocusedView = view; + }); - // Open editors view - this.addOpenEditorsView(); + const customViews = this.explorerViewsService.getViews(); - // Track focus - this.focusListener = this.splitView.onFocus((view: ExplorerView | OpenEditorsView | EmptyView) => { - this.lastFocusedView = view; - }); - } - - // Explorer view - this.addExplorerView(); - this.lastFocusedView = this.explorerView; - - return TPromise.join(this.views.map(view => view.create())).then(() => void 0).then(() => { - if (this.dimension) { - this.layout(this.dimension); - } - - // Update title area since the title actions have changed. - this.updateTitleArea(); - return this.setVisible(this.isVisible()).then(() => this.focus()); // Focus the viewlet since that triggers a rerender. - }); + if (this.openEditorsVisible) { + // Open editors view + this.openEditorsView = this.instantiationService.createInstance(OpenEditorsView, this.getActionRunner(), this.viewletSettings); + this.views.push(this.openEditorsView); } - return TPromise.as(null); + // Explorer view + this.createExplorerView(this.views.length || customViews.length ? undefined : 0); + this.views.push(this.explorerView); + + // custom views + for (const view of customViews) { + this.views.push(view.instantiate(this.getActionRunner(), this.viewletSettings, this.instantiationService)); + } + + for (const view of this.views) { + attachHeaderViewStyler(view, this.themeService); + this.splitView.addView(view); + } + + this.lastFocusedView = this.explorerView; + + return TPromise.join(this.views.map(view => view.create())).then(() => void 0).then(() => { + if (this.dimension) { + this.layout(this.dimension); + } + + // Update title area since the title actions have changed. + this.updateTitleArea(); + return this.setVisible(this.isVisible()).then(() => this.focus()); // Focus the viewlet since that triggers a rerender. + }); } - private addOpenEditorsView(): void { - this.openEditorsView = this.instantiationService.createInstance(OpenEditorsView, this.getActionRunner(), this.viewletSettings); - attachHeaderViewStyler(this.openEditorsView, this.themeService); - - this.splitView.addView(this.openEditorsView); - - this.views.push(this.openEditorsView); + private onConfigurationUpdated(config: IFilesConfiguration): void { + // Open editors view should always be visible in no folder workspace. + const openEditorsVisible = !this.contextService.hasWorkspace() || config.explorer.openEditors.visible !== 0; + if (this.openEditorsVisible !== openEditorsVisible) { + this.render(); + } } - private addExplorerView(): void { + private createExplorerView(headerSize: number): void { let explorerOrEmptyView: ExplorerView | EmptyView; // With a Workspace @@ -188,25 +192,13 @@ export class ExplorerViewlet extends Viewlet { }); const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService])); - - const headerSize = this.openEditorsVisible ? undefined : 0; // If open editors are not visible set header size explicitly to 0, otherwise const it be computed by super class. this.explorerView = explorerOrEmptyView = explorerInstantiator.createInstance(ExplorerView, this.viewletState, this.getActionRunner(), this.viewletSettings, headerSize); - attachHeaderViewStyler(this.explorerView, this.themeService); } // No workspace else { this.emptyView = explorerOrEmptyView = this.instantiationService.createInstance(EmptyView, this.getActionRunner()); - attachHeaderViewStyler(this.emptyView, this.themeService); } - - if (this.openEditorsVisible) { - this.splitView.addView(explorerOrEmptyView); - } else { - explorerOrEmptyView.render(this.viewletContainer.getHTMLElement(), Orientation.VERTICAL); - } - - this.views.push(explorerOrEmptyView); } public getExplorerView(): ExplorerView { @@ -260,7 +252,7 @@ export class ExplorerViewlet extends Viewlet { return this.openEditorsView.focus(); } - private hasSelectionOrFocus(view: ExplorerView | OpenEditorsView | EmptyView): boolean { + private hasSelectionOrFocus(view: IViewletView): boolean { if (!view) { return false; } @@ -284,12 +276,7 @@ export class ExplorerViewlet extends Viewlet { public layout(dimension: Dimension): void { this.dimension = dimension; - - if (this.openEditorsVisible) { - this.splitView.layout(dimension.height); - } else if (this.explorerView) { - this.explorerView.layout(dimension.height, Orientation.VERTICAL); - } + this.splitView.layout(dimension.height); } public getActionRunner(): IActionRunner { @@ -306,7 +293,7 @@ export class ExplorerViewlet extends Viewlet { public getOptimalWidth(): number { const additionalMargin = 16; - const openedEditorsViewWidth = this.openEditorsVisible ? this.openEditorsView.getOptimalWidth() : 0; + const openedEditorsViewWidth = this.openEditorsView ? this.openEditorsView.getOptimalWidth() : 0; const explorerView = this.getExplorerView(); const explorerViewWidth = explorerView ? explorerView.getOptimalWidth() : 0; const optimalWidth = Math.max(openedEditorsViewWidth, explorerViewWidth);