// ***************************************************************************** // 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, unmanaged } from '@theia/core/shared/inversify'; import { TreeImpl, CompositeTreeNode, TreeNode, SelectableTreeNode, ExpandableTreeNode } from '@theia/core/lib/browser'; import { MarkerManager } from './marker-manager'; import { Marker } from '../common/marker'; import { UriSelection } from '@theia/core/lib/common/selection'; import URI from '@theia/core/lib/common/uri'; import { ProblemSelection } from './problem/problem-selection'; import { DiagnosticSeverity } from '@theia/core/shared/vscode-languageserver-protocol'; export const MarkerOptions = Symbol('MarkerOptions'); export interface MarkerOptions { readonly kind: string; } @injectable() export abstract class MarkerTree extends TreeImpl { constructor( @unmanaged() protected readonly markerManager: MarkerManager, @unmanaged() protected readonly markerOptions: MarkerOptions ) { super(); this.toDispose.push(markerManager.onDidChangeMarkers(uri => this.refreshMarkerInfo(uri))); this.root = { visible: false, id: 'theia-' + markerOptions.kind + '-marker-widget', name: 'MarkerTree', kind: markerOptions.kind, children: [], parent: undefined }; } protected async refreshMarkerInfo(uri: URI): Promise { const id = uri.toString(); const existing = this.getNode(id); const markers = this.markerManager.findMarkers({ uri }); if (markers.length <= 0) { if (MarkerInfoNode.is(existing)) { CompositeTreeNode.removeChild(existing.parent, existing); this.removeNode(existing); this.fireChanged(); } return; } const node = MarkerInfoNode.is(existing) ? existing : this.createMarkerInfo(id, uri); this.insertNodeWithMarkers(node, markers); } protected insertNodeWithMarkers(node: MarkerInfoNode, markers: Marker[]): void { CompositeTreeNode.addChild(node.parent, node); const children = this.getMarkerNodes(node, markers); node.numberOfMarkers = markers.length; this.setChildren(node, children); } protected override async resolveChildren(parent: CompositeTreeNode): Promise { if (MarkerRootNode.is(parent)) { const nodes: MarkerInfoNode[] = []; for (const id of this.markerManager.getUris()) { const uri = new URI(id); const existing = this.getNode(id); const markers = this.markerManager.findMarkers({ uri }); const node = MarkerInfoNode.is(existing) ? existing : this.createMarkerInfo(id, uri); node.children = this.getMarkerNodes(node, markers); node.numberOfMarkers = node.children.length; nodes.push(node); } return nodes; } return super.resolveChildren(parent); } protected createMarkerInfo(id: string, uri: URI): MarkerInfoNode { return { children: [], expanded: true, uri, id, parent: this.root as MarkerRootNode, selected: false, numberOfMarkers: 0 }; } protected getMarkerNodes(parent: MarkerInfoNode, markers: Marker[]): MarkerNode[] { return markers.map((marker, index) => this.createMarkerNode(marker, index, parent) ); } protected createMarkerNode(marker: Marker, index: number, parent: MarkerInfoNode): MarkerNode { const id = parent.id + '_' + index; const existing = this.getNode(id); if (MarkerNode.is(existing)) { existing.marker = marker; return existing; } return { id, name: 'marker', parent, selected: false, uri: parent.uri, marker }; } } export interface MarkerNode extends UriSelection, SelectableTreeNode, ProblemSelection { marker: Marker; } export namespace MarkerNode { export function is(node: TreeNode | undefined): node is MarkerNode { return UriSelection.is(node) && SelectableTreeNode.is(node) && ProblemSelection.is(node); } } export interface MarkerInfoNode extends UriSelection, SelectableTreeNode, ExpandableTreeNode { parent: MarkerRootNode; numberOfMarkers: number; severity?: DiagnosticSeverity; } export namespace MarkerInfoNode { export function is(node: unknown): node is MarkerInfoNode { return ExpandableTreeNode.is(node) && UriSelection.is(node) && 'numberOfMarkers' in node; } } export interface MarkerRootNode extends CompositeTreeNode { kind: string; } export namespace MarkerRootNode { export function is(node: TreeNode | undefined): node is MarkerRootNode { return CompositeTreeNode.is(node) && 'kind' in node; } }