Skip to content

Commit

Permalink
allow path navigation for templates #1076
Browse files Browse the repository at this point in the history
  • Loading branch information
Haehnchen committed Nov 13, 2017
1 parent e8e1ae8 commit ccf9187
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 17 deletions.
143 changes: 140 additions & 3 deletions src/fr/adrienbrault/idea/symfony2plugin/TwigHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,6 @@ private static TwigNamespaceSetting findManagedTwigNamespace(@NotNull Project pr
* todo: provide setting for that
*/
public static String normalizeTemplateName(@NotNull String templateName) {

// force linux path style
templateName = templateName.replace("\\", "/");

Expand Down Expand Up @@ -336,16 +335,154 @@ public static PsiFile[] getTemplatePsiElements(@NotNull Project project, @NotNul
String templatePath = StringUtils.strip(normalizedTemplateName.replace(":", "/").replace("//", "/"), "/");
addFileInsideTwigPath(project, templatePath, psiFiles, twigPath);
}

}

}

psiFiles.addAll(getTemplateOverwrites(project, normalizedTemplateName));

return psiFiles.toArray(new PsiFile[psiFiles.size()]);
}

/**
* Switch template target on caret offset "foo/bar.html.twig". Resolve template name or directory structure:
*
* "foo" "bar.html.twig"
*/
@NotNull
public static Collection<PsiElement> getTemplateNavigationOnOffset(@NotNull Project project, @NotNull String templateName, int offset) {
Collection<PsiElement> files = new ArrayList<>();

if(offset < templateName.length()) {
String templateNameWithCaret = normalizeTemplateName(new StringBuilder(templateName).insert(offset, '\u0182').toString());
offset = templateNameWithCaret.indexOf('\u0182');

int i = StringUtils.strip(templateNameWithCaret.replace(String.valueOf('\u0182'), "").replace(":", "/"), "/").indexOf("/", offset);
if(i > 0) {
files.addAll(getTemplateTargetOnOffset(project, templateName, offset));
}
}

// full filepath fallback: "foo/foo<caret>.html.twig"
if(files.size() == 0) {
files.addAll(Arrays.asList(getTemplatePsiElements(project, templateName)));
}

return files;
}

/**
* Switch template target on caret offset "foo/bar.html.twig". Resolve template name or directory structure:
*
* "foo" "bar.html.twig"
*/
@NotNull
public static Collection<PsiElement> getTemplateTargetOnOffset(@NotNull Project project, @NotNull String templateName, int offset) {
// no match for length
if(offset > templateName.length()) {
return Collections.emptyList();
}

// please give use a normalized path:
// Foo:foo:foo => foo/foo/foo
String templatePathWithFileName = normalizeTemplateName(new StringBuilder(templateName).insert(offset, '\u0182').toString());
offset = templatePathWithFileName.indexOf('\u0182');

int indexOf = templatePathWithFileName.replace(":", "/").indexOf("/", offset);
if(indexOf <= 0) {
return Collections.emptyList();
}

String templatePath = StringUtils.strip(templatePathWithFileName.substring(0, indexOf).replace(String.valueOf('\u0182'), ""), "/");

Collection<VirtualFile> virtualFiles = new ArrayList<>();

for (TwigPath twigPath : getTwigNamespaces(project)) {
if(!twigPath.isEnabled()) {
continue;
}

if(templatePath.startsWith("@")) {
// @Namespace/base.html.twig
// @Namespace/folder/base.html.twig

if(templatePath.length() > 1 && twigPath.getNamespaceType() != TwigPathIndex.NamespaceType.BUNDLE) {
int x = templatePath.indexOf("/");

if(x < 0 && templatePath.substring(1).equals(twigPath.getNamespace())) {
// Click on namespace itself: "@Foobar"
VirtualFile relativeFile = twigPath.getDirectory(project);
if (relativeFile != null) {
virtualFiles.add(relativeFile);
}
} else if (x > 0 && templatePath.substring(1, x).equals(twigPath.getNamespace())) {
// Click on path: "@Foobar/Foo"
VirtualFile relativeFile = VfsUtil.findRelativeFile(twigPath.getDirectory(project), templatePath.substring(x + 1).split("/"));
if (relativeFile != null) {
virtualFiles.add(relativeFile);
}
}
}
} else if(templatePath.startsWith(":")) {
// ::base.html.twig
// :Foo:base.html.twig
if(twigPath.getNamespaceType() == TwigPathIndex.NamespaceType.BUNDLE && twigPath.isGlobalNamespace()) {
String replace = StringUtils.strip(templatePath.replace(":", "/"), "/");

VirtualFile relativeFile = VfsUtil.findRelativeFile(twigPath.getDirectory(project), replace.split("/"));
if(relativeFile != null) {
virtualFiles.add(relativeFile);
}
}
} else {
// FooBundle::base.html.twig
// FooBundle:Bar:base.html.twig
if(twigPath.getNamespaceType() == TwigPathIndex.NamespaceType.BUNDLE) {
templatePath = templatePath.replace(":", "/");
int x = templatePath.indexOf("/");

if(x < 0 && templatePath.equals(twigPath.getNamespace())) {
// Click on namespace itself: "FooBundle"
VirtualFile relativeFile = twigPath.getDirectory(project);
if (relativeFile != null) {
virtualFiles.add(relativeFile);
}
} else if(x > 0 && templatePath.substring(0, x).equals(twigPath.getNamespace())) {
// Click on path: "FooBundle/Foo"
VirtualFile relativeFile = VfsUtil.findRelativeFile(twigPath.getDirectory(project), templatePath.substring(x + 1).split("/"));
if (relativeFile != null) {
virtualFiles.add(relativeFile);
}
}
}

// form_div_layout.html.twig
if(twigPath.isGlobalNamespace() && twigPath.getNamespaceType() == TwigPathIndex.NamespaceType.ADD_PATH) {
int i = templateName.indexOf("/", offset);
if(i > 0) {
String substring = templateName.substring(0, i);

VirtualFile relativeFile = VfsUtil.findRelativeFile(twigPath.getDirectory(project), substring);
if(relativeFile != null) {
virtualFiles.add(relativeFile);
}
}
}

// Bundle overwrite:
// FooBundle:index.html -> app/views/FooBundle:index.html
if(twigPath.isGlobalNamespace() && !templatePath.startsWith(":") && !templatePath.startsWith("@")) {
// @TODO: support this later on; also its deprecated by Symfony
}
}
}

return virtualFiles
.stream()
.map(virtualFile -> PsiManager.getInstance(project).findDirectory(virtualFile))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

/**
* Resolve TwigFile to its possible template names:
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
Expand Down Expand Up @@ -36,7 +37,7 @@ public class TwigTemplateGoToDeclarationHandler implements GotoDeclarationHandle

@Nullable
@Override
public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int i, Editor editor) {
public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int offset, Editor editor) {

if(!Symfony2ProjectComponent.isEnabled(psiElement) || !PlatformPatterns.psiElement().withLanguage(TwigLanguage.INSTANCE).accepts(psiElement)) {
return null;
Expand All @@ -55,7 +56,8 @@ public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int i, Edit

// support: {% include() %}, {{ include() }}
if(TwigHelper.getTemplateFileReferenceTagPattern().accepts(psiElement) || TwigHelper.getPrintBlockFunctionPattern("include", "source").accepts(psiElement)) {
return this.getTwigFiles(psiElement);
Collection<PsiElement> twigFiles = this.getTwigFiles(psiElement, offset);
return twigFiles.toArray(new PsiElement[twigFiles.size()]);
}

if(TwigHelper.getAutocompletableRoutePattern().accepts(psiElement)) {
Expand Down Expand Up @@ -85,7 +87,8 @@ public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int i, Edit
if (PlatformPatterns.psiElement(TwigTokenTypes.STRING_TEXT)
.withText(PlatformPatterns.string().endsWith(".twig")).accepts(psiElement)) {

return this.getTwigFiles(psiElement);
Collection<PsiElement> twigFiles = this.getTwigFiles(psiElement, offset);
return twigFiles.toArray(new PsiElement[twigFiles.size()]);
}

if(TwigHelper.getPrintBlockOrTagFunctionPattern("controller").accepts(psiElement) || TwigHelper.getStringAfterTagNamePattern("render").accepts(psiElement)) {
Expand Down Expand Up @@ -173,17 +176,13 @@ private Collection<PsiElement> getControllerGoTo(@NotNull PsiElement psiElement
return Arrays.asList(RouteHelper.getMethodsOnControllerShortcut(psiElement.getProject(), text));
}

@Nullable
private PsiElement[] getTwigFiles(PsiElement psiElement) {

String templateName = psiElement.getText();
PsiElement[] psiElements = TwigHelper.getTemplatePsiElements(psiElement.getProject(), templateName);

if(psiElements.length == 0) {
return null;
}

return psiElements;
@NotNull
private Collection<PsiElement> getTwigFiles(@NotNull PsiElement psiElement, int offset) {
return TwigHelper.getTemplateNavigationOnOffset(
psiElement.getProject(),
psiElement.getText(),
offset - psiElement.getTextRange().getStartOffset()
);
}

private PsiElement[] getFilterGoTo(PsiElement psiElement) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package fr.adrienbrault.idea.symfony2plugin.tests;

import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import fr.adrienbrault.idea.symfony2plugin.Settings;
import fr.adrienbrault.idea.symfony2plugin.TwigHelper;
Expand All @@ -8,7 +10,9 @@
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;

/**
* @author Daniel Espendiller <[email protected]>
Expand Down Expand Up @@ -86,6 +90,55 @@ public void testGetTwigAndPhpTemplateFiles() {
);
}

/**
* @see fr.adrienbrault.idea.symfony2plugin.TwigHelper#getTemplateNavigationOnOffset
*/
public void testGetTemplateNavigationOnOffset() {
createFiles("res/foobar/foo.html.twig");

Settings.getInstance(getProject()).twigNamespaces.addAll(createTwigNamespaceSettings());

assertTrue(TwigHelper.getTemplateNavigationOnOffset(getProject(), "foobar/foo.html.twig", 3).stream().filter(psiElement -> psiElement instanceof PsiDirectory && "foobar".equals(((PsiDirectory) psiElement).getName())).count() > 0);
assertTrue(TwigHelper.getTemplateNavigationOnOffset(getProject(), ":foobar:foo.html.twig", 3).stream().filter(psiElement -> psiElement instanceof PsiDirectory && "foobar".equals(((PsiDirectory) psiElement).getName())).count() > 0);

assertTrue(TwigHelper.getTemplateNavigationOnOffset(getProject(), "foobar/foo.html.twig", 10).stream().filter(psiElement -> psiElement instanceof PsiFile && "foo.html.twig".equals(((PsiFile) psiElement).getName())).count() > 0);

assertTrue(TwigHelper.getTemplateTargetOnOffset(getProject(), "foo.html.twig", 40).size() == 0);
}

/**
* @see fr.adrienbrault.idea.symfony2plugin.TwigHelper#getTemplateTargetOnOffset
*/
public void testGetTemplateTargetOnOffset() {
createFiles("res/foobar/foo.html.twig");
createFiles("res/foobar/apple/foo.html.twig");

Settings.getInstance(getProject()).twigNamespaces.addAll(createTwigNamespaceSettings());

assertIsDirectoryAtOffset("@Foo/foobar/foo.html.twig", 2, "res");
assertIsDirectoryAtOffset("@Foo/foobar\\foo.html.twig", 6, "foobar");

assertIsDirectoryAtOffset( "foobar/foo.html.twig", 3, "foobar");
assertIsDirectoryAtOffset("@Foo/foobar/foo.html.twig", 6, "foobar");
assertIsDirectoryAtOffset("@Foo/foobar\\foo.html.twig", 6, "foobar");

assertIsDirectoryAtOffset( "@Foo/foobar/foo.html.twig", 3, "res");
assertIsDirectoryAtOffset("FooBundle:foobar:foo.html.twig", 6, "res");
assertIsDirectoryAtOffset("FooBundle:foobar:foo.html.twig", 13, "foobar");
assertIsDirectoryAtOffset(":foobar:foo.php", 4, "foobar");

assertIsDirectoryAtOffset(":foobar/apple:foo.php", 10, "apple");
assertIsDirectoryAtOffset(":foobar\\apple:foo.php", 10, "apple");
assertIsDirectoryAtOffset(":foobar\\apple\\foo.php", 10, "apple");

assertTrue(TwigHelper.getTemplateTargetOnOffset(getProject(), "@Foo/foobar/foo.html.twig", 15).size() == 0);
assertTrue(TwigHelper.getTemplateTargetOnOffset(getProject(), "foo.html.twig", 40).size() == 0);
}

private void assertIsDirectoryAtOffset(@NotNull String templateName, int offset, @NotNull String directory) {
assertTrue(TwigHelper.getTemplateTargetOnOffset(getProject(), templateName, offset).stream().filter(psiElement -> psiElement instanceof PsiDirectory && directory.equals(((PsiDirectory) psiElement).getName())).count() > 0);
}

@NotNull
private List<TwigNamespaceSetting> createTwigNamespaceSettings() {
return Arrays.asList(
Expand Down

0 comments on commit ccf9187

Please sign in to comment.