package fr.adrienbrault.idea.symfony2plugin.config.yaml;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.StandardPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Consumer;
import com.jetbrains.php.lang.psi.elements.Method;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
import fr.adrienbrault.idea.symfony2plugin.config.EventDispatcherSubscriberUtil;
import fr.adrienbrault.idea.symfony2plugin.config.utils.ConfigUtil;
import fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil;
import fr.adrienbrault.idea.symfony2plugin.util.resource.FileResourceUtil;
import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.yaml.psi.YAMLCompoundValue;
import org.jetbrains.yaml.psi.YAMLKeyValue;
import org.jetbrains.yaml.psi.YAMLScalar;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class YamlGoToKnownDeclarationHandler implements GotoDeclarationHandler {
@Nullable
@Override
public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int i, Editor editor) {
if(!Symfony2ProjectComponent.isEnabled(psiElement)) {
return null;
}
List<PsiElement> results = new ArrayList<>();
if(YamlElementPatternHelper.getSingleLineScalarKey("_controller").accepts(psiElement)) {
this.getControllerGoto(psiElement, results);
}
if(YamlElementPatternHelper.getSingleLineScalarKey("class").accepts(psiElement)) {
this.getClassGoto(psiElement, results);
}
if(YamlElementPatternHelper.getSingleLineScalarKey("resource").accepts(psiElement)) {
this.attachResourceBundleGoto(psiElement, results);
this.attachResourceOnPathGoto(psiElement, results);
}
// tags: { name: foobar }
if(StandardPatterns.and(
YamlElementPatternHelper.getInsideKeyValue("tags"),
YamlElementPatternHelper.getSingleLineScalarKey("name")
).accepts(psiElement)) {
this.getTagClassesGoto(psiElement, results);
}
// tags: [ name: foobar ]
if(YamlElementPatternHelper.getTagsAsSequencePattern().accepts(psiElement)) {
this.getTagClassesGoto(psiElement, results);
}
if(StandardPatterns.and(
YamlElementPatternHelper.getInsideKeyValue("tags"),
YamlElementPatternHelper.getSingleLineScalarKey("event")
).accepts(psiElement)) {
this.getEventGoto(psiElement, results);
}
if(StandardPatterns.and(
YamlElementPatternHelper.getInsideKeyValue("calls")
).accepts(psiElement)) {
this.getMethodGoto(psiElement, results);
}
if(StandardPatterns.and(
YamlElementPatternHelper.getInsideKeyValue("tags"),
YamlElementPatternHelper.getSingleLineScalarKey("method")
).accepts(psiElement)) {
this.getTagMethodGoto(psiElement, results);
}
// ["@service", method]
if(YamlElementPatternHelper.getAfterCommaPattern().accepts(psiElement)) {
this.getArrayMethodGoto(psiElement, results);
}
// config.yml: "as<caret>setic": ~
if(PlatformPatterns.psiElement().inFile(YamlElementPatternHelper.getConfigFileNamePattern()).accepts(psiElement)) {
this.visitConfigKey(psiElement, results);
}
// factory: "service:method"
if(YamlElementPatternHelper.getSingleLineScalarKey("factory").accepts(psiElement)) {
this.getFactoryStringGoto(psiElement, results);
}
// services:
// My<caret>Class: ~
if(YamlElementPatternHelper.getServicesKeyPattern().accepts(psiElement)) {
getClassesForServiceKey(psiElement, results);
}
return results.toArray(new PsiElement[results.size()]);
}
private void visitConfigKey(@NotNull PsiElement psiElement, @NotNull Collection<PsiElement> results) {
PsiElement parent = psiElement.getParent();
if(!(parent instanceof YAMLKeyValue)) {
return;
}
String keyText = ((YAMLKeyValue) parent).getKeyText();
if(StringUtils.isBlank(keyText)) {
return;
}
results.addAll(ConfigUtil.getTreeSignatureTargets(psiElement.getProject(), keyText));
}
private void getArrayMethodGoto(PsiElement psiElement, List<PsiElement> results) {
String text = PsiElementUtils.trimQuote(psiElement.getText());
if(StringUtils.isBlank(text)) {
return;
}
String service = YamlHelper.getPreviousSequenceItemAsText(psiElement);
if (service == null) {
return;
}
PhpClass phpClass = ServiceUtil.getServiceClass(psiElement.getProject(), service);
if(phpClass == null) {
return;
}
for (Method method : phpClass.getMethods()) {
if(text.equals(method.getName())) {
results.add(method);
}
}
}
/**
* Factory goto: "factory: 'foo:bar'"
*/
private void getFactoryStringGoto(PsiElement psiElement, List<PsiElement> results) {
PsiElement parent = psiElement.getParent();
if(!(parent instanceof YAMLScalar)) {
return;
}
String textValue = ((YAMLScalar) parent).getTextValue();
String[] split = textValue.split(":");
if(split.length != 2) {
return;
}
PhpClass phpClass = ServiceUtil.getServiceClass(psiElement.getProject(), split[0]);
if(phpClass == null) {
return;
}
for (Method method : phpClass.getMethods()) {
if(split[1].equals(method.getName())) {
results.add(method);
}
}
}
private void getClassGoto(PsiElement psiElement, List<PsiElement> results) {
String text = PsiElementUtils.trimQuote(psiElement.getText());
PhpClass phpClass = PhpElementsUtil.getClassInterface(psiElement.getProject(), text);
if(phpClass != null) {
results.add(phpClass);
}
}
private void getMethodGoto(PsiElement psiElement, List<PsiElement> results) {
PsiElement parent = psiElement.getParent();
if(parent instanceof YAMLScalar) {
YamlHelper.visitServiceCall((YAMLScalar) parent, clazz -> {
PhpClass phpClass = ServiceUtil.getResolvedClassDefinition(psiElement.getProject(),clazz);
if(phpClass != null) {
for(Method method: PhpElementsUtil.getClassPublicMethod(phpClass)) {
if(method.getName().equals(PsiElementUtils.trimQuote(psiElement.getText()))) {
results.add(method);
}
}
}
});
}
}
private void getTagMethodGoto(PsiElement psiElement, List<PsiElement> results) {
String methodName = PsiElementUtils.trimQuote(psiElement.getText());
if(StringUtils.isBlank(methodName)) {
return;
}
String classValue = YamlHelper.getServiceDefinitionClass(psiElement);
if(classValue == null) {
return;
}
PhpClass phpClass = ServiceUtil.getResolvedClassDefinition(psiElement.getProject(), classValue);
if(phpClass == null) {
return;
}
Method method = phpClass.findMethodByName(methodName);
if(method != null) {
results.add(method);
}
}
private void attachResourceBundleGoto(PsiElement psiElement, List<PsiElement> results) {
String text = PsiElementUtils.trimQuote(psiElement.getText());
if(StringUtils.isBlank(text)) {
return;
}
results.addAll(FileResourceUtil.getFileResourceTargetsInBundleScope(psiElement.getProject(), text));
results.addAll(FileResourceUtil.getFileResourceTargetsInBundleDirectory(psiElement.getProject(), text));
}
private void attachResourceOnPathGoto(PsiElement psiElement, List<PsiElement> results) {
String text = PsiElementUtils.trimQuote(psiElement.getText());
if(StringUtils.isBlank(text) || text.startsWith("@")) {
return;
}
PsiFile containingFile = psiElement.getContainingFile();
if(containingFile == null) {
return;
}
results.addAll(FileResourceUtil.getFileResourceTargetsInDirectoryScope(containingFile, text));
}
private void getControllerGoto(PsiElement psiElement, List<PsiElement> results) {
String text = PsiElementUtils.trimQuote(psiElement.getText());
if(StringUtils.isNotBlank(text)) {
results.addAll(Arrays.asList(RouteHelper.getMethodsOnControllerShortcut(psiElement.getProject(), text)));
}
}
private void getTagClassesGoto(PsiElement psiElement, List<PsiElement> results) {
String tagName = PsiElementUtils.trimQuote(psiElement.getText());
if(StringUtils.isNotBlank(tagName)) {
results.addAll(ServiceUtil.getTaggedClassesWithCompiled(psiElement.getProject(), tagName));
}
}
private void getEventGoto(PsiElement psiElement, List<PsiElement> results) {
results.addAll(EventDispatcherSubscriberUtil.getEventPsiElements(psiElement.getProject(), PsiElementUtils.trimQuote(psiElement.getText())));
}
/**
* services:
* My<caret>Class: ~
*/
private void getClassesForServiceKey(PsiElement psiElement, List<PsiElement> results) {
PsiElement parent = psiElement.getParent();
if(parent instanceof YAMLKeyValue) {
String valueText = ((YAMLKeyValue) parent).getKeyText();
if(StringUtils.isNotBlank(valueText)) {
results.addAll(PhpElementsUtil.getClassesInterface(psiElement.getProject(), valueText));
}
}
}
@Nullable
@Override
public String getActionText(DataContext dataContext) {
return null;
}
}