/*
* Copyright 2013-2017 consulo.io
*
* 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 consulo.csharp.ide;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.annotations.RequiredDispatchThread;
import consulo.annotations.RequiredReadAction;
import consulo.csharp.ide.completion.CSharpCompletionSorting;
import consulo.csharp.ide.completion.insertHandler.CSharpParenthesesWithSemicolonInsertHandler;
import consulo.csharp.ide.completion.item.CSharpTypeLikeLookupElement;
import consulo.csharp.ide.completion.util.LtGtInsertHandler;
import consulo.csharp.lang.psi.CSharpCallArgument;
import consulo.csharp.lang.psi.CSharpIndexMethodDeclaration;
import consulo.csharp.lang.psi.CSharpMacroDefine;
import consulo.csharp.lang.psi.CSharpMethodDeclaration;
import consulo.csharp.lang.psi.CSharpMethodUtil;
import consulo.csharp.lang.psi.CSharpSimpleParameterInfo;
import consulo.csharp.lang.psi.CSharpTypeDeclaration;
import consulo.csharp.lang.psi.CSharpTypeDefStatement;
import consulo.csharp.lang.psi.CSharpTypeRefPresentationUtil;
import consulo.csharp.lang.psi.impl.source.CSharpLabeledStatementImpl;
import consulo.csharp.lang.psi.impl.source.CSharpPsiUtilImpl;
import consulo.csharp.lang.psi.impl.source.resolve.util.CSharpMethodImplUtil;
import consulo.dotnet.DotNetTypes;
import consulo.dotnet.ide.DotNetElementPresentationUtil;
import consulo.dotnet.psi.DotNetAttributeUtil;
import consulo.dotnet.psi.DotNetGenericParameter;
import consulo.dotnet.psi.DotNetGenericParameterListOwner;
import consulo.dotnet.psi.DotNetQualifiedElement;
import consulo.dotnet.psi.DotNetVariable;
import consulo.dotnet.resolve.DotNetGenericExtractor;
import consulo.dotnet.resolve.DotNetNamespaceAsElement;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.completion.InsertHandler;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import consulo.ide.IconDescriptorUpdaters;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
/**
* @author VISTALL
* @since 29.12.13.
*/
public class CSharpLookupElementBuilder
{
@NotNull
@RequiredReadAction
public static LookupElement[] buildToLookupElements(@NotNull PsiElement[] arguments)
{
if(arguments.length == 0)
{
return LookupElement.EMPTY_ARRAY;
}
List<LookupElement> list = new ArrayList<LookupElement>(arguments.length);
for(PsiElement argument : arguments)
{
ContainerUtil.addIfNotNull(list, buildLookupElementWithContextType(argument, null, DotNetGenericExtractor.EMPTY, null));
}
return list.toArray(new LookupElement[list.size()]);
}
@Nullable
@RequiredReadAction
public static LookupElement buildLookupElementWithContextType(final PsiElement element,
@Nullable final CSharpTypeDeclaration contextType,
@NotNull DotNetGenericExtractor extractor,
@Nullable PsiElement expression)
{
LookupElementBuilder builder = createLookupElementBuilder(element, extractor, expression);
if(builder == null)
{
return null;
}
if(contextType != null && contextType.isEquivalentTo(element.getParent()))
{
LookupElementBuilder oldBuilder = builder;
// don't bold lookup like '[int key]' looks ugly
if(!(element instanceof CSharpIndexMethodDeclaration))
{
builder = oldBuilder.bold();
}
CSharpCompletionSorting.copyForce(oldBuilder, builder);
}
if(CSharpPsiUtilImpl.isTypeLikeElement(element))
{
return CSharpTypeLikeLookupElement.create(builder, extractor, expression);
}
return builder;
}
@RequiredReadAction
public static LookupElementBuilder createLookupElementBuilder(@NotNull final PsiElement element, @NotNull DotNetGenericExtractor extractor, @Nullable final PsiElement completionParent)
{
LookupElementBuilder builder = null;
if(element instanceof CSharpMethodDeclaration)
{
final CSharpMethodDeclaration methodDeclaration = (CSharpMethodDeclaration) element;
if(!methodDeclaration.isDelegate())
{
String name = methodDeclaration.getName();
if(name == null)
{
return null;
}
CSharpMethodUtil.Result inheritGeneric = CSharpMethodUtil.isCanInheritGeneric(methodDeclaration);
String lookupString = inheritGeneric == CSharpMethodUtil.Result.CAN ? name + "<>()" : name;
builder = LookupElementBuilder.create(methodDeclaration, lookupString);
builder = builder.withIcon(IconDescriptorUpdaters.getIcon(element, Iconable.ICON_FLAG_VISIBILITY));
final CSharpSimpleParameterInfo[] parameterInfos = methodDeclaration.getParameterInfos();
String genericText = DotNetElementPresentationUtil.formatGenericParameters((DotNetGenericParameterListOwner) element);
String parameterText = genericText + "(" + StringUtil.join(parameterInfos, new Function<CSharpSimpleParameterInfo, String>()
{
@Override
@RequiredReadAction
public String fun(CSharpSimpleParameterInfo parameter)
{
return CSharpTypeRefPresentationUtil.buildShortText(parameter.getTypeRef(), element) + " " + parameter.getNotNullName();
}
}, ", ") + ")";
if(inheritGeneric == CSharpMethodUtil.Result.CAN)
{
builder = builder.withPresentableText(name);
builder = builder.withInsertHandler(new InsertHandler<LookupElement>()
{
@Override
public void handleInsert(InsertionContext context, LookupElement item)
{
CaretModel caretModel = context.getEditor().getCaretModel();
caretModel.moveToOffset(caretModel.getOffset() - 3);
}
});
}
else
{
builder = builder.withInsertHandler(new CSharpParenthesesWithSemicolonInsertHandler(methodDeclaration));
}
if(CSharpMethodImplUtil.isExtensionWrapper(methodDeclaration))
{
builder = builder.withItemTextUnderlined(true);
}
builder = builder.withTypeText(CSharpTypeRefPresentationUtil.buildShortText(methodDeclaration.getReturnTypeRef(), element));
builder = builder.withTailText(parameterText, false);
}
else
{
builder = buildTypeLikeElement((CSharpMethodDeclaration) element, extractor);
}
}
else if(element instanceof CSharpIndexMethodDeclaration)
{
builder = LookupElementBuilder.create(element, "[]");
builder = builder.withIcon(IconDescriptorUpdaters.getIcon(element, Iconable.ICON_FLAG_VISIBILITY));
final CSharpSimpleParameterInfo[] parameterInfos = ((CSharpIndexMethodDeclaration) element).getParameterInfos();
String parameterText = "[" + StringUtil.join(parameterInfos, new Function<CSharpSimpleParameterInfo, String>()
{
@Override
@RequiredReadAction
public String fun(CSharpSimpleParameterInfo parameter)
{
return CSharpTypeRefPresentationUtil.buildShortText(parameter.getTypeRef(), element) + " " + parameter.getNotNullName();
}
}, ", ") + "]";
builder = builder.withTypeText(CSharpTypeRefPresentationUtil.buildShortText(((CSharpIndexMethodDeclaration) element).getReturnTypeRef(), element));
builder = builder.withPresentableText(parameterText);
builder = builder.withInsertHandler(new InsertHandler<LookupElement>()
{
@Override
public void handleInsert(InsertionContext context, LookupElement item)
{
CharSequence charSequence = context.getDocument().getImmutableCharSequence();
int start = -1, end = -1;
for(int i = context.getTailOffset(); i != 0; i--)
{
char c = charSequence.charAt(i);
if(c == '.')
{
start = i;
break;
}
else if(c == '[')
{
end = i;
}
}
if(start != -1 && end != -1)
{
// .[ -> [ replace
context.getDocument().replaceString(start, end + 1, "[");
}
context.getEditor().getCaretModel().moveToOffset(end);
}
});
}
/*else if(element instanceof DotNetXXXAccessor)
{
DotNetNamedElement parent = (DotNetNamedElement) element.getParent();
DotNetXXXAccessor.Kind accessorKind = ((DotNetXXXAccessor) element).getAccessorKind();
if(accessorKind == null)
{
return null;
}
String ownerName = parent.getName();
if(ownerName == null)
{
return null;
}
String accessorPrefix = accessorKind.name().toLowerCase(Locale.US);
builder = LookupElementBuilder.create(element, ownerName);
builder = builder.withPresentableText(accessorPrefix + "::" + parent.getName());
builder = builder.withLookupString(accessorPrefix + "::" + parent.getName());
builder = builder.withIcon(IconDescriptorUpdaters.getIcon(parent, Iconable.ICON_FLAG_VISIBILITY));
if(parent instanceof DotNetVariable)
{
builder = builder.withTypeText(CSharpTypeRefPresentationUtil.buildShortText(((DotNetVariable) parent).toTypeRef(true), parent));
}
switch(accessorKind)
{
case SET:
builder = builder.withTailText(" = ", true);
builder = builder.withInsertHandler(new InsertHandler<LookupElement>()
{
@Override
public void handleInsert(InsertionContext context, LookupElement item)
{
if(context.getCompletionChar() != '=')
{
int offset = context.getEditor().getCaretModel().getOffset();
context.getDocument().insertString(offset, " = ");
context.getEditor().getCaretModel().moveToOffset(offset + 3);
}
}
});
break;
case ADD:
builder = builder.withTailText(" += ", true);
builder = builder.withInsertHandler(new InsertHandler<LookupElement>()
{
@Override
public void handleInsert(InsertionContext context, LookupElement item)
{
int offset = context.getEditor().getCaretModel().getOffset();
if(context.getCompletionChar() == '+')
{
context.getDocument().insertString(offset, "= ");
}
else
{
context.getDocument().insertString(offset, " += ");
}
context.getEditor().getCaretModel().moveToOffset(offset + 4);
}
});
break;
case REMOVE:
builder = builder.withTailText(" -= ", true);
builder = builder.withInsertHandler(new InsertHandler<LookupElement>()
{
@Override
public void handleInsert(InsertionContext context, LookupElement item)
{
int offset = context.getEditor().getCaretModel().getOffset();
if(context.getCompletionChar() == '-')
{
context.getDocument().insertString(offset, "= ");
}
else
{
context.getDocument().insertString(offset, " -= ");
}
context.getEditor().getCaretModel().moveToOffset(offset + 4);
}
});
break;
}
} */
else if(element instanceof DotNetNamespaceAsElement)
{
DotNetNamespaceAsElement namespaceAsElement = (DotNetNamespaceAsElement) element;
String name = namespaceAsElement.getName();
if(name == null)
{
return null;
}
builder = LookupElementBuilder.create(name);
builder = builder.withIcon(IconDescriptorUpdaters.getIcon(element, Iconable.ICON_FLAG_VISIBILITY));
CSharpCompletionSorting.force(builder, CSharpCompletionSorting.KindSorter.Type.namespace);
}
else if(element instanceof CSharpTypeDefStatement)
{
CSharpTypeDefStatement typeDefStatement = (CSharpTypeDefStatement) element;
String name = typeDefStatement.getName();
if(name == null)
{
return null;
}
builder = LookupElementBuilder.create(name);
builder = builder.withIcon(IconDescriptorUpdaters.getIcon(element, Iconable.ICON_FLAG_VISIBILITY));
builder = builder.withTypeText(CSharpTypeRefPresentationUtil.buildShortText(typeDefStatement.toTypeRef(), typeDefStatement));
}
else if(element instanceof CSharpLabeledStatementImpl)
{
CSharpLabeledStatementImpl labeledStatement = (CSharpLabeledStatementImpl) element;
String name = labeledStatement.getName();
if(name == null)
{
return null;
}
builder = LookupElementBuilder.create(name);
builder = builder.withIcon(IconDescriptorUpdaters.getIcon(element, Iconable.ICON_FLAG_VISIBILITY));
}
else if(element instanceof DotNetGenericParameter)
{
DotNetGenericParameter typeDefStatement = (DotNetGenericParameter) element;
String name = typeDefStatement.getName();
if(name == null)
{
return null;
}
builder = LookupElementBuilder.create(name);
builder = builder.withIcon(IconDescriptorUpdaters.getIcon(element, Iconable.ICON_FLAG_VISIBILITY));
}
else if(element instanceof DotNetVariable)
{
DotNetVariable dotNetVariable = (DotNetVariable) element;
builder = LookupElementBuilder.create(dotNetVariable);
builder = builder.withIcon(IconDescriptorUpdaters.getIcon(element, Iconable.ICON_FLAG_VISIBILITY));
builder = builder.withTypeText(CSharpTypeRefPresentationUtil.buildShortText(dotNetVariable.toTypeRef(true), dotNetVariable));
builder = builder.withInsertHandler(new InsertHandler<LookupElement>()
{
@Override
@RequiredDispatchThread
public void handleInsert(InsertionContext context, LookupElement item)
{
char completionChar = context.getCompletionChar();
switch(completionChar)
{
case '=':
context.setAddCompletionChar(false);
TailType.EQ.processTail(context.getEditor(), context.getTailOffset());
break;
case ',':
if(completionParent != null && completionParent.getParent() instanceof CSharpCallArgument)
{
context.setAddCompletionChar(false);
TailType.COMMA.processTail(context.getEditor(), context.getTailOffset());
}
break;
}
}
});
}
else if(element instanceof CSharpMacroDefine)
{
builder = LookupElementBuilder.create((CSharpMacroDefine) element);
builder = builder.withIcon(IconDescriptorUpdaters.getIcon(element, Iconable.ICON_FLAG_VISIBILITY));
}
else if(element instanceof CSharpTypeDeclaration)
{
builder = buildTypeLikeElement((CSharpTypeDeclaration) element, extractor);
}
if(builder != null && DotNetAttributeUtil.hasAttribute(element, DotNetTypes.System.ObsoleteAttribute))
{
builder = builder.withStrikeoutness(true);
}
return builder;
}
@RequiredReadAction
private static <E extends DotNetGenericParameterListOwner & DotNetQualifiedElement> LookupElementBuilder buildTypeLikeElement(@NotNull E element, @NotNull DotNetGenericExtractor extractor)
{
String genericText = CSharpElementPresentationUtil.formatGenericParameters(element, extractor);
String name = element.getName();
LookupElementBuilder builder = LookupElementBuilder.create(element, name + (extractor == DotNetGenericExtractor.EMPTY ? "" : genericText));
builder = builder.withPresentableText(name); // always show only name
builder = builder.withIcon(IconDescriptorUpdaters.getIcon(element, Iconable.ICON_FLAG_VISIBILITY));
builder = builder.withTypeText(element.getPresentableParentQName());
builder = builder.withTailText(genericText, true);
if(extractor == DotNetGenericExtractor.EMPTY)
{
builder = withGenericInsertHandler(element, builder);
}
return builder;
}
private static LookupElementBuilder withGenericInsertHandler(PsiElement element, LookupElementBuilder builder)
{
if(!(element instanceof DotNetGenericParameterListOwner))
{
return builder;
}
int genericParametersCount = ((DotNetGenericParameterListOwner) element).getGenericParametersCount();
if(genericParametersCount == 0)
{
return builder;
}
builder = builder.withInsertHandler(LtGtInsertHandler.getInstance(true));
return builder;
}
}