Skip to content

Commit 3d43cf9

Browse files
author
Vitaliy Boyko
committed
Added menu.xml parent menu completion and reference
1 parent 24f5b57 commit 3d43cf9

File tree

13 files changed

+354
-1
lines changed

13 files changed

+354
-1
lines changed

resources/META-INF/plugin.xml

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

128129
<codeInsight.lineMarkerProvider language="PHP" implementationClass="com.magento.idea.magento2plugin.linemarker.php.PluginLineMarkerProvider"/>
129130
<codeInsight.lineMarkerProvider language="PHP" implementationClass="com.magento.idea.magento2plugin.linemarker.php.PluginTargetLineMarkerProvider"/>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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.util.ProcessingContext;
14+
import com.intellij.util.indexing.FileBasedIndex;
15+
import com.magento.idea.magento2plugin.MagentoIcons;
16+
import com.magento.idea.magento2plugin.stubs.indexes.xml.MenuIndex;
17+
import java.util.Collection;
18+
import org.jetbrains.annotations.NotNull;
19+
20+
public class MenuCompletionProvider extends CompletionProvider<CompletionParameters> {
21+
22+
@Override
23+
protected void addCompletions(final @NotNull CompletionParameters parameters,
24+
final ProcessingContext context,
25+
final @NotNull CompletionResultSet result) {
26+
final PsiElement position = parameters.getPosition().getOriginalElement();
27+
if (position == null) {
28+
return;
29+
}
30+
final String prefix = result.getPrefixMatcher().getPrefix();
31+
32+
final Collection<String> menuReferences
33+
= FileBasedIndex.getInstance().getAllKeys(MenuIndex.KEY, position.getProject());
34+
35+
menuReferences.removeIf(m -> !m.startsWith(prefix));
36+
for (final String menuReference : menuReferences) {
37+
result.addElement(
38+
LookupElementBuilder
39+
.create(menuReference)
40+
.withIcon(MagentoIcons.MODULE)
41+
);
42+
}
43+
}
44+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,16 @@ public XmlCompletionContributor() {
292292
new TestNameCompletionProvider()
293293
);
294294

295+
// <add parent="completion" />
296+
extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN)
297+
.inside(XmlPatterns.xmlAttribute().withName(ModuleMenuXml.parentTagAttribute)
298+
.withParent(XmlPatterns.xmlTag().withName(ModuleMenuXml.addTag)
299+
.withParent(XmlPatterns.xmlTag().withName(ModuleMenuXml.menuTag))
300+
)
301+
).inFile(xmlFile().withName(string().matches(ModuleMenuXml.fileName))),
302+
new MenuCompletionProvider()
303+
);
304+
295305
registerCompletionsForDifferentNesting();
296306
}
297307

src/com/magento/idea/magento2plugin/indexes/IndexManager.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/**
1+
/*
22
* Copyright © Magento, Inc. All rights reserved.
33
* See COPYING.txt for license details.
44
*/
@@ -25,6 +25,7 @@
2525
import com.magento.idea.magento2plugin.stubs.indexes.mftf.SectionIndex;
2626
import com.magento.idea.magento2plugin.stubs.indexes.mftf.TestNameIndex;
2727
import com.magento.idea.magento2plugin.stubs.indexes.xml.AclResourceIndex;
28+
import com.magento.idea.magento2plugin.stubs.indexes.xml.MenuIndex;
2829
import com.magento.idea.magento2plugin.stubs.indexes.xml.PhpClassNameIndex;
2930

3031
@SuppressWarnings({"PMD.ClassNamingConventions", "PMD.UseUtilityClass"})
@@ -52,6 +53,8 @@ public static void manualReindex() {
5253
PhpClassNameIndex.KEY,
5354
//acl
5455
AclResourceIndex.KEY,
56+
//menu
57+
MenuIndex.KEY,
5558
//require_js
5659
RequireJsIndex.KEY,
5760
MagentoLibJsIndex.KEY,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.magento.files;
7+
8+
@SuppressWarnings({"PMD.ClassNamingConventions"})
9+
public class ModuleMenuXml {
10+
public static String fileName = "menu.xml";
11+
public static String menuTag = "menu";
12+
public static String addTag = "add";
13+
public static String idTagAttribute = "id";
14+
public static String parentTagAttribute = "parent";
15+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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.ide.highlighter.XmlFileType;
9+
import com.intellij.openapi.vfs.VirtualFile;
10+
import com.intellij.psi.PsiElement;
11+
import com.intellij.psi.PsiManager;
12+
import com.intellij.psi.PsiReference;
13+
import com.intellij.psi.PsiReferenceProvider;
14+
import com.intellij.psi.impl.source.xml.XmlAttributeValueImpl;
15+
import com.intellij.psi.search.GlobalSearchScope;
16+
import com.intellij.psi.xml.XmlDocument;
17+
import com.intellij.psi.xml.XmlFile;
18+
import com.intellij.psi.xml.XmlTag;
19+
import com.intellij.util.ProcessingContext;
20+
import com.intellij.util.indexing.FileBasedIndex;
21+
import com.magento.idea.magento2plugin.magento.files.ModuleMenuXml;
22+
import com.magento.idea.magento2plugin.reference.xml.PolyVariantReferenceBase;
23+
import com.magento.idea.magento2plugin.stubs.indexes.xml.MenuIndex;
24+
import java.util.ArrayList;
25+
import java.util.Collection;
26+
import java.util.List;
27+
import java.util.Objects;
28+
import org.jetbrains.annotations.NotNull;
29+
import org.jetbrains.annotations.Nullable;
30+
31+
public class MenuReferenceProvider extends PsiReferenceProvider {
32+
33+
@NotNull
34+
@Override
35+
public PsiReference[] getReferencesByElement(
36+
final @NotNull PsiElement element,
37+
final @NotNull ProcessingContext context
38+
) {
39+
final List<PsiReference> psiReferences = new ArrayList<>();
40+
final String identifier = ((XmlAttributeValueImpl) element).getValue();
41+
42+
final Collection<VirtualFile> moduleFiles = FileBasedIndex.getInstance()
43+
.getContainingFiles(MenuIndex.KEY, identifier,
44+
GlobalSearchScope.getScopeRestrictedByFileTypes(
45+
GlobalSearchScope.allScope(element.getProject()),
46+
XmlFileType.INSTANCE
47+
)
48+
);
49+
50+
final List<PsiElement> psiElements = new ArrayList<>();
51+
52+
final PsiManager psiManager = PsiManager.getInstance(element.getProject());
53+
for (final VirtualFile moduleVf : moduleFiles) {
54+
55+
final XmlDocument xmlDocument = ((XmlFile) Objects.requireNonNull(
56+
psiManager.findFile(moduleVf))
57+
).getDocument();
58+
if (xmlDocument != null) {
59+
final XmlTag xmlRootTag = xmlDocument.getRootTag();
60+
if (xmlRootTag != null) {
61+
final XmlTag addTag = getAddTagByIdentifier(identifier, xmlRootTag);
62+
psiElements.add(addTag);
63+
}
64+
}
65+
}
66+
67+
if (psiElements.isEmpty()) {
68+
psiReferences.add(
69+
new PolyVariantReferenceBase(element, psiElements)
70+
);
71+
}
72+
73+
return psiReferences.toArray(new PsiReference[0]);
74+
}
75+
76+
protected XmlTag getAddTagByIdentifier(final String identifier, final XmlTag xmlRootTag) {
77+
if (identifier == null) {
78+
return null;
79+
}
80+
@Nullable final XmlTag menuTag = xmlRootTag.findFirstSubTag(ModuleMenuXml.menuTag);
81+
82+
return parseMenuTag(identifier, menuTag);
83+
}
84+
85+
private XmlTag parseMenuTag(final String identifier, final XmlTag menuTag) {
86+
for (final XmlTag addTag : menuTag.findSubTags(ModuleMenuXml.addTag)) {
87+
if (addTag.getAttributeValue(ModuleMenuXml.idTagAttribute).equals(identifier)) {
88+
return addTag;
89+
}
90+
}
91+
return null;
92+
}
93+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import com.intellij.psi.xml.XmlTokenType;
1212
import com.magento.idea.magento2plugin.magento.files.MftfActionGroup;
1313
import com.magento.idea.magento2plugin.magento.files.MftfTest;
14+
import com.magento.idea.magento2plugin.magento.files.ModuleMenuXml;
15+
import com.magento.idea.magento2plugin.magento.files.ModuleXml;
1416
import com.magento.idea.magento2plugin.magento.files.UiComponentXml;
1517
// CHECKSTYLE IGNORE check FOR NEXT 5 LINES
1618
import com.magento.idea.magento2plugin.reference.provider.*;//NOPMD
@@ -267,6 +269,18 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar)
267269
new FilePathReferenceProvider()
268270
);
269271

272+
// <add parent="reference" />
273+
registrar.registerReferenceProvider(
274+
XmlPatterns.xmlAttributeValue().withParent(
275+
XmlPatterns.xmlAttribute().withName(ModuleMenuXml.parentTagAttribute).withParent(
276+
XmlPatterns.xmlTag().withName(ModuleMenuXml.addTag)
277+
)
278+
).inFile(
279+
xmlFile().withName(string().endsWith(ModuleMenuXml.fileName))
280+
),
281+
new MenuReferenceProvider()
282+
);
283+
270284
registerReferenceForDifferentNesting(registrar);
271285
}
272286

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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.psi.PsiFile;
10+
import com.intellij.psi.xml.XmlDocument;
11+
import com.intellij.psi.xml.XmlFile;
12+
import com.intellij.psi.xml.XmlTag;
13+
import com.intellij.util.indexing.DataIndexer;
14+
import com.intellij.util.indexing.FileBasedIndex;
15+
import com.intellij.util.indexing.FileContent;
16+
import com.intellij.util.indexing.ID;
17+
import com.intellij.util.indexing.ScalarIndexExtension;
18+
import com.intellij.util.io.EnumeratorStringDescriptor;
19+
import com.intellij.util.io.KeyDescriptor;
20+
import com.intellij.util.xml.impl.DomApplicationComponent;
21+
import com.magento.idea.magento2plugin.magento.files.ModuleMenuXml;
22+
import com.magento.idea.magento2plugin.project.Settings;
23+
import java.util.Map;
24+
import java.util.concurrent.ConcurrentHashMap;
25+
import org.jetbrains.annotations.NotNull;
26+
import org.jetbrains.annotations.Nullable;
27+
28+
public class MenuIndex extends ScalarIndexExtension<String> {
29+
public static final ID<String, Void> KEY = ID.create(
30+
"com.magento.idea.magento2plugin.stubs.indexes.menu");
31+
private final KeyDescriptor<String> myKeyDescriptor = new EnumeratorStringDescriptor();
32+
33+
@NotNull
34+
@Override
35+
public DataIndexer<String, Void, FileContent> getIndexer() {
36+
return inputData -> {
37+
final ConcurrentHashMap<String, Void> map = new ConcurrentHashMap<>();
38+
final PsiFile psiFile = inputData.getPsiFile();
39+
if (!Settings.isEnabled(psiFile.getProject())) {
40+
return map;
41+
}
42+
43+
if (!(psiFile instanceof XmlFile)) {
44+
return map;
45+
}
46+
final XmlDocument xmlDocument = ((XmlFile) psiFile).getDocument();
47+
if (xmlDocument != null) {
48+
final XmlTag xmlRootTag = xmlDocument.getRootTag();
49+
if (xmlRootTag != null) {
50+
parseRootTag(map, xmlRootTag);
51+
}
52+
}
53+
54+
return map;
55+
};
56+
}
57+
58+
protected void parseRootTag(final Map<String, Void> map, final XmlTag xmlRootTag) {
59+
@Nullable final XmlTag menuTag = xmlRootTag.findFirstSubTag(ModuleMenuXml.menuTag);
60+
if (menuTag == null) {
61+
return;
62+
}
63+
64+
parseMenuTag(map, menuTag);
65+
}
66+
67+
private void parseMenuTag(final Map<String, Void> map, final XmlTag menuTag) {
68+
for (final XmlTag addTag : menuTag.findSubTags(ModuleMenuXml.addTag)) {
69+
final String identifier = addTag.getAttributeValue(ModuleMenuXml.idTagAttribute);
70+
71+
if (identifier != null && !identifier.isEmpty()) {
72+
map.putIfAbsent(identifier, null);
73+
}
74+
}
75+
}
76+
77+
@NotNull
78+
@Override
79+
public ID<String, Void> getName() {
80+
return KEY;
81+
}
82+
83+
@NotNull
84+
@Override
85+
public KeyDescriptor<String> getKeyDescriptor() {
86+
return this.myKeyDescriptor;
87+
}
88+
89+
@NotNull
90+
@Override
91+
public FileBasedIndex.InputFilter getInputFilter() {
92+
return file ->
93+
file.getFileType() == XmlFileType.INSTANCE
94+
&& file.getName().equalsIgnoreCase(ModuleMenuXml.fileName);
95+
}
96+
97+
@Override
98+
public boolean dependsOnFileContent() {
99+
return true;
100+
}
101+
102+
@Override
103+
public int getVersion() {
104+
return DomApplicationComponent.getInstance().getCumulativeVersion(false);
105+
}
106+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
3+
<menu>
4+
<add id="Foo_Bar::catalog" title="Catalog" translate="title" parent="Magento_Catalog:<caret>"/>
5+
</menu>
6+
</config>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0"?>
2+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
3+
<menu>
4+
<add id="Magento_Catalog::catalog" title="Catalog" translate="title" module="Magento_Catalog" sortOrder="20" dependsOnModule="Magento_Catalog" resource="Magento_Catalog::catalog"/>
5+
<add id="Magento_Catalog::inventory" title="Inventory" translate="title" module="Magento_Catalog" sortOrder="10" parent="Magento_Catalog::catalog" dependsOnModule="Magento_Catalog" resource="Magento_Catalog::catalog"/>
6+
</menu>
7+
</config>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
3+
<menu>
4+
<add id="Foo_Bar::catalog" title="Catalog" translate="title" parent="Magento_Catalog::inventory<caret>"/>
5+
</menu>
6+
</config>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.completion.xml;
7+
8+
import com.magento.idea.magento2plugin.magento.files.ModuleMenuXml;
9+
10+
public class MenuCompletionRegistrarTest extends CompletionXmlFixtureTestCase {
11+
12+
/**
13+
* The `parent` attribute of the `add` tag in the men XML must
14+
* have completion based on the index.
15+
*/
16+
public void testAddTagMustHaveCompletion() {
17+
final String filePath = this.getFixturePath(ModuleMenuXml.fileName);
18+
myFixture.configureByFile(filePath);
19+
20+
assertCompletionContains(
21+
filePath,
22+
"Magento_Catalog::catalog",
23+
"Magento_Catalog::inventory"
24+
);
25+
}
26+
}

0 commit comments

Comments
 (0)