Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 94cfbd2

Browse files
#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
Lines changed: 119 additions & 0 deletions
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 <daniel@espendiller.net>
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

Lines changed: 80 additions & 74 deletions
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-
}elseif(psiElementinstanceofTwigCompositeElement) {
2321-
// {{ include() }}
2322-
// {{ source() }}
2323-
PsiElementincludeTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getPrintBlockOrTagFunctionPattern("include", "source"));
2324-
if(includeTag != null) {
2325-
StringtemplateName = includeTag.getText();
2326-
if(StringUtils.isNotBlank(templateName)) {
2327-
consumer.consume(newTemplateInclude(psiElement, templateName, TemplateInclude.TYPE.INCLUDE_FUNCTION));
2328-
}
2318+
}
2319+
} elseif(psiElementinstanceofTwigCompositeElement) {
2320+
// {{ include() }}
2321+
// {{ source() }}
2322+
PsiElementincludeTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getPrintBlockOrTagFunctionPattern("include", "source"));
2323+
if(includeTag != null) {
2324+
StringtemplateName = includeTag.getText();
2325+
if(StringUtils.isNotBlank(templateName)) {
2326+
consumer.consume(newTemplateInclude(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

Lines changed: 1 addition & 0 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /