Skip to content
This repository has been archived by the owner on Mar 12, 2022. It is now read-only.

Custom blade directives #103

Merged
merged 3 commits into from Feb 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions META-INF/plugin.xml
Expand Up @@ -164,6 +164,7 @@
<fileBasedIndex implementation="de.espend.idea.laravel.stub.BladeStackStubIndex"/>
<fileBasedIndex implementation="de.espend.idea.laravel.stub.BladeEachStubIndex"/>
<fileBasedIndex implementation="de.espend.idea.laravel.stub.BladeYieldStubIndex"/>
<fileBasedIndex implementation="de.espend.idea.laravel.stub.BladeCustomDirectivesStubIndex"/>

<fileBasedIndex implementation="de.espend.idea.laravel.stub.TranslationKeyStubIndex"/>
<fileBasedIndex implementation="de.espend.idea.laravel.stub.ConfigKeyStubIndex"/>
Expand Down
105 changes: 104 additions & 1 deletion src/de/espend/idea/laravel/blade/BladeDirectiveReferences.java
@@ -1,14 +1,19 @@
package de.espend.idea.laravel.blade;

import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.ProjectScope;
import com.intellij.util.indexing.FileBasedIndex;
import com.jetbrains.php.blade.BladeFileType;
import com.jetbrains.php.blade.BladeLanguage;
import com.jetbrains.php.blade.psi.BladeDirectiveElementType;
import com.jetbrains.php.blade.psi.BladePsiLanguageInjectionHost;
import com.jetbrains.php.blade.psi.BladeTokenTypes;
Expand All @@ -19,6 +24,9 @@
import de.espend.idea.laravel.LaravelProjectComponent;
import de.espend.idea.laravel.blade.util.BladePsiUtil;
import de.espend.idea.laravel.blade.util.BladeTemplateUtil;
import de.espend.idea.laravel.stub.BladeCustomDirectivesStubIndex;
import de.espend.idea.laravel.stub.processor.BladeCustomDirectivesVisitor;
import de.espend.idea.laravel.stub.processor.CollectProjectUniqueKeys;
import de.espend.idea.laravel.translation.TranslationReferences;
import de.espend.idea.laravel.view.ViewCollector;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
Expand Down Expand Up @@ -101,6 +109,15 @@ public void register(GotoCompletionRegistrarParameter registrar) {

return new MyInjectedClassGotoCompletionProvider(psiElement);
});

registrar.register(PlatformPatterns.psiElement().withLanguage(BladeLanguage.INSTANCE)
.withElementType(BladeTokenTypes.CUSTOM_DIRECTIVE), psiElement -> {
if(psiElement == null || !LaravelProjectComponent.isEnabled(psiElement)) {
return null;
}

return new CustomDirectivesGotoCompletionProvider(psiElement);
});
}

private boolean isDirectiveWithName(PsiElement psiElement, String directiveName) {
Expand Down Expand Up @@ -156,7 +173,7 @@ public Collection<PsiElement> getPsiTargets(StringLiteralExpression element) {
if(StringUtils.isBlank(contents)) {
return Collections.emptyList();
}

contents = contents.replace("/", ".");
final Collection<PsiElement> psiElements = new ArrayList<>();

Expand Down Expand Up @@ -284,4 +301,90 @@ public Collection<PsiElement> getPsiTargets(PsiElement element) {
);
}
}

private static class CustomDirectivesGotoCompletionProvider extends GotoCompletionProvider {

public CustomDirectivesGotoCompletionProvider(@NotNull PsiElement element) {
super(element);
}

@NotNull
@Override
public Collection<LookupElement> getLookupElements() {

final List<LookupElement> lookupElementList = new ArrayList<>();

Set<String> directiveNames = CollectProjectUniqueKeys.collect(getProject(), BladeCustomDirectivesStubIndex.KEY);

for(String directiveName: directiveNames) {
lookupElementList.add(new BladeCustomDirectiveLookup(directiveName + "()"));
}

return lookupElementList;
}

@NotNull
@Override
public Collection<PsiElement> getPsiTargets(StringLiteralExpression element) {
return Collections.emptyList();
}

@NotNull
@Override
public Collection<PsiElement> getPsiTargets(PsiElement psiElement) {

Collection<PsiElement> targets = new ArrayList<>();

String directiveName = psiElement.getText().substring(1);

FileBasedIndex.getInstance().getFilesWithKey(
BladeCustomDirectivesStubIndex.KEY,
new HashSet<>(Collections.singletonList(directiveName)),
virtualFile -> {

PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(virtualFile);

if(psiFile == null) {
return true;
}

psiFile.acceptChildren(new BladeCustomDirectivesVisitor(hit -> {
if(directiveName.equals(hit.second)) {
targets.add(hit.first);
}
}));

return true;
},
ProjectScope.getAllScope(getProject()));

return targets;
}

private class BladeCustomDirectiveLookup extends LookupElement {

private String lookupString;

private BladeCustomDirectiveLookup(String lookupString) {
this.lookupString = lookupString;
}

@NotNull
@Override
public String getLookupString() {
return lookupString;
}

@Override
public void handleInsert(InsertionContext context) {
super.handleInsert(context);

context.getEditor().getCaretModel().moveCaretRelatively(-1, 0, false, false, true);
}

public void renderElement(LookupElementPresentation presentation) {
presentation.setItemText("@" + lookupString.substring(0, lookupString.length() - 2));
}
}
}
}
@@ -0,0 +1,69 @@
package de.espend.idea.laravel.stub;

import com.intellij.psi.PsiFile;
import com.intellij.util.indexing.*;
import com.intellij.util.io.DataExternalizer;
import com.intellij.util.io.EnumeratorStringDescriptor;
import com.intellij.util.io.KeyDescriptor;
import com.jetbrains.php.lang.PhpFileType;
import com.jetbrains.php.lang.psi.PhpFile;
import de.espend.idea.laravel.stub.processor.BladeCustomDirectivesVisitor;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;

import java.util.Map;

public class BladeCustomDirectivesStubIndex extends FileBasedIndexExtension<String, Void> {

public static final ID<String, Void> KEY = ID.create("de.espend.idea.laravel.blade.customs");
private final KeyDescriptor<String> myKeyDescriptor = new EnumeratorStringDescriptor();

@NotNull
@Override
public ID<String, Void> getName() {
return KEY;
}

@NotNull
@Override
public DataIndexer<String, Void, FileContent> getIndexer() {
return fileContent -> {
final Map<String, Void> map = new THashMap<>();
PsiFile psiFile = fileContent.getPsiFile();

if(!(psiFile instanceof PhpFile)) {
return map;
}

psiFile.acceptChildren(new BladeCustomDirectivesVisitor(hit -> map.put(hit.second, null)));

return map;
};
}

@NotNull
@Override
public KeyDescriptor<String> getKeyDescriptor() {
return this.myKeyDescriptor;
}

@NotNull
@Override
public DataExternalizer<Void> getValueExternalizer() {
return ScalarIndexExtension.VOID_DATA_EXTERNALIZER;
}

@NotNull
@Override
public FileBasedIndex.InputFilter getInputFilter() { return file -> file.getFileType() == PhpFileType.INSTANCE; }

@Override
public boolean dependsOnFileContent() {
return true;
}

@Override
public int getVersion() {
return 1;
}
}
@@ -0,0 +1,78 @@
package de.espend.idea.laravel.stub.processor;

import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiRecursiveElementVisitor;
import com.jetbrains.php.lang.psi.elements.MethodReference;
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.utils.PhpElementsUtil;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

public class BladeCustomDirectivesVisitor extends PsiRecursiveElementVisitor {

private static Set<String> availableClasses = new HashSet<>(Arrays.asList("\\Illuminate\\Support\\Facades\\Blade",
"\\Blade"));

private Consumer<Pair<PsiElement, String>> consumer;

public BladeCustomDirectivesVisitor(@NotNull Consumer<Pair<PsiElement, String>> consumer) {
this.consumer = consumer;
}

@Override
public void visitElement(PsiElement element) {

if (element instanceof MethodReference) {
visitMethodReference((MethodReference) element);
}

super.visitElement(element);
}

private void visitMethodReference(MethodReference methodReference) {

if (!"directive".equals(methodReference.getName())) {
return;
}

PsiElement[] parameters = methodReference.getParameters();

if (parameters.length == 0) {
return;
}

if (!(parameters[0] instanceof StringLiteralExpression)) {
return;
}

checkClassName(methodReference, ((StringLiteralExpression) parameters[0]).getContents());
}

private void checkClassName(MethodReference methodReference, String directiveName) {

if(methodReference.getClassReference() == null) {
return;
}

Map<String, String> useImports = PhpElementsUtil.getUseImports(methodReference);

String className = methodReference.getClassReference().getText();
className = useImports.getOrDefault(className, className);

if(!className.startsWith("\\")) {
className = "\\" + className;
}

if(!availableClasses.contains(className)) {
return;
}

consumer.accept(Pair.create(methodReference, directiveName));
}
}
Expand Up @@ -8,6 +8,7 @@
import com.intellij.psi.util.PsiElementFilter;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.php.PhpIndex;
import com.jetbrains.php.codeInsight.PhpCodeInsightUtil;
import com.jetbrains.php.lang.parser.PhpElementTypes;
import com.jetbrains.php.lang.patterns.PhpPatterns;
import com.jetbrains.php.lang.psi.PhpPsiUtil;
Expand Down Expand Up @@ -376,4 +377,28 @@ static public boolean isMethodWithFirstStringOrFieldReference(PsiElement psiElem

return null != methodRefName && Arrays.asList(methodName).contains(methodRefName);
}

@NotNull
public static Map<String, String> getUseImports(@NotNull PsiElement element) {
// search for use alias in local file
final Map<String, String> useImports = new HashMap<>();

PhpPsiElement scope = PhpCodeInsightUtil.findScopeForUseOperator(element);
if(scope == null) {
return useImports;
}

for (PhpUseList phpUseList : PhpCodeInsightUtil.collectImports(scope)) {
for (PhpUse phpUse : phpUseList.getDeclarations()) {
String alias = phpUse.getAliasName();
if (alias != null) {
useImports.put(alias, phpUse.getFQN());
} else {
useImports.put(phpUse.getName(), phpUse.getFQN());
}
}
}

return useImports;
}
}
@@ -0,0 +1,26 @@
package de.espend.idea.laravel.tests.blade;

import de.espend.idea.laravel.stub.BladeCustomDirectivesStubIndex;
import de.espend.idea.laravel.tests.LaravelLightCodeInsightFixtureTestCase;

import java.io.File;

/**
* @see de.espend.idea.laravel.stub.BladeCustomDirectivesStubIndex
*/
public class CustomBladeDirectiveIndexTest extends LaravelLightCodeInsightFixtureTestCase {

public void setUp() throws Exception {
super.setUp();
myFixture.copyFileToProject("custom_directives.php");
}

protected String getTestDataPath() {
return new File(this.getClass().getResource("fixtures").getFile()).getAbsolutePath();
}

public void testCustomDirective() {
assertIndexContains(BladeCustomDirectivesStubIndex.KEY, "datetime");
assertIndexContains(BladeCustomDirectivesStubIndex.KEY, "foo");
}
}
@@ -0,0 +1,27 @@
<?php

namespace Illuminate\Support\Facades
{
class Blade
{
public static function directive($name, $handler) {}
}
}

namespace
{
use \Illuminate\Support\Facades\Blade as Foo;

class Blade extends \Illuminate\Support\Facades\Blade
{
public static function directive($name, $handler)
{
}
}

Blade::directive('datetime', function ($expression) {
return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
});

Foo::directive('foo', function($expression) {});
}