deploy: current vibn theia state
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled

Made-with: Cursor
This commit is contained in:
2026-02-27 12:01:08 -08:00
commit 8bb5110148
3782 changed files with 640947 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: [
'../../configs/build.eslintrc.json'
],
parserOptions: {
tsconfigRootDir: __dirname,
project: 'tsconfig.json'
}
};

View File

@@ -0,0 +1,31 @@
<div align='center'>
<br />
<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />
<h2>ECLIPSE THEIA - OUTLINE-VIEW EXTENSION</h2>
<hr />
</div>
## Description
The `@theia/outline-view` extension contributes the code outline tree based on a document's symbols.
## Additional Information
- [API documentation for `@theia/outline-view`](https://eclipse-theia.github.io/theia/docs/next/modules/_theia_outline-view.html)
- [Theia - GitHub](https://github.com/eclipse-theia/theia)
- [Theia - Website](https://theia-ide.org/)
## License
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
## Trademark
"Theia" is a trademark of the Eclipse Foundation
<https://www.eclipse.org/theia>

View File

@@ -0,0 +1,48 @@
{
"name": "@theia/outline-view",
"version": "1.68.0",
"description": "Theia - Outline View Extension",
"dependencies": {
"@theia/core": "1.68.0",
"tslib": "^2.6.2"
},
"publishConfig": {
"access": "public"
},
"theiaExtensions": [
{
"frontend": "lib/browser/outline-view-frontend-module"
}
],
"keywords": [
"theia-extension"
],
"license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
"repository": {
"type": "git",
"url": "https://github.com/eclipse-theia/theia.git"
},
"bugs": {
"url": "https://github.com/eclipse-theia/theia/issues"
},
"homepage": "https://github.com/eclipse-theia/theia",
"files": [
"lib",
"src"
],
"scripts": {
"build": "theiaext build",
"clean": "theiaext clean",
"compile": "theiaext compile",
"lint": "theiaext lint",
"test": "theiaext test",
"watch": "theiaext watch"
},
"devDependencies": {
"@theia/ext-scripts": "1.68.0"
},
"nyc": {
"extends": "../../configs/nyc.json"
},
"gitHead": "21358137e41342742707f660b8e222f940a27652"
}

View File

@@ -0,0 +1,18 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox 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-only WITH Classpath-exception-2.0
// *****************************************************************************
export * from './outline-view-widget';
export * from './outline-view-frontend-module';

View File

@@ -0,0 +1,233 @@
// *****************************************************************************
// Copyright (C) 2019 TypeFox 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-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import { LabelProvider, BreadcrumbsService, Widget, TreeNode, OpenerService, open, SelectableTreeNode, BreadcrumbsContribution, Breadcrumb } from '@theia/core/lib/browser';
import URI from '@theia/core/lib/common/uri';
import { OutlineViewService } from './outline-view-service';
import { OutlineSymbolInformationNode, OutlineViewWidget } from './outline-view-widget';
import { Disposable, DisposableCollection, Emitter, Event, UriSelection } from '@theia/core/lib/common';
export const OutlineBreadcrumbType = Symbol('OutlineBreadcrumb');
export const BreadcrumbPopupOutlineViewFactory = Symbol('BreadcrumbPopupOutlineViewFactory');
export const OUTLINE_BREADCRUMB_CONTAINER_CLASS = 'outline-element';
export interface BreadcrumbPopupOutlineViewFactory {
(): BreadcrumbPopupOutlineView;
}
export class BreadcrumbPopupOutlineView extends OutlineViewWidget {
@inject(OpenerService) protected readonly openerService: OpenerService;
@inject(OutlineViewService)
protected readonly outlineViewService: OutlineViewService;
protected override tapNode(node?: TreeNode): void {
if (UriSelection.is(node) && OutlineSymbolInformationNode.hasRange(node)) {
open(this.openerService, node.uri, { selection: node.range });
} else {
this.outlineViewService.didTapNode(node as OutlineSymbolInformationNode);
super.tapNode(node);
}
}
cloneState(roots: OutlineSymbolInformationNode[]): void {
const nodes = this.reconcileTreeState(roots);
const root = this.getRoot(nodes);
this.model.root = this.inflateFromStorage(this.deflateForStorage(root));
}
}
@injectable()
export class OutlineBreadcrumbsContribution implements BreadcrumbsContribution {
@inject(LabelProvider)
protected readonly labelProvider: LabelProvider;
@inject(OutlineViewService)
protected readonly outlineViewService: OutlineViewService;
@inject(BreadcrumbsService)
protected readonly breadcrumbsService: BreadcrumbsService;
@inject(BreadcrumbPopupOutlineViewFactory)
protected readonly outlineFactory: BreadcrumbPopupOutlineViewFactory;
protected outlineView: BreadcrumbPopupOutlineView;
readonly type = OutlineBreadcrumbType;
readonly priority: number = 200;
protected currentUri: URI | undefined = undefined;
protected currentBreadcrumbs: OutlineBreadcrumb[] = [];
protected roots: OutlineSymbolInformationNode[] = [];
protected readonly onDidChangeBreadcrumbsEmitter = new Emitter<URI>();
get onDidChangeBreadcrumbs(): Event<URI> {
return this.onDidChangeBreadcrumbsEmitter.event;
}
@postConstruct()
init(): void {
this.outlineView = this.outlineFactory();
this.outlineView.node.style.height = 'auto';
this.outlineView.node.style.maxHeight = '200px';
this.outlineViewService.onDidChangeOutline(roots => {
if (roots.length > 0) {
this.roots = roots;
const first = roots[0];
if (UriSelection.is(first)) {
this.updateOutlineItems(first.uri, this.findSelectedNode(roots));
}
} else {
this.currentBreadcrumbs = [];
this.roots = [];
}
});
this.outlineViewService.onDidSelect(node => {
if (UriSelection.is(node)) {
this.updateOutlineItems(node.uri, node);
}
});
}
protected async updateOutlineItems(uri: URI, selectedNode: OutlineSymbolInformationNode | undefined): Promise<void> {
this.currentUri = uri;
const outlinePath = this.toOutlinePath(selectedNode);
if (outlinePath && selectedNode) {
this.currentBreadcrumbs = outlinePath.map((node, index) =>
new OutlineBreadcrumb(
node,
uri,
index.toString(),
this.labelProvider.getName(node),
'symbol-icon-center codicon codicon-symbol-' + node.iconClass,
OUTLINE_BREADCRUMB_CONTAINER_CLASS,
)
);
if (selectedNode.children && selectedNode.children.length > 0) {
this.currentBreadcrumbs.push(new OutlineBreadcrumb(
selectedNode.children as OutlineSymbolInformationNode[],
uri,
this.currentBreadcrumbs.length.toString(),
'…',
'',
OUTLINE_BREADCRUMB_CONTAINER_CLASS,
));
}
} else {
this.currentBreadcrumbs = [];
if (this.roots) {
this.currentBreadcrumbs.push(new OutlineBreadcrumb(
this.roots,
uri,
this.currentBreadcrumbs.length.toString(),
'…',
'',
OUTLINE_BREADCRUMB_CONTAINER_CLASS
));
}
}
this.onDidChangeBreadcrumbsEmitter.fire(uri);
}
async computeBreadcrumbs(uri: URI): Promise<Breadcrumb[]> {
if (this.currentUri && uri.toString() === this.currentUri.toString()) {
return this.currentBreadcrumbs;
}
return [];
}
async attachPopupContent(breadcrumb: Breadcrumb, parent: HTMLElement): Promise<Disposable | undefined> {
if (!OutlineBreadcrumb.is(breadcrumb)) {
return undefined;
}
const node = Array.isArray(breadcrumb.node) ? breadcrumb.node[0] : breadcrumb.node;
if (!node.parent) {
return undefined;
}
const siblings = node.parent.children.filter((child): child is OutlineSymbolInformationNode => OutlineSymbolInformationNode.is(child));
const toDisposeOnHide = new DisposableCollection();
this.outlineView.cloneState(siblings);
this.outlineView.model.selectNode(node);
this.outlineView.model.collapseAll();
Widget.attach(this.outlineView, parent);
this.outlineView.activate();
toDisposeOnHide.pushAll([
this.outlineView.model.onExpansionChanged(expandedNode => SelectableTreeNode.is(expandedNode) && this.outlineView.model.selectNode(expandedNode)),
Disposable.create(() => {
this.outlineView.model.root = undefined;
Widget.detach(this.outlineView);
}),
]);
return toDisposeOnHide;
}
/**
* Returns the path of the given outline node.
*/
protected toOutlinePath(node: OutlineSymbolInformationNode | undefined, path: OutlineSymbolInformationNode[] = []): OutlineSymbolInformationNode[] | undefined {
if (!node) { return undefined; }
if (node.id === 'outline-view-root') { return path; }
if (node.parent) {
return this.toOutlinePath(node.parent as OutlineSymbolInformationNode, [node, ...path]);
} else {
return [node, ...path];
}
}
/**
* Find the node that is selected. Returns after the first match.
*/
protected findSelectedNode(roots: OutlineSymbolInformationNode[]): OutlineSymbolInformationNode | undefined {
const result = roots.find(node => node.selected);
if (result) {
return result;
}
for (const node of roots) {
const result2 = this.findSelectedNode(node.children.map(child => child as OutlineSymbolInformationNode));
if (result2) {
return result2;
}
}
}
}
export class OutlineBreadcrumb implements Breadcrumb {
constructor(
readonly node: OutlineSymbolInformationNode | OutlineSymbolInformationNode[],
readonly uri: URI,
readonly index: string,
readonly label: string,
readonly iconClass: string,
readonly containerClass: string,
) { }
get id(): string {
return this.type.toString() + '_' + this.uri.toString() + '_' + this.index;
}
get type(): symbol {
return OutlineBreadcrumbType;
}
get longLabel(): string {
return this.label;
}
}
export namespace OutlineBreadcrumb {
export function is(breadcrumb: Breadcrumb): breadcrumb is OutlineBreadcrumb {
return 'node' in breadcrumb && 'uri' in breadcrumb;
}
}

View File

@@ -0,0 +1,36 @@
// *****************************************************************************
// Copyright (C) 2018 Redhat, 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-only WITH Classpath-exception-2.0
// *****************************************************************************
import { inject, injectable, named } from '@theia/core/shared/inversify';
import { ContributionProvider } from '@theia/core/lib/common/contribution-provider';
import { TreeDecorator, AbstractTreeDecoratorService } from '@theia/core/lib/browser/tree/tree-decorator';
/**
* Symbol for all decorators that would like to contribute into the outline.
*/
export const OutlineTreeDecorator = Symbol('OutlineTreeDecorator');
/**
* Decorator service for the outline.
*/
@injectable()
export class OutlineDecoratorService extends AbstractTreeDecoratorService {
constructor(@inject(ContributionProvider) @named(OutlineTreeDecorator) protected readonly contributions: ContributionProvider<TreeDecorator>) {
super(contributions.getContributions());
}
}

View File

@@ -0,0 +1,132 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox 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-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable } from '@theia/core/shared/inversify';
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
import { Command, CommandRegistry } from '@theia/core/lib/common/command';
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { codicon, Widget } from '@theia/core/lib/browser/widgets';
import { OutlineViewWidget } from './outline-view-widget';
import { CompositeTreeNode } from '@theia/core/lib/browser/tree';
import { OS } from '@theia/core/lib/common/os';
import { nls } from '@theia/core/lib/common/nls';
export const OUTLINE_WIDGET_FACTORY_ID = 'outline-view';
/**
* Collection of `outline-view` commands.
*/
export namespace OutlineViewCommands {
/**
* Command which collapses all nodes from the `outline-view` tree.
*/
export const COLLAPSE_ALL: Command = {
id: 'outlineView.collapse.all',
iconClass: codicon('collapse-all')
};
/**
* Command which expands all nodes from the `outline-view` tree.
*/
export const EXPAND_ALL: Command = {
id: 'outlineView.expand.all',
iconClass: codicon('expand-all')
};
}
@injectable()
export class OutlineViewContribution extends AbstractViewContribution<OutlineViewWidget> implements FrontendApplicationContribution, TabBarToolbarContribution {
constructor() {
super({
widgetId: OUTLINE_WIDGET_FACTORY_ID,
widgetName: OutlineViewWidget.LABEL,
defaultWidgetOptions: {
area: 'right',
rank: 500
},
toggleCommandId: 'outlineView:toggle',
toggleKeybinding: OS.type() !== OS.Type.Linux
? 'ctrlcmd+shift+i'
: undefined
});
}
async initializeLayout(app: FrontendApplication): Promise<void> {
await this.openView();
}
override registerCommands(commands: CommandRegistry): void {
super.registerCommands(commands);
commands.registerCommand(OutlineViewCommands.COLLAPSE_ALL, {
isEnabled: w => this.withWidget(w, () => true),
isVisible: w => this.withWidget(w, widget => !widget.model.areNodesCollapsed()),
execute: () => this.collapseAllItems()
});
commands.registerCommand(OutlineViewCommands.EXPAND_ALL, {
isEnabled: w => this.withWidget(w, () => true),
isVisible: w => this.withWidget(w, widget => widget.model.areNodesCollapsed()),
execute: () => this.expandAllItems()
});
}
async registerToolbarItems(toolbar: TabBarToolbarRegistry): Promise<void> {
const widget = await this.widget;
const onDidChange = widget.onDidUpdate;
toolbar.registerItem({
id: OutlineViewCommands.COLLAPSE_ALL.id,
command: OutlineViewCommands.COLLAPSE_ALL.id,
tooltip: nls.localizeByDefault('Collapse All'),
priority: 0,
onDidChange
});
toolbar.registerItem({
id: OutlineViewCommands.EXPAND_ALL.id,
command: OutlineViewCommands.EXPAND_ALL.id,
tooltip: nls.localizeByDefault('Expand All'),
priority: 0,
onDidChange
});
}
/**
* Collapse all nodes in the outline view tree.
*/
protected async collapseAllItems(): Promise<void> {
const { model } = await this.widget;
const root = model.root;
if (CompositeTreeNode.is(root)) {
model.collapseAll(root);
}
}
protected async expandAllItems(): Promise<void> {
const { model } = await this.widget;
model.expandAll(model.root);
}
/**
* Determine if the current widget is the `outline-view`.
*/
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;
}
}

View File

@@ -0,0 +1,84 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox 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-only WITH Classpath-exception-2.0
// *****************************************************************************
import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
import { OutlineViewService } from './outline-view-service';
import { OutlineViewContribution } from './outline-view-contribution';
import { WidgetFactory } from '@theia/core/lib/browser/widget-manager';
import {
FrontendApplicationContribution,
createTreeContainer,
bindViewContribution,
TreeProps,
defaultTreeProps,
BreadcrumbsContribution
} 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-model';
import { BreadcrumbPopupOutlineView, BreadcrumbPopupOutlineViewFactory, OutlineBreadcrumbsContribution } from './outline-breadcrumbs-contribution';
export default new ContainerModule(bind => {
bind(OutlineViewWidgetFactory).toFactory(ctx =>
() => createOutlineViewWidget(ctx.container)
);
bind(BreadcrumbPopupOutlineViewFactory).toFactory(({ container }) => () => {
const child = createOutlineViewWidgetContainer(container);
child.rebind(OutlineViewWidget).to(BreadcrumbPopupOutlineView);
child.rebind(TreeProps).toConstantValue({ ...defaultTreeProps, expandOnlyOnExpansionToggleClick: true, search: false, virtualized: false });
return child.get(OutlineViewWidget);
});
bind(OutlineViewService).toSelf().inSingletonScope();
bind(WidgetFactory).toService(OutlineViewService);
bindViewContribution(bind, OutlineViewContribution);
bind(FrontendApplicationContribution).toService(OutlineViewContribution);
bind(TabBarToolbarContribution).toService(OutlineViewContribution);
bind(OutlineBreadcrumbsContribution).toSelf().inSingletonScope();
bind(BreadcrumbsContribution).toService(OutlineBreadcrumbsContribution);
});
function createOutlineViewWidgetContainer(parent: interfaces.Container): interfaces.Container {
const child = createTreeContainer(parent, {
props: { expandOnlyOnExpansionToggleClick: true, search: true },
widget: OutlineViewWidget,
model: OutlineViewTreeModel,
decoratorService: OutlineDecoratorService,
});
bindContributionProvider(child, OutlineTreeDecorator);
return child;
}
/**
* Create an `OutlineViewWidget`.
* - The creation of the `OutlineViewWidget` includes:
* - The creation of the tree widget itself with it's own customized props.
* - The binding of necessary components into the container.
* @param parent the Inversify container.
*
* @returns the `OutlineViewWidget`.
*/
function createOutlineViewWidget(parent: interfaces.Container): OutlineViewWidget {
const child = createOutlineViewWidgetContainer(parent);
return child.get(OutlineViewWidget);
}

View File

@@ -0,0 +1,91 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox 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-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject } from '@theia/core/shared/inversify';
import { Event, Emitter, DisposableCollection } from '@theia/core';
import { WidgetFactory } from '@theia/core/lib/browser';
import { OutlineViewWidget, OutlineViewWidgetFactory, OutlineSymbolInformationNode } from './outline-view-widget';
import { Widget } from '@theia/core/shared/@lumino/widgets';
@injectable()
export class OutlineViewService implements WidgetFactory {
id = 'outline-view';
protected widget?: OutlineViewWidget;
protected readonly onDidChangeOutlineEmitter = new Emitter<OutlineSymbolInformationNode[]>();
protected readonly onDidChangeOpenStateEmitter = new Emitter<boolean>();
protected readonly onDidSelectEmitter = new Emitter<OutlineSymbolInformationNode>();
protected readonly onDidOpenEmitter = new Emitter<OutlineSymbolInformationNode>();
protected readonly onDidTapNodeEmitter = new Emitter<OutlineSymbolInformationNode>();
constructor(@inject(OutlineViewWidgetFactory) protected factory: OutlineViewWidgetFactory) { }
get onDidSelect(): Event<OutlineSymbolInformationNode> {
return this.onDidSelectEmitter.event;
}
get onDidOpen(): Event<OutlineSymbolInformationNode> {
return this.onDidOpenEmitter.event;
}
get onDidChangeOutline(): Event<OutlineSymbolInformationNode[]> {
return this.onDidChangeOutlineEmitter.event;
}
get onDidChangeOpenState(): Event<boolean> {
return this.onDidChangeOpenStateEmitter.event;
}
get onDidTapNode(): Event<OutlineSymbolInformationNode> {
return this.onDidTapNodeEmitter.event;
}
get open(): boolean {
return this.widget !== undefined && this.widget.isVisible;
}
didTapNode(node: OutlineSymbolInformationNode): void {
this.onDidTapNodeEmitter.fire(node);
}
/**
* Publish the collection of outline view symbols.
* - Publishing includes setting the `OutlineViewWidget` tree with symbol information.
* @param roots the list of outline symbol information nodes.
*/
publish(roots: OutlineSymbolInformationNode[]): void {
if (this.widget) {
this.widget.setOutlineTree(roots);
}
// onDidChangeOutline needs to be fired even when the outline view widget is closed
// in order to update breadcrumbs.
this.onDidChangeOutlineEmitter.fire(roots);
}
createWidget(): Promise<Widget> {
this.widget = this.factory();
const disposables = new DisposableCollection();
disposables.push(this.widget.onDidChangeOpenStateEmitter.event(open => this.onDidChangeOpenStateEmitter.fire(open)));
disposables.push(this.widget.model.onOpenNode(node => this.onDidOpenEmitter.fire(node as OutlineSymbolInformationNode)));
disposables.push(this.widget.model.onSelectionChanged(selection => this.onDidSelectEmitter.fire(selection[0] as OutlineSymbolInformationNode)));
this.widget.disposed.connect(() => {
this.widget = undefined;
disposables.dispose();
});
return Promise.resolve(this.widget);
}
}

View File

@@ -0,0 +1,77 @@
// *****************************************************************************
// 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-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable } from '@theia/core/shared/inversify';
import { CompositeTreeNode, TreeModelImpl, ExpandableTreeNode, TreeNode } from '@theia/core/lib/browser';
@injectable()
export class OutlineViewTreeModel extends TreeModelImpl {
/**
* Handle the expansion of the tree node.
* - The method is a no-op in order to preserve focus on the editor
* after attempting to perform a `collapse-all`.
* @param node the expandable tree node.
*/
protected override handleExpansion(node: Readonly<ExpandableTreeNode>): void {
// no-op
}
override async collapseAll(raw?: Readonly<CompositeTreeNode>): Promise<boolean> {
const node = raw || this.getFocusedNode();
if (CompositeTreeNode.is(node)) {
return this.expansionService.collapseAll(node);
}
return false;
}
/**
* The default behavior of `openNode` calls `doOpenNode` which by default
* toggles the expansion of the node. Overriding to prevent expansion, but
* allow for the `onOpenNode` event to still fire on a double-click event.
*/
override openNode(raw?: TreeNode | undefined): void {
const node = raw || this.getFocusedNode();
if (node) {
this.onOpenNodeEmitter.fire(node);
}
}
expandAll(raw?: TreeNode): void {
if (CompositeTreeNode.is(raw)) {
for (const child of raw.children) {
if (ExpandableTreeNode.is(child)) {
this.expandAll(child);
}
}
}
if (ExpandableTreeNode.is(raw)) {
this.expandNode(raw);
}
}
areNodesCollapsed(): boolean {
if (CompositeTreeNode.is(this.root)) {
for (const child of this.root.children) {
if (!ExpandableTreeNode.isCollapsed(child)) {
return false;
}
}
}
return true;
}
}

View File

@@ -0,0 +1,212 @@
// *****************************************************************************
// Copyright (C) 2017-2018 TypeFox 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-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import {
TreeWidget,
TreeNode,
NodeProps,
SelectableTreeNode,
CompositeTreeNode,
TreeProps,
ContextMenuRenderer,
TreeModel,
ExpandableTreeNode,
codicon
} from '@theia/core/lib/browser';
import { OutlineViewTreeModel } from './outline-view-tree-model';
import { Message } from '@theia/core/shared/@lumino/messaging';
import { Emitter, Event, isObject, Mutable, UriSelection } from '@theia/core';
import * as React from '@theia/core/shared/react';
import { Range } from '@theia/core/shared/vscode-languageserver-protocol';
import URI from '@theia/core/lib/common/uri';
import { nls } from '@theia/core/lib/common/nls';
/**
* Representation of an outline symbol information node.
*/
export interface OutlineSymbolInformationNode extends CompositeTreeNode, SelectableTreeNode, ExpandableTreeNode {
/**
* The `iconClass` for the given tree node.
*/
iconClass: string;
}
/**
* Collection of outline symbol information node functions.
*/
export namespace OutlineSymbolInformationNode {
/**
* Determine if the given tree node is an `OutlineSymbolInformationNode`.
* - The tree node is an `OutlineSymbolInformationNode` if:
* - The node exists.
* - The node is selectable.
* - The node contains a defined `iconClass` property.
* @param node the tree node.
*
* @returns `true` if the given node is an `OutlineSymbolInformationNode`.
*/
export function is(node: TreeNode): node is OutlineSymbolInformationNode {
return !!node && SelectableTreeNode.is(node) && 'iconClass' in node;
}
export function hasRange(node: unknown): node is { range: Range } {
return isObject<{ range: Range }>(node) && Range.is(node.range);
}
}
export type OutlineViewWidgetFactory = () => OutlineViewWidget;
export const OutlineViewWidgetFactory = Symbol('OutlineViewWidgetFactory');
@injectable()
export class OutlineViewWidget extends TreeWidget {
static LABEL = nls.localizeByDefault('Outline');
readonly onDidChangeOpenStateEmitter = new Emitter<boolean>();
protected readonly onDidUpdateEmitter = new Emitter<void>();
readonly onDidUpdate: Event<void> = this.onDidUpdateEmitter.event;
constructor(
@inject(TreeProps) treeProps: TreeProps,
@inject(OutlineViewTreeModel) override readonly model: OutlineViewTreeModel,
@inject(ContextMenuRenderer) contextMenuRenderer: ContextMenuRenderer
) {
super(treeProps, model, contextMenuRenderer);
this.id = 'outline-view';
this.title.label = OutlineViewWidget.LABEL;
this.title.caption = OutlineViewWidget.LABEL;
this.title.closable = true;
this.title.iconClass = codicon('symbol-class');
this.addClass('theia-outline-view');
}
@postConstruct()
protected override init(): void {
super.init();
this.toDispose.push(this.model.onExpansionChanged(() => this.onDidUpdateEmitter.fire()));
}
/**
* Set the outline tree with the list of `OutlineSymbolInformationNode`.
* @param roots the list of `OutlineSymbolInformationNode`.
*/
public setOutlineTree(roots: OutlineSymbolInformationNode[]): void {
// Gather the list of available nodes.
const nodes = this.reconcileTreeState(roots);
// Update the model root node, appending the outline symbol information nodes as children.
this.model.root = this.getRoot(nodes);
}
protected getRoot(children: TreeNode[]): CompositeTreeNode {
return {
id: 'outline-view-root',
name: OutlineViewWidget.LABEL,
visible: false,
children,
parent: undefined
};
}
/**
* Reconcile the outline tree state, gathering all available nodes.
* @param nodes the list of `TreeNode`.
*
* @returns the list of tree nodes.
*/
protected reconcileTreeState(nodes: TreeNode[]): TreeNode[] {
nodes.forEach(node => {
if (OutlineSymbolInformationNode.is(node)) {
const treeNode = this.model.getNode(node.id);
if (treeNode && OutlineSymbolInformationNode.is(treeNode)) {
treeNode.expanded = node.expanded;
treeNode.selected = node.selected;
}
this.reconcileTreeState(Array.from(node.children));
}
});
return nodes;
}
protected override onAfterHide(msg: Message): void {
super.onAfterHide(msg);
this.onDidChangeOpenStateEmitter.fire(false);
}
protected override onAfterShow(msg: Message): void {
super.onAfterShow(msg);
this.onDidChangeOpenStateEmitter.fire(true);
}
override renderIcon(node: TreeNode, props: NodeProps): React.ReactNode {
if (OutlineSymbolInformationNode.is(node)) {
return <div className={'symbol-icon-center codicon codicon-symbol-' + node.iconClass}></div>;
}
return undefined;
}
protected override createNodeAttributes(node: TreeNode, props: NodeProps): React.Attributes & React.HTMLAttributes<HTMLElement> {
const elementAttrs = super.createNodeAttributes(node, props);
return {
...elementAttrs,
title: this.getNodeTooltip(node)
};
}
/**
* Get the tooltip for the given tree node.
* - The tooltip is discovered when hovering over a tree node.
* - If available, the tooltip is the concatenation of the node name, and it's type.
* @param node the tree node.
*
* @returns the tooltip for the tree node if available, else `undefined`.
*/
protected getNodeTooltip(node: TreeNode): string | undefined {
if (OutlineSymbolInformationNode.is(node)) {
return node.name + ` (${node.iconClass})`;
}
return undefined;
}
protected override isExpandable(node: TreeNode): node is ExpandableTreeNode {
return OutlineSymbolInformationNode.is(node) && node.children.length > 0;
}
protected override renderTree(model: TreeModel): React.ReactNode {
if (CompositeTreeNode.is(this.model.root) && !this.model.root.children.length) {
return <div className='theia-widget-noInfo no-outline'>{nls.localizeByDefault('The active editor cannot provide outline information.')}</div>;
}
return super.renderTree(model);
}
protected override deflateForStorage(node: TreeNode): object {
const deflated = super.deflateForStorage(node) as { uri: string };
if (UriSelection.is(node)) {
deflated.uri = node.uri.toString();
}
return deflated;
}
protected override inflateFromStorage(node: any, parent?: TreeNode): TreeNode { /* eslint-disable-line @typescript-eslint/no-explicit-any */
const inflated = super.inflateFromStorage(node, parent) as Mutable<TreeNode & UriSelection>;
if (node && 'uri' in node && typeof node.uri === 'string') {
inflated.uri = new URI(node.uri);
}
return inflated;
}
}

View File

@@ -0,0 +1,24 @@
/********************************************************************************
* Copyright (C) 2017-2018 TypeFox 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-only WITH Classpath-exception-2.0
********************************************************************************/
.no-outline {
color: var(--theia-foreground);
text-align: left;
}
.theia-side-panel .no-outline {
margin-left: 9px;
}

View File

@@ -0,0 +1,28 @@
// *****************************************************************************
// Copyright (C) 2017 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-only WITH Classpath-exception-2.0
// *****************************************************************************
/* note: this bogus test file is required so that
we are able to run mocha unit tests on this
package, without having any actual unit tests in it.
This way a coverage report will be generated,
showing 0% coverage, instead of no report.
This file can be removed once we have real unit
tests in place. */
describe('outline view package', () => {
it('should support code coverage statistics', () => true);
});

View File

@@ -0,0 +1,16 @@
{
"extends": "../../configs/base.tsconfig",
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "lib"
},
"include": [
"src"
],
"references": [
{
"path": "../core"
}
]
}