diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/TwigBlockEmbedIndex.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/TwigBlockEmbedIndex.java index 682a6ee97..050921808 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/TwigBlockEmbedIndex.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/TwigBlockEmbedIndex.java @@ -34,11 +34,11 @@ public DataIndexer, FileContent> getIndexer() { PsiFile psiFile = fileContent.getPsiFile(); if (psiFile instanceof TwigFile twigFile) { - TwigUtil.visitEmbedBlocks(twigFile, pair -> { - String templateName = pair.getFirst(); + TwigUtil.visitEmbedBlocks(twigFile, embedBlock -> { + String templateName = embedBlock.templateName(); blocks.putIfAbsent(templateName, new HashSet<>()); - blocks.get(templateName).add(pair.getSecond()); + blocks.get(templateName).add(embedBlock.blockName()); }); } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/dict/TwigBlockEmbed.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/dict/TwigBlockEmbed.java new file mode 100644 index 000000000..db949d26a --- /dev/null +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/dict/TwigBlockEmbed.java @@ -0,0 +1,10 @@ +package fr.adrienbrault.idea.symfony2plugin.templating.dict; + +import com.jetbrains.twig.elements.TwigBlockStatement; +import org.jetbrains.annotations.NotNull; + +/** + * @author Daniel Espendiller + */ +public record TwigBlockEmbed(@NotNull String templateName, @NotNull String blockName, @NotNull TwigBlockStatement target) { +} 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 8bd9ad3ee..6b72ed5f7 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 @@ -27,7 +27,10 @@ import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment; import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag; import com.jetbrains.php.lang.psi.PhpPsiUtil; -import com.jetbrains.php.lang.psi.elements.*; +import com.jetbrains.php.lang.psi.elements.Function; +import com.jetbrains.php.lang.psi.elements.Method; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; import com.jetbrains.php.phpunit.PhpUnitUtil; import com.jetbrains.twig.TwigFile; import com.jetbrains.twig.TwigFileType; @@ -1784,8 +1787,7 @@ public void visitElement(PsiElement element) { return block; } - @NotNull - public static void visitEmbedBlocks(@NotNull TwigFile psiFile, @NotNull Consumer> consumer) { + public static void visitEmbedBlocks(@NotNull TwigFile psiFile, @NotNull Consumer consumer) { PsiElement[] embedStatements = PsiTreeUtil.collectElements(psiFile, psiElement -> psiElement instanceof TwigCompositeElement && psiElement.getNode().getElementType() == TwigElementTypes.EMBED_STATEMENT ); @@ -1805,7 +1807,7 @@ public static void visitEmbedBlocks(@NotNull TwigFile psiFile, @NotNull Consumer String blockName = twigBlockStatement.getName(); if (blockName != null && !blockName.isBlank()) { - consumer.consume(Pair.create(templateNameForEmbedTag, blockName)); + consumer.consume(new TwigBlockEmbed(templateNameForEmbedTag, blockName, twigBlockStatement)); } } } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/twig/utils/TwigBlockUtil.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/twig/utils/TwigBlockUtil.java index 90aa2a207..53a2f2bca 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/twig/utils/TwigBlockUtil.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/twig/utils/TwigBlockUtil.java @@ -3,17 +3,22 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; 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.PsiManager; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.indexing.FileBasedIndex; import com.jetbrains.twig.TwigFile; +import com.jetbrains.twig.TwigTokenTypes; +import com.jetbrains.twig.elements.TwigBlockStatement; +import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigBlockEmbedIndex; import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigBlockIndexExtension; import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigBlock; import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil; import fr.adrienbrault.idea.symfony2plugin.twig.loader.FileImplementsLazyLoader; import fr.adrienbrault.idea.symfony2plugin.twig.loader.FileOverwritesLazyLoader; +import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; @@ -54,25 +59,41 @@ private static Collection collectBlocksInFile(boolean includeSelf, @N */ public static boolean hasBlockImplementations(@NotNull PsiElement blockPsiName, @NotNull FileImplementsLazyLoader implementsLazyLoader) { String blockName = blockPsiName.getText(); - if(StringUtils.isBlank(blockName)) { + if (StringUtils.isBlank(blockName)) { return false; } PsiFile psiFile = blockPsiName.getContainingFile(); - if(psiFile == null) { + if (psiFile == null) { return false; } Collection twigChild = implementsLazyLoader.getFiles(); - if(twigChild.size() == 0) { + + Set virtualFiles = new HashSet<>(twigChild); + VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile != null) { + virtualFiles.add(virtualFile); + } + + Project project = psiFile.getProject(); + + for (VirtualFile file : virtualFiles) { + if (hasBlockImplementationsForEmbed(project, file, blockName)) { + return true; + } + } + + if (twigChild.isEmpty()) { return false; } - return hasBlockNamesForFiles(blockPsiName.getProject(), blockName, twigChild); + return hasBlockNamesForFiles(project, blockName, twigChild); } /** * {% extends 'foobar.html.twig' %} + * {% embed 'foobar.html.twig' %} * * {{ block('foobar') }} * {% block 'foobar' %} @@ -81,29 +102,51 @@ public static boolean hasBlockImplementations(@NotNull PsiElement blockPsiName, @NotNull public static Collection getBlockImplementationTargets(@NotNull PsiElement blockPsiName) { String blockName = blockPsiName.getText(); - if(StringUtils.isBlank(blockName)) { + if (StringUtils.isBlank(blockName)) { return Collections.emptyList(); } PsiFile psiFile = blockPsiName.getContainingFile(); - if(psiFile == null) { + if (psiFile == null) { return Collections.emptyList(); } - Collection twigChild = TwigUtil.getTemplatesExtendingFile(psiFile.getProject(), psiFile.getVirtualFile()); - if(twigChild.size() == 0) { - return Collections.emptyList(); - } + Project project = psiFile.getProject(); + VirtualFile currentVirtualFile = psiFile.getVirtualFile(); + Collection parentTwigExtendingFiles = TwigUtil.getTemplatesExtendingFile(project, currentVirtualFile); + + HashSet embedFiles = new HashSet<>(parentTwigExtendingFiles); + embedFiles.add(currentVirtualFile); + + Collection blockTargets = new HashSet<>(); - Collection blockTargets = new ArrayList<>(); + // embed targets + for (VirtualFile vFile : embedFiles) { + Set templateNames = TwigUtil.getTemplateNamesForFile(blockPsiName.getProject(), vFile).stream() + .map(TwigUtil::normalizeTemplateName) + .collect(Collectors.toSet()); - for (VirtualFile virtualFile : twigChild) { - PsiFile file = PsiManager.getInstance(blockPsiName.getProject()).findFile(virtualFile); - if(!(file instanceof TwigFile)) { + for (String templateName : templateNames) { + for (VirtualFile containingFile : FileBasedIndex.getInstance().getContainingFiles(TwigBlockEmbedIndex.KEY, templateName, GlobalSearchScope.allScope(project))) { + if(!(PsiManager.getInstance(project).findFile(containingFile) instanceof TwigFile twigFile)) { + continue; + } + + TwigUtil.visitEmbedBlocks(twigFile, embedBlock -> { + if (embedBlock.blockName().equals(blockName) && templateName.equals(embedBlock.templateName())) { + blockTargets.add(getBlockNamePsiElementTarget(embedBlock.target())); + } + }); + } + } + } + + for (VirtualFile parentTwigExtendingFile : parentTwigExtendingFiles) { + if (!(PsiManager.getInstance(project).findFile(parentTwigExtendingFile) instanceof TwigFile twigFile)) { continue; } - for (TwigBlock twigBlock : TwigUtil.getBlocksInFile((TwigFile) file)) { + for (TwigBlock twigBlock : TwigUtil.getBlocksInFile(twigFile)) { if (blockName.equals(twigBlock.getName())) { blockTargets.add(twigBlock.getTarget()); } @@ -113,6 +156,22 @@ public static Collection getBlockImplementationTargets(@NotNull PsiE return blockTargets; } + /** + * Extracted named block element for ui presentable + */ + private static PsiElement getBlockNamePsiElementTarget(@NotNull TwigBlockStatement twigBlockStatement) { + PsiElement blockTag = twigBlockStatement.getFirstChild(); + + if (blockTag != null) { + PsiElement childrenOfType = PsiElementUtils.getChildrenOfType(blockTag, PlatformPatterns.psiElement(TwigTokenTypes.IDENTIFIER)); + if (childrenOfType != null) { + return childrenOfType; + } + } + + return twigBlockStatement; + } + /** * Collect every block name by given file name; resolve the "extends" or "embed" scope */ @@ -140,7 +199,7 @@ public static Collection getBlockOverwriteTargets(@NotNull PsiElemen public static boolean hasBlockOverwrites(@NotNull PsiElement psiElement, @NotNull FileOverwritesLazyLoader fileOverwritesLazyLoader) { String blockName = psiElement.getText(); - if(StringUtils.isBlank(blockName)) { + if (StringUtils.isBlank(blockName)) { return false; } @@ -154,6 +213,21 @@ public static boolean hasBlockOverwrites(@NotNull PsiElement psiElement, @NotNul return hasBlockNamesForFiles(psiElement.getProject(), blockName, virtualFiles); } + private static boolean hasBlockImplementationsForEmbed(@NotNull Project project, @NotNull VirtualFile virtualFile, @NotNull String blockName) { + for (String templateName : TwigUtil.getTemplateNamesForFile(project, virtualFile)) { + Set blockNames = FileBasedIndex.getInstance().getValues(TwigBlockEmbedIndex.KEY, templateName, GlobalSearchScope.allScope(project)) + .stream() + .flatMap(Set::stream) + .collect(Collectors.toSet()); + + if (blockNames.contains(blockName)) { + return true; + } + } + + return false; + } + /** * Collect every block name by given file name; resolve the "extends" */