diff --git a/.vscode/launch.json b/.vscode/launch.json index 57261f9f..7e154613 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -18,6 +18,7 @@ { "type": "java", "name": "Attach to Plugin", + "projectName": "com.microsoft.jdtls.ext.core", "request": "attach", "hostName": "localhost", "port": 1044 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..9917e21e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# How to Contribute + +We greatly appreciate contributions to the vscode-java-dependency project. Your efforts help us maintain and improve this extension. To ensure a smooth contribution process, please follow these guidelines. + +## Prerequisites +- [JDK](https://www.oracle.com/java/technologies/downloads/?er=221886) +- [Node.JS](https://nodejs.org/en/) +- [VSCode](https://code.visualstudio.com/) + +## Build and Run + +To set up the vscode-java-dependency project, follow these steps: + +1. **Build the Server JAR**: + - The server JAR (Java application) is located in the [jdtls.ext](./jdtls.ext) directory. + - Run the following command to build the server: + ```shell + npm run build-server + ``` + +2. **Install Dependencies**: + - Execute the following command to install the necessary dependencies: + ```shell + npm install + ``` + +3. **Run/Debug the Extension**: + - Open the "Run and Debug" view in Visual Studio Code. + - Run the "Run Extension" task. + +4. **Attach to Plugin[Debug Java]**: + - Prerequisite: Ensure that the extension is activated, meaning the Java process is already launched. This is required for the task to run properly. + - Open the "Run and Debug" view in Visual Studio Code. + - Run the "Attach to Plugin" task. + - Note: This task is required only if you want to debug Java code [jdtls.ext](./jdtls.ext). It requires the [vscode-pde](https://marketplace.visualstudio.com/items?itemName=yaozheng.vscode-pde) extension to be installed. + +Thank you for your contributions and support! \ No newline at end of file diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java index 1bb2e99b..cada8754 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -78,7 +79,7 @@ public class PackageCommand { private static final Map>> commands; static { - commands = new HashMap<>(); + commands = new EnumMap<>(NodeKind.class); commands.put(NodeKind.PROJECT, PackageCommand::getProjectChildren); commands.put(NodeKind.CONTAINER, PackageCommand::getContainerChildren); commands.put(NodeKind.PACKAGEROOT, PackageCommand::getPackageRootChildren); diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/PackageNode.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/PackageNode.java index 7f146d8d..92fa4cf7 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/PackageNode.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/PackageNode.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Objects; +import org.apache.commons.lang3.StringUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; @@ -273,11 +274,32 @@ public static PackageRootNode createNodeForPackageFragmentRoot(IPackageFragmentR for (IClasspathAttribute attribute : resolvedClasspathEntry.getExtraAttributes()) { node.setMetaDataValue(attribute.getName(), attribute.getValue()); } + + String computedDisplayName = computeDisplayName(node); + if (StringUtils.isNotBlank(computedDisplayName)) { + node.setDisplayName(computedDisplayName); + } } return node; } + private static String computeDisplayName(PackageRootNode node) { + if (node.getMetaData() == null || node.getMetaData().isEmpty()) { + return node.getName(); + } + + String version = (String) node.getMetaData().get("maven.version"); + String groupId = (String) node.getMetaData().get("maven.groupId"); + String artifactId = (String) node.getMetaData().get("maven.artifactId"); + + if (StringUtils.isBlank(version) || StringUtils.isBlank(groupId) || StringUtils.isBlank(artifactId)) { + return node.getName(); + } + + return groupId + ":" + artifactId + ":" + version; + } + /** * Get the correspond node of classpath, it may be container or a package root. * diff --git a/src/views/PrimaryTypeNode.ts b/src/views/PrimaryTypeNode.ts index 8ee95223..0d80bf8b 100644 --- a/src/views/PrimaryTypeNode.ts +++ b/src/views/PrimaryTypeNode.ts @@ -34,6 +34,10 @@ export class PrimaryTypeNode extends DataNode { return ""; } + public getLabel(): string { + return this._nodeData.displayName ?? this._nodeData.name; + } + protected async loadData(): Promise { if (!this.hasChildren() || !this.nodeData.uri) { return undefined; diff --git a/src/views/containerNode.ts b/src/views/containerNode.ts index 0245514c..a8cbd6c7 100644 --- a/src/views/containerNode.ts +++ b/src/views/containerNode.ts @@ -15,23 +15,36 @@ export class ContainerNode extends DataNode { super(nodeData, parent); } + private _containerType: ContainerType; + public get projectBasePath() { return this._project.uri && Uri.parse(this._project.uri).fsPath; } - public getContainerType(): string { + public getContainerType(): ContainerType { + if (this._containerType) { + return this._containerType; + } + const containerPath: string = this._nodeData.path || ""; if (containerPath.startsWith(ContainerPath.JRE)) { - return ContainerType.JRE; + this._containerType = ContainerType.JRE; } else if (containerPath.startsWith(ContainerPath.Maven)) { - return ContainerType.Maven; + this._containerType = ContainerType.Maven; } else if (containerPath.startsWith(ContainerPath.Gradle)) { - return ContainerType.Gradle; + this._containerType = ContainerType.Gradle; } else if (containerPath.startsWith(ContainerPath.ReferencedLibrary) && this._project.isUnmanagedFolder()) { // currently, we only support editing referenced libraries in unmanaged folders - return ContainerType.ReferencedLibrary; + this._containerType = ContainerType.ReferencedLibrary; + } else { + this._containerType = ContainerType.Unknown; } - return ContainerType.Unknown; + + return this._containerType; + } + + public isMavenType(): boolean { + return this._containerType === ContainerType.Maven; } protected async loadData(): Promise { diff --git a/src/views/dataNode.ts b/src/views/dataNode.ts index 21be07a7..200abf13 100644 --- a/src/views/dataNode.ts +++ b/src/views/dataNode.ts @@ -42,6 +42,10 @@ export abstract class DataNode extends ExplorerNode { return item; } + public getDisplayName(): string { + return this._nodeData.displayName || this._nodeData.name; + } + public get nodeData(): INodeData { return this._nodeData; } diff --git a/src/views/dependencyDataProvider.ts b/src/views/dependencyDataProvider.ts index dd0bd05b..12001be2 100644 --- a/src/views/dependencyDataProvider.ts +++ b/src/views/dependencyDataProvider.ts @@ -7,7 +7,7 @@ import { RelativePattern, TreeDataProvider, TreeItem, Uri, window, workspace, } from "vscode"; import { instrumentOperationAsVsCodeCommand, sendError } from "vscode-extension-telemetry-wrapper"; -import { contextManager } from "../../extension.bundle"; +import { ContainerNode, contextManager } from "../../extension.bundle"; import { Commands } from "../commands"; import { Context } from "../constants"; import { appendOutput, executeExportJarTask } from "../tasks/buildArtifact/BuildArtifactTaskProvider"; @@ -124,6 +124,14 @@ export class DependencyDataProvider implements TreeDataProvider { const children = (!this._rootItems || !element) ? await this.getRootNodes() : await element.getChildren(); + if (children && element instanceof ContainerNode) { + if (element.isMavenType()) { + children.sort((a, b) => { + return a.getDisplayName().localeCompare(b.getDisplayName()); + }); + } + } + explorerNodeCache.saveNodes(children || []); return children; } diff --git a/src/views/documentSymbolNode.ts b/src/views/documentSymbolNode.ts index a6ead753..7552116e 100644 --- a/src/views/documentSymbolNode.ts +++ b/src/views/documentSymbolNode.ts @@ -28,6 +28,10 @@ export class DocumentSymbolNode extends ExplorerNode { super(parent); } + public getDisplayName(): string { + return this.symbolInfo.name; + } + public getChildren(): ExplorerNode[] | Promise { const res: ExplorerNode[] = []; if (this.symbolInfo?.children?.length) { @@ -39,7 +43,7 @@ export class DocumentSymbolNode extends ExplorerNode { } public getTreeItem(): TreeItem | Promise { - const item = new TreeItem(this.symbolInfo.name, + const item = new TreeItem(this.getDisplayName(), this.symbolInfo?.children?.length ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None); item.iconPath = this.iconPath; diff --git a/src/views/explorerNode.ts b/src/views/explorerNode.ts index c5c29092..1dcac373 100644 --- a/src/views/explorerNode.ts +++ b/src/views/explorerNode.ts @@ -33,4 +33,6 @@ export abstract class ExplorerNode { public abstract getTreeItem(): TreeItem | Promise; public abstract computeContextValue(): string | undefined; + + public abstract getDisplayName(): string; } diff --git a/test/gradle-suite/projectView.test.ts b/test/gradle-suite/projectView.test.ts index ec5a045d..4a4e6776 100644 --- a/test/gradle-suite/projectView.test.ts +++ b/test/gradle-suite/projectView.test.ts @@ -3,13 +3,17 @@ import * as assert from "assert"; import { ContainerNode, contextManager, DataNode, DependencyExplorer, + languageServerApiManager, PackageRootNode, PrimaryTypeNode, ProjectNode } from "../../extension.bundle"; import { fsPath, setupTestEnv, Uris } from "../shared"; // tslint:disable: only-arrow-functions suite("Gradle Project View Tests", () => { - suiteSetup(setupTestEnv); + suiteSetup(async () => { + await setupTestEnv(); + await languageServerApiManager.ready(); + }); test("Can node render correctly", async function() { const explorer = DependencyExplorer.getInstance(contextManager.context); diff --git a/test/invisible-suite/projectView.test.ts b/test/invisible-suite/projectView.test.ts index c1488ce7..61e91d01 100644 --- a/test/invisible-suite/projectView.test.ts +++ b/test/invisible-suite/projectView.test.ts @@ -3,22 +3,21 @@ import * as assert from "assert"; import * as fse from "fs-extra"; -import { platform } from "os"; import * as path from "path"; import * as vscode from "vscode"; -import { Commands, contextManager, DependencyExplorer, PackageNode, PackageRootNode, ProjectNode } from "../../extension.bundle"; +import { Commands, contextManager, DependencyExplorer, languageServerApiManager, PackageNode, PackageRootNode, ProjectNode } from "../../extension.bundle"; import { setupTestEnv } from "../shared"; import { sleep } from "../util"; // tslint:disable: only-arrow-functions suite("Invisible Project View Tests", () => { - suiteSetup(setupTestEnv); + suiteSetup(async () => { + await setupTestEnv(); + await languageServerApiManager.ready(); + }); test("Can execute command java.project.refreshLibraries correctly", async function() { - if (platform() === "darwin") { - this.skip(); - } const explorer = DependencyExplorer.getInstance(contextManager.context); let projectNode = (await explorer.dataProvider.getChildren())![0] as ProjectNode; @@ -37,9 +36,6 @@ suite("Invisible Project View Tests", () => { }); test("Can execute command java.project.removeLibrary correctly", async function() { - if (platform() === "darwin") { - this.skip(); - } const explorer = DependencyExplorer.getInstance(contextManager.context); let projectNode = (await explorer.dataProvider.getChildren())![0] as ProjectNode; diff --git a/test/maven-suite/projectView.test.ts b/test/maven-suite/projectView.test.ts index f6863758..ec305edd 100644 --- a/test/maven-suite/projectView.test.ts +++ b/test/maven-suite/projectView.test.ts @@ -4,13 +4,16 @@ import * as assert from "assert"; import * as vscode from "vscode"; import { Commands, ContainerNode, contextManager, DataNode, DependencyExplorer, FileNode, - INodeData, Jdtls, NodeKind, PackageNode, PackageRootNode, PrimaryTypeNode, ProjectNode } from "../../extension.bundle"; + INodeData, Jdtls, languageServerApiManager, NodeKind, PackageNode, PackageRootNode, PrimaryTypeNode, ProjectNode } from "../../extension.bundle"; import { fsPath, printNodes, setupTestEnv, Uris } from "../shared"; // tslint:disable: only-arrow-functions suite("Maven Project View Tests", () => { - suiteSetup(setupTestEnv); + suiteSetup(async () => { + await setupTestEnv(); + await languageServerApiManager.ready(); + }); test("Can node render correctly in hierarchical view", async function() { await vscode.workspace.getConfiguration("java.dependency").update("packagePresentation", "hierarchical"); @@ -253,6 +256,19 @@ suite("Maven Project View Tests", () => { assert.equal(projectChildren.length, 4); }); + test("Can maven dependency nodes display in correct groupId:artifactId:version format", async function() { + const explorer = DependencyExplorer.getInstance(contextManager.context); + + const roots = await explorer.dataProvider.getChildren(); + const projectNode = roots![0] as ProjectNode; + const projectChildren = await projectNode.getChildren(); + const mavenDependency = projectChildren[3] as ContainerNode; + const mavenChildren = await mavenDependency.getChildren(); + + assert.equal(mavenChildren[0].getDisplayName(), "org.hamcrest:hamcrest-core:1.3"); + assert.equal(mavenChildren[1].getDisplayName(), "junit:junit:4.13.1"); + }); + teardown(async () => { // Restore default settings. Some tests might alter them and others depend on a specific setting. // Not resetting to the default settings will also show the file as changed in the source control view.