提交 ccae6f96 编写于 作者: S Sandeep Somavarapu

Contribute explorer views

- Adopt proposed API to contribute explorer views
- Adopt extension host and main to contribute explorer views
- Implement contributable views in explorer viewlet
上级 8b4a17ca
......@@ -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<T>(viewId: string, provider: TreeDataProvider<T>): TreeView<T>;
export function createExplorerView<T>(id: string, name: string, dataProvider: TreeDataProvider<T>): View<T>;
}
/**
* 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<T> {
export interface View<T> {
/**
* 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.
*
......
......@@ -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<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 extHostTreeView = col.define(ExtHostContext.ExtHostTreeView).set<ExtHostTreeView>(new ExtHostTreeView(threadService, extHostCommands));
const extHostExplorerView = col.define(ExtHostContext.ExtHostExplorerView).set<ExtHostExplorerView>(new ExtHostExplorerView(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!', {}, []);
}),
createTreeView: proposedApiFunction(extension, (providerId: string, provider: vscode.TreeDataProvider<any>): vscode.TreeView<any> => {
return extHostTreeView.createTreeView(providerId, provider);
createExplorerView: proposedApiFunction(extension, (id: string, name: string, provider: vscode.TreeDataProvider<any>): vscode.View<any> => {
return extHostExplorerView.createExplorerView(id, name, provider);
})
};
......
......@@ -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));
......
......@@ -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<editorCommon.ILineChange[]> { 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<InternalTreeNodeContent> { throw ni(); };
$resolveChildren(providerId: string, node: InternalTreeNodeContent): TPromise<InternalTreeNodeContent[]> { throw ni(); }
$getInternalCommand(providerId: string, node: InternalTreeNodeContent): TPromise<modes.Command> { throw ni(); }
}
export abstract class ExtHostTreeShape {
$resolveChildren(providerId: string, node: InternalTreeNodeContent): TPromise<InternalTreeNodeContent[]> { throw ni(); }
$getInternalCommand(providerId: string, node: InternalTreeNodeContent): TPromise<modes.Command> { throw ni(); }
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 ExtHostExtensionServiceShape {
......@@ -465,7 +462,7 @@ export const MainContext = {
MainThreadDocuments: createMainId<MainThreadDocumentsShape>('MainThreadDocuments', MainThreadDocumentsShape),
MainThreadEditors: createMainId<MainThreadEditorsShape>('MainThreadEditors', MainThreadEditorsShape),
MainThreadErrors: createMainId<MainThreadErrorsShape>('MainThreadErrors', MainThreadErrorsShape),
MainThreadExplorers: createMainId<MainThreadTreeViewShape>('MainThreadTreeView', MainThreadTreeViewShape),
MainThreadExplorerViews: createMainId<MainThreadExplorerViewShape>('MainThreadExplorerView', MainThreadExplorerViewShape),
MainThreadLanguageFeatures: createMainId<MainThreadLanguageFeaturesShape>('MainThreadLanguageFeatures', MainThreadLanguageFeaturesShape),
MainThreadLanguages: createMainId<MainThreadLanguagesShape>('MainThreadLanguages', MainThreadLanguagesShape),
MainThreadMessageService: createMainId<MainThreadMessageServiceShape>('MainThreadMessageService', MainThreadMessageServiceShape),
......@@ -490,7 +487,7 @@ export const ExtHostContext = {
ExtHostDocuments: createExtId<ExtHostDocumentsShape>('ExtHostDocuments', ExtHostDocumentsShape),
ExtHostDocumentSaveParticipant: createExtId<ExtHostDocumentSaveParticipantShape>('ExtHostDocumentSaveParticipant', ExtHostDocumentSaveParticipantShape),
ExtHostEditors: createExtId<ExtHostEditorsShape>('ExtHostEditors', ExtHostEditorsShape),
ExtHostTreeView: createExtId<ExtHostTreeViewShape>('ExtHostTreeView', ExtHostTreeViewShape),
ExtHostExplorerView: createExtId<ExtHostExplorerViewShape>('ExtHostExplorerView', ExtHostExplorerViewShape),
ExtHostFileSystemEventService: createExtId<ExtHostFileSystemEventServiceShape>('ExtHostFileSystemEventService', ExtHostFileSystemEventServiceShape),
ExtHostHeapService: createExtId<ExtHostHeapServiceShape>('ExtHostHeapMonitor', ExtHostHeapServiceShape),
ExtHostLanguageFeatures: createExtId<ExtHostLanguageFeaturesShape>('ExtHostLanguageFeatures', ExtHostLanguageFeaturesShape),
......
......@@ -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<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) {
......@@ -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<any> };
private _extViews: Map<string, TreeView<any>> = new Map<string, TreeView<any>>();
private _extNodeMaps: { [providerId: string]: { [id: string]: InternalTreeNode } };
private _mainNodesMap: Map<string, Map<any, InternalTreeNode>>;
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(
......@@ -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<string, Map<any, InternalTreeNode>>();
this._mainNodesMap = new Map<string, Map<any, ITreeNode>>();
this._childrenNodesMap = new Map<string, Map<any, any[]>>();
commands.registerArgumentProcessor({
......@@ -68,39 +69,39 @@ export class ExtHostTreeView extends ExtHostTreeViewShape {
});
}
createTreeView<T>(providerId: string, provider: TreeDataProvider<T>): TreeView<T> {
this._proxy.$registerTreeDataProvider(providerId);
this._extNodeProviders[providerId] = provider;
this._mainNodesMap.set(providerId, new Map<any, InternalTreeNode>());
this._childrenNodesMap.set(providerId, new Map<any, any>());
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: TreeView<T> = {
const treeView: View<T> = {
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<InternalTreeNode> {
$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<InternalTreeNode>(errMessage);
return TPromise.wrapError<ITreeNode>(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<InternalTreeNode>(errMessage);
return TPromise.wrapError<ITreeNode>(errMessage);
});
}
$resolveChildren(providerId: string, mainThreadNode: InternalTreeNode): TPromise<InternalTreeNode[]> {
$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<InternalTreeNode[]>(errMessage);
return TPromise.wrapError<ITreeNode[]>(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<modes.Command> {
$getInternalCommand(providerId: string, mainThreadNode: ITreeNode): TPromise<modes.Command> {
const commandConverter = this.commands.converter;
if (mainThreadNode.clickCommand) {
......
......@@ -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<string, IExplorerView<ITreeNode>> = new Map<string, IExplorerView<ITreeNode>>();
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 {
(<TreeExplorerNodeProvider>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<ITreeNode> {
readonly _onRefresh: Emitter<InternalTreeNodeContent> = new Emitter<InternalTreeNodeContent>();
readonly onRefresh: Event<InternalTreeNodeContent> = this._onRefresh.event;
readonly _onRefresh: Emitter<ITreeNode> = new Emitter<ITreeNode>();
readonly onRefresh: Event<ITreeNode> = 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<InternalTreeNodeContent> {
provideRoot(): TPromise<ITreeNode> {
return this._proxy.$provideRootNode(this.id).then(rootNode => rootNode, err => this.messageService.show(Severity.Error, err));
}
resolveChildren(node: InternalTreeNodeContent): TPromise<InternalTreeNodeContent[]> {
resolveChildren(node: ITreeNode): TPromise<ITreeNode[]> {
return this._proxy.$resolveChildren(this.id, node).then(children => children, err => this.messageService.show(Severity.Error, err));
}
executeCommand(node: InternalTreeNodeContent): TPromise<any> {
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<any> {
return this._proxy.$getInternalCommand(this.id, node).then(command => {
return this.commandService.executeCommand(command.id, ...command.arguments);
});
......
......@@ -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<void>;
setVisible(visible: boolean): TPromise<void>;
getActions(): IAction[];
......
......@@ -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
......
/*---------------------------------------------------------------------------------------------
* 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
/*---------------------------------------------------------------------------------------------
* 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<string, IExplorerView<any>> = new Map<string, IExplorerView<any>>();
private _onViewCreated: Emitter<IExplorerView<any>> = new Emitter<IExplorerView<any>>();
public readonly onViewCreated: Event<IExplorerView<any>> = this._onViewCreated.event;
private _onDataProviderRegistered: Emitter<IExplorerView<any>> = new Emitter<IExplorerView<any>>();
public readonly onDataProviderRegistered: Event<IExplorerView<any>> = this._onDataProviderRegistered.event;
createView(id: string, name: string, dataProvider: IExplorerViewDataProvider<any>): IExplorerView<any> {
const view = new ExplorerView(id, name, dataProvider);
this.explorerViews.set(id, view);
this._onViewCreated.fire(view);
return view;
}
public getViews(): IExplorerView<any>[] {
const views = [];
this.explorerViews.forEach(view => {
views.push(view);
});
return views;
}
}
class ExplorerView<T> extends Disposable implements IExplorerView<T>, IViewInstantiator {
private view: TreeExplorerView;
constructor(private id: string, private name: string, private dataProvider: IExplorerViewDataProvider<T>) {
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<boolean>;
constructor(
private id: string,
private name: string,
private dataProvider: IExplorerViewDataProvider<any>,
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<boolean>(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<void> {
return this.updateInput();
}
public setVisible(visible: boolean): TPromise<void> {
return super.setVisible(visible);
}
public updateInput(): TPromise<void> {
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<any>,
@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<any[]> {
const promise = this.dataProvider.resolveChildren(node);
this.progressService.showWhile(promise, 800);
return promise;
}
public getParent(tree: ITree, node: any): TPromise<any> {
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<any>,
@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<any> {
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<void>();
get onDidChangeTitle(): Event<void> { return this._onDidChangeTitle.event; }
constructor(
private viewId: string,
private dataProvider: IExplorerViewDataProvider<any>,
@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);
}
}
/*---------------------------------------------------------------------------------------------
* 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<IExplorerViewsService>('explorerViewsService');
export interface IExplorerViewsService {
_serviceBrand: any;
readonly onViewCreated: Event<IExplorerView<any>>;
createView<T>(id: string, name: string, dataProvider: IExplorerViewDataProvider<T>): IExplorerView<T>;
getViews<T>(): IExplorerView<T>[];
}
export interface IExplorerView<T> extends IDisposable {
refresh(element: T): void;
instantiate(actionRunner: IActionRunner, viewletSetings: any, instantiationService: IInstantiationService): any;
}
export interface IExplorerViewDataProvider<T> {
provideRoot(): TPromise<T>;
resolveChildren(element: T): TPromise<T[]>;
getId(element: T): string;
getLabel(element: T): string;
getContextKey(element: T): string;
hasChildren(element: T): boolean;
}
......@@ -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<void> {
......@@ -80,78 +83,79 @@ export class ExplorerViewlet extends Viewlet {
this.viewletContainer = parent.div().addClass('explorer-viewlet');
const settings = this.configurationService.getConfiguration<IFilesConfiguration>();
return this.onConfigurationUpdated(settings);
return this.render();
}
public getActions(): IAction[] {
if (this.openEditorsVisible) {
return [];
}
if (this.explorerView) {
return this.explorerView.getActions();
if (this.views.length === 1) {
return this.views[0].getActions();
}
return [];
}
private onConfigurationUpdated(config: IFilesConfiguration): TPromise<void> {
private render(): TPromise<void> {
const config = this.configurationService.getConfiguration<IFilesConfiguration>();
// 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;
});
}
if (this.openEditorsVisible) {
// Open editors view
this.openEditorsView = this.instantiationService.createInstance(OpenEditorsView, this.getActionRunner(), this.viewletSettings);
this.views.push(this.openEditorsView);
}
// Explorer view
this.addExplorerView();
this.lastFocusedView = this.explorerView;
// Explorer view
this.createExplorerView(this.views.length || customViews.length ? undefined : 0);
this.views.push(this.explorerView);
return TPromise.join(this.views.map(view => view.create())).then(() => void 0).then(() => {
if (this.dimension) {
this.layout(this.dimension);
}
// custom views
for (const view of customViews) {
this.views.push(view.instantiate(this.getActionRunner(), this.viewletSettings, this.instantiationService));
}
// 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.
});
for (const view of this.views) {
attachHeaderViewStyler(view, this.themeService);
this.splitView.addView(view);
}
return TPromise.as(null);
}
this.lastFocusedView = this.explorerView;
private addOpenEditorsView(): void {
this.openEditorsView = this.instantiationService.createInstance(OpenEditorsView, this.getActionRunner(), this.viewletSettings);
attachHeaderViewStyler(this.openEditorsView, this.themeService);
return TPromise.join(this.views.map(view => view.create())).then(() => void 0).then(() => {
if (this.dimension) {
this.layout(this.dimension);
}
this.splitView.addView(this.openEditorsView);
// 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.
});
}
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);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册