mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
project view: support deleting unloaded modules
Previously when a user press 'Delete' on a content root from an unloaded module in 'Project View', IDE will suggest to delete a directory. It may be unexpected and even lead to data loss if the user don't read the message carefully (IDEA-182200). Now it'll suggest to remove the unloaded module from the project.
This commit is contained in:
+71
-10
@@ -1,10 +1,12 @@
|
||||
// Copyright 2000-2017 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.projectView.ProjectView;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.LangDataKeys;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.module.UnloadedModuleDescription;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
@@ -12,6 +14,10 @@ import com.intellij.openapi.ui.TestDialog;
|
||||
import com.intellij.testFramework.MapDataContext;
|
||||
import com.intellij.testFramework.PlatformTestCase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author nik
|
||||
*/
|
||||
@@ -23,10 +29,10 @@ public class ModuleDeleteProviderTest extends PlatformTestCase {
|
||||
}
|
||||
|
||||
public void testSimple() {
|
||||
Module a = createModule("a");
|
||||
assertNotNull(ModuleManager.getInstance(myProject).findModuleByName("a"));
|
||||
deleteModules(a);
|
||||
assertNull(ModuleManager.getInstance(myProject).findModuleByName("a"));
|
||||
createModule("a");
|
||||
assertNotNull(getModuleManager().findModuleByName("a"));
|
||||
deleteModules("a");
|
||||
assertNull(getModuleManager().findModuleByName("a"));
|
||||
}
|
||||
|
||||
public void testDeleteDependency() {
|
||||
@@ -34,7 +40,7 @@ public class ModuleDeleteProviderTest extends PlatformTestCase {
|
||||
Module b = createModule("b");
|
||||
ModuleRootModificationUtil.addDependency(a, b);
|
||||
assertSameElements(ModuleRootManager.getInstance(a).getDependencyModuleNames(), "b");
|
||||
deleteModules(b);
|
||||
deleteModules("b");
|
||||
assertEmpty(ModuleRootManager.getInstance(a).getDependencyModuleNames());
|
||||
}
|
||||
|
||||
@@ -45,17 +51,72 @@ public class ModuleDeleteProviderTest extends PlatformTestCase {
|
||||
ModuleRootModificationUtil.addDependency(myModule, a);
|
||||
ModuleRootModificationUtil.addDependency(myModule, b);
|
||||
assertSameElements(ModuleRootManager.getInstance(myModule).getDependencyModuleNames(), "a", "b");
|
||||
deleteModules(a, b);
|
||||
assertNull(ModuleManager.getInstance(myProject).findModuleByName("a"));
|
||||
assertNull(ModuleManager.getInstance(myProject).findModuleByName("b"));
|
||||
deleteModules("a", "b");
|
||||
assertNull(getModuleManager().findModuleByName("a"));
|
||||
assertNull(getModuleManager().findModuleByName("b"));
|
||||
assertEmpty(ModuleRootManager.getInstance(myModule).getDependencyModuleNames());
|
||||
}
|
||||
|
||||
private void deleteModules(Module... modules) {
|
||||
public void testUnloaded() {
|
||||
createModule("a");
|
||||
getModuleManager().setUnloadedModules(Arrays.asList("a"));
|
||||
assertNotNull(getModuleManager().getUnloadedModuleDescription("a"));
|
||||
deleteModules("a");
|
||||
assertNull(getModuleManager().getUnloadedModuleDescription("a"));
|
||||
}
|
||||
|
||||
public void testDeleteDependencyOnUnloadedModule() {
|
||||
Module a = createModule("a");
|
||||
Module b = createModule("b");
|
||||
ModuleRootModificationUtil.addDependency(a, b);
|
||||
getModuleManager().setUnloadedModules(Arrays.asList("b"));
|
||||
assertSameElements(ModuleRootManager.getInstance(a).getDependencyModuleNames(), "b");
|
||||
deleteModules("b");
|
||||
assertEmpty(ModuleRootManager.getInstance(a).getDependencyModuleNames());
|
||||
}
|
||||
|
||||
public void testLoadedAndUnloadedModule() {
|
||||
Module a = createModule("a");
|
||||
Module b = createModule("b");
|
||||
ModuleRootModificationUtil.addDependency(a, b);
|
||||
ModuleRootModificationUtil.addDependency(myModule, a);
|
||||
ModuleRootModificationUtil.addDependency(myModule, b);
|
||||
getModuleManager().setUnloadedModules(Arrays.asList("a"));
|
||||
assertSameElements(ModuleRootManager.getInstance(myModule).getDependencyModuleNames(), "a", "b");
|
||||
deleteModules("a", "b");
|
||||
assertNull(getModuleManager().findModuleByName("a"));
|
||||
assertNull(getModuleManager().findModuleByName("b"));
|
||||
assertEmpty(getModuleManager().getUnloadedModuleDescriptions());
|
||||
assertEmpty(ModuleRootManager.getInstance(myModule).getDependencyModuleNames());
|
||||
}
|
||||
|
||||
private ModuleManager getModuleManager() {
|
||||
return ModuleManager.getInstance(myProject);
|
||||
}
|
||||
|
||||
private void deleteModules(String... names) {
|
||||
ModuleDeleteProvider provider = new ModuleDeleteProvider();
|
||||
MapDataContext dataContext = new MapDataContext();
|
||||
dataContext.put(CommonDataKeys.PROJECT, myProject);
|
||||
dataContext.put(LangDataKeys.MODULE_CONTEXT_ARRAY, modules);
|
||||
List<Module> modules = new ArrayList<>();
|
||||
List<UnloadedModuleDescription> unloaded = new ArrayList<>();
|
||||
for (String name : names) {
|
||||
UnloadedModuleDescription description = getModuleManager().getUnloadedModuleDescription(name);
|
||||
if (description != null) {
|
||||
unloaded.add(description);
|
||||
}
|
||||
else {
|
||||
Module module = getModuleManager().findModuleByName(name);
|
||||
assertNotNull("Module " + name + " not found", module);
|
||||
modules.add(module);
|
||||
}
|
||||
}
|
||||
if (!modules.isEmpty()) {
|
||||
dataContext.put(LangDataKeys.MODULE_CONTEXT_ARRAY, modules.toArray(Module.EMPTY_ARRAY));
|
||||
}
|
||||
if (!unloaded.isEmpty()) {
|
||||
dataContext.put(ProjectView.UNLOADED_MODULES_CONTEXT_KEY, unloaded);
|
||||
}
|
||||
assertTrue(provider.canDeleteElement(dataContext));
|
||||
provider.deleteElement(dataContext);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ package com.intellij.ide.projectView;
|
||||
|
||||
import com.intellij.ide.SelectInTarget;
|
||||
import com.intellij.ide.projectView.impl.AbstractProjectViewPane;
|
||||
import com.intellij.openapi.actionSystem.DataKey;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.module.UnloadedModuleDescription;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.ActionCallback;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
@@ -27,8 +29,14 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ProjectView {
|
||||
/**
|
||||
* Use this key to get unloaded modules which content roots are selected in Project View
|
||||
*/
|
||||
public static final DataKey<List<UnloadedModuleDescription>> UNLOADED_MODULES_CONTEXT_KEY = DataKey.create("context.unloaded.modules.list");
|
||||
|
||||
public static ProjectView getInstance(Project project) {
|
||||
return ServiceManager.getService(project, ProjectView.class);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,9 @@ import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.fileEditor.TextEditor;
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.module.UnloadedModuleDescription;
|
||||
import com.intellij.openapi.project.DumbAware;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.*;
|
||||
@@ -87,6 +89,7 @@ import com.intellij.ui.tree.TreeVisitor;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IJSwingUtilities;
|
||||
import com.intellij.util.PlatformIcons;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.JBIterable;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
@@ -1104,7 +1107,7 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo
|
||||
}
|
||||
if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
|
||||
final Module[] modules = getSelectedModules();
|
||||
if (modules != null) {
|
||||
if (modules != null || !getSelectedUnloadedModules().isEmpty()) {
|
||||
return myDeleteModuleProvider;
|
||||
}
|
||||
final LibraryOrderEntry orderEntry = getSelectedLibrary();
|
||||
@@ -1152,6 +1155,9 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo
|
||||
if (LangDataKeys.MODULE_CONTEXT_ARRAY.is(dataId)) {
|
||||
return getSelectedModules();
|
||||
}
|
||||
if (UNLOADED_MODULES_CONTEXT_KEY.is(dataId)) {
|
||||
return Collections.unmodifiableList(getSelectedUnloadedModules());
|
||||
}
|
||||
if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) {
|
||||
final List<ModuleGroup> selectedElements = getSelectedElements(ModuleGroup.class);
|
||||
return selectedElements.isEmpty() ? null : selectedElements.toArray(new ModuleGroup[selectedElements.size()]);
|
||||
@@ -1260,6 +1266,21 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo
|
||||
return result.toArray(new Module[result.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
private List<UnloadedModuleDescription> getSelectedUnloadedModules() {
|
||||
final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
|
||||
if (viewPane == null) return Collections.emptyList();
|
||||
List<UnloadedModuleDescription> result = new SmartList<>();
|
||||
for (Object element : viewPane.getSelectedElements()) {
|
||||
if (element instanceof PsiDirectory) {
|
||||
ContainerUtil.addIfNotNull(result, getUnloadedModuleByContentRoot(((PsiDirectory)element).getVirtualFile()));
|
||||
}
|
||||
else if (element instanceof VirtualFile) {
|
||||
ContainerUtil.addIfNotNull(result, getUnloadedModuleByContentRoot((VirtualFile)element));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/** Project view has the same node for module and its single content root
|
||||
@@ -1280,6 +1301,15 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private UnloadedModuleDescription getUnloadedModuleByContentRoot(@NotNull VirtualFile file) {
|
||||
String moduleName = ProjectRootsUtil.findUnloadedModuleByContentRoot(file, myProject);
|
||||
if (moduleName != null) {
|
||||
return ModuleManager.getInstance(myProject).getUnloadedModuleDescription(moduleName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private <T> List<T> getSelectedElements(@NotNull Class<T> klass) {
|
||||
List<T> result = new ArrayList<>();
|
||||
|
||||
+31
-16
@@ -18,14 +18,14 @@ package com.intellij.openapi.roots.ui.configuration.actions;
|
||||
|
||||
import com.intellij.ide.DeleteProvider;
|
||||
import com.intellij.ide.TitledHandler;
|
||||
import com.intellij.ide.projectView.ProjectView;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.actionSystem.LangDataKeys;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.module.ModifiableModuleModel;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.module.*;
|
||||
import com.intellij.openapi.module.impl.LoadedModuleDescriptionImpl;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectBundle;
|
||||
import com.intellij.openapi.roots.ModifiableRootModel;
|
||||
@@ -39,6 +39,7 @@ import com.intellij.project.ProjectKt;
|
||||
import com.intellij.projectImport.ProjectAttachProcessor;
|
||||
import com.intellij.util.PathUtilRt;
|
||||
import com.intellij.util.PlatformUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
@@ -48,7 +49,8 @@ public class ModuleDeleteProvider implements DeleteProvider, TitledHandler {
|
||||
@Override
|
||||
public boolean canDeleteElement(@NotNull DataContext dataContext) {
|
||||
final Module[] modules = LangDataKeys.MODULE_CONTEXT_ARRAY.getData(dataContext);
|
||||
return modules != null && !containsPrimaryModule(modules);
|
||||
List<UnloadedModuleDescription> unloadedModules = ProjectView.UNLOADED_MODULES_CONTEXT_KEY.getData(dataContext);
|
||||
return modules != null && !containsPrimaryModule(modules) || unloadedModules != null && !unloadedModules.isEmpty();
|
||||
}
|
||||
|
||||
private static boolean containsPrimaryModule(Module[] modules) {
|
||||
@@ -73,12 +75,21 @@ public class ModuleDeleteProvider implements DeleteProvider, TitledHandler {
|
||||
|
||||
@Override
|
||||
public void deleteElement(@NotNull DataContext dataContext) {
|
||||
final Module[] modules = LangDataKeys.MODULE_CONTEXT_ARRAY.getData(dataContext);
|
||||
assert modules != null;
|
||||
final Project project = CommonDataKeys.PROJECT.getData(dataContext);
|
||||
assert project != null;
|
||||
String names = StringUtil.join(Arrays.asList(modules), module -> "\'" + module.getName() + "\'", ", ");
|
||||
int ret = Messages.showOkCancelDialog(getConfirmationText(modules, names), getActionTitle(), Messages.getQuestionIcon());
|
||||
|
||||
List<ModuleDescription> moduleDescriptions = new ArrayList<>();
|
||||
final Module[] modules = LangDataKeys.MODULE_CONTEXT_ARRAY.getData(dataContext);
|
||||
if (modules != null) {
|
||||
moduleDescriptions.addAll(ContainerUtil.map(modules, LoadedModuleDescriptionImpl::new));
|
||||
}
|
||||
List<UnloadedModuleDescription> unloadedModules = ProjectView.UNLOADED_MODULES_CONTEXT_KEY.getData(dataContext);
|
||||
if (unloadedModules != null) {
|
||||
moduleDescriptions.addAll(unloadedModules);
|
||||
}
|
||||
|
||||
String names = StringUtil.join(moduleDescriptions, description -> "\'" + description.getName() + "\'", ", ");
|
||||
int ret = Messages.showOkCancelDialog(getConfirmationText(names, moduleDescriptions.size()), getActionTitle(), Messages.getQuestionIcon());
|
||||
if (ret != Messages.OK) return;
|
||||
CommandProcessor.getInstance().executeCommand(project, () -> {
|
||||
final Runnable action = () -> {
|
||||
@@ -86,28 +97,33 @@ public class ModuleDeleteProvider implements DeleteProvider, TitledHandler {
|
||||
final Module[] currentModules = moduleManager.getModules();
|
||||
final ModifiableModuleModel modifiableModuleModel = moduleManager.getModifiableModel();
|
||||
final Map<Module, ModifiableRootModel> otherModuleRootModels = new HashMap<>();
|
||||
Set<String> moduleNamesToDelete = Arrays.stream(modules).map(Module::getName).collect(Collectors.toSet());
|
||||
Set<String> moduleNamesToDelete = moduleDescriptions.stream().map(ModuleDescription::getName).collect(Collectors.toSet());
|
||||
for (final Module otherModule : currentModules) {
|
||||
if (!moduleNamesToDelete.contains(otherModule.getName())) {
|
||||
otherModuleRootModels.put(otherModule, ModuleRootManager.getInstance(otherModule).getModifiableModel());
|
||||
}
|
||||
}
|
||||
removeDependenciesOnModules(moduleNamesToDelete, otherModuleRootModels.values());
|
||||
for (final Module module : modules) {
|
||||
modifiableModuleModel.disposeModule(module);
|
||||
if (modules != null) {
|
||||
for (final Module module : modules) {
|
||||
modifiableModuleModel.disposeModule(module);
|
||||
}
|
||||
}
|
||||
final ModifiableRootModel[] modifiableRootModels = otherModuleRootModels.values().toArray(new ModifiableRootModel[otherModuleRootModels.size()]);
|
||||
ModifiableModelCommitter.multiCommit(modifiableRootModels, modifiableModuleModel);
|
||||
if (unloadedModules != null) {
|
||||
moduleManager.removeUnloadedModules(unloadedModules);
|
||||
}
|
||||
};
|
||||
ApplicationManager.getApplication().runWriteAction(action);
|
||||
}, ProjectBundle.message("module.remove.command"), null);
|
||||
}
|
||||
|
||||
private static String getConfirmationText(Module[] modules, String names) {
|
||||
private static String getConfirmationText(String names, int numberOfModules) {
|
||||
if (ProjectAttachProcessor.canAttachToProject()) {
|
||||
return ProjectBundle.message("project.remove.confirmation.prompt", names, modules.length);
|
||||
return ProjectBundle.message("project.remove.confirmation.prompt", names, numberOfModules);
|
||||
}
|
||||
return ProjectBundle.message("module.remove.confirmation.prompt", names, modules.length);
|
||||
return ProjectBundle.message("module.remove.confirmation.prompt", names, numberOfModules);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,8 +143,7 @@ public class ModuleDeleteProvider implements DeleteProvider, TitledHandler {
|
||||
for (final ModifiableRootModel modifiableRootModel : otherModuleRootModels) {
|
||||
final OrderEntry[] orderEntries = modifiableRootModel.getOrderEntries();
|
||||
for (final OrderEntry orderEntry : orderEntries) {
|
||||
if (orderEntry instanceof ModuleOrderEntry && orderEntry.isValid() &&
|
||||
moduleNamesToRemove.contains(((ModuleOrderEntry)orderEntry).getModuleName())) {
|
||||
if (orderEntry instanceof ModuleOrderEntry && moduleNamesToRemove.contains(((ModuleOrderEntry)orderEntry).getModuleName())) {
|
||||
modifiableRootModel.removeOrderEntry(orderEntry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,4 +194,8 @@ public abstract class ModuleManager extends SimpleModificationTracker {
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
public abstract void setUnloadedModules(@NotNull List<String> unloadedModuleNames);
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public void removeUnloadedModules(@NotNull Collection<UnloadedModuleDescription> unloadedModules) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +57,7 @@ class EmptyModuleManager(project: Project, messageBus: MessageBus) : ModuleManag
|
||||
override fun getUnloadedModuleDescriptions() = emptyList<UnloadedModuleDescription>()
|
||||
|
||||
override fun getUnloadedModuleDescription(moduleName: String) = null
|
||||
|
||||
override fun removeUnloadedModules(unloadedModules: MutableCollection<UnloadedModuleDescription>) {
|
||||
}
|
||||
}
|
||||
@@ -1068,6 +1068,15 @@ public abstract class ModuleManagerImpl extends ModuleManager implements Disposa
|
||||
myModulePathsToLoad.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUnloadedModules(@NotNull Collection<UnloadedModuleDescription> unloadedModules) {
|
||||
ApplicationManager.getApplication().assertWriteAccessAllowed();
|
||||
for (UnloadedModuleDescription module : unloadedModules) {
|
||||
myUnloadedModules.remove(module.getName());
|
||||
}
|
||||
setUnloadedModuleNames(new ArrayList<>(myUnloadedModules.keySet()));
|
||||
}
|
||||
|
||||
protected void setUnloadedModuleNames(@NotNull List<String> unloadedModuleNames) {
|
||||
UnloadedModulesListStorage.getInstance(myProject).setUnloadedModuleNames(unloadedModuleNames);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user