Skip to content

Commit 82829a5

Browse files
authored
Merge pull request #1077 from bohdan-harniuk/600-config-autocompletion
600: Developed configurations autocompletion
2 parents 351281b + 32d53d9 commit 82829a5

File tree

21 files changed

+1624
-39
lines changed

21 files changed

+1624
-39
lines changed

resources/META-INF/plugin.xml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,11 @@
178178
<projectService serviceImplementation="com.magento.idea.magento2plugin.project.Settings"/>
179179
<projectService serviceImplementation="com.magento.idea.magento2uct.settings.UctSettingsService"/>
180180

181-
<completion.contributor language="XML" implementationClass="com.magento.idea.magento2plugin.completion.xml.XmlCompletionContributor" id="xml" />
182-
<completion.contributor language="PHP" implementationClass="com.magento.idea.magento2plugin.completion.php.PhpCompletionContributor" id="php" />
181+
<completion.contributor language="XML" implementationClass="com.magento.idea.magento2plugin.completion.xml.XmlCompletionContributor" id="xml"/>
182+
<completion.contributor language="PHP" implementationClass="com.magento.idea.magento2plugin.completion.php.PhpCompletionContributor" id="php"/>
183+
184+
<completion.contributor language="XML" implementationClass="com.magento.idea.magento2plugin.completion.xml.SystemConfigurationContributor" id="magentoSystemConfiguration"/>
185+
<xml.tagNameProvider implementation="com.magento.idea.magento2plugin.completion.provider.xml.ConfigXmlTagNameProvider"/>
183186

184187
<psi.referenceContributor language="XML" implementation="com.magento.idea.magento2plugin.reference.xml.XmlReferenceContributor"/>
185188
<psi.referenceContributor language="PHP" implementation="com.magento.idea.magento2plugin.reference.php.PhpReferenceContributor"/>
@@ -209,6 +212,9 @@
209212
<fileBasedIndex implementation="com.magento.idea.magento2plugin.stubs.indexes.xml.DeclarativeSchemaElementsIndex" />
210213
<fileBasedIndex implementation="com.magento.idea.magento2plugin.stubs.indexes.xml.UIComponentIndex" />
211214
<fileBasedIndex implementation="com.magento.idea.magento2plugin.stubs.indexes.xml.ProductTypeIndex" />
215+
<fileBasedIndex implementation="com.magento.idea.magento2plugin.stubs.indexes.xml.SystemXmlSectionIndex" />
216+
<fileBasedIndex implementation="com.magento.idea.magento2plugin.stubs.indexes.xml.SystemXmlGroupIndex" />
217+
<fileBasedIndex implementation="com.magento.idea.magento2plugin.stubs.indexes.xml.SystemXmlFieldIndex" />
212218

213219
<codeInsight.lineMarkerProvider language="PHP" implementationClass="com.magento.idea.magento2plugin.linemarker.php.PluginLineMarkerProvider"/>
214220
<codeInsight.lineMarkerProvider language="PHP" implementationClass="com.magento.idea.magento2plugin.linemarker.php.PluginTargetLineMarkerProvider"/>
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
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.xml;
7+
8+
import static com.magento.idea.magento2plugin.magento.files.ModuleConfigXmlFile.FILE_NAME;
9+
import static com.magento.idea.magento2plugin.magento.files.ModuleConfigXmlFile.ROOT_TAG_NAME;
10+
11+
import com.intellij.codeInsight.lookup.LookupElement;
12+
import com.intellij.openapi.project.Project;
13+
import com.intellij.psi.xml.XmlFile;
14+
import com.intellij.psi.xml.XmlTag;
15+
import com.intellij.xml.XmlTagNameProvider;
16+
import com.magento.idea.magento2plugin.project.Settings;
17+
import java.util.ArrayList;
18+
import java.util.Collections;
19+
import java.util.List;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.jetbrains.annotations.Nullable;
22+
23+
public class ConfigXmlTagNameProvider implements XmlTagNameProvider {
24+
25+
@Override
26+
public void addTagNameVariants(
27+
final List<LookupElement> elements,
28+
final @NotNull XmlTag tag,
29+
final String prefix
30+
) {
31+
if (!Settings.isEnabled(tag.getProject()) || !validate(tag)) {
32+
return;
33+
}
34+
final int tagDepth = getTagDepth(tag);
35+
36+
if (tagDepth == -1) {
37+
return;
38+
}
39+
final ConfigTagScope tagScope = getTagScope(tag);
40+
41+
if (tagScope == null) {
42+
return;
43+
}
44+
final ConfigTagTypeByScopedDepth configType = ConfigTagTypeByScopedDepth
45+
.getTagTypeByScopeAndDepth(tagDepth, tagScope);
46+
47+
if (configType == null) {
48+
return;
49+
}
50+
final String indexKey = configType.getConfigPath(tag);
51+
final String phrase = indexKey.isEmpty() ? prefix : indexKey + "." + prefix;
52+
53+
elements.addAll(configType.makeCompletion(phrase, tag.getProject()));
54+
}
55+
56+
private boolean validate(final @NotNull XmlTag tag) {
57+
if (!(tag.getContainingFile() instanceof XmlFile)) {
58+
return false;
59+
}
60+
final XmlFile file = (XmlFile) tag.getContainingFile();
61+
final XmlTag rootTag = file.getRootTag();
62+
63+
if (!FILE_NAME.equals(file.getName())
64+
|| rootTag == null
65+
|| !ROOT_TAG_NAME.equals(rootTag.getName())) {
66+
return false;
67+
}
68+
final int tagDepth = getTagDepth(tag);
69+
70+
return tagDepth >= 2 && tagDepth <= 5;
71+
}
72+
73+
private int getTagDepth(final @NotNull XmlTag tag) {
74+
if (!(tag.getContainingFile() instanceof XmlFile)) {
75+
return -1;
76+
}
77+
final XmlFile file = (XmlFile) tag.getContainingFile();
78+
final XmlTag rootTag = file.getRootTag();
79+
80+
if (rootTag == null) {
81+
return -1;
82+
}
83+
int depth = 0;
84+
XmlTag iterable = tag;
85+
86+
while (!iterable.equals(rootTag)) {
87+
depth++;
88+
iterable = iterable.getParentTag();
89+
90+
if (iterable == null) {
91+
break;
92+
}
93+
}
94+
95+
return depth;
96+
}
97+
98+
private @Nullable ConfigTagScope getTagScope(final @NotNull XmlTag tag) {
99+
if (!(tag.getContainingFile() instanceof XmlFile)) {
100+
return null;
101+
}
102+
final XmlFile file = (XmlFile) tag.getContainingFile();
103+
final XmlTag rootTag = file.getRootTag();
104+
105+
if (rootTag == null) {
106+
return null;
107+
}
108+
XmlTag scopeTag = null;
109+
XmlTag iterable = tag;
110+
111+
while (!iterable.equals(rootTag)) {
112+
scopeTag = iterable;
113+
iterable = iterable.getParentTag();
114+
115+
if (iterable == null) {
116+
break;
117+
}
118+
}
119+
120+
if (scopeTag == null) {
121+
return null;
122+
}
123+
124+
return ConfigTagScope.getScopeByTagName(scopeTag.getName());
125+
}
126+
127+
private enum ConfigTagTypeByScopedDepth {
128+
129+
SECTION(2, 3, 3, 0),
130+
GROUP(3, 4, 4, 1),
131+
FIELD(4, 5, 5, 2);
132+
133+
private final int defaultScope;
134+
private final int websitesScope;
135+
private final int storesScope;
136+
private final int fallbackDepth;
137+
138+
ConfigTagTypeByScopedDepth(
139+
final int defaultScope,
140+
final int websitesScope,
141+
final int storesScope,
142+
final int fallbackDepth
143+
) {
144+
this.defaultScope = defaultScope;
145+
this.websitesScope = websitesScope;
146+
this.storesScope = storesScope;
147+
this.fallbackDepth = fallbackDepth;
148+
}
149+
150+
public static ConfigTagTypeByScopedDepth getTagTypeByScopeAndDepth(
151+
final int depth,
152+
final ConfigTagScope scope
153+
) {
154+
for (final ConfigTagTypeByScopedDepth tagType : ConfigTagTypeByScopedDepth.values()) {
155+
int depthForScope = -1;
156+
157+
if (ConfigTagScope.DEFAULT.equals(scope)) {
158+
depthForScope = tagType.defaultScope;
159+
} else if (ConfigTagScope.WEBSITES.equals(scope)) {
160+
depthForScope = tagType.websitesScope;
161+
} else if (ConfigTagScope.STORES.equals(scope)) {
162+
depthForScope = tagType.storesScope;
163+
}
164+
165+
if (depth == depthForScope) {
166+
return tagType;
167+
}
168+
}
169+
170+
return null;
171+
}
172+
173+
public @NotNull String getConfigPath(final @NotNull XmlTag tag) {
174+
final List<String> parts = new ArrayList<>();
175+
XmlTag iterable = tag.getParentTag();
176+
177+
for (int i = 0; i < fallbackDepth; i++) {
178+
if (iterable != null) {
179+
parts.add(iterable.getName());
180+
iterable = iterable.getParentTag();
181+
}
182+
}
183+
184+
if (parts.isEmpty()) {
185+
return "";
186+
}
187+
Collections.reverse(parts);
188+
189+
return String.join(".", parts);
190+
}
191+
192+
public List<LookupElement> makeCompletion(
193+
final @NotNull String phrase,
194+
final @NotNull Project project
195+
) {
196+
if (this.equals(ConfigTagTypeByScopedDepth.SECTION)) {
197+
return SectionNameCompletionProvider.makeCompletion(phrase, project);
198+
} else if (this.equals(ConfigTagTypeByScopedDepth.GROUP)) {
199+
return GroupNameCompletionProvider.makeCompletion(phrase, project);
200+
} else if (this.equals(ConfigTagTypeByScopedDepth.FIELD)) {
201+
return FieldNameCompletionProvider.makeCompletion(phrase, project);
202+
}
203+
204+
return new ArrayList<>();
205+
}
206+
}
207+
208+
private enum ConfigTagScope {
209+
210+
DEFAULT("default"),
211+
WEBSITES("websites"),
212+
STORES("stores");
213+
214+
private final String scopeTagName;
215+
216+
ConfigTagScope(final String scopeTagName) {
217+
this.scopeTagName = scopeTagName;
218+
}
219+
220+
public static ConfigTagScope getScopeByTagName(final @NotNull String tagName) {
221+
for (final ConfigTagScope scope : ConfigTagScope.values()) {
222+
if (tagName.equals(scope.scopeTagName)) {
223+
return scope;
224+
}
225+
}
226+
227+
return null;
228+
}
229+
}
230+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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.xml;
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.LookupElement;
12+
import com.intellij.codeInsight.lookup.LookupElementBuilder;
13+
import com.intellij.openapi.project.Project;
14+
import com.intellij.psi.PsiElement;
15+
import com.intellij.psi.search.GlobalSearchScope;
16+
import com.intellij.psi.util.PsiTreeUtil;
17+
import com.intellij.psi.xml.XmlTag;
18+
import com.intellij.util.ProcessingContext;
19+
import com.intellij.util.indexing.FileBasedIndex;
20+
import com.magento.idea.magento2plugin.MagentoIcons;
21+
import com.magento.idea.magento2plugin.project.Settings;
22+
import com.magento.idea.magento2plugin.stubs.indexes.xml.SystemXmlFieldIndex;
23+
import com.magento.idea.magento2plugin.util.magento.xml.SystemConfigurationParserUtil;
24+
import java.util.ArrayList;
25+
import java.util.Collection;
26+
import java.util.List;
27+
import java.util.concurrent.atomic.AtomicInteger;
28+
import org.jetbrains.annotations.NotNull;
29+
30+
public class FieldNameCompletionProvider extends CompletionProvider<CompletionParameters> {
31+
32+
@Override
33+
protected void addCompletions(
34+
final @NotNull CompletionParameters parameters,
35+
final @NotNull ProcessingContext context,
36+
final @NotNull CompletionResultSet result
37+
) {
38+
final PsiElement position = parameters.getPosition().getOriginalElement();
39+
40+
if (position == null || !Settings.isEnabled(position.getProject())) {
41+
return;
42+
}
43+
final XmlTag startingTag = PsiTreeUtil.getParentOfType(
44+
position,
45+
XmlTag.class
46+
);
47+
48+
if (startingTag == null) {
49+
return;
50+
}
51+
final String currentPath = SystemConfigurationParserUtil
52+
.parseOuterConfigPath(
53+
startingTag,
54+
SystemConfigurationParserUtil.ParsingDepth.GROUP_ID
55+
);
56+
57+
if (currentPath == null) {
58+
return;
59+
}
60+
final String prefix = result.getPrefixMatcher().getPrefix().trim();
61+
final String capture = currentPath + "." + prefix;
62+
63+
for (final LookupElement element : makeCompletion(capture, position.getProject())) {
64+
result.addElement(element);
65+
}
66+
}
67+
68+
/**
69+
* Make completion for fields.
70+
*
71+
* @param phrase String
72+
* @param project Project
73+
*
74+
* @return List[LookupElement]
75+
*/
76+
public static List<LookupElement> makeCompletion(
77+
final @NotNull String phrase,
78+
final @NotNull Project project
79+
) {
80+
final List<LookupElement> result = new ArrayList<>();
81+
final FileBasedIndex index = FileBasedIndex.getInstance();
82+
final Collection<String> allKeys = index.getAllKeys(SystemXmlFieldIndex.KEY, project);
83+
final AtomicInteger resultsCounter = new AtomicInteger(0);
84+
85+
allKeys.stream()
86+
.filter(input -> input.startsWith(phrase) && !input.equals(phrase))
87+
.takeWhile(input -> resultsCounter.get() < 5)
88+
.forEach(
89+
input -> {
90+
if (index.getValues(
91+
SystemXmlFieldIndex.KEY,
92+
input,
93+
GlobalSearchScope.allScope(project)).isEmpty()
94+
) {
95+
return;
96+
}
97+
final String[] pathParts = input.split("\\.");
98+
99+
if (pathParts.length != 3) { //NOPMD
100+
return;
101+
}
102+
final String fieldId = pathParts[2];
103+
result.add(
104+
LookupElementBuilder.create(fieldId)
105+
.withIcon(MagentoIcons.MODULE)
106+
);
107+
resultsCounter.incrementAndGet();
108+
}
109+
);
110+
111+
return result;
112+
}
113+
}

0 commit comments

Comments
 (0)