Skip to content

Commit 7308cbe

Browse files
committed
completion for variables of template rendering #1052
1 parent a384d2d commit 7308cbe

File tree

12 files changed

+214
-7
lines changed

12 files changed

+214
-7
lines changed

META-INF/plugin.xml

+1
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@
582582
<GotoCompletionRegistrar implementation="fr.adrienbrault.idea.symfony2plugin.security.VoterGotoCompletionRegistrar"/>
583583
<GotoCompletionRegistrar implementation="fr.adrienbrault.idea.symfony2plugin.translation.TranslationPlaceholderGotoCompletionRegistrar"/>
584584
<GotoCompletionRegistrar implementation="fr.adrienbrault.idea.symfony2plugin.dic.TaggedParameterGotoCompletionRegistrar"/>
585+
<GotoCompletionRegistrar implementation="fr.adrienbrault.idea.symfony2plugin.templating.RenderParameterGotoCompletionRegistrar"/>
585586

586587
<TwigNamespaceExtension implementation="fr.adrienbrault.idea.symfony2plugin.templating.path.JsonFileIndexTwigNamespaces"/>
587588
<TwigNamespaceExtension implementation="fr.adrienbrault.idea.symfony2plugin.templating.path.ConfigAddPathTwigNamespaces"/>

src/fr/adrienbrault/idea/symfony2plugin/TwigHelper.java

+2-6
Original file line numberDiff line numberDiff line change
@@ -271,15 +271,11 @@ public static String normalizeTemplateName(String templateName) {
271271
* @param templateName path known, should not be normalized
272272
* @return target files
273273
*/
274-
public static PsiFile[] getTemplatePsiElements(Project project, String templateName) {
275-
276-
274+
public static PsiFile[] getTemplatePsiElements(@NotNull Project project, @NotNull String templateName) {
277275
String normalizedTemplateName = normalizeTemplateName(templateName);
278276

279277
Collection<PsiFile> psiFiles = new HashSet<>();
280-
281278
for (TwigPath twigPath : getTwigNamespaces(project)) {
282-
283279
if(!twigPath.isEnabled()) {
284280
continue;
285281
}
@@ -392,7 +388,7 @@ private static Collection<PsiFile> getTemplateOverwrites(@NotNull Project projec
392388
return PsiElementUtils.convertVirtualFilesToPsiFiles(project, files);
393389
}
394390

395-
private static void addFileInsideTwigPath(Project project, String templatePath, Collection<PsiFile> psiFiles, TwigPath twigPath) {
391+
private static void addFileInsideTwigPath(@NotNull Project project, @NotNull String templatePath, @NotNull Collection<PsiFile> psiFiles, @NotNull TwigPath twigPath) {
396392
String[] split = templatePath.split("/");
397393
VirtualFile virtualFile = VfsUtil.findRelativeFile(twigPath.getDirectory(project), split);
398394
if(virtualFile != null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package fr.adrienbrault.idea.symfony2plugin.templating;
2+
3+
import com.intellij.codeInsight.lookup.LookupElement;
4+
import com.intellij.codeInsight.lookup.LookupElementBuilder;
5+
import com.intellij.patterns.PlatformPatterns;
6+
import com.intellij.psi.PsiElement;
7+
import com.intellij.psi.PsiFile;
8+
import com.jetbrains.php.lang.psi.elements.ArrayCreationExpression;
9+
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
10+
import com.jetbrains.twig.TwigFile;
11+
import fr.adrienbrault.idea.symfony2plugin.TwigHelper;
12+
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
13+
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrar;
14+
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrarParameter;
15+
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
16+
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
17+
import icons.TwigIcons;
18+
import org.jetbrains.annotations.NotNull;
19+
20+
import java.util.ArrayList;
21+
import java.util.Collection;
22+
import java.util.Collections;
23+
24+
/**
25+
* render('foo.html.twig', ['fo<caret>obar' => 'foobar'])
26+
*
27+
* @author Daniel Espendiller <daniel@espendiller.net>
28+
*/
29+
public class RenderParameterGotoCompletionRegistrar implements GotoCompletionRegistrar {
30+
@Override
31+
public void register(GotoCompletionRegistrarParameter registrar) {
32+
registrar.register(
33+
PlatformPatterns.psiElement(),
34+
MyGotoCompletionProvider::new
35+
);
36+
}
37+
38+
private static class MyGotoCompletionProvider extends GotoCompletionProvider {
39+
private MyGotoCompletionProvider(PsiElement psiElement) {
40+
super(psiElement);
41+
}
42+
43+
@NotNull
44+
@Override
45+
public Collection<LookupElement> getLookupElements() {
46+
PsiElement parent = getElement().getParent();
47+
if(!(parent instanceof StringLiteralExpression)) {
48+
return Collections.emptyList();
49+
}
50+
51+
ArrayCreationExpression arrayCreationElement = PhpElementsUtil.getCompletableArrayCreationElement(parent);
52+
if(arrayCreationElement == null) {
53+
return Collections.emptyList();
54+
}
55+
56+
PsiElement prevSibling = arrayCreationElement.getPrevPsiSibling();
57+
if(prevSibling == null) {
58+
return Collections.emptyList();
59+
}
60+
61+
String stringValue = PhpElementsUtil.getStringValue(prevSibling);
62+
if(stringValue == null || !stringValue.toLowerCase().endsWith(".twig")) {
63+
return Collections.emptyList();
64+
}
65+
66+
Collection<LookupElement> elements = new ArrayList<>();
67+
68+
for (PsiFile psiFile : TwigHelper.getTemplatePsiElements(getProject(), stringValue)) {
69+
if(!(psiFile instanceof TwigFile)) {
70+
continue;
71+
}
72+
73+
TwigUtil.visitTemplateVariables((TwigFile) psiFile, pair ->
74+
elements.add(LookupElementBuilder.create(pair.getFirst())
75+
.withIcon(TwigIcons.TwigFileIcon)
76+
.withTypeText(stringValue, true)
77+
));
78+
}
79+
80+
return elements;
81+
}
82+
}
83+
}

src/fr/adrienbrault/idea/symfony2plugin/templating/path/TwigPath.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,22 @@ public String getRelativePath(Project project) {
8686
}
8787

8888
@Nullable
89-
public VirtualFile getDirectory(Project project) {
89+
public VirtualFile getDirectory(@NotNull Project project) {
9090
String relativePath = this.getRelativePath(project);
9191
if(relativePath == null) {
9292
return null;
9393
}
9494

95+
//LocalFileSystem.getInstance().
96+
//VirtualFile fileByPath = LocalFileSystem.getInstance().findFileByPath("ide-twig.json");
97+
//VfsUtilCore.getRelativeLocation()
98+
//BuildProperties.getProjectBuildFileName()
99+
//VirtualFile baseDir = project.getBaseDir();
100+
101+
//VirtualFile[] children = baseDir.getChildren();
102+
103+
//VirtualFile fileByRelativePath = baseDir.findFileByRelativePath(relativePath);
104+
95105
return VfsUtil.findRelativeFile(relativePath, project.getBaseDir());
96106
}
97107

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

+13
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,19 @@ public static void visitTokenParsers(@NotNull Project project, @NotNull Consumer
13841384
}
13851385
}
13861386

1387+
/**
1388+
* Visit all template variables which are completion in Twig rendering call as array
1389+
*/
1390+
public static void visitTemplateVariables(@NotNull TwigFile twigFile, @NotNull Consumer<Pair<String, PsiElement>> consumer) {
1391+
for (TwigSet twigSet : TwigUtil.getSetDeclaration(twigFile)) {
1392+
consumer.consume(Pair.create(twigSet.getName(), null));
1393+
}
1394+
1395+
for (String variable : TwigTypeResolveUtil.findFileVariableDocBlock(twigFile).keySet()) {
1396+
consumer.consume(Pair.create(variable, null));
1397+
}
1398+
}
1399+
13871400
public static class DomainScope {
13881401
@NotNull
13891402
private final String defaultDomain;

src/fr/adrienbrault/idea/symfony2plugin/util/PsiElementUtils.java

+21
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,27 @@ public static MethodReference getMethodReferenceWithFirstStringParameter(PsiElem
245245
return (MethodReference) parameterList.getContext();
246246
}
247247

248+
@Nullable
249+
public static MethodReference getFunctionReferenceWithFirstStringParameter(@NotNull PsiElement psiElement) {
250+
if(!PlatformPatterns.psiElement()
251+
.withParent(StringLiteralExpression.class).inside(ParameterList.class)
252+
.withLanguage(PhpLanguage.INSTANCE).accepts(psiElement)) {
253+
254+
return null;
255+
}
256+
257+
ParameterList parameterList = PsiTreeUtil.getParentOfType(psiElement, ParameterList.class);
258+
if (parameterList == null) {
259+
return null;
260+
}
261+
262+
if (!(parameterList.getContext() instanceof FunctionReference)) {
263+
return null;
264+
}
265+
266+
return (MethodReference) parameterList.getContext();
267+
}
268+
248269
public static String trimQuote(String text) {
249270
return text.replaceAll("^\"|\"$|\'|\'$", "");
250271
}

src/fr/adrienbrault/idea/symfony2plugin/util/VfsExUtil.java

+14
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import com.intellij.openapi.project.Project;
55
import com.intellij.openapi.vfs.VfsUtil;
66
import com.intellij.openapi.vfs.VirtualFile;
7+
import org.apache.commons.lang.StringUtils;
78
import org.jetbrains.annotations.NotNull;
9+
import org.jetbrains.annotations.Nullable;
810

911
/**
1012
* @author Daniel Espendiller <daniel@espendiller.net>
@@ -16,6 +18,18 @@ public static String getRelativeProjectPath(@NotNull Project project, @NotNull V
1618
if(ApplicationManager.getApplication().isUnitTestMode()) {
1719
return virtualFile.getPath();
1820
}
21+
1922
return VfsUtil.getRelativePath(virtualFile, project.getBaseDir());
2023
}
24+
25+
@Nullable
26+
public static VirtualFile findRelativeFile(@Nullable VirtualFile base, String... path) {
27+
// hacking around project as temp file
28+
if(ApplicationManager.getApplication().isUnitTestMode()) {
29+
VirtualFile fileByRelativePath = base.findFileByRelativePath(StringUtils.join(path, "/"));
30+
return fileByRelativePath;
31+
}
32+
33+
return VfsUtil.findRelativeFile(base, path);
34+
}
2135
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package fr.adrienbrault.idea.symfony2plugin.tests.templating;
2+
3+
import com.intellij.openapi.vfs.VirtualFile;
4+
import com.jetbrains.php.lang.PhpFileType;
5+
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
6+
7+
import java.io.File;
8+
9+
/**
10+
* @author Daniel Espendiller <daniel@espendiller.net>
11+
* @see fr.adrienbrault.idea.symfony2plugin.templating.RenderParameterGotoCompletionRegistrar
12+
*/
13+
public class RenderParameterGotoCompletionRegistrarTest extends SymfonyLightCodeInsightFixtureTestCase {
14+
public void setUp() throws Exception {
15+
super.setUp();
16+
17+
myFixture.copyFileToProject("ide-twig.json", "ide-twig.json");
18+
VirtualFile virtualFile = myFixture.copyFileToProject("RenderParameterGotoCompletionRegistrar.html.twig", "res/foobar.html.twig");
19+
20+
System.out.println(virtualFile.getPath());
21+
System.out.println(virtualFile);
22+
}
23+
24+
public String getTestDataPath() {
25+
return new File(this.getClass().getResource("fixtures").getFile()).getAbsolutePath();
26+
}
27+
28+
public void testFoo() {
29+
assertCompletionContains(
30+
PhpFileType.INSTANCE,
31+
"<?php foo('foobar.html.twig', ['<caret>']);",
32+
"foobar"
33+
);
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{ foobar }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"namespaces": [
3+
{
4+
"path": "res"
5+
}
6+
]
7+
}

tests/fr/adrienbrault/idea/symfony2plugin/tests/templating/util/TwigUtilTest.java

+17
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.intellij.openapi.vfs.VirtualFile;
66
import com.intellij.psi.*;
77
import com.intellij.psi.tree.IElementType;
8+
import com.intellij.util.Consumer;
89
import com.intellij.util.containers.ContainerUtil;
910
import com.jetbrains.php.lang.PhpFileType;
1011
import com.jetbrains.php.lang.psi.elements.Function;
@@ -498,6 +499,22 @@ public void testGetTwigFileDomainScope() {
498499
assertEquals("bar", twigFileDomainScope.getDomain());
499500
}
500501

502+
public void testVisitTemplateVariables() {
503+
PsiFile psiFile = myFixture.configureByFile("variables.html.twig");
504+
505+
Map<String, PsiElement> elementMap = new HashMap<>();
506+
507+
TwigUtil.visitTemplateVariables((TwigFile) psiFile, pair ->
508+
elementMap.put(pair.getFirst(), pair.getSecond())
509+
);
510+
511+
// set declaration
512+
assertContainsElements(elementMap.keySet(), "set_foo");
513+
514+
// file doc
515+
assertContainsElements(elementMap.keySet(), "inline_foo");
516+
}
517+
501518
private PsiElement createPsiElementAndFindString(@NotNull String content, @NotNull IElementType type) {
502519
PsiElement psiElement = TwigElementFactory.createPsiElement(getProject(), content, type);
503520
if(psiElement == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% set set_foo = '' %}
2+
3+
{% if if_foo %}{% endif %}
4+
5+
{{ print_foo }}
6+
7+
{% for foo in bar %}{% endfor %}
8+
9+
{# @var inline_foo bar #}

0 commit comments

Comments
 (0)