Skip to content

Commit 15c74f3

Browse files
authored
Merge pull request #354 from dylanngo95/issues-305
#305_Add reference navigation and autocomplete for UI component
2 parents 2e1a9f5 + 2a21a94 commit 15c74f3

File tree

7 files changed

+331
-10
lines changed

7 files changed

+331
-10
lines changed

resources/META-INF/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
<fileBasedIndex implementation="com.magento.idea.magento2plugin.stubs.indexes.js.MagentoLibJsIndex" />
126126
<fileBasedIndex implementation="com.magento.idea.magento2plugin.stubs.indexes.xml.AclResourceIndex" />
127127
<fileBasedIndex implementation="com.magento.idea.magento2plugin.stubs.indexes.xml.MenuIndex" />
128+
<fileBasedIndex implementation="com.magento.idea.magento2plugin.stubs.indexes.xml.UIComponentIndex" />
128129

129130
<codeInsight.lineMarkerProvider language="PHP" implementationClass="com.magento.idea.magento2plugin.linemarker.php.PluginLineMarkerProvider"/>
130131
<codeInsight.lineMarkerProvider language="PHP" implementationClass="com.magento.idea.magento2plugin.linemarker.php.PluginTargetLineMarkerProvider"/>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.completion.provider;
7+
8+
import com.intellij.codeInsight.completion.CompletionParameters;
9+
import com.intellij.codeInsight.completion.CompletionProvider;
10+
import com.intellij.codeInsight.completion.CompletionResultSet;
11+
import com.intellij.codeInsight.lookup.LookupElementBuilder;
12+
import com.intellij.psi.PsiElement;
13+
import com.intellij.psi.xml.XmlFile;
14+
import com.intellij.util.ProcessingContext;
15+
import com.jetbrains.php.PhpIcons;
16+
import com.magento.idea.magento2plugin.indexes.UIComponentIndex;
17+
import java.util.List;
18+
import org.jetbrains.annotations.NotNull;
19+
20+
21+
public class UiComponentCompletionProvider extends CompletionProvider<CompletionParameters> {
22+
23+
@Override
24+
protected void addCompletions(@NotNull final CompletionParameters parameters,
25+
final ProcessingContext context,
26+
@NotNull final CompletionResultSet result) {
27+
final PsiElement position = parameters.getPosition().getOriginalElement();
28+
if (position == null) {
29+
return;
30+
}
31+
32+
final List<XmlFile> targets = UIComponentIndex.getUiComponentFiles(position.getProject());
33+
if (!targets.isEmpty()) {
34+
for (final XmlFile file : targets) {
35+
result.addElement(LookupElementBuilder
36+
.create(file.getVirtualFile().getNameWithoutExtension())
37+
.withIcon(PhpIcons.XML_TAG_ICON)
38+
);
39+
}
40+
}
41+
}
42+
}

src/com/magento/idea/magento2plugin/completion/xml/XmlCompletionContributor.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ public XmlCompletionContributor() {
8888
new FilePathCompletionProvider()
8989
);
9090

91+
// <uiComponent name="completion"/>
92+
extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN)
93+
.inside(XmlPatterns.xmlAttribute().withName(LayoutXml.NAME_ATTRIBUTE)
94+
.withParent(XmlPatterns.xmlTag().withName(LayoutXml.UI_COMPONENT_TAG_NAME))),
95+
new UiComponentCompletionProvider()
96+
);
97+
9198
extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_DATA_CHARACTERS)
9299
.withParent(XmlPatterns.xmlText().withParent(
93100
XmlPatterns.xmlTag().withName(UiComponentXml.XML_TAG_ITEM).withChild(
@@ -126,20 +133,20 @@ public XmlCompletionContributor() {
126133

127134
// <observer instance="Class">
128135
extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN)
129-
.inside(XmlPatterns.xmlAttribute().withName(ModuleEventsXml.INSTANCE_ATTRIBUTE)
130-
.withParent(XmlPatterns.xmlTag().withName(ModuleEventsXml.OBSERVER_TAG)
131-
)
132-
).inFile(xmlFile().withName(string().matches(ModuleEventsXml.FILE_NAME))),
133-
new PhpClassCompletionProvider()
136+
.inside(XmlPatterns.xmlAttribute().withName(ModuleEventsXml.INSTANCE_ATTRIBUTE)
137+
.withParent(XmlPatterns.xmlTag().withName(ModuleEventsXml.OBSERVER_TAG)
138+
)
139+
).inFile(xmlFile().withName(string().matches(ModuleEventsXml.FILE_NAME))),
140+
new PhpClassCompletionProvider()
134141
);
135142

136143
// <job instance="class">
137144
extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN)
138-
.inside(XmlPatterns.xmlAttribute().withName(CommonXml.ATTR_INSTANCE)
139-
.withParent(XmlPatterns.xmlTag().withName(CrontabXmlTemplate.CRON_JOB_TAG)
140-
)
141-
).inFile(xmlFile().withName(string().matches(CrontabXmlTemplate.FILE_NAME))),
142-
new PhpClassCompletionProvider()
145+
.inside(XmlPatterns.xmlAttribute().withName(CommonXml.ATTR_INSTANCE)
146+
.withParent(XmlPatterns.xmlTag().withName(CrontabXmlTemplate.CRON_JOB_TAG)
147+
)
148+
).inFile(xmlFile().withName(string().matches(CrontabXmlTemplate.FILE_NAME))),
149+
new PhpClassCompletionProvider()
143150
);
144151

145152
// <source_model>php class completion</source_model> in system.xml files.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.indexes;
7+
8+
import com.intellij.ide.highlighter.XmlFileType;
9+
import com.intellij.openapi.project.Project;
10+
import com.intellij.openapi.vfs.VirtualFile;
11+
import com.intellij.psi.PsiFile;
12+
import com.intellij.psi.PsiManager;
13+
import com.intellij.psi.search.FilenameIndex;
14+
import com.intellij.psi.xml.XmlFile;
15+
import com.intellij.util.indexing.FileBasedIndex;
16+
import com.intellij.util.indexing.ID;
17+
import java.util.ArrayList;
18+
import java.util.Collection;
19+
import java.util.List;
20+
import org.jetbrains.annotations.Nullable;
21+
22+
@SuppressWarnings("PMD")
23+
public final class UIComponentIndex {
24+
25+
private UIComponentIndex() {
26+
throw new AssertionError("Instantiating utility class...");
27+
}
28+
29+
/**
30+
* Available ui component file.
31+
*
32+
* @param virtualFile VirtualFile
33+
* @return boolean
34+
*/
35+
public static boolean isUiComponentFile(final VirtualFile virtualFile) {
36+
final VirtualFile parent = virtualFile.getParent();
37+
return virtualFile.getFileType() == XmlFileType.INSTANCE && parent.isDirectory()
38+
&& parent.getName().endsWith("ui_component");
39+
}
40+
41+
/**
42+
* Get ui component files.
43+
*
44+
* @param project Project
45+
* @param fileName String
46+
* @return List
47+
*/
48+
public static List<XmlFile> getUiComponentFiles(
49+
final Project project,
50+
final @Nullable String fileName
51+
) {
52+
final List<XmlFile> results = new ArrayList<XmlFile>();//NOPMD
53+
final Collection<VirtualFile> xmlFiles = FilenameIndex.getAllFilesByExt(project, "xml");
54+
55+
final PsiManager psiManager = PsiManager.getInstance(project);
56+
for (final VirtualFile xmlFile: xmlFiles) {
57+
if (isUiComponentFile(xmlFile)) {
58+
if (fileName != null && !xmlFile.getNameWithoutExtension().equals(fileName)) {
59+
continue;
60+
}
61+
62+
final PsiFile file = psiManager.findFile(xmlFile);
63+
if (file != null) {
64+
results.add((XmlFile)file);
65+
}
66+
}
67+
}
68+
69+
return results;
70+
}
71+
72+
/**
73+
* Get ui component files.
74+
*
75+
* @param project Project
76+
* @return List
77+
*/
78+
public static List<XmlFile> getUiComponentFiles(final Project project) {
79+
return getUiComponentFiles(project, null);
80+
}
81+
82+
/**
83+
* Get All Keys.
84+
*
85+
* @param identifier ID
86+
* @param project Project
87+
* @return Collection
88+
*/
89+
public static Collection<String> getAllKeys(
90+
final ID<String, Void> identifier,
91+
final Project project
92+
) {
93+
return FileBasedIndex.getInstance().getAllKeys(identifier, project);
94+
}
95+
96+
/**
97+
* Get ui component files.
98+
*
99+
* @param project Project
100+
* @param fileName String
101+
* @return List
102+
*/
103+
public static List<XmlFile> getUIComponentFiles(Project project, @Nullable String fileName) {
104+
List<XmlFile> results = new ArrayList<XmlFile>();
105+
Collection<VirtualFile> xmlFiles = FilenameIndex.getAllFilesByExt(project, "xml");
106+
107+
PsiManager psiManager = PsiManager.getInstance(project);
108+
for (VirtualFile xmlFile: xmlFiles) {
109+
if (isUIComponentFile(xmlFile)) {
110+
if (fileName != null && !xmlFile.getNameWithoutExtension().equals(fileName)) {
111+
continue;
112+
}
113+
114+
PsiFile file = psiManager.findFile(xmlFile);
115+
if (file != null) {
116+
results.add((XmlFile)file);
117+
}
118+
}
119+
}
120+
121+
return results;
122+
}
123+
124+
/**
125+
* Available ui component file.
126+
*
127+
* @param virtualFile VirtualFile
128+
* @return boolean
129+
*/
130+
public static boolean isUIComponentFile(VirtualFile virtualFile) {
131+
VirtualFile parent = virtualFile.getParent();
132+
return virtualFile.getFileType() == XmlFileType.INSTANCE && parent.isDirectory()
133+
&& parent.getName().endsWith("ui_component");
134+
}
135+
136+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.reference.provider;
7+
8+
import com.intellij.openapi.util.text.StringUtil;
9+
import com.intellij.psi.PsiElement;
10+
import com.intellij.psi.PsiReference;
11+
import com.intellij.psi.PsiReferenceProvider;
12+
import com.intellij.psi.xml.XmlFile;
13+
import com.intellij.util.ProcessingContext;
14+
import com.magento.idea.magento2plugin.indexes.UIComponentIndex;
15+
import com.magento.idea.magento2plugin.reference.xml.PolyVariantReferenceBase;
16+
import java.util.List;
17+
import org.jetbrains.annotations.NotNull;
18+
19+
20+
public class UIComponentReferenceProvider extends PsiReferenceProvider {
21+
22+
@NotNull
23+
@Override
24+
public PsiReference[] getReferencesByElement(
25+
@NotNull final PsiElement element,
26+
@NotNull final ProcessingContext context
27+
) {
28+
final String value = StringUtil.unquoteString(element.getText());
29+
final List<XmlFile> targets = UIComponentIndex.getUIComponentFiles(
30+
element.getProject(),
31+
value
32+
);
33+
if (!targets.isEmpty()) {
34+
return new PsiReference[] {new PolyVariantReferenceBase(element, targets)};
35+
}
36+
return PsiReference.EMPTY_ARRAY;
37+
}
38+
}

src/com/magento/idea/magento2plugin/reference/xml/XmlReferenceContributor.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar)
132132
new LayoutUpdateReferenceProvider()
133133
);
134134

135+
// <uiComponent name="completion"/>
136+
registrar.registerReferenceProvider(
137+
XmlPatterns.xmlAttributeValue().withParent(
138+
XmlPatterns.xmlAttribute().withName("name").withParent(
139+
XmlPatterns.xmlTag().withName("uiComponent")
140+
)
141+
),
142+
new UIComponentReferenceProvider()
143+
);
144+
135145
// <event name="reference" />
136146
registrar.registerReferenceProvider(
137147
XmlPatterns.xmlAttributeValue().withParent(
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.stubs.indexes.xml;
7+
8+
import com.intellij.ide.highlighter.XmlFileType;
9+
import com.intellij.openapi.vfs.VirtualFile;
10+
import com.intellij.psi.PsiFile;
11+
import com.intellij.util.indexing.DataIndexer;
12+
import com.intellij.util.indexing.FileBasedIndex;
13+
import com.intellij.util.indexing.FileContent;
14+
import com.intellij.util.indexing.ID;
15+
import com.intellij.util.indexing.ScalarIndexExtension;
16+
import com.intellij.util.io.EnumeratorStringDescriptor;
17+
import com.intellij.util.io.KeyDescriptor;
18+
import com.intellij.util.xml.impl.DomApplicationComponent;
19+
import com.magento.idea.magento2plugin.project.Settings;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
import org.jetbrains.annotations.NotNull;
23+
24+
25+
public class UIComponentIndex extends ScalarIndexExtension<String> {
26+
27+
public static final ID<String, Void> KEY =
28+
ID.create("com.magento.idea.magento2plugin.stubs.indexes.ui_component");
29+
30+
@NotNull
31+
@Override
32+
public ID<String, Void> getName() {
33+
return KEY;
34+
}
35+
36+
/**
37+
* Indexer for `ui_component` files.
38+
*
39+
* @return this
40+
*/
41+
@NotNull
42+
@Override
43+
public DataIndexer<String, Void, FileContent> getIndexer() {
44+
return inputData -> {
45+
final Map<String, Void> map = new HashMap<>();//NOPMD
46+
47+
final PsiFile psiFile = inputData.getPsiFile();
48+
if (!Settings.isEnabled(psiFile.getProject())) {
49+
return map;
50+
}
51+
52+
final VirtualFile file = inputData.getFile();
53+
if (!file.getPath().matches("uiComponent")) {
54+
return map;
55+
}
56+
57+
final String key = file.getName();
58+
if (!key.isEmpty()) {
59+
map.put(key, null);
60+
}
61+
62+
return map;
63+
};
64+
}
65+
66+
@NotNull
67+
@Override
68+
public KeyDescriptor<String> getKeyDescriptor() {
69+
return new EnumeratorStringDescriptor();
70+
}
71+
72+
@NotNull
73+
@Override
74+
public FileBasedIndex.InputFilter getInputFilter() {
75+
return file -> file.getFileType() == XmlFileType.INSTANCE;
76+
}
77+
78+
@Override
79+
public boolean dependsOnFileContent() {
80+
return true;
81+
}
82+
83+
@Override
84+
public int getVersion() {
85+
return DomApplicationComponent.getInstance().getCumulativeVersion(false);
86+
}
87+
}

0 commit comments

Comments
 (0)