From 5996552ee305996d28e07269e6707f30c6707248 Mon Sep 17 00:00:00 2001 From: Alexander Kozinko Date: Sun, 27 Jun 2021 02:11:15 +0300 Subject: [PATCH] [VSCode] Enable problem decorator in TreeView Signed-off-by: Alexander Kozinko --- .../browser/plugin-ext-frontend-module.ts | 13 ++- .../view/tree-view-decorator-service.ts | 35 +++++++ .../view/tree-view-problem-decorator.ts | 96 +++++++++++++++++++ .../main/browser/view/tree-view-widget.tsx | 31 ++++++ 4 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 packages/plugin-ext/src/main/browser/view/tree-view-decorator-service.ts create mode 100644 packages/plugin-ext/src/main/browser/view/tree-view-problem-decorator.ts diff --git a/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts b/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts index 2082841d0c160..25bb200b982d5 100644 --- a/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts +++ b/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts @@ -21,7 +21,7 @@ import '../../../src/main/browser/style/comments.css'; import { ContainerModule } from '@theia/core/shared/inversify'; import { FrontendApplicationContribution, WidgetFactory, bindViewContribution, - ViewContainerIdentifier, ViewContainer, createTreeContainer, TreeImpl, TreeWidget, TreeModelImpl, LabelProviderContribution + ViewContainerIdentifier, ViewContainer, createTreeContainer, TreeImpl, TreeWidget, TreeModelImpl, LabelProviderContribution, TreeDecoratorService } from '@theia/core/lib/browser'; import { MaybePromise, CommandContribution, ResourceResolver, bindContributionProvider } from '@theia/core/lib/common'; import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging'; @@ -75,6 +75,8 @@ import { CustomEditorWidgetFactory } from '../browser/custom-editors/custom-edit import { CustomEditorWidget } from './custom-editors/custom-editor-widget'; import { CustomEditorService } from './custom-editors/custom-editor-service'; import { UndoRedoService } from './custom-editors/undo-redo-service'; +import { TreeViewDecorator, TreeViewDecoratorService } from './view/treeview-decorator-service'; +import { TreeViewProblemDecorator } from './view/treeview-problem-decorator'; export default new ContainerModule((bind, unbind, isBound, rebind) => { @@ -155,10 +157,19 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { child.rebind(TreeModelImpl).toService(PluginTreeModel); child.bind(TreeViewWidget).toSelf(); child.rebind(TreeWidget).toService(TreeViewWidget); + + child.bind(TreeViewDecoratorService).toSelf().inSingletonScope(); + child.rebind(TreeDecoratorService).toService(TreeViewDecoratorService); + + bindContributionProvider(child, TreeViewDecorator); + return child.get(TreeWidget); } })).inSingletonScope(); + bind(TreeViewProblemDecorator).toSelf().inSingletonScope(); + bind(TreeViewDecorator).toService(TreeViewProblemDecorator); + bindWebviewPreferences(bind); bind(WebviewEnvironment).toSelf().inSingletonScope(); bind(WebviewThemeDataProvider).toSelf().inSingletonScope(); diff --git a/packages/plugin-ext/src/main/browser/view/tree-view-decorator-service.ts b/packages/plugin-ext/src/main/browser/view/tree-view-decorator-service.ts new file mode 100644 index 0000000000000..da821c162fef4 --- /dev/null +++ b/packages/plugin-ext/src/main/browser/view/tree-view-decorator-service.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (C) 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 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 vscode tree views. + */ +export const TreeViewDecorator = Symbol('TreeViewDecorator'); + +/** + * Decorator service for the vscode tree views. + */ +@injectable() +export class TreeViewDecoratorService extends AbstractTreeDecoratorService { + + constructor(@inject(ContributionProvider) @named(TreeViewDecorator) protected readonly contributions: ContributionProvider) { + super(contributions.getContributions()); + } +} diff --git a/packages/plugin-ext/src/main/browser/view/tree-view-problem-decorator.ts b/packages/plugin-ext/src/main/browser/view/tree-view-problem-decorator.ts new file mode 100644 index 0000000000000..61c19bb1c2ea9 --- /dev/null +++ b/packages/plugin-ext/src/main/browser/view/tree-view-problem-decorator.ts @@ -0,0 +1,96 @@ +/******************************************************************************** + * Copyright (C) 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 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { DepthFirstTreeIterator, Tree, TreeDecoration } from '@theia/core/lib/browser'; +import { ProblemDecorator } from '@theia/markers/lib/browser/problem/problem-decorator'; +import { Marker } from '@theia/markers/lib/common/marker'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { Diagnostic } from '@theia/core/shared/vscode-languageserver-protocol'; +import URI from '@theia/core/lib/common/uri'; +import { TreeItem } from '../../../plugin/types-impl'; +import { ProblemManager } from '@theia/markers/lib/browser'; + +@injectable() +export class TreeViewProblemDecorator extends ProblemDecorator { + + constructor(@inject(ProblemManager) protected readonly problemManager: ProblemManager) { + super(problemManager); + } + + protected collectDecorators(tree: Tree): Map { + const result = new Map>(); + + // If the tree root is undefined or the preference for the decorations is disabled, return an empty result map. + if (tree.root === undefined || !this.problemPreferences['problems.decorations.enabled']) { + return new Map(); + } + + const pathToIdMap = new Map(); + + for (const node of new DepthFirstTreeIterator(tree.root)) { + if (this.isTreeItem(node) && node.resourceUri) { + pathToIdMap.set(node.resourceUri.toString(), node.id); + } + } + + const markers = this.appendContainerItemMarkers(tree, this.collectMarkers(tree), pathToIdMap); + + markers.forEach((marker: Marker, uri: string) => { + const nodeId = pathToIdMap.get(uri); + if (nodeId) { + result.set(nodeId, marker); + } + }); + + return new Map(Array.from(result.entries()).map(m => [m[0], this.toDecorator(m[1])])); + } + + protected appendContainerItemMarkers(tree: Tree, markers: Marker[], pathToIdMap: Map): Map> { + const result: Map> = new Map(); + // We traverse up and assign the diagnostic to the container element. + // Just like file based traverse, but use element parent instead. + for (const [uri, marker] of new Map>(markers.map(m => [new URI(m.uri), m])).entries()) { + const uriString = uri.toString(); + result.set(uriString, marker); + const parentNode = tree.getNode(pathToIdMap.get(uriString))?.parent; + if (this.isTreeItem(parentNode) && parentNode.resourceUri) { + let parentUri: URI | undefined = new URI(parentNode.resourceUri); + while (parentUri && !parentUri.path.isRoot) { + const parentUriString = parentUri.toString(); + const existing = result.get(parentUriString); + // Make sure the highest diagnostic severity (smaller number) will be propagated to the container directory. + if (existing === undefined || this.compare(marker, existing) < 0) { + result.set(parentUriString, { + data: marker.data, + uri: parentUriString, + owner: marker.owner, + kind: marker.kind + }); + parentUri = parentUri.parent; + } else { + parentUri = undefined; + } + } + } + } + + return result; + } + + private isTreeItem(node: object | undefined): node is TreeItem { + return !!node && 'resourceUri' in node; + } +} diff --git a/packages/plugin-ext/src/main/browser/view/tree-view-widget.tsx b/packages/plugin-ext/src/main/browser/view/tree-view-widget.tsx index cfb5468717370..0edda73bec7b5 100644 --- a/packages/plugin-ext/src/main/browser/view/tree-view-widget.tsx +++ b/packages/plugin-ext/src/main/browser/view/tree-view-widget.tsx @@ -42,6 +42,7 @@ import { MessageService } from '@theia/core/lib/common/message-service'; import { View } from '../../../common/plugin-protocol'; import CoreURI from '@theia/core/lib/common/uri'; import { ContextKeyService } from '@theia/core/lib/browser/context-key-service'; +import { CancellationTokenSource } from '@theia/core/lib/common'; export const TREE_NODE_HYPERLINK = 'theia-TreeNodeHyperlink'; export const VIEW_ITEM_CONTEXT_MENU: MenuPath = ['view-item-context-menu']; @@ -116,6 +117,36 @@ export class PluginTree extends TreeImpl { return this._isEmpty; } + async refresh(raw?: CompositeTreeNode): Promise { + const parent = !raw ? this._root : this.validateNode(raw); + let result: CompositeTreeNode | undefined; + if (CompositeTreeNode.is(parent)) { + const busySource = new CancellationTokenSource(); + this.doMarkAsBusy(parent, 800, busySource.token); + try { + result = parent; + // Resolve all tree items to enable problem decorator + const children = await this.resolveAllChildren(parent); + result = await this.setChildren(parent, children); + } finally { + busySource.cancel(); + } + } + this.fireChanged(); + return result; + } + + async resolveAllChildren(parent: CompositeTreeNode): Promise { + const children = await this.resolveChildren(parent); + children.forEach(async childElement => { + if (CompositeTreeNode.is(childElement)) { + const innerChildElements = await this.resolveAllChildren(childElement); + await this.setChildren(childElement, innerChildElements); + } + }); + return children; + } + protected async resolveChildren(parent: CompositeTreeNode): Promise { if (!this._proxy) { return super.resolveChildren(parent);