package org.elixir_lang.code_insight.line_marker_provider;
import com.intellij.codeHighlighting.Pass;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.codeInsight.daemon.LineMarkerProvider;
import com.intellij.openapi.editor.colors.CodeInsightColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.editor.markup.SeparatorPlacement;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import org.apache.commons.lang.math.IntRange;
import org.elixir_lang.psi.AtUnqualifiedNoParenthesesCall;
import org.elixir_lang.psi.call.Call;
import org.elixir_lang.structure_view.element.CallDefinitionClause;
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.psi.impl.ElixirPsiImplUtil.*;
import static org.elixir_lang.structure_view.element.CallDefinitionClause.nameArityRange;
import static org.elixir_lang.structure_view.element.CallDefinitionSpecification.*;
public class CallDefinition implements LineMarkerProvider {
/*
* Fields
*/
private final DaemonCodeAnalyzerSettings daemonCodeAnalyzerSettings;
private final EditorColorsManager editorColorsManager;
/*
* Constructors
*/
public CallDefinition(DaemonCodeAnalyzerSettings daemonCodeAnalyzerSettings,
EditorColorsManager editorColorsManager) {
this.daemonCodeAnalyzerSettings = daemonCodeAnalyzerSettings;
this.editorColorsManager = editorColorsManager;
}
/*
*
* Instance Methods
*
*/
/*
* Public Instance Methods
*/
@Override
public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) {
assert elements != null;
// do nothing
}
@Nullable
@Override
public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
LineMarkerInfo lineMarkerInfo = null;
if (element instanceof AtUnqualifiedNoParenthesesCall) {
lineMarkerInfo = getLineMarkerInfo((AtUnqualifiedNoParenthesesCall) element);
} else if (element instanceof Call) {
lineMarkerInfo = getLineMarkerInfo((Call) element);
}
return lineMarkerInfo;
}
/*
* Private Instance Methods
*/
@NotNull
private LineMarkerInfo callDefinitionSeparator(@NotNull Call call) {
LineMarkerInfo lineMarkerInfo;
lineMarkerInfo = new LineMarkerInfo<Call>(
call,
call.getTextRange(),
null,
Pass.UPDATE_ALL,
null,
null,
GutterIconRenderer.Alignment.RIGHT
);
EditorColorsScheme editorColorsScheme = editorColorsManager.getGlobalScheme();
lineMarkerInfo.separatorColor = editorColorsScheme.getColor(CodeInsightColors.METHOD_SEPARATORS_COLOR);
lineMarkerInfo.separatorPlacement = SeparatorPlacement.TOP;
return lineMarkerInfo;
}
@Nullable
private LineMarkerInfo getLineMarkerInfo(@NotNull AtUnqualifiedNoParenthesesCall atUnqualifiedNoParenthesesCall) {
LineMarkerInfo lineMarkerInfo = null;
String moduleAttributeName = moduleAttributeName(atUnqualifiedNoParenthesesCall);
if (moduleAttributeName.equals("@doc")) {
PsiElement previousExpression = siblingExpression(atUnqualifiedNoParenthesesCall, PREVIOUS_SIBLING);
boolean firstInGroup = true;
if (previousExpression instanceof AtUnqualifiedNoParenthesesCall) {
AtUnqualifiedNoParenthesesCall previousModuleAttribute =
(AtUnqualifiedNoParenthesesCall) previousExpression;
String previousModuleAttributeName = moduleAttributeName(previousModuleAttribute);
if (previousModuleAttributeName.equals("@spec")) {
Pair<String, Integer> moduleAttributeNameArity = moduleAttributeNameArity(previousModuleAttribute);
if (moduleAttributeNameArity != null) {
Call nextSiblingCallDefinitionClause = siblingCallDefinitionClause(atUnqualifiedNoParenthesesCall, NEXT_SIBLING);
if (nextSiblingCallDefinitionClause != null) {
Pair<String, IntRange> nameArityRange = nameArityRange(nextSiblingCallDefinitionClause);
if (nameArityRange != null) {
IntRange arityRange = nameArityRange.second;
if (arityRange.containsInteger(moduleAttributeNameArity.second)) {
// the previous spec is part of the group
firstInGroup = false;
}
}
}
}
}
}
if (firstInGroup) {
lineMarkerInfo = callDefinitionSeparator(atUnqualifiedNoParenthesesCall);
}
} else if (moduleAttributeName.equals("@spec")) {
PsiElement previousExpression = siblingExpression(atUnqualifiedNoParenthesesCall, PREVIOUS_SIBLING);
boolean firstInGroup = true;
if (previousExpression instanceof AtUnqualifiedNoParenthesesCall) {
AtUnqualifiedNoParenthesesCall previousModuleAttribute =
(AtUnqualifiedNoParenthesesCall) previousExpression;
String previousModuleAttributeName = moduleAttributeName(previousModuleAttribute);
if (previousModuleAttributeName.equals("@doc")) {
firstInGroup = false;
} else if (previousModuleAttributeName.equals("@spec")) {
Pair<String, Integer> moduleAttributeNameArity =
moduleAttributeNameArity(atUnqualifiedNoParenthesesCall);
if (moduleAttributeNameArity != null) {
Pair<String, Integer> previousModuleAttributeNameArity =
moduleAttributeNameArity(previousModuleAttribute);
if (previousModuleAttributeNameArity != null) {
// name match, now check if the arities match.
if (moduleAttributeNameArity.first.equals(previousModuleAttributeNameArity.first)) {
Integer moduleAttributeArity = moduleAttributeNameArity.second;
Integer previousModuleAttributeArity = previousModuleAttributeNameArity.second;
if (moduleAttributeArity.equals(previousModuleAttributeArity)) {
/* same arity with different pattern is same function, so the previous @spec should
check if it is first because this one isn't */
firstInGroup = false;
} else {
/* same name, but different arity needs to determine if the call definition has an
arity range. */
Call specification = specification(atUnqualifiedNoParenthesesCall);
if (specification != null) {
Call type = specificationType(specification);
if (type != null) {
PsiReference reference = type.getReference();
if (reference != null) {
List<PsiElement> resolvedList = null;
if (reference instanceof PsiPolyVariantReference) {
PsiPolyVariantReference polyVariantReference =
(PsiPolyVariantReference) reference;
ResolveResult[] resolveResults =
polyVariantReference.multiResolve(false);
if (resolveResults.length > 0) {
resolvedList = new ArrayList<PsiElement>();
for (ResolveResult resolveResult : resolveResults) {
resolvedList.add(resolveResult.getElement());
}
}
} else {
PsiElement resolved = reference.resolve();
if (resolved != null) {
resolvedList = Collections.singletonList(resolved);
}
}
if (resolvedList != null && resolvedList.size() > 0) {
for (PsiElement resolved : resolvedList) {
if (resolved instanceof Call) {
Pair<String, IntRange> resolvedNameArityRange =
nameArityRange((Call) resolved);
if (resolvedNameArityRange != null) {
IntRange resolvedArityRange = resolvedNameArityRange.second;
if (resolvedArityRange.containsInteger(moduleAttributeArity) &&
resolvedArityRange.containsInteger(previousModuleAttributeArity)) {
// the current @spec and the previous @spec apply to the same call definition clause
firstInGroup = false;
break;
}
}
}
}
}
}
}
}
}
}
}
}
}
} else {
Call specification = specification(atUnqualifiedNoParenthesesCall);
if (specification != null) {
Call type = specificationType(specification);
if (type != null) {
PsiReference reference = type.getReference();
if (reference != null) {
if (reference instanceof PsiPolyVariantReference) {
PsiPolyVariantReference polyVariantReference = (PsiPolyVariantReference) reference;
ResolveResult[] resolveResults = polyVariantReference.multiResolve(false);
PsiFile containingFile = type.getContainingFile();
for (ResolveResult resolveResult : resolveResults) {
PsiElement element = resolveResult.getElement();
if (element != null) {
if (element.getContainingFile().equals(containingFile) &&
element.getTextOffset() < type.getTextOffset()) {
firstInGroup = false;
break;
}
}
}
}
}
}
}
}
if (firstInGroup) {
lineMarkerInfo = callDefinitionSeparator(atUnqualifiedNoParenthesesCall);
}
}
return lineMarkerInfo;
}
@Nullable
private LineMarkerInfo getLineMarkerInfo(@NotNull Call call) {
LineMarkerInfo lineMarkerInfo = null;
if (daemonCodeAnalyzerSettings.SHOW_METHOD_SEPARATORS && CallDefinitionClause.is(call)) {
Call previousCallDefinitionClause = siblingCallDefinitionClause(call, PREVIOUS_SIBLING);
boolean firstClause;
if (previousCallDefinitionClause == null) {
firstClause = true;
} else {
Pair<String, IntRange> callNameArityRange = nameArityRange(call);
if (callNameArityRange != null) {
Pair<String, IntRange> previousNameArityRange = nameArityRange(previousCallDefinitionClause);
firstClause = previousNameArityRange == null || !previousNameArityRange.equals(callNameArityRange);
} else {
firstClause = true;
}
}
if (firstClause) {
PsiElement previousExpression = previousSiblingExpression(call);
if (previousExpression instanceof AtUnqualifiedNoParenthesesCall) {
AtUnqualifiedNoParenthesesCall previousModuleAttributeDefinition =
(AtUnqualifiedNoParenthesesCall) previousExpression;
String moduleAttributeName = moduleAttributeName(previousModuleAttributeDefinition);
if (moduleAttributeName.equals("@doc")) {
firstClause = false;
} else if (moduleAttributeName.equals("@spec")) {
Pair<String, IntRange> callNameArityRange = nameArityRange(call);
if (callNameArityRange != null) {
Pair<String, Integer> specNameArity =
moduleAttributeNameArity(previousModuleAttributeDefinition);
if (specNameArity != null) {
Integer specArity = specNameArity.second;
IntRange callArityRange = callNameArityRange.second;
if (callArityRange.containsInteger(specArity)) {
firstClause = false;
}
}
}
}
}
}
if (firstClause) {
lineMarkerInfo = callDefinitionSeparator(call);
}
}
return lineMarkerInfo;
}
@Nullable
private Call siblingCallDefinitionClause(@NotNull PsiElement element,
@NotNull com.intellij.util.Function<PsiElement, PsiElement> function) {
PsiElement expression = element;
Call siblingCallDefinitionClause = null;
while (expression != null) {
expression = siblingExpression(expression, function);
if (expression instanceof Call) {
Call call = (Call) expression;
if (CallDefinitionClause.is(call)) {
siblingCallDefinitionClause = call;
break;
}
}
}
return siblingCallDefinitionClause;
}
}