/*
* 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.completion;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import javax.swing.Icon;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.csharp.CSharpIcons;
import consulo.csharp.ide.CSharpElementPresentationUtil;
import consulo.csharp.ide.actions.generate.GenerateImplementMemberHandler;
import consulo.csharp.ide.actions.generate.GenerateOverrideMemberHandler;
import consulo.csharp.lang.psi.CSharpAccessModifier;
import consulo.csharp.lang.psi.CSharpIndexMethodDeclaration;
import consulo.csharp.lang.psi.CSharpMethodDeclaration;
import consulo.csharp.lang.psi.CSharpModifier;
import consulo.csharp.lang.psi.CSharpPropertyDeclaration;
import consulo.csharp.lang.psi.CSharpTypeDeclaration;
import consulo.csharp.lang.psi.CSharpTypeRefPresentationUtil;
import consulo.csharp.lang.psi.CSharpXXXAccessorOwner;
import consulo.csharp.lang.psi.impl.CSharpVisibilityUtil;
import consulo.csharp.lang.psi.impl.source.CSharpBlockStatementImpl;
import consulo.csharp.lang.psi.impl.source.resolve.overrideSystem.OverrideUtil;
import com.intellij.codeInsight.completion.CompletionParameters;
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 com.intellij.icons.AllIcons;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Consumer;
import com.intellij.util.ProcessingContext;
import consulo.annotations.RequiredDispatchThread;
import consulo.annotations.RequiredReadAction;
import consulo.dotnet.ide.DotNetElementPresentationUtil;
import consulo.dotnet.psi.DotNetElement;
import consulo.dotnet.psi.DotNetLikeMethodDeclaration;
import consulo.dotnet.psi.DotNetModifier;
import consulo.dotnet.psi.DotNetModifierListOwner;
import consulo.dotnet.psi.DotNetStatement;
import consulo.dotnet.psi.DotNetTypeDeclaration;
import consulo.dotnet.psi.DotNetVirtualImplementOwner;
import consulo.dotnet.psi.DotNetXXXAccessor;
import consulo.dotnet.resolve.DotNetGenericExtractor;
import consulo.ide.IconDescriptor;
import consulo.ide.IconDescriptorUpdaters;
/**
* @author VISTALL
* @since 17.12.14
*/
public class CSharpOverrideOrImplementCompletionContributor extends CSharpMemberAddByCompletionContributor
{
@RequiredReadAction
@Override
public void processCompletion(@NotNull CompletionParameters parameters,
@NotNull ProcessingContext context,
@NotNull final Consumer<LookupElement> result,
@NotNull CSharpTypeDeclaration typeDeclaration)
{
Collection<DotNetModifierListOwner> overrideItems = getItemsImpl(typeDeclaration);
for(DotNetModifierListOwner overrideItem : overrideItems)
{
LookupElementBuilder builder = buildLookupItem(typeDeclaration, overrideItem, false);
result.consume(builder);
if(!typeDeclaration.isInterface() && overrideItem.hasModifier(CSharpModifier.INTERFACE_ABSTRACT))
{
builder = buildLookupItem(typeDeclaration, overrideItem, true);
result.consume(builder);
}
}
}
@Nullable
@RequiredReadAction
private static LookupElementBuilder buildLookupItem(CSharpTypeDeclaration typeDeclaration, DotNetModifierListOwner element, boolean hide)
{
LookupElementBuilder lookupElementBuilder = null;
if(element instanceof CSharpMethodDeclaration)
{
CSharpMethodDeclaration methodDeclaration = (CSharpMethodDeclaration) element;
StringBuilder builder = new StringBuilder();
CSharpAccessModifier modifier = hide || typeDeclaration.isInterface() ? CSharpAccessModifier.NONE : CSharpAccessModifier.findModifier(methodDeclaration);
if(modifier != CSharpAccessModifier.NONE)
{
builder.append(modifier.getPresentableText()).append(" ");
}
CSharpModifier requiredOverrideModifier = OverrideUtil.getRequiredOverrideModifier(methodDeclaration);
if(requiredOverrideModifier != null)
{
builder.append(requiredOverrideModifier.getPresentableText()).append(" ");
}
formatNameElement(methodDeclaration, builder, hide);
String presentationText = builder.toString();
if(typeDeclaration.isInterface())
{
builder.append(";");
if(!methodDeclaration.hasModifier(DotNetModifier.ABSTRACT))
{
return null;
}
}
else
{
builder.append("{\n");
if(methodDeclaration.hasModifier(DotNetModifier.ABSTRACT))
{
GenerateImplementMemberHandler.generateReturn(builder, element);
}
else
{
GenerateOverrideMemberHandler.generateReturn(builder, element);
}
builder.append("}");
}
lookupElementBuilder = LookupElementBuilder.create(builder.toString());
lookupElementBuilder = lookupElementBuilder.withPresentableText(presentationText);
lookupElementBuilder = lookupElementBuilder.withLookupString(methodDeclaration.getName());
lookupElementBuilder = lookupElementBuilder.withTailText(" {...}", true);
}
else if(element instanceof CSharpXXXAccessorOwner)
{
CSharpXXXAccessorOwner accessorOwner = (CSharpXXXAccessorOwner) element;
StringBuilder builder = new StringBuilder();
CSharpAccessModifier modifier = hide || typeDeclaration.isInterface() ? CSharpAccessModifier.NONE : CSharpAccessModifier.findModifier((DotNetModifierListOwner) accessorOwner);
if(modifier != CSharpAccessModifier.NONE)
{
builder.append(modifier.getPresentableText()).append(" ");
}
formatNameElement(accessorOwner, builder, hide);
String presentationText = builder.toString();
builder.append(buildAccessorTail(typeDeclaration, accessorOwner, hide, true));
lookupElementBuilder = LookupElementBuilder.create(builder.toString());
lookupElementBuilder = lookupElementBuilder.withPresentableText(presentationText);
if(accessorOwner instanceof CSharpIndexMethodDeclaration)
{
lookupElementBuilder = lookupElementBuilder.withLookupString("this");
}
else
{
lookupElementBuilder = lookupElementBuilder.withLookupString(((PsiNamedElement) accessorOwner).getName());
}
lookupElementBuilder = lookupElementBuilder.withTailText(buildAccessorTail(typeDeclaration, accessorOwner, hide, false), true);
}
if(lookupElementBuilder == null)
{
return null;
}
final Icon rightIcon;
if(typeDeclaration.isInterface())
{
if(element.hasModifier(DotNetModifier.ABSTRACT))
{
rightIcon = AllIcons.Gutter.OverridingMethod;
}
else
{
return null;
}
}
else
{
if(hide)
{
rightIcon = CSharpIcons.Gutter.HidingMethod;
}
else if(element.hasModifier(DotNetModifier.ABSTRACT))
{
rightIcon = AllIcons.Gutter.ImplementingMethod;
}
else
{
rightIcon = AllIcons.Gutter.OverridingMethod;
}
}
IconDescriptor iconDescriptor = new IconDescriptor(IconDescriptorUpdaters.getIcon(element, 0));
iconDescriptor.setRightIcon(rightIcon);
lookupElementBuilder = lookupElementBuilder.withIcon(iconDescriptor.toIcon());
PsiElement parent = element.getParent();
if(parent instanceof DotNetTypeDeclaration)
{
lookupElementBuilder = lookupElementBuilder.withTypeText(DotNetElementPresentationUtil.formatTypeWithGenericParameters((DotNetTypeDeclaration) parent));
}
lookupElementBuilder = lookupElementBuilder.withInsertHandler(new InsertHandler<LookupElement>()
{
@Override
@RequiredDispatchThread
public void handleInsert(InsertionContext context, LookupElement item)
{
CaretModel caretModel = context.getEditor().getCaretModel();
PsiElement elementAt = context.getFile().findElementAt(caretModel.getOffset() - 1);
if(elementAt == null)
{
return;
}
DotNetVirtualImplementOwner virtualImplementOwner = PsiTreeUtil.getParentOfType(elementAt, DotNetVirtualImplementOwner.class);
if(virtualImplementOwner == null)
{
return;
}
if(virtualImplementOwner instanceof CSharpMethodDeclaration)
{
PsiElement codeBlock = ((CSharpMethodDeclaration) virtualImplementOwner).getCodeBlock();
if(codeBlock instanceof CSharpBlockStatementImpl)
{
DotNetStatement[] statements = ((CSharpBlockStatementImpl) codeBlock).getStatements();
if(statements.length > 0)
{
caretModel.moveToOffset(statements[0].getTextOffset() + statements[0].getTextLength());
}
else
{
caretModel.moveToOffset(((CSharpBlockStatementImpl) codeBlock).getLeftBrace().getTextOffset() + 1);
}
}
}
context.commitDocument();
CodeStyleManager.getInstance(context.getProject()).reformat(virtualImplementOwner);
}
});
return lookupElementBuilder;
}
@NotNull
@RequiredReadAction
private static String buildAccessorTail(CSharpTypeDeclaration typeDeclaration, CSharpXXXAccessorOwner owner, boolean hide, boolean body)
{
StringBuilder builder = new StringBuilder();
builder.append(" { ");
for(DotNetXXXAccessor accessor : owner.getAccessors())
{
DotNetXXXAccessor.Kind accessorKind = accessor.getAccessorKind();
if(accessorKind == null)
{
continue;
}
builder.append(accessorKind.name().toLowerCase(Locale.US));
if(!body)
{
builder.append("; ");
}
else
{
if(typeDeclaration.isInterface())
{
builder.append("; ");
}
else
{
builder.append("{\n");
if(owner.hasModifier(DotNetModifier.ABSTRACT))
{
GenerateImplementMemberHandler.generateReturn(builder, accessor);
}
else
{
GenerateOverrideMemberHandler.generateReturn(builder, accessor);
}
builder.append("}");
}
}
}
builder.append("}");
return builder.toString();
}
@RequiredReadAction
public static void formatNameElement(@NotNull DotNetElement element, @NotNull StringBuilder builder, boolean hide)
{
if(element instanceof CSharpPropertyDeclaration)
{
CSharpPropertyDeclaration propertyDeclaration = (CSharpPropertyDeclaration) element;
CSharpTypeRefPresentationUtil.appendTypeRef(element, builder, propertyDeclaration.toTypeRef(true), CSharpTypeRefPresentationUtil.QUALIFIED_NAME_WITH_KEYWORD);
builder.append(" ");
if(hide)
{
builder.append(DotNetElementPresentationUtil.formatTypeWithGenericParameters((CSharpTypeDeclaration) element.getParent()));
builder.append(".");
}
builder.append(((PsiNamedElement) element).getName());
}
else if(element instanceof DotNetLikeMethodDeclaration)
{
DotNetLikeMethodDeclaration likeMethodDeclaration = (DotNetLikeMethodDeclaration) element;
CSharpTypeRefPresentationUtil.appendTypeRef(element, builder, likeMethodDeclaration.getReturnTypeRef(), CSharpTypeRefPresentationUtil.QUALIFIED_NAME_WITH_KEYWORD);
builder.append(" ");
if(hide)
{
builder.append(DotNetElementPresentationUtil.formatTypeWithGenericParameters((CSharpTypeDeclaration) element.getParent()));
builder.append(".");
}
if(likeMethodDeclaration instanceof CSharpIndexMethodDeclaration)
{
builder.append("this");
}
else
{
builder.append(((PsiNamedElement) element).getName());
}
CSharpElementPresentationUtil.formatTypeGenericParameters(likeMethodDeclaration.getGenericParameters(), builder);
CSharpElementPresentationUtil.formatParameters(likeMethodDeclaration, builder, CSharpElementPresentationUtil.METHOD_WITH_RETURN_TYPE | CSharpElementPresentationUtil
.METHOD_PARAMETER_NAME);
}
else
{
builder.append(((PsiNamedElement) element).getName());
}
}
@NotNull
@RequiredReadAction
public static Collection<DotNetModifierListOwner> getItemsImpl(@NotNull CSharpTypeDeclaration typeDeclaration)
{
Collection<PsiElement> allMembers = OverrideUtil.getAllMembers(typeDeclaration, typeDeclaration.getResolveScope(), DotNetGenericExtractor.EMPTY, false, true);
List<DotNetModifierListOwner> elements = new ArrayList<DotNetModifierListOwner>();
for(PsiElement element : allMembers)
{
if(element instanceof DotNetModifierListOwner)
{
if(((DotNetModifierListOwner) element).hasModifier(DotNetModifier.STATIC))
{
continue;
}
if(!CSharpVisibilityUtil.isVisible(typeDeclaration, element))
{
continue;
}
if(element instanceof CSharpMethodDeclaration || element instanceof CSharpXXXAccessorOwner)
{
elements.add((DotNetModifierListOwner) element);
}
}
}
return elements;
}
}