Skip to content

Commit

Permalink
Fixed #5670: implemented "collapse all" in the Outline widget
Browse files Browse the repository at this point in the history
- Added a "collapse all" toolbar item to the Outline widget.

Signed-off-by: fangnx <fangnaxin@gmail.com>
  • Loading branch information
fangnx authored and fangnx committed Jul 12, 2019
1 parent b0a2d87 commit 2a45799
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 5 deletions.
38 changes: 38 additions & 0 deletions packages/core/src/browser/tree/tree-expansion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export interface TreeExpansionService extends Disposable {
* Return true if a node has been collapsed; otherwise false.
*/
collapseAll(node: Readonly<CompositeTreeNode>): Promise<boolean>;

// collapseAllWithoutSelection(raw: CompositeTreeNode): Promise<boolean>
// readonly onCollapseAllFinished: Event<Readonly<ExpandableTreeNode>>;

/**
* If the given node is invalid then does nothing.
* If the given node is collapsed then expand it; otherwise collapse it.
Expand Down Expand Up @@ -83,6 +87,7 @@ export class TreeExpansionServiceImpl implements TreeExpansionService {

@inject(Tree) protected readonly tree: Tree;
protected readonly onExpansionChangedEmitter = new Emitter<ExpandableTreeNode>();
// protected readonly onCollapseAllFinishedEmitter = new Emitter<ExpandableTreeNode>();

@postConstruct()
protected init(): void {
Expand All @@ -97,16 +102,25 @@ export class TreeExpansionServiceImpl implements TreeExpansionService {

dispose() {
this.onExpansionChangedEmitter.dispose();
// this.onCollapseAllFinishedEmitter.dispose();
}

get onExpansionChanged(): Event<ExpandableTreeNode> {
return this.onExpansionChangedEmitter.event;
}

// get onCollapseAllFinished(): Event<ExpandableTreeNode> {
// 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<boolean> {
const node = this.tree.validateNode(raw);
if (ExpandableTreeNode.isCollapsed(node)) {
Expand All @@ -132,6 +146,11 @@ export class TreeExpansionServiceImpl implements TreeExpansionService {
return this.doCollapseAll(node);
}

// async collapseAllWithoutSelection(raw: CompositeTreeNode): Promise<boolean> {
// const node = this.tree.validateNode(raw);
// return this.doCollapseAllWithoutSelection(node);
// }

protected doCollapseAll(node: TreeNode | undefined): boolean {
let result = false;
if (CompositeTreeNode.is(node)) {
Expand All @@ -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;
Expand All @@ -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<void> {
if (node.expanded) {
await this.collapseNode(node);
Expand Down
56 changes: 54 additions & 2 deletions packages/outline-view/src/browser/outline-view-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OutlineViewWidget> implements FrontendApplicationContribution {
export class OutlineViewContribution extends AbstractViewContribution<OutlineViewWidget> implements FrontendApplicationContribution, TabBarToolbarContribution {

constructor() {
super({
Expand All @@ -40,4 +51,45 @@ export class OutlineViewContribution extends AbstractViewContribution<OutlineVie
async initializeLayout(app: FrontendApplication): Promise<void> {
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<void> {
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<T>(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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand All @@ -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 {
Expand All @@ -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);
Expand Down
47 changes: 47 additions & 0 deletions packages/outline-view/src/browser/outline-view-tree.ts
Original file line number Diff line number Diff line change
@@ -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<CompositeTreeNode>): Promise<boolean> {
const node = raw || this.selectedNodes[0];
if (CompositeTreeNode.is(node)) {
return await this.expansionService.collapseAll(node);
}
return false;
}
}
20 changes: 18 additions & 2 deletions packages/outline-view/src/browser/outline-view-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand All @@ -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);
Expand All @@ -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 = {
Expand Down

0 comments on commit 2a45799

Please sign in to comment.