Skip to content

Commit dc2908e

Browse files
authored
feat: Re-organize the new class & package commands (#741)
* 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 <[email protected]>
1 parent ad8a462 commit dc2908e

File tree

9 files changed

+125
-51
lines changed

9 files changed

+125
-51
lines changed

package.json

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"explorer"
1313
],
1414
"engines": {
15-
"vscode": "^1.69.0"
15+
"vscode": "^1.75.0"
1616
},
1717
"repository": {
1818
"type": "git",
@@ -39,10 +39,7 @@
3939
"workspaceContains:settings.gradle.kts",
4040
"workspaceContains:*/settings.gradle.kts",
4141
"workspaceContains:.classpath",
42-
"workspaceContains:*/.classpath",
43-
"onCommand:_java.project.open",
44-
"onCommand:java.project.create",
45-
"onCommand:java.view.package.newJavaClass"
42+
"workspaceContains:*/.classpath"
4643
],
4744
"license": "MIT",
4845
"main": "./main.js",
@@ -166,6 +163,12 @@
166163
"title": "%contributes.commands.java.view.package.copyRelativeFilePath%",
167164
"category": "Java"
168165
},
166+
{
167+
"command": "java.view.menus.file.newJavaClass",
168+
"title": "%contributes.commands.java.view.menus.file.newJavaClass%",
169+
"category": "Java",
170+
"icon": "$(add)"
171+
},
169172
{
170173
"command": "java.view.package.newJavaClass",
171174
"title": "%contributes.commands.java.view.package.newJavaClass%",
@@ -290,7 +293,7 @@
290293
"menus": {
291294
"file/newFile": [
292295
{
293-
"command": "java.view.package.newJavaClass"
296+
"command": "java.view.menus.file.newJavaClass"
294297
}
295298
],
296299
"commandPalette": [
@@ -350,6 +353,10 @@
350353
"command": "java.project.refreshLibraries",
351354
"when": "false"
352355
},
356+
{
357+
"command": "java.view.package.newJavaClass",
358+
"when": "false"
359+
},
353360
{
354361
"command": "java.view.package.newPackage",
355362
"when": "false"
@@ -522,35 +529,20 @@
522529
"group": "8_execution@6"
523530
},
524531
{
525-
"command": "java.view.package.newJavaClass",
526-
"when": "view == javaProjectExplorer && viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)(?=.*?\\b\\+uri\\b)/",
532+
"submenu": "javaProject.new",
533+
"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)/)",
527534
"group": "1_new@10"
528535
},
529536
{
530537
"command": "java.view.package.newJavaClass",
531538
"when": "view == javaProjectExplorer && viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/",
532539
"group": "inline@add_0"
533540
},
534-
{
535-
"command": "java.view.package.newJavaClass",
536-
"when": "view == javaProjectExplorer && viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/",
537-
"group": "1_new@10"
538-
},
539541
{
540542
"command": "java.view.package.newJavaClass",
541543
"when": "view == javaProjectExplorer && viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)(?=.*?\\b\\+uri\\b)/",
542544
"group": "inline@add_0"
543545
},
544-
{
545-
"command": "java.view.package.newPackage",
546-
"when": "view == javaProjectExplorer && viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)(?=.*?\\b\\+uri\\b)/",
547-
"group": "1_new@20"
548-
},
549-
{
550-
"command": "java.view.package.newPackage",
551-
"when": "view == javaProjectExplorer && viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/",
552-
"group": "1_new@20"
553-
},
554546
{
555547
"command": "java.project.addLibraries",
556548
"alt": "java.project.addLibraryFolders",
@@ -594,6 +586,16 @@
594586
"command": "java.project.update",
595587
"group": "gradle@10"
596588
}
589+
],
590+
"javaProject.new": [
591+
{
592+
"command": "java.view.package.newJavaClass",
593+
"group": "new@10"
594+
},
595+
{
596+
"command": "java.view.package.newPackage",
597+
"group": "new@40"
598+
}
597599
]
598600
},
599601
"submenus": [
@@ -604,6 +606,10 @@
604606
{
605607
"id": "javaProject.gradle",
606608
"label": "Gradle"
609+
},
610+
{
611+
"id": "javaProject.new",
612+
"label": "%contributes.submenus.javaProject.new%"
607613
}
608614
],
609615
"views": {
@@ -813,7 +819,7 @@
813819
"@types/mocha": "^9.1.1",
814820
"@types/node": "^16.18.11",
815821
"@types/semver": "^7.3.13",
816-
"@types/vscode": "1.69.0",
822+
"@types/vscode": "1.75.0",
817823
"@vscode/test-electron": "^2.2.2",
818824
"copy-webpack-plugin": "^11.0.0",
819825
"glob": "^7.2.3",

package.nls.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
"contributes.commands.java.view.package.exportJar": "Export Jar...",
2020
"contributes.commands.java.view.package.copyFilePath": "Copy Path",
2121
"contributes.commands.java.view.package.copyRelativeFilePath": "Copy Relative Path",
22-
"contributes.commands.java.view.package.newJavaClass": "New Java Class",
23-
"contributes.commands.java.view.package.newPackage": "New Package",
22+
"contributes.commands.java.view.package.newJavaClass": "Java Class",
23+
"contributes.commands.java.view.package.newPackage": "Package",
2424
"contributes.commands.java.view.package.renameFile": "Rename",
2525
"contributes.commands.java.view.package.moveFileToTrash": "Delete",
2626
"contributes.commands.java.view.package.deleteFilePermanently": "Delete Permanently",
27+
"contributes.submenus.javaProject.new": "New",
28+
"contributes.commands.java.view.menus.file.newJavaClass": "New Java Class",
2729
"configuration.java.dependency.showMembers": "Show the members in the explorer",
2830
"configuration.java.dependency.syncWithFolderExplorer": "Synchronize Java Projects explorer selection with folder explorer",
2931
"configuration.java.dependency.autoRefresh": "Synchronize Java Projects explorer with changes",

package.nls.zh-cn.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
"contributes.commands.java.view.package.exportJar": "导出到 Jar 文件...",
2020
"contributes.commands.java.view.package.copyFilePath": "复制路径",
2121
"contributes.commands.java.view.package.copyRelativeFilePath": "复制相对路径",
22-
"contributes.commands.java.view.package.newJavaClass": "创建 Java 类",
23-
"contributes.commands.java.view.package.newPackage": "创建包",
22+
"contributes.commands.java.view.package.newJavaClass": "Java 类",
23+
"contributes.commands.java.view.package.newPackage": "",
2424
"contributes.commands.java.view.package.renameFile": "重命名",
2525
"contributes.commands.java.view.package.moveFileToTrash": "删除",
2626
"contributes.commands.java.view.package.deleteFilePermanently": "永久删除",
27+
"contributes.submenus.javaProject.new": "创建",
28+
"contributes.commands.java.view.menus.file.newJavaClass": "创建 Java 类",
2729
"configuration.java.dependency.showMembers": "在 Java 项目管理器中显示成员",
2830
"configuration.java.dependency.syncWithFolderExplorer": "在 Java 项目管理器中同步关联当前打开的文件",
2931
"configuration.java.dependency.autoRefresh": "在 Java 项目管理器中自动同步修改",

package.nls.zh-tw.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
"contributes.commands.java.view.package.exportJar": "匯出成 Jar 檔案...",
2020
"contributes.commands.java.view.package.copyFilePath": "複製路徑",
2121
"contributes.commands.java.view.package.copyRelativeFilePath": "複製相對路徑",
22-
"contributes.commands.java.view.package.newJavaClass": "建立 Java 類別",
23-
"contributes.commands.java.view.package.newPackage": "建立套件",
22+
"contributes.commands.java.view.package.newJavaClass": "Java 類別",
23+
"contributes.commands.java.view.package.newPackage": "套件",
2424
"contributes.commands.java.view.package.renameFile": "重新命名",
2525
"contributes.commands.java.view.package.moveFileToTrash": "刪除",
2626
"contributes.commands.java.view.package.deleteFilePermanently": "永久刪除",
27+
"contributes.submenus.javaProject.new": "建立",
28+
"contributes.commands.java.view.menus.file.newJavaClass": "建立 Java 類別",
2729
"configuration.java.dependency.showMembers": "在 Java 專案管理員中顯示成員",
2830
"configuration.java.dependency.syncWithFolderExplorer": "在 Java 專案管理員中同步關聯當前開啟的檔案",
2931
"configuration.java.dependency.autoRefresh": "在 Java 專案管理員中自動同步修改",

src/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ export namespace Commands {
4646

4747
export const VIEW_PACKAGE_REVEAL_IN_PROJECT_EXPLORER = "java.view.package.revealInProjectExplorer";
4848

49+
export const VIEW_MENUS_FILE_NEW_JAVA_CLASS = "java.view.menus.file.newJavaClass";
50+
4951
export const JAVA_PROJECT_OPEN = "_java.project.open";
5052

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

src/explorerCommands/new.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,19 @@ import * as fse from "fs-extra";
55
import * as path from "path";
66
import { commands, Extension, extensions, languages, QuickPickItem, SnippetString, TextEditor, Uri,
77
window, workspace, WorkspaceEdit, WorkspaceFolder } from "vscode";
8-
import { sendInfo } from "vscode-extension-telemetry-wrapper";
9-
import { Commands } from "../../extension.bundle";
8+
import { Commands, PrimaryTypeNode } from "../../extension.bundle";
109
import { ExtensionName } from "../constants";
1110
import { NodeKind } from "../java/nodeData";
1211
import { DataNode } from "../views/dataNode";
1312
import { resourceRoots } from "../views/packageRootNode";
1413
import { checkJavaQualifiedName } from "./utility";
1514

15+
// TODO: separate to two function to handle creation from menu bar and explorer.
1616
export async function newJavaClass(node?: DataNode): Promise<void> {
1717
let packageFsPath: string | undefined;
1818
if (!node) {
19-
// from the new file menu entry
20-
sendInfo("", {
21-
triggerNewFileFrom: "menuBar",
22-
});
2319
packageFsPath = await inferPackageFsPath();
2420
} else {
25-
sendInfo("", {
26-
triggerNewFileFrom: "projectExplorer",
27-
});
2821
if (!node?.uri || !canCreateClass(node)) {
2922
return;
3023
}
@@ -209,6 +202,19 @@ export async function newPackage(node?: DataNode): Promise<void> {
209202
} else if (nodeKind === NodeKind.Package) {
210203
defaultValue = node.nodeData.name + ".";
211204
packageRootPath = getPackageRootPath(Uri.parse(node.uri).fsPath, node.nodeData.name);
205+
} else if (nodeKind === NodeKind.PrimaryType) {
206+
const primaryTypeNode = <PrimaryTypeNode> node;
207+
packageRootPath = primaryTypeNode.getPackageRootPath();
208+
if (packageRootPath === "") {
209+
window.showErrorMessage("Failed to get the package root path.");
210+
return;
211+
}
212+
const packagePath = await getPackageFsPath(node);
213+
if (!packagePath) {
214+
window.showErrorMessage("Failed to get the package path.");
215+
return;
216+
}
217+
defaultValue = path.relative(packageRootPath, packagePath).replace(/[\\\/]/g, ".") + ".";
212218
} else {
213219
return;
214220
}
@@ -239,14 +245,12 @@ export async function newPackage(node?: DataNode): Promise<void> {
239245
await fse.ensureDir(getNewPackagePath(packageRootPath, packageName));
240246
}
241247

248+
/**
249+
* Check if the create package command is available for the given node.
250+
* Currently the check logic is the same as the create class command.
251+
*/
242252
function canCreatePackage(node: DataNode): boolean {
243-
if (node.nodeData.kind === NodeKind.Project ||
244-
node.nodeData.kind === NodeKind.PackageRoot ||
245-
node.nodeData.kind === NodeKind.Package) {
246-
return true;
247-
}
248-
249-
return false;
253+
return canCreateClass(node);
250254
}
251255

252256
function getPackageRootPath(packageFsPath: string, packageName: string): string {

src/extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { DependencyExplorer } from "./views/dependencyExplorer";
1919
import { DiagnosticProvider } from "./tasks/buildArtifact/migration/DiagnosticProvider";
2020
import { setContextForDeprecatedTasks, updateExportTaskType } from "./tasks/buildArtifact/migration/utils";
2121
import { CodeActionProvider } from "./tasks/buildArtifact/migration/CodeActionProvider";
22+
import { newJavaClass } from "./explorerCommands/new";
2223

2324
export async function activate(context: ExtensionContext): Promise<void> {
2425
contextManager.initialize(context);
@@ -46,7 +47,7 @@ async function activateExtension(_operationId: string, context: ExtensionContext
4647
context.subscriptions.push(tasks.registerTaskProvider(DeprecatedExportJarTaskProvider.type, new DeprecatedExportJarTaskProvider()));
4748
context.subscriptions.push(tasks.registerTaskProvider(BuildArtifactTaskProvider.exportJarType, new BuildArtifactTaskProvider()));
4849
context.subscriptions.push(tasks.registerTaskProvider(BuildTaskProvider.type, new BuildTaskProvider()));
49-
50+
context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.VIEW_MENUS_FILE_NEW_JAVA_CLASS, newJavaClass));
5051
context.subscriptions.push(window.onDidChangeActiveTextEditor((e: TextEditor | undefined) => {
5152
setContextForReloadProject(e?.document);
5253
}));

src/views/PrimaryTypeNode.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { isTest } from "../utility";
1111
import { DataNode } from "./dataNode";
1212
import { DocumentSymbolNode } from "./documentSymbolNode";
1313
import { ExplorerNode } from "./explorerNode";
14+
import { ProjectNode } from "./projectNode";
15+
import { IPackageRootNodeData, PackageRootKind } from "../java/packageRootNodeData";
1416

1517
export class PrimaryTypeNode extends DataNode {
1618

@@ -20,6 +22,19 @@ export class PrimaryTypeNode extends DataNode {
2022
super(nodeData, parent);
2123
}
2224

25+
public getPackageRootPath(): string {
26+
if (this._rootNode?.uri) {
27+
return Uri.parse(this._rootNode.uri).fsPath;
28+
}
29+
30+
const unmanagedFolder = this.getUnmanagedFolderAncestor();
31+
if (unmanagedFolder?.uri) {
32+
return Uri.parse(unmanagedFolder.uri).fsPath;
33+
}
34+
35+
return "";
36+
}
37+
2338
protected async loadData(): Promise<SymbolInformation[] | DocumentSymbol[] | undefined> {
2439
if (!this.hasChildren() || !this.nodeData.uri) {
2540
return undefined;
@@ -107,6 +122,46 @@ export class PrimaryTypeNode extends DataNode {
107122
contextValue += "+test";
108123
}
109124

125+
if (this.belongsToSourceRoot() || this.getUnmanagedFolderAncestor()) {
126+
contextValue += "+source";
127+
}
128+
110129
return contextValue;
111130
}
131+
132+
/**
133+
* Check if the type belongs to a source root. Following conditions can cause the
134+
* result to be false:
135+
* - The type belongs to a jar package
136+
* - The type belongs to an unmanaged folder with '.' as its source root.
137+
*/
138+
private belongsToSourceRoot(): boolean {
139+
const rootNodeData = this._rootNode?.nodeData;
140+
if (!rootNodeData) {
141+
return false;
142+
}
143+
144+
const data = <IPackageRootNodeData>rootNodeData;
145+
if (data.entryKind === PackageRootKind.K_SOURCE) {
146+
return true;
147+
}
148+
149+
return false;
150+
}
151+
152+
/**
153+
* @returns ProjectNode if the current node is under an unmanaged folder,
154+
* otherwise undefined.
155+
*/
156+
private getUnmanagedFolderAncestor(): ProjectNode | undefined {
157+
let ancestor = this.getParent();
158+
while (ancestor && !(ancestor instanceof ProjectNode)) {
159+
ancestor = ancestor.getParent();
160+
}
161+
if (ancestor?.isUnmanagedFolder()) {
162+
return ancestor;
163+
}
164+
165+
return undefined;
166+
}
112167
}

test/suite/contextValue.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,19 +89,19 @@ suite("Context Value Tests", () => {
8989
});
9090

9191
test("test class type node", async function() {
92-
assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+uri\b)/.test((await classType.getTreeItem()).contextValue || ""));
92+
assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await classType.getTreeItem()).contextValue || ""));
9393
});
9494

9595
test("test test-class type node", async function() {
96-
assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+test\b)(?=.*?\b\+uri\b)/.test((await testClassType.getTreeItem()).contextValue || ""));
96+
assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+test\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await testClassType.getTreeItem()).contextValue || ""));
9797
});
9898

9999
test("test enum type node", async function() {
100-
assert.ok(/java:type(?=.*?\b\+enum\b)(?=.*?\b\+uri\b)/.test((await enumType.getTreeItem()).contextValue || ""));
100+
assert.ok(/java:type(?=.*?\b\+enum\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await enumType.getTreeItem()).contextValue || ""));
101101
});
102102

103103
test("test interface type node", async function() {
104-
assert.ok(/java:type(?=.*?\b\+interface\b)(?=.*?\b\+uri\b)/.test((await interfaceType.getTreeItem()).contextValue || ""));
104+
assert.ok(/java:type(?=.*?\b\+interface\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await interfaceType.getTreeItem()).contextValue || ""));
105105
});
106106

107107
test("test folder node", async function() {

0 commit comments

Comments
 (0)