Skip to content

Commit de50d5d

Browse files
committed
feature: add support for completing template tags based on imported components
1 parent cb968dc commit de50d5d

File tree

5 files changed

+181
-122
lines changed

5 files changed

+181
-122
lines changed
Lines changed: 16 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,17 @@
11
package com.axellience.vuegwtplugin;
22

3-
import static com.axellience.vuegwtplugin.util.VueGWTPluginUtil.COMPONENT_QUALIFIED_NAME;
4-
import static com.axellience.vuegwtplugin.util.VueGWTPluginUtil.findHtmlTemplate;
5-
63
import com.axellience.vuegwtplugin.language.htmltemplate.HtmlTemplateLanguage;
4+
import com.axellience.vuegwtplugin.util.VueGWTComponentAnnotationUtil;
75
import com.axellience.vuegwtplugin.util.VueGWTPluginUtil;
86
import com.intellij.lang.ASTNode;
9-
import com.intellij.openapi.project.Project;
107
import com.intellij.openapi.util.TextRange;
11-
import com.intellij.psi.PsiAnnotation;
12-
import com.intellij.psi.PsiAnnotationMemberValue;
13-
import com.intellij.psi.PsiArrayInitializerMemberValue;
14-
import com.intellij.psi.PsiClass;
15-
import com.intellij.psi.PsiClassObjectAccessExpression;
16-
import com.intellij.psi.PsiClassType;
178
import com.intellij.psi.PsiFile;
18-
import com.intellij.psi.PsiJavaFile;
19-
import com.intellij.psi.PsiManager;
20-
import com.intellij.psi.impl.PsiImplUtil;
219
import com.intellij.psi.impl.source.xml.SchemaPrefix;
2210
import com.intellij.psi.impl.source.xml.TagNameReference;
2311
import com.intellij.psi.xml.XmlAttribute;
2412
import com.intellij.psi.xml.XmlDocument;
25-
import com.intellij.psi.xml.XmlFile;
2613
import com.intellij.psi.xml.XmlTag;
2714
import com.intellij.xml.HtmlXmlExtension;
28-
import java.util.List;
29-
import java.util.Optional;
3015
import org.jetbrains.annotations.NotNull;
3116
import org.jetbrains.annotations.Nullable;
3217

@@ -38,8 +23,7 @@ public boolean isAvailable(PsiFile psiFile) {
3823
return false;
3924
}
4025

41-
Optional<PsiJavaFile> optionalPsiJavaFile = getJavaFileForTemplate(psiFile);
42-
return optionalPsiJavaFile.isPresent();
26+
return VueGWTPluginUtil.findJavaFromTemplate(psiFile).isPresent();
4327
}
4428

4529
@Override
@@ -84,110 +68,26 @@ public String[][] getNamespacesFromDocument(XmlDocument parent, boolean declarat
8468
@Nullable
8569
@Override
8670
public TagNameReference createTagNameReference(ASTNode astNode, boolean b) {
87-
PsiFile templateFile = astNode.getTreeParent().getPsi().getContainingFile();
88-
89-
Optional<PsiJavaFile> optionalPsiJavaFile = getJavaFileForTemplate(templateFile);
90-
if (!optionalPsiJavaFile.isPresent()) {
91-
return super.createTagNameReference(astNode, b);
92-
}
93-
94-
PsiJavaFile psiJavaFile = optionalPsiJavaFile.get();
9571
String tagName = astNode.getText(); // Tag name of the current element
9672

97-
return getComponentAnnotationFromJavaFile(psiJavaFile)
98-
.flatMap(annotation -> getComponentTemplateFromAnnotation(annotation, tagName))
99-
.map(componentTemplate ->
100-
(TagNameReference) new VueGWTTagNameReference(astNode, componentTemplate, b))
101-
.orElse(super.createTagNameReference(astNode, b));
102-
}
103-
104-
private Optional<PsiAnnotation> getComponentAnnotationFromJavaFile(PsiJavaFile psiJavaFile) {
105-
for (PsiClass psiClass : psiJavaFile.getClasses()) {
106-
PsiAnnotation[] annotations = psiClass.getAnnotations();
107-
for (PsiAnnotation annotation : annotations) {
108-
if (COMPONENT_QUALIFIED_NAME.equals(annotation.getQualifiedName())) {
109-
return Optional.of(annotation);
110-
}
111-
}
112-
}
113-
114-
return Optional.empty();
115-
}
116-
117-
private Optional<PsiFile> getComponentTemplateFromAnnotation(PsiAnnotation componentAnnotation,
118-
String tagName) {
119-
PsiAnnotationMemberValue componentsValue =
120-
PsiImplUtil.findAttributeValue(componentAnnotation, "components");
121-
122-
if (componentsValue instanceof PsiClassObjectAccessExpression) {
123-
return getComponentTemplateForClassObjectAccess(
124-
(PsiClassObjectAccessExpression) componentsValue, tagName);
125-
}
126-
127-
if (componentsValue instanceof PsiArrayInitializerMemberValue) {
128-
return getComponentTemplateFromArrayInitializerMemberValue(
129-
(PsiArrayInitializerMemberValue) componentsValue, tagName);
130-
}
131-
132-
return Optional.empty();
133-
}
134-
135-
private Optional<PsiFile> getComponentTemplateFromArrayInitializerMemberValue(
136-
PsiArrayInitializerMemberValue arrayInitializer, String tagName) {
137-
for (PsiAnnotationMemberValue initializer : arrayInitializer.getInitializers()) {
138-
if (!(initializer instanceof PsiClassObjectAccessExpression)) {
139-
continue;
140-
}
141-
142-
Optional<PsiFile> optionalComponentTemplate = getComponentTemplateForClassObjectAccess(
143-
(PsiClassObjectAccessExpression) initializer, tagName);
144-
if (optionalComponentTemplate.isPresent()) {
145-
return optionalComponentTemplate;
146-
}
147-
}
148-
149-
return Optional.empty();
150-
}
151-
152-
private Optional<PsiFile> getComponentTemplateForClassObjectAccess(
153-
PsiClassObjectAccessExpression componentsClassAccess, String tagName) {
154-
// classType is Class<MyComponent>
155-
PsiClassType classType = (PsiClassType) PsiImplUtil.getType(componentsClassAccess);
156-
157-
// componentClassType is MyComponent
158-
PsiClassType componentClassType = (PsiClassType) classType.getParameters()[0];
159-
String componentTagName = VueGWTPluginUtil.componentToTagName(componentClassType);
160-
if (!tagName.equals(componentTagName)) {
161-
return Optional.empty();
162-
}
163-
PsiClass componentClass = componentClassType.resolve();
164-
if (componentClass == null) {
165-
return Optional.empty();
166-
}
167-
168-
return findHtmlTemplate(componentClass.getContainingFile());
73+
PsiFile templateFile = astNode.getTreeParent().getPsi().getContainingFile();
74+
return
75+
VueGWTPluginUtil
76+
.findJavaFromTemplate(templateFile)
77+
.flatMap(VueGWTComponentAnnotationUtil::getComponentAnnotationFromJavaFile)
78+
.flatMap(
79+
annotation -> VueGWTComponentAnnotationUtil
80+
.getImportedComponentTemplateFromAnnotationComponent(annotation, tagName)
81+
)
82+
.map(
83+
componentTemplate ->
84+
(TagNameReference) new VueGWTTagNameReference(astNode, componentTemplate, b)
85+
)
86+
.orElse(super.createTagNameReference(astNode, b));
16987
}
17088

17189
@Override
17290
public boolean isSelfClosingTagAllowed(@NotNull XmlTag tag) {
17391
return true;
17492
}
175-
176-
private Optional<PsiJavaFile> getJavaFileForTemplate(PsiFile templateFile) {
177-
// Get the Java file for the template
178-
Optional<PsiFile> optionalJavaFile = VueGWTPluginUtil.findJavaFromTemplate(templateFile);
179-
if (!optionalJavaFile.isPresent()) {
180-
return Optional.empty();
181-
}
182-
183-
// Find the project for the current file
184-
Project project = templateFile.getProject();
185-
PsiFile file = PsiManager.getInstance(project)
186-
.findFile(optionalJavaFile.get().getVirtualFile());
187-
if (!(file instanceof PsiJavaFile)) {
188-
return Optional.empty();
189-
}
190-
191-
return Optional.of((PsiJavaFile) file);
192-
}
19393
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.axellience.vuegwtplugin.tags;
2+
3+
import com.axellience.vuegwtplugin.VueGWTIcons;
4+
import com.axellience.vuegwtplugin.util.VueGWTComponentAnnotationUtil;
5+
import com.axellience.vuegwtplugin.util.VueGWTPluginUtil;
6+
import com.intellij.codeInsight.lookup.LookupElement;
7+
import com.intellij.codeInsight.lookup.LookupElementBuilder;
8+
import com.intellij.psi.PsiClass;
9+
import com.intellij.psi.PsiClassType;
10+
import com.intellij.psi.PsiJavaFile;
11+
import com.intellij.psi.SmartPointerManager;
12+
import com.intellij.psi.xml.XmlTag;
13+
import com.intellij.xml.XmlTagNameProvider;
14+
import java.util.List;
15+
import java.util.Optional;
16+
import java.util.stream.Collectors;
17+
import kotlin.Pair;
18+
import org.jetbrains.annotations.NotNull;
19+
20+
public class VueGWTTagProvider implements XmlTagNameProvider {
21+
22+
@Override
23+
public void addTagNameVariants(List<LookupElement> elements, @NotNull XmlTag tag, String prefix) {
24+
Optional<PsiJavaFile> optionalComponentJavaFile = VueGWTPluginUtil
25+
.findJavaFromTemplate(tag.getContainingFile().getOriginalFile());
26+
27+
optionalComponentJavaFile
28+
.flatMap(VueGWTComponentAnnotationUtil::getComponentAnnotationFromJavaFile)
29+
.map(VueGWTComponentAnnotationUtil::getImportedComponentsClassTypeFromComponentAnnotation)
30+
.ifPresent(
31+
set -> elements
32+
.addAll(set.stream().map(this::createLookupElement).collect(Collectors.toList()))
33+
);
34+
}
35+
36+
private LookupElement createLookupElement(PsiClassType componentClassType) {
37+
PsiClass componentClass = componentClassType.resolve();
38+
if (componentClass == null) {
39+
return null;
40+
}
41+
42+
return LookupElementBuilder
43+
.create(
44+
new Pair<>(
45+
componentClass.getName(),
46+
SmartPointerManager
47+
.getInstance(componentClass.getProject())
48+
.createSmartPsiElementPointer(componentClass)),
49+
VueGWTPluginUtil.componentToTagName(componentClassType)
50+
)
51+
.withBoldness(true)
52+
.withTypeText(componentClass.getQualifiedName())
53+
.withIcon(VueGWTIcons.VUE);
54+
}
55+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.axellience.vuegwtplugin.util;
2+
3+
import static com.axellience.vuegwtplugin.util.VueGWTPluginUtil.COMPONENT_QUALIFIED_NAME;
4+
5+
import com.intellij.psi.PsiAnnotation;
6+
import com.intellij.psi.PsiAnnotationMemberValue;
7+
import com.intellij.psi.PsiArrayInitializerMemberValue;
8+
import com.intellij.psi.PsiClass;
9+
import com.intellij.psi.PsiClassObjectAccessExpression;
10+
import com.intellij.psi.PsiClassType;
11+
import com.intellij.psi.PsiFile;
12+
import com.intellij.psi.PsiJavaFile;
13+
import com.intellij.psi.impl.PsiImplUtil;
14+
import java.util.Arrays;
15+
import java.util.Collections;
16+
import java.util.Optional;
17+
import java.util.Set;
18+
import java.util.stream.Collectors;
19+
20+
public class VueGWTComponentAnnotationUtil {
21+
22+
public static Optional<PsiAnnotation> getComponentAnnotationFromJavaFile(
23+
PsiJavaFile psiJavaFile) {
24+
for (PsiClass psiClass : psiJavaFile.getClasses()) {
25+
PsiAnnotation[] annotations = psiClass.getAnnotations();
26+
for (PsiAnnotation annotation : annotations) {
27+
if (COMPONENT_QUALIFIED_NAME.equals(annotation.getQualifiedName())) {
28+
return Optional.of(annotation);
29+
}
30+
}
31+
}
32+
33+
return Optional.empty();
34+
}
35+
36+
public static Optional<PsiFile> getImportedComponentTemplateFromAnnotationComponent(
37+
PsiAnnotation componentAnnotation,
38+
String tagName) {
39+
40+
Set<PsiClassType> importedComponentsClassType = getImportedComponentsClassTypeFromComponentAnnotation(
41+
componentAnnotation);
42+
43+
return importedComponentsClassType.stream()
44+
.filter(psiClassType -> VueGWTPluginUtil.componentToTagName(psiClassType).equals(tagName))
45+
.findFirst()
46+
.map(PsiClassType::resolve)
47+
.map(PsiClass::getContainingFile)
48+
.flatMap(VueGWTPluginUtil::findHtmlTemplate);
49+
}
50+
51+
public static Set<PsiClassType> getImportedComponentsClassTypeFromComponentAnnotation(
52+
PsiAnnotation componentAnnotation) {
53+
PsiAnnotationMemberValue componentsValue =
54+
PsiImplUtil.findAttributeValue(componentAnnotation, "components");
55+
56+
if (componentsValue instanceof PsiClassObjectAccessExpression) {
57+
return Collections.singleton(getComponentClassTypeFromClassObjectAccessExpression(
58+
(PsiClassObjectAccessExpression) componentsValue));
59+
}
60+
61+
if (componentsValue instanceof PsiArrayInitializerMemberValue) {
62+
return getComponentsClassTypeFromPsiArrayInitializerMemberValue(
63+
(PsiArrayInitializerMemberValue) componentsValue);
64+
}
65+
66+
return Collections.emptySet();
67+
}
68+
69+
public static PsiClassType getComponentClassTypeFromClassObjectAccessExpression(
70+
PsiClassObjectAccessExpression componentsClassAccess) {
71+
// classType is Class<MyComponent>
72+
PsiClassType classType = (PsiClassType) PsiImplUtil.getType(componentsClassAccess);
73+
74+
// componentClassType is MyComponent
75+
return (PsiClassType) classType.getParameters()[0];
76+
}
77+
78+
public static Set<PsiClassType> getComponentsClassTypeFromPsiArrayInitializerMemberValue(
79+
PsiArrayInitializerMemberValue arrayInitializer) {
80+
return Arrays.stream(arrayInitializer.getInitializers())
81+
.map(PsiClassObjectAccessExpression.class::cast)
82+
.map(VueGWTComponentAnnotationUtil::getComponentClassTypeFromClassObjectAccessExpression)
83+
.collect(Collectors.toSet());
84+
}
85+
}

src/main/java/com/axellience/vuegwtplugin/util/VueGWTPluginUtil.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
import static com.intellij.psi.impl.PsiImplUtil.findAttributeValue;
44

55
import com.google.common.base.CaseFormat;
6+
import com.intellij.openapi.project.Project;
67
import com.intellij.psi.PsiAnnotation;
78
import com.intellij.psi.PsiAnnotationMemberValue;
89
import com.intellij.psi.PsiClassType;
910
import com.intellij.psi.PsiDirectory;
1011
import com.intellij.psi.PsiFile;
12+
import com.intellij.psi.PsiJavaFile;
13+
import com.intellij.psi.PsiManager;
1114
import java.util.Arrays;
1215
import java.util.Optional;
1316

@@ -32,18 +35,32 @@ public static Optional<PsiFile> findHtmlTemplate(PsiFile javaFile) {
3235
return findFileWithName(parentDirectory, templateName);
3336
}
3437

35-
public static String getTemplateNameFrom(PsiFile javaFile) {
36-
return javaFile.getName().replaceAll("(.*)\\.java", "$1.html");
37-
}
38-
39-
public static Optional<PsiFile> findJavaFromTemplate(PsiFile templateFile) {
38+
public static Optional<PsiJavaFile> findJavaFromTemplate(PsiFile templateFile) {
4039
PsiDirectory parentDirectory = templateFile.getContainingDirectory();
4140
if (parentDirectory == null) {
4241
return Optional.empty();
4342
}
4443

4544
String javaName = getJavaNameFromTemplate(templateFile);
46-
return findFileWithName(parentDirectory, javaName);
45+
Optional<PsiFile> optionalJavaFile = findFileWithName(parentDirectory, javaName);
46+
47+
if (!optionalJavaFile.isPresent()) {
48+
return Optional.empty();
49+
}
50+
51+
// Find the project for the current file
52+
Project project = templateFile.getProject();
53+
PsiFile file = PsiManager.getInstance(project)
54+
.findFile(optionalJavaFile.get().getVirtualFile());
55+
if (!(file instanceof PsiJavaFile)) {
56+
return Optional.empty();
57+
}
58+
59+
return Optional.of((PsiJavaFile) file);
60+
}
61+
62+
public static String getTemplateNameFrom(PsiFile javaFile) {
63+
return javaFile.getName().replaceAll("(.*)\\.java", "$1.html");
4764
}
4865

4966
public static String getJavaNameFromTemplate(PsiFile templateFile) {

src/main/resources/META-INF/plugin.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@
7474

7575

7676
<xml.xmlExtension implementation="com.axellience.vuegwtplugin.VueGWTXmlExtension"/>
77+
<xml.tagNameProvider implementation="com.axellience.vuegwtplugin.tags.VueGWTTagProvider"/>
78+
7779
<fileTypeFactory
7880
implementation="com.axellience.vuegwtplugin.language.VueGWTFileTypeFactory"/>
7981

0 commit comments

Comments
 (0)