Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support right click context for viewer nodes #208

Merged
merged 11 commits into from
Dec 26, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IJarEntryResource;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.JarEntryDirectory;
import org.eclipse.jdt.internal.core.JarEntryFile;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;

public final class ExtUtils {
Expand Down Expand Up @@ -104,6 +107,18 @@ public static IPath removeProjectSegment(String projectElementName, IPath path)
return path;
}

public static URI getContainerURI(IJavaProject javaProject, IClasspathContainer container) throws CoreException {
switch (container.getKind()) {
case IClasspathContainer.K_DEFAULT_SYSTEM: // JRE Container
case IClasspathContainer.K_SYSTEM:
return JavaRuntime.getVMInstall(javaProject).getInstallLocation().toURI();
case IClasspathContainer.K_APPLICATION: // Plugin Container, Maven Container, etc
return null; // TODO: find out a good way to detect these containers' uri
default: // Persistent container (e.g. /src/main/java)
return container.getPath().toFile().toURI();
}
}

private static JarEntryFile findFileInJar(JarEntryDirectory directory, String path) {
for (IJarEntryResource child : directory.getChildren()) {
if (child instanceof JarEntryFile && child.getFullPath().toPortableString().equals(path)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,9 @@ public static List<PackageNode> resolvePath(List<Object> arguments, IProgressMon
IPackageFragmentRoot pkgRoot = (IPackageFragmentRoot) packageFragment.getParent();
PackageNode rootNode = null;

rootNode = new PackageRootNode(
rootNode = new PackageRootNode(pkgRoot,
ExtUtils.removeProjectSegment(packageFragment.getJavaProject().getElementName(), pkgRoot.getPath()).toPortableString(),
pkgRoot.getPath().toPortableString(), NodeKind.PACKAGEROOT, pkgRoot.getKind());
NodeKind.PACKAGEROOT);

result.add(PackageNode.createNodeForProject(packageFragment));
result.add(rootNode);
Expand Down Expand Up @@ -209,8 +209,8 @@ private static List<PackageNode> getParentAncestorNodes(IResource element) throw
IJavaElement javaElement = JavaCore.create(element);
if (javaElement instanceof IPackageFragmentRoot) {
IPackageFragmentRoot pkgRoot = (IPackageFragmentRoot) javaElement;
nodeList.add(0, new PackageRootNode(element.getProjectRelativePath().toPortableString(), pkgRoot.getPath().toPortableString(),
NodeKind.PACKAGEROOT, pkgRoot.getKind()));
nodeList.add(0, new PackageRootNode(pkgRoot,
element.getProjectRelativePath().toPortableString(), NodeKind.PACKAGEROOT));
nodeList.add(0, PackageNode.createNodeForProject(javaElement));
return nodeList;
} else if (javaElement instanceof IPackageFragment) {
Expand Down Expand Up @@ -280,8 +280,7 @@ private static List<PackageNode> getPackageFragmentRoots(PackageParams query, IP
if (fragmentRoot.getKind() == IPackageFragmentRoot.K_SOURCE) {
displayName = ExtUtils.removeProjectSegment(javaProject.getElementName(), fragmentRoot.getPath()).toPortableString();
}
PackageRootNode node = new PackageRootNode(displayName, fragmentRoot.getPath().toPortableString(), NodeKind.PACKAGEROOT,
fragmentRoot.getKind());
PackageRootNode node = new PackageRootNode(fragmentRoot, displayName, NodeKind.PACKAGEROOT);
children.add(node);
if (fragmentRoot instanceof JrtPackageFragmentRoot) {
node.setModuleName(fragmentRoot.getModuleDescription().getElementName());
Expand Down Expand Up @@ -440,7 +439,14 @@ private static List<PackageNode> convertToPackageNode(Object[] rootContent, IPac
List<PackageNode> result = new ArrayList<>();
for (Object root : rootContent) {
if (root instanceof IPackageFragment) {
result.add(PackageNode.createNodeForPackageFragment((IPackageFragment) root));
IPackageFragment fragment = (IPackageFragment) root;
PackageNode entry = PackageNode.createNodeForPackageFragment(fragment);
if (fragment.getResource() != null) {
entry.setUri(fragment.getResource().getLocationURI().toString());
} else {
entry.setUri(fragment.getPath().toFile().toURI().toString());
}
result.add(entry);
} else if (root instanceof IClassFile) {
IClassFile classFile = (IClassFile) root;
PackageNode entry = new PackageNode(classFile.getElementName(), null, NodeKind.TYPEROOT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,11 @@ public static PackageNode createNodeForPackageFragmentRoot(IPackageFragmentRoot
if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
return createNodeForClasspathVariable(entry);
} else {
return new PackageRootNode(pkgRoot.getElementName(), pkgRoot.getPath().toPortableString(), NodeKind.PACKAGEROOT, pkgRoot.getKind());
return new PackageRootNode(pkgRoot, pkgRoot.getElementName(), NodeKind.PACKAGEROOT);
}
} else {
return new PackageRootNode(ExtUtils.removeProjectSegment(pkgRoot.getJavaProject().getElementName(), pkgRoot.getPath()).toPortableString(),
pkgRoot.getPath().toPortableString(), NodeKind.PACKAGEROOT, pkgRoot.getKind());
return new PackageRootNode(pkgRoot,
ExtUtils.removeProjectSegment(pkgRoot.getJavaProject().getElementName(), pkgRoot.getPath()).toPortableString(), NodeKind.PACKAGEROOT);
}
}

Expand All @@ -161,17 +161,20 @@ public static PackageNode createNodeForClasspathEntry(IClasspathEntry classpathE
container = JavaCore.getClasspathContainer(entry.getPath(), javaProject);
}
if (container != null) {
switch (nodeKind) {
case CONTAINER:
return new ContainerNode(container.getDescription(), container.getPath().toPortableString(), nodeKind, entry.getEntryKind());
case PACKAGEROOT:
PackageNode node = null;
if (nodeKind == NodeKind.CONTAINER) {
node = new ContainerNode(container.getDescription(), container.getPath().toPortableString(), nodeKind, entry.getEntryKind());
node.setUri(ExtUtils.getContainerURI(javaProject, container).toString());
} else if (nodeKind == NodeKind.PACKAGEROOT) { // ClasspathEntry for referenced jar files
// Use package name as package root name
String[] pathSegments = container.getPath().segments();
return new PackageRootNode(pathSegments[pathSegments.length - 1], container.getPath().toPortableString(), nodeKind,
IPackageFragmentRoot.K_BINARY);
default:
return null;
node = new PackageRootNode(
pathSegments[pathSegments.length - 1],
container.getPath().toPortableString(),
container.getPath().toFile().toURI().toString(),
nodeKind, IPackageFragmentRoot.K_BINARY);
}
return node;
}
} catch (CoreException e) {
JdtlsExtActivator.logException("Problems when convert classpath entry to package node ", e);
Expand Down Expand Up @@ -201,7 +204,8 @@ public static PackageNode createNodeForClasspathVariable(IClasspathEntry classpa
IClasspathEntry entry = JavaCore.getResolvedClasspathEntry(classpathEntry);
String name = classpathEntry.getPath().toPortableString();
String path = entry.getPath().toPortableString();
return new PackageRootNode(name, path, NodeKind.PACKAGEROOT, IPackageFragmentRoot.K_BINARY);
String uri = entry.getPath().toFile().toURI().toString();
return new PackageRootNode(name, path, uri, NodeKind.PACKAGEROOT, IPackageFragmentRoot.K_BINARY);
}

public String getName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,30 @@

import java.util.Map;

import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;

public class PackageRootNode extends PackageNode {

private int entryKind;

private Map<String, String> attributes;

public PackageRootNode(String name, String path, NodeKind kind, int entryKind) {
public PackageRootNode(String name, String path, String uri, NodeKind kind, int entryKind) {
super(name, path, kind);
this.setUri(uri);
this.entryKind = entryKind;
}

public PackageRootNode(IPackageFragmentRoot pkgRoot, String name, NodeKind kind) throws JavaModelException {
this(name, pkgRoot.getPath().toPortableString(), null, kind, pkgRoot.getKind());
if (pkgRoot.getResource() != null) {
this.setUri(pkgRoot.getResource().getLocationURI().toString());
} else {
this.setUri(pkgRoot.getPath().toFile().toURI().toString());
}
}

public int getEntryType() {
return this.entryKind;
}
Expand Down
48 changes: 47 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"explorer"
],
"engines": {
"vscode": "^1.30.0"
"vscode": "^1.31.0"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -83,6 +83,21 @@
"dark": "images/dark/icon-link.svg",
"light": "images/light/icon-link.svg"
}
},
{
"command": "java.view.package.revealFileInOS",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revealFileInOS -> revealInExplorer

Copy link
Member Author

@Vigilans Vigilans Nov 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revealInExplorer is not appropriate since it is related to two built-in commands in VS Code: revealInExplorer and git.revealInExplorer - They all refer to the meaning of "Reveal the file node in VS Code File Explorer", not the OS explorer.

In contrast, revealFileInOS is a built-in command of VS Code to open a file in OS explorer, and our command just forwards the dependency node's uri to it, so this naming would be fine.

Copy link
Contributor

@testforstephen testforstephen Nov 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so why not to mount the VS Code builtin command to the dependency explorer directly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be best that we could mount the builtin command directly.
But the builtin command accepts a Uri argument, where context entry specified in package.json only allows for passing a ExplorerNode to the command binding.

(The TreeItem.command property can indeed specify the argument to be passed, but this is not the case with view/item/context entry)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems VS Code provides different names in different OSes, could you check whether we need keep consistent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for I didn't notice this comment...

As this file shows, VS Code does provide different names here:

  • Windows: Reveal In Explorer
  • Mac OS: Reveal In Finder
  • Linux: Open Containing Folder

And the Chinese translation uses that localization:

"vs/workbench/contrib/files/electron-browser/fileActions.contribution": {
    "revealInWindows": "在资源管理器中显示",
    "revealInMac": "在 Finder 中显示",
    "openContainer": "打开所在的文件夹",
    "filesCategory": "文件"
},

Currently I have no knowledge of how to utilize the localization here according to OS...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently i'm OK to keep the same one, if there is user complaint, then fix it.
A workaround is add three difference commands, and use when clause to enable the correct one.

"title": "%contributes.commands.java.view.package.revealFileInOS%",
"category": "Java"
},
{
"command": "java.view.package.copyFilePath",
"title": "%contributes.commands.java.view.package.copyFilePath%",
"category": "Java"
},
{
"command": "java.view.package.copyRelativeFilePath",
"title": "%contributes.commands.java.view.package.copyRelativeFilePath%",
"category": "Java"
}
],
"configuration": {
Expand Down Expand Up @@ -121,6 +136,20 @@
}
},
"menus": {
"commandPalette": [
{
"command": "java.view.package.revealFileInOS",
"when": "never"
},
{
"command": "java.view.package.copyFilePath",
"when": "never"
},
{
"command": "java.view.package.copyRelativeFilePath",
"when": "never"
}
],
"view/title": [
{
"command": "java.view.package.refresh",
Expand All @@ -147,6 +176,23 @@
"when": "view == javaDependencyExplorer && config.java.dependency.syncWithFolderExplorer == true",
"group": "navigation@0"
}
],
"view/item/context": [
{
"command": "java.view.package.revealFileInOS",
"when": "view == javaDependencyExplorer && viewItem =~ /java:.*?\\+uri/",
"group": "@1"
},
{
"command": "java.view.package.copyFilePath",
"when": "view == javaDependencyExplorer && viewItem =~ /java:.*?\\+uri/",
"group": "@2"
},
{
"command": "java.view.package.copyRelativeFilePath",
"when": "view == javaDependencyExplorer && viewItem =~ /java:.*?\\+uri/",
"group": "@2"
}
]
},
"views": {
Expand Down
3 changes: 3 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"contributes.commands.java.view.package.changeToHierarchicalPackageView":"Change to hierarchical package representation",
"contributes.commands.java.view.package.linkWithFolderExplorer":"Synchronize dependency viewer selection with folder explorer",
"contributes.commands.java.view.package.unlinkWithFolderExplorer":"Desynchronize dependency viewer selection with folder explorer",
"contributes.commands.java.view.package.revealFileInOS": "Reveal In Explorer",
"contributes.commands.java.view.package.copyFilePath": "Copy Path",
"contributes.commands.java.view.package.copyRelativeFilePath": "Copy Relative Path",
"configuration.java.dependency.title": "Java Dependency Configuration",
"configuration.java.dependency.showOutline": "Enable show outline in the Java Dependency explorer",
"configuration.java.dependency.syncWithFolderExplorer": "Synchronize dependency viewer selection with folder explorer",
Expand Down
3 changes: 3 additions & 0 deletions package.nls.zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"contributes.commands.java.view.package.changeToHierarchicalPackageView":"将 Java 包显示方式切换为分层显示",
"contributes.commands.java.view.package.linkWithFolderExplorer":"开启 Java 依赖项资源管理器与当前浏览文件的关联",
"contributes.commands.java.view.package.unlinkWithFolderExplorer":"关闭 Java 依赖项资源管理器与当前浏览文件的关联",
"contributes.commands.java.view.package.revealFileInOS": "打开所在的文件夹",
"contributes.commands.java.view.package.copyFilePath": "复制路径",
"contributes.commands.java.view.package.copyRelativeFilePath": "复制相对路径",
"configuration.java.dependency.title": "Java 依赖管理配置",
"configuration.java.dependency.showOutline": "在 Java 依赖项资源管理器中显示类成员大纲",
"configuration.java.dependency.syncWithFolderExplorer": "在 Java 依赖项资源管理器中同步关联当前打开的文件",
Expand Down
6 changes: 6 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export namespace Commands {

export const VIEW_PACKAGE_OUTLINE = "java.view.package.outline";

export const VIEW_PACKAGE_REVEAL_FILE_OS = "java.view.package.revealFileInOS";

export const VIEW_PACKAGE_COPY_FILE_PATH = "java.view.package.copyFilePath";

export const VIEW_PACKAGE_COPY_RELATIVE_FILE_PATH = "java.view.package.copyRelativeFilePath";

export const JAVA_PROJECT_CREATE = "java.project.create";

export const JAVA_PROJECT_LIST = "java.project.list";
Expand Down
6 changes: 6 additions & 0 deletions src/views/dependencyDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export class DependencyDataProvider implements TreeDataProvider<ExplorerNode> {

constructor(public readonly context: ExtensionContext) {
context.subscriptions.push(commands.registerCommand(Commands.VIEW_PACKAGE_REFRESH, (debounce?: boolean) => this.refreshWithLog(debounce)));
context.subscriptions.push(commands.registerCommand(Commands.VIEW_PACKAGE_REVEAL_FILE_OS,
(node: INodeData) => commands.executeCommand("revealFileInOS", Uri.parse(node.uri))));
context.subscriptions.push(commands.registerCommand(Commands.VIEW_PACKAGE_COPY_FILE_PATH,
(node: INodeData) => commands.executeCommand("copyFilePath", Uri.parse(node.uri))));
context.subscriptions.push(commands.registerCommand(Commands.VIEW_PACKAGE_COPY_RELATIVE_FILE_PATH,
(node: INodeData) => commands.executeCommand("copyRelativeFilePath", Uri.parse(node.uri))));
context.subscriptions.push(commands.registerCommand(Commands.VIEW_PACKAGE_OPEN_FILE,
instrumentOperation(Commands.VIEW_PACKAGE_OPEN_FILE, (_operationId, uri) => this.openFile(uri))));
context.subscriptions.push(commands.registerCommand(Commands.VIEW_PACKAGE_OUTLINE,
Expand Down