From 132dcb8a769703c166d5b52e4fbd10cfc1446cd2 Mon Sep 17 00:00:00 2001 From: nik Date: Mon, 12 Feb 2018 15:41:44 +0300 Subject: [PATCH] project configuration: provide action to change names for multiple modules Since now modules are grouped automatically accordingly to their names there should be a convenient way to specify or change a group for several modules (IDEA-186246). --- .../actions/ChangeModuleNamesAction.kt | 69 +++++++++++++++++++ .../ModuleStructureConfigurable.java | 14 +++- .../src/messages/ProjectBundle.properties | 10 +++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/actions/ChangeModuleNamesAction.kt diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/actions/ChangeModuleNamesAction.kt b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/actions/ChangeModuleNamesAction.kt new file mode 100644 index 000000000000..1b05f2db6abf --- /dev/null +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/actions/ChangeModuleNamesAction.kt @@ -0,0 +1,69 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.roots.ui.configuration.actions + +import com.intellij.ide.highlighter.ModuleFileType +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.LangDataKeys +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.isQualifiedModuleNamesEnabled +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.openapi.project.ProjectBundle +import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService +import com.intellij.openapi.ui.InputValidatorEx +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.util.TextRange +import com.intellij.util.PathUtilRt + +class ChangeModuleNamesAction : DumbAwareAction(ProjectBundle.message("action.text.change.module.names"), + ProjectBundle.message("action.description.change.module.names"), null) { + override fun update(e: AnActionEvent) { + val project = e.project + e.presentation.isVisible = project != null && isQualifiedModuleNamesEnabled(project) && e.getData(LangDataKeys.MODIFIABLE_MODULE_MODEL) != null + val modules = e.getData(LangDataKeys.MODULE_CONTEXT_ARRAY) + e.presentation.isEnabled = modules != null && modules.size > 1 + } + + override fun actionPerformed(e: AnActionEvent) { + val modules = e.getData(LangDataKeys.MODULE_CONTEXT_ARRAY) ?: return + val model = e.getData(LangDataKeys.MODIFIABLE_MODULE_MODEL) ?: return + + fun getGroupName(module: Module) = model.getActualName(module).substringBeforeLast('.') + "." + + val commonPrefix = modules.fold(getGroupName(modules[0])) { prefix, m -> prefix.commonPrefixWith(getGroupName(m)) } + .substringBeforeLast('.', "") + val isPrefixEqualToModuleName = modules.any { model.getActualName(it) == commonPrefix } + val validator = object : InputValidatorEx { + override fun getErrorText(inputString: String): String? { + if (inputString.isNotEmpty() && inputString.split('.').any { it.isEmpty() }) { + return ProjectBundle.message("error.message.module.name.prefix.contains.empty.string") + } + if (isPrefixEqualToModuleName && inputString.isEmpty()) { + return ProjectBundle.message("error.message.module.name.cannot.be.empty") + } + if (!PathUtilRt.isValidFileName(inputString + ModuleFileType.DOT_DEFAULT_EXTENSION, true)) { + return ProjectBundle.message("error.message.module.name.prefix.contains.invalid.chars") + } + return null + } + + override fun checkInput(inputString: String) = getErrorText(inputString) == null + + override fun canClose(inputString: String) = getErrorText(inputString) == null + } + + val newPrefix = Messages.showInputDialog(e.project, ProjectBundle.message("dialog.text.enter.common.prefix", modules.size), + ProjectBundle.message("dialog.title.change.module.names"), null, commonPrefix, validator, + TextRange.allOf(commonPrefix), ProjectBundle.message("dialog.text.enter.common.prefix.comment")) + if (newPrefix == null) return + + val prefixToRemove = if (commonPrefix.isEmpty()) "" else "$commonPrefix." + val prefixToPrepend = if (newPrefix.isEmpty() || newPrefix.endsWith('.')) newPrefix else "$newPrefix." + + modules.forEach { + val oldName = model.getActualName(it) + val newName = if (oldName == commonPrefix) newPrefix else prefixToPrepend + oldName.removePrefix(prefixToRemove) + model.renameModule(it, newName) + } + ProjectSettingsService.getInstance(e.project).processModulesMoved(modules, null) + } +} \ No newline at end of file diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java index 6c3ecefeea22..213d212d3a82 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java @@ -34,6 +34,7 @@ import com.intellij.openapi.roots.ui.configuration.ClasspathEditor; import com.intellij.openapi.roots.ui.configuration.ModuleEditor; import com.intellij.openapi.roots.ui.configuration.ModulesConfigurator; import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; +import com.intellij.openapi.roots.ui.configuration.actions.ChangeModuleNamesAction; import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.LibraryProjectStructureElement; import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ModuleProjectStructureElement; import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureDaemonAnalyzer; @@ -137,6 +138,7 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple protected ArrayList getAdditionalActions() { final ArrayList result = new ArrayList<>(); result.add(ActionManager.getInstance().getAction(IdeActions.GROUP_MOVE_MODULE_TO_GROUP)); + result.add(new ChangeModuleNamesAction()); return result; } @@ -736,7 +738,7 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple if (LangDataKeys.MODULE_CONTEXT_ARRAY.is(dataId)) { final TreePath[] paths = myTree.getSelectionPaths(); if (paths != null) { - ArrayList modules = new ArrayList<>(); + Set modules = new LinkedHashSet<>(); for (TreePath path : paths) { MyNode node = (MyNode)path.getLastPathComponent(); final NamedConfigurable configurable = node.getConfigurable(); @@ -745,6 +747,16 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple if (o instanceof Module) { modules.add((Module)o); } + else if (node instanceof ModuleGroupNode && ((ModuleGroupNode)node).getModuleGroup() != null) { + TreeUtil.treeNodeTraverser(node).forEach(descendant -> { + if (descendant instanceof MyNode) { + Object object = ((MyNode)descendant).getConfigurable().getEditableObject(); + if (object instanceof Module) { + modules.add((Module)object); + } + } + }); + } } return !modules.isEmpty() ? modules.toArray(Module.EMPTY_ARRAY) : null; } diff --git a/platform/projectModel-impl/src/messages/ProjectBundle.properties b/platform/projectModel-impl/src/messages/ProjectBundle.properties index 97a02213e3c4..4960e2273bc4 100644 --- a/platform/projectModel-impl/src/messages/ProjectBundle.properties +++ b/platform/projectModel-impl/src/messages/ProjectBundle.properties @@ -388,6 +388,16 @@ project.roots.external.annotations.description=Manage external annotations add.external.annotations.path.title=Add path to external annotations add.external.annotations.path.description=Select path in which external annotations for module sources is located +action.text.change.module.names=Change Module Names... +action.description.change.module.names=Adjust automatic grouping of modules by changing the common prefix for their names +dialog.title.change.module.names=Change Module Names +dialog.text.enter.common.prefix=Specify prefix for names of {0} selected modules: +dialog.text.enter.common.prefix.comment=Modules are grouped in the IDE accordingly to their names;
if names of several modules have a common \ + prefix they will be shown together. +error.message.module.name.prefix.contains.empty.string=A prefix must be a sequence of words separated by dots +error.message.module.name.cannot.be.empty=A module name cannot be empty +error.message.module.name.prefix.contains.invalid.chars=A prefix must be composed of characters which can be used in a file name + dialog.title.packaging.choose.library=Choose Library dialog.title.packaging.choose.module=Choose Module