From 2a680d25b66ce16aa422ffd73d906e11594634a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20C=C3=A9bron?= Date: Mon, 12 May 2014 21:21:47 +0200 Subject: [PATCH] DevKit: extract PluginDescriptorChooser, improve plugin.xml candidates list presentation, fix dumb mode error --- .../quickfix/PluginDescriptorChooser.java | 237 ++++++++++++++++++ .../quickfix/RegisterExtensionFix.java | 13 +- .../quickfix/RegisterInspectionFix.java | 129 +--------- 3 files changed, 249 insertions(+), 130 deletions(-) create mode 100644 plugins/devkit/src/inspections/quickfix/PluginDescriptorChooser.java diff --git a/plugins/devkit/src/inspections/quickfix/PluginDescriptorChooser.java b/plugins/devkit/src/inspections/quickfix/PluginDescriptorChooser.java new file mode 100644 index 000000000000..f46429fcf44f --- /dev/null +++ b/plugins/devkit/src/inspections/quickfix/PluginDescriptorChooser.java @@ -0,0 +1,237 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.inspections.quickfix; + +import com.google.common.collect.ImmutableMap; +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.roots.ui.configuration.ModulesAlphaComparator; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.ListSeparator; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.util.Consumer; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.xml.DomFileElement; +import com.intellij.util.xml.DomService; +import com.intellij.xml.util.IncludedXmlTag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.dom.Extensions; +import org.jetbrains.idea.devkit.dom.IdeaPlugin; + +import javax.swing.*; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class PluginDescriptorChooser { + + private static final ImmutableMap INTELLIJ_MODULES = ImmutableMap.builder() + .put("platform-api", "PlatformExtensions.xml") + .put("platform-impl", "PlatformExtensions.xml") + .put("lang-api", "LangExtensions.xml") + .put("lang-impl", "LangExtensions.xml") + .put("vcs-api", "VcsExtensions.xml") + .put("vcs-impl", "VcsExtensions.xml") + .put("openapi", "IdeaPlugin.xml") + .put("java-impl", "IdeaPlugin.xml") + .build(); + + public static void show(final Project project, + final Editor editor, + final PsiFile file, + final Consumer> consumer) { + final Module module = ModuleUtilCore.findModuleForPsiElement(file); + assert module != null; + List> elements = + DomService.getInstance().getFileElements(IdeaPlugin.class, + project, + module.getModuleContentWithDependenciesScope()); + + elements = ContainerUtil.filter(elements, new Condition>() { + @Override + public boolean value(DomFileElement element) { + VirtualFile virtualFile = element.getFile().getVirtualFile(); + return virtualFile != null && ProjectRootManager.getInstance(project).getFileIndex().isInContent(virtualFile); + } + }); + + elements = findAppropriateIntelliJModule(module.getName(), elements); + + if (elements.isEmpty()) { + HintManager.getInstance().showErrorHint(editor, "Cannot find plugin descriptor"); + return; + } + + if (elements.size() == 1) { + consumer.consume(elements.get(0)); + return; + } + + final BaseListPopupStep popupStep = + new BaseListPopupStep("Choose Plugin Descriptor", + createCandidates(module, elements)) { + + @Override + public boolean isSpeedSearchEnabled() { + return true; + } + + @Override + public Icon getIconFor(PluginDescriptorCandidate candidate) { + return candidate.getIcon(); + } + + @NotNull + @Override + public String getTextFor(PluginDescriptorCandidate candidate) { + return candidate.getText(); + } + + @Nullable + @Override + public ListSeparator getSeparatorAbove(PluginDescriptorCandidate candidate) { + final String separatorText = candidate.getSeparatorText(); + if (separatorText != null) { + return new ListSeparator(separatorText); + } + return null; + } + + @Override + public PopupStep onChosen(PluginDescriptorCandidate selectedValue, boolean finalChoice) { + consumer.consume(selectedValue.myDomFileElement); + return FINAL_CHOICE; + } + }; + JBPopupFactory.getInstance().createListPopup(popupStep).showInBestPositionFor(editor); + } + + @NotNull + public static Extensions findOrCreateExtensionsForEP(DomFileElement domFileElement, String epName) { + final IdeaPlugin ideaPlugin = domFileElement.getRootElement(); + final List extensionsList = ideaPlugin.getExtensions(); + for (Extensions extensions : extensionsList) { + if (extensions.getXmlTag() instanceof IncludedXmlTag) { + continue; + } + String s = extensions.getDefaultExtensionNs().getStringValue(); + if (s != null && epName.startsWith(s)) { + return extensions; + } + } + + Extensions extensions = ideaPlugin.addExtensions(); + final String epPrefix = StringUtil.getPackageName(epName); + extensions.getDefaultExtensionNs().setStringValue(epPrefix); + return extensions; + } + + private static List createCandidates(final Module currentModule, + List> elements) { + Collections.sort(elements, new Comparator>() { + @Override + public int compare(DomFileElement o1, DomFileElement o2) { + // current module = first group + final Module module1 = o1.getModule(); + if (currentModule.equals(module1)) return -1; + final Module module2 = o2.getModule(); + if (currentModule.equals(module2)) return 1; + + return ModulesAlphaComparator.INSTANCE.compare(module1, module2); + } + }); + Collections.sort(elements, new Comparator>() { + @Override + public int compare(DomFileElement o1, DomFileElement o2) { + if (!Comparing.equal(o1.getModule(), o2.getModule())) return 0; + return o1.getFile().getName().compareTo(o2.getFile().getName()); + } + }); + + return ContainerUtil.map(elements, new Function, PluginDescriptorCandidate>() { + + private Module myLastModule = currentModule; + + @Override + public PluginDescriptorCandidate fun(DomFileElement element) { + final Module module = element.getModule(); + boolean startsNewGroup = !myLastModule.equals(module); + myLastModule = module; + return new PluginDescriptorCandidate(element, startsNewGroup); + } + }); + } + + private static List> findAppropriateIntelliJModule(String moduleName, + List> elements) { + String extensionsFile = INTELLIJ_MODULES.get(moduleName); + if (extensionsFile != null) { + for (DomFileElement element : elements) { + if (element.getFile().getName().equals(extensionsFile)) { + return Collections.singletonList(element); + } + } + } + return elements; + } + + + private static class PluginDescriptorCandidate { + private final DomFileElement myDomFileElement; + private final boolean myStartsNewGroup; + + private PluginDescriptorCandidate(DomFileElement domFileElement, + boolean startsNewGroup) { + myDomFileElement = domFileElement; + myStartsNewGroup = startsNewGroup; + } + + public String getText() { + final String name = myDomFileElement.getFile().getName(); + final String pluginId = getPluginId(); + return pluginId != null ? name + " [" + pluginId + "]" : name; + } + + public Icon getIcon() { + return getPluginId() != null ? AllIcons.Nodes.Plugin : EmptyIcon.create(AllIcons.Nodes.Plugin); + } + + public String getSeparatorText() { + if (!myStartsNewGroup) return null; + + final Module module = myDomFileElement.getModule(); + return module == null ? null : module.getName(); + } + + private String getPluginId() { + return myDomFileElement.getRootElement().getPluginId(); + } + } +} diff --git a/plugins/devkit/src/inspections/quickfix/RegisterExtensionFix.java b/plugins/devkit/src/inspections/quickfix/RegisterExtensionFix.java index 65392a815e93..96fceb373130 100644 --- a/plugins/devkit/src/inspections/quickfix/RegisterExtensionFix.java +++ b/plugins/devkit/src/inspections/quickfix/RegisterExtensionFix.java @@ -23,6 +23,7 @@ import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.extensions.KeyedFactoryEPBean; import com.intellij.openapi.fileTypes.FileTypeExtensionPoint; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.PopupStep; @@ -71,12 +72,12 @@ public class RegisterExtensionFix implements IntentionAction { @Override public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - return true; + return !DumbService.isDumb(project); } @Override public void invoke(@NotNull Project project, final Editor editor, PsiFile file) throws IncorrectOperationException { - RegisterInspectionFix.choosePluginDescriptor(project, editor, file, new Consumer>() { + PluginDescriptorChooser.show(project, editor, file, new Consumer>() { @Override public void consume(DomFileElement element) { doFix(editor, element); @@ -101,11 +102,11 @@ public class RegisterExtensionFix implements IntentionAction { } } - private void registerExtension(final DomFileElement element, final ExtensionPointCandidate candidate) { - PsiElement navTarget = new WriteCommandAction(element.getFile().getProject(), element.getFile()) { + private void registerExtension(final DomFileElement selectedValue, final ExtensionPointCandidate candidate) { + PsiElement navTarget = new WriteCommandAction(selectedValue.getFile().getProject(), selectedValue.getFile()) { @Override - protected void run(Result result) throws Throwable { - Extensions extensions = RegisterInspectionFix.getExtension(element.getRootElement(), candidate.epName); + protected void run(@NotNull Result result) throws Throwable { + Extensions extensions = PluginDescriptorChooser.findOrCreateExtensionsForEP(selectedValue, candidate.epName); Extension extension = extensions.addExtension(candidate.epName); XmlTag tag = extension.getXmlTag(); PsiElement navTarget = null; diff --git a/plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java b/plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java index eb5cb74c401c..fb4fa6893f8d 100644 --- a/plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java +++ b/plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java @@ -15,45 +15,27 @@ */ package org.jetbrains.idea.devkit.inspections.quickfix; -import com.google.common.collect.ImmutableMap; -import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.InspectionEP; -import com.intellij.ide.TypePresentationService; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.ui.popup.PopupStep; -import com.intellij.openapi.ui.popup.util.BaseListPopupStep; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.Consumer; import com.intellij.util.IncorrectOperationException; import com.intellij.util.PsiNavigateUtil; -import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xml.DomFileElement; -import com.intellij.util.xml.DomService; -import com.intellij.xml.util.IncludedXmlTag; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.devkit.DevKitBundle; import org.jetbrains.idea.devkit.dom.Extension; import org.jetbrains.idea.devkit.dom.Extensions; import org.jetbrains.idea.devkit.dom.IdeaPlugin; -import javax.swing.*; -import java.util.Collections; -import java.util.List; - /** * @author Dmitry Avdeev */ @@ -81,12 +63,12 @@ class RegisterInspectionFix implements IntentionAction { @Override public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - return true; + return !DumbService.isDumb(project); } @Override public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { - choosePluginDescriptor(project, editor, file, new Consumer>() { + PluginDescriptorChooser.show(project, editor, file, new Consumer>() { @Override public void consume(DomFileElement element) { doFix(element, project, file); @@ -94,95 +76,12 @@ class RegisterInspectionFix implements IntentionAction { }); } - public static void choosePluginDescriptor(final Project project, Editor editor, final PsiFile file, - final Consumer> consumer) { - Module module = ModuleUtilCore.findModuleForPsiElement(file); - assert module != null; - List> elements = - DomService.getInstance().getFileElements(IdeaPlugin.class, project, module.getModuleContentWithDependenciesScope()); - - elements = ContainerUtil.filter(elements, new Condition>() { - @Override - public boolean value(DomFileElement element) { - VirtualFile virtualFile = element.getFile().getVirtualFile(); - return virtualFile != null && ProjectRootManager.getInstance(project).getFileIndex().isInContent(virtualFile); - } - }); - - elements = findAppropriateIntelliJModule(module.getName(), elements); - - if (elements.isEmpty()) { - HintManager.getInstance().showErrorHint(editor, "Cannot find plugin descriptor"); - return; - } - - if (elements.size() == 1) { - consumer.consume(elements.get(0)); - return; - } - - final BaseListPopupStep> popupStep = - new BaseListPopupStep>("Choose Plugin Descriptor", elements) { - - @Override - public boolean isSpeedSearchEnabled() { - return true; - } - - @Override - public Icon getIconFor(DomFileElement aValue) { - return TypePresentationService.getService().getIcon(aValue); - } - - @NotNull - @Override - public String getTextFor(DomFileElement value) { - final String name = value.getFile().getName(); - final Module module = value.getModule(); - return module != null ? name + " [" + module.getName() + "]" : name; - } - - @Override - public PopupStep onChosen(DomFileElement selectedValue, boolean finalChoice) { - consumer.consume(selectedValue); - return FINAL_CHOICE; - } - }; - JBPopupFactory.getInstance().createListPopup(popupStep) - .showInBestPositionFor(editor); - } - - private static final ImmutableMap INTELLIJ_MODULES = ImmutableMap.builder() - .put("platform-api", "PlatformExtensions.xml") - .put("platform-impl", "PlatformExtensions.xml") - .put("lang-api", "LangExtensions.xml") - .put("lang-impl", "LangExtensions.xml") - .put("vcs-api", "VcsExtensions.xml") - .put("vcs-impl", "VcsExtensions.xml") - .put("openapi", "IdeaPlugin.xml") - .put("java-impl", "IdeaPlugin.xml") - .build(); - - private static List> findAppropriateIntelliJModule(String moduleName, - List> elements) { - String extensionsFile = INTELLIJ_MODULES.get(moduleName); - if (extensionsFile != null) { - for (DomFileElement element : elements) { - if (element.getFile().getName().equals(extensionsFile)) { - return Collections.singletonList(element); - } - } - } - return elements; - } - - private void doFix(DomFileElement selectedValue, final Project project, final PsiFile file) { - final IdeaPlugin plugin = selectedValue.getRootElement(); + private void doFix(final DomFileElement selectedValue, final Project project, final PsiFile file) { Extension extension = new WriteCommandAction(project, file) { @Override protected void run(@NotNull Result result) throws Throwable { - final Extensions extensions = getExtension(plugin, myEp.getName()); + final Extensions extensions = PluginDescriptorChooser.findOrCreateExtensionsForEP(selectedValue, myEp.getName()); Extension extension = extensions.addExtension(myEp.getName()); XmlTag tag = extension.getXmlTag(); tag.setAttribute("implementationClass", myPsiClass.getQualifiedName()); @@ -192,24 +91,6 @@ class RegisterInspectionFix implements IntentionAction { PsiNavigateUtil.navigate(extension.getXmlTag()); } - public static Extensions getExtension(IdeaPlugin plugin, String epName) { - final List extensionsList = plugin.getExtensions(); - for (Extensions extensions : extensionsList) { - if (extensions.getXmlTag() instanceof IncludedXmlTag) { - continue; - } - String s = extensions.getDefaultExtensionNs().getStringValue(); - if (s != null && epName.startsWith(s)) { - return extensions; - } - } - - Extensions extensions = plugin.addExtensions(); - final String epPrefix = StringUtil.getPackageName(epName); - extensions.getDefaultExtensionNs().setStringValue(epPrefix); - return extensions; - } - @Override public boolean startInWriteAction() { return false;