From 28d6f904d0ed9aa5667c6b4e15c27a8f4cee06c9 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Tue, 28 Nov 2023 15:33:40 +0800 Subject: [PATCH 1/7] Adjust the user experience of new Java file --- .../jdtls/ext/core/model/PackageNode.java | 55 ++-- package-lock.json | 17 ++ package.json | 76 ++++++ package.nls.json | 9 +- package.nls.zh-cn.json | 9 +- package.nls.zh-tw.json | 4 +- src/commands.ts | 15 ++ src/explorerCommands/new.ts | 236 ++++++++++++++++-- src/extension.ts | 4 +- src/views/PrimaryTypeNode.ts | 5 + src/views/dependencyExplorer.ts | 19 +- src/views/packageNode.ts | 3 + src/views/packageRootNode.ts | 3 + src/views/projectNode.ts | 3 + 14 files changed, 415 insertions(+), 43 deletions(-) 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 618b391d..f945bfd0 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 @@ -36,6 +36,8 @@ import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.core.JrtPackageFragmentRoot; import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.ProjectUtils; @@ -68,9 +70,11 @@ public class PackageNode { public static final String REFERENCED_LIBRARIES_PATH = "REFERENCED_LIBRARIES_PATH"; private static final String REFERENCED_LIBRARIES_CONTAINER_NAME = "Referenced Libraries"; private static final String IMMUTABLE_REFERENCED_LIBRARIES_CONTAINER_NAME = "Referenced Libraries (Read-only)"; - public static final ContainerNode REFERENCED_LIBRARIES_CONTAINER = new ContainerNode(REFERENCED_LIBRARIES_CONTAINER_NAME, REFERENCED_LIBRARIES_PATH, + public static final ContainerNode REFERENCED_LIBRARIES_CONTAINER = new ContainerNode( + REFERENCED_LIBRARIES_CONTAINER_NAME, REFERENCED_LIBRARIES_PATH, NodeKind.CONTAINER, IClasspathEntry.CPE_CONTAINER); - public static final ContainerNode IMMUTABLE_REFERENCED_LIBRARIES_CONTAINER = new ContainerNode(IMMUTABLE_REFERENCED_LIBRARIES_CONTAINER_NAME, + public static final ContainerNode IMMUTABLE_REFERENCED_LIBRARIES_CONTAINER = new ContainerNode( + IMMUTABLE_REFERENCED_LIBRARIES_CONTAINER_NAME, REFERENCED_LIBRARIES_PATH, NodeKind.CONTAINER, IClasspathEntry.CPE_CONTAINER); /** @@ -85,6 +89,8 @@ public class PackageNode { */ private static final String UNMANAGED_FOLDER_NATURE_ID = "org.eclipse.jdt.ls.core.unmanagedFolder"; + private static final String MAX_SOURCE_VERSION = "MaxSourceVersion"; + /** * The name of the PackageNode. */ @@ -164,7 +170,8 @@ public static PackageNode createNodeForProject(IJavaElement javaElement) { return null; } IProject proj = javaElement.getJavaProject().getProject(); - PackageNode projectNode = new PackageNode(proj.getName(), proj.getFullPath().toPortableString(), NodeKind.PROJECT); + PackageNode projectNode = new PackageNode(proj.getName(), proj.getFullPath().toPortableString(), + NodeKind.PROJECT); projectNode.setUri(ProjectUtils.getProjectRealFolder(proj).toFile().toURI().toString()); try { List natureIds = new ArrayList<>(Arrays.asList(proj.getDescription().getNatureIds())); @@ -173,6 +180,10 @@ public static PackageNode createNodeForProject(IJavaElement javaElement) { projectNode.setMetaDataValue(UNMANAGED_FOLDER_INNER_PATH, proj.getLocationURI().toString()); } projectNode.setMetaDataValue(NATURE_ID, natureIds); + String sourceVersion = javaElement.getJavaProject().getOption(JavaCore.COMPILER_SOURCE, true); + int jdkLevel = (int) (CompilerOptions.versionToJdkLevel(sourceVersion, true) >>> 16); + int majorVersion = Math.max(0, jdkLevel - ClassFileConstants.MAJOR_VERSION_0); + projectNode.setMetaDataValue(MAX_SOURCE_VERSION, majorVersion); } catch (CoreException e) { // do nothing } @@ -201,7 +212,8 @@ public static PackageNode createNodeForResource(IResource resource) { } public static PackageNode createNodeForPackageFragment(IPackageFragment packageFragment) { - PackageNode fragmentNode = new PackageNode(packageFragment.getElementName(), packageFragment.getPath().toPortableString(), NodeKind.PACKAGE); + PackageNode fragmentNode = new PackageNode(packageFragment.getElementName(), + packageFragment.getPath().toPortableString(), NodeKind.PACKAGE); fragmentNode.setHandlerIdentifier(packageFragment.getHandleIdentifier()); if (packageFragment.getResource() != null) { fragmentNode.setUri(packageFragment.getResource().getLocationURI().toString()); @@ -215,16 +227,19 @@ public static PackageNode createNodeForVirtualContainer(IPackageFragmentRoot pkg IClasspathEntry entry = pkgRoot.getRawClasspathEntry(); IClasspathContainer container = JavaCore.getClasspathContainer(entry.getPath(), pkgRoot.getJavaProject()); PackageNode containerNode = null; - if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY || entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { + if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY + || entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { containerNode = REFERENCED_LIBRARIES_CONTAINER; } else { - containerNode = new ContainerNode(container.getDescription(), container.getPath().toPortableString(), NodeKind.CONTAINER, entry.getEntryKind()); + containerNode = new ContainerNode(container.getDescription(), container.getPath().toPortableString(), + NodeKind.CONTAINER, entry.getEntryKind()); } return containerNode; } - public static PackageRootNode createNodeForPackageFragmentRoot(IPackageFragmentRoot pkgRoot) throws JavaModelException { + public static PackageRootNode createNodeForPackageFragmentRoot(IPackageFragmentRoot pkgRoot) + throws JavaModelException { PackageRootNode node; String displayName = pkgRoot.getElementName(); boolean isSourcePath = pkgRoot.getKind() == IPackageFragmentRoot.K_SOURCE; @@ -271,14 +286,16 @@ public static PackageRootNode createNodeForPackageFragmentRoot(IPackageFragmentR * Get the correspond node of classpath, it may be container or a package root. * * @param classpathEntry - * classpath entry + * classpath entry * @param javaProject - * correspond java project + * correspond java project * @param nodeKind - * could be CONTAINER or PACKAGEROOT(for referenced libraries) + * could be CONTAINER or PACKAGEROOT(for referenced + * libraries) * @return correspond PackageNode of classpath entry */ - public static PackageNode createNodeForClasspathEntry(IClasspathEntry classpathEntry, IJavaProject javaProject, NodeKind nodeKind) { + public static PackageNode createNodeForClasspathEntry(IClasspathEntry classpathEntry, IJavaProject javaProject, + NodeKind nodeKind) { try { IClasspathEntry entry = JavaCore.getResolvedClasspathEntry(classpathEntry); IClasspathContainer container = JavaCore.getClasspathContainer(entry.getPath(), javaProject); @@ -289,17 +306,18 @@ public static PackageNode createNodeForClasspathEntry(IClasspathEntry classpathE if (container != null) { PackageNode node = null; if (nodeKind == NodeKind.CONTAINER) { - node = new ContainerNode(container.getDescription(), container.getPath().toPortableString(), nodeKind, entry.getEntryKind()); + node = new ContainerNode(container.getDescription(), container.getPath().toPortableString(), + nodeKind, entry.getEntryKind()); final URI containerURI = ExtUtils.getContainerURI(javaProject, container); node.setUri(containerURI != null ? containerURI.toString() : null); } else if (nodeKind == NodeKind.PACKAGEROOT) { // ClasspathEntry for referenced jar files // Use package name as package root name String[] pathSegments = container.getPath().segments(); node = new PackageRootNode( - pathSegments[pathSegments.length - 1], - container.getPath().toPortableString(), - container.getPath().toFile().toURI().toString(), - nodeKind, IPackageFragmentRoot.K_BINARY); + pathSegments[pathSegments.length - 1], + container.getPath().toPortableString(), + container.getPath().toFile().toURI().toString(), + nodeKind, IPackageFragmentRoot.K_BINARY); } return node; } @@ -310,7 +328,8 @@ public static PackageNode createNodeForClasspathEntry(IClasspathEntry classpathE } public static PackageNode createNodeForPrimaryType(IType type) { - PackageNode primaryTypeNode = new PackageNode(type.getElementName(), type.getPath().toPortableString(), NodeKind.PRIMARYTYPE); + PackageNode primaryTypeNode = new PackageNode(type.getElementName(), type.getPath().toPortableString(), + NodeKind.PRIMARYTYPE); try { if (type.isEnum()) { @@ -332,7 +351,7 @@ public static PackageNode createNodeForPrimaryType(IType type) { * Get correspond node of referenced variable. * * @param classpathEntry - * referenced variable's classpath entry + * referenced variable's classpath entry * @return correspond package node */ public static PackageRootNode createNodeForClasspathVariable(IClasspathEntry classpathEntry) { diff --git a/package-lock.json b/package-lock.json index ea42e4d9..64d6a4da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "await-lock": "^2.2.2", + "fmtr": "^1.1.4", "fs-extra": "^10.1.0", "globby": "^13.1.3", "lodash": "^4.17.21", @@ -2052,6 +2053,14 @@ "flat": "cli.js" } }, + "node_modules/fmtr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fmtr/-/fmtr-1.1.4.tgz", + "integrity": "sha512-5qCv7eLlkR7LUDrcQdx0vGFo8tLgwFtO0JHkX9xfQm+DNnXpk9zQNSIldKlZ2VPZBwxTM9GB1esyeQuxhCVvmQ==", + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/follow-redirects": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", @@ -6879,6 +6888,14 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, + "fmtr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fmtr/-/fmtr-1.1.4.tgz", + "integrity": "sha512-5qCv7eLlkR7LUDrcQdx0vGFo8tLgwFtO0JHkX9xfQm+DNnXpk9zQNSIldKlZ2VPZBwxTM9GB1esyeQuxhCVvmQ==", + "requires": { + "lodash": "^4.17.21" + } + }, "follow-redirects": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", diff --git a/package.json b/package.json index aba8da47..95b00002 100644 --- a/package.json +++ b/package.json @@ -192,6 +192,36 @@ "category": "Java", "icon": "$(add)" }, + { + "command": "java.view.package.newJavaInterface", + "title": "%contributes.commands.java.view.package.newJavaInterface%", + "category": "Java", + "icon": "$(add)" + }, + { + "command": "java.view.package.newJavaEnum", + "title": "%contributes.commands.java.view.package.newJavaEnum%", + "category": "Java", + "icon": "$(add)" + }, + { + "command": "java.view.package.newJavaRecord", + "title": "%contributes.commands.java.view.package.newJavaRecord%", + "category": "Java", + "icon": "$(add)" + }, + { + "command": "java.view.package.newJavaAnnotation", + "title": "%contributes.commands.java.view.package.newJavaAnnotation%", + "category": "Java", + "icon": "$(add)" + }, + { + "command": "java.view.package.newJavaAbstractClass", + "title": "%contributes.commands.java.view.package.newJavaAbstractClass%", + "category": "Java", + "icon": "$(add)" + }, { "command": "java.view.package.newPackage", "title": "%contributes.commands.java.view.package.newPackage%", @@ -395,6 +425,26 @@ "command": "java.view.package.newJavaClass", "when": "false" }, + { + "command": "java.view.package.newJavaInterface", + "when": "false" + }, + { + "command": "java.view.package.newJavaEnum", + "when": "false" + }, + { + "command": "java.view.package.newJavaRecord", + "when": "false" + }, + { + "command": "java.view.package.newJavaAnnotation", + "when": "false" + }, + { + "command": "java.view.package.newJavaAbstractClass", + "when": "false" + }, { "command": "java.view.package.newPackage", "when": "false" @@ -644,6 +694,31 @@ "group": "new@10", "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)" }, + { + "command": "java.view.package.newJavaInterface", + "group": "new@11", + "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)" + }, + { + "command": "java.view.package.newJavaEnum", + "group": "new@12", + "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)" + }, + { + "command": "java.view.package.newJavaRecord", + "group": "new@13", + "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/) && viewItem =~ /java:.*\\+allowRecord\\b/" + }, + { + "command": "java.view.package.newJavaAnnotation", + "group": "new@14", + "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)" + }, + { + "command": "java.view.package.newJavaAbstractClass", + "group": "new@15", + "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)" + }, { "command": "java.view.package.newPackage", "group": "new@20", @@ -895,6 +970,7 @@ }, "dependencies": { "await-lock": "^2.2.2", + "fmtr": "^1.1.4", "fs-extra": "^10.1.0", "globby": "^13.1.3", "lodash": "^4.17.21", diff --git a/package.nls.json b/package.nls.json index c876d1f8..1cf98d01 100644 --- a/package.nls.json +++ b/package.nls.json @@ -22,10 +22,15 @@ "contributes.commands.java.view.package.copyFilePath": "Copy Path", "contributes.commands.java.view.package.copyRelativeFilePath": "Copy Relative Path", "contributes.commands.java.view.package.new": "New...", - "contributes.commands.java.view.package.newJavaClass": "Java Class...", + "contributes.commands.java.view.package.newJavaClass": "Class...", + "contributes.commands.java.view.package.newJavaInterface": "Interface...", + "contributes.commands.java.view.package.newJavaEnum": "Enum...", + "contributes.commands.java.view.package.newJavaRecord": "Record...", + "contributes.commands.java.view.package.newJavaAnnotation": "Annotation...", + "contributes.commands.java.view.package.newJavaAbstractClass": "Abstract Class...", "contributes.commands.java.view.package.newPackage": "Package...", "contributes.commands.java.view.package.newFile": "File...", - "contributes.commands.java.view.package.newFolder": "Folder", + "contributes.commands.java.view.package.newFolder": "Folder...", "contributes.commands.java.view.package.renameFile": "Rename", "contributes.commands.java.view.package.moveFileToTrash": "Delete", "contributes.commands.java.view.package.deleteFilePermanently": "Delete Permanently", diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index ed5f3403..f9253872 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -22,10 +22,15 @@ "contributes.commands.java.view.package.copyFilePath": "复制路径", "contributes.commands.java.view.package.copyRelativeFilePath": "复制相对路径", "contributes.commands.java.view.package.new": "创建...", - "contributes.commands.java.view.package.newJavaClass": "Java 类...", + "contributes.commands.java.view.package.newJavaClass": "类...", + "contributes.commands.java.view.package.newJavaInterface": "接口类型...", + "contributes.commands.java.view.package.newJavaEnum": "枚举类型...", + "contributes.commands.java.view.package.newJavaRecord": "记录类型...", + "contributes.commands.java.view.package.newJavaAnnotation": "注解类型...", + "contributes.commands.java.view.package.newJavaAbstractClass": "抽象类型...", "contributes.commands.java.view.package.newPackage": "包...", "contributes.commands.java.view.package.newFile": "文件...", - "contributes.commands.java.view.package.newFolder": "文件夹", + "contributes.commands.java.view.package.newFolder": "文件夹...", "contributes.commands.java.view.package.renameFile": "重命名", "contributes.commands.java.view.package.moveFileToTrash": "删除", "contributes.commands.java.view.package.deleteFilePermanently": "永久删除", diff --git a/package.nls.zh-tw.json b/package.nls.zh-tw.json index 94ac2499..c40519b6 100644 --- a/package.nls.zh-tw.json +++ b/package.nls.zh-tw.json @@ -20,10 +20,10 @@ "contributes.commands.java.view.package.copyFilePath": "複製路徑", "contributes.commands.java.view.package.copyRelativeFilePath": "複製相對路徑", "contributes.commands.java.view.package.new": "建立...", - "contributes.commands.java.view.package.newJavaClass": "Java 類別...", + "contributes.commands.java.view.package.newJavaClass": "類別...", "contributes.commands.java.view.package.newPackage": "套件...", "contributes.commands.java.view.package.newFile": "檔案...", - "contributes.commands.java.view.package.newFolder": "資料夾", + "contributes.commands.java.view.package.newFolder": "資料夾...", "contributes.commands.java.view.package.renameFile": "重新命名", "contributes.commands.java.view.package.moveFileToTrash": "刪除", "contributes.commands.java.view.package.deleteFilePermanently": "永久刪除", diff --git a/src/commands.ts b/src/commands.ts index c64f0f37..2e449862 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -42,6 +42,16 @@ export namespace Commands { export const VIEW_PACKAGE_NEW_JAVA_CLASS = "java.view.package.newJavaClass"; + export const VIEW_PACKAGE_NEW_JAVA_INTERFACE = "java.view.package.newJavaInterface"; + + export const VIEW_PACKAGE_NEW_JAVA_ENUM = "java.view.package.newJavaEnum"; + + export const VIEW_PACKAGE_NEW_JAVA_RECORD = "java.view.package.newJavaRecord"; + + export const VIEW_PACKAGE_NEW_JAVA_ANNOTATION = "java.view.package.newJavaAnnotation"; + + export const VIEW_PACKAGE_NEW_JAVA_ABSTRACT_CLASS = "java.view.package.newJavaAbstractClass"; + export const VIEW_PACKAGE_NEW_JAVA_PACKAGE = "java.view.package.newPackage"; export const VIEW_PACKAGE_RENAME_FILE = "java.view.package.renameFile"; @@ -133,6 +143,11 @@ export namespace Commands { export const GET_ALL_PROJECTS = "java.project.getAll"; export const BUILD_PROJECT = "java.project.build"; + + /** + * Get the project settings + */ + export const GET_PROJECT_SETTINGS = 'java.project.getSettings'; } export function executeJavaLanguageServerCommand(...rest: any[]) { diff --git a/src/explorerCommands/new.ts b/src/explorerCommands/new.ts index bc835034..a0266b30 100644 --- a/src/explorerCommands/new.ts +++ b/src/explorerCommands/new.ts @@ -2,8 +2,9 @@ // Licensed under the MIT license. import * as fse from "fs-extra"; +import { userInfo } from "os"; import * as path from "path"; -import { commands, Extension, extensions, languages, QuickPickItem, SnippetString, TextEditor, Uri, +import { commands, Extension, extensions, languages, Position, QuickPickItem, SnippetString, TextEditor, Uri, window, workspace, WorkspaceEdit, WorkspaceFolder } from "vscode"; import { Commands, PrimaryTypeNode } from "../../extension.bundle"; import { ExtensionName } from "../constants"; @@ -12,6 +13,49 @@ import { DataNode } from "../views/dataNode"; import { resourceRoots } from "../views/packageRootNode"; import { checkJavaQualifiedName } from "./utility"; import { sendError, setUserError } from "vscode-extension-telemetry-wrapper"; +const stringInterpolate = require("fmtr"); + +export class JavaType { + public static readonly Class: JavaType = new JavaType("Class", "class", "$(symbol-class)"); + public static readonly Interface: JavaType = new JavaType("Interface", "interface", "$(symbol-interface)"); + public static readonly Enum: JavaType = new JavaType("Enum", "enum", "$(symbol-enum)"); + public static readonly Record: JavaType = new JavaType("Record", "record", "$(symbol-class)"); + public static readonly Annotation: JavaType = new JavaType("Annotation", "@interface", "$(symbol-interface)"); + public static readonly AbstractClass: JavaType = new JavaType("Abstract Class", "abstract class", "$(symbol-class)"); + + public static readonly All: JavaType[] = [ + JavaType.Class, + JavaType.Interface, + JavaType.Enum, + JavaType.Record, + JavaType.Annotation, + JavaType.AbstractClass, + ]; + + public static fromDisplayName(label: string): JavaType | undefined { + if (label?.startsWith("$")) { + return JavaType.All.find((javaType) => `${javaType.icon} ${javaType.label}` === label); + } + + return JavaType.All.find((javaType) => javaType.label === label); + } + + public static getDisplayNames(includeIcon: boolean, includeRecord?: boolean): string[] { + return JavaType.All + .filter((javaType) => includeRecord || javaType !== JavaType.Record) + .map((javaType) => { + if (includeIcon) { + return `${javaType.icon} ${javaType.label}`; + } else { + return javaType.label; + } + }); + } + + private constructor(public readonly label: string, public readonly keyword: string, + public readonly icon: string) { + } +} export async function newResource(node: DataNode): Promise { const availableTypes: string[] = []; @@ -21,7 +65,9 @@ export async function newResource(node: DataNode): Promise { node.nodeData.kind === NodeKind.Package || node.nodeData.kind === NodeKind.PrimaryType || node.nodeData.kind === NodeKind.CompilationUnit) { - availableTypes.push("$(symbol-class) Java Class", "$(symbol-namespace) Package"); + const allowRecord = node.computeContextValue()?.includes("+allowRecord"); + availableTypes.push(...JavaType.getDisplayNames(true, allowRecord)); + availableTypes.push("$(symbol-namespace) Package"); } // add new file option @@ -44,9 +90,6 @@ export async function newResource(node: DataNode): Promise { ); switch (type) { - case "$(symbol-class) Java Class": - await newJavaClass(node); - break; case "$(symbol-namespace) Package": await newPackage(node); break; @@ -57,12 +100,40 @@ export async function newResource(node: DataNode): Promise { await newFolder(node); break; default: + const javaType = JavaType.fromDisplayName(type || ""); + if (javaType) { + await newJavaFileWithSpecificType(javaType, node); + } break; } } -// TODO: separate to two function to handle creation from menu bar and explorer. -export async function newJavaClass(node?: DataNode): Promise { +// Create a new Java file from the menu bar. +export async function newJavaFile(): Promise { + let packageFsPath: string | undefined = await inferPackageFsPath(); + if (packageFsPath === undefined) { + // User canceled + return; + } else if (packageFsPath.length === 0) { + return newUntitledJavaFile(); + } + + const includeRecord = !(await isVersionLessThan(Uri.file(packageFsPath).toString(), 14)); + const supportedTypes: string[] = JavaType.getDisplayNames(true, includeRecord); + const typeName: string | undefined = await window.showQuickPick(supportedTypes, + { + placeHolder: "Select the Java type you want to create", + ignoreFocusOut: true, + }); + if (!typeName) { + return; + } + + newJavaFile0(packageFsPath, JavaType.fromDisplayName(typeName)); +} + +// Create a new Java file from the context menu of Java Projects view. +export async function newJavaFileWithSpecificType(javaType: JavaType, node?: DataNode): Promise { let packageFsPath: string | undefined; if (!node) { packageFsPath = await inferPackageFsPath(); @@ -70,10 +141,8 @@ export async function newJavaClass(node?: DataNode): Promise { if (!node?.uri || !canCreateClass(node)) { return; } - packageFsPath = await getPackageFsPath(node); } - if (packageFsPath === undefined) { // User canceled return; @@ -81,8 +150,16 @@ export async function newJavaClass(node?: DataNode): Promise { return newUntitledJavaFile(); } + newJavaFile0(packageFsPath, javaType); +} + +async function newJavaFile0(packageFsPath: string, javaType: JavaType | undefined) { + if (!javaType) { + return; + } + const className: string | undefined = await window.showInputBox({ - placeHolder: "Enter the Java file name for class/interface/enum/record/@interface", + placeHolder: `Input the ${javaType.label.toLowerCase()} name`, ignoreFocusOut: true, validateInput: async (value: string): Promise => { const checkMessage: string = checkJavaQualifiedName(value); @@ -102,12 +179,86 @@ export async function newJavaClass(node?: DataNode): Promise { return; } - // `workspace.applyEdit()` will trigger a workspace file event, and let the - // vscode-java extension to handle the type: class, interface or enum. - const workspaceEdit: WorkspaceEdit = new WorkspaceEdit(); const fsPath: string = getNewFilePath(packageFsPath, className); - workspaceEdit.createFile(Uri.file(fsPath)); - workspace.applyEdit(workspaceEdit); + const packageName = await resolvePackageName(fsPath); + await newJavaFileWithContents(fsPath, javaType, packageName); +} + +// New File implementation is copied from +// https://github.com/redhat-developer/vscode-java/blob/86bf3ae02f4f457184e6cc217f20240f9882dde9/src/fileEventHandler.ts#L66 +async function newJavaFileWithContents(fsPath: string, javaType: JavaType, packageName: string) { + const snippets: string[] = []; + const formatNumber = (num: number) => num > 9 ? String(num) : `0${num}`; + const typeName: string = resolveTypeName(fsPath); + const isPackageInfo = typeName === 'package-info'; + const isModuleInfo = typeName === 'module-info'; + const date = new Date(); + const context: any = { + fileName: path.basename(fsPath), + packageName: "", + typeName: typeName, + user: userInfo().username, + date: date.toLocaleDateString(undefined, {month: "short", day: "2-digit", year: "numeric"}), + time: date.toLocaleTimeString(), + year: date.getFullYear(), + month: formatNumber(date.getMonth() + 1), + shortmonth: date.toLocaleDateString(undefined, {month: "short"}), + day: formatNumber(date.getDate()), + hour: formatNumber(date.getHours()), + minute: formatNumber(date.getMinutes()), + }; + + if (!isModuleInfo) { + context.packageName = packageName; + } + + const fileHeader = workspace.getConfiguration('java').get("templates.fileHeader"); + if (fileHeader && fileHeader.length) { + for (const template of fileHeader) { + snippets.push(stringInterpolate(template, context)); + } + } + + if (!isModuleInfo) { + if (context.packageName) { + snippets.push(`package ${context.packageName};`); + snippets.push(""); + } + } + + if (!isPackageInfo) { + const typeComment = workspace.getConfiguration('java').get("templates.typeComment"); + if (typeComment && typeComment.length) { + for (const template of typeComment) { + snippets.push(stringInterpolate(template, context)); + } + } + + if (isModuleInfo) { + snippets.push(`module {`); + } else { + snippets.push(`public ${javaType.keyword} ${typeName}${javaType === JavaType.Record ? "()" : ""} {`); + } + snippets.push(""); + snippets.push("}"); + snippets.push(""); + } + + const workspaceEdit: WorkspaceEdit = new WorkspaceEdit(); + const fsUri: Uri = Uri.file(fsPath); + workspaceEdit.createFile(fsUri); + workspaceEdit.insert(fsUri, new Position(0, 0), snippets.join("\n")); + await workspace.applyEdit(workspaceEdit); + const editor = await window.showTextDocument(fsUri); + if (editor) { + editor.document.save(); + } +} + +function resolveTypeName(filePath: string): string { + const fileName: string = path.basename(filePath); + const extName: string = path.extname(fileName); + return fileName.substring(0, fileName.length - extName.length); } async function newUntitledJavaFile(): Promise { @@ -186,6 +337,61 @@ function canCreateClass(node: DataNode): boolean { return false; } +const COMPLIANCE = "org.eclipse.jdt.core.compiler.compliance"; +async function isVersionLessThan(fileUri: string, targetVersion: number): Promise { + let projectSettings: any = {}; + try { + projectSettings = await commands.executeCommand( + Commands.EXECUTE_WORKSPACE_COMMAND, Commands.GET_PROJECT_SETTINGS, fileUri, [ COMPLIANCE ]); + } catch (err) { + // do nothing. + } + + let javaVersion = 0; + let complianceVersion = projectSettings[COMPLIANCE]; + if (complianceVersion) { + // Ignore '1.' prefix for legacy Java versions + if (complianceVersion.startsWith('1.')) { + complianceVersion = complianceVersion.substring(2); + } + + // look into the interesting bits now + const regexp = /\d+/g; + const match = regexp.exec(complianceVersion); + if (match) { + javaVersion = parseInt(match[0]); + } + } + + return javaVersion < targetVersion; +} + +async function resolvePackageName(filePath: string): Promise { + let sourcePaths: string[] = []; + const result: IListCommandResult = await commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.LIST_SOURCEPATHS); + if (result && result.data && result.data.length) { + sourcePaths = result.data.map((sourcePath) => sourcePath.path).sort((a, b) => b.length - a.length); + } + + if (!sourcePaths || !sourcePaths.length) { + return ""; + } + + for (const sourcePath of sourcePaths) { + if (isPrefix(sourcePath, filePath)) { + const relative = path.relative(sourcePath, path.dirname(filePath)); + return relative.replace(/[\/\\]/g, "."); + } + } + + return ""; +} + +function isPrefix(parentPath: string, filePath: string): boolean { + const relative = path.relative(parentPath, filePath); + return !relative || (!relative.startsWith('..') && !path.isAbsolute(relative)); +} + async function getPackageFsPath(node: DataNode): Promise { if (node.nodeData.kind === NodeKind.Project) { const childrenNodes: DataNode[] = await node.getChildren() as DataNode[]; diff --git a/src/extension.ts b/src/extension.ts index 4f4ad709..b9b1d0d7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -19,7 +19,7 @@ import { DependencyExplorer } from "./views/dependencyExplorer"; import { DiagnosticProvider } from "./tasks/buildArtifact/migration/DiagnosticProvider"; import { setContextForDeprecatedTasks, updateExportTaskType } from "./tasks/buildArtifact/migration/utils"; import { CodeActionProvider } from "./tasks/buildArtifact/migration/CodeActionProvider"; -import { newJavaClass } from "./explorerCommands/new"; +import { newJavaFile } from "./explorerCommands/new"; export async function activate(context: ExtensionContext): Promise { contextManager.initialize(context); @@ -47,7 +47,7 @@ async function activateExtension(_operationId: string, context: ExtensionContext context.subscriptions.push(tasks.registerTaskProvider(DeprecatedExportJarTaskProvider.type, new DeprecatedExportJarTaskProvider())); context.subscriptions.push(tasks.registerTaskProvider(BuildArtifactTaskProvider.exportJarType, new BuildArtifactTaskProvider())); context.subscriptions.push(tasks.registerTaskProvider(BuildTaskProvider.type, new BuildTaskProvider())); - context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.VIEW_MENUS_FILE_NEW_JAVA_CLASS, newJavaClass)); + context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.VIEW_MENUS_FILE_NEW_JAVA_CLASS, newJavaFile)); context.subscriptions.push(window.onDidChangeActiveTextEditor((e: TextEditor | undefined) => { setContextForReloadProject(e?.document); })); diff --git a/src/views/PrimaryTypeNode.ts b/src/views/PrimaryTypeNode.ts index 3b5540ca..e4ed193b 100644 --- a/src/views/PrimaryTypeNode.ts +++ b/src/views/PrimaryTypeNode.ts @@ -121,6 +121,11 @@ export class PrimaryTypeNode extends DataNode { contextValue += "+test"; } + if (this._rootNode?.getParent() instanceof ProjectNode + && (this._rootNode.getParent() as ProjectNode).nodeData?.metaData?.MaxSourceVersion >= 14) { + contextValue += "+allowRecord"; + } + return contextValue; } diff --git a/src/views/dependencyExplorer.ts b/src/views/dependencyExplorer.ts index 90384d14..8d973e9e 100644 --- a/src/views/dependencyExplorer.ts +++ b/src/views/dependencyExplorer.ts @@ -12,7 +12,7 @@ import { import { instrumentOperationAsVsCodeCommand, sendInfo } from "vscode-extension-telemetry-wrapper"; import { Commands } from "../commands"; import { deleteFiles } from "../explorerCommands/delete"; -import { newFile, newFolder, newJavaClass, newPackage, newResource } from "../explorerCommands/new"; +import { JavaType, newFile, newFolder, newJavaFileWithSpecificType, newPackage, newResource } from "../explorerCommands/new"; import { renameFile } from "../explorerCommands/rename"; import { getCmdNode } from "../explorerCommands/utility"; import { Jdtls } from "../java/jdtls"; @@ -117,7 +117,22 @@ export class DependencyExplorer implements Disposable { newResource(node); }), instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_CLASS, async (node?: DataNode) => { - newJavaClass(node); + newJavaFileWithSpecificType(JavaType.Class, node); + }), + instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_INTERFACE, async (node?: DataNode) => { + newJavaFileWithSpecificType(JavaType.Interface, node); + }), + instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_ENUM, async (node?: DataNode) => { + newJavaFileWithSpecificType(JavaType.Enum, node); + }), + instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_RECORD, async (node?: DataNode) => { + newJavaFileWithSpecificType(JavaType.Record, node); + }), + instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_ANNOTATION, async (node?: DataNode) => { + newJavaFileWithSpecificType(JavaType.Annotation, node); + }), + instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_ABSTRACT_CLASS, async (node?: DataNode) => { + newJavaFileWithSpecificType(JavaType.AbstractClass, node); }), instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_FILE, async (node: DataNode) => { newFile(node); diff --git a/src/views/packageNode.ts b/src/views/packageNode.ts index 501b369b..d30b0a29 100644 --- a/src/views/packageNode.ts +++ b/src/views/packageNode.ts @@ -50,6 +50,9 @@ export class PackageNode extends DataNode { let contextValue: string = Explorer.ContextValueType.Package; if (parentData.entryKind === PackageRootKind.K_SOURCE || parentData.kind === NodeKind.Project) { contextValue += "+source"; + if (this._project.nodeData.metaData?.MaxSourceVersion >= 14) { + contextValue += "+allowRecord"; + } } else if (parentData.entryKind === PackageRootKind.K_BINARY) { contextValue += "+binary"; } diff --git a/src/views/packageRootNode.ts b/src/views/packageRootNode.ts index 8ca4ca19..79e993d4 100644 --- a/src/views/packageRootNode.ts +++ b/src/views/packageRootNode.ts @@ -76,6 +76,9 @@ export class PackageRootNode extends DataNode { } else { contextValue += "+source"; } + if (this._project.nodeData.metaData?.MaxSourceVersion >= 14) { + contextValue += "+allowRecord"; + } return contextValue; } } diff --git a/src/views/projectNode.ts b/src/views/projectNode.ts index 2d127fb0..112000fd 100644 --- a/src/views/projectNode.ts +++ b/src/views/projectNode.ts @@ -96,6 +96,9 @@ export class ProjectNode extends DataNode { const attributeString: string = getProjectTypeAttributes(natureIds); contextValue += attributeString; } + if (this.nodeData.metaData?.MaxSourceVersion >= 14) { + contextValue += "+allowRecord"; + } return contextValue; } } From b296389f88562704668088a120a1ea63781013a0 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Tue, 28 Nov 2023 16:00:40 +0800 Subject: [PATCH 2/7] fix tslint --- src/explorerCommands/new.ts | 53 +++++++++++++++++---------------- src/views/dependencyExplorer.ts | 12 ++++---- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/explorerCommands/new.ts b/src/explorerCommands/new.ts index a0266b30..8bdac43c 100644 --- a/src/explorerCommands/new.ts +++ b/src/explorerCommands/new.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +import * as stringInterpolate from "fmtr"; import * as fse from "fs-extra"; import { userInfo } from "os"; import * as path from "path"; @@ -13,36 +14,35 @@ import { DataNode } from "../views/dataNode"; import { resourceRoots } from "../views/packageRootNode"; import { checkJavaQualifiedName } from "./utility"; import { sendError, setUserError } from "vscode-extension-telemetry-wrapper"; -const stringInterpolate = require("fmtr"); export class JavaType { - public static readonly Class: JavaType = new JavaType("Class", "class", "$(symbol-class)"); - public static readonly Interface: JavaType = new JavaType("Interface", "interface", "$(symbol-interface)"); - public static readonly Enum: JavaType = new JavaType("Enum", "enum", "$(symbol-enum)"); - public static readonly Record: JavaType = new JavaType("Record", "record", "$(symbol-class)"); - public static readonly Annotation: JavaType = new JavaType("Annotation", "@interface", "$(symbol-interface)"); - public static readonly AbstractClass: JavaType = new JavaType("Abstract Class", "abstract class", "$(symbol-class)"); - - public static readonly All: JavaType[] = [ - JavaType.Class, - JavaType.Interface, - JavaType.Enum, - JavaType.Record, - JavaType.Annotation, - JavaType.AbstractClass, + public static readonly CLASS: JavaType = new JavaType("Class", "class", "$(symbol-class)"); + public static readonly INTERFACE: JavaType = new JavaType("Interface", "interface", "$(symbol-interface)"); + public static readonly ENUM: JavaType = new JavaType("Enum", "enum", "$(symbol-enum)"); + public static readonly RECORD: JavaType = new JavaType("Record", "record", "$(symbol-class)"); + public static readonly ANNOTATION: JavaType = new JavaType("Annotation", "@interface", "$(symbol-interface)"); + public static readonly ABSTRACT_CLASS: JavaType = new JavaType("Abstract Class", "abstract class", "$(symbol-class)"); + + public static readonly ALL: JavaType[] = [ + JavaType.CLASS, + JavaType.INTERFACE, + JavaType.ENUM, + JavaType.RECORD, + JavaType.ANNOTATION, + JavaType.ABSTRACT_CLASS, ]; public static fromDisplayName(label: string): JavaType | undefined { if (label?.startsWith("$")) { - return JavaType.All.find((javaType) => `${javaType.icon} ${javaType.label}` === label); + return JavaType.ALL.find((javaType) => `${javaType.icon} ${javaType.label}` === label); } - return JavaType.All.find((javaType) => javaType.label === label); + return JavaType.ALL.find((javaType) => javaType.label === label); } public static getDisplayNames(includeIcon: boolean, includeRecord?: boolean): string[] { - return JavaType.All - .filter((javaType) => includeRecord || javaType !== JavaType.Record) + return JavaType.ALL + .filter((javaType) => includeRecord || javaType !== JavaType.RECORD) .map((javaType) => { if (includeIcon) { return `${javaType.icon} ${javaType.label}`; @@ -53,7 +53,7 @@ export class JavaType { } private constructor(public readonly label: string, public readonly keyword: string, - public readonly icon: string) { + public readonly icon: string) { } } @@ -110,7 +110,7 @@ export async function newResource(node: DataNode): Promise { // Create a new Java file from the menu bar. export async function newJavaFile(): Promise { - let packageFsPath: string | undefined = await inferPackageFsPath(); + const packageFsPath: string | undefined = await inferPackageFsPath(); if (packageFsPath === undefined) { // User canceled return; @@ -196,7 +196,7 @@ async function newJavaFileWithContents(fsPath: string, javaType: JavaType, packa const context: any = { fileName: path.basename(fsPath), packageName: "", - typeName: typeName, + typeName, user: userInfo().username, date: date.toLocaleDateString(undefined, {month: "short", day: "2-digit", year: "numeric"}), time: date.toLocaleTimeString(), @@ -237,7 +237,7 @@ async function newJavaFileWithContents(fsPath: string, javaType: JavaType, packa if (isModuleInfo) { snippets.push(`module {`); } else { - snippets.push(`public ${javaType.keyword} ${typeName}${javaType === JavaType.Record ? "()" : ""} {`); + snippets.push(`public ${javaType.keyword} ${typeName}${javaType === JavaType.RECORD ? "()" : ""} {`); } snippets.push(""); snippets.push("}"); @@ -341,7 +341,7 @@ const COMPLIANCE = "org.eclipse.jdt.core.compiler.compliance"; async function isVersionLessThan(fileUri: string, targetVersion: number): Promise { let projectSettings: any = {}; try { - projectSettings = await commands.executeCommand( + projectSettings = await commands.executeCommand( Commands.EXECUTE_WORKSPACE_COMMAND, Commands.GET_PROJECT_SETTINGS, fileUri, [ COMPLIANCE ]); } catch (err) { // do nothing. @@ -359,7 +359,7 @@ async function isVersionLessThan(fileUri: string, targetVersion: number): Promis const regexp = /\d+/g; const match = regexp.exec(complianceVersion); if (match) { - javaVersion = parseInt(match[0]); + javaVersion = parseInt(match[0], 10); } } @@ -368,7 +368,8 @@ async function isVersionLessThan(fileUri: string, targetVersion: number): Promis async function resolvePackageName(filePath: string): Promise { let sourcePaths: string[] = []; - const result: IListCommandResult = await commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.LIST_SOURCEPATHS); + const result: IListCommandResult = + await commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.LIST_SOURCEPATHS); if (result && result.data && result.data.length) { sourcePaths = result.data.map((sourcePath) => sourcePath.path).sort((a, b) => b.length - a.length); } diff --git a/src/views/dependencyExplorer.ts b/src/views/dependencyExplorer.ts index 8d973e9e..af248add 100644 --- a/src/views/dependencyExplorer.ts +++ b/src/views/dependencyExplorer.ts @@ -117,22 +117,22 @@ export class DependencyExplorer implements Disposable { newResource(node); }), instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_CLASS, async (node?: DataNode) => { - newJavaFileWithSpecificType(JavaType.Class, node); + newJavaFileWithSpecificType(JavaType.CLASS, node); }), instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_INTERFACE, async (node?: DataNode) => { - newJavaFileWithSpecificType(JavaType.Interface, node); + newJavaFileWithSpecificType(JavaType.INTERFACE, node); }), instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_ENUM, async (node?: DataNode) => { - newJavaFileWithSpecificType(JavaType.Enum, node); + newJavaFileWithSpecificType(JavaType.ENUM, node); }), instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_RECORD, async (node?: DataNode) => { - newJavaFileWithSpecificType(JavaType.Record, node); + newJavaFileWithSpecificType(JavaType.RECORD, node); }), instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_ANNOTATION, async (node?: DataNode) => { - newJavaFileWithSpecificType(JavaType.Annotation, node); + newJavaFileWithSpecificType(JavaType.ANNOTATION, node); }), instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_ABSTRACT_CLASS, async (node?: DataNode) => { - newJavaFileWithSpecificType(JavaType.AbstractClass, node); + newJavaFileWithSpecificType(JavaType.ABSTRACT_CLASS, node); }), instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_FILE, async (node: DataNode) => { newFile(node); From 7dfb8dd743e168e17dba81b02f2cdc3df74b0af7 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Tue, 28 Nov 2023 16:07:14 +0800 Subject: [PATCH 3/7] fix tslint --- src/explorerCommands/new.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/explorerCommands/new.ts b/src/explorerCommands/new.ts index 8bdac43c..da696b17 100644 --- a/src/explorerCommands/new.ts +++ b/src/explorerCommands/new.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import * as stringInterpolate from "fmtr"; import * as fse from "fs-extra"; import { userInfo } from "os"; import * as path from "path"; @@ -15,6 +14,9 @@ import { resourceRoots } from "../views/packageRootNode"; import { checkJavaQualifiedName } from "./utility"; import { sendError, setUserError } from "vscode-extension-telemetry-wrapper"; +// tslint:disable no-var-requires +const stringInterpolate = require("fmtr"); + export class JavaType { public static readonly CLASS: JavaType = new JavaType("Class", "class", "$(symbol-class)"); public static readonly INTERFACE: JavaType = new JavaType("Interface", "interface", "$(symbol-interface)"); From dfe040d55b35f273c27716cd855bc8662ee1f6e2 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Tue, 28 Nov 2023 17:40:39 +0800 Subject: [PATCH 4/7] fix the ui tests --- test/ui/command.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ui/command.test.ts b/test/ui/command.test.ts index 7894907a..c53c45bc 100644 --- a/test/ui/command.test.ts +++ b/test/ui/command.test.ts @@ -114,14 +114,14 @@ describe("Command Tests", function() { it("Test java.view.package.newJavaClass", async function() { let inputBox = await createJavaResource(); - const javaClassQuickPick = await inputBox.findQuickPick("Java Class"); + const javaClassQuickPick = await inputBox.findQuickPick("$(symbol-class) Class"); await javaClassQuickPick!.click(); assert.ok(await inputBox.getPlaceHolder() === "Choose a source folder", `InputBox "Choose a source folder" should appear`); const quickPick = await inputBox.findQuickPick("src/main/java"); assert.ok(quickPick, `Quickpick item "src/main/java" should be found`); await quickPick!.click(); inputBox = await InputBox.create(); - assert.ok(await inputBox.getPlaceHolder() === "Enter the Java file name for class/interface/enum/record/@interface", `InputBox "Enter the Java file name" should appear`); + assert.ok(await inputBox.getPlaceHolder() === "Input the class name", `InputBox "Input the class name" should appear`); await inputBox.setText("App2"); await inputBox.confirm(); await sleep(1000); From 36c12beed9f515e87e6f22f2be5cddc314d59400 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Tue, 28 Nov 2023 17:56:46 +0800 Subject: [PATCH 5/7] fix ui test --- test/ui/command.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ui/command.test.ts b/test/ui/command.test.ts index c53c45bc..1b9f1656 100644 --- a/test/ui/command.test.ts +++ b/test/ui/command.test.ts @@ -114,7 +114,7 @@ describe("Command Tests", function() { it("Test java.view.package.newJavaClass", async function() { let inputBox = await createJavaResource(); - const javaClassQuickPick = await inputBox.findQuickPick("$(symbol-class) Class"); + const javaClassQuickPick = await inputBox.findQuickPick("Class"); await javaClassQuickPick!.click(); assert.ok(await inputBox.getPlaceHolder() === "Choose a source folder", `InputBox "Choose a source folder" should appear`); const quickPick = await inputBox.findQuickPick("src/main/java"); From cce4e00fef251bed971a035c48dbd2a44f1a5f90 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Tue, 28 Nov 2023 18:11:49 +0800 Subject: [PATCH 6/7] fix ui test --- test/ui/command.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ui/command.test.ts b/test/ui/command.test.ts index 1b9f1656..37efdaee 100644 --- a/test/ui/command.test.ts +++ b/test/ui/command.test.ts @@ -114,7 +114,7 @@ describe("Command Tests", function() { it("Test java.view.package.newJavaClass", async function() { let inputBox = await createJavaResource(); - const javaClassQuickPick = await inputBox.findQuickPick("Class"); + const javaClassQuickPick = await inputBox.findQuickPick(0); await javaClassQuickPick!.click(); assert.ok(await inputBox.getPlaceHolder() === "Choose a source folder", `InputBox "Choose a source folder" should appear`); const quickPick = await inputBox.findQuickPick("src/main/java"); From 8bde32803f7333d8ce0e1fb02636e897b7b6ecc6 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Wed, 29 Nov 2023 15:52:26 +0800 Subject: [PATCH 7/7] Address comments --- ThirdPartyNotices.txt | 13 +++++++++++++ package.json | 18 +++++++++--------- src/explorerCommands/new.ts | 34 ++++++++++++++++++++++++---------- src/views/PrimaryTypeNode.ts | 2 +- src/views/packageNode.ts | 2 +- src/views/packageRootNode.ts | 2 +- src/views/projectNode.ts | 2 +- 7 files changed, 50 insertions(+), 23 deletions(-) diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 86dfa178..de26a4ca 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -13,6 +13,7 @@ This project incorporates components from the projects listed below. The origina 6. jprichardson/node-fs-extra (https://github.com/jprichardson/node-fs-extra) 7. lodash/lodash (https://github.com/lodash/lodash) 8. sindresorhus/globby (https://github.com/sindresorhus/globby) +9. tcort/fmtr (https://github.com/tcort/fmtr) %% Apache Commons Lang NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -571,3 +572,15 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF sindresorhus/globby NOTICES AND INFORMATION + +%% tcort/fmtr NOTICES AND INFORMATION BEGIN HERE +========================================= +ISC License + +Copyright (c) 2015-2021 Thomas Cort linuxgeek@gmail.com + +Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF tcort/fmtr NOTICES AND INFORMATION diff --git a/package.json b/package.json index 95b00002..0770e28e 100644 --- a/package.json +++ b/package.json @@ -691,46 +691,46 @@ "javaProject.new": [ { "command": "java.view.package.newJavaClass", - "group": "new@10", + "group": "new1@10", "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)" }, { "command": "java.view.package.newJavaInterface", - "group": "new@11", + "group": "new1@11", "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)" }, { "command": "java.view.package.newJavaEnum", - "group": "new@12", + "group": "new1@12", "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)" }, { "command": "java.view.package.newJavaRecord", - "group": "new@13", + "group": "new1@13", "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/) && viewItem =~ /java:.*\\+allowRecord\\b/" }, { "command": "java.view.package.newJavaAnnotation", - "group": "new@14", + "group": "new1@14", "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)" }, { "command": "java.view.package.newJavaAbstractClass", - "group": "new@15", + "group": "new1@15", "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)" }, { "command": "java.view.package.newPackage", - "group": "new@20", + "group": "new1@20", "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)" }, { "command": "java.view.package.newFile", - "group": "new@30" + "group": "new2@30" }, { "command": "java.view.package.newFolder", - "group": "new@40", + "group": "new2@40", "when": "view == javaProjectExplorer && (viewItem =~ /java:(file|folder|project)/ || viewItem =~ /java:(packageRoot)(?=.*?\\b\\+resource\\b)/)" } ] diff --git a/src/explorerCommands/new.ts b/src/explorerCommands/new.ts index da696b17..248f57e1 100644 --- a/src/explorerCommands/new.ts +++ b/src/explorerCommands/new.ts @@ -4,7 +4,7 @@ import * as fse from "fs-extra"; import { userInfo } from "os"; import * as path from "path"; -import { commands, Extension, extensions, languages, Position, QuickPickItem, SnippetString, TextEditor, Uri, +import { commands, Extension, extensions, languages, Position, QuickPickItem, QuickPickItemKind, SnippetString, TextEditor, Uri, window, workspace, WorkspaceEdit, WorkspaceFolder } from "vscode"; import { Commands, PrimaryTypeNode } from "../../extension.bundle"; import { ExtensionName } from "../constants"; @@ -60,7 +60,7 @@ export class JavaType { } export async function newResource(node: DataNode): Promise { - const availableTypes: string[] = []; + const availableTypes: QuickPickItem[] = []; // add options for Java nodes if (node.nodeData.kind === NodeKind.Project || (node.nodeData.kind === NodeKind.PackageRoot && !resourceRoots.includes(node.nodeData.name)) || @@ -68,19 +68,33 @@ export async function newResource(node: DataNode): Promise { node.nodeData.kind === NodeKind.PrimaryType || node.nodeData.kind === NodeKind.CompilationUnit) { const allowRecord = node.computeContextValue()?.includes("+allowRecord"); - availableTypes.push(...JavaType.getDisplayNames(true, allowRecord)); - availableTypes.push("$(symbol-namespace) Package"); + availableTypes.push(...JavaType.getDisplayNames(true, allowRecord).map((label) => { + return { + label, + }; + })); + availableTypes.push({ + label: "$(symbol-namespace) Package", + }); } + availableTypes.push({ + label: "", + kind: QuickPickItemKind.Separator, + }); // add new file option - availableTypes.push("$(file) File"); + availableTypes.push({ + label: "$(file) File", + }); // add new folder option if (node.nodeData.kind === NodeKind.Project || (node.nodeData.kind === NodeKind.PackageRoot && resourceRoots.includes(node.nodeData.name)) || node.nodeData.kind === NodeKind.Folder || node.nodeData.kind === NodeKind.File) { - availableTypes.push("$(folder) Folder"); + availableTypes.push({ + label: "$(folder) Folder", + }); } const type = await window.showQuickPick( @@ -91,7 +105,7 @@ export async function newResource(node: DataNode): Promise { } ); - switch (type) { + switch (type?.label) { case "$(symbol-namespace) Package": await newPackage(node); break; @@ -102,7 +116,7 @@ export async function newResource(node: DataNode): Promise { await newFolder(node); break; default: - const javaType = JavaType.fromDisplayName(type || ""); + const javaType = JavaType.fromDisplayName(type?.label || ""); if (javaType) { await newJavaFileWithSpecificType(javaType, node); } @@ -120,7 +134,7 @@ export async function newJavaFile(): Promise { return newUntitledJavaFile(); } - const includeRecord = !(await isVersionLessThan(Uri.file(packageFsPath).toString(), 14)); + const includeRecord = !(await isVersionLessThan(Uri.file(packageFsPath).toString(), 16)); const supportedTypes: string[] = JavaType.getDisplayNames(true, includeRecord); const typeName: string | undefined = await window.showQuickPick(supportedTypes, { @@ -383,7 +397,7 @@ async function resolvePackageName(filePath: string): Promise { for (const sourcePath of sourcePaths) { if (isPrefix(sourcePath, filePath)) { const relative = path.relative(sourcePath, path.dirname(filePath)); - return relative.replace(/[\/\\]/g, "."); + return relative.replace(/[/\\]/g, "."); } } diff --git a/src/views/PrimaryTypeNode.ts b/src/views/PrimaryTypeNode.ts index e4ed193b..8ee95223 100644 --- a/src/views/PrimaryTypeNode.ts +++ b/src/views/PrimaryTypeNode.ts @@ -122,7 +122,7 @@ export class PrimaryTypeNode extends DataNode { } if (this._rootNode?.getParent() instanceof ProjectNode - && (this._rootNode.getParent() as ProjectNode).nodeData?.metaData?.MaxSourceVersion >= 14) { + && (this._rootNode.getParent() as ProjectNode).nodeData?.metaData?.MaxSourceVersion >= 16) { contextValue += "+allowRecord"; } diff --git a/src/views/packageNode.ts b/src/views/packageNode.ts index d30b0a29..a6151435 100644 --- a/src/views/packageNode.ts +++ b/src/views/packageNode.ts @@ -50,7 +50,7 @@ export class PackageNode extends DataNode { let contextValue: string = Explorer.ContextValueType.Package; if (parentData.entryKind === PackageRootKind.K_SOURCE || parentData.kind === NodeKind.Project) { contextValue += "+source"; - if (this._project.nodeData.metaData?.MaxSourceVersion >= 14) { + if (this._project.nodeData.metaData?.MaxSourceVersion >= 16) { contextValue += "+allowRecord"; } } else if (parentData.entryKind === PackageRootKind.K_BINARY) { diff --git a/src/views/packageRootNode.ts b/src/views/packageRootNode.ts index 79e993d4..a4951a28 100644 --- a/src/views/packageRootNode.ts +++ b/src/views/packageRootNode.ts @@ -76,7 +76,7 @@ export class PackageRootNode extends DataNode { } else { contextValue += "+source"; } - if (this._project.nodeData.metaData?.MaxSourceVersion >= 14) { + if (this._project.nodeData.metaData?.MaxSourceVersion >= 16) { contextValue += "+allowRecord"; } return contextValue; diff --git a/src/views/projectNode.ts b/src/views/projectNode.ts index 112000fd..f48ef75a 100644 --- a/src/views/projectNode.ts +++ b/src/views/projectNode.ts @@ -96,7 +96,7 @@ export class ProjectNode extends DataNode { const attributeString: string = getProjectTypeAttributes(natureIds); contextValue += attributeString; } - if (this.nodeData.metaData?.MaxSourceVersion >= 14) { + if (this.nodeData.metaData?.MaxSourceVersion >= 16) { contextValue += "+allowRecord"; } return contextValue;