diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ab557a257a3b..02098fe0ec9e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Breaking changes: - `HostedPluginSupport` is refactored to support multiple `PluginManagerExt` properly - [plugin] added support of `workspaceContains` activation events [#5649](https://github.com/theia-ide/theia/pull/5649) - [plugin] activate dependencies before activating a plugin [#5661](https://github.com/theia-ide/theia/pull/5661) +- [outline] added `OutlineViewTreeModel` for the outline view tree widget ## v0.8.0 diff --git a/packages/core/src/browser/tree/tree-model.ts b/packages/core/src/browser/tree/tree-model.ts index 6bb58c8d51079..76149e37aa46e 100644 --- a/packages/core/src/browser/tree/tree-model.ts +++ b/packages/core/src/browser/tree/tree-model.ts @@ -157,11 +157,7 @@ export class TreeModelImpl implements TreeModel, SelectionProvider { this.fireChanged(); - if (!node.expanded && [...this.selectedNodes].some(selectedNode => CompositeTreeNode.isAncestor(node, selectedNode))) { - if (SelectableTreeNode.isVisible(node)) { - this.selectNode(node); - } - } + this.selectOnExpansion(node); })); this.toDispose.push(this.onOpenNodeEmitter); @@ -173,6 +169,14 @@ export class TreeModelImpl implements TreeModel, SelectionProvider): void { + if (!node.expanded && [...this.selectedNodes].some(selectedNode => CompositeTreeNode.isAncestor(node, selectedNode))) { + if (SelectableTreeNode.isVisible(node)) { + this.selectNode(node); + } + } + } + get root() { return this.tree.root; } diff --git a/packages/outline-view/src/browser/outline-view-contribution.ts b/packages/outline-view/src/browser/outline-view-contribution.ts index f22c44f16581c..18714bc5d29a6 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,40 @@ 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. + */ + protected async collapseAllItems(): Promise { + const { model } = await this.widget; + const root = model.root; + if (CompositeTreeNode.is(root)) { + model.collapseAll(root); + } + } + + 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..c5cb45c6171bd --- /dev/null +++ b/packages/outline-view/src/browser/outline-view-tree.ts @@ -0,0 +1,36 @@ +/******************************************************************************** + * 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 } from 'inversify'; +import { CompositeTreeNode, TreeModelImpl, TreeExpansionService, ExpandableTreeNode } from '@theia/core/lib/browser'; + +@injectable() +export class OutlineViewTreeModel extends TreeModelImpl { + + @inject(TreeExpansionService) protected readonly expansionService: TreeExpansionService; + + protected selectOnExpansion(node: Readonly): void { + // Do not select anything in order to preserve focus on the editor. + } + + async collapseAll(raw?: Readonly): Promise { + const node = raw || this.selectedNodes[0]; + if (CompositeTreeNode.is(node)) { + return 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..4bd5ad89aba3a 100644 --- a/packages/outline-view/src/browser/outline-view-widget.tsx +++ b/packages/outline-view/src/browser/outline-view-widget.tsx @@ -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);