From ebfbc30fc8b5ec2ebe01df7a7f258fc8e80d7706 Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Mon, 3 Jun 2024 14:30:34 +0200 Subject: [PATCH] IDEA-354377 Create workspace via Project Wizard GitOrigin-RevId: dee62d7a48553003b9efd12ea24b315e85d25343 --- .../ide/projectWizard/ProjectTypeStep.java | 2 + .../resources/messages/LangBundle.properties | 8 +- platform/lang-impl/api-dump-unreviewed.txt | 8 + .../ide/workspace/CreateWorkspaceAction.kt | 68 -------- .../ide/workspace/NewWorkspaceDialog.kt | 157 ------------------ .../configuration/BaseWorkspaceAction.kt | 20 +++ .../ManageWorkspaceAction.kt | 9 +- .../configuration/ManageWorkspaceDialog.kt | 75 +++++++++ .../configuration/NewWorkspaceWizard.kt | 56 +++++++ .../workspace/configuration/SubprojectList.kt | 60 +++++++ .../platform-impl/api-dump-unreviewed.txt | 2 + .../ide/wizard/NewProjectWizardBaseStep.kt | 6 +- .../src/messages/ActionsBundle.properties | 1 - .../src/idea/LangActions.xml | 7 +- .../java/testSources/setup/WorkspaceTest.kt | 3 - 15 files changed, 238 insertions(+), 244 deletions(-) delete mode 100644 platform/lang-impl/src/com/intellij/ide/workspace/CreateWorkspaceAction.kt delete mode 100644 platform/lang-impl/src/com/intellij/ide/workspace/NewWorkspaceDialog.kt create mode 100644 platform/lang-impl/src/com/intellij/ide/workspace/configuration/BaseWorkspaceAction.kt rename platform/lang-impl/src/com/intellij/ide/workspace/{ => configuration}/ManageWorkspaceAction.kt (86%) create mode 100644 platform/lang-impl/src/com/intellij/ide/workspace/configuration/ManageWorkspaceDialog.kt create mode 100644 platform/lang-impl/src/com/intellij/ide/workspace/configuration/NewWorkspaceWizard.kt create mode 100644 platform/lang-impl/src/com/intellij/ide/workspace/configuration/SubprojectList.kt diff --git a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java index 49ec4e25bee6..9d77d245fdf9 100644 --- a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java +++ b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java @@ -13,6 +13,7 @@ import com.intellij.ide.util.projectWizard.*; import com.intellij.ide.wizard.*; import com.intellij.ide.wizard.LanguageNewProjectWizard; import com.intellij.ide.wizard.language.*; +import com.intellij.ide.workspace.configuration.NewWorkspaceWizard; import com.intellij.internal.statistic.utils.PluginInfoDetectorKt; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; @@ -377,6 +378,7 @@ public final class ProjectTypeStep extends ModuleWizardStep implements SettingsS generators.sort(Comparator.comparing(it -> it.getOrdinal())); if (context.isCreatingNewProject()) { generators.add(new EmptyProjectGeneratorNewProjectWizard()); + generators.add(new NewWorkspaceWizard()); } var generatorItems = ContainerUtil.map(generators, it -> new LanguageGeneratorItem(it)); for (var generatorItem : generatorItems) { diff --git a/platform/lang-api/resources/messages/LangBundle.properties b/platform/lang-api/resources/messages/LangBundle.properties index ee2bab1d9270..806795688ae1 100644 --- a/platform/lang-api/resources/messages/LangBundle.properties +++ b/platform/lang-api/resources/messages/LangBundle.properties @@ -560,15 +560,9 @@ tooltip.bookmarked=Bookmarked progress.title.searching.for.classes.for.test=Searching for classes for test\u2026 progress.title.searching.for.tests.for.class=Searching for tests for class\u2026 -chooser.title.select.workspace.directory=Select Workspace Location chooser.title.select.file.or.directory.to.import=Select Files or Directories to Import -new.workspace.dialog.title=New Workspace manage.workspace.dialog.title=Manage Workspace new.workspace.dialog.name.label=Name: -new.workspace.dialog.location.label=Location: -new.workspace.dialog.hint=Projects will be opened in the same window as a workspace. \ - Projects remain in the same place on disk, \ - the workspace only stores common project settings. new.workspace.dialog.default.workspace.name=workspace prompt.open.project.attach.to.workspace=Projects can either be opened in a new window, or replace the project in the current window, \ or be attached to the current workspace.
How would you like to open the project? @@ -581,6 +575,8 @@ project.remove.confirmation.prompt=Remove {1, choice, 1#project|2#projects} {0} border.title.linked.projects=Linked Projects action.add.projects.text=Add Projects\u2026 dialog.message.project.can.t.be.added.to.workspace=Project ''{0}'' cannot be added to workspace +workspace.project.type.name=Workspace +workspace.project.type.comment=A special project type intended to work with multiple projects in the same window target.usages.option=usages target.text.occurrences.option=text occurrences diff --git a/platform/lang-impl/api-dump-unreviewed.txt b/platform/lang-impl/api-dump-unreviewed.txt index dfc2d5f6c891..5e1596e364d7 100644 --- a/platform/lang-impl/api-dump-unreviewed.txt +++ b/platform/lang-impl/api-dump-unreviewed.txt @@ -19202,6 +19202,14 @@ c:com.intellij.ide.util.treeView.SmartElementDescriptor - p:isMarkModified():Z - p:isMarkReadOnly():Z - update():Z +f:com.intellij.ide.workspace.configuration.NewWorkspaceWizard +- com.intellij.ide.wizard.GeneratorNewProjectWizard +- ():V +- createStep(com.intellij.ide.util.projectWizard.WizardContext):com.intellij.ide.wizard.NewProjectWizardStep +- getIcon():javax.swing.Icon +- getId():java.lang.String +- getName():java.lang.String +- isEnabled():Z f:com.intellij.internal.AddInlayBlockInternalAction - com.intellij.openapi.actionSystem.AnAction - com.intellij.openapi.project.DumbAware diff --git a/platform/lang-impl/src/com/intellij/ide/workspace/CreateWorkspaceAction.kt b/platform/lang-impl/src/com/intellij/ide/workspace/CreateWorkspaceAction.kt deleted file mode 100644 index 2e372845e01f..000000000000 --- a/platform/lang-impl/src/com/intellij/ide/workspace/CreateWorkspaceAction.kt +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ide.workspace - -import com.intellij.ide.impl.OpenProjectTask -import com.intellij.ide.impl.TrustedPaths -import com.intellij.openapi.actionSystem.ActionUpdateThread -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.project.DumbAwareAction -import com.intellij.openapi.project.Project -import com.intellij.openapi.project.ex.ProjectManagerEx -import com.intellij.openapi.startup.StartupManager -import com.intellij.util.concurrency.annotations.RequiresEdt -import java.nio.file.Files -import java.nio.file.Path - -internal abstract class BaseWorkspaceAction(private val workspaceOnly: Boolean): DumbAwareAction() { - override fun getActionUpdateThread() = ActionUpdateThread.BGT - override fun update(e: AnActionEvent) { - val project = e.project - e.presentation.isEnabledAndVisible = isWorkspaceSupportEnabled && - project != null && - (workspaceOnly && project.isWorkspace - || !workspaceOnly && getAllSubprojects(project).isNotEmpty()) - } -} - -internal open class CreateWorkspaceAction: BaseWorkspaceAction(false) { - override fun actionPerformed(e: AnActionEvent) { - val project = requireNotNull(e.project) - createWorkspace(project) - } -} - -@RequiresEdt -internal fun createWorkspace(project: Project) { - val subprojects = getAllSubprojects(project).associateBy { it.projectPath } - val dialog = NewWorkspaceDialog(project, subprojects.values, true) - if (!dialog.showAndGet()) return - - ApplicationManager.getApplication().executeOnPooledThread { - val workspace = createAndOpenWorkspaceProject(project, dialog.projectPath, dialog.projectName) - ?: return@executeOnPooledThread - StartupManager.getInstance(workspace).runAfterOpened { - addToWorkspace(workspace, dialog.projectPaths) - } - } -} - -private fun createAndOpenWorkspaceProject(project: Project, - workspacePath: Path, - projectName: String): Project? { - val options = OpenProjectTask { - projectToClose = project - this.projectName = projectName - forceReuseFrame = true - isNewProject = true - isProjectCreatedWithWizard = true - isRefreshVfsNeeded = true - beforeOpen = { workspace -> - setWorkspace(workspace) - true - } - } - Files.createDirectories(workspacePath) - TrustedPaths.getInstance().setProjectPathTrusted(workspacePath, true) - return ProjectManagerEx.getInstanceEx().openProject(workspacePath, options) -} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/ide/workspace/NewWorkspaceDialog.kt b/platform/lang-impl/src/com/intellij/ide/workspace/NewWorkspaceDialog.kt deleted file mode 100644 index 917d28a73b7c..000000000000 --- a/platform/lang-impl/src/com/intellij/ide/workspace/NewWorkspaceDialog.kt +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ide.workspace - -import com.intellij.ide.IdeBundle -import com.intellij.ide.RecentProjectsManager -import com.intellij.ide.workspace.projectView.isWorkspaceNode -import com.intellij.lang.LangBundle -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.fileChooser.FileChooser -import com.intellij.openapi.fileChooser.FileChooserDescriptor -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory -import com.intellij.openapi.project.Project -import com.intellij.openapi.project.guessProjectDir -import com.intellij.openapi.ui.DialogWrapper -import com.intellij.openapi.ui.TextFieldWithBrowseButton -import com.intellij.openapi.util.NlsSafe -import com.intellij.openapi.util.io.FileUtil -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.ui.* -import com.intellij.ui.components.JBList -import com.intellij.ui.components.JBTextField -import com.intellij.ui.dsl.builder.* -import com.intellij.util.SystemProperties -import java.io.File -import java.nio.file.Path -import java.nio.file.Paths -import javax.swing.Action -import javax.swing.Icon -import javax.swing.JComponent -import javax.swing.JList -import kotlin.io.path.pathString - -internal class NewWorkspaceDialog( - private val project: Project, - initialProjects: Collection, - private val isNewWorkspace: Boolean -) : DialogWrapper(project) { - private lateinit var nameField: JBTextField - private lateinit var locationField: TextFieldWithBrowseButton - private val listModel = CollectionListModel(initialProjects.map { Item(it.name, it.projectPath, it.handler.subprojectIcon) }) - private val projectList = JBList(listModel).apply { - cellRenderer = Renderer().apply { iconTextGap = 3 } - } - - val projectName: String get() = nameField.text - val location: Path get() = Paths.get(locationField.text) - val projectPath: Path get() = location.resolve(nameField.text) - - init { - if (isNewWorkspace) { - title = LangBundle.message("new.workspace.dialog.title") - okAction.putValue(Action.NAME, IdeBundle.message("button.create")) - } - else { - title = LangBundle.message("manage.workspace.dialog.title") - } - init() - } - - val projectPaths: List - get() = listModel.items.map { it.path } - - override fun createCenterPanel(): JComponent { - val suggestLocation = RecentProjectsManager.getInstance().suggestNewProjectLocation() - val suggestName = if (isNewWorkspace) - FileUtil.createSequentFileName(File(suggestLocation), - LangBundle.message("new.workspace.dialog.default.workspace.name"), "") { !it.exists() } - else - project.name - - val toolbarDecorator = ToolbarDecorator.createDecorator(projectList) - .setPanelBorder(IdeBorderFactory.createTitledBorder(LangBundle.message("border.title.linked.projects"))) - .disableUpDownActions() - .setAddActionName(LangBundle.message("action.add.projects.text")) - .setAddAction { addProjects() } - return panel { - row(LangBundle.message("new.workspace.dialog.name.label")) { - nameField = textField() - .text(suggestName) - .columns(COLUMNS_MEDIUM) - .align(Align.FILL) - .component - } - if (isNewWorkspace) { - row(LangBundle.message("new.workspace.dialog.location.label")) { - val descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor() - descriptor.isHideIgnored = false - descriptor.title = LangBundle.message("chooser.title.select.workspace.directory") - - locationField = textFieldWithBrowseButton(project = project, fileChooserDescriptor = descriptor) - .text(suggestLocation) - .columns(COLUMNS_MEDIUM) - .align(Align.FILL) - .component - } - } - row { - cell(toolbarDecorator.createPanel()).align(Align.FILL) - } - if (isNewWorkspace) { - row { - comment(LangBundle.message("new.workspace.dialog.hint"), maxLineLength = 60) - } - } - } - } - - override fun doOKAction() { - if (isNewWorkspace) { - RecentProjectsManager.getInstance().setLastProjectCreationLocation(location) - } - super.doOKAction() - } - - private fun addProjects() { - val files = browseForProjects(project) - val allItems = listModel.items - for (file in files) { - if (allItems.any { it.path == file.path }) continue - val handler = getHandlers(file).firstOrNull() ?: continue - listModel.add(Item(file.name, file.path, handler.subprojectIcon)) - } - } - - private data class Item(@NlsSafe val name: String, @NlsSafe val path: String, val icon: Icon?) - - private class Renderer: ColoredListCellRenderer() { - override fun customizeCellRenderer(list: JList, value: Item?, index: Int, selected: Boolean, hasFocus: Boolean) { - value ?: return - icon = value.icon - append(value.name + " ", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES) - @Suppress("HardCodedStringLiteral") val userHome = SystemProperties.getUserHome() - append(Path.of(value.path).parent.pathString.replaceFirst(userHome, "~"), SimpleTextAttributes.GRAY_ATTRIBUTES) - } - } -} - -private fun browseForProjects(project: Project): Array { - val handlers = SubprojectHandler.EP_NAME.extensionList - val descriptor = FileChooserDescriptor(true, true, false, false, false, true) - descriptor.title = LangBundle.message("chooser.title.select.file.or.directory.to.import") - descriptor.withFileFilter { file -> handlers.any { it.canImportFromFile(file) } } - return FileChooser.chooseFiles(descriptor, project, project.guessProjectDir()?.parent) -} - -internal class AddProjectsToWorkspaceAction: BaseWorkspaceAction(true) { - override fun actionPerformed(e: AnActionEvent) { - val project = requireNotNull(e.project) - val files = browseForProjects(project) - if (files.isEmpty()) return - addToWorkspace(project, files.map { it.path }) - } - - override fun update(e: AnActionEvent) { - e.presentation.isEnabledAndVisible = isWorkspaceNode(e) - } -} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/ide/workspace/configuration/BaseWorkspaceAction.kt b/platform/lang-impl/src/com/intellij/ide/workspace/configuration/BaseWorkspaceAction.kt new file mode 100644 index 000000000000..cb3e1059ee75 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/workspace/configuration/BaseWorkspaceAction.kt @@ -0,0 +1,20 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.ide.workspace.configuration + +import com.intellij.ide.workspace.getAllSubprojects +import com.intellij.ide.workspace.isWorkspace +import com.intellij.ide.workspace.isWorkspaceSupportEnabled +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.DumbAwareAction + +internal abstract class BaseWorkspaceAction(private val workspaceOnly: Boolean): DumbAwareAction() { + override fun getActionUpdateThread() = ActionUpdateThread.BGT + override fun update(e: AnActionEvent) { + val project = e.project + e.presentation.isEnabledAndVisible = isWorkspaceSupportEnabled && + project != null && + (workspaceOnly && project.isWorkspace + || !workspaceOnly && getAllSubprojects(project).isNotEmpty()) + } +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/ide/workspace/ManageWorkspaceAction.kt b/platform/lang-impl/src/com/intellij/ide/workspace/configuration/ManageWorkspaceAction.kt similarity index 86% rename from platform/lang-impl/src/com/intellij/ide/workspace/ManageWorkspaceAction.kt rename to platform/lang-impl/src/com/intellij/ide/workspace/configuration/ManageWorkspaceAction.kt index 64f724eb38a1..76d3608ca96c 100644 --- a/platform/lang-impl/src/com/intellij/ide/workspace/ManageWorkspaceAction.kt +++ b/platform/lang-impl/src/com/intellij/ide/workspace/configuration/ManageWorkspaceAction.kt @@ -1,8 +1,11 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ide.workspace +package com.intellij.ide.workspace.configuration import com.intellij.ide.projectView.ProjectView +import com.intellij.ide.workspace.addToWorkspace +import com.intellij.ide.workspace.getAllSubprojects import com.intellij.ide.workspace.projectView.isWorkspaceNode +import com.intellij.ide.workspace.removeSubprojects import com.intellij.idea.ActionsBundle import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.AnActionEvent @@ -11,14 +14,14 @@ import com.intellij.openapi.project.ex.ProjectEx internal open class ManageWorkspaceAction: BaseWorkspaceAction(true) { override fun actionPerformed(e: AnActionEvent) { val project = requireNotNull(e.project) - val subprojects = getAllSubprojects(project) - val dialog = NewWorkspaceDialog(project, subprojects, false) + val dialog = ManageWorkspaceDialog(project) if (!dialog.showAndGet()) return if (dialog.projectName != project.name) { (project as ProjectEx).setProjectName(dialog.projectName) ProjectView.getInstance(project).currentProjectViewPane?.updateFromRoot(true) } + val subprojects = getAllSubprojects(project) val set = dialog.projectPaths.toSet() val removed = subprojects.filter { !set.contains(it.projectPath) } removeSubprojects(project, removed) diff --git a/platform/lang-impl/src/com/intellij/ide/workspace/configuration/ManageWorkspaceDialog.kt b/platform/lang-impl/src/com/intellij/ide/workspace/configuration/ManageWorkspaceDialog.kt new file mode 100644 index 000000000000..123cdaa919f5 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/workspace/configuration/ManageWorkspaceDialog.kt @@ -0,0 +1,75 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.ide.workspace.configuration + +import com.intellij.ide.workspace.SubprojectHandler +import com.intellij.ide.workspace.addToWorkspace +import com.intellij.ide.workspace.projectView.isWorkspaceNode +import com.intellij.lang.LangBundle +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.fileChooser.FileChooser +import com.intellij.openapi.fileChooser.FileChooserDescriptor +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.guessProjectDir +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.TextFieldWithBrowseButton +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.ui.components.JBTextField +import com.intellij.ui.dsl.builder.* +import java.nio.file.Path +import java.nio.file.Paths +import javax.swing.JComponent + +internal class ManageWorkspaceDialog(private val project: Project) : DialogWrapper(project) { + private lateinit var nameField: JBTextField + private lateinit var locationField: TextFieldWithBrowseButton + private val subprojectList: SubprojectList = SubprojectList(project) + + val projectName: String get() = nameField.text + val location: Path get() = Paths.get(locationField.text) + val projectPath: Path get() = location.resolve(nameField.text) + + init { + title = LangBundle.message("manage.workspace.dialog.title") + init() + } + + val projectPaths: List + get() = subprojectList.projectPaths + + override fun createCenterPanel(): JComponent { + + return panel { + row(LangBundle.message("new.workspace.dialog.name.label")) { + nameField = textField() + .text(project.name) + .columns(COLUMNS_MEDIUM) + .align(Align.FILL) + .component + } + row { + cell(subprojectList.createDecorator().createPanel()).align(Align.FILL) + } + } + } +} + +internal fun browseForProjects(project: Project?): Array { + val handlers = SubprojectHandler.EP_NAME.extensionList + val descriptor = FileChooserDescriptor(true, true, false, false, false, true) + descriptor.title = LangBundle.message("chooser.title.select.file.or.directory.to.import") + descriptor.withFileFilter { file -> handlers.any { it.canImportFromFile(file) } } + return FileChooser.chooseFiles(descriptor, project, project?.guessProjectDir()?.parent) +} + +internal class AddProjectsToWorkspaceAction: BaseWorkspaceAction(true) { + override fun actionPerformed(e: AnActionEvent) { + val project = requireNotNull(e.project) + val files = browseForProjects(project) + if (files.isEmpty()) return + addToWorkspace(project, files.map { it.path }) + } + + override fun update(e: AnActionEvent) { + e.presentation.isEnabledAndVisible = isWorkspaceNode(e) + } +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/ide/workspace/configuration/NewWorkspaceWizard.kt b/platform/lang-impl/src/com/intellij/ide/workspace/configuration/NewWorkspaceWizard.kt new file mode 100644 index 000000000000..936c5394ecc6 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/workspace/configuration/NewWorkspaceWizard.kt @@ -0,0 +1,56 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.ide.workspace.configuration + +import com.intellij.icons.ExpUiIcons +import com.intellij.ide.impl.ProjectUtil +import com.intellij.ide.util.projectWizard.WizardContext +import com.intellij.ide.wizard.* +import com.intellij.ide.wizard.NewProjectWizardChainStep.Companion.nextStep +import com.intellij.ide.wizard.comment.CommentNewProjectWizardStep +import com.intellij.ide.workspace.addToWorkspace +import com.intellij.ide.workspace.isWorkspaceSupportEnabled +import com.intellij.ide.workspace.setWorkspace +import com.intellij.lang.LangBundle +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.StartupManager +import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.Panel +import org.jetbrains.annotations.Nls +import javax.swing.Icon + +class NewWorkspaceWizard: GeneratorNewProjectWizard { + override val id: String = "jb.workspace" + override val name: @Nls(capitalization = Nls.Capitalization.Title) String = LangBundle.message("workspace.project.type.name") + override val icon: Icon = ExpUiIcons.Nodes.Workspace + + override fun createStep(context: WizardContext): NewProjectWizardStep = + RootNewProjectWizardStep(context) + .nextStep(::CommentStep) + .nextStep { parent -> newProjectWizardBaseStepWithoutGap(parent).apply { defaultName = "workspace" } } + .nextStep(::GitNewProjectWizardStep) + .nextStep(::Step) + + override fun isEnabled(): Boolean = isWorkspaceSupportEnabled + + private class CommentStep(parent: NewProjectWizardStep) : CommentNewProjectWizardStep(parent) { + override val comment: @Nls(capitalization = Nls.Capitalization.Sentence) String = + LangBundle.message("workspace.project.type.comment") + } + + private class Step(parent: NewProjectWizardStep) : AbstractNewProjectWizardStep(parent) { + private val subprojectList = SubprojectList(ProjectUtil.getActiveProject()) + + override fun setupUI(builder: Panel) { + builder.row { + cell(subprojectList.createDecorator().createPanel()).align(Align.FILL) + } + } + + override fun setupProject(project: Project) { + setWorkspace(project) + StartupManager.getInstance(project).runAfterOpened { + addToWorkspace(project, subprojectList.projectPaths) + } + } + } +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/ide/workspace/configuration/SubprojectList.kt b/platform/lang-impl/src/com/intellij/ide/workspace/configuration/SubprojectList.kt new file mode 100644 index 000000000000..fbde9beb0e0f --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/workspace/configuration/SubprojectList.kt @@ -0,0 +1,60 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.ide.workspace.configuration + +import com.intellij.ide.workspace.getAllSubprojects +import com.intellij.ide.workspace.getHandlers +import com.intellij.lang.LangBundle +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.NlsSafe +import com.intellij.ui.* +import com.intellij.ui.components.JBList +import com.intellij.util.SystemProperties +import java.nio.file.Path +import javax.swing.Icon +import javax.swing.JList +import kotlin.io.path.pathString + +internal class SubprojectList(private val currentProject: Project?) { + + private val listModel: CollectionListModel + private val projectList: JBList + + init { + val subprojects = currentProject?.let { getAllSubprojects(currentProject) } ?: emptyList() + listModel = CollectionListModel(subprojects.map { Item(it.name, it.projectPath, it.handler.subprojectIcon) }) + projectList = JBList(listModel).apply { + cellRenderer = Renderer().apply { iconTextGap = 3 } + } + } + + fun createDecorator(): ToolbarDecorator = ToolbarDecorator.createDecorator(projectList) + .setPanelBorder(IdeBorderFactory.createTitledBorder(LangBundle.message("border.title.linked.projects"))) + .disableUpDownActions() + .setAddActionName(LangBundle.message("action.add.projects.text")) + .setAddAction { addProjects() } + + val projectPaths: List + get() = listModel.items.map { it.path } + + private data class Item(@NlsSafe val name: String, @NlsSafe val path: String, val icon: Icon?) + + private class Renderer: ColoredListCellRenderer() { + override fun customizeCellRenderer(list: JList, value: Item?, index: Int, selected: Boolean, hasFocus: Boolean) { + value ?: return + icon = value.icon + append(value.name + " ", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES) + @Suppress("HardCodedStringLiteral") val userHome = SystemProperties.getUserHome() + append(Path.of(value.path).parent.pathString.replaceFirst(userHome, "~"), SimpleTextAttributes.GRAY_ATTRIBUTES) + } + } + + private fun addProjects() { + val files = browseForProjects(currentProject) + val allItems = listModel.items + for (file in files) { + if (allItems.any { it.path == file.path }) continue + val handler = getHandlers(file).firstOrNull() ?: continue + listModel.add(Item(file.name, file.path, handler.subprojectIcon)) + } + } +} \ No newline at end of file diff --git a/platform/platform-impl/api-dump-unreviewed.txt b/platform/platform-impl/api-dump-unreviewed.txt index 2bf26cb04330..704f54a02f71 100644 --- a/platform/platform-impl/api-dump-unreviewed.txt +++ b/platform/platform-impl/api-dump-unreviewed.txt @@ -10754,10 +10754,12 @@ f:com.intellij.ide.wizard.NewProjectWizardBaseStep - com.intellij.ide.wizard.NewProjectWizardBaseData - sf:Companion:com.intellij.ide.wizard.NewProjectWizardBaseStep$Companion - (com.intellij.ide.wizard.NewProjectWizardStep):V +- f:getDefaultName():java.lang.String - getName():java.lang.String - getNameProperty():com.intellij.openapi.observable.properties.GraphProperty - getPath():java.lang.String - getPathProperty():com.intellij.openapi.observable.properties.GraphProperty +- f:setDefaultName(java.lang.String):V - setName(java.lang.String):V - setPath(java.lang.String):V - setupProject(com.intellij.openapi.project.Project):V diff --git a/platform/platform-impl/src/com/intellij/ide/wizard/NewProjectWizardBaseStep.kt b/platform/platform-impl/src/com/intellij/ide/wizard/NewProjectWizardBaseStep.kt index d335deeeae9e..aa9628a6c92a 100644 --- a/platform/platform-impl/src/com/intellij/ide/wizard/NewProjectWizardBaseStep.kt +++ b/platform/platform-impl/src/com/intellij/ide/wizard/NewProjectWizardBaseStep.kt @@ -43,6 +43,8 @@ class NewProjectWizardBaseStep(parent: NewProjectWizardStep) : AbstractNewProjec override var name: String by nameProperty override var path: String by pathProperty + var defaultName: String = "untitled" + internal var bottomGap: Boolean = true private fun suggestLocation(): Path { @@ -70,8 +72,8 @@ class NewProjectWizardBaseStep(parent: NewProjectWizardStep) : AbstractNewProjec private fun suggestUniqueName(): String { val moduleNames = findAllModules().map { it.name }.toSet() - val path = path.toNioPathOrNull() ?: return "untitled" - return FileUtil.createSequentFileName(path.toFile(), "untitled", "") { + val path = path.toNioPathOrNull() ?: return defaultName + return FileUtil.createSequentFileName(path.toFile(), defaultName, "") { !it.exists() && it.name !in moduleNames } } diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index 0c152f6848b0..0a4e1da014ff 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -2800,7 +2800,6 @@ action.SwitchFileBasedIndexStorageAction.text=Switch File-Based Index Storage action.AddToWorkspace.text=Add Projects to Workspace\u2026 action.ManageWorkspace.text=Manage Workspace\u2026 -action.CreateWorkspace.text=Create Workspace\u2026 action.remove.workspace.subproject.x.text=Remove ''{0}'' from Workspace action.remove.workspace.subprojects.text=Remove Projects from Workspace diff --git a/platform/platform-resources/src/idea/LangActions.xml b/platform/platform-resources/src/idea/LangActions.xml index 9643f5c5d637..8b1adb3df6a5 100644 --- a/platform/platform-resources/src/idea/LangActions.xml +++ b/platform/platform-resources/src/idea/LangActions.xml @@ -1656,15 +1656,14 @@ - - + - - + + diff --git a/plugins/gradle/java/testSources/setup/WorkspaceTest.kt b/plugins/gradle/java/testSources/setup/WorkspaceTest.kt index 622bc6bf564f..f3e7767e6a32 100644 --- a/plugins/gradle/java/testSources/setup/WorkspaceTest.kt +++ b/plugins/gradle/java/testSources/setup/WorkspaceTest.kt @@ -9,7 +9,6 @@ import com.intellij.testFramework.useProject import kotlinx.coroutines.runBlocking import org.jetbrains.plugins.gradle.testFramework.GradleTestCase import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -29,8 +28,6 @@ class WorkspaceTest: GradleTestCase() { val event = TestActionEvent.createTestEvent(SimpleDataContext.getProjectContext(it)) ActionManager.getInstance().getAction("ManageWorkspace").update(event) assertFalse(event.presentation.isEnabledAndVisible) - ActionManager.getInstance().getAction("CreateWorkspace").update(event) - assertTrue(event.presentation.isEnabledAndVisible) } } }