Skip to content

Commit

Permalink
[VSCode] Enable problem decorator in TreeView
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Kozinko <[email protected]>
  • Loading branch information
xcariba committed Jun 28, 2021
1 parent 144da47 commit 427c047
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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/tree-view-decorator-service';
import { TreeViewProblemDecorator } from './view/tree-view-problem-decorator';

export default new ContainerModule((bind, unbind, isBound, rebind) => {

Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/********************************************************************************
* Copyright (C) 2021 1C-Soft LLC 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 plugin tree views.
*/
export const TreeViewDecorator = Symbol('TreeViewDecorator');

/**
* Decorator service for plugin tree views.
*/
@injectable()
export class TreeViewDecoratorService extends AbstractTreeDecoratorService {

constructor(@inject(ContributionProvider) @named(TreeViewDecorator) protected readonly contributions: ContributionProvider<TreeDecorator>) {
super(contributions.getContributions());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/********************************************************************************
* Copyright (C) 2021 1C-Soft LLC 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<string, TreeDecoration.Data> {
const result = new Map<string, Marker<Diagnostic>>();

// 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<string, TreeDecoration.Data>();
}

const pathToIdMap = new Map<string, string>();

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<Diagnostic>, 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<Diagnostic>[], pathToIdMap: Map<string, string>): Map<string, Marker<Diagnostic>> {
const result: Map<string, Marker<Diagnostic>> = 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<URI, Marker<Diagnostic>>(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;
}
}
31 changes: 31 additions & 0 deletions packages/plugin-ext/src/main/browser/view/tree-view-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -116,6 +117,36 @@ export class PluginTree extends TreeImpl {
return this._isEmpty;
}

async refresh(raw?: CompositeTreeNode): Promise<CompositeTreeNode | undefined> {
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<TreeNode[]> {
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<TreeNode[]> {
if (!this._proxy) {
return super.resolveChildren(parent);
Expand Down

0 comments on commit 427c047

Please sign in to comment.