Skip to content

Commit d1e96d7

Browse files
committed
provide twig variable detection on doc blocks {# variable \Foo\Class #}, currently parser support inline blocks and global "app" Haehnchen#31
1 parent 260966c commit d1e96d7

File tree

5 files changed

+224
-3
lines changed

5 files changed

+224
-3
lines changed

src/fr/adrienbrault/idea/symfony2plugin/TwigHelper.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,15 @@ public static ElementPattern<PsiElement> getPathAfterLeafPattern() {
303303
.withLanguage(TwigLanguage.INSTANCE);
304304
}
305305

306+
public static ElementPattern<PsiElement> getTypeCompletionPattern() {
307+
return PlatformPatterns
308+
.psiElement(TwigTokenTypes.IDENTIFIER)
309+
.afterLeaf(
310+
PlatformPatterns.psiElement(TwigTokenTypes.DOT)
311+
)
312+
.withLanguage(TwigLanguage.INSTANCE);
313+
}
314+
306315
public static ElementPattern<PsiElement> getRoutePattern() {
307316
return PlatformPatterns
308317
.psiElement(TwigTokenTypes.IDENTIFIER).withText("path")
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package fr.adrienbrault.idea.symfony2plugin.templating;
2+
3+
import com.jetbrains.php.completion.PhpLookupElement;
4+
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
5+
import org.jetbrains.annotations.NotNull;
6+
7+
import java.util.regex.Pattern;
8+
9+
10+
public class PhpTwigMethodLookupElement extends PhpLookupElement {
11+
12+
public PhpTwigMethodLookupElement(@NotNull PhpNamedElement namedElement) {
13+
super(namedElement);
14+
}
15+
16+
@NotNull
17+
public String getLookupString() {
18+
String lookupString = super.getLookupString();
19+
20+
// remove getter and set lcfirst
21+
if(lookupString.startsWith("get") && lookupString.length() > 3) {
22+
lookupString = lookupString.substring(3);
23+
lookupString = Character.toLowerCase(lookupString.charAt(0)) + lookupString.substring(1);
24+
}
25+
26+
return lookupString;
27+
}
28+
29+
}

src/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import com.intellij.psi.PsiElement;
1010
import com.intellij.psi.PsiWhiteSpace;
1111
import com.intellij.util.ProcessingContext;
12+
import com.jetbrains.php.lang.psi.elements.Method;
13+
import com.jetbrains.php.lang.psi.elements.PhpClass;
14+
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
1215
import com.jetbrains.twig.TwigFile;
1316
import com.jetbrains.twig.TwigLanguage;
1417
import com.jetbrains.twig.TwigTokenTypes;
@@ -21,6 +24,7 @@
2124
import fr.adrienbrault.idea.symfony2plugin.routing.RouteLookupElement;
2225
import fr.adrienbrault.idea.symfony2plugin.templating.dict.*;
2326
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigExtensionParser;
27+
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigTypeResolveUtil;
2428
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
2529
import fr.adrienbrault.idea.symfony2plugin.translation.TranslationIndex;
2630
import fr.adrienbrault.idea.symfony2plugin.translation.TranslatorLookupElement;
@@ -317,6 +321,34 @@ public void addCompletions(@NotNull CompletionParameters parameters,
317321
new PathParameterCompletionProvider()
318322
);
319323

324+
// routing completion like path() function
325+
extend(
326+
CompletionType.BASIC,
327+
TwigHelper.getTypeCompletionPattern(),
328+
new TypeCompletionProvider()
329+
);
330+
331+
}
332+
333+
private class TypeCompletionProvider extends CompletionProvider<CompletionParameters> {
334+
335+
@Override
336+
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext paramProcessingContext, @NotNull CompletionResultSet resultSet) {
337+
PsiElement psiElement = parameters.getOriginalPosition();
338+
String[] possibleTypes = TwigTypeResolveUtil.formatPsiTypeName(psiElement);
339+
340+
// find core function for that
341+
for(PhpNamedElement phpNamedElement: TwigTypeResolveUtil.resolveTwigMethodName(psiElement, possibleTypes)) {
342+
if(phpNamedElement instanceof PhpClass) {
343+
for(Method method: ((PhpClass) phpNamedElement).getMethods()) {
344+
if(!(method.getName().startsWith("set") || method.getName().startsWith("__"))) {
345+
resultSet.addElement(new PhpTwigMethodLookupElement(method));
346+
}
347+
}
348+
}
349+
}
350+
351+
}
320352

321353
}
322354

src/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateGoToLocalDeclarationHandler.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
import com.intellij.patterns.PlatformPatterns;
77
import com.intellij.psi.PsiElement;
88
import com.intellij.psi.util.PsiTreeUtil;
9-
import com.jetbrains.php.PhpIndex;
10-
import com.jetbrains.php.lang.psi.elements.Method;
119
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
12-
import com.jetbrains.twig.*;
10+
import com.jetbrains.twig.TwigFile;
11+
import com.jetbrains.twig.TwigLanguage;
12+
import com.jetbrains.twig.TwigTokenTypes;
1313
import com.jetbrains.twig.elements.TwigElementTypes;
1414
import com.jetbrains.twig.elements.TwigTagWithFileReference;
1515
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
@@ -18,6 +18,7 @@
1818
import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigMacro;
1919
import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigSet;
2020
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigExtensionParser;
21+
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigTypeResolveUtil;
2122
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
2223
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
2324
import fr.adrienbrault.idea.symfony2plugin.util.RegexPsiElementFilter;
@@ -70,9 +71,18 @@ public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int i, Edit
7071
psiElements.addAll(Arrays.asList(this.getFunctions(psiElement)));
7172
}
7273

74+
if(TwigHelper.getTypeCompletionPattern().accepts(psiElement)) {
75+
psiElements.addAll(Arrays.asList(this.getTypeGoto(psiElement)));
76+
}
77+
7378
return psiElements.toArray(new PsiElement[psiElements.size()]);
7479
}
7580

81+
private PsiElement[] getTypeGoto(PsiElement psiElement) {
82+
Collection<? extends PhpNamedElement> types = TwigTypeResolveUtil.resolveTwigMethodName(psiElement, TwigTypeResolveUtil.formatPsiTypeName(psiElement));
83+
return types.toArray(new PsiElement[types.size()]);
84+
}
85+
7686
private PsiElement[] getFunctions(PsiElement psiElement) {
7787
HashMap<String, TwigExtension> functions = new TwigExtensionParser(psiElement.getProject()).getFunctions();
7888

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package fr.adrienbrault.idea.symfony2plugin.templating.util;
2+
3+
import com.intellij.openapi.util.Condition;
4+
import com.intellij.patterns.PlatformPatterns;
5+
import com.intellij.psi.PsiComment;
6+
import com.intellij.psi.PsiElement;
7+
import com.intellij.psi.PsiWhiteSpace;
8+
import com.intellij.psi.util.PsiTreeUtil;
9+
import com.jetbrains.php.lang.psi.elements.Method;
10+
import com.jetbrains.php.lang.psi.elements.PhpClass;
11+
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
12+
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
13+
import com.jetbrains.twig.elements.TwigCompositeElement;
14+
import com.jetbrains.twig.elements.TwigElementTypes;
15+
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
16+
17+
import java.util.ArrayList;
18+
import java.util.Collection;
19+
import java.util.HashMap;
20+
import java.util.regex.Matcher;
21+
import java.util.regex.Pattern;
22+
23+
public class TwigTypeResolveUtil {
24+
25+
public static String[] formatPsiTypeName(PsiElement psiElement) {
26+
27+
String typeNames = PhpElementsUtil.getPrevSiblingAsTextUntil(psiElement, PlatformPatterns.psiElement(PsiWhiteSpace.class)).trim();
28+
if(typeNames.endsWith(".")) {
29+
typeNames = typeNames.substring(0, typeNames.length() -1);
30+
}
31+
32+
String[] possibleTypes;
33+
if(typeNames.contains(".")) {
34+
possibleTypes = typeNames.split("\\.");
35+
} else {
36+
possibleTypes = new String[]{typeNames};
37+
}
38+
39+
return possibleTypes;
40+
}
41+
42+
public static Collection<? extends PhpNamedElement> resolveTwigMethodName(PsiElement psiElement, String[] typeName) {
43+
44+
if(typeName.length == 0) {
45+
return null;
46+
}
47+
48+
Collection<? extends PhpNamedElement> rootVariable = getRootVariableByName(psiElement, typeName[0]);
49+
if(typeName.length == 1) {
50+
return rootVariable;
51+
}
52+
53+
Collection<? extends PhpNamedElement> type = rootVariable;
54+
for (int i = 1; i <= typeName.length - 1; i++ ) {
55+
type = resolveTwigMethodName(type, typeName[i]);
56+
}
57+
58+
return type;
59+
}
60+
61+
private static Collection<? extends PhpNamedElement> findInlineDocBlockVariableByName(PsiElement psiInsideBlock, String variableName) {
62+
63+
PsiElement twigCompositeElement = PsiTreeUtil.findFirstParent(psiInsideBlock, new Condition<PsiElement>() {
64+
@Override
65+
public boolean value(PsiElement psiElement) {
66+
if (psiElement instanceof TwigCompositeElement) {
67+
if (PlatformPatterns.psiElement(TwigElementTypes.BLOCK_STATEMENT).accepts(psiElement)) {
68+
return true;
69+
}
70+
}
71+
return false;
72+
}
73+
});
74+
75+
76+
Pattern pattern = Pattern.compile("\\{#[\\s]+" + Pattern.quote(variableName) + "[\\s]+(.*)[\\s]+#}");
77+
Collection<PsiComment> psiComments = PsiTreeUtil.findChildrenOfType(twigCompositeElement, PsiComment.class);
78+
79+
ArrayList<PhpNamedElement> arrayList = new ArrayList<PhpNamedElement>();
80+
for(PsiComment psiComment: psiComments) {
81+
Matcher matcher = pattern.matcher(psiComment.getText());
82+
if (matcher.find()) {
83+
84+
// think of multi resolve
85+
PhpClass phpClass = PhpElementsUtil.getClass(psiComment.getProject(), matcher.group(1));
86+
if(phpClass != null) {
87+
arrayList.add(phpClass);
88+
return arrayList;
89+
}
90+
91+
}
92+
}
93+
94+
return null;
95+
}
96+
97+
private static Collection<? extends PhpNamedElement> getRootVariableByName(PsiElement psiElement, String variableName) {
98+
99+
HashMap<String, String> globalVars = new HashMap<String, String>();
100+
globalVars.put("app", "\\Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables");
101+
102+
ArrayList<PhpNamedElement> phpNamedElements = new ArrayList<PhpNamedElement>();
103+
104+
// parameter prio?
105+
if(globalVars.containsKey(variableName)) {
106+
PhpClass phpClass = PhpElementsUtil.getClass(psiElement.getProject(), globalVars.get(variableName));
107+
if(phpClass != null) {
108+
phpNamedElements.add(phpClass);
109+
return phpNamedElements;
110+
}
111+
}
112+
113+
114+
return findInlineDocBlockVariableByName(psiElement, variableName);
115+
}
116+
117+
private static Collection<? extends PhpNamedElement> resolveTwigMethodName(Collection<? extends PhpNamedElement> previousElement, String typeName) {
118+
119+
ArrayList<PhpNamedElement> phpNamedElements = new ArrayList<PhpNamedElement>();
120+
121+
for(PhpNamedElement phpNamedElement: previousElement) {
122+
if(phpNamedElement instanceof PhpClass) {
123+
for(Method method: ((PhpClass) phpNamedElement).getMethods()) {
124+
if(method.getName().toLowerCase().equals("get" + typeName.toLowerCase())) {
125+
126+
PhpType phpType = method.getType();
127+
for(String typeString: phpType.getTypes()) {
128+
PhpNamedElement phpNamedElement1 = PhpElementsUtil.getClassInterface(phpNamedElement.getProject(), typeString);
129+
if(phpNamedElement1 != null) {
130+
phpNamedElements.add(phpNamedElement1);
131+
}
132+
133+
}
134+
}
135+
}
136+
}
137+
}
138+
139+
return phpNamedElements;
140+
}
141+
}

0 commit comments

Comments
 (0)