From 94cfbd20b4da2fa419e46278798483853bd3ea0c Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Mon, 9 May 2022 17:29:00 +0200 Subject: [PATCH] #769 provided related twig symbols navigation --- .../navigation/TwigGotoRelatedProvider.java | 119 ++++++++++++++ .../templating/util/TwigUtil.java | 154 +++++++++--------- src/main/resources/META-INF/plugin.xml | 1 + 3 files changed, 200 insertions(+), 74 deletions(-) create mode 100644 src/main/java/fr/adrienbrault/idea/symfony2plugin/navigation/TwigGotoRelatedProvider.java diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/navigation/TwigGotoRelatedProvider.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/navigation/TwigGotoRelatedProvider.java new file mode 100644 index 000000000..a8abb1e17 --- /dev/null +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/navigation/TwigGotoRelatedProvider.java @@ -0,0 +1,119 @@ +package fr.adrienbrault.idea.symfony2plugin.navigation; + +import com.intellij.lang.Language; +import com.intellij.lang.html.HTMLLanguage; +import com.intellij.navigation.GotoRelatedItem; +import com.intellij.navigation.GotoRelatedProvider; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.twig.TwigFile; +import com.jetbrains.twig.TwigLanguage; +import com.jetbrains.twig.elements.TwigBlockStatement; +import com.jetbrains.twig.elements.TwigBlockTag; +import com.jetbrains.twig.elements.TwigElementTypes; +import com.jetbrains.twig.elements.TwigTagWithFileReference; +import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons; +import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; +import fr.adrienbrault.idea.symfony2plugin.dic.RelatedPopupGotoLineMarker; +import fr.adrienbrault.idea.symfony2plugin.templating.TwigPattern; +import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil; +import fr.adrienbrault.idea.symfony2plugin.twig.utils.TwigBlockUtil; +import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; +import icons.TwigIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author Daniel Espendiller + */ +public class TwigGotoRelatedProvider extends GotoRelatedProvider { + @NotNull + @Override + public List getItems(@NotNull PsiElement psiElement2) { + if (!Symfony2ProjectComponent.isEnabled(psiElement2)) { + return Collections.emptyList(); + } + + Language language = psiElement2.getLanguage(); + if (language != TwigLanguage.INSTANCE && language != HTMLLanguage.INSTANCE) { + return Collections.emptyList(); + } + + List gotoRelatedItems = new ArrayList<>(); + + PsiElement psiElement = TwigUtil.getElementOnTwigViewProvider(psiElement2); + if (psiElement == null) { + return Collections.emptyList(); + } + + PsiFile psiFile = psiElement.getContainingFile(); + if (psiFile instanceof TwigFile) { + // extends + Set templates = new HashSet<>(); + TwigUtil.visitTemplateExtends((TwigFile) psiFile, pair -> templates.add(pair.getFirst())); + Set virtualFiles = new HashSet<>(); + for (String template : templates) { + for (PsiFile templatePsiElement : TwigUtil.getTemplatePsiElements(psiElement.getProject(), template)) { + VirtualFile virtualFile = templatePsiElement.getVirtualFile(); + if (!virtualFiles.contains(virtualFile)) { + virtualFiles.add(virtualFile); + gotoRelatedItems.add(new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(templatePsiElement, "extends").withIcon(TwigIcons.TwigFileIcon, Symfony2Icons.TWIG_LINE_MARKER)); + } + } + } + + // twig blocks up + @Nullable TwigBlockStatement parentOfType = PsiTreeUtil.getParentOfType(psiElement, TwigBlockStatement.class); + if (parentOfType != null) { + TwigBlockTag twigBlockTag = PsiTreeUtil.findChildOfType(parentOfType, TwigBlockTag.class); + if (twigBlockTag != null) { + PsiElement childrenOfType = PsiElementUtils.getChildrenOfType(twigBlockTag, TwigPattern.getBlockTagPattern()); + if (childrenOfType != null) { + String blockName = twigBlockTag.getName(); + + gotoRelatedItems.addAll(TwigBlockUtil.getBlockOverwriteTargets(childrenOfType).stream().map((Function) psiElement1 -> + new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(psiElement1, blockName).withIcon(Symfony2Icons.TWIG_BLOCK_OVERWRITE, Symfony2Icons.TWIG_BLOCK_OVERWRITE) + ).collect(Collectors.toList())); + + gotoRelatedItems.addAll(TwigBlockUtil.getBlockImplementationTargets(childrenOfType).stream().map((Function) psiElement1 -> + new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(psiElement1, blockName).withIcon(Symfony2Icons.TWIG_BLOCK_OVERWRITE, Symfony2Icons.TWIG_BLOCK_OVERWRITE) + ).collect(Collectors.toList())); + } + } + } + + // "include" and other file tags + TwigTagWithFileReference twigTagWithFileReference = PsiTreeUtil.getParentOfType(psiElement, TwigTagWithFileReference.class); + if (twigTagWithFileReference != null) { + visitFileReferenceElement(psiElement.getProject(), gotoRelatedItems, twigTagWithFileReference); + } + + // "embed" tag + PsiElement parentOfType1 = PsiElementUtils.getParentOfType(psiElement, TwigElementTypes.EMBED_STATEMENT); + if (parentOfType1 != null) { + PsiElement embedTag = PsiElementUtils.getChildrenOfType(parentOfType1, PlatformPatterns.psiElement().withElementType(TwigElementTypes.EMBED_TAG)); + if (embedTag != null) { + visitFileReferenceElement(psiElement.getProject(), gotoRelatedItems, embedTag); + } + } + } + + return gotoRelatedItems; + } + + private void visitFileReferenceElement(@NotNull Project project, @NotNull List gotoRelatedItems, @NotNull PsiElement psiElement) { + TwigUtil.visitTemplateIncludes(psiElement, templateInclude -> { + for (PsiFile templatePsiElement : TwigUtil.getTemplatePsiElements(project, templateInclude.getTemplateName())) { + gotoRelatedItems.add(new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(templatePsiElement, templateInclude.getType().toString().toLowerCase()).withIcon(Symfony2Icons.TWIG_BLOCK_OVERWRITE, Symfony2Icons.TWIG_BLOCK_OVERWRITE)); + } + }); + } +} diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigUtil.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigUtil.java index 875a6d2ca..d561940f1 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigUtil.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigUtil.java @@ -2288,103 +2288,109 @@ public static Map> getBlockNamesForFiles(@NotNul /** * Visit all possible Twig include file pattern */ - public static void visitTemplateIncludes(@NotNull TwigFile twigFile, @NotNull Consumer consumer) { - PsiTreeUtil.collectElements(twigFile, psiElement -> { - if(psiElement instanceof TwigTagWithFileReference) { - // {% include %} - if(psiElement.getNode().getElementType() == TwigElementTypes.INCLUDE_TAG) { - for (String templateName : getIncludeTagStrings((TwigTagWithFileReference) psiElement)) { - if(StringUtils.isNotBlank(templateName)) { - consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.INCLUDE)); - } + public static void visitTemplateIncludes(@NotNull PsiElement psiElement, @NotNull Consumer consumer) { + if(psiElement instanceof TwigTagWithFileReference) { + // {% include %} + if(psiElement.getNode().getElementType() == TwigElementTypes.INCLUDE_TAG) { + for (String templateName : getIncludeTagStrings((TwigTagWithFileReference) psiElement)) { + if(StringUtils.isNotBlank(templateName)) { + consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.INCLUDE)); } } + } - // {% import "foo.html.twig" - PsiElement importTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getTagNameParameterPattern(TwigElementTypes.IMPORT_TAG, "import")); - if(importTag != null) { - String templateName = importTag.getText(); - if(StringUtils.isNotBlank(templateName)) { - consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.IMPORT)); - } + // {% import "foo.html.twig" + PsiElement importTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getTagNameParameterPattern(TwigElementTypes.IMPORT_TAG, "import")); + if(importTag != null) { + String templateName = importTag.getText(); + if(StringUtils.isNotBlank(templateName)) { + consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.IMPORT)); } + } - // {% from 'forms.html' import ... %} - PsiElement fromTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getTagNameParameterPattern(TwigElementTypes.IMPORT_TAG, "from")); - if(fromTag != null) { - String templateName = fromTag.getText(); - if(StringUtils.isNotBlank(templateName)) { - consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.IMPORT)); - } + // {% from 'forms.html' import ... %} + PsiElement fromTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getTagNameParameterPattern(TwigElementTypes.IMPORT_TAG, "from")); + if(fromTag != null) { + String templateName = fromTag.getText(); + if(StringUtils.isNotBlank(templateName)) { + consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.IMPORT)); } - } else if(psiElement instanceof TwigCompositeElement) { - // {{ include() }} - // {{ source() }} - PsiElement includeTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getPrintBlockOrTagFunctionPattern("include", "source")); - if(includeTag != null) { - String templateName = includeTag.getText(); - if(StringUtils.isNotBlank(templateName)) { - consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.INCLUDE_FUNCTION)); - } + } + } else if(psiElement instanceof TwigCompositeElement) { + // {{ include() }} + // {{ source() }} + PsiElement includeTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getPrintBlockOrTagFunctionPattern("include", "source")); + if(includeTag != null) { + String templateName = includeTag.getText(); + if(StringUtils.isNotBlank(templateName)) { + consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.INCLUDE_FUNCTION)); } + } - // {% embed "foo.html.twig" - PsiElement embedTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getEmbedPattern()); - if(embedTag != null) { - String templateName = embedTag.getText(); - if(StringUtils.isNotBlank(templateName)) { - consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.EMBED)); - } + // {% embed "foo.html.twig" + PsiElement embedTag = PsiElementUtils.getChildrenOfType(psiElement, TwigPattern.getEmbedPattern()); + if(embedTag != null) { + String templateName = embedTag.getText(); + if(StringUtils.isNotBlank(templateName)) { + consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.EMBED)); } + } - if(psiElement.getNode().getElementType() == TwigElementTypes.TAG) { - PsiElement tagElement = PsiElementUtils.getChildrenOfType(psiElement, PlatformPatterns.psiElement().withElementType(TwigTokenTypes.TAG_NAME)); - if(tagElement != null) { - String text = tagElement.getText(); - if("form_theme".equals(text)) { - // {% form_theme form.child 'form/fields_child.html.twig' %} - PsiElement childrenOfType = PsiElementUtils.getNextSiblingAndSkip(tagElement, TwigTokenTypes.STRING_TEXT, - TwigTokenTypes.IDENTIFIER, TwigTokenTypes.SINGLE_QUOTE, TwigTokenTypes.DOUBLE_QUOTE, TwigTokenTypes.DOT - ); + if(psiElement.getNode().getElementType() == TwigElementTypes.TAG) { + PsiElement tagElement = PsiElementUtils.getChildrenOfType(psiElement, PlatformPatterns.psiElement().withElementType(TwigTokenTypes.TAG_NAME)); + if(tagElement != null) { + String text = tagElement.getText(); + if("form_theme".equals(text)) { + // {% form_theme form.child 'form/fields_child.html.twig' %} + PsiElement childrenOfType = PsiElementUtils.getNextSiblingAndSkip(tagElement, TwigTokenTypes.STRING_TEXT, + TwigTokenTypes.IDENTIFIER, TwigTokenTypes.SINGLE_QUOTE, TwigTokenTypes.DOUBLE_QUOTE, TwigTokenTypes.DOT + ); - if(childrenOfType != null) { - String templateName = childrenOfType.getText(); - if(StringUtils.isNotBlank(templateName)) { - consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.FORM_THEME)); - } + if(childrenOfType != null) { + String templateName = childrenOfType.getText(); + if(StringUtils.isNotBlank(templateName)) { + consumer.consume(new TemplateInclude(psiElement, templateName, TemplateInclude.TYPE.FORM_THEME)); } + } - // {% form_theme form.child with ['form/fields_child.html.twig'] %} - PsiElement withElement = PsiElementUtils.getNextSiblingOfType(tagElement, PlatformPatterns.psiElement().withElementType(TwigTokenTypes.IDENTIFIER).withText("with")); - if(withElement != null) { - // find LITERAL "[", "{" - PsiElement arrayStart = PsiElementUtils.getNextSiblingAndSkip(tagElement, TwigElementTypes.LITERAL, - TwigTokenTypes.IDENTIFIER, TwigTokenTypes.SINGLE_QUOTE, TwigTokenTypes.DOUBLE_QUOTE, TwigTokenTypes.DOT - ); - - if(arrayStart != null) { - PsiElement firstChild = arrayStart.getFirstChild(); - if(firstChild != null) { - visitStringInArray(firstChild, pair -> - consumer.consume(new TemplateInclude(psiElement, pair.getFirst(), TemplateInclude.TYPE.FORM_THEME)) - ); - } + // {% form_theme form.child with ['form/fields_child.html.twig'] %} + PsiElement withElement = PsiElementUtils.getNextSiblingOfType(tagElement, PlatformPatterns.psiElement().withElementType(TwigTokenTypes.IDENTIFIER).withText("with")); + if(withElement != null) { + // find LITERAL "[", "{" + PsiElement arrayStart = PsiElementUtils.getNextSiblingAndSkip(tagElement, TwigElementTypes.LITERAL, + TwigTokenTypes.IDENTIFIER, TwigTokenTypes.SINGLE_QUOTE, TwigTokenTypes.DOUBLE_QUOTE, TwigTokenTypes.DOT + ); + + if(arrayStart != null) { + PsiElement firstChild = arrayStart.getFirstChild(); + if(firstChild != null) { + visitStringInArray(firstChild, pair -> + consumer.consume(new TemplateInclude(psiElement, pair.getFirst(), TemplateInclude.TYPE.FORM_THEME)) + ); } } } } } + } - for (TwigFileUsage extension : TWIG_FILE_USAGE_EXTENSIONS.getExtensions()) { - if (extension.isIncludeTemplate(psiElement)) { - for (String template : extension.getIncludeTemplate(psiElement)) { - consumer.consume(new TemplateInclude(psiElement, template, TemplateInclude.TYPE.INCLUDE)); - } + for (TwigFileUsage extension : TWIG_FILE_USAGE_EXTENSIONS.getExtensions()) { + if (extension.isIncludeTemplate(psiElement)) { + for (String template : extension.getIncludeTemplate(psiElement)) { + consumer.consume(new TemplateInclude(psiElement, template, TemplateInclude.TYPE.INCLUDE)); } } } + } + } - return false; + /** + * Visit all possible Twig include file pattern + */ + public static void visitTemplateIncludes(@NotNull TwigFile twigFile, @NotNull Consumer consumer) { + PsiTreeUtil.collectElements(twigFile, psiElement -> { + visitTemplateIncludes(psiElement, consumer); + return true; }); } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 3078faa87..18956f7c7 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -249,6 +249,7 @@ +