diff --git a/package.json b/package.json index d75f024..8b3f24b 100644 --- a/package.json +++ b/package.json @@ -210,6 +210,11 @@ "category": "Maven", "icon": "$(wrench)" }, + { + "command": "maven.dependency.goToEffective", + "title": "%contributes.commands.maven.dependency.goToEffective%", + "category": "Maven" + }, { "command": "maven.project.goToDefinition", "title": "%contributes.commands.maven.project.goToDefinition%", @@ -321,6 +326,10 @@ "command": "maven.project.setDependencyVersion", "when": "never" }, + { + "command": "maven.dependency.goToEffective", + "when": "never" + }, { "command": "maven.project.goToDefinition", "when": "never" @@ -457,6 +466,11 @@ "when": "view == mavenProjects && viewItem =~ /maven:dependency(?=.*?\\b\\+conflict\\b)/", "group": "inline" }, + { + "command": "maven.dependency.goToEffective", + "when": "view == mavenProjects && viewItem =~ /maven:dependency(?=.*?\\b\\+conflict\\b)/", + "group": "0-dependency@2" + }, { "command": "maven.project.goToDefinition", "when": "view == mavenProjects && viewItem =~ /maven:dependency/", diff --git a/package.nls.json b/package.nls.json index 0e61202..d587135 100644 --- a/package.nls.json +++ b/package.nls.json @@ -14,9 +14,10 @@ "contributes.commands.maven.view.hierarchical": "Switch to hierarchical view", "contributes.commands.maven.view.flat": "Switch to flat view", "contributes.commands.maven.project.addDependency": "Add a dependency...", - "contributes.commands.maven.project.showDependencies": "Show dependencies", - "contributes.commands.maven.project.excludeDependency": "Exclude dependency", - "contributes.commands.maven.project.setDependencyVersion": "Resolve conflict...", + "contributes.commands.maven.project.showDependencies": "Show Dependencies", + "contributes.commands.maven.project.excludeDependency": "Exclude Dependency", + "contributes.commands.maven.project.setDependencyVersion": "Resolve Conflict...", + "contributes.commands.maven.dependency.goToEffective": "Go to Effective Dependency", "contributes.commands.maven.project.goToDefinition": "Go to Definition", "contributes.views.explorer.mavenProjects": "Maven", "contributes.viewsWelcome.mavenProjects.untrustedWorkspaces": "Advanced features (e.g. executing lifecycle phases and plugin goals) are disabled in Restricted Mode for security concern.\nPOM editing assistance (e.g. [add a dependency](command:maven.project.addDependency)) is still available.\nLearn more about [Workspace Trust](https://aka.ms/vscode-workspace-trust).\n[Manage Workspace Trust](command:workbench.action.manageTrust)", diff --git a/package.nls.zh.json b/package.nls.zh.json index 4d2fdd1..329a747 100644 --- a/package.nls.zh.json +++ b/package.nls.zh.json @@ -18,6 +18,7 @@ "contributes.commands.maven.project.goToDefinition": "转到定义", "contributes.commands.maven.project.excludeDependency": "删除依赖", "contributes.commands.maven.project.setDependencyVersion": "指定依赖版本为...", + "contributes.commands.maven.dependency.goToEffective": "转到有效的依赖项", "contributes.views.explorer.mavenProjects": "Maven", "configuration.maven.excludedFolders": "指定搜索 Maven 项目时要排除的文件夹。", "configuration.maven.executable.preferMavenWrapper": "指定是否优先使用 Maven Wrapper。如果为 true,则尝试向上遍历父文件夹寻找 mvnw 作为可执行文件;如果为 false,或者找不到 mvnw,则尝试使用系统 PATH 中的 mvn。", diff --git a/src/DiagnosticProvider.ts b/src/DiagnosticProvider.ts index 84bfb26..aa97afb 100644 --- a/src/DiagnosticProvider.ts +++ b/src/DiagnosticProvider.ts @@ -66,7 +66,7 @@ class DiagnosticProvider { } public async createDiagnostics(node: Dependency): Promise { - const root: Dependency = node.root; + const root: Dependency = node.root; const range: vscode.Range = await this.findConflictRange(root.projectPomPath, root.groupId, root.artifactId); const message: string = `Dependency conflict in ${root.artifactId}: ${node.groupId}:${node.artifactId}:${node.version} conflict with ${node.omittedStatus?.effectiveVersion}`; const diagnostic: vscode.Diagnostic = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Warning); diff --git a/src/explorer/mavenExplorerProvider.ts b/src/explorer/mavenExplorerProvider.ts index 599d2e9..395ece6 100644 --- a/src/explorer/mavenExplorerProvider.ts +++ b/src/explorer/mavenExplorerProvider.ts @@ -4,6 +4,7 @@ import { TreeDataProvider } from "vscode"; import * as vscode from "vscode"; import { Utils } from "../utils/Utils"; +import { Dependency } from "./model/Dependency"; import { ITreeItem } from "./model/ITreeItem"; import { MavenProject } from "./model/MavenProject"; import { WorkspaceFolder } from "./model/WorkspaceFolder"; @@ -80,6 +81,13 @@ class MavenExplorerProvider implements TreeDataProvider { return element.getChildren ? element.getChildren() : undefined; } } + public async getParent(element: ITreeItem): Promise { + if (element instanceof Dependency) { + return element.parent; + } else { + return undefined; + } + } public refresh(item?: ITreeItem): void { this._onDidChangeTreeData.fire(item); diff --git a/src/explorer/model/Dependency.ts b/src/explorer/model/Dependency.ts index 9faa5e5..fb670fe 100644 --- a/src/explorer/model/Dependency.ts +++ b/src/explorer/model/Dependency.ts @@ -3,10 +3,10 @@ import * as vscode from "vscode"; import { ITreeItem } from "./ITreeItem"; +import { ITreeNode } from "./ITreeNode"; import { IOmittedStatus } from "./OmittedStatus"; -import { TreeNode } from "./TreeNode"; -export class Dependency extends TreeNode implements ITreeItem { +export class Dependency implements ITreeItem, ITreeNode { public fullArtifactName: string = ""; // groupId:artifactId:version:scope public projectPomPath: string; public groupId: string; @@ -15,8 +15,10 @@ export class Dependency extends TreeNode implements ITreeItem { public scope: string; public omittedStatus?: IOmittedStatus; public uri: vscode.Uri; + public children: Dependency[] = []; + public root: Dependency; + public parent: Dependency; constructor(gid: string, aid: string, version: string, scope: string, projectPomPath: string, omittedStatus?: IOmittedStatus) { - super(); this.groupId = gid; this.artifactId = aid; this.version = version; @@ -26,8 +28,13 @@ export class Dependency extends TreeNode implements ITreeItem { this.omittedStatus = omittedStatus; } + public addChild(node: Dependency): void { + node.parent = this; + this.children.push(node); + } + public getContextValue(): string { - const root = this.root; + const root = this.root; let contextValue: string = "maven:dependency"; if (root.fullArtifactName === this.fullArtifactName) { contextValue = `${contextValue}+root`; diff --git a/src/explorer/model/ITreeNode.ts b/src/explorer/model/ITreeNode.ts new file mode 100644 index 0000000..89f1338 --- /dev/null +++ b/src/explorer/model/ITreeNode.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export interface ITreeNode { + children: ITreeNode[]; + parent?: ITreeNode | undefined; + root?: ITreeNode | undefined; + + addChild(node: ITreeNode): void; + +} diff --git a/src/explorer/model/MavenProject.ts b/src/explorer/model/MavenProject.ts index e44da76..3adeac6 100644 --- a/src/explorer/model/MavenProject.ts +++ b/src/explorer/model/MavenProject.ts @@ -26,6 +26,7 @@ export class MavenProject implements ITreeItem { public pomPath: string; public _fullDependencyText: string; public conflictNodes: Dependency[]; + public dependencyNodes: Dependency[]; private ePomProvider: EffectivePomProvider; private _ePom: any; private _pom: any; diff --git a/src/explorer/model/TreeNode.ts b/src/explorer/model/TreeNode.ts deleted file mode 100644 index c950a4f..0000000 --- a/src/explorer/model/TreeNode.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -export class TreeNode { - public children: TreeNode[] = []; - public parent?: TreeNode | undefined; - public root?: TreeNode | undefined; - - public addChild(node: TreeNode): void { - node.parent = this; - this.children.push(node); - } - - public addChildren(nodes: TreeNode[]): void { - nodes.forEach(node => node.parent = this); - this.children = this.children.concat(nodes); - } -} diff --git a/src/extension.ts b/src/extension.ts index 9a159f5..5507692 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,6 +17,7 @@ import { diagnosticProvider } from "./DiagnosticProvider"; import { initExpService } from "./experimentationService"; import { decorationProvider } from "./explorer/decorationProvider"; import { mavenExplorerProvider } from "./explorer/mavenExplorerProvider"; +import { Dependency } from "./explorer/model/Dependency"; import { ITreeItem } from "./explorer/model/ITreeItem"; import { MavenProject } from "./explorer/model/MavenProject"; import { PluginGoal } from "./explorer/model/PluginGoal"; @@ -24,6 +25,7 @@ import { pluginInfoProvider } from "./explorer/pluginInfoProvider"; import { addDependencyHandler } from "./handlers/addDependencyHandler"; import { debugHandler } from "./handlers/debugHandler"; import { excludeDependencyHandler } from "./handlers/excludeDependencyHandler"; +import { goToEffectiveHandler } from "./handlers/goToEffectiveHandler"; import { jumpToDefinitionHandler } from "./handlers/jumpToDefinitionHandler"; import { runFavoriteCommandsHandler } from "./handlers/runFavoriteCommandsHandler"; import { setDependencyVersionHandler } from "./handlers/setDependencyVersionHandler"; @@ -59,7 +61,9 @@ async function doActivate(_operationId: string, context: vscode.ExtensionContext await vscode.commands.executeCommand("setContext", "vscode-maven:activated", true); // register tree view await mavenExplorerProvider.loadProjects(); - context.subscriptions.push(vscode.window.createTreeView("mavenProjects", { treeDataProvider: mavenExplorerProvider, showCollapseAll: true })); + const view = vscode.window.createTreeView("mavenProjects", { treeDataProvider: mavenExplorerProvider, showCollapseAll: true }); + context.subscriptions.push(view); + registerCommand(context, "maven.dependency.goToEffective", (node?: Dependency) => goToEffectiveHandler(view, node)); context.subscriptions.push(vscode.workspace.onDidGrantWorkspaceTrust(_e => { mavenExplorerProvider.refresh(); })); diff --git a/src/handlers/excludeDependencyHandler.ts b/src/handlers/excludeDependencyHandler.ts index 04463a9..dfc1433 100644 --- a/src/handlers/excludeDependencyHandler.ts +++ b/src/handlers/excludeDependencyHandler.ts @@ -12,7 +12,7 @@ export async function excludeDependencyHandler(toExclude?: Dependency): Promise< if (toExclude === undefined) { throw new UserError("Only Dependency can be excluded."); } - const root: Dependency | undefined = toExclude.root ? toExclude.root : undefined; + const root: Dependency = toExclude.root; if (root === undefined || toExclude.fullArtifactName === root.fullArtifactName) { vscode.window.showInformationMessage("The dependency written in pom can not be excluded."); return; diff --git a/src/handlers/goToEffectiveHandler.ts b/src/handlers/goToEffectiveHandler.ts new file mode 100644 index 0000000..62c0942 --- /dev/null +++ b/src/handlers/goToEffectiveHandler.ts @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as vscode from "vscode"; +import { mavenExplorerProvider } from "../explorer/mavenExplorerProvider"; +import { Dependency } from "../explorer/model/Dependency"; +import { ITreeItem } from "../explorer/model/ITreeItem"; +import { MavenProject } from "../explorer/model/MavenProject"; +import { Queue } from "../taskExecutor"; + +export async function goToEffectiveHandler(view: vscode.TreeView, node?: Dependency): Promise { + if (node === undefined || node.omittedStatus === undefined) { + throw new Error("No conflict dependency node specified."); + } + const fullArtifactName: string = [node.groupId, node.artifactId, node.omittedStatus.effectiveVersion, node.scope].join(":"); + const pomPath: string = node.projectPomPath; + const project: MavenProject | undefined = mavenExplorerProvider.getMavenProject(pomPath); + if (project === undefined) { + throw new Error("Failed to find maven projects."); + } + + const dependencyNodes = project.dependencyNodes; + const treeItem: Dependency | undefined = await searchFirstEffective(dependencyNodes, fullArtifactName); + if (treeItem === undefined) { + throw new Error("Failed to find dependency."); + } + view.reveal(treeItem, { focus: true}); + +} + +async function searchFirstEffective(dependencyNodes: Dependency[], fullArtifactName: string): Promise { + let targetItem: Dependency | undefined; + const queue: Queue = new Queue(); + for (const child of dependencyNodes) { + queue.push(child); + } + while (queue.empty() === false) { + const node: Dependency | undefined = queue.pop(); + if (node === undefined) { + throw new Error("Failed to find dependency."); + } + if (node.fullArtifactName === fullArtifactName) { + targetItem = node; + break; + } + const children = node.children; + for (const child of children) { + queue.push(child); + } + } + return targetItem; +} diff --git a/src/handlers/jumpToDefinitionHandler.ts b/src/handlers/jumpToDefinitionHandler.ts index 4103ebf..85ced83 100644 --- a/src/handlers/jumpToDefinitionHandler.ts +++ b/src/handlers/jumpToDefinitionHandler.ts @@ -16,7 +16,7 @@ export async function jumpToDefinitionHandler(node?: Dependency): Promise if (node.parent === undefined) { selectedPath = node.projectPomPath; } else { - const parent: Dependency = node.parent; + const parent: Dependency = node.parent; selectedPath = localPomPath(parent.groupId, parent.artifactId, parent.version); } await goToDefinition(selectedPath, node.groupId, node.artifactId); diff --git a/src/handlers/parseRawDependencyDataHandler.ts b/src/handlers/parseRawDependencyDataHandler.ts index 8eddebb..37e15a7 100644 --- a/src/handlers/parseRawDependencyDataHandler.ts +++ b/src/handlers/parseRawDependencyDataHandler.ts @@ -32,6 +32,7 @@ export async function parseRawDependencyDataHandler(project: MavenProject): Prom const prefix: string = "+- "; const [treeNodes, conflictNodes] = await parseTreeNodes(treeContent, eol, indent, prefix, project.pomPath); project.conflictNodes = conflictNodes; + project.dependencyNodes = treeNodes; return treeNodes; } @@ -86,7 +87,7 @@ async function parseTreeNodes(treecontent: string, eol: string, indent: string, } else { const level: number = (preIndentCnt - curIndentCnt) / indent.length; for (let i = level; i > 0; i -= 1) { - parentNode = parentNode.parent; + parentNode = parentNode.parent; } parentNode.addChild(curNode); } @@ -103,7 +104,7 @@ async function parseTreeNodes(treecontent: string, eol: string, indent: string, // find all parent and set hasConflict upforward let tmpNode = curNode; while (tmpNode.parent !== undefined) { - const parent = tmpNode.parent; + const parent = tmpNode.parent; if (parent.uri.query !== "hasConflict") { parent.uri = uri.with({query: "hasConflict"}); tmpNode = parent; diff --git a/src/taskExecutor.ts b/src/taskExecutor.ts index 9a86926..424bd1d 100644 --- a/src/taskExecutor.ts +++ b/src/taskExecutor.ts @@ -3,7 +3,7 @@ import { Disposable } from "vscode"; -class Queue { +export class Queue { private _store: T[] = []; public push(val: T): void { this._store.push(val); @@ -11,6 +11,12 @@ class Queue { public pop(): T | undefined { return this._store.shift(); } + public empty(): boolean { + if (this._store.length === 0) { + return true; + } + return false; + } } class TaskExecutor implements Disposable {