package fr.adrienbrault.idea.symfony2plugin.completion.xml;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.patterns.XmlPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlToken;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.php.completion.PhpLookupElement;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons;
import fr.adrienbrault.idea.symfony2plugin.TwigHelper;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionContributor;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrar;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrarParameter;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.utils.GotoCompletionUtil;
import fr.adrienbrault.idea.symfony2plugin.completion.DecoratedServiceCompletionProvider;
import fr.adrienbrault.idea.symfony2plugin.config.xml.XmlHelper;
import fr.adrienbrault.idea.symfony2plugin.routing.RouteGotoCompletionProvider;
import fr.adrienbrault.idea.symfony2plugin.templating.TemplateGotoCompletionRegistrar;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil;
import fr.adrienbrault.idea.symfony2plugin.util.resource.FileResourceUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Collectors;
/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class XmlGotoCompletionRegistrar implements GotoCompletionRegistrar {
@Override
public void register(GotoCompletionRegistrarParameter registrar) {
// <import resource="config_foo.xml"/>
registrar.register(
XmlPatterns.psiElement().withParent(XmlHelper.getImportResourcePattern()),
ImportResourceGotoCompletionProvider::new
);
// <service id="<caret>" class="MyFoo\Foo\Apple"/>
registrar.register(
XmlPatterns.psiElement().withParent(XmlHelper.getServiceIdNamePattern()),
ServiceIdCompletionProvider::new
);
// <factory class="AppBundle\Trivago\ConfigFactory" method="create"/>
// <factory service="foo" method="create"/>
registrar.register(
XmlPatterns.psiElement().withParent(XmlHelper.getTagAttributePattern("factory", "method")
.inside(XmlHelper.getInsideTagPattern("services"))
.inFile(XmlHelper.getXmlFilePattern())),
ServiceFactoryMethodCompletionProvider::new
);
// <default key="route">sonata_admin_dashboard</default>
registrar.register(
XmlHelper.getRouteDefaultWithKeyAttributePattern("route"),
RouteGotoCompletionProvider::new
);
// <default key="template">foobar.html.twig</default>
registrar.register(
XmlHelper.getRouteDefaultWithKeyAttributePattern("template"),
TemplateGotoCompletionRegistrar::new
);
// <service decorates="<caret>"/>
registrar.register(
XmlPatterns.psiElement().withParent(XmlHelper.getTagAttributePattern("service", "decorates")
.inside(XmlHelper.getInsideTagPattern("services"))
.inFile(XmlHelper.getXmlFilePattern())),
MyDecoratedServiceCompletionProvider::new
);
// <foobar="@Foobar/profiler.html.twig" />
registrar.register(
XmlPatterns.psiElement().withParent(XmlHelper.getGlobalStringAttributePattern()),
new MyGlobalStringTemplateGotoCompletionContributor()
);
// <foo template="@Foobar/profiler.html.twig" />
registrar.register(
XmlPatterns.psiElement().withParent(XmlHelper.getAttributePattern("template")),
MyTemplateCompletionRegistrar::new
);
}
private static class MyTemplateCompletionRegistrar extends TemplateGotoCompletionRegistrar{
MyTemplateCompletionRegistrar(PsiElement element) {
super(element);
}
@NotNull
public Collection<PsiElement> getPsiTargets(PsiElement element) {
return Collections.emptyList();
}
}
private static class ImportResourceGotoCompletionProvider extends GotoCompletionProvider {
ImportResourceGotoCompletionProvider(PsiElement element) {
super(element);
}
@NotNull
@Override
public Collection<LookupElement> getLookupElements() {
return Collections.emptyList();
}
@NotNull
@Override
public Collection<PsiElement> getPsiTargets(PsiElement element) {
String xmlAttributeValue = GotoCompletionUtil.getXmlAttributeValue(element);
if(xmlAttributeValue == null) {
return Collections.emptyList();
}
Collection<PsiElement> targets = new ArrayList<>();
targets.addAll(FileResourceUtil.getFileResourceTargetsInBundleScope(element.getProject(), xmlAttributeValue));
targets.addAll(FileResourceUtil.getFileResourceTargetsInBundleDirectory(element.getProject(), xmlAttributeValue));
PsiFile containingFile = element.getContainingFile();
if(containingFile != null) {
targets.addAll(FileResourceUtil.getFileResourceTargetsInDirectoryScope(containingFile, xmlAttributeValue));
}
return targets;
}
}
private static class ServiceIdCompletionProvider extends GotoCompletionProvider {
private ServiceIdCompletionProvider(PsiElement element) {
super(element);
}
@NotNull
@Override
public Collection<LookupElement> getLookupElements() {
Collection<LookupElement> lookupElements = new ArrayList<>();
// find class name of service tag
PsiElement xmlToken = this.getElement();
if(xmlToken instanceof XmlToken) {
PsiElement xmlAttrValue = xmlToken.getParent();
if(xmlAttrValue instanceof XmlAttributeValue) {
PsiElement xmlAttribute = xmlAttrValue.getParent();
if(xmlAttribute instanceof XmlAttribute) {
PsiElement xmlTag = xmlAttribute.getParent();
if(xmlTag instanceof XmlTag) {
String aClass = ((XmlTag) xmlTag).getAttributeValue("class");
if(aClass != null && StringUtils.isNotBlank(aClass)) {
lookupElements.add(LookupElementBuilder.create(
ServiceUtil.getServiceNameForClass(getProject(), aClass)).withIcon(Symfony2Icons.SERVICE)
);
}
}
}
}
}
return lookupElements;
}
@NotNull
@Override
public Collection<PsiElement> getPsiTargets(PsiElement element) {
return Collections.emptyList();
}
}
private static class ServiceFactoryMethodCompletionProvider extends GotoCompletionProvider {
ServiceFactoryMethodCompletionProvider(PsiElement element) {
super(element);
}
@NotNull
@Override
public Collection<LookupElement> getLookupElements() {
PsiElement parent = getElement().getParent();
if(!(parent instanceof XmlAttributeValue)) {
return Collections.emptyList();
}
Collection<PhpClass> phpClasses = new ArrayList<>();
ContainerUtil.addIfNotNull(phpClasses, XmlHelper.getPhpClassForClassFactory((XmlAttributeValue) parent));
ContainerUtil.addIfNotNull(phpClasses, XmlHelper.getPhpClassForServiceFactory((XmlAttributeValue) parent));
Collection<LookupElement> lookupElements = new ArrayList<>();
for (PhpClass phpClass : phpClasses) {
lookupElements.addAll(PhpElementsUtil.getClassPublicMethod(phpClass).stream()
.map(PhpLookupElement::new)
.collect(Collectors.toList())
);
}
return lookupElements;
}
@NotNull
@Override
public Collection<PsiElement> getPsiTargets(PsiElement element) {
return Collections.emptyList();
}
}
private static class MyDecoratedServiceCompletionProvider extends DecoratedServiceCompletionProvider {
MyDecoratedServiceCompletionProvider(PsiElement psiElement) {
super(psiElement);
}
@Nullable
@Override
public String findClassForElement(@NotNull PsiElement psiElement) {
XmlTag parentOfType = PsiTreeUtil.getParentOfType(psiElement, XmlTag.class);
if (parentOfType != null) {
String aClass = parentOfType.getAttributeValue("class");
if (StringUtils.isNotBlank(aClass)) {
return aClass;
}
}
return null;
}
@Nullable
@Override
public String findIdForElement(@NotNull PsiElement psiElement) {
XmlTag parentOfType = PsiTreeUtil.getParentOfType(psiElement, XmlTag.class);
if(parentOfType == null) {
return null;
}
return parentOfType.getAttributeValue("id");
}
}
private static class MyGlobalStringTemplateGotoCompletionContributor implements GotoCompletionContributor {
@Nullable
@Override
public GotoCompletionProvider getProvider(@NotNull PsiElement psiElement) {
return new GotoCompletionProvider(psiElement) {
@NotNull
@Override
public Collection<LookupElement> getLookupElements() {
return Collections.emptyList();
}
@NotNull
@Override
public Collection<PsiElement> getPsiTargets(PsiElement element) {
String xmlAttributeValue = GotoCompletionUtil.getXmlAttributeValue(element);
if(xmlAttributeValue == null) {
return Collections.emptyList();
}
String s = xmlAttributeValue.toLowerCase();
if(!s.endsWith(".html.twig") && !s.endsWith(".html.php")) {
return Collections.emptyList();
}
return Arrays.asList(
TwigHelper.getTemplatePsiElements(getElement().getProject(), xmlAttributeValue)
);
}
};
}
}
}