summaryrefslogtreecommitdiff
path: root/plugins/devkit/devkit-core/src/references/DevKitRelatedPropertiesProvider.java
blob: dc804bf51e813de7864de27f154103ca91aa804b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// Copyright 2000-2020 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 org.jetbrains.idea.devkit.references;

import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo;
import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder;
import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.lang.properties.psi.Property;
import com.intellij.navigation.GotoRelatedItem;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlToken;
import com.intellij.psi.xml.XmlTokenType;
import com.intellij.util.Query;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomUtil;
import com.intellij.util.xml.GenericDomValue;
import icons.DevkitIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.devkit.DevKitBundle;
import org.jetbrains.idea.devkit.dom.ActionOrGroup;
import org.jetbrains.idea.devkit.dom.OverrideText;
import org.jetbrains.idea.devkit.navigation.DevkitRelatedLineMarkerProviderBase;
import org.jetbrains.idea.devkit.util.DescriptorI18nUtil;

import javax.swing.*;
import java.util.Collection;
import java.util.Collections;

/**
 * Provides gutter icon/goto related navigation for implicit message keys in {@code plugin.xml}.
 * <p>
 * The following elements are supported:
 * <ul>
 *   <li>{@code <plugin> -> <id>}: {@code plugin.@id.description}</li>
 *   <li>{@code <action>|<group>}: {@code action.@id.text|description}</li>
 *   <li>{@code <override-text>}: {@code action.actionId.@place.text}</li>
 * </ul>
 * </p>
 *
 * @see MessageBundleReferenceContributor
 */
public final class DevKitRelatedPropertiesProvider extends DevkitRelatedLineMarkerProviderBase {
  @Override
  public String getName() {
    return DevKitBundle.message("line.marker.related.property.description");
  }

  @NotNull
  @Override
  public Icon getIcon() {
    return DevkitIcons.Gutter.Properties;
  }

  @Override
  protected void collectNavigationMarkers(@NotNull PsiElement leaf, @NotNull Collection<? super RelatedItemLineMarkerInfo<?>> result) {
    if (!(leaf instanceof XmlToken)) return;
    if (leaf.getNode().getElementType() != XmlTokenType.XML_NAME) return;
    PsiElement prev = PsiTreeUtil.getPrevSiblingOfType(leaf, XmlToken.class);
    if (prev == null || prev.getNode().getElementType() != XmlTokenType.XML_START_TAG_START) return;
    if (!leaf.textMatches("action") && !leaf.textMatches("group") &&
        !leaf.textMatches("override-text") &&
        !leaf.textMatches("id")) {
      return;
    }

    DomElement domElement = DomUtil.getDomElement(leaf);
    if (domElement instanceof ActionOrGroup) {
      ActionOrGroup actionOrGroup = (ActionOrGroup)domElement;
      createLineMarker(leaf, result, domElement, actionOrGroup.getId());
    }
    else if (domElement instanceof OverrideText) {
      OverrideText overrideText = (OverrideText)domElement;
      createLineMarker(leaf, result, domElement, overrideText.getPlace());
    }
    else if (domElement instanceof GenericDomValue) {
      createLineMarker(leaf, result, domElement, (GenericDomValue<?>)domElement);
    }
  }

  private static void createLineMarker(@NotNull PsiElement leaf,
                                       @NotNull Collection<? super RelatedItemLineMarkerInfo<?>> result,
                                       DomElement domElement,
                                       GenericDomValue<?> referenceElement) {
    if (!DomUtil.hasXml(referenceElement)) return;
    final XmlElement valueXmlElement = DomUtil.getValueElement(referenceElement);
    if (valueXmlElement == null) return;

    PropertiesFile file = DescriptorI18nUtil.findBundlePropertiesFile(domElement);
    if (file == null) return;

    final Query<PsiReference> query = ReferencesSearch.search(valueXmlElement, new LocalSearchScope(file.getContainingFile()));
    if (query.findFirst() == null) return;

    result.add(
      NavigationGutterIconBuilder.create(DevkitIcons.Gutter.Properties,
                                         e -> Collections.singletonList(((PsiElement)e)),
                                         e -> Collections.singletonList(new GotoRelatedItem((PsiElement)e)))
        .setTargets(NotNullLazyValue.createValue(() -> {
          return ContainerUtil.map(query.findAll(),
                                   reference -> PsiTreeUtil.getParentOfType(reference.getElement(), Property.class));
        }))
        .setPopupTitle(DevKitBundle.message("line.marker.related.property.popup.title"))
        .setTooltipText(DevKitBundle.message("line.marker.related.property.tooltip"))
        .setAlignment(GutterIconRenderer.Alignment.RIGHT)
        .createLineMarkerInfo(leaf)
    );
  }
}