From 2a457991f2074c57cf5e4199b9ec8c4daace7c5c Mon Sep 17 00:00:00 2001 From: fangnx Date: Tue, 9 Jul 2019 16:40:40 -0400 Subject: [PATCH] Fixed #5670: implemented "collapse all" in the Outline widget - Added a "collapse all" toolbar item to the Outline widget. Signed-off-by: fangnx --- .../core/src/browser/tree/tree-expansion.ts | 38 +++++++++++++ .../src/browser/outline-view-contribution.ts | 56 ++++++++++++++++++- .../browser/outline-view-frontend-module.ts | 11 +++- .../src/browser/outline-view-tree.ts | 47 ++++++++++++++++ .../src/browser/outline-view-widget.tsx | 20 ++++++- 5 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 packages/outline-view/src/browser/outline-view-tree.ts diff --git a/packages/core/src/browser/tree/tree-expansion.ts b/packages/core/src/browser/tree/tree-expansion.ts index 01c8dcfb737be..33a91af32c1a2 100644 --- a/packages/core/src/browser/tree/tree-expansion.ts +++ b/packages/core/src/browser/tree/tree-expansion.ts @@ -47,6 +47,10 @@ export interface TreeExpansionService extends Disposable { * Return true if a node has been collapsed; otherwise false. */ collapseAll(node: Readonly): Promise; + + // collapseAllWithoutSelection(raw: CompositeTreeNode): Promise + // readonly onCollapseAllFinished: Event>; + /** * If the given node is invalid then does nothing. * If the given node is collapsed then expand it; otherwise collapse it. @@ -83,6 +87,7 @@ export class TreeExpansionServiceImpl implements TreeExpansionService { @inject(Tree) protected readonly tree: Tree; protected readonly onExpansionChangedEmitter = new Emitter(); + // protected readonly onCollapseAllFinishedEmitter = new Emitter(); @postConstruct() protected init(): void { @@ -97,16 +102,25 @@ export class TreeExpansionServiceImpl implements TreeExpansionService { dispose() { this.onExpansionChangedEmitter.dispose(); + // this.onCollapseAllFinishedEmitter.dispose(); } get onExpansionChanged(): Event { return this.onExpansionChangedEmitter.event; } + // get onCollapseAllFinished(): Event { + // return this.onCollapseAllFinishedEmitter.event; + // } + protected fireExpansionChanged(node: ExpandableTreeNode): void { this.onExpansionChangedEmitter.fire(node); } + // protected fireExpansionX(node: ExpandableTreeNode): void { + // this.onCollapseAllFinishedEmitter.fire(node); + // } + async expandNode(raw: ExpandableTreeNode): Promise { const node = this.tree.validateNode(raw); if (ExpandableTreeNode.isCollapsed(node)) { @@ -132,6 +146,11 @@ export class TreeExpansionServiceImpl implements TreeExpansionService { return this.doCollapseAll(node); } + // async collapseAllWithoutSelection(raw: CompositeTreeNode): Promise { + // const node = this.tree.validateNode(raw); + // return this.doCollapseAllWithoutSelection(node); + // } + protected doCollapseAll(node: TreeNode | undefined): boolean { let result = false; if (CompositeTreeNode.is(node)) { @@ -142,6 +161,16 @@ export class TreeExpansionServiceImpl implements TreeExpansionService { return this.doCollapseNode(node) || result; } + // protected doCollapseAllWithoutSelection(node: TreeNode | undefined): boolean { + // let result = false; + // if (CompositeTreeNode.is(node)) { + // for (const child of node.children) { + // result = this.doCollapseAllWithoutSelection(child) || result; + // } + // } + // return this.doCollapseNodeWithoutFiring(node) || result; + // } + protected doCollapseNode(node: TreeNode | undefined): boolean { if (!ExpandableTreeNode.isExpanded(node)) { return false; @@ -151,6 +180,15 @@ export class TreeExpansionServiceImpl implements TreeExpansionService { return true; } + // protected doCollapseNodeWithoutFiring(node: TreeNode | undefined): boolean { + // if (!ExpandableTreeNode.isExpanded(node)) { + // return false; + // } + // node.expanded = false; + // this.fireExpansionX(node); + // return true; + // } + async toggleNodeExpansion(node: ExpandableTreeNode): Promise { if (node.expanded) { await this.collapseNode(node); diff --git a/packages/outline-view/src/browser/outline-view-contribution.ts b/packages/outline-view/src/browser/outline-view-contribution.ts index f22c44f16581c..f1df7ce4ba683 100644 --- a/packages/outline-view/src/browser/outline-view-contribution.ts +++ b/packages/outline-view/src/browser/outline-view-contribution.ts @@ -16,13 +16,24 @@ import { injectable } from 'inversify'; import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; -import { OutlineViewWidget } from './outline-view-widget'; import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application'; +import { Command, CommandRegistry } from '@theia/core/lib/common/command'; +import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; +import { Widget } from '@theia/core/lib/browser/widgets'; +import { OutlineViewWidget } from './outline-view-widget'; +import { CompositeTreeNode } from '@theia/core/lib/browser/tree'; export const OUTLINE_WIDGET_FACTORY_ID = 'outline-view'; +export namespace OutlineViewCommands { + export const COLLAPSE_ALL: Command = { + id: 'outlineView.collapse.all', + iconClass: 'collapse-all' + }; +} + @injectable() -export class OutlineViewContribution extends AbstractViewContribution implements FrontendApplicationContribution { +export class OutlineViewContribution extends AbstractViewContribution implements FrontendApplicationContribution, TabBarToolbarContribution { constructor() { super({ @@ -40,4 +51,45 @@ export class OutlineViewContribution extends AbstractViewContribution { await this.openView(); } + + registerCommands(commands: CommandRegistry): void { + super.registerCommands(commands); + commands.registerCommand(OutlineViewCommands.COLLAPSE_ALL, { + isEnabled: widget => this.withWidget(widget, () => true), + isVisible: widget => this.withWidget(widget, () => true), + execute: () => this.collapseAllItems() + }); + } + + registerToolbarItems(toolbar: TabBarToolbarRegistry): void { + toolbar.registerItem({ + id: OutlineViewCommands.COLLAPSE_ALL.id, + command: OutlineViewCommands.COLLAPSE_ALL.id, + tooltip: 'Collapse All', + priority: 0 + }); + } + + /** + * Collapse all nodes in the outline view tree. + * Reselect the last selected node to keep the focus in the editor. + */ + protected async collapseAllItems(): Promise { + const { model } = await this.widget; + const selectedNode = model.selectedNodes[0]; + const root = model.root as CompositeTreeNode; + root.children.forEach(async childNode => { + if (CompositeTreeNode.is(childNode)) { + await model.collapseAll(childNode); + } + }); + model.selectNode(selectedNode); + } + + protected withWidget(widget: Widget | undefined = this.tryGetWidget(), cb: (widget: OutlineViewWidget) => T): T | false { + if (widget instanceof OutlineViewWidget && widget.id === OUTLINE_WIDGET_FACTORY_ID) { + return cb(widget); + } + return false; + } } diff --git a/packages/outline-view/src/browser/outline-view-frontend-module.ts b/packages/outline-view/src/browser/outline-view-frontend-module.ts index 01066c49654a6..0558e338ffabd 100644 --- a/packages/outline-view/src/browser/outline-view-frontend-module.ts +++ b/packages/outline-view/src/browser/outline-view-frontend-module.ts @@ -25,12 +25,16 @@ import { bindViewContribution, TreeProps, defaultTreeProps, - TreeDecoratorService + TreeDecoratorService, + TreeModel, + TreeModelImpl } from '@theia/core/lib/browser'; +import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { OutlineViewWidgetFactory, OutlineViewWidget } from './outline-view-widget'; import '../../src/browser/styles/index.css'; import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider'; import { OutlineDecoratorService, OutlineTreeDecorator } from './outline-decorator-service'; +import { OutlineViewTreeModel } from './outline-view-tree'; export default new ContainerModule(bind => { bind(OutlineViewWidgetFactory).toFactory(ctx => @@ -42,6 +46,7 @@ export default new ContainerModule(bind => { bindViewContribution(bind, OutlineViewContribution); bind(FrontendApplicationContribution).toService(OutlineViewContribution); + bind(TabBarToolbarContribution).toService(OutlineViewContribution); }); function createOutlineViewWidget(parent: interfaces.Container): OutlineViewWidget { @@ -52,6 +57,10 @@ function createOutlineViewWidget(parent: interfaces.Container): OutlineViewWidge child.unbind(TreeWidget); child.bind(OutlineViewWidget).toSelf(); + child.unbind(TreeModelImpl); + child.bind(OutlineViewTreeModel).toSelf(); + child.rebind(TreeModel).toService(OutlineViewTreeModel); + child.bind(OutlineDecoratorService).toSelf().inSingletonScope(); child.rebind(TreeDecoratorService).toDynamicValue(ctx => ctx.container.get(OutlineDecoratorService)).inSingletonScope(); bindContributionProvider(child, OutlineTreeDecorator); diff --git a/packages/outline-view/src/browser/outline-view-tree.ts b/packages/outline-view/src/browser/outline-view-tree.ts new file mode 100644 index 0000000000000..42a171f3b052f --- /dev/null +++ b/packages/outline-view/src/browser/outline-view-tree.ts @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (C) 2019 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable, inject, postConstruct } from 'inversify'; +import { CompositeTreeNode, TreeModelImpl, TreeSelectionService, TreeExpansionService } from '@theia/core/lib/browser'; + +@injectable() +export class OutlineViewTreeModel extends TreeModelImpl { + + @inject(TreeSelectionService) protected readonly selectionService: TreeSelectionService; + @inject(TreeExpansionService) protected readonly expansionService: TreeExpansionService; + + @postConstruct() + protected init(): void { + this.toDispose.push(this.tree); + this.toDispose.push(this.tree.onChanged(() => this.fireChanged())); + this.toDispose.push(this.selectionService); + this.toDispose.push(this.expansionService); + this.toDispose.push(this.expansionService.onExpansionChanged(node => { + this.fireChanged(); + })); + this.toDispose.push(this.onOpenNodeEmitter); + this.toDispose.push(this.onChangedEmitter); + this.toDispose.push(this.treeSearch); + } + + async collapseAll(raw?: Readonly): Promise { + const node = raw || this.selectedNodes[0]; + if (CompositeTreeNode.is(node)) { + return await this.expansionService.collapseAll(node); + } + return false; + } +} diff --git a/packages/outline-view/src/browser/outline-view-widget.tsx b/packages/outline-view/src/browser/outline-view-widget.tsx index e1b1ac8bbf472..c51d6fe769ed6 100644 --- a/packages/outline-view/src/browser/outline-view-widget.tsx +++ b/packages/outline-view/src/browser/outline-view-widget.tsx @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, inject } from 'inversify'; +import { injectable, inject, postConstruct } from 'inversify'; import { TreeWidget, TreeNode, @@ -25,6 +25,7 @@ import { TreeModel, ExpandableTreeNode } from '@theia/core/lib/browser'; +import { OutlineViewTreeModel } from './outline-view-tree'; import { Message } from '@phosphor/messaging'; import { Emitter } from '@theia/core'; import { CompositeTreeNode } from '@theia/core/lib/browser'; @@ -50,7 +51,7 @@ export class OutlineViewWidget extends TreeWidget { constructor( @inject(TreeProps) protected readonly treeProps: TreeProps, - @inject(TreeModel) model: TreeModel, + @inject(OutlineViewTreeModel) model: OutlineViewTreeModel, @inject(ContextMenuRenderer) protected readonly contextMenuRenderer: ContextMenuRenderer ) { super(treeProps, model, contextMenuRenderer); @@ -63,6 +64,21 @@ export class OutlineViewWidget extends TreeWidget { this.addClass('theia-outline-view'); } + @postConstruct() + protected init(): void { + super.init(); + this.toDispose.pushAll([ + // this.model.onExpansionChanged(node => { + // console.log('a'); + // const temp = node.children[0]; + // if (SelectableTreeNode.is(node) && SelectableTreeNode.is(temp)) { + // console.log(temp.name); + // this.model.selectNode(temp); + // } + // }) + ]); + } + public setOutlineTree(roots: OutlineSymbolInformationNode[]): void { const nodes = this.reconcileTreeState(roots); this.model.root = {