Skip to content

Commit 7100c1e

Browse files
authored
Merge pull request Haehnchen#1094 from Haehnchen/feature/1091-twig-block-index
add Twig block name indexer to improve performance Haehnchen#1091
2 parents ff5a145 + 278020a commit 7100c1e

18 files changed

+677
-248
lines changed

Diff for: META-INF/plugin.xml

+1
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@
228228
<fileBasedIndex implementation="fr.adrienbrault.idea.symfony2plugin.stubs.indexes.ContainerBuilderStubIndex"/>
229229
<fileBasedIndex implementation="fr.adrienbrault.idea.symfony2plugin.stubs.indexes.EventAnnotationStubIndex"/>
230230
<fileBasedIndex implementation="fr.adrienbrault.idea.symfony2plugin.stubs.indexes.ContainerIdUsagesStubIndex"/>
231+
<fileBasedIndex implementation="fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigBlockIndexExtension"/>
231232

232233
<codeInsight.lineMarkerProvider language="PHP" implementationClass="fr.adrienbrault.idea.symfony2plugin.config.ServiceLineMarkerProvider"/>
233234
<codeInsight.lineMarkerProvider language="PHP" implementationClass="fr.adrienbrault.idea.symfony2plugin.dic.ControllerMethodLineMarkerProvider"/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package fr.adrienbrault.idea.symfony2plugin.stubs.indexes;
2+
3+
import com.intellij.psi.PsiElement;
4+
import com.intellij.psi.PsiFile;
5+
import com.intellij.util.indexing.*;
6+
import com.intellij.util.io.DataExternalizer;
7+
import com.intellij.util.io.EnumeratorStringDescriptor;
8+
import com.intellij.util.io.KeyDescriptor;
9+
import com.jetbrains.twig.TwigFile;
10+
import com.jetbrains.twig.TwigFileType;
11+
import com.jetbrains.twig.elements.TwigElementTypes;
12+
import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.externalizer.StringSetDataExternalizer;
13+
import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigBlock;
14+
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
15+
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
16+
import org.jetbrains.annotations.NotNull;
17+
18+
import java.util.HashMap;
19+
import java.util.HashSet;
20+
import java.util.Map;
21+
import java.util.Set;
22+
23+
/**
24+
* @author Daniel Espendiller <daniel@espendiller.net>
25+
*/
26+
public class TwigBlockIndexExtension extends FileBasedIndexExtension<String, Set<String>> {
27+
28+
public static final ID<String, Set<String>> KEY = ID.create("fr.adrienbrault.idea.symfony2plugin.twig_block_names");
29+
30+
private final KeyDescriptor<String> KEY_DESCRIPTOR = new EnumeratorStringDescriptor();
31+
private static final StringSetDataExternalizer DATA_EXTERNALIZER = new StringSetDataExternalizer();
32+
33+
@NotNull
34+
@Override
35+
public DataIndexer<String, Set<String>, FileContent> getIndexer() {
36+
return fileContent -> {
37+
Map<String, Set<String>> blocks = new HashMap<>();
38+
39+
PsiFile psiFile = fileContent.getPsiFile();
40+
if(psiFile instanceof TwigFile) {
41+
for (TwigBlock twigBlock : TwigUtil.getBlocksInFile((TwigFile) psiFile)) {
42+
// we only index file scope
43+
// {% embed 'foo.html.twig' %}{% block foo %}{% endembed %}
44+
PsiElement embedStatement = PsiElementUtils.getParentOfType(twigBlock.getTarget(), TwigElementTypes.EMBED_STATEMENT);
45+
if(embedStatement == null) {
46+
blocks.putIfAbsent("block", new HashSet<>());
47+
blocks.get("block").add(twigBlock.getName());
48+
}
49+
}
50+
}
51+
52+
return blocks;
53+
};
54+
}
55+
56+
@NotNull
57+
@Override
58+
public ID<String, Set<String>> getName() {
59+
return KEY;
60+
}
61+
62+
@NotNull
63+
@Override
64+
public KeyDescriptor<String> getKeyDescriptor() {
65+
return KEY_DESCRIPTOR;
66+
}
67+
68+
@NotNull
69+
@Override
70+
public DataExternalizer<Set<String>> getValueExternalizer() {
71+
return DATA_EXTERNALIZER;
72+
}
73+
74+
@Override
75+
public int getVersion() {
76+
return 1;
77+
}
78+
79+
@NotNull
80+
@Override
81+
public FileBasedIndex.InputFilter getInputFilter() {
82+
return file -> file.getFileType() == TwigFileType.INSTANCE;
83+
}
84+
85+
@Override
86+
public boolean dependsOnFileContent() {
87+
return true;
88+
}
89+
}

Diff for: src/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/externalizer/StringSetDataExternalizer.java

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
*/
2020
public class StringSetDataExternalizer implements DataExternalizer<Set<String>> {
2121

22+
public static StringSetDataExternalizer INSTANCE = new StringSetDataExternalizer();
23+
2224
public synchronized void save(@NotNull DataOutput out, Set<String> value) throws IOException {
2325
out.writeInt(value.size());
2426
Iterator var = value.iterator();

Diff for: src/fr/adrienbrault/idea/symfony2plugin/stubs/util/IndexUtil.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
* @author Daniel Espendiller <daniel@espendiller.net>
99
*/
1010
public class IndexUtil {
11-
1211
public static void forceReindex() {
13-
1412
ID<?,?>[] indexIds = new ID<?,?>[] {
1513
ContainerBuilderStubIndex.KEY,
1614
ContainerParameterStubIndex.KEY,
@@ -25,13 +23,12 @@ public static void forceReindex() {
2523
TwigIncludeStubIndex.KEY,
2624
TwigMacroFunctionStubIndex.KEY,
2725
TranslationStubIndex.KEY,
26+
TwigBlockIndexExtension.KEY
2827
};
2928

3029
for(ID<?,?> id: indexIds) {
3130
FileBasedIndex.getInstance().requestRebuild(id);
3231
FileBasedIndex.getInstance().scheduleRebuild(id, new Throwable());
3332
}
34-
3533
}
36-
3734
}

Diff for: src/fr/adrienbrault/idea/symfony2plugin/templating/BlockGotoCompletionRegistrar.java

+11-13
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package fr.adrienbrault.idea.symfony2plugin.templating;
22

3-
import com.intellij.codeInsight.lookup.LookupElement;
43
import com.intellij.psi.PsiElement;
54
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
6-
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
7-
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrar;
8-
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrarParameter;
9-
import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigBlockParser;
5+
import fr.adrienbrault.idea.symfony2plugin.codeInsight.*;
106
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
7+
import fr.adrienbrault.idea.symfony2plugin.twig.utils.TwigBlockUtil;
8+
import fr.adrienbrault.idea.symfony2plugin.twig.utils.TwigFileUtil;
119
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
1210
import org.apache.commons.lang.StringUtils;
1311
import org.jetbrains.annotations.NotNull;
@@ -34,7 +32,7 @@ public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
3432
});
3533
}
3634

37-
private static class BlockFunctionReferenceCompletionProvider extends GotoCompletionProvider {
35+
private static class BlockFunctionReferenceCompletionProvider extends GotoCompletionProvider implements GotoCompletionProviderInterfaceEx {
3836
private BlockFunctionReferenceCompletionProvider(@NotNull PsiElement element) {
3937
super(element);
4038
}
@@ -49,11 +47,11 @@ public Collection<PsiElement> getPsiTargets(PsiElement element) {
4947
Collection<PsiElement> psiElements = new HashSet<>();
5048

5149
psiElements.addAll(
52-
TwigTemplateGoToDeclarationHandler.getBlockNameGoTo(element.getContainingFile(), blockName, true)
50+
TwigBlockUtil.getBlockOverwriteTargets(element.getContainingFile(), blockName, true)
5351
);
5452

5553
psiElements.addAll(
56-
TwigUtil.getBlocksByImplementations(element)
54+
TwigBlockUtil.getBlockImplementationTargets(element)
5755
);
5856

5957
// filter self navigation
@@ -62,12 +60,12 @@ public Collection<PsiElement> getPsiTargets(PsiElement element) {
6260
.collect(Collectors.toSet());
6361
}
6462

65-
@NotNull
66-
public Collection<LookupElement> getLookupElements() {
67-
return TwigUtil.getBlockLookupElements(
63+
@Override
64+
public void getLookupElements(@NotNull GotoCompletionProviderLookupArguments arguments) {
65+
arguments.addAllElements(TwigUtil.getBlockLookupElements(
6866
getProject(),
69-
new TwigBlockParser(true).walk(getElement().getContainingFile())
70-
);
67+
TwigFileUtil.collectParentFiles(true, arguments.getParameters().getOriginalFile())
68+
));
7169
}
7270
}
7371
}

Diff for: src/fr/adrienbrault/idea/symfony2plugin/templating/TwigLineMarkerProvider.java

+87-36
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
import fr.adrienbrault.idea.symfony2plugin.dic.RelatedPopupGotoLineMarker;
2929
import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigIncludeStubIndex;
3030
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
31+
import fr.adrienbrault.idea.symfony2plugin.twig.loader.FileImplementsLazyLoader;
32+
import fr.adrienbrault.idea.symfony2plugin.twig.loader.FileOverwritesLazyLoader;
33+
import fr.adrienbrault.idea.symfony2plugin.twig.utils.TwigBlockUtil;
3134
import icons.TwigIcons;
3235
import org.apache.commons.lang.StringUtils;
3336
import org.jetbrains.annotations.NotNull;
@@ -42,11 +45,13 @@
4245
public class TwigLineMarkerProvider implements LineMarkerProvider {
4346
@Override
4447
public void collectSlowLineMarkers(@NotNull List<PsiElement> psiElements, @NotNull Collection<LineMarkerInfo> results) {
45-
4648
if(psiElements.size() == 0 || !Symfony2ProjectComponent.isEnabled(psiElements.get(0))) {
4749
return;
4850
}
4951

52+
Map<VirtualFile, FileImplementsLazyLoader> implementsMap = new HashMap<>();
53+
FileOverwritesLazyLoader fileOverwritesLazyLoader = null;
54+
5055
for(PsiElement psiElement: psiElements) {
5156
// controller
5257
if(psiElement instanceof TwigFile) {
@@ -67,12 +72,21 @@ public void collectSlowLineMarkers(@NotNull List<PsiElement> psiElements, @NotNu
6772
} else if (TwigPattern.getBlockTagPattern().accepts(psiElement) || TwigPattern.getPrintBlockFunctionPattern("block").accepts(psiElement)) {
6873
// blocks: {% block 'foobar' %}, {{ block('foobar') }}
6974

70-
LineMarkerInfo lineImpl = this.attachBlockImplements(psiElement);
75+
VirtualFile virtualFile = psiElement.getContainingFile().getVirtualFile();
76+
if(!implementsMap.containsKey(virtualFile)) {
77+
implementsMap.put(virtualFile, new FileImplementsLazyLoader(psiElement.getProject(), virtualFile));
78+
}
79+
80+
LineMarkerInfo lineImpl = attachBlockImplements(psiElement, implementsMap.get(virtualFile));
7181
if(lineImpl != null) {
7282
results.add(lineImpl);
7383
}
7484

75-
LineMarkerInfo lineOverwrites = this.attachBlockOverwrites(psiElement);
85+
if(fileOverwritesLazyLoader == null) {
86+
fileOverwritesLazyLoader = new FileOverwritesLazyLoader(psiElements.get(0).getProject());
87+
}
88+
89+
LineMarkerInfo lineOverwrites = attachBlockOverwrites(psiElement, fileOverwritesLazyLoader);
7690
if(lineOverwrites != null) {
7791
results.add(lineOverwrites);
7892
}
@@ -179,52 +193,64 @@ private LineMarkerInfo getRelatedPopover(String singleItemTitle, String singleIt
179193
}
180194

181195
@Nullable
182-
private LineMarkerInfo attachBlockImplements(@NotNull PsiElement psiElement) {
183-
Collection<PsiElement> blockTargets = TwigUtil.getBlocksByImplementations(psiElement);
184-
if(blockTargets.size() == 0) {
196+
private LineMarkerInfo attachBlockImplements(@NotNull PsiElement psiElement, @NotNull FileImplementsLazyLoader implementsLazyLoader) {
197+
if(!TwigBlockUtil.hasBlockImplementations(psiElement, implementsLazyLoader)) {
185198
return null;
186199
}
187200

188-
List<GotoRelatedItem> gotoRelatedItems = new ArrayList<>();
189-
for(PsiElement blockTag: blockTargets) {
190-
gotoRelatedItems.add(new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(blockTag, TwigUtil.getPresentableTemplateName(blockTag, true)).withIcon(TwigIcons.TwigFileIcon, Symfony2Icons.TWIG_LINE_MARKER));
201+
NavigationGutterIconBuilder<PsiElement> builder = NavigationGutterIconBuilder.create(PhpIcons.IMPLEMENTED)
202+
.setTargets(new BlockImplementationLazyValue(psiElement))
203+
.setTooltipText("Implementations")
204+
.setCellRenderer(new MyBlockListCellRenderer());
205+
206+
return builder.createLineMarkerInfo(psiElement);
207+
}
208+
209+
private static class BlockImplementationLazyValue extends NotNullLazyValue<Collection<? extends PsiElement>> {
210+
@NotNull
211+
private final PsiElement psiElement;
212+
213+
BlockImplementationLazyValue(@NotNull PsiElement psiElement) {
214+
this.psiElement = psiElement;
191215
}
192216

193-
return getRelatedPopover("Implementations", "Impl: ", psiElement, gotoRelatedItems, PhpIcons.IMPLEMENTED);
217+
@NotNull
218+
@Override
219+
protected Collection<? extends PsiElement> compute() {
220+
return TwigBlockUtil.getBlockImplementationTargets(psiElement);
221+
}
194222
}
195223

196-
@Nullable
197-
private LineMarkerInfo attachBlockOverwrites(PsiElement psiElement) {
198-
Collection<PsiElement> blocks = TwigTemplateGoToDeclarationHandler.getBlockGoTo(psiElement);
199-
if(blocks.size() == 0) {
200-
return null;
224+
/**
225+
* Provides lazy targets for given template scope
226+
*/
227+
private static class BlockOverwriteLazyValue extends NotNullLazyValue<Collection<? extends PsiElement>> {
228+
@NotNull
229+
private final PsiElement psiElement;
230+
231+
BlockOverwriteLazyValue(@NotNull PsiElement psiElement) {
232+
this.psiElement = psiElement;
201233
}
202234

203-
List<GotoRelatedItem> gotoRelatedItems = new ArrayList<>();
204-
for(PsiElement blockTag: blocks) {
205-
gotoRelatedItems.add(new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(
206-
blockTag,
207-
TwigUtil.getPresentableTemplateName(blockTag, true)
208-
).withIcon(TwigIcons.TwigFileIcon, Symfony2Icons.TWIG_LINE_MARKER));
235+
@NotNull
236+
@Override
237+
protected Collection<? extends PsiElement> compute() {
238+
return TwigBlockUtil.getBlockOverwriteTargets(psiElement);
209239
}
240+
}
210241

211-
// single item has no popup
212-
String title = "Overwrites";
213-
if(gotoRelatedItems.size() == 1) {
214-
String customName = gotoRelatedItems.get(0).getCustomName();
215-
if(customName != null) {
216-
title = title.concat(": ").concat(customName);
217-
}
242+
@Nullable
243+
private LineMarkerInfo attachBlockOverwrites(PsiElement psiElement, @NotNull FileOverwritesLazyLoader loader) {
244+
if(!TwigBlockUtil.hasBlockOverwrites(psiElement, loader)) {
245+
return null;
218246
}
219247

220-
return new LineMarkerInfo<>(
221-
psiElement,
222-
psiElement.getTextRange(),
223-
PhpIcons.OVERRIDES, 6,
224-
new ConstantFunction<>(title),
225-
new RelatedPopupGotoLineMarker.NavigationHandler(gotoRelatedItems),
226-
GutterIconRenderer.Alignment.RIGHT
227-
);
248+
NavigationGutterIconBuilder<PsiElement> builder = NavigationGutterIconBuilder.create(PhpIcons.OVERRIDES)
249+
.setTargets(new BlockOverwriteLazyValue(psiElement))
250+
.setTooltipText("Overwrites")
251+
.setCellRenderer(new MyBlockListCellRenderer());
252+
253+
return builder.createLineMarkerInfo(psiElement);
228254
}
229255

230256
@Nullable
@@ -323,4 +349,29 @@ protected Collection<? extends PsiElement> compute() {
323349
return targets;
324350
}
325351
}
352+
353+
private static class MyBlockListCellRenderer extends PsiElementListCellRenderer {
354+
@Override
355+
public String getElementText(PsiElement psiElement) {
356+
return StringUtils.abbreviate(
357+
SymbolPresentationUtil.getSymbolPresentableText(psiElement),
358+
50
359+
);
360+
}
361+
362+
@Override
363+
protected String getContainerText(PsiElement psiElement, String s) {
364+
return TwigUtil.getPresentableTemplateName(psiElement, true);
365+
}
366+
367+
@Override
368+
protected int getIconFlags() {
369+
return 1;
370+
}
371+
372+
@Override
373+
protected Icon getIcon(PsiElement psiElement) {
374+
return TwigIcons.TwigFileIcon;
375+
}
376+
}
326377
}

Diff for: src/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@
2424
import fr.adrienbrault.idea.symfony2plugin.asset.provider.AssetCompletionProvider;
2525
import fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper;
2626
import fr.adrienbrault.idea.symfony2plugin.templating.completion.QuotedInsertionLookupElement;
27-
import fr.adrienbrault.idea.symfony2plugin.templating.dict.*;
27+
import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigExtension;
28+
import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigExtensionLookupElement;
29+
import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigMacro;
30+
import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigMacroTagInterface;
2831
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigExtensionParser;
2932
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigTypeResolveUtil;
3033
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
3134
import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigTypeContainer;
3235
import fr.adrienbrault.idea.symfony2plugin.templating.variable.dict.PsiVariable;
3336
import fr.adrienbrault.idea.symfony2plugin.translation.dict.TranslationUtil;
37+
import fr.adrienbrault.idea.symfony2plugin.twig.utils.TwigFileUtil;
3438
import fr.adrienbrault.idea.symfony2plugin.twig.variable.collector.ControllerDocVariableCollector;
3539
import fr.adrienbrault.idea.symfony2plugin.twig.variable.globals.TwigGlobalEnum;
3640
import fr.adrienbrault.idea.symfony2plugin.twig.variable.globals.TwigGlobalVariable;
@@ -576,7 +580,7 @@ public void addCompletions(@NotNull CompletionParameters parameters, ProcessingC
576580

577581
myResultSet.addAllElements(TwigUtil.getBlockLookupElements(
578582
position.getProject(),
579-
new TwigBlockParser(scopedContext.getSecond()).visit(scopedContext.getFirst())
583+
TwigFileUtil.collectParentFiles(scopedContext.getSecond(), scopedContext.getFirst())
580584
));
581585
}
582586
}

0 commit comments

Comments
 (0)