From 124e44b40775ad3d9e9d7f8e4121b85fe687d04b Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Tue, 11 Apr 2023 13:38:06 +0800 Subject: [PATCH 1/4] feat: Re-organize the new class & package commands - Group new class and new package commands to the submenu. - Use another command to handle new class creation from the menu bar. - Enable the class and package creation at the source type level. Signed-off-by: Sheng Chen --- package.json | 54 +++++++++++++++++-------------- package.nls.json | 6 ++-- package.nls.zh-cn.json | 6 ++-- package.nls.zh-tw.json | 6 ++-- src/commands.ts | 2 ++ src/explorerCommands/new.ts | 36 +++++++++++---------- src/extension.ts | 3 +- src/views/PrimaryTypeNode.ts | 56 +++++++++++++++++++++++++++++++++ test/suite/contextValue.test.ts | 8 ++--- 9 files changed, 126 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index a7ce21fa..291b67bd 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "explorer" ], "engines": { - "vscode": "^1.69.0" + "vscode": "^1.75.0" }, "repository": { "type": "git", @@ -39,10 +39,7 @@ "workspaceContains:settings.gradle.kts", "workspaceContains:*/settings.gradle.kts", "workspaceContains:.classpath", - "workspaceContains:*/.classpath", - "onCommand:_java.project.open", - "onCommand:java.project.create", - "onCommand:java.view.package.newJavaClass" + "workspaceContains:*/.classpath" ], "license": "MIT", "main": "./main.js", @@ -166,6 +163,12 @@ "title": "%contributes.commands.java.view.package.copyRelativeFilePath%", "category": "Java" }, + { + "command": "java.view.menus.file.newJavaClass", + "title": "%contributes.commands.java.view.menus.file.newJavaClass%", + "category": "Java", + "icon": "$(add)" + }, { "command": "java.view.package.newJavaClass", "title": "%contributes.commands.java.view.package.newJavaClass%", @@ -290,7 +293,7 @@ "menus": { "file/newFile": [ { - "command": "java.view.package.newJavaClass" + "command": "java.view.menus.file.newJavaClass" } ], "commandPalette": [ @@ -350,6 +353,10 @@ "command": "java.project.refreshLibraries", "when": "false" }, + { + "command": "java.view.package.newJavaClass", + "when": "false" + }, { "command": "java.view.package.newPackage", "when": "false" @@ -522,8 +529,8 @@ "group": "8_execution@6" }, { - "command": "java.view.package.newJavaClass", - "when": "view == javaProjectExplorer && viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)(?=.*?\\b\\+uri\\b)/", + "submenu": "javaProject.new", + "when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)(?=.*?\\b\\+uri\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/ || viewItem =~ /java:type(?=.*?\\b\\+source\\b)(?=.*?\\b\\+uri\\b)/)", "group": "1_new@10" }, { @@ -531,26 +538,11 @@ "when": "view == javaProjectExplorer && viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/", "group": "inline@add_0" }, - { - "command": "java.view.package.newJavaClass", - "when": "view == javaProjectExplorer && viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/", - "group": "1_new@10" - }, { "command": "java.view.package.newJavaClass", "when": "view == javaProjectExplorer && viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)(?=.*?\\b\\+uri\\b)/", "group": "inline@add_0" }, - { - "command": "java.view.package.newPackage", - "when": "view == javaProjectExplorer && viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)(?=.*?\\b\\+uri\\b)/", - "group": "1_new@20" - }, - { - "command": "java.view.package.newPackage", - "when": "view == javaProjectExplorer && viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/", - "group": "1_new@20" - }, { "command": "java.project.addLibraries", "alt": "java.project.addLibraryFolders", @@ -594,6 +586,16 @@ "command": "java.project.update", "group": "gradle@10" } + ], + "javaProject.new": [ + { + "command": "java.view.package.newJavaClass", + "group": "new@10" + }, + { + "command": "java.view.package.newPackage", + "group": "new@40" + } ] }, "submenus": [ @@ -604,6 +606,10 @@ { "id": "javaProject.gradle", "label": "Gradle" + }, + { + "id": "javaProject.new", + "label": "%contributes.submenus.javaProject.new%" } ], "views": { @@ -813,7 +819,7 @@ "@types/mocha": "^9.1.1", "@types/node": "^16.18.11", "@types/semver": "^7.3.13", - "@types/vscode": "1.69.0", + "@types/vscode": "1.75.0", "@vscode/test-electron": "^2.2.2", "copy-webpack-plugin": "^11.0.0", "glob": "^7.2.3", diff --git a/package.nls.json b/package.nls.json index 9873b339..507e2eaf 100644 --- a/package.nls.json +++ b/package.nls.json @@ -19,11 +19,13 @@ "contributes.commands.java.view.package.exportJar": "Export Jar...", "contributes.commands.java.view.package.copyFilePath": "Copy Path", "contributes.commands.java.view.package.copyRelativeFilePath": "Copy Relative Path", - "contributes.commands.java.view.package.newJavaClass": "New Java Class", - "contributes.commands.java.view.package.newPackage": "New Package", + "contributes.commands.java.view.package.newJavaClass": "Java Class", + "contributes.commands.java.view.package.newPackage": "Package", "contributes.commands.java.view.package.renameFile": "Rename", "contributes.commands.java.view.package.moveFileToTrash": "Delete", "contributes.commands.java.view.package.deleteFilePermanently": "Delete Permanently", + "contributes.submenus.javaProject.new": "New", + "contributes.commands.java.view.menus.file.newJavaClass": "New Java Class", "configuration.java.dependency.showMembers": "Show the members in the explorer", "configuration.java.dependency.syncWithFolderExplorer": "Synchronize Java Projects explorer selection with folder explorer", "configuration.java.dependency.autoRefresh": "Synchronize Java Projects explorer with changes", diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 99fa7448..75a45a6d 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -19,11 +19,13 @@ "contributes.commands.java.view.package.exportJar": "导出到 Jar 文件...", "contributes.commands.java.view.package.copyFilePath": "复制路径", "contributes.commands.java.view.package.copyRelativeFilePath": "复制相对路径", - "contributes.commands.java.view.package.newJavaClass": "创建 Java 类", - "contributes.commands.java.view.package.newPackage": "创建包", + "contributes.commands.java.view.package.newJavaClass": "Java 类", + "contributes.commands.java.view.package.newPackage": "包", "contributes.commands.java.view.package.renameFile": "重命名", "contributes.commands.java.view.package.moveFileToTrash": "删除", "contributes.commands.java.view.package.deleteFilePermanently": "永久删除", + "contributes.submenus.javaProject.new": "创建", + "contributes.commands.java.view.menus.file.newJavaClass": "创建 Java 类", "configuration.java.dependency.showMembers": "在 Java 项目管理器中显示成员", "configuration.java.dependency.syncWithFolderExplorer": "在 Java 项目管理器中同步关联当前打开的文件", "configuration.java.dependency.autoRefresh": "在 Java 项目管理器中自动同步修改", diff --git a/package.nls.zh-tw.json b/package.nls.zh-tw.json index 67f8d6c4..461bfa00 100644 --- a/package.nls.zh-tw.json +++ b/package.nls.zh-tw.json @@ -19,11 +19,13 @@ "contributes.commands.java.view.package.exportJar": "匯出成 Jar 檔案...", "contributes.commands.java.view.package.copyFilePath": "複製路徑", "contributes.commands.java.view.package.copyRelativeFilePath": "複製相對路徑", - "contributes.commands.java.view.package.newJavaClass": "建立 Java 類別", - "contributes.commands.java.view.package.newPackage": "建立套件", + "contributes.commands.java.view.package.newJavaClass": "Java 類別", + "contributes.commands.java.view.package.newPackage": "套件", "contributes.commands.java.view.package.renameFile": "重新命名", "contributes.commands.java.view.package.moveFileToTrash": "刪除", "contributes.commands.java.view.package.deleteFilePermanently": "永久刪除", + "contributes.submenus.javaProject.new": "建立", + "contributes.commands.java.view.menus.file.newJavaClass": "建立 Java 類別", "configuration.java.dependency.showMembers": "在 Java 專案管理員中顯示成員", "configuration.java.dependency.syncWithFolderExplorer": "在 Java 專案管理員中同步關聯當前開啟的檔案", "configuration.java.dependency.autoRefresh": "在 Java 專案管理員中自動同步修改", diff --git a/src/commands.ts b/src/commands.ts index c8b8d641..0542cda1 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -46,6 +46,8 @@ export namespace Commands { export const VIEW_PACKAGE_REVEAL_IN_PROJECT_EXPLORER = "java.view.package.revealInProjectExplorer"; + export const VIEW_MENUS_FILE_NEW_JAVA_CLASS = "java.view.menus.file.newJavaClass"; + export const JAVA_PROJECT_OPEN = "_java.project.open"; export const JAVA_PROJECT_CREATE = "java.project.create"; diff --git a/src/explorerCommands/new.ts b/src/explorerCommands/new.ts index ba93aeb4..6ab20688 100644 --- a/src/explorerCommands/new.ts +++ b/src/explorerCommands/new.ts @@ -5,26 +5,19 @@ import * as fse from "fs-extra"; import * as path from "path"; import { commands, Extension, extensions, languages, QuickPickItem, SnippetString, TextEditor, Uri, window, workspace, WorkspaceEdit, WorkspaceFolder } from "vscode"; -import { sendInfo } from "vscode-extension-telemetry-wrapper"; -import { Commands } from "../../extension.bundle"; +import { Commands, PrimaryTypeNode } from "../../extension.bundle"; import { ExtensionName } from "../constants"; import { NodeKind } from "../java/nodeData"; import { DataNode } from "../views/dataNode"; import { resourceRoots } from "../views/packageRootNode"; import { checkJavaQualifiedName } from "./utility"; +// TODO: separate to two function to handle creation from menu bar and explorer. export async function newJavaClass(node?: DataNode): Promise { let packageFsPath: string | undefined; if (!node) { - // from the new file menu entry - sendInfo("", { - triggerNewFileFrom: "menuBar", - }); packageFsPath = await inferPackageFsPath(); } else { - sendInfo("", { - triggerNewFileFrom: "projectExplorer", - }); if (!node?.uri || !canCreateClass(node)) { return; } @@ -209,6 +202,19 @@ export async function newPackage(node?: DataNode): Promise { } else if (nodeKind === NodeKind.Package) { defaultValue = node.nodeData.name + "."; packageRootPath = getPackageRootPath(Uri.parse(node.uri).fsPath, node.nodeData.name); + } else if (nodeKind === NodeKind.PrimaryType) { + const primaryTypeNode = node; + packageRootPath = primaryTypeNode.getPackageRootPath(); + if (packageRootPath === "") { + window.showErrorMessage("Failed to get the package root path."); + return; + } + const packagePath = await getPackageFsPath(node); + if (!packagePath) { + window.showErrorMessage("Failed to get the package path."); + return; + } + defaultValue = path.relative(packageRootPath, packagePath).replace(/[\\,/]/g, ".") + "."; } else { return; } @@ -239,14 +245,12 @@ export async function newPackage(node?: DataNode): Promise { await fse.ensureDir(getNewPackagePath(packageRootPath, packageName)); } +/** + * Check if the create package command is available for the given node. + * Currently the check logic is the same as the create class command. + */ function canCreatePackage(node: DataNode): boolean { - if (node.nodeData.kind === NodeKind.Project || - node.nodeData.kind === NodeKind.PackageRoot || - node.nodeData.kind === NodeKind.Package) { - return true; - } - - return false; + return canCreateClass(node); } function getPackageRootPath(packageFsPath: string, packageName: string): string { diff --git a/src/extension.ts b/src/extension.ts index 35eb624e..4f4ad709 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -19,6 +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"; export async function activate(context: ExtensionContext): Promise { contextManager.initialize(context); @@ -46,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(window.onDidChangeActiveTextEditor((e: TextEditor | undefined) => { setContextForReloadProject(e?.document); })); diff --git a/src/views/PrimaryTypeNode.ts b/src/views/PrimaryTypeNode.ts index 0f0e6693..8661805f 100644 --- a/src/views/PrimaryTypeNode.ts +++ b/src/views/PrimaryTypeNode.ts @@ -11,6 +11,8 @@ import { isTest } from "../utility"; import { DataNode } from "./dataNode"; import { DocumentSymbolNode } from "./documentSymbolNode"; import { ExplorerNode } from "./explorerNode"; +import { ProjectNode } from "./projectNode"; +import { IPackageRootNodeData, PackageRootKind } from "../java/packageRootNodeData"; export class PrimaryTypeNode extends DataNode { @@ -20,6 +22,19 @@ export class PrimaryTypeNode extends DataNode { super(nodeData, parent); } + public getPackageRootPath(): string { + if (this._rootNode?.uri) { + return Uri.parse(this._rootNode.uri).fsPath; + } + + const unmanagedFolder = this.getUnmanagedFolderAncestor(); + if (unmanagedFolder) { + return unmanagedFolder.uri ? Uri.parse(unmanagedFolder.uri).fsPath : ""; + } + + return ""; + } + protected async loadData(): Promise { if (!this.hasChildren() || !this.nodeData.uri) { return undefined; @@ -107,6 +122,47 @@ export class PrimaryTypeNode extends DataNode { contextValue += "+test"; } + if (this.belongsToSourceRoot() || this.getUnmanagedFolderAncestor()) { + contextValue += "+source"; + } + return contextValue; } + + /** + * Check if the type belongs to a source root. Following conditions can cause the + * result to be false: + * - The type belongs to a jar package + * - The type belongs to an unmanaged folder with '.' as its source root. + */ + private belongsToSourceRoot(): boolean { + const rootNodeData = this._rootNode?.nodeData; + if (!rootNodeData) { + return false; + } + + const data = rootNodeData; + + if (data.entryKind === PackageRootKind.K_SOURCE) { + return true; + } + + return false; + } + + /** + * @returns ProjectNode if the current node is under an unmanaged folder, + * otherwise undefined. + */ + private getUnmanagedFolderAncestor(): ProjectNode | undefined { + let ancestor = this.getParent(); + while (ancestor && !(ancestor instanceof ProjectNode)) { + ancestor = ancestor.getParent(); + } + if (ancestor?.isUnmanagedFolder()) { + return ancestor; + } + + return undefined; + } } diff --git a/test/suite/contextValue.test.ts b/test/suite/contextValue.test.ts index 9fa1e1d5..061dde48 100644 --- a/test/suite/contextValue.test.ts +++ b/test/suite/contextValue.test.ts @@ -89,19 +89,19 @@ suite("Context Value Tests", () => { }); test("test class type node", async function() { - assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+uri\b)/.test((await classType.getTreeItem()).contextValue || "")); + assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await classType.getTreeItem()).contextValue || "")); }); test("test test-class type node", async function() { - assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+test\b)(?=.*?\b\+uri\b)/.test((await testClassType.getTreeItem()).contextValue || "")); + assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+test\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await testClassType.getTreeItem()).contextValue || "")); }); test("test enum type node", async function() { - assert.ok(/java:type(?=.*?\b\+enum\b)(?=.*?\b\+uri\b)/.test((await enumType.getTreeItem()).contextValue || "")); + assert.ok(/java:type(?=.*?\b\+enum\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await enumType.getTreeItem()).contextValue || "")); }); test("test interface type node", async function() { - assert.ok(/java:type(?=.*?\b\+interface\b)(?=.*?\b\+uri\b)/.test((await interfaceType.getTreeItem()).contextValue || "")); + assert.ok(/java:type(?=.*?\b\+interface\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await interfaceType.getTreeItem()).contextValue || "")); }); test("test folder node", async function() { From 25b9dbd5a2b492a9272f1e1148fd322c3a6a20d4 Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Tue, 11 Apr 2023 14:41:52 +0800 Subject: [PATCH 2/4] Refine code --- src/views/PrimaryTypeNode.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/views/PrimaryTypeNode.ts b/src/views/PrimaryTypeNode.ts index 8661805f..e7182337 100644 --- a/src/views/PrimaryTypeNode.ts +++ b/src/views/PrimaryTypeNode.ts @@ -28,8 +28,8 @@ export class PrimaryTypeNode extends DataNode { } const unmanagedFolder = this.getUnmanagedFolderAncestor(); - if (unmanagedFolder) { - return unmanagedFolder.uri ? Uri.parse(unmanagedFolder.uri).fsPath : ""; + if (unmanagedFolder?.uri) { + return Uri.parse(unmanagedFolder.uri).fsPath; } return ""; @@ -142,7 +142,6 @@ export class PrimaryTypeNode extends DataNode { } const data = rootNodeData; - if (data.entryKind === PackageRootKind.K_SOURCE) { return true; } From f25de9d794c98c91bf0963f93b9090297401c2c7 Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Tue, 11 Apr 2023 15:38:26 +0800 Subject: [PATCH 3/4] Correct the regex --- src/explorerCommands/new.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/explorerCommands/new.ts b/src/explorerCommands/new.ts index 6ab20688..69bffbdc 100644 --- a/src/explorerCommands/new.ts +++ b/src/explorerCommands/new.ts @@ -214,7 +214,7 @@ export async function newPackage(node?: DataNode): Promise { window.showErrorMessage("Failed to get the package path."); return; } - defaultValue = path.relative(packageRootPath, packagePath).replace(/[\\,/]/g, ".") + "."; + defaultValue = path.relative(packageRootPath, packagePath).replace(/[\\/]/g, ".") + "."; } else { return; } From ed387457cb3242595ec656e1c54538f3e794c3cb Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Tue, 11 Apr 2023 15:42:49 +0800 Subject: [PATCH 4/4] Correct the regex --- src/explorerCommands/new.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/explorerCommands/new.ts b/src/explorerCommands/new.ts index 69bffbdc..2e17bd62 100644 --- a/src/explorerCommands/new.ts +++ b/src/explorerCommands/new.ts @@ -214,7 +214,7 @@ export async function newPackage(node?: DataNode): Promise { window.showErrorMessage("Failed to get the package path."); return; } - defaultValue = path.relative(packageRootPath, packagePath).replace(/[\\/]/g, ".") + "."; + defaultValue = path.relative(packageRootPath, packagePath).replace(/[\\\/]/g, ".") + "."; } else { return; }