diff --git a/package.json b/package.json index 7bee2fe5..1815342e 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ { "command": "java.project.create", "title": "%contributes.commands.java.project.create%", - "category": "Java" + "category": "Java", + "icon": "$(add)" }, { "command": "java.project.addLibraries", @@ -205,27 +206,32 @@ { "command": "java.view.package.refresh", "when": "view == javaProjectExplorer && java:serverMode!= LightWeight", - "group": "navigation@2" + "group": "navigation@40" }, { "command": "java.view.package.changeToHierarchicalPackageView", "when": "view == javaProjectExplorer && config.java.dependency.packagePresentation == flat && java:serverMode!= LightWeight", - "group": "navigation@1" + "group": "navigation@30" }, { "command": "java.view.package.changeToFlatPackageView", "when": "view == javaProjectExplorer && config.java.dependency.packagePresentation != flat && java:serverMode!= LightWeight", - "group": "navigation@1" + "group": "navigation@30" }, { "command": "java.view.package.linkWithFolderExplorer", "when": "view == javaProjectExplorer && config.java.dependency.syncWithFolderExplorer != true && java:serverMode!= LightWeight", - "group": "navigation@0" + "group": "navigation@20" }, { "command": "java.view.package.unlinkWithFolderExplorer", "when": "view == javaProjectExplorer && config.java.dependency.syncWithFolderExplorer == true && java:serverMode!= LightWeight", - "group": "navigation@0" + "group": "navigation@20" + }, + { + "command": "java.project.create", + "when": "view == javaProjectExplorer", + "group": "navigation@10" } ], "view/item/context": [ diff --git a/package.nls.json b/package.nls.json index 78182ec4..9aeab13b 100644 --- a/package.nls.json +++ b/package.nls.json @@ -1,6 +1,6 @@ { "description": "Manage Java projects in Visual Studio Code", - "contributes.commands.java.project.create": "Create Java Project", + "contributes.commands.java.project.create": "Create Java Project...", "contributes.commands.java.project.addLibraries": "Add a jar file or a folder to project classpath", "contributes.commands.java.project.maven.addDependency": "Add a new dependency to the Maven project", "contributes.commands.java.project.removeLibrary": "Remove jar file from project classpath", diff --git a/package.nls.zh.json b/package.nls.zh.json index 81756f70..423aca44 100644 --- a/package.nls.zh.json +++ b/package.nls.zh.json @@ -1,6 +1,6 @@ { "description": "在 Visual Studio Code 中管理 Java 项目", - "contributes.commands.java.project.create": "创建 Java 项目", + "contributes.commands.java.project.create": "创建 Java 项目...", "contributes.commands.java.project.addLibraries": "将一个 Jar 文件或一个目录添加到 Java 项目类路径中", "contributes.commands.java.project.maven.addDependency": "为该 Maven 项目增加依赖库", "contributes.commands.java.project.removeLibrary": "将该 Jar 文件从 Java 项目类路径中移除", diff --git a/src/controllers/projectController.ts b/src/controllers/projectController.ts index 0b4c710d..ca22df86 100644 --- a/src/controllers/projectController.ts +++ b/src/controllers/projectController.ts @@ -4,12 +4,9 @@ import * as fse from "fs-extra"; import * as _ from "lodash"; import * as path from "path"; -import { commands, Disposable, ExtensionContext, QuickPickItem, Uri, window, workspace } from "vscode"; -import { instrumentOperationAsVsCodeCommand, sendInfo } from "vscode-extension-telemetry-wrapper"; +import { commands, Disposable, Extension, ExtensionContext, extensions, QuickPickItem, Uri, window, workspace } from "vscode"; +import { instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-wrapper"; import { Commands } from "../commands"; -import { Context } from "../constants"; -import { contextManager } from "../contextManager"; -import { getExpService } from "../ExperimentationService"; import { Utility } from "../utility"; export class ProjectController implements Disposable { @@ -27,97 +24,173 @@ export class ProjectController implements Disposable { } public async createJavaProject() { - const projectKinds: QuickPickItem[] = [{ - label: BuildTool.None, - detail: "A project without any build tools", - }]; - if (contextManager.getContextValue(Context.MAVEN_ENABLED)) { - const isMavenDefault: boolean = await getExpService()?.isCachedFlightEnabled("defaultMaven") || false; - const mavenItem: QuickPickItem = { - label: BuildTool.Maven, - detail: "Use Maven to manage your project", + const items: IProjectTypeQuickPick[] = projectTypes.map((type: IProjectType) => { + return { + label: type.displayName, + description: type.description, + detail: type.metadata.extensionName ? `Provided by $(extensions) ${type.metadata.extensionName}` : type.detail, + metadata: type.metadata, }; - if (isMavenDefault) { - projectKinds.unshift(mavenItem); - } else { - projectKinds.push(mavenItem); - } - } - const choice: QuickPickItem | undefined = projectKinds.length === 1 ? projectKinds[0] : - await window.showQuickPick(projectKinds, { - ignoreFocusOut: true, - placeHolder: "Select the project build tool", - }, - ); - - if (!choice) { + }); + const choice = await window.showQuickPick(items, { + ignoreFocusOut: true, + placeHolder: "Select the project type", + }); + if (!choice || !await ensureExtension(choice.label, choice.metadata)) { return; } - if (projectKinds.length > 1) { - const chooseDefault: boolean = choice.label === projectKinds[0].label; - sendInfo("", {"project.create.chooseDefault": `${chooseDefault}`}); - } - - switch (choice.label) { - case BuildTool.Maven: - await commands.executeCommand(Commands.JAVA_MAVEN_CREATE_PROJECT); - break; - case BuildTool.None: - await this.scaffoldSimpleProject(); - break; - default: - break; + if (choice.metadata.type === ProjectType.NoBuildTool) { + await scaffoldSimpleProject(); + } else if (choice.metadata.createCommandId) { + await commands.executeCommand(choice.metadata.createCommandId); } } +} - private async scaffoldSimpleProject(): Promise { - const workspaceFolder = Utility.getDefaultWorkspaceFolder(); - const location: Uri[] | undefined = await window.showOpenDialog({ - defaultUri: workspaceFolder && workspaceFolder.uri, - canSelectFiles: false, - canSelectFolders: true, - openLabel: "Select the location", - }); - if (!location || !location.length) { - return; - } +interface IProjectType { + displayName: string; + description?: string; + detail?: string; + metadata: IProjectTypeMetadata; +} - const basePath: string = location[0].fsPath; - const projectName: string | undefined = await window.showInputBox({ - prompt: "Input a java project name", - ignoreFocusOut: true, - validateInput: async (name: string): Promise => { - if (name && !name.match(/^[^*~/\\]+$/)) { - return "Please input a valid project name"; - } - if (name && await fse.pathExists(path.join(basePath, name))) { - return "A project with this name already exists."; - } - return ""; - }, - }); +interface IProjectTypeMetadata { + type: ProjectType; + extensionId: string; + extensionName: string; + createCommandId: string; +} - if (!projectName) { - return; - } +interface IProjectTypeQuickPick extends QuickPickItem { + metadata: IProjectTypeMetadata; +} - const projectRoot: string = path.join(basePath, projectName); - const templateRoot: string = path.join(this.context.extensionPath, "templates", "invisible-project"); - try { - await fse.ensureDir(projectRoot); - await fse.copy(templateRoot, projectRoot); - await fse.ensureDir(path.join(projectRoot, "lib")); - } catch (error) { - window.showErrorMessage(error.message); - return; - } - const openInNewWindow = workspace && !_.isEmpty(workspace.workspaceFolders); - await commands.executeCommand(Commands.VSCODE_OPEN_FOLDER, Uri.file(path.join(basePath, projectName)), openInNewWindow); +enum ProjectType { + NoBuildTool = "NoBuildTool", + Maven = "Maven", + SpringBoot = "SpringBoot", + Quarkus = "Quarkus", + MicroProfile = "MicroProfile", +} + +async function ensureExtension(typeName: string, metaData: IProjectTypeMetadata): Promise { + if (!metaData.extensionId) { + return true; + } + + const extension: Extension | undefined = extensions.getExtension(metaData.extensionId); + if (extension === undefined) { + await promptInstallExtension(typeName, metaData); + return false; } + + await extension.activate(); + return true; } -enum BuildTool { - Maven = "Maven", - None = "No build tools", +async function promptInstallExtension(projectType: string, metaData: IProjectTypeMetadata): Promise { + const choice: string | undefined = await window.showInformationMessage(`${metaData.extensionName} is required to create ${projectType} projects. Please re-run the command 'Java: Create Java Project...' after the extension is installed.`, "Install"); + if (choice === "Install") { + commands.executeCommand("workbench.extensions.installExtension", metaData.extensionId); + // So far there is no API to query the extension's state, so we open the extension's homepage + // here, where users can check the state: installing, disabled, installed, etc... + // See: https://github.com/microsoft/vscode/issues/14444 + commands.executeCommand("extension.open", metaData.extensionId); + } +} + +async function scaffoldSimpleProject(): Promise { + const workspaceFolder = Utility.getDefaultWorkspaceFolder(); + const location: Uri[] | undefined = await window.showOpenDialog({ + defaultUri: workspaceFolder && workspaceFolder.uri, + canSelectFiles: false, + canSelectFolders: true, + openLabel: "Select the project location", + }); + if (!location || !location.length) { + return; + } + + const basePath: string = location[0].fsPath; + const projectName: string | undefined = await window.showInputBox({ + prompt: "Input a Java project name", + ignoreFocusOut: true, + validateInput: async (name: string): Promise => { + if (name && !name.match(/^[^*~/\\]+$/)) { + return "Please input a valid project name"; + } + if (name && await fse.pathExists(path.join(basePath, name))) { + return "A project with this name already exists"; + } + return ""; + }, + }); + + if (!projectName) { + return; + } + + const projectRoot: string = path.join(basePath, projectName); + const templateRoot: string = path.join(this.context.extensionPath, "templates", "invisible-project"); + try { + await fse.ensureDir(projectRoot); + await fse.copy(templateRoot, projectRoot); + await fse.ensureDir(path.join(projectRoot, "lib")); + } catch (error) { + window.showErrorMessage(error.message); + return; + } + const openInNewWindow = workspace && !_.isEmpty(workspace.workspaceFolders); + await commands.executeCommand(Commands.VSCODE_OPEN_FOLDER, Uri.file(path.join(basePath, projectName)), openInNewWindow); } + +const projectTypes: IProjectType[] = [ + { + displayName: "No build tools", + detail: "Create a project without any build tools", + metadata: { + type: ProjectType.NoBuildTool, + extensionId: "", + extensionName: "", + createCommandId: "", + }, + }, + { + displayName: "Maven", + description: "create from archetype", + metadata: { + type: ProjectType.Maven, + extensionId: "vscjava.vscode-maven", + extensionName: "Maven for Java", + createCommandId: "maven.archetype.generate", + }, + }, + { + displayName: "Spring Boot", + metadata: { + type: ProjectType.SpringBoot, + extensionId: "vscjava.vscode-spring-initializr", + extensionName: "Spring Initializr Java Support", + createCommandId: "spring.initializr.createProject", + }, + }, + { + displayName: "Quarkus", + metadata: { + type: ProjectType.Quarkus, + extensionId: "redhat.vscode-quarkus", + extensionName: "Quarkus", + createCommandId: "quarkusTools.createProject", + }, + }, + { + displayName: "MicroProfile", + metadata: { + type: ProjectType.MicroProfile, + extensionId: "microprofile-community.mp-starter-vscode-ext", + extensionName: "MicroProfile Starter", + createCommandId: "extension.microProfileStarter", + }, + }, +];