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 12005ca5..878af8b7 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 @@ -134,7 +134,12 @@ public static List resolvePath(List arguments, IProgressMon if (isClassFile) { result.add(PackageNode.createNodeForVirtualContainer(pkgRoot)); } - result.add(PackageNode.createNodeForPackageFragmentRoot(pkgRoot)); + // for invisible project, removing the '_' link name may cause an empty named package root + // in this case, we will avoid that 'empty' node from displaying + PackageNode pkgRootNode = PackageNode.createNodeForPackageFragmentRoot(pkgRoot); + if (StringUtils.isNotBlank(pkgRootNode.getName())) { + result.add(pkgRootNode); + } result.add(PackageNode.createNodeForPackageFragment(packageFragment)); result.add(PackageNode.createNodeForPrimaryType(typeRoot.findPrimaryType())); } else if (ExtUtils.isJarResourceUri(uri)) { @@ -171,15 +176,16 @@ public static List resolvePath(List arguments, IProgressMon IJavaElement parentJavaElement = JavaCore.create(parent); if (parent instanceof IFolder && parentJavaElement instanceof IPackageFragment) { IPackageFragment packageFragment = (IPackageFragment) parentJavaElement; - IPackageFragmentRoot pkgRoot = (IPackageFragmentRoot) packageFragment.getParent(); - PackageNode rootNode = null; - - rootNode = new PackageRootNode(pkgRoot, - ExtUtils.removeProjectSegment(packageFragment.getJavaProject().getElementName(), pkgRoot.getPath()).toPortableString(), - NodeKind.PACKAGEROOT); result.add(PackageNode.createNodeForProject(packageFragment)); - result.add(rootNode); + + IPackageFragmentRoot pkgRoot = (IPackageFragmentRoot) packageFragment.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); + // for invisible project, removing the '_' link name may cause an empty named package root + // in this case, we will avoid that 'empty' node from displaying + PackageNode pkgRootNode = PackageNode.createNodeForPackageFragmentRoot(pkgRoot); + if (StringUtils.isNotBlank(pkgRootNode.getName())) { + result.add(pkgRootNode); + } result.add(PackageNode.createNodeForPackageFragment(packageFragment)); PackageNode item = new PackageNode(resource.getName(), resource.getFullPath().toPortableString(), NodeKind.FILE); @@ -276,24 +282,30 @@ private static List getPackageFragmentRoots(PackageParams query, IP if (containerEntry != null) { IPackageFragmentRoot[] packageFragmentRoots = javaProject.findPackageFragmentRoots(containerEntry); for (IPackageFragmentRoot fragmentRoot : packageFragmentRoots) { - String displayName = fragmentRoot.getElementName(); - if (fragmentRoot.getKind() == IPackageFragmentRoot.K_SOURCE) { - displayName = ExtUtils.removeProjectSegment(javaProject.getElementName(), fragmentRoot.getPath()).toPortableString(); - } - PackageRootNode node = new PackageRootNode(fragmentRoot, displayName, NodeKind.PACKAGEROOT); - node.setHandlerIdentifier(fragmentRoot.getHandleIdentifier()); - children.add(node); - if (fragmentRoot instanceof JrtPackageFragmentRoot) { - node.setModuleName(fragmentRoot.getModuleDescription().getElementName()); - } - - IClasspathEntry resolvedClasspathEntry = fragmentRoot.getResolvedClasspathEntry(); - if (resolvedClasspathEntry != null) { - Map attributes = new HashMap<>(); - for (IClasspathAttribute attribute : resolvedClasspathEntry.getExtraAttributes()) { - attributes.put(attribute.getName(), attribute.getValue()); + PackageRootNode node = PackageNode.createNodeForPackageFragmentRoot(fragmentRoot); + if (StringUtils.isNotBlank(node.getName())) { + node.setHandlerIdentifier(fragmentRoot.getHandleIdentifier()); + if (fragmentRoot instanceof JrtPackageFragmentRoot) { + node.setModuleName(fragmentRoot.getModuleDescription().getElementName()); } - node.setAttributes(attributes); + + IClasspathEntry resolvedClasspathEntry = fragmentRoot.getResolvedClasspathEntry(); + if (resolvedClasspathEntry != null) { + Map attributes = new HashMap<>(); + for (IClasspathAttribute attribute : resolvedClasspathEntry.getExtraAttributes()) { + attributes.put(attribute.getName(), attribute.getValue()); + } + node.setAttributes(attributes); + } + + children.add(node); + } else { + // for invisible project, the package root name may become empty after removing the '_', + // in this case, we skip this root node from showing in the explorer and keep finding its children. + PackageParams subQuery = new PackageParams(NodeKind.PACKAGEROOT, query.getProjectUri(), + query.getPath(), fragmentRoot.getHandleIdentifier()); + List packageNodes = getPackages(subQuery, pm); + children.addAll(packageNodes); } } return children; diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageParams.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageParams.java index 649a518f..a772ca59 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageParams.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageParams.java @@ -27,35 +27,22 @@ public class PackageParams { private String handlerIdentifier; - private String rootPath; - public PackageParams() { } - public String getHandlerIdentifier() { - return handlerIdentifier; - } - - public void setHandlerIdentifier(String handlerIdentifier) { - this.handlerIdentifier = handlerIdentifier; - } - public PackageParams(NodeKind kind, String projectUri) { this.kind = kind; this.projectUri = projectUri; } public PackageParams(NodeKind kind, String projectUri, String path) { - this.kind = kind; - this.projectUri = projectUri; + this(kind, projectUri); this.path = path; } - public PackageParams(NodeKind kind, String projectUri, String path, String rootPath) { - this.kind = kind; - this.projectUri = projectUri; - this.path = path; - this.rootPath = rootPath; + public PackageParams(NodeKind kind, String projectUri, String path, String handlerIdentifier) { + this(kind, projectUri, path); + this.handlerIdentifier = handlerIdentifier; } public NodeKind getKind() { @@ -82,11 +69,12 @@ public void setPath(String nodePath) { this.path = nodePath; } - public String getRootPath() { - return rootPath; + public String getHandlerIdentifier() { + return handlerIdentifier; } - public void setRootPath(String rootPath) { - this.rootPath = rootPath; + public void setHandlerIdentifier(String handlerIdentifier) { + this.handlerIdentifier = handlerIdentifier; } + } diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index 00378758..f2dc237e 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -14,6 +14,7 @@ import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -84,13 +85,19 @@ public MainClassInfo(String name, String path) { public static List listProjects(List arguments, IProgressMonitor monitor) { String workspaceUri = (String) arguments.get(0); - IPath workspacePath = ResourceUtils.canonicalFilePathFromURI(workspaceUri); - String invisibleProjectName = ProjectUtils.getWorkspaceInvisibleProjectName(workspacePath); + IPath workspaceFolderPath = ResourceUtils.canonicalFilePathFromURI(workspaceUri); + String invisibleProjectName = ProjectUtils.getWorkspaceInvisibleProjectName(workspaceFolderPath); IProject[] projects = getWorkspaceRoot().getProjects(); ArrayList children = new ArrayList<>(); + List paths = Collections.singletonList(workspaceFolderPath); for (IProject project : projects) { - if (!project.isAccessible() || !ProjectUtils.isJavaProject(project) || Objects.equals(project, JavaLanguageServerPlugin.getProjectsManager().getDefaultProject())) { + if (!project.isAccessible() || !ProjectUtils.isJavaProject(project)) { + continue; + } + // Ignore all the projects that's not contained in the workspace folder, except for the invisible project. + // This check is needed in multi-root scenario. + if ((!ResourceUtils.isContainedIn(project.getLocation(), paths) && !Objects.equals(project.getName(), invisibleProjectName))) { continue; } PackageNode projectNode = PackageNode.createNodeForProject(JavaCore.create(project)); 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 aa439813..817e4be6 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 @@ -15,6 +15,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; @@ -32,6 +33,7 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.ls.core.internal.JDTUtils; +import org.eclipse.jdt.ls.core.internal.ProjectUtils; import com.microsoft.jdtls.ext.core.ExtUtils; import com.microsoft.jdtls.ext.core.JdtlsExtActivator; @@ -194,7 +196,8 @@ public static PackageNode createNodeForVirtualContainer(IPackageFragmentRoot pkg } - public static PackageNode createNodeForPackageFragmentRoot(IPackageFragmentRoot pkgRoot) throws JavaModelException { + public static PackageRootNode createNodeForPackageFragmentRoot(IPackageFragmentRoot pkgRoot) throws JavaModelException { + String displayName = pkgRoot.getElementName(); boolean isSourcePath = pkgRoot.getKind() == IPackageFragmentRoot.K_SOURCE; if (!isSourcePath) { IClasspathEntry entry = pkgRoot.getRawClasspathEntry(); @@ -202,11 +205,19 @@ public static PackageNode createNodeForPackageFragmentRoot(IPackageFragmentRoot if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { return createNodeForClasspathVariable(entry); } else { - return new PackageRootNode(pkgRoot, pkgRoot.getElementName(), NodeKind.PACKAGEROOT); + return new PackageRootNode(pkgRoot, displayName, NodeKind.PACKAGEROOT); } } else { - return new PackageRootNode(pkgRoot, - ExtUtils.removeProjectSegment(pkgRoot.getJavaProject().getElementName(), pkgRoot.getPath()).toPortableString(), NodeKind.PACKAGEROOT); + IJavaProject javaProject = pkgRoot.getJavaProject(); + IPath relativePath = pkgRoot.getPath(); + if (pkgRoot.getJavaProject().getPath().isPrefixOf(relativePath)) { + relativePath = relativePath.makeRelativeTo(javaProject.getPath()); + } + if (Objects.equals(ProjectUtils.WORKSPACE_LINK, relativePath.segment(0))) { + relativePath = relativePath.removeFirstSegments(1); // Remove the '_' prefix + } + displayName = relativePath.toPortableString(); + return new PackageRootNode(pkgRoot, displayName, NodeKind.PACKAGEROOT); } } @@ -278,7 +289,7 @@ public static PackageNode createNodeForPrimaryType(IType type) { * referenced variable's classpath entry * @return correspond package node */ - public static PackageNode createNodeForClasspathVariable(IClasspathEntry classpathEntry) { + public static PackageRootNode createNodeForClasspathVariable(IClasspathEntry classpathEntry) { IClasspathEntry entry = JavaCore.getResolvedClasspathEntry(classpathEntry); String name = classpathEntry.getPath().toPortableString(); String path = entry.getPath().toPortableString(); diff --git a/src/syncHandler.ts b/src/syncHandler.ts index e3cd1adf..ddd12810 100644 --- a/src/syncHandler.ts +++ b/src/syncHandler.ts @@ -5,12 +5,11 @@ import * as path from "path"; import { commands, Disposable, FileSystemWatcher, Uri, workspace } from "vscode"; import { instrumentOperation } from "vscode-extension-telemetry-wrapper"; import { Commands } from "./commands"; +import { NodeKind } from "./java/nodeData"; import { Settings } from "./settings"; import { DataNode } from "./views/dataNode"; import { ExplorerNode } from "./views/explorerNode"; -import { HierarchicalPackageRootNode } from "./views/hierarchicalPackageRootNode"; import { explorerNodeCache } from "./views/nodeCache/explorerNodeCache"; -import { PackageRootNode } from "./views/packageRootNode"; const ENABLE_AUTO_REFRESH: string = "java.view.package.enableAutoRefresh"; const DISABLE_AUTO_REFRESH: string = "java.view.package.disableAutoRefresh"; @@ -75,7 +74,7 @@ class SyncHandler implements Disposable { if (Settings.isHierarchicalView()) { // TODO: has to get the hierarchical package root node due to the java side implementation // because currently it will only get the types for a package node but no child packages. - while (!(node instanceof HierarchicalPackageRootNode)) { + while (node && node.nodeData.kind !== NodeKind.PackageRoot) { node = node.getParent(); } return node; @@ -88,7 +87,7 @@ class SyncHandler implements Disposable { } else { // the direct parent is not rendered in the explorer, the returned node // is other package fragment, we need to refresh the package fragment root. - while (!(node instanceof PackageRootNode)) { + while (node && node.nodeData.kind !== NodeKind.PackageRoot) { node = node.getParent(); } return node; diff --git a/src/views/dependencyDataProvider.ts b/src/views/dependencyDataProvider.ts index 3450168d..555207cb 100644 --- a/src/views/dependencyDataProvider.ts +++ b/src/views/dependencyDataProvider.ts @@ -120,6 +120,9 @@ export class DependencyDataProvider implements TreeDataProvider { } private doRefresh(element?: ExplorerNode): void { + if (!element) { + this._rootItems = undefined; + } explorerNodeCache.removeNodeChildren(element); this._onDidChangeTreeData.fire(element); } diff --git a/src/views/folderNode.ts b/src/views/folderNode.ts index a44fae18..23d9d271 100644 --- a/src/views/folderNode.ts +++ b/src/views/folderNode.ts @@ -19,7 +19,6 @@ export class FolderNode extends DataNode { kind: NodeKind.Folder, projectUri: this._project.uri, path: this.path, - rootPath: this._rootNode.path, handlerIdentifier: this._rootNode.handlerIdentifier, }); } diff --git a/src/views/packageNode.ts b/src/views/packageNode.ts index 37e2fbcf..36d197f4 100644 --- a/src/views/packageNode.ts +++ b/src/views/packageNode.ts @@ -21,7 +21,6 @@ export class PackageNode extends DataNode { kind: NodeKind.Package, projectUri: this._project.nodeData.uri, path: this.nodeData.name, - rootPath: this._rootNode.path, handlerIdentifier: this.nodeData.handlerIdentifier, }); } @@ -49,7 +48,7 @@ export class PackageNode extends DataNode { protected get contextValue(): string { const parentData = this._rootNode.nodeData; - if (parentData.entryKind === PackageRootKind.K_SOURCE) { + if (parentData.entryKind === PackageRootKind.K_SOURCE || parentData.kind === NodeKind.Project) { return `${Explorer.ContextValueType.Package}+source`; } else if (parentData.entryKind === PackageRootKind.K_BINARY) { return `${Explorer.ContextValueType.Package}+binary`; diff --git a/src/views/packageRootNode.ts b/src/views/packageRootNode.ts index 31a5428b..0b0d007e 100644 --- a/src/views/packageRootNode.ts +++ b/src/views/packageRootNode.ts @@ -25,7 +25,6 @@ export class PackageRootNode extends DataNode { return Jdtls.getPackageData({ kind: NodeKind.PackageRoot, projectUri: this._project.nodeData.uri, - rootPath: this.nodeData.path, handlerIdentifier: this.nodeData.handlerIdentifier, }); } diff --git a/src/views/projectNode.ts b/src/views/projectNode.ts index 68b208fe..69718fe4 100644 --- a/src/views/projectNode.ts +++ b/src/views/projectNode.ts @@ -1,15 +1,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { ThemeIcon } from "vscode"; +import { ThemeIcon, Uri, workspace } from "vscode"; import { Explorer } from "../constants"; import { ContainerEntryKind, IContainerNodeData } from "../java/containerNodeData"; +import { HierarchicalPackageNodeData } from "../java/hierarchicalPackageNodeData"; import { Jdtls } from "../java/jdtls"; import { INodeData, NodeKind } from "../java/nodeData"; +import { Settings } from "../settings"; import { ContainerNode } from "./containerNode"; import { DataNode } from "./dataNode"; import { ExplorerNode } from "./explorerNode"; +import { HierarchicalPackageNode } from "./hierarchicalPackageNode"; import { NodeFactory } from "./nodeFactory"; +import { PackageNode } from "./packageNode"; export class ProjectNode extends DataNode { @@ -17,6 +21,32 @@ export class ProjectNode extends DataNode { super(nodeData, parent); } + public async revealPaths(paths: INodeData[]): Promise { + if (workspace.getWorkspaceFolder(Uri.parse(this.uri))) { + return super.revealPaths(paths); + } + + // invisible project uri is not contained in workspace + const childNodeData = paths[0]; + const children: ExplorerNode[] = await this.getChildren(); + if (!children) { + return undefined; + } + + const childNode = children.find((child: DataNode) => { + if (child instanceof HierarchicalPackageNode) { + return childNodeData.name.startsWith(child.nodeData.name + ".") || childNodeData.name === child.nodeData.name; + } + return child.nodeData.name === childNodeData.name && child.path === childNodeData.path; + }); + + // don't shift when child node is an hierarchical node, or it may lose data of package node + if (!(childNode instanceof HierarchicalPackageNode)) { + paths.shift(); + } + return (childNode && paths.length > 0) ? childNode.revealPaths(paths) : childNode; + } + protected loadData(): Thenable { let result: INodeData[] = []; return Jdtls.getPackageData({ kind: NodeKind.Project, projectUri: this.nodeData.uri }).then((res) => { @@ -50,16 +80,32 @@ export class ProjectNode extends DataNode { protected createChildNodeList(): ExplorerNode[] { const result = []; + const packageData = []; if (this.nodeData.children && this.nodeData.children.length) { this.nodeData.children.forEach((data) => { if (data.kind === NodeKind.Container) { result.push(new ContainerNode(data, this, this)); } else if (data.kind === NodeKind.PackageRoot) { result.push(NodeFactory.createPackageRootNode(data, this, this)); + } else if (data.kind === NodeKind.Package) { + // Invisible project may have an empty named package root, in that case, + // we will skip it. + packageData.push(data); } }); } + if (packageData.length > 0) { + if (Settings.isHierarchicalView()) { + const data: HierarchicalPackageNodeData = HierarchicalPackageNodeData.createHierarchicalNodeDataByPackageList(packageData); + const hierarchicalPackageNodes: HierarchicalPackageNode[] = data === null ? [] : data.children.map((hierarchicalChildrenNode) => + new HierarchicalPackageNode(hierarchicalChildrenNode, this, this, this)); + result.push(...hierarchicalPackageNodes); + } else { + result.push(...packageData.map((data) => new PackageNode(data, this, this, this))); + } + } + result.sort((a: DataNode, b: DataNode) => { return b.nodeData.kind - a.nodeData.kind; });