package org.elixir_lang.psi.scope.module; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.StubIndex; import com.intellij.psi.util.PsiTreeUtil; import org.elixir_lang.psi.NamedElement; import org.elixir_lang.psi.call.Named; import org.elixir_lang.psi.scope.Module; import org.elixir_lang.psi.stub.index.AllName; import org.elixir_lang.reference.module.UnaliasedName; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import static org.elixir_lang.Module.concat; import static org.elixir_lang.Module.split; import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.ENTRANCE; public class MultiResolve extends Module { /* * Public Static Methods */ @Nullable public static List<ResolveResult> resolveResultList(@NotNull String name, boolean incompleteCode, @NotNull PsiElement entrance, @NotNull PsiElement maxScope) { return resolveResultList(name, incompleteCode, entrance, maxScope, ResolveState.initial()); } /* * Private Static Methods */ @NotNull private static Collection<NamedElement> indexedNamedElements(@NotNull PsiNamedElement match, @NotNull String unaliasedName) { Project project = match.getProject(); Collection<NamedElement> indexNamedElementCollection; if (DumbService.isDumb(project)) { indexNamedElementCollection = Collections.emptyList(); } else { indexNamedElementCollection = StubIndex.getElements( AllName.KEY, unaliasedName, project, GlobalSearchScope.allScope(project), NamedElement.class ); } return indexNamedElementCollection; } @Nullable private static List<ResolveResult> resolveResultList(@NotNull String name, boolean incompleteCode, @NotNull PsiElement entrance, @NotNull PsiElement maxScope, @NotNull ResolveState state) { MultiResolve multiResolve = new MultiResolve(name, incompleteCode); PsiTreeUtil.treeWalkUp( multiResolve, entrance, maxScope, state.put(ENTRANCE, entrance) ); return multiResolve.getResolveResultList(); } @NotNull private static String unaliasedName(@NotNull PsiNamedElement match, @NotNull List<String> namePartList) { String matchUnaliasedName = UnaliasedName.unaliasedName(match); List<String> unaliasedNamePartList = new ArrayList<String>(namePartList.size()); unaliasedNamePartList.add(matchUnaliasedName); for (int i = 1; i < namePartList.size(); i++) { unaliasedNamePartList.add(namePartList.get(i)); } return concat(unaliasedNamePartList); } /* * Fields */ @NotNull private final String name; private final boolean incompleteCode; @Nullable private List<ResolveResult> resolveResultList = null; /* * Constructors */ MultiResolve(@NotNull String name, boolean incompleteCode) { this.incompleteCode = incompleteCode; this.name = name; } /* * Public Instance Methods */ @Override public boolean execute(@NotNull PsiElement match, @NotNull ResolveState state) { boolean keepProcessing = true; if (match instanceof Named) { keepProcessing = execute((Named) match, state); } return keepProcessing; } @Nullable public List<ResolveResult> getResolveResultList() { return resolveResultList; } /* * Protected Instance Methods */ /** * Decides whether {@code match} matches the criteria being searched for. All other {@link #execute} methods * eventually end here. * * @return {@code true} to keep processing; {@code false} to stop processing. */ @Override protected boolean executeOnAliasedName(@NotNull PsiNamedElement match, @NotNull String aliasedName, @NotNull ResolveState state) { if (aliasedName.equals(name)) { List<String> namePartList = split(name); // adds `Foo.SSH` in `alias Foo.SSH` addToResolveResultList(match, true); // adds `defmodule Foo.SSH` for `alias Foo.SSH` addUnaliasedNamedElementsToResolveResultList(match, namePartList); } else { List<String> namePartList = split(name); String firstNamePart = namePartList.get(0); // alias Foo.SSH, then SSH.Key is name if (aliasedName.equals(firstNamePart)) { addToResolveResultList(match, true); addUnaliasedNamedElementsToResolveResultList(match, namePartList); } else if (incompleteCode && aliasedName.startsWith(name)) { addToResolveResultList(match, false); } } return org.elixir_lang.psi.scope.MultiResolve.keepProcessing(incompleteCode, resolveResultList); } /* * Private Instance Methods */ private void addToResolveResultList(@NotNull PsiElement element, boolean validResult) { resolveResultList = org.elixir_lang.psi.scope.MultiResolve.addToResolveResultList( resolveResultList, new PsiElementResolveResult(element, validResult) ); } private void addUnaliasedNamedElementsToResolveResultList(@NotNull PsiNamedElement match, List<String> namePartList) { String unaliasedName = unaliasedName(match, namePartList); Collection<NamedElement> namedElementCollection = indexedNamedElements(match, unaliasedName); for (PsiElement element : namedElementCollection) { addToResolveResultList(element, true); } } }