resource bundle editor: highlight unused properties in editor (as was previously...
authorDmitry Batkovich <dmitry.batkovich@jetbrains.com>
Wed, 10 Aug 2016 10:13:14 +0000 (13:13 +0300)
committerDmitry Batkovich <dmitry.batkovich@jetbrains.com>
Wed, 10 Aug 2016 10:13:14 +0000 (13:13 +0300)
plugins/properties/properties-psi-impl/src/com/intellij/codeInspection/unused/UnusedPropertyInspection.java
plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/ResourceBundlePropertyStructureViewElement.java
plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/inspections/InspectedPropertyNodeInfo.java [new file with mode: 0644]
plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/inspections/ResourceBundleEditorInspection.java
plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/inspections/ResourceBundleEditorInspectionPass.java [deleted file]
plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditor.java
plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditorHighlighter.java [new file with mode: 0644]
plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditorShowQuickFixesAction.java
plugins/properties/src/com/intellij/lang/properties/editor/inspections/incomplete/IncompletePropertyInspection.java

index af9763ad5d659dbea6d6351030edf1073fd1c564..eb0a4b34ded5b0c12b9ca24ee310f79c5c658fea 100644 (file)
  */
 package com.intellij.codeInspection.unused;
 
-import com.intellij.codeInspection.LocalInspectionToolSession;
-import com.intellij.codeInspection.LocalQuickFix;
-import com.intellij.codeInspection.ProblemHighlightType;
-import com.intellij.codeInspection.ProblemsHolder;
+import com.intellij.codeInsight.FileModificationService;
+import com.intellij.codeInspection.*;
 import com.intellij.lang.ASTNode;
 import com.intellij.lang.properties.*;
+import com.intellij.lang.properties.editor.inspections.ResourceBundleEditorInspection;
+import com.intellij.lang.properties.editor.inspections.ResourceBundleEditorProblemDescriptor;
 import com.intellij.lang.properties.findUsages.PropertySearcher;
+import com.intellij.lang.properties.psi.PropertiesFile;
 import com.intellij.lang.properties.psi.Property;
+import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.extensions.Extensions;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.module.ModuleUtilCore;
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.project.Project;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiElementVisitor;
-import com.intellij.psi.PsiFile;
+import com.intellij.psi.*;
 import com.intellij.psi.search.GlobalSearchScope;
 import com.intellij.psi.search.PsiSearchHelper;
 import com.intellij.psi.search.searches.ReferencesSearch;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.containers.FilteringIterator;
+import org.jetbrains.annotations.Nls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.function.Function;
 
 /**
  * @author cdr
  */
-public class UnusedPropertyInspection extends PropertySuppressableInspectionBase {
+public class UnusedPropertyInspection extends PropertySuppressableInspectionBase implements ResourceBundleEditorInspection {
+  private final static Logger LOG = Logger.getInstance(UnusedPropertyInspection.class);
+
   @Override
   @NotNull
   public String getDisplayName() {
@@ -92,56 +97,145 @@ public class UnusedPropertyInspection extends PropertySuppressableInspectionBase
     final Module module = ModuleUtilCore.findModuleForPsiElement(file);
     if (module == null) return super.buildVisitor(holder, isOnTheFly, session);
 
-    final GlobalSearchScope ownUseScope = GlobalSearchScope.moduleWithDependentsScope(module);
-
-    Object[] extensions = Extensions.getExtensions("com.intellij.referencesSearch");
-    final PropertySearcher searcher =
-      (PropertySearcher)ContainerUtil.find(extensions, new FilteringIterator.InstanceOf<>(PropertySearcher.class));
-    final PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(file.getProject());
+    final UnusedPropertiesSearchHelper helper = new UnusedPropertiesSearchHelper(module);
     return new PsiElementVisitor() {
       @Override
       public void visitElement(PsiElement element) {
         if (!(element instanceof Property)) return;
         Property property = (Property)element;
 
-        final ProgressIndicator original = ProgressManager.getInstance().getProgressIndicator();
-        if (original != null) {
-          if (original.isCanceled()) return;
-          original.setText(PropertiesBundle.message("searching.for.property.key.progress.text", property.getUnescapedKey()));
-        }
-
-        if (ImplicitPropertyUsageProvider.isImplicitlyUsed(property)) return;
-
-        String name = property.getName();
-        if (name == null) return;
-        if (searcher != null) {
-          name = searcher.getKeyToSearch(name, element.getProject());
-          if (name == null) return;
-        }
-
-        if (mayHaveUsages(property, original, name, ownUseScope, isOnTheFly)) return;
-
-        final GlobalSearchScope widerScope = getWidestUseScope(property.getKey(), element.getProject(), module);
-        if (widerScope != null && mayHaveUsages(property, original, name, widerScope, isOnTheFly)) return;
+        if (isPropertyUsed(property, helper, isOnTheFly)) return;
 
         final ASTNode propertyNode = property.getNode();
         assert propertyNode != null;
 
         ASTNode[] nodes = propertyNode.getChildren(null);
         PsiElement key = nodes.length == 0 ? property : nodes[0].getPsi();
-        String description = PropertiesBundle.message("unused.property.problem.descriptor.name");
-
         LocalQuickFix fix = PropertiesQuickFixFactory.getInstance().createRemovePropertyLocalFix();
-        holder.registerProblem(key, description, ProblemHighlightType.LIKE_UNUSED_SYMBOL, fix);
+        holder.registerProblem(key, PropertiesBundle.message("unused.property.problem.descriptor.name"),
+                               ProblemHighlightType.LIKE_UNUSED_SYMBOL, fix);
       }
+    };
+  }
 
-      private boolean mayHaveUsages(Property property, ProgressIndicator original, String name, GlobalSearchScope searchScope, boolean onTheFly) {
-        PsiSearchHelper.SearchCostResult cheapEnough = searchHelper.isCheapEnoughToSearch(name, searchScope, file, original);
-        if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES) return false;
-        if (onTheFly && cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return true;
+  @NotNull
+  @Override
+  public Function<IProperty[], ResourceBundleEditorProblemDescriptor[]> buildPropertyGroupVisitor(@NotNull ResourceBundle resourceBundle) {
+    final Module module = ModuleUtilCore.findModuleForPsiElement(resourceBundle.getDefaultPropertiesFile().getContainingFile());
+    if (module == null) return x -> null;
+    final UnusedPropertiesSearchHelper helper = new UnusedPropertiesSearchHelper(module);
+
+    return properties -> !isPropertyUsed((Property)properties[0], helper, true) ? new ResourceBundleEditorProblemDescriptor[]{
+      new ResourceBundleEditorProblemDescriptor(ProblemHighlightType.LIKE_UNUSED_SYMBOL,
+                                                PropertiesBundle.message("unused.property.problem.descriptor.name"),
+                                                new RemovePropertiesFromAllLocalesFix((Property)properties[0]))} : null;
+  }
 
-        return ReferencesSearch.search(property, searchScope, false).findFirst() != null;
-      }
-    };
+  private static boolean isPropertyUsed(@NotNull Property property, @NotNull UnusedPropertiesSearchHelper helper, boolean isOnTheFly) {
+    final ProgressIndicator original = ProgressManager.getInstance().getProgressIndicator();
+    if (original != null) {
+      if (original.isCanceled()) return true;
+      original.setText(PropertiesBundle.message("searching.for.property.key.progress.text", property.getUnescapedKey()));
+    }
+
+    if (ImplicitPropertyUsageProvider.isImplicitlyUsed(property)) return true;
+
+    String name = property.getName();
+    if (name == null) return true;
+    if (helper.getSearcher() != null) {
+      name = helper.getSearcher().getKeyToSearch(name, property.getProject());
+      if (name == null) return true;
+    }
+
+    if (mayHaveUsages(property, original, name, helper.getOwnUseScope(), helper, isOnTheFly)) return true;
+
+    final GlobalSearchScope widerScope = getWidestUseScope(property.getKey(), property.getProject(), helper.getModule());
+    if (widerScope != null && mayHaveUsages(property, original, name, widerScope, helper, isOnTheFly)) return true;
+    return false;
+  }
+
+  private static boolean mayHaveUsages(Property property,
+                                       ProgressIndicator original,
+                                       String name,
+                                       GlobalSearchScope searchScope,
+                                       @NotNull UnusedPropertiesSearchHelper helper,
+                                       boolean onTheFly) {
+    PsiSearchHelper.SearchCostResult cheapEnough = helper.getSearchHelper().isCheapEnoughToSearch(name, searchScope, null, original);
+    if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES) return false;
+    if (onTheFly && cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return true;
+
+    return ReferencesSearch.search(property, searchScope, false).findFirst() != null;
+  }
+
+  private static class UnusedPropertiesSearchHelper {
+
+    private final GlobalSearchScope myOwnUseScope;
+    private final Module myModule;
+    private final PropertySearcher mySearcher;
+    private final PsiSearchHelper mySearchHelper;
+
+    public UnusedPropertiesSearchHelper(Module module) {
+      myOwnUseScope = GlobalSearchScope.moduleWithDependentsScope(module);
+      myModule = module;
+      mySearcher = (PropertySearcher)ContainerUtil.find(Extensions.getExtensions("com.intellij.referencesSearch"),
+                                                        new FilteringIterator.InstanceOf<PropertySearcher>(PropertySearcher.class));
+      mySearchHelper = PsiSearchHelper.SERVICE.getInstance(module.getProject());
+    }
+
+    public Module getModule() {
+      return myModule;
+    }
+
+    public GlobalSearchScope getOwnUseScope() {
+      return myOwnUseScope;
+    }
+
+    public PropertySearcher getSearcher() {
+      return mySearcher;
+    }
+
+    public PsiSearchHelper getSearchHelper() {
+      return mySearchHelper;
+    }
+  }
+
+  private static class RemovePropertiesFromAllLocalesFix implements QuickFix<ResourceBundleEditorProblemDescriptor> {
+    private final SmartPsiElementPointer<Property> myRepresentativePointer;
+
+    private RemovePropertiesFromAllLocalesFix(Property property) {
+      myRepresentativePointer = SmartPointerManager.getInstance(property.getProject()).createSmartPsiElementPointer(property);
+    }
+
+    @Nls
+    @NotNull
+    @Override
+    public String getName() {
+      return getFamilyName();
+    }
+
+    @Nls
+    @NotNull
+    @Override
+    public String getFamilyName() {
+      return PropertiesBundle.message("remove.property.intention.text");
+    }
+
+    @Override
+    public void applyFix(@NotNull Project project, @NotNull ResourceBundleEditorProblemDescriptor descriptor) {
+      final Property element = myRepresentativePointer.getElement();
+      if (element == null) return;
+      final String key = element.getKey();
+      if (key == null) return;
+      final PropertiesFile file = PropertiesImplUtil.getPropertiesFile(myRepresentativePointer.getContainingFile());
+      LOG.assertTrue(file != null);
+      file.getResourceBundle()
+        .getPropertiesFiles()
+        .stream()
+        .flatMap(f -> f.findPropertiesByKey(key).stream())
+        .filter(Objects::nonNull)
+        .map(IProperty::getPsiElement)
+        .filter(FileModificationService.getInstance()::preparePsiElementForWrite)
+        .forEach(PsiElement::delete);
+    }
   }
 }
index 54e25a0ea21d6d610625183b6fea588520379d60..81236a6d15a6a76490769fd3768601d2cfb819a9 100644 (file)
@@ -21,13 +21,13 @@ package com.intellij.lang.properties.editor;
 
 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
 import com.intellij.ide.structureView.StructureViewTreeElement;
-import com.intellij.lang.properties.*;
+import com.intellij.lang.properties.IProperty;
+import com.intellij.lang.properties.PropertiesHighlighter;
 import com.intellij.lang.properties.ResourceBundle;
-import com.intellij.lang.properties.editor.inspections.ResourceBundleEditorInspectionPass;
+import com.intellij.lang.properties.editor.inspections.InspectedPropertyNodeInfo;
 import com.intellij.lang.properties.editor.inspections.ResourceBundleEditorProblemDescriptor;
 import com.intellij.lang.properties.editor.inspections.ResourceBundleEditorRenderer;
 import com.intellij.navigation.ItemPresentation;
-import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.colors.EditorColorsManager;
 import com.intellij.openapi.editor.colors.EditorColorsScheme;
 import com.intellij.openapi.editor.colors.TextAttributesKey;
@@ -42,7 +42,6 @@ import javax.swing.*;
 import java.awt.*;
 
 public class ResourceBundlePropertyStructureViewElement implements StructureViewTreeElement, ResourceBundleEditorViewElement {
-  private static final Logger LOG = Logger.getInstance(ResourceBundlePropertyStructureViewElement.class);
   private static final TextAttributesKey GROUP_KEY;
 
   public static final String PROPERTY_GROUP_KEY_TEXT = "<property>";
@@ -57,7 +56,7 @@ public class ResourceBundlePropertyStructureViewElement implements StructureView
     GROUP_KEY = TextAttributesKey.createTextAttributesKey("GROUP_KEY", groupKeyTextAttributes);
   }
 
-  private ResourceBundleEditorInspectionPass.InspectionPassInfo myInspectionPassInfo;
+  private volatile InspectedPropertyNodeInfo myInspectedPropertyNodeInfo;
 
   public ResourceBundlePropertyStructureViewElement(final ResourceBundle resourceBundle, final @NotNull PropertiesAnchorizer.PropertyAnchor anchor) {
     myAnchor = anchor;
@@ -96,7 +95,11 @@ public class ResourceBundlePropertyStructureViewElement implements StructureView
 
   @NotNull
   public Pair<ResourceBundleEditorProblemDescriptor, HighlightDisplayKey>[] getProblemDescriptors() {
-    return myInspectionPassInfo == null ? new Pair[0] : myInspectionPassInfo.getDescriptors();
+    return myInspectedPropertyNodeInfo == null ? new Pair[0] : myInspectedPropertyNodeInfo.getDescriptors();
+  }
+
+  public void setInspectedPropertyNodeInfo(InspectedPropertyNodeInfo inspectedPropertyNodeInfo) {
+    myInspectedPropertyNodeInfo = inspectedPropertyNodeInfo;
   }
 
   @Override
@@ -125,10 +128,8 @@ public class ResourceBundlePropertyStructureViewElement implements StructureView
           (myPresentableName != null && myPresentableName.isEmpty()) ? GROUP_KEY : PropertiesHighlighter.PROPERTY_KEY;
         final TextAttributes baseAttrs = colorsScheme.getAttributes(baseAttrKey);
         if (getProperty().getPsiElement().isValid()) {
-          myInspectionPassInfo =
-            ResourceBundleEditorInspectionPass.inspect(getProperty().getKey(), getProperty().getPropertiesFile().getResourceBundle());
-          if (myInspectionPassInfo != null) {
-            TextAttributes highlightingAttributes = myInspectionPassInfo.getTextAttributes(colorsScheme);
+          if (myInspectedPropertyNodeInfo != null) {
+            TextAttributes highlightingAttributes = myInspectedPropertyNodeInfo.getTextAttributes(colorsScheme);
             if (highlightingAttributes != null) {
               return TextAttributes.merge(baseAttrs, highlightingAttributes);
             }
diff --git a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/inspections/InspectedPropertyNodeInfo.java b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/inspections/InspectedPropertyNodeInfo.java
new file mode 100644 (file)
index 0000000..29a33a9
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2016 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 com.intellij.lang.properties.editor.inspections;
+
+import com.intellij.codeInsight.daemon.HighlightDisplayKey;
+import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.markup.TextAttributes;
+import com.intellij.openapi.util.Pair;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.SortedSet;
+
+public class InspectedPropertyNodeInfo {
+  private final Pair<ResourceBundleEditorProblemDescriptor, HighlightDisplayKey>[] myDescriptors;
+  private final SortedSet<HighlightInfoType> myHighlightTypes;
+
+  public InspectedPropertyNodeInfo(Pair<ResourceBundleEditorProblemDescriptor, HighlightDisplayKey>[] descriptors, SortedSet<HighlightInfoType> types) {
+    myDescriptors = descriptors;
+    myHighlightTypes = types;
+  }
+
+  public Pair<ResourceBundleEditorProblemDescriptor, HighlightDisplayKey>[] getDescriptors() {
+    return myDescriptors;
+  }
+
+  @Nullable
+  public TextAttributes getTextAttributes(EditorColorsScheme scheme) {
+    TextAttributes mixedAttributes = null;
+    for (HighlightInfoType type : myHighlightTypes) {
+      final TextAttributes current = scheme.getAttributes(type.getAttributesKey());
+      if (mixedAttributes == null) {
+        mixedAttributes = current;
+      } else {
+        mixedAttributes = TextAttributes.merge(mixedAttributes, current);
+      }
+    }
+    return mixedAttributes;
+  }
+}
index b4f36412fa06a35c104ebc6a2f15a989d3a7b4d0..27ca5a93358fd10326a27ff99e31cdc43341da9f 100644 (file)
@@ -18,14 +18,13 @@ package com.intellij.lang.properties.editor.inspections;
 import com.intellij.lang.properties.IProperty;
 import com.intellij.lang.properties.ResourceBundle;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
-import java.util.List;
+import java.util.function.Function;
 
 /**
  * @author Dmitry Batkovich
  */
 public interface ResourceBundleEditorInspection {
-  @Nullable
-  ResourceBundleEditorProblemDescriptor[] checkPropertyGroup(@NotNull List<IProperty> properties, @NotNull ResourceBundle resourceBundle);
+  @NotNull
+  Function<IProperty[], ResourceBundleEditorProblemDescriptor[]> buildPropertyGroupVisitor(@NotNull ResourceBundle resourceBundle);
 }
diff --git a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/inspections/ResourceBundleEditorInspectionPass.java b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/inspections/ResourceBundleEditorInspectionPass.java
deleted file mode 100644 (file)
index 82a2ed0..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2000-2016 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 com.intellij.lang.properties.editor.inspections;
-
-import com.intellij.codeInsight.daemon.HighlightDisplayKey;
-import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
-import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
-import com.intellij.codeInspection.InspectionProfile;
-import com.intellij.codeInspection.ProblemDescriptorUtil;
-import com.intellij.codeInspection.QuickFix;
-import com.intellij.codeInspection.ex.InspectionToolWrapper;
-import com.intellij.lang.annotation.HighlightSeverity;
-import com.intellij.lang.properties.IProperty;
-import com.intellij.lang.properties.ResourceBundle;
-import com.intellij.openapi.editor.colors.EditorColorsScheme;
-import com.intellij.openapi.editor.markup.TextAttributes;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Comparing;
-import com.intellij.openapi.util.Pair;
-import com.intellij.profile.codeInspection.InspectionProfileManager;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.util.SmartList;
-import com.intellij.util.containers.ContainerUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-/**
- * @author Dmitry Batkovich
- */
-public class ResourceBundleEditorInspectionPass {
-  @Nullable
-  public static InspectionPassInfo inspect(@NotNull final String key, ResourceBundle resourceBundle) {
-    final List<IProperty> properties =
-      ContainerUtil.mapNotNull(resourceBundle.getPropertiesFiles(), propertiesFile -> propertiesFile.findPropertyByKey(key));
-
-    if (properties.isEmpty()) {
-      return null;
-    }
-    final IProperty property = properties.get(0);
-    final PsiElement representativeElement = property.getPsiElement();
-    final PsiFile representativeFile = representativeElement.getContainingFile();
-
-    final Project project = representativeElement.getProject();
-
-    InspectionProfile profileToUse = InspectionProfileManager.getInstance().getCurrentProfile();
-    final PsiFile containingFile = representativeFile.getContainingFile();
-    final InspectionToolWrapper[] propertiesTools = profileToUse.getInspectionTools(containingFile);
-
-    List<Pair<ResourceBundleEditorProblemDescriptor, HighlightDisplayKey>> allDescriptors =
-      new SmartList<>();
-    SortedSet<HighlightInfoType> highlightTypes = new TreeSet<>((o1, o2) -> {
-      final HighlightSeverity s1 = o1.getSeverity(null);
-      final HighlightSeverity s2 = o2.getSeverity(null);
-      return Comparing.compare(s1, s2);
-    });
-
-    for (InspectionToolWrapper tool : propertiesTools) {
-      final HighlightDisplayKey toolKey;
-      if (tool.getTool() instanceof ResourceBundleEditorInspection &&
-          profileToUse.isToolEnabled(toolKey = HighlightDisplayKey.find(tool.getShortName()), containingFile)) {
-        final ResourceBundleEditorInspection inspection = (ResourceBundleEditorInspection)tool.getTool();
-        final ResourceBundleEditorProblemDescriptor[] descriptors = inspection.checkPropertyGroup(properties, resourceBundle);
-        if (descriptors != null) {
-          for (ResourceBundleEditorProblemDescriptor descriptor : descriptors) {
-            final QuickFix[] currentFixes = descriptor.getFixes();
-            if (currentFixes != null) {
-              allDescriptors.add(Pair.create(descriptor, toolKey));
-            }
-            HighlightSeverity severity = profileToUse.getErrorLevel(toolKey, containingFile).getSeverity();
-            final HighlightInfoType infoType =
-              ProblemDescriptorUtil.getHighlightInfoType(descriptor.getHighlightType(),
-                                                         severity,
-                                                         SeverityRegistrar.getSeverityRegistrar(project));
-            highlightTypes.add(infoType);
-          }
-        }
-      }
-    }
-    return new InspectionPassInfo(allDescriptors.toArray(new Pair[allDescriptors.size()]), highlightTypes);
-  }
-
-  public static class InspectionPassInfo {
-    private final Pair<ResourceBundleEditorProblemDescriptor, HighlightDisplayKey>[] myDescriptors;
-    private final SortedSet<HighlightInfoType> myHighlightTypes;
-
-    public InspectionPassInfo(Pair<ResourceBundleEditorProblemDescriptor, HighlightDisplayKey>[] descriptors, SortedSet<HighlightInfoType> types) {
-      myDescriptors = descriptors;
-      myHighlightTypes = types;
-    }
-
-    public Pair<ResourceBundleEditorProblemDescriptor, HighlightDisplayKey>[] getDescriptors() {
-      return myDescriptors;
-    }
-
-    @Nullable
-    public TextAttributes getTextAttributes(EditorColorsScheme scheme) {
-      TextAttributes mixedAttributes = null;
-      for (HighlightInfoType type : myHighlightTypes) {
-        final TextAttributes current = scheme.getAttributes(type.getAttributesKey());
-        if (mixedAttributes == null) {
-          mixedAttributes = current;
-        } else {
-          mixedAttributes = TextAttributes.merge(mixedAttributes, current);
-        }
-      }
-      return mixedAttributes;
-    }
-  }
-}
index c9fb90d555cf1b903b9e24a131d2f83181e0c693..dcaa154d87b0337e0fbded9bdd04df4ba009a715 100644 (file)
@@ -38,6 +38,7 @@ import com.intellij.lang.properties.ResourceBundle;
 import com.intellij.lang.properties.editor.inspections.incomplete.IncompletePropertyInspection;
 import com.intellij.lang.properties.psi.PropertiesFile;
 import com.intellij.lang.properties.psi.PropertiesResourceBundleUtil;
+import com.intellij.lang.properties.xml.XmlPropertiesFile;
 import com.intellij.openapi.actionSystem.*;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.command.CommandProcessor;
@@ -131,6 +132,7 @@ public class ResourceBundleEditor extends UserDataHolderBase implements Document
   private VirtualFileListener myVfsListener;
   private Editor              mySelectedEditor;
   private String              myPropertyToSelectWhenVisible;
+  private ResourceBundleEditorHighlighter myHighlighter;
 
   public ResourceBundleEditor(@NotNull ResourceBundle resourceBundle) {
     myProject = resourceBundle.getProject();
@@ -223,6 +225,7 @@ public class ResourceBundleEditor extends UserDataHolderBase implements Document
         onSelectionChanged(event);
       }
     });
+    myHighlighter = myResourceBundle.getDefaultPropertiesFile() instanceof XmlPropertiesFile ? null : new ResourceBundleEditorHighlighter(this);
   }
 
   public ResourceBundle getResourceBundle() {
@@ -534,7 +537,6 @@ public class ResourceBundleEditor extends UserDataHolderBase implements Document
       ((TitledBorder)titledPanel.getBorder()).setTitleColor(property == null ? JBColor.RED : UIUtil.getLabelTextForeground());
       titledPanel.repaint();
     }
-    undoManager.flushCurrentCommandMerger();
   }
 
   private void installPropertiesChangeListeners() {
@@ -686,6 +688,10 @@ public class ResourceBundleEditor extends UserDataHolderBase implements Document
     return myDataProviderPanel;
   }
 
+  public StructureViewComponent getStructureViewComponent() {
+    return myStructureViewComponent;
+  }
+
   private Object getData(final String dataId) {
     if (SelectInContext.DATA_KEY.is(dataId)) {
       return new SelectInContext(){
@@ -823,7 +829,7 @@ public class ResourceBundleEditor extends UserDataHolderBase implements Document
 
   @Override
   public BackgroundEditorHighlighter getBackgroundHighlighter() {
-    return null;
+    return myHighlighter;
   }
 
   @Override
diff --git a/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditorHighlighter.java b/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditorHighlighter.java
new file mode 100644 (file)
index 0000000..db59674
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2000-2016 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 com.intellij.lang.properties.editor;
+
+import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
+import com.intellij.codeHighlighting.HighlightingPass;
+import com.intellij.codeInsight.daemon.HighlightDisplayKey;
+import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
+import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
+import com.intellij.codeInspection.InspectionProfile;
+import com.intellij.codeInspection.InspectionProfileEntry;
+import com.intellij.codeInspection.ProblemDescriptorUtil;
+import com.intellij.codeInspection.ex.InspectionToolWrapper;
+import com.intellij.ide.util.treeView.TreeVisitor;
+import com.intellij.ide.util.treeView.smartTree.TreeElement;
+import com.intellij.ide.util.treeView.smartTree.TreeElementWrapper;
+import com.intellij.lang.annotation.HighlightSeverity;
+import com.intellij.lang.properties.IProperty;
+import com.intellij.lang.properties.editor.inspections.*;
+import com.intellij.lang.properties.psi.PropertiesFile;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
+import com.intellij.profile.codeInspection.InspectionProfileManager;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.SmartList;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.function.Function;
+
+public class ResourceBundleEditorHighlighter implements BackgroundEditorHighlighter {
+  private final static Logger LOG = Logger.getInstance(ResourceBundleEditorHighlighter.class);
+
+  private final ResourceBundleEditor myEditor;
+
+  public ResourceBundleEditorHighlighter(ResourceBundleEditor editor) {
+    myEditor = editor;
+  }
+
+  @NotNull
+  @Override
+  public HighlightingPass[] createPassesForEditor() {
+    return new HighlightingPass[]{new ResourceBundleEditorHighlightingPass(myEditor)};
+  }
+
+  @NotNull
+  @Override
+  public HighlightingPass[] createPassesForVisibleArea() {
+    throw new UnsupportedOperationException();
+  }
+
+  private static class ResourceBundleEditorHighlightingPass implements HighlightingPass {
+    private final ResourceBundleEditor myEditor;
+
+    private ResourceBundleEditorHighlightingPass(ResourceBundleEditor editor) {
+      myEditor = editor;
+    }
+
+    @Override
+    public void collectInformation(@NotNull ProgressIndicator progress) {
+      InspectionProfile profileToUse = InspectionProfileManager.getInstance().getCurrentProfile();
+      final PsiFile containingFile = myEditor.getResourceBundle().getDefaultPropertiesFile().getContainingFile();
+      final InspectionVisitorWrapper[] visitors =
+        Arrays.stream(profileToUse.getInspectionTools(containingFile))
+          .filter(t -> profileToUse.isToolEnabled(HighlightDisplayKey.find(t.getShortName()), containingFile))
+          .map(InspectionToolWrapper::getTool)
+          .filter(ResourceBundleEditorInspection.class::isInstance)
+          .map(ResourceBundleEditorInspection.class::cast)
+          .map(i -> {
+            final HighlightDisplayKey key = HighlightDisplayKey.find(((InspectionProfileEntry)i).getShortName());
+            return new InspectionVisitorWrapper(i.buildPropertyGroupVisitor(myEditor.getResourceBundle()),
+                                                profileToUse.getErrorLevel(key, containingFile).getSeverity(),
+                                                key);
+          })
+          .toArray(InspectionVisitorWrapper[]::new);
+
+      final List<PropertiesFile> files = myEditor.getResourceBundle().getPropertiesFiles();
+      final Project project = myEditor.getResourceBundle().getProject();
+
+      final TreeVisitor<TreeElementWrapper> nodeVisitor =
+        new TreeVisitor<TreeElementWrapper>() {
+          @Override
+          public boolean visit(@NotNull TreeElementWrapper wrapper) {
+            final TreeElement treeElement = wrapper.getValue();
+            if (!(treeElement instanceof ResourceBundlePropertyStructureViewElement)) return false;
+            ResourceBundlePropertyStructureViewElement node = (ResourceBundlePropertyStructureViewElement) treeElement;
+            final String key = node.getProperty().getKey();
+            LOG.assertTrue(key != null);
+            SortedSet<HighlightInfoType> highlightTypes = new TreeSet<>(Comparator.comparing(t -> t.getSeverity(null)));
+            List<Pair<ResourceBundleEditorProblemDescriptor, HighlightDisplayKey>> allDescriptors =
+              new SmartList<>();
+            final IProperty[] properties =
+              files.stream().map(f -> f.findPropertyByKey(key)).filter(Objects::nonNull).toArray(IProperty[]::new);
+            for (InspectionVisitorWrapper v : visitors) {
+              final ResourceBundleEditorProblemDescriptor[] problemDescriptors = v.getProblemVisitor().apply(properties);
+              if (!ArrayUtil.isEmpty(problemDescriptors)) {
+                final HighlightSeverity severity = v.getSeverity();
+                for (ResourceBundleEditorProblemDescriptor descriptor : problemDescriptors) {
+                  allDescriptors.add(Pair.create(descriptor, v.getKey()));
+                  final HighlightInfoType infoType =
+                    ProblemDescriptorUtil.getHighlightInfoType(descriptor.getHighlightType(),
+                                                               severity,
+                                                               SeverityRegistrar.getSeverityRegistrar(project));
+                  highlightTypes.add(infoType);
+                }
+              }
+            }
+            node.setInspectedPropertyNodeInfo(allDescriptors.isEmpty() ? null : new InspectedPropertyNodeInfo(allDescriptors.toArray(new Pair[allDescriptors.size()]), highlightTypes));
+            return false;
+          }
+        };
+      myEditor.getStructureViewComponent().getTreeBuilder().accept(TreeElementWrapper.class,
+                                                                   nodeVisitor);
+    }
+
+    @Override
+    public void applyInformationToEditor() {
+      myEditor.getStructureViewComponent().repaint();
+    }
+  }
+
+  private static class InspectionVisitorWrapper {
+    private final Function<IProperty[], ResourceBundleEditorProblemDescriptor[]> myProblemVisitor;
+    private final HighlightSeverity mySeverity;
+    private final HighlightDisplayKey myKey;
+
+    private InspectionVisitorWrapper(@NotNull Function<IProperty[], ResourceBundleEditorProblemDescriptor[]> visitor,
+                                     @NotNull HighlightSeverity severity,
+                                     @NotNull HighlightDisplayKey key) {
+      myProblemVisitor = visitor;
+      mySeverity = severity;
+      myKey = key;
+    }
+
+    public Function<IProperty[], ResourceBundleEditorProblemDescriptor[]> getProblemVisitor() {
+      return myProblemVisitor;
+    }
+
+    public HighlightSeverity getSeverity() {
+      return mySeverity;
+    }
+
+    public HighlightDisplayKey getKey() {
+      return myKey;
+    }
+  }
+}
index b753963e3aed48a408886b14f719f487e690b744..5c51402cfe787e538a504876b8e01ced20cc56d1 100644 (file)
@@ -27,17 +27,18 @@ import com.intellij.lang.properties.editor.inspections.ResourceBundleEditorProbl
 import com.intellij.openapi.actionSystem.AnAction;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.application.WriteAction;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.fileEditor.FileEditor;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
 import com.intellij.openapi.util.Pair;
 import com.intellij.psi.PsiFile;
-import com.intellij.ui.popup.PopupFactoryImpl;
 import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.ThrowableRunnable;
 import org.jetbrains.annotations.Nls;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 /**
  * @author Dmitry Batkovich
@@ -85,7 +86,7 @@ public class ResourceBundleEditorShowQuickFixesAction extends AnAction {
 
     final Project project = e.getProject();
     LOG.assertTrue(project != null);
-    PopupFactoryImpl
+    JBPopupFactory
       .getInstance()
       .createListPopup(new IntentionListStep(null, intentions, null, file, project))
       .showInBestPositionFor(e.getDataContext());
@@ -98,7 +99,7 @@ public class ResourceBundleEditorShowQuickFixesAction extends AnAction {
                                              editor.getSelectedElementIfOnlyOne() instanceof ResourceBundlePropertyStructureViewElement);
   }
 
-  private ResourceBundleEditor getEditor(AnActionEvent e) {
+  private static ResourceBundleEditor getEditor(AnActionEvent e) {
     final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(e.getDataContext());
     return editor instanceof ResourceBundleEditor ? (ResourceBundleEditor)editor : null;
   }
@@ -133,7 +134,13 @@ public class ResourceBundleEditorShowQuickFixesAction extends AnAction {
 
     @Override
     public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
-      getQuickFix().applyFix(project, myDescriptor);
+      final QuickFix<ResourceBundleEditorProblemDescriptor> fix = getQuickFix();
+      ThrowableRunnable<RuntimeException> fixAction = () -> fix.applyFix(project, myDescriptor);
+      if (fix.startInWriteAction()) {
+        WriteAction.run(fixAction);
+      } else {
+        fixAction.run();
+      }
     }
 
     @Override
index ed7b2dcbb0cdfb5d90a29956af841a38997c10df..e2427201fdd5e2ee7260c3e9cfacbf2830ec97e4 100644 (file)
@@ -42,6 +42,7 @@ import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
 import java.util.*;
+import java.util.function.Function;
 
 /**
  * @author Dmitry Batkovich
@@ -75,15 +76,17 @@ public class IncompletePropertyInspection extends LocalInspectionTool implements
     }
   }
 
-  @Nullable
+  @NotNull
   @Override
-  public ResourceBundleEditorProblemDescriptor[] checkPropertyGroup(@NotNull List<IProperty> properties, @NotNull ResourceBundle resourceBundle) {
-    return !isPropertyComplete(properties, resourceBundle)
-           ? new ResourceBundleEditorProblemDescriptor[] {new ResourceBundleEditorProblemDescriptor(ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
-                                                                                                    PropertiesBundle.message("incomplete.property.inspection.description",
-                                                                                           properties.get(0).getName()),
-                                                                                                    new IgnoreLocalesQuickFix(properties.get(0), resourceBundle))}
-           : null;
+  public Function<IProperty[], ResourceBundleEditorProblemDescriptor[]> buildPropertyGroupVisitor(@NotNull ResourceBundle resourceBundle) {
+    return properties -> !isPropertyComplete(properties, resourceBundle)
+    ? new ResourceBundleEditorProblemDescriptor[]{new ResourceBundleEditorProblemDescriptor(ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+                                                                                            PropertiesBundle.message(
+                                                                                              "incomplete.property.inspection.description",
+                                                                                              properties[0].getName()),
+                                                                                            new IgnoreLocalesQuickFix(properties[0],
+                                                                                                                      resourceBundle))}
+    : null;
   }
 
   @NotNull
@@ -145,11 +148,11 @@ public class IncompletePropertyInspection extends LocalInspectionTool implements
   }
 
   public boolean isPropertyComplete(final String key, final ResourceBundle resourceBundle) {
-    return isPropertyComplete(ContainerUtil.mapNotNull(resourceBundle.getPropertiesFiles(), file -> file.findPropertyByKey(key)), resourceBundle);
+    return isPropertyComplete(resourceBundle.getPropertiesFiles().stream().map(f -> f.findPropertyByKey(key)).toArray(IProperty[]::new), resourceBundle);
   }
 
-  private boolean isPropertyComplete(final List<IProperty> properties, final ResourceBundle resourceBundle) {
-    final Set<PropertiesFile> existed = ContainerUtil.map2Set(properties, property -> property.getPropertiesFile());
+  private boolean isPropertyComplete(final IProperty[] properties, final ResourceBundle resourceBundle) {
+    final Set<PropertiesFile> existed = ContainerUtil.map2Set(properties, IProperty::getPropertiesFile);
     for (PropertiesFile file : resourceBundle.getPropertiesFiles()) {
       if (!existed.contains(file) && !getIgnoredSuffixes().contains(PropertiesUtil.getSuffix(file))) {
         return false;