Skip to content

Commit 94cfbd2

Browse files
committed
#769 provided related twig symbols navigation
1 parent eb13d45 commit 94cfbd2

File tree

3 files changed

+200
-74
lines changed

3 files changed

+200
-74
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package fr.adrienbrault.idea.symfony2plugin.navigation;
2+
3+
import com.intellij.lang.Language;
4+
import com.intellij.lang.html.HTMLLanguage;
5+
import com.intellij.navigation.GotoRelatedItem;
6+
import com.intellij.navigation.GotoRelatedProvider;
7+
import com.intellij.openapi.project.Project;
8+
import com.intellij.openapi.vfs.VirtualFile;
9+
import com.intellij.patterns.PlatformPatterns;
10+
import com.intellij.psi.PsiElement;
11+
import com.intellij.psi.PsiFile;
12+
import com.intellij.psi.util.PsiTreeUtil;
13+
import com.jetbrains.twig.TwigFile;
14+
import com.jetbrains.twig.TwigLanguage;
15+
import com.jetbrains.twig.elements.TwigBlockStatement;
16+
import com.jetbrains.twig.elements.TwigBlockTag;
17+
import com.jetbrains.twig.elements.TwigElementTypes;
18+
import com.jetbrains.twig.elements.TwigTagWithFileReference;
19+
import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons;
20+
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
21+
import fr.adrienbrault.idea.symfony2plugin.dic.RelatedPopupGotoLineMarker;
22+
import fr.adrienbrault.idea.symfony2plugin.templating.TwigPattern;
23+
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
24+
import fr.adrienbrault.idea.symfony2plugin.twig.utils.TwigBlockUtil;
25+
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
26+
import icons.TwigIcons;
27+
import org.jetbrains.annotations.NotNull;
28+
import org.jetbrains.annotations.Nullable;
29+
30+
import java.util.*;
31+
import java.util.function.Function;
32+
import java.util.stream.Collectors;
33+
34+
/**
35+
* @author Daniel Espendiller <[email protected]>
36+
*/
37+
public class TwigGotoRelatedProvider extends GotoRelatedProvider {
38+
@NotNull
39+
@Override
40+
public List<? extends GotoRelatedItem> getItems(@NotNull PsiElement psiElement2) {
41+
if (!Symfony2ProjectComponent.isEnabled(psiElement2)) {
42+
return Collections.emptyList();
43+
}
44+
45+
Language language = psiElement2.getLanguage();
46+
if (language != TwigLanguage.INSTANCE && language != HTMLLanguage.INSTANCE) {
47+
return Collections.emptyList();
48+
}
49+
50+
List<GotoRelatedItem> gotoRelatedItems = new ArrayList<>();
51+
52+
PsiElement psiElement = TwigUtil.getElementOnTwigViewProvider(psiElement2);
53+
if (psiElement == null) {
54+
return Collections.emptyList();
55+
}
56+
57+
PsiFile psiFile = psiElement.getContainingFile();
58+
if (psiFile instanceof TwigFile) {
59+
// extends
60+
Set<String> templates = new HashSet<>();
61+
TwigUtil.visitTemplateExtends((TwigFile) psiFile, pair -> templates.add(pair.getFirst()));
62+
Set<VirtualFile> virtualFiles = new HashSet<>();
63+
for (String template : templates) {
64+
for (PsiFile templatePsiElement : TwigUtil.getTemplatePsiElements(psiElement.getProject(), template)) {
65+
VirtualFile virtualFile = templatePsiElement.getVirtualFile();
66+
if (!virtualFiles.contains(virtualFile)) {
67+
virtualFiles.add(virtualFile);
68+
gotoRelatedItems.add(new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(templatePsiElement, "extends").withIcon(TwigIcons.TwigFileIcon, Symfony2Icons.TWIG_LINE_MARKER));
69+
}
70+
}
71+
}
72+
73+
// twig blocks up
74+
@Nullable TwigBlockStatement parentOfType = PsiTreeUtil.getParentOfType(psiElement, TwigBlockStatement.class);
75+
if (parentOfType != null) {
76+
TwigBlockTag twigBlockTag = PsiTreeUtil.findChildOfType(parentOfType, TwigBlockTag.class);
77+
if (twigBlockTag != null) {
78+
PsiElement childrenOfType = PsiElementUtils.getChildrenOfType(twigBlockTag, TwigPattern.getBlockTagPattern());
79+
if (childrenOfType != null) {
80+
String blockName = twigBlockTag.getName();
81+
82+
gotoRelatedItems.addAll(TwigBlockUtil.getBlockOverwriteTargets(childrenOfType).stream().map((Function<PsiElement, GotoRelatedItem>) psiElement1 ->
83+
new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(psiElement1, blockName).withIcon(Symfony2Icons.TWIG_BLOCK_OVERWRITE, Symfony2Icons.TWIG_BLOCK_OVERWRITE)
84+
).collect(Collectors.toList()));
85+
86+
gotoRelatedItems.addAll(TwigBlockUtil.getBlockImplementationTargets(childrenOfType).stream().map((Function<PsiElement, GotoRelatedItem>) psiElement1 ->
87+
new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(psiElement1, blockName).withIcon(Symfony2Icons.TWIG_BLOCK_OVERWRITE, Symfony2Icons.TWIG_BLOCK_OVERWRITE)
88+
).collect(Collectors.toList()));
89+
}
90+
}
91+
}
92+
93+
// "include" and other file tags
94+
TwigTagWithFileReference twigTagWithFileReference = PsiTreeUtil.getParentOfType(psiElement, TwigTagWithFileReference.class);
95+
if (twigTagWithFileReference != null) {
96+
visitFileReferenceElement(psiElement.getProject(), gotoRelatedItems, twigTagWithFileReference);
97+
}
98+
99+
// "embed" tag
100+
PsiElement parentOfType1 = PsiElementUtils.getParentOfType(psiElement, TwigElementTypes.EMBED_STATEMENT);
101+
if (parentOfType1 != null) {
102+
PsiElement embedTag = PsiElementUtils.getChildrenOfType(parentOfType1, PlatformPatterns.psiElement().withElementType(TwigElementTypes.EMBED_TAG));
103+
if (embedTag != null) {
104+
visitFileReferenceElement(psiElement.getProject(), gotoRelatedItems, embedTag);
105+
}
106+
}
107+
}
108+
109+
return gotoRelatedItems;
110+
}
111+
112+
private void visitFileReferenceElement(@NotNull Project project, @NotNull List<GotoRelatedItem> gotoRelatedItems, @NotNull PsiElement psiElement) {
113+
TwigUtil.visitTemplateIncludes(psiElement, templateInclude -> {
114+
for (PsiFile templatePsiElement : TwigUtil.getTemplatePsiElements(project, templateInclude.getTemplateName())) {
115+
gotoRelatedItems.add(new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(templatePsiElement, templateInclude.getType().toString().toLowerCase()).withIcon(Symfony2Icons.TWIG_BLOCK_OVERWRITE, Symfony2Icons.TWIG_BLOCK_OVERWRITE));
116+
}
117+
});
118+
}
119+
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigUtil.java

+80-74
Original file line numberDiff line numberDiff line change
@@ -2288,103 +2288,109 @@ public static Map<VirtualFile, Collection<String>> getBlockNamesForFiles(@NotNul
22882288
/**
22892289
* Visit all possible Twig include file pattern
22902290
*/
2291-
public static void visitTemplateIncludes(@NotNull TwigFile twigFile, @NotNull Consumer<TemplateInclude> consumer) {
2292-
PsiTreeUtil.collectElements(twigFile, psiElement -> {
2293-
if(psiElement instanceof TwigTagWithFileReference) {
2294-
// {% include %}
2295-
if(psiElement.getNode().getElementType() == TwigElementTypes.INCLUDE_TAG) {
2296-
for (String templateName : getIncludeTagStrings((TwigTagWithFileReference) psiElement)) {
2297-
if(StringUtils.isNotBlank(templateName)) {
2298-
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.INCLUDE));
2299-
}
2291+
public static void visitTemplateIncludes(@NotNull PsiElement psiElement, @NotNull Consumer<TemplateInclude> consumer) {
2292+
if(psiElement instanceof TwigTagWithFileReference) {
2293+
// {% include %}
2294+
if(psiElement.getNode().getElementType() == TwigElementTypes.INCLUDE_TAG) {
2295+
for (String templateName : getIncludeTagStrings((TwigTagWithFileReference) psiElement)) {
2296+
if(StringUtils.isNotBlank(templateName)) {
2297+
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.INCLUDE));
23002298
}
23012299
}
2300+
}
23022301

2303-
// {% import "foo.html.twig"
2304-
PsiElement importTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getTagNameParameterPattern(TwigElementTypes.IMPORT_TAG, "import"));
2305-
if(importTag != null) {
2306-
String templateName = importTag.getText();
2307-
if(StringUtils.isNotBlank(templateName)) {
2308-
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.IMPORT));
2309-
}
2302+
// {% import "foo.html.twig"
2303+
PsiElement importTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getTagNameParameterPattern(TwigElementTypes.IMPORT_TAG, "import"));
2304+
if(importTag != null) {
2305+
String templateName = importTag.getText();
2306+
if(StringUtils.isNotBlank(templateName)) {
2307+
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.IMPORT));
23102308
}
2309+
}
23112310

2312-
// {% from 'forms.html' import ... %}
2313-
PsiElement fromTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getTagNameParameterPattern(TwigElementTypes.IMPORT_TAG, "from"));
2314-
if(fromTag != null) {
2315-
String templateName = fromTag.getText();
2316-
if(StringUtils.isNotBlank(templateName)) {
2317-
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.IMPORT));
2318-
}
2311+
// {% from 'forms.html' import ... %}
2312+
PsiElement fromTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getTagNameParameterPattern(TwigElementTypes.IMPORT_TAG, "from"));
2313+
if(fromTag != null) {
2314+
String templateName = fromTag.getText();
2315+
if(StringUtils.isNotBlank(templateName)) {
2316+
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.IMPORT));
23192317
}
2320-
} else if(psiElement instanceof TwigCompositeElement) {
2321-
// {{ include() }}
2322-
// {{ source() }}
2323-
PsiElement includeTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getPrintBlockOrTagFunctionPattern("include", "source"));
2324-
if(includeTag != null) {
2325-
String templateName = includeTag.getText();
2326-
if(StringUtils.isNotBlank(templateName)) {
2327-
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.INCLUDE_FUNCTION));
2328-
}
2318+
}
2319+
} else if(psiElement instanceof TwigCompositeElement) {
2320+
// {{ include() }}
2321+
// {{ source() }}
2322+
PsiElement includeTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getPrintBlockOrTagFunctionPattern("include", "source"));
2323+
if(includeTag != null) {
2324+
String templateName = includeTag.getText();
2325+
if(StringUtils.isNotBlank(templateName)) {
2326+
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.INCLUDE_FUNCTION));
23292327
}
2328+
}
23302329

2331-
// {% embed "foo.html.twig"
2332-
PsiElement embedTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getEmbedPattern());
2333-
if(embedTag != null) {
2334-
String templateName = embedTag.getText();
2335-
if(StringUtils.isNotBlank(templateName)) {
2336-
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.EMBED));
2337-
}
2330+
// {% embed "foo.html.twig"
2331+
PsiElement embedTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getEmbedPattern());
2332+
if(embedTag != null) {
2333+
String templateName = embedTag.getText();
2334+
if(StringUtils.isNotBlank(templateName)) {
2335+
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.EMBED));
23382336
}
2337+
}
23392338

2340-
if(psiElement.getNode().getElementType() == TwigElementTypes.TAG) {
2341-
PsiElement tagElement = PsiElementUtils.getChildrenOfType(psiElement, PlatformPatterns.psiElement().withElementType(TwigTokenTypes.TAG_NAME));
2342-
if(tagElement != null) {
2343-
String text = tagElement.getText();
2344-
if("form_theme".equals(text)) {
2345-
// {% form_theme form.child 'form/fields_child.html.twig' %}
2346-
PsiElement childrenOfType = PsiElementUtils.getNextSiblingAndSkip(tagElement, TwigTokenTypes.STRING_TEXT,
2347-
TwigTokenTypes.IDENTIFIER, TwigTokenTypes.SINGLE_QUOTE, TwigTokenTypes.DOUBLE_QUOTE, TwigTokenTypes.DOT
2348-
);
2339+
if(psiElement.getNode().getElementType() == TwigElementTypes.TAG) {
2340+
PsiElement tagElement = PsiElementUtils.getChildrenOfType(psiElement, PlatformPatterns.psiElement().withElementType(TwigTokenTypes.TAG_NAME));
2341+
if(tagElement != null) {
2342+
String text = tagElement.getText();
2343+
if("form_theme".equals(text)) {
2344+
// {% form_theme form.child 'form/fields_child.html.twig' %}
2345+
PsiElement childrenOfType = PsiElementUtils.getNextSiblingAndSkip(tagElement, TwigTokenTypes.STRING_TEXT,
2346+
TwigTokenTypes.IDENTIFIER, TwigTokenTypes.SINGLE_QUOTE, TwigTokenTypes.DOUBLE_QUOTE, TwigTokenTypes.DOT
2347+
);
23492348

2350-
if(childrenOfType != null) {
2351-
String templateName = childrenOfType.getText();
2352-
if(StringUtils.isNotBlank(templateName)) {
2353-
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.FORM_THEME));
2354-
}
2349+
if(childrenOfType != null) {
2350+
String templateName = childrenOfType.getText();
2351+
if(StringUtils.isNotBlank(templateName)) {
2352+
consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.FORM_THEME));
23552353
}
2354+
}
23562355

2357-
// {% form_theme form.child with ['form/fields_child.html.twig'] %}
2358-
PsiElement withElement = PsiElementUtils.getNextSiblingOfType(tagElement, PlatformPatterns.psiElement().withElementType(TwigTokenTypes.IDENTIFIER).withText("with"));
2359-
if(withElement != null) {
2360-
// find LITERAL "[", "{"
2361-
PsiElement arrayStart = PsiElementUtils.getNextSiblingAndSkip(tagElement, TwigElementTypes.LITERAL,
2362-
TwigTokenTypes.IDENTIFIER, TwigTokenTypes.SINGLE_QUOTE, TwigTokenTypes.DOUBLE_QUOTE, TwigTokenTypes.DOT
2363-
);
2364-
2365-
if(arrayStart != null) {
2366-
PsiElement firstChild = arrayStart.getFirstChild();
2367-
if(firstChild != null) {
2368-
visitStringInArray(firstChild, pair ->
2369-
consumer.consume(new TemplateInclude(psiElement, pair.getFirst(), TemplateInclude.TYPE.FORM_THEME))
2370-
);
2371-
}
2356+
// {% form_theme form.child with ['form/fields_child.html.twig'] %}
2357+
PsiElement withElement = PsiElementUtils.getNextSiblingOfType(tagElement, PlatformPatterns.psiElement().withElementType(TwigTokenTypes.IDENTIFIER).withText("with"));
2358+
if(withElement != null) {
2359+
// find LITERAL "[", "{"
2360+
PsiElement arrayStart = PsiElementUtils.getNextSiblingAndSkip(tagElement, TwigElementTypes.LITERAL,
2361+
TwigTokenTypes.IDENTIFIER, TwigTokenTypes.SINGLE_QUOTE, TwigTokenTypes.DOUBLE_QUOTE, TwigTokenTypes.DOT
2362+
);
2363+
2364+
if(arrayStart != null) {
2365+
PsiElement firstChild = arrayStart.getFirstChild();
2366+
if(firstChild != null) {
2367+
visitStringInArray(firstChild, pair ->
2368+
consumer.consume(new TemplateInclude(psiElement, pair.getFirst(), TemplateInclude.TYPE.FORM_THEME))
2369+
);
23722370
}
23732371
}
23742372
}
23752373
}
23762374
}
2375+
}
23772376

2378-
for (TwigFileUsage extension : TWIG_FILE_USAGE_EXTENSIONS.getExtensions()) {
2379-
if (extension.isIncludeTemplate(psiElement)) {
2380-
for (String template : extension.getIncludeTemplate(psiElement)) {
2381-
consumer.consume(new TemplateInclude(psiElement, template, TemplateInclude.TYPE.INCLUDE));
2382-
}
2377+
for (TwigFileUsage extension : TWIG_FILE_USAGE_EXTENSIONS.getExtensions()) {
2378+
if (extension.isIncludeTemplate(psiElement)) {
2379+
for (String template : extension.getIncludeTemplate(psiElement)) {
2380+
consumer.consume(new TemplateInclude(psiElement, template, TemplateInclude.TYPE.INCLUDE));
23832381
}
23842382
}
23852383
}
2384+
}
2385+
}
23862386

2387-
return false;
2387+
/**
2388+
* Visit all possible Twig include file pattern
2389+
*/
2390+
public static void visitTemplateIncludes(@NotNull TwigFile twigFile, @NotNull Consumer<TemplateInclude> consumer) {
2391+
PsiTreeUtil.collectElements(twigFile, psiElement -> {
2392+
visitTemplateIncludes(psiElement, consumer);
2393+
return true;
23882394
});
23892395
}
23902396

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

+1
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@
249249
<gotoFileContributor implementation="fr.adrienbrault.idea.symfony2plugin.navigation.TemplateFileContributor"/>
250250

251251
<gotoRelatedProvider implementation="fr.adrienbrault.idea.symfony2plugin.navigation.PhpGotoRelatedProvider"/>
252+
<gotoRelatedProvider implementation="fr.adrienbrault.idea.symfony2plugin.navigation.TwigGotoRelatedProvider"/>
252253

253254
<directoryProjectGenerator implementation="fr.adrienbrault.idea.symfony2plugin.installer.SymfonyInstallerProjectGenerator"/>
254255
<projectTemplatesFactory implementation="fr.adrienbrault.idea.symfony2plugin.installer.SymfonyInstallerTemplatesFactory"/>

0 commit comments

Comments
 (0)