feat: Improve the user experience of new Java file. (#801)

This commit is contained in:
Jinbo Wang 2023-11-29 16:57:08 +08:00 коммит произвёл GitHub
Родитель 69268d938c
Коммит a0dcf41bca
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 464 добавлений и 62 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -36,6 +36,8 @@ import org.eclipse.jdt.core.IPackageFragmentRoot;
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 class PackageNode {
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<String> natureIds = new ArrayList<>(Arrays.asList(proj.getDescription().getNatureIds()));
@ -173,6 +180,10 @@ public class PackageNode {
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 class PackageNode {
}
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 class PackageNode {
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 class PackageNode {
* 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 class PackageNode {
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 class PackageNode {
}
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 class PackageNode {
* 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) {

17
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",

Просмотреть файл

@ -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"
@ -641,21 +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": "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": "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": "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": "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": "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)/)"
}
]
@ -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",

Просмотреть файл

@ -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",

Просмотреть файл

@ -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": "永久删除",

Просмотреть файл

@ -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": "永久刪除",

Просмотреть файл

@ -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[]) {

Просмотреть файл

@ -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, QuickPickItemKind, SnippetString, TextEditor, Uri,
window, workspace, WorkspaceEdit, WorkspaceFolder } from "vscode";
import { Commands, PrimaryTypeNode } from "../../extension.bundle";
import { ExtensionName } from "../constants";
@ -13,26 +14,87 @@ 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)");
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.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<void> {
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)) ||
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).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(
@ -43,10 +105,7 @@ export async function newResource(node: DataNode): Promise<void> {
}
);
switch (type) {
case "$(symbol-class) Java Class":
await newJavaClass(node);
break;
switch (type?.label) {
case "$(symbol-namespace) Package":
await newPackage(node);
break;
@ -57,23 +116,17 @@ export async function newResource(node: DataNode): Promise<void> {
await newFolder(node);
break;
default:
const javaType = JavaType.fromDisplayName(type?.label || "");
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<void> {
let packageFsPath: string | undefined;
if (!node) {
packageFsPath = await inferPackageFsPath();
} else {
if (!node?.uri || !canCreateClass(node)) {
return;
}
packageFsPath = await getPackageFsPath(node);
}
// Create a new Java file from the menu bar.
export async function newJavaFile(): Promise<void> {
const packageFsPath: string | undefined = await inferPackageFsPath();
if (packageFsPath === undefined) {
// User canceled
return;
@ -81,8 +134,48 @@ export async function newJavaClass(node?: DataNode): Promise<void> {
return newUntitledJavaFile();
}
const includeRecord = !(await isVersionLessThan(Uri.file(packageFsPath).toString(), 16));
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<void> {
let packageFsPath: string | undefined;
if (!node) {
packageFsPath = await inferPackageFsPath();
} else {
if (!node?.uri || !canCreateClass(node)) {
return;
}
packageFsPath = await getPackageFsPath(node);
}
if (packageFsPath === undefined) {
// User canceled
return;
} else if (packageFsPath.length === 0) {
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<string> => {
const checkMessage: string = checkJavaQualifiedName(value);
@ -102,12 +195,86 @@ export async function newJavaClass(node?: DataNode): Promise<void> {
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,
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<string[]>("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<string[]>("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<void> {
@ -186,6 +353,62 @@ function canCreateClass(node: DataNode): boolean {
return false;
}
const COMPLIANCE = "org.eclipse.jdt.core.compiler.compliance";
async function isVersionLessThan(fileUri: string, targetVersion: number): Promise<boolean> {
let projectSettings: any = {};
try {
projectSettings = await commands.executeCommand<any>(
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], 10);
}
}
return javaVersion < targetVersion;
}
async function resolvePackageName(filePath: string): Promise<string> {
let sourcePaths: string[] = [];
const result: IListCommandResult =
await commands.executeCommand<IListCommandResult>(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<string | undefined> {
if (node.nodeData.kind === NodeKind.Project) {
const childrenNodes: DataNode[] = await node.getChildren() as DataNode[];

Просмотреть файл

@ -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<void> {
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);
}));

Просмотреть файл

@ -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 >= 16) {
contextValue += "+allowRecord";
}
return contextValue;
}

Просмотреть файл

@ -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.ABSTRACT_CLASS, node);
}),
instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_FILE, async (node: DataNode) => {
newFile(node);

Просмотреть файл

@ -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 >= 16) {
contextValue += "+allowRecord";
}
} else if (parentData.entryKind === PackageRootKind.K_BINARY) {
contextValue += "+binary";
}

Просмотреть файл

@ -76,6 +76,9 @@ export class PackageRootNode extends DataNode {
} else {
contextValue += "+source";
}
if (this._project.nodeData.metaData?.MaxSourceVersion >= 16) {
contextValue += "+allowRecord";
}
return contextValue;
}
}

Просмотреть файл

@ -96,6 +96,9 @@ export class ProjectNode extends DataNode {
const attributeString: string = getProjectTypeAttributes(natureIds);
contextValue += attributeString;
}
if (this.nodeData.metaData?.MaxSourceVersion >= 16) {
contextValue += "+allowRecord";
}
return contextValue;
}
}

Просмотреть файл

@ -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(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");
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);