package org.jetbrains.plugins.clojure.psi.resolve.completion; import com.intellij.codeInsight.lookup.LookupItem; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.util.MethodSignature; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.HashSet; import org.jetbrains.plugins.clojure.ClojureIcons; import org.jetbrains.plugins.clojure.psi.api.symbols.ClSymbol; import org.jetbrains.plugins.clojure.psi.impl.ns.ClSyntheticNamespace; import org.jetbrains.plugins.clojure.psi.resolve.ClojureResolveResult; import org.jetbrains.plugins.clojure.psi.resolve.ResolveUtil; import java.util.*; /** * @author ilyas */ public class CompleteSymbol { public static Object[] getVariants(ClSymbol symbol) { Collection<Object> variants = new ArrayList<Object>(); ClSymbol qualifier = symbol.getQualifierSymbol(); final CompletionProcessor processor = new CompletionProcessor(symbol, symbol.getKinds()); if (qualifier == null) { ResolveUtil.treeWalkUp(symbol, processor); } else { for (ResolveResult result : qualifier.multiResolve(false)) { final PsiElement element = result.getElement(); if (element != null) { final PsiElement sep = symbol.getSeparatorToken(); final String sepText = sep == null ? "." : sep.getText(); if ("/".equals(sepText) && isNamespaceLike(element)) { element.processDeclarations(processor, ResolveState.initial(), null, symbol); } else if (".".equals(sepText)) { element.processDeclarations(processor, ResolveState.initial(), null, symbol); } } } } final ClojureResolveResult[] candidates = processor.getCandidates(); if (candidates.length == 0) return PsiNamedElement.EMPTY_ARRAY; List<PsiElement> psiElements = ContainerUtil.newArrayList(); for (ClojureResolveResult candidate : candidates) { PsiElement element = candidate.getElement(); if (element != null && element != symbol) { variants.add(new ClojureLookupItem(element)); psiElements.add(element); } } // Add Java methods for all imported classes final boolean withoutDot = mayBeMethodReference(symbol); if (symbol.getChildren().length == 0 && symbol.getText().startsWith(".") || withoutDot) { addJavaMethods(psiElements.toArray(new PsiElement[psiElements.size()]), variants, withoutDot); } return variants.toArray(new Object[variants.size()]); } private static boolean isNamespaceLike(PsiElement element) { return element instanceof PsiClass || element instanceof ClSyntheticNamespace; } private static boolean mayBeMethodReference(ClSymbol symbol) { final PsiElement parent = symbol.getParent(); if (parent == null) return false; // if (parent.getParent() instanceof ClList && ".".equals(((ClList) parent.getParent()).getHeadText())) return true; return false; } private static void addJavaMethods(PsiElement[] psiElements, Collection<Object> variants, boolean withoutDot) { final HashMap<MethodSignature, HashSet<PsiMethod>> sig2Methods = collectAvailableMethods(psiElements); for (Map.Entry<MethodSignature, HashSet<PsiMethod>> entry : sig2Methods.entrySet()) { final MethodSignature sig = entry.getKey(); final String name = sig.getName(); final StringBuffer buffer = new StringBuffer(); buffer.append(name).append("("); buffer.append(StringUtil.join(ContainerUtil.map2Array(sig.getParameterTypes(), String.class, new Function<PsiType, String>() { public String fun(PsiType psiType) { return psiType.getPresentableText(); } }), ", ") ).append(")"); final String methodText = buffer.toString(); final StringBuffer tailBuffer = new StringBuffer(); tailBuffer.append(" in "); final ArrayList<String> list = new ArrayList<String>(); for (PsiMethod method : entry.getValue()) { final PsiClass clazz = method.getContainingClass(); if (clazz != null) { list.add(clazz.getQualifiedName()); } } tailBuffer.append(StringUtil.join(list, ", ")); final LookupItem item = new LookupItem(methodText, (!withoutDot ? "." : "") + name); item.setIcon(ClojureIcons.JAVA_METHOD); item.setTailText(tailBuffer.toString(), true); variants.add(item); } } public static HashMap<MethodSignature, HashSet<PsiMethod>> collectAvailableMethods(PsiElement[] psiElements) { final HashMap<MethodSignature, HashSet<PsiMethod>> sig2Methods = new HashMap<MethodSignature, HashSet<PsiMethod>>(); for (PsiElement element : psiElements) { if (element instanceof PsiClass) { PsiClass clazz = (PsiClass) element; for (PsiMethod method : clazz.getAllMethods()) { if (!method.isConstructor() && method.hasModifierProperty(PsiModifier.PUBLIC)) { final MethodSignature sig = method.getSignature(PsiSubstitutor.EMPTY); final HashSet<PsiMethod> set = sig2Methods.get(sig); if (set == null) { final HashSet<PsiMethod> newSet = new HashSet<PsiMethod>(); newSet.add(method); sig2Methods.put(sig, newSet); } else { set.add(method); } } } } } return sig2Methods; } }