Skip to content

Commit

Permalink
add dependency treeview (#647)
Browse files Browse the repository at this point in the history
* dependencies treeview

* fix CI errors

* fix other ci errors

* refine the implementation by adding TreeNode between plaintext and vscode.TreeItem

* fix CI, delete unread variables in class Dependency

* Dependency entends TreeNode

* delete unnecessary error message

Co-authored-by: Yan Zhang <[email protected]>
  • Loading branch information
ShihanMeng618 and Eskibear authored Jul 6, 2021
1 parent 6fa0e46 commit 47b2964
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 6 deletions.
27 changes: 27 additions & 0 deletions src/explorer/model/DependenciesMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import * as vscode from "vscode";
import { parseRawDependencyDataHandler } from "../../handlers/parseRawDependencyDataHandler";
import { Dependency } from "./Dependency";
import { ITreeItem } from "./ITreeItem";
import { MavenProject } from "./MavenProject";
import { Menu } from "./Menu";

export class DependenciesMenu extends Menu implements ITreeItem {
constructor(project: MavenProject) {
super(project);
this.name = "Dependencies";
}

public async getChildren() : Promise<Dependency[]> {
const treeNodes = await parseRawDependencyDataHandler(this.project);
return Promise.resolve(treeNodes);
}

public getTreeItem(): vscode.TreeItem | Thenable<vscode.TreeItem> {
const treeItem: vscode.TreeItem = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.Collapsed);
treeItem.iconPath = new vscode.ThemeIcon("library");
return treeItem;
}
}
52 changes: 52 additions & 0 deletions src/explorer/model/Dependency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import * as vscode from "vscode";
import { ITreeItem } from "./ITreeItem";
import { TreeNode } from "./TreeNode";

const DUPLICATE_INDICATOR: string = "omitted for duplicate";
const CONFLICT_INDICATOR: string = "omitted for conflict";

export class Dependency extends TreeNode implements ITreeItem {
private label: string = ""; // groupId:artifactId:version:scope
private description: string = "";
constructor(value: string) {
super(value);
const indexCut: number = value.indexOf("(");
if (indexCut !== -1) {
this.description = value.substr(indexCut);
this.label = value.substr(0, indexCut);
} else {
this.label = value;
}
}

public getContextValue(): string {
return "Dependencies";
}

public async getChildren(): Promise<Dependency[] | undefined> {
return Promise.resolve(<Dependency[]> this.children);
}

public getTreeItem(): vscode.TreeItem | Thenable<vscode.TreeItem> {
const treeItem: vscode.TreeItem = new vscode.TreeItem(this.label);
if (this.children.length !== 0) {
treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
} else {
treeItem.collapsibleState = vscode.TreeItemCollapsibleState.None;
}

if (this.description.indexOf(DUPLICATE_INDICATOR) !== -1) {
treeItem.iconPath = new vscode.ThemeIcon("trash");
treeItem.description = this.description;
} else if (this.description.indexOf(CONFLICT_INDICATOR) !== -1) {
treeItem.iconPath = new vscode.ThemeIcon("warning");
treeItem.description = this.description;
} else {
treeItem.iconPath = new vscode.ThemeIcon("library");
}
return treeItem;
}
}
4 changes: 4 additions & 0 deletions src/explorer/model/MavenProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getPathToExtensionRoot } from "../../utils/contextUtils";
import { Utils } from "../../utils/Utils";
import { EffectivePomProvider } from "../EffectivePomProvider";
import { mavenExplorerProvider } from "../mavenExplorerProvider";
import { DependenciesMenu} from "./DependenciesMenu";
import { IEffectivePom } from "./IEffectivePom";
import { ITreeItem } from "./ITreeItem";
import { LifecycleMenu } from "./LifecycleMenu";
Expand Down Expand Up @@ -146,6 +147,9 @@ export class MavenProject implements ITreeItem {
const ret: ITreeItem[] = [];
ret.push(new LifecycleMenu(this));
ret.push(new PluginsMenu(this));
if (this.packaging !== "pom") { // hide the "Dependencies" item for "pom" project
ret.push(new DependenciesMenu(this));
}
if (this.moduleNames.length > 0 && Settings.viewType() === "hierarchical") {
const projects: MavenProject[] = <MavenProject[]>this.modules.map(m => mavenExplorerProvider.getMavenProject(m)).filter(Boolean);
ret.push(...projects);
Expand Down
27 changes: 27 additions & 0 deletions src/explorer/model/TreeNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

export class TreeNode {
protected value: string;
protected children: TreeNode[] = [];
protected parent?: TreeNode | undefined;
constructor(value: string) {
this.value = value;
}

public addChild(valueOrNode: string | TreeNode): void {
let child: TreeNode;
if (typeof valueOrNode === "string") {
child = new TreeNode(valueOrNode);
} else {
child = valueOrNode;
}
child.parent = this;
this.children.push(child);
}

public addChildren(nodes: TreeNode[]): void {
nodes.forEach(node => node.parent = this);
this.children = this.children.concat(nodes);
}
}
50 changes: 50 additions & 0 deletions src/handlers/parseRawDependencyDataHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import { Dependency } from "../explorer/model/Dependency";
import { MavenProject } from "../explorer/model/MavenProject";
import { getDependencyTree } from "../handlers/showDependenciesHandler";

export async function parseRawDependencyDataHandler(project: MavenProject): Promise<Dependency[]> {
const dependencyTree: string | undefined = await getDependencyTree(project.pomPath);
if (dependencyTree === undefined) {
throw new Error("Failed to generate dependency tree.");
}
let treeContent: string = dependencyTree.slice(0, -1); // delete last "\n"
treeContent = treeContent.replace(/\|/g, " ");
treeContent = treeContent.replace(/\\/g, "+");
treeContent = treeContent.replace(/\n/g, "\r\n");
// handle the version switch in conflict
// input = (groupId:artifactId:)(version1)(:scope (omitted for conflict): (version2))
// output = (groupId:artifactId:)(version2)(:scope (omitted for conflict) with (version1))
const re = /([\w.]+:[\w.-]+:)([\w.-]+)(:[\w/.(\s]+):\s([\w.-]+)\)/gm;
treeContent = treeContent.replace(re, "$1$4$3 with $2)");

const eol: string = "\r\n";
const indent: string = " "; // three spaces
const separator: string = "\r\n";
const starter: string = "+- ";
return parseTreeNodes(treeContent, separator, indent, starter, eol);
}

function parseTreeNodes(treecontent: string, separator: string, indent: string, starter: string, eol: string): Dependency[] {
const treeNodes: Dependency[] = [];
if (treecontent) {
const treeChildren: string[] = treecontent.split(`${separator}${starter}`).splice(1); // delete first line
const toTreeNode = (treeChild: string): Dependency => {
let curNode: Dependency;
if (treeChild.indexOf(eol) === -1) {
curNode = new Dependency(treeChild);
} else {
const curValue: string = treeChild.split(separator, 1)[0];
curNode = new Dependency(curValue);
const nextSeparator = `${separator}${indent}`;
const childrenNodes: Dependency[] = parseTreeNodes(treeChild, nextSeparator, indent, starter, eol);
curNode.addChildren(childrenNodes);
}
return curNode;
};
treeChildren.forEach(treeChild => treeNodes.push(toTreeNode(treeChild)));
}
return treeNodes;
}
2 changes: 1 addition & 1 deletion src/handlers/showDependenciesHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function showDependenciesHandler(project: MavenProject): Promise<vo
await vscode.window.showTextDocument(document, { viewColumn: vscode.ViewColumn.Active, preview: false });
}

async function getDependencyTree(pomPathOrMavenProject: string | MavenProject): Promise<string | undefined> {
export async function getDependencyTree(pomPathOrMavenProject: string | MavenProject): Promise<string | undefined> {
let pomPath: string;
let name: string;
if (typeof pomPathOrMavenProject === "object" && pomPathOrMavenProject instanceof MavenProject) {
Expand Down
11 changes: 6 additions & 5 deletions src/utils/mavenUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ export async function rawEffectivePom(pomPath: string, options?: {cacheOnly?: bo
return await readFileIfExists(outputPath);
}

export async function rawDependencyTree(pomPath: string): Promise<string | undefined> {
const outputPath: string = getTempFolder(pomPath);
await executeInBackground(`dependency:tree -Dverbose -DoutputFile="${outputPath}"`, pomPath);
return await readFileIfExists(outputPath);
export async function rawDependencyTree(pomPath: string): Promise<any> {
const outputDirectory: string = path.dirname(pomPath);
const outputFileName: string = "dependency-graph.txt";
await executeInBackground(`com.github.ferstl:depgraph-maven-plugin:graph -DgraphFormat=text -DshowDuplicates -DshowConflicts -DshowVersions -DshowGroupIds -DoutputDirectory="${outputDirectory}" -DoutputFileName="${outputFileName}"`, pomPath);
return await readFileIfExists(path.join(outputDirectory, outputFileName));
}

export async function pluginDescription(pluginId: string, pomPath: string): Promise<string | undefined> {
Expand Down Expand Up @@ -83,7 +84,7 @@ async function executeInBackground(mvnArgs: string, pomfile?: string): Promise<a
if (code === 0) {
resolve(code);
} else {
reject(new Error(`Background process terminated with code ${code}.`));
reject(new Error(`Background process terminated with code ${code}`));
}
} else {
reject(new Error(`Background process killed by signal ${signal}.`));
Expand Down

0 comments on commit 47b2964

Please sign in to comment.