/*
* Copyright 2012-2015 Sergey Ignatov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.intellij.erlang.completion;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.project.DumbAware;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.BaseScopeProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.erlang.ErlangTypes;
import org.intellij.erlang.icons.ErlangIcons;
import org.intellij.erlang.psi.*;
import org.intellij.erlang.psi.impl.ErlangVarProcessor;
import org.intellij.erlang.psi.impl.ResolveUtil;
import org.intellij.erlang.types.ErlangExpressionType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import static com.intellij.patterns.PlatformPatterns.psiElement;
import static org.intellij.erlang.psi.impl.ErlangPsiImplUtil.*;
public class ErlangVariableCompletionContributor extends CompletionContributor implements DumbAware {
public ErlangVariableCompletionContributor() {
extend(CompletionType.BASIC, psiElement(ErlangTypes.ERL_VAR), new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {
PsiElement position = parameters.getPosition();
ErlangFile file = (ErlangFile)position.getContainingFile();
ErlangQVar originalVar = PsiTreeUtil.getParentOfType(parameters.getOriginalPosition(), ErlangQVar.class);
if (originalVar != null) {
boolean isDeclaration = inArgumentDefinition(originalVar) || inLeftPartOfAssignment(originalVar);
PsiReference reference = !isDeclaration ? originalVar.getReference() : null;
PsiElement resolved = reference != null ? reference.resolve() : null;
if (isDeclaration || resolved != null && resolved != originalVar) {
addVariable(result, originalVar.getName());
}
}
if (!(position.getParent() instanceof ErlangRecordExpression)) {
Collection<String> vars = ContainerUtil.newHashSet();
//noinspection unchecked
PsiElement scopeOwner = PsiTreeUtil.getParentOfType(position,
ErlangFunctionClause.class, ErlangMacrosDefinition.class, ErlangTypeDefinition.class, ErlangSpecification.class);
ResolveUtil.treeWalkUp(position, new MyBaseScopeProcessor(vars, position, scopeOwner, false));
ErlangModule module = file.getModule();
if (module != null) {
module.processDeclarations(new MyBaseScopeProcessor(vars, position, scopeOwner, true), ResolveState.initial(), module, module);
}
addVariables(result, vars);
}
Map<String, ErlangQVar> erlangVarContext = file.getOriginalFile().getUserData(ErlangVarProcessor.ERLANG_VARIABLE_CONTEXT);
if (erlangVarContext != null && PsiTreeUtil.getParentOfType(position, ErlangColonQualifiedExpression.class) == null) {
addVariables(result, erlangVarContext.keySet());
}
}
});
extend(CompletionType.SMART, psiElement(ErlangTypes.ERL_VAR), new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
PsiElement position = parameters.getPosition();
Set<ErlangExpressionType> expectedTypes = ErlangCompletionUtil.expectedArgumentTypes(position);
if (expectedTypes.isEmpty()) return;
Collection<ErlangQVar> vars = ContainerUtil.newHashSet();
ErlangFunctionClause clause = PsiTreeUtil.getParentOfType(position, ErlangFunctionClause.class);
ResolveUtil.treeWalkUp(position, new MyBaseScopeProcessor(vars, position, clause));
for (ErlangQVar v : vars) {
if (!inLeftPartOfAssignment(v, true)) continue;
ErlangAssignmentExpression assign = PsiTreeUtil.getParentOfType(v, ErlangAssignmentExpression.class);
ErlangExpression right = assign != null ? assign.getRight() : null;
ErlangExpressionType varType = ErlangExpressionType.create(right);
if (ErlangCompletionUtil.containsType(expectedTypes, varType)) {
addVariable(result, v.getName());
}
}
}
});
}
private static void addVariables(@NotNull CompletionResultSet result, @NotNull Iterable<String> variables) {
for (String variable : variables) {
addVariable(result, variable);
}
}
private static void addVariable(@NotNull CompletionResultSet result, @NotNull String variable) {
result.addElement(LookupElementBuilder.create(variable).withIcon(ErlangIcons.VARIABLE));
}
private static class MyBaseScopeProcessor extends BaseScopeProcessor {
private final PsiElement myElement;
private final PsiElement myScopeOwner;
private final boolean myForce;
private final Collection<String> myResult;
@Nullable private Collection<ErlangQVar> myVars;
public MyBaseScopeProcessor(@NotNull Collection<String> result, @NotNull PsiElement element, @Nullable PsiElement scopeOwner, boolean force) {
myElement = element;
myScopeOwner = scopeOwner;
myForce = force;
myResult = result;
}
@SuppressWarnings("NullableProblems")
private MyBaseScopeProcessor(@NotNull Collection<ErlangQVar> result, @NotNull PsiElement element, @Nullable PsiElement scopeOwner) {
this(ContainerUtil.<String>newArrayList(), element, scopeOwner, true);
myVars = result;
}
@Override
public boolean execute(@NotNull PsiElement psiElement, @NotNull ResolveState resolveState) {
if (!psiElement.equals(myElement) && psiElement instanceof ErlangQVar && !psiElement.getText().equals("_") && !inColonQualified(myElement)) {
boolean ancestor = PsiTreeUtil.isAncestor(myScopeOwner, psiElement, false);
if ((ancestor || myForce) && (inArgumentDefinition(psiElement) || inLeftPartOfAssignment(psiElement) || inFunctionTypeArgument(psiElement))) {
myResult.add(((ErlangQVar) psiElement).getName());
if (myVars != null) myVars.add((ErlangQVar) psiElement);
}
}
return true;
}
}
}