package fr.adrienbrault.idea.symfony2plugin.form;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.jetbrains.php.lang.PhpLanguage;
import com.jetbrains.php.lang.psi.elements.*;
import fr.adrienbrault.idea.symfony2plugin.Symfony2InterfacesUtil;
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
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.form.util.FormOptionsUtil;
import fr.adrienbrault.idea.symfony2plugin.form.util.FormUtil;
import fr.adrienbrault.idea.symfony2plugin.form.visitor.FormOptionLookupVisitor;
import fr.adrienbrault.idea.symfony2plugin.form.visitor.FormOptionTargetVisitor;
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
import fr.adrienbrault.idea.symfony2plugin.util.ParameterBag;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class FormOptionGotoCompletionRegistrar implements GotoCompletionRegistrar {
/**
* Symfony 2 / 3 switch
*/
private static final String[] DEFAULT_FORM = {
"form",
"Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType"
};
public void register(GotoCompletionRegistrarParameter registrar) {
registrar.register(
PlatformPatterns.psiElement().withLanguage(PhpLanguage.INSTANCE),
new FormOptionBuilderCompletionContributor()
);
/*
* eg "$resolver->setDefault('<caret>')"
*/
registrar.register(
PlatformPatterns.psiElement().withParent(PhpElementsUtil.methodWithFirstStringPattern()),
new OptionDefaultCompletionContributor()
);
}
private static class FormOptionBuilderCompletionContributor implements GotoCompletionContributor {
@Nullable
@Override
public GotoCompletionProvider getProvider(@NotNull PsiElement psiElement) {
if (!Symfony2ProjectComponent.isEnabled(psiElement)) {
return null;
}
ArrayCreationExpression arrayCreationExpression = PhpElementsUtil.getCompletableArrayCreationElement(psiElement.getParent());
if(arrayCreationExpression != null) {
PsiElement parameterList = arrayCreationExpression.getParent();
if (parameterList instanceof ParameterList) {
PsiElement context = parameterList.getContext();
if(context instanceof MethodReference) {
ParameterBag currentIndex = PsiElementUtils.getCurrentParameterIndex(arrayCreationExpression);
if(currentIndex != null && currentIndex.getIndex() == 2) {
if (new Symfony2InterfacesUtil().isFormBuilderFormTypeCall(context)) {
return getMatchingOption((ParameterList) parameterList, psiElement);
}
}
}
}
}
return null;
}
@Nullable
private GotoCompletionProvider getMatchingOption(ParameterList parameterList, @NotNull PsiElement psiElement) {
// form name can be a string alias; also resolve on constants, properties, ...
PsiElement psiElementAt = PsiElementUtils.getMethodParameterPsiElementAt(parameterList, 1);
String formTypeName = null;
if(psiElementAt != null) {
PhpClass phpClass = FormUtil.getFormTypeClassOnParameter(psiElementAt);
if(phpClass != null) {
formTypeName = phpClass.getFQN();
}
}
// fallback to form
if(formTypeName == null) {
formTypeName = "form";
}
return new FormReferenceCompletionProvider(psiElement, formTypeName);
}
}
private static class FormReferenceCompletionProvider extends GotoCompletionProvider {
private final String formType;
FormReferenceCompletionProvider(@NotNull PsiElement element, @NotNull String formType) {
super(element);
this.formType = formType;
}
@NotNull
public Collection<PsiElement> getPsiTargets(PsiElement psiElement) {
PsiElement element = psiElement.getParent();
if(!(element instanceof StringLiteralExpression)) {
return Collections.emptyList();
}
final String value = ((StringLiteralExpression) element).getContents();
if(StringUtils.isBlank(value)) {
return Collections.emptyList();
}
final Collection<PsiElement> psiElements = new ArrayList<>();
FormOptionsUtil.visitFormOptions(getProject(), formType, new FormOptionTargetVisitor(value, psiElements));
return psiElements;
}
@NotNull
public Collection<LookupElement> getLookupElements() {
Collection<LookupElement> lookupElements = new ArrayList<>();
FormOptionsUtil.visitFormOptions(getProject(), formType, new FormOptionLookupVisitor(lookupElements));
return lookupElements;
}
}
/*
* eg "$resolver->setDefault('<caret>')"
*/
private static class FormOptionGotoCompletionProvider extends GotoCompletionProvider {
FormOptionGotoCompletionProvider(PsiElement psiElement) {
super(psiElement);
}
@NotNull
@Override
public Collection<LookupElement> getLookupElements() {
Collection<LookupElement> elements = new ArrayList<>();
for (String formType : DEFAULT_FORM) {
FormOptionsUtil.visitFormOptions(
getElement().getProject(), formType, new FormOptionLookupVisitor(elements)
);
}
return elements;
}
@NotNull
@Override
public Collection<PsiElement> getPsiTargets(PsiElement element) {
String contents = GotoCompletionUtil.getTextValueForElement(element);
if(contents == null) {
return Collections.emptyList();
}
Collection<PsiElement> elements = new ArrayList<>();
for (String formType : DEFAULT_FORM) {
FormOptionsUtil.visitFormOptions(
getElement().getProject(), formType, new FormOptionTargetVisitor(contents, elements)
);
}
return elements;
}
}
private static class OptionDefaultCompletionContributor implements GotoCompletionContributor {
@Nullable
@Override
public GotoCompletionProvider getProvider(@NotNull PsiElement psiElement) {
PsiElement context = psiElement.getContext();
if (!(context instanceof StringLiteralExpression)) {
return null;
}
MethodMatcher.StringParameterRecursiveMatcher matcher = new MethodMatcher.StringParameterRecursiveMatcher(context, 0);
String[] methods = new String[] {
"setDefault", "hasDefault", "isRequired", "isMissing",
"setAllowedValues", "addAllowedValues", "setAllowedTypes", "addAllowedTypes"
};
// @TODO: drop too many classes, add PhpMatcher
for (String method : methods) {
matcher.withSignature("Symfony\\Component\\OptionsResolver\\OptionsResolver", method);
// BC: Symfony < 3
matcher.withSignature("Symfony\\Component\\OptionsResolver\\OptionsResolverInterface", method);
}
if (matcher.match() == null) {
return null;
}
return new FormOptionGotoCompletionProvider(psiElement);
}
}
}