package org.elixir_lang.psi.scope.module;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.util.containers.ContainerUtil;
import org.elixir_lang.psi.*;
import org.elixir_lang.psi.call.Named;
import org.elixir_lang.psi.operation.Normalized;
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.List;
import static com.intellij.psi.util.PsiTreeUtil.treeWalkUp;
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 Variants extends Module {
/*
* Public Static Methods
*/
@NotNull
public static List<LookupElement> lookupElementList(@NotNull PsiElement entrance) {
Variants variants = new Variants();
treeWalkUp(
variants,
entrance,
entrance.getContainingFile(),
ResolveState.initial().put(ENTRANCE, entrance)
);
List<LookupElement> lookupElementList = variants.getLookupElementList();
if (lookupElementList == null) {
lookupElementList = new ArrayList<LookupElement>();
}
variants.addProjectNameElementsTo(lookupElementList, entrance);
return lookupElementList;
}
/*
* Private Static Methods
*/
/**
* Filters to only those names that work as Alias, that is those that start with a capital letter
*/
@NotNull
private static Collection<String> filterIndexedNameCollection(@NotNull Collection<String> indexedNameCollection) {
return ContainerUtil.filter(
indexedNameCollection,
new Condition<String>() {
@Override
public boolean value(String indexedName) {
boolean value = false;
if (indexedName != null) {
value = Character.isUpperCase(indexedName.codePointAt(0));
}
return value;
}
}
);
}
@NotNull
private static Collection<String> filterIndexedNameCollection(@NotNull Collection<String> indexedNameCollection,
@Nullable final String prefix) {
Collection<String> filteredIndexNameCollection = indexedNameCollection;
if (prefix != null) {
filteredIndexNameCollection = ContainerUtil.filter(
indexedNameCollection,
new Condition<String>() {
@Override
public boolean value(@Nullable String indexedName) {
boolean value = false;
if (indexedName != null) {
value = indexedName.startsWith(prefix);
}
return value;
}
}
);
}
return filteredIndexNameCollection;
}
@Nullable
private static String indexedNamePrefix(@Nullable ElixirMultipleAliases multipleAliases) {
String prefix = null;
if (multipleAliases != null) {
QualifiedMultipleAliases parent = (QualifiedMultipleAliases) multipleAliases.getParent();
PsiElement[] children = parent.getChildren();
int operatorIndex = Normalized.operatorIndex(children);
Quotable qualifierQuotable = org.elixir_lang.psi.operation.infix.Normalized.leftOperand(
children,
operatorIndex
);
prefix = qualifierToIndexNamePrefix(qualifierQuotable);
}
return prefix;
}
@NotNull
private static String lookupName(@NotNull String indexedName, @Nullable String prefix) {
String lookupName = indexedName;
if (prefix != null) {
if (indexedName.startsWith(prefix)) {
lookupName = indexedName.substring(prefix.length());
}
}
return lookupName;
}
@Nullable
private static String qualifierToIndexNamePrefix(@NotNull ElixirAccessExpression qualifier) {
PsiElement[] children = qualifier.getChildren();
String prefix = null;
if (children.length == 1) {
prefix = qualifierToIndexNamePrefix(children[0]);
}
return prefix;
}
@Nullable
private static String qualifierToIndexNamePrefix(@NotNull PsiElement qualifier) {
String prefix = null;
if (qualifier instanceof ElixirAccessExpression) {
prefix = qualifierToIndexNamePrefix((ElixirAccessExpression) qualifier);
} else if (qualifier instanceof QualifiableAlias) {
prefix = qualifierToIndexNamePrefix((QualifiableAlias) qualifier);
}
return prefix;
}
@Nullable
private static String qualifierToIndexNamePrefix(@NotNull QualifiableAlias qualifier) {
String qualifierFullyQualifiedName = qualifier.fullyQualifiedName();
String prefix = null;
if (qualifierFullyQualifiedName != null) {
prefix = qualifierFullyQualifiedName + ".";
}
return prefix;
}
/*
* Fields
*/
private ElixirMultipleAliases multipleAliases = null;
private List<LookupElement> lookupElementList = null;
/*
* Public Instance Methods
*/
@Override
public boolean execute(@NotNull PsiElement match, @NotNull ResolveState state) {
boolean keepProcessing = true;
if (match instanceof ElixirMultipleAliases) {
keepProcessing = execute((ElixirMultipleAliases) match, state);
} else if (match instanceof Named) {
keepProcessing = execute((Named) match, state);
}
return keepProcessing;
}
/*
* Protected Instance Methods
*/
/**
* Decides whether {@code match} matches the criteria being searched for. All other {@link #execute} methods
* eventually end here.
*
* @param match
* @param aliasedName
* @param state
* @return {@code true} to keep processing; {@code false} to stop processing.
*/
@Override
protected boolean executeOnAliasedName(@NotNull PsiNamedElement match, @NotNull final String aliasedName, @NotNull ResolveState state) {
if (lookupElementList == null) {
lookupElementList = new ArrayList<LookupElement>();
}
lookupElementList.add(
LookupElementBuilder.createWithSmartPointer(
aliasedName,
match
)
);
String unaliasedName = UnaliasedName.unaliasedName(match);
if (unaliasedName != null) {
Project project = match.getProject();
Collection<String> indexedNameCollection = StubIndex
.getInstance()
.getAllKeys(AllName.KEY, project);
List<String> unaliasedNestedNames = ContainerUtil.findAll(
indexedNameCollection,
new org.elixir_lang.Module.IsNestedUnder(unaliasedName)
);
if (unaliasedNestedNames.size() > 0) {
GlobalSearchScope scope = GlobalSearchScope.allScope(project);
for (String unaliasedNestedName : unaliasedNestedNames) {
Collection<NamedElement> unaliasedNestedNamedElementCollection = StubIndex.getElements(
AllName.KEY,
unaliasedNestedName,
project,
scope,
NamedElement.class
);
if (unaliasedNestedNamedElementCollection.size() > 0) {
List<String> unaliasedNestedNamePartList = split(unaliasedNestedName);
List<String> unaliasedNamePartList = split(unaliasedName);
List<String> aliasedNamePartList = split(aliasedName);
List<String> aliasedNestedNamePartList = new ArrayList<String>();
aliasedNestedNamePartList.addAll(aliasedNamePartList);
for (int i = unaliasedNamePartList.size(); i < unaliasedNestedNamePartList.size(); i++) {
aliasedNestedNamePartList.add(unaliasedNestedNamePartList.get(i));
}
String aliasedNestedName = concat(aliasedNestedNamePartList);
for (NamedElement unaliasedNestedNamedElement : unaliasedNestedNamedElementCollection) {
lookupElementList.add(
LookupElementBuilder.createWithSmartPointer(
aliasedNestedName,
unaliasedNestedNamedElement
)
);
}
}
}
}
}
return true;
}
/*
* Private Instance Methods
*/
private void addProjectNameElementsTo(List<LookupElement> lookupElementList, PsiElement entrance) {
Project project = entrance.getProject();
/* getAllKeys is not the actual keys in the actual project. They need to be checked.
See https://intellij-support.jetbrains.com/hc/en-us/community/posts/207930789-StubIndex-persisting-between-test-runs-leading-to-incorrect-completions */
Collection<String> indexedNameCollection = StubIndex.getInstance().getAllKeys(AllName.KEY, project);
GlobalSearchScope scope = GlobalSearchScope.allScope(project);
Collection<String> aliasNameCollection = filterIndexedNameCollection(indexedNameCollection);
String prefix = indexedNamePrefix(multipleAliases);
Collection<String> prefixedNameCollection = filterIndexedNameCollection(
aliasNameCollection,
prefix
);
for (String prefixedName : prefixedNameCollection) {
Collection<NamedElement> prefixedNameNamedElementCollection = StubIndex.getElements(
AllName.KEY,
prefixedName,
project,
scope,
NamedElement.class
);
String lookupName = lookupName(prefixedName, prefix);
for (NamedElement prefixedNameNamedElement : prefixedNameNamedElementCollection) {
/* Generalizes over whether the prefixedNameNamedElement is a source element or a compiled element as
the navigation element is defined to be always be a source element */
PsiElement navigationElement = prefixedNameNamedElement.getNavigationElement();
lookupElementList.add(
LookupElementBuilder.createWithSmartPointer(
lookupName,
navigationElement
)
);
}
}
}
private boolean execute(@NotNull ElixirMultipleAliases match,
@NotNull @SuppressWarnings("unused") ResolveState state) {
multipleAliases = match;
return false;
}
private List<LookupElement> getLookupElementList() {
return lookupElementList;
}
}