package org.jetbrains.plugins.ruby.motion.paramdefs; import com.intellij.openapi.module.Module; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.ruby.motion.RubyMotionUtil; import org.jetbrains.plugins.ruby.motion.bridgesupport.Function; import org.jetbrains.plugins.ruby.rails.codeInsight.paramDefs.MethodRefParam; import org.jetbrains.plugins.ruby.ruby.codeInsight.paramDefs.DynamicHashKeyProviderEx; import org.jetbrains.plugins.ruby.ruby.codeInsight.paramDefs.ParamContext; import org.jetbrains.plugins.ruby.ruby.codeInsight.paramDefs.matcher.ParamDefExpression; import org.jetbrains.plugins.ruby.ruby.codeInsight.paramDefs.matcher.ParamDefLeaf; import org.jetbrains.plugins.ruby.ruby.lang.psi.RPsiElement; import org.jetbrains.plugins.ruby.ruby.lang.psi.assoc.RAssoc; import org.jetbrains.plugins.ruby.ruby.lang.psi.controlStructures.methods.Visibility; import org.jetbrains.plugins.ruby.ruby.lang.psi.expressions.RListOfExpressions; import org.jetbrains.plugins.ruby.ruby.lang.psi.impl.expressions.RListOfExpressionsNavigator; import java.util.*; /** * @author Dennis.Ushakov */ public class SelectorKeysProvider implements DynamicHashKeyProviderEx { static final MethodRefParam METHOD_REF_PARAM = new MethodRefParam(null, Visibility.PUBLIC); private final Map<String, Collection<Function>> mySelectors; public SelectorKeysProvider(final Map<String, Collection<Function>> selectors) { mySelectors = selectors; } @NotNull @Override public List<String> getKeys(ParamContext context) { final Collection<Function> functions = getFunctions(context); if (functions == null) return Collections.emptyList(); final Pair<Integer, List<String>> pair = determineArgNumberAndPath(context); final int argument = pair.first; if (argument < 1) return Collections.emptyList(); final List<String> result = new ArrayList<>(); for (Function function : functions) { final String[] namedArguments = function.getName().split(":"); if (pathMatches(argument, namedArguments, pair.second)) { result.add(namedArguments[argument]); } } return result; } private static boolean pathMatches(int argument, String[] arguments, List<String> path) { if (arguments.length <= argument) { return false; } for (int i = 1; i < argument; i++) { String arg = arguments[i]; if (!StringUtil.equals(arg, path.get(i - 1))) { return false; } } return true; } private static Pair<Integer, List<String>> determineArgNumberAndPath(ParamContext context) { final RPsiElement element = context.getValueElement(); final PsiElement parent = element.getParent(); final PsiElement argument = parent instanceof RAssoc ? parent : element; final RListOfExpressions expressions = RListOfExpressionsNavigator.getByPsiElement(argument); if (expressions == null) return Pair.create(-1, null); final List<String> path = new ArrayList<>(); for (int i = 1; i < expressions.getElements().size(); i++) { PsiElement arg = expressions.getElements().get(i); if (arg == argument) return Pair.create(i, path); if (arg instanceof RAssoc) { path.add(((RAssoc)arg).getKeyText()); } } return Pair.create(-1, null); } @Override public boolean isSymbolOnly() { return true; } @Override public boolean hasKey(ParamContext context, String text) { return getKeys(context).contains(text); } @Nullable @Override public PsiElement resolveKey(ParamContext context, String text) { return null; } @Nullable @Override public ParamDefExpression getValue(ParamContext context, String text) { final Collection<Function> functions = getFunctions(context); if (functions == null) return null; final Pair<Integer, List<String>> pair = determineArgNumberAndPath(context); final int argument = pair.first; if (argument < 1) return null; for (Function function : functions) { final List<Pair<String, String>> arguments = function.getArguments(); final String[] namedArguments = function.getName().split(":"); if (pathMatches(argument, namedArguments, pair.second) && "SEL".equals(arguments.get(argument).second)) { return new ParamDefLeaf(METHOD_REF_PARAM); } } return null; } @Nullable private Collection<Function> getFunctions(ParamContext context) { final Module module = context.getModule(); if (module == null) return null; final String version = RubyMotionUtil.getInstance().getSdkVersion(module); final Collection<Function> functions = mySelectors.get(version); if (functions == null) return null; return functions; } }