/*
* 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 gnu.trove.THashSet;
import java.util.Collections;
import java.util.Set;
import com.intellij.codeInsight.completion.CompletionContributor;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionResult;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.codeInsight.completion.InsertHandler;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.codeInsight.completion.impl.BetterPrefixMatcher;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.Processor;
import com.intellij.util.indexing.IdFilter;
import consulo.annotations.RequiredDispatchThread;
import consulo.annotations.RequiredReadAction;
import consulo.annotations.RequiredWriteAction;
import consulo.csharp.ide.codeInsight.actions.AddUsingAction;
import consulo.csharp.ide.completion.item.CSharpTypeLikeLookupElement;
import consulo.csharp.ide.completion.util.LtGtInsertHandler;
import consulo.csharp.lang.psi.CSharpMethodDeclaration;
import consulo.csharp.lang.psi.CSharpReferenceExpression;
import consulo.csharp.lang.psi.CSharpUsingListChild;
import consulo.csharp.lang.psi.impl.source.CSharpPsiUtilImpl;
import consulo.csharp.lang.psi.impl.source.resolve.util.CSharpResolveUtil;
import consulo.dotnet.DotNetTypes;
import consulo.dotnet.libraryAnalyzer.NamespaceReference;
import consulo.dotnet.psi.DotNetAttributeUtil;
import consulo.dotnet.psi.DotNetGenericParameter;
import consulo.dotnet.psi.DotNetQualifiedElement;
import consulo.dotnet.psi.DotNetTypeDeclaration;
import consulo.dotnet.resolve.DotNetGenericExtractor;
import consulo.dotnet.resolve.DotNetNamespaceAsElement;
import consulo.dotnet.resolve.DotNetShortNameSearcher;
import consulo.dotnet.resolve.GlobalSearchScopeFilter;
import consulo.ide.IconDescriptorUpdaters;
import consulo.internal.dotnet.msil.decompiler.util.MsilHelper;
/**
* @author VISTALL
* @see com.intellij.codeInsight.completion.JavaNoVariantsDelegator
* @since 23.08.2015
*/
public class CSharpNoVariantsDelegator extends CompletionContributor
{
public static class ResultTracker implements Consumer<CompletionResult>
{
private final CompletionResultSet myResult;
public boolean containsOnlyNamespaces = true;
public BetterPrefixMatcher betterMatcher;
public ResultTracker(CompletionResultSet result)
{
myResult = result;
betterMatcher = new BetterPrefixMatcher.AutoRestarting(result);
}
@Override
public void consume(CompletionResult plainResult)
{
myResult.passResult(plainResult);
LookupElement element = plainResult.getLookupElement();
if(containsOnlyNamespaces && !(CompletionUtil.getTargetElement(element) instanceof DotNetNamespaceAsElement))
{
containsOnlyNamespaces = false;
}
betterMatcher = betterMatcher.improve(plainResult);
}
}
@RequiredReadAction
@Override
@RequiredDispatchThread
public void fillCompletionVariants(CompletionParameters parameters, CompletionResultSet result)
{
final InheritorsHolder holder = new InheritorsHolder(result);
ResultTracker tracker = new ResultTracker(result)
{
@Override
public void consume(CompletionResult plainResult)
{
super.consume(plainResult);
LookupElement element = plainResult.getLookupElement();
Object o = element.getObject();
if(o instanceof PsiElement && CSharpPsiUtilImpl.isTypeLikeElement((PsiElement) o))
{
holder.registerTypeLike((DotNetQualifiedElement) o);
}
}
};
result.runRemainingContributors(parameters, tracker);
final boolean empty = tracker.containsOnlyNamespaces;
if(!empty && parameters.getInvocationCount() == 0)
{
result.restartCompletionWhenNothingMatches();
}
if(empty)
{
delegate(parameters, CSharpCompletionSorting.modifyResultSet(parameters, result), holder);
}
else
{
if(parameters.getCompletionType() == CompletionType.BASIC && parameters.getInvocationCount() <= 1 && CSharpCompletionUtil.mayStartClassName(result) && CSharpCompletionUtil
.isClassNamePossible(parameters))
{
addTypesForUsing(parameters, CSharpCompletionSorting.modifyResultSet(parameters, result.withPrefixMatcher(tracker.betterMatcher)), holder);
}
}
}
@RequiredReadAction
private static void delegate(CompletionParameters parameters, final CompletionResultSet result, final InheritorsHolder inheritorsHolder)
{
if(parameters.getCompletionType() == CompletionType.BASIC)
{
PsiElement position = parameters.getPosition();
if(parameters.getInvocationCount() <= 1 && CSharpCompletionUtil.mayStartClassName(result) && CSharpCompletionUtil.isClassNamePossible(parameters))
{
addTypesForUsing(parameters, result, inheritorsHolder);
return;
}
//suggestChainedCalls(parameters, result, position);
}
if(parameters.getCompletionType() == CompletionType.SMART && parameters.getInvocationCount() == 2)
{
result.runRemainingContributors(parameters.withInvocationCount(3), true);
}
}
@RequiredReadAction
public static void addTypesForUsing(final CompletionParameters parameters, final CompletionResultSet result, final InheritorsHolder inheritorsHolder)
{
final PrefixMatcher matcher = result.getPrefixMatcher();
final CSharpReferenceExpression parent = (CSharpReferenceExpression) parameters.getPosition().getParent();
final Project project = parent.getProject();
final GlobalSearchScope resolveScope = parent.getResolveScope();
final DotNetShortNameSearcher shortNameSearcher = DotNetShortNameSearcher.getInstance(project);
final IdFilter projectIdFilter = new GlobalSearchScopeFilter(resolveScope);
final boolean insideUsing = PsiTreeUtil.getParentOfType(parent, CSharpUsingListChild.class) != null;
final Set<String> names = new THashSet<String>(1000);
shortNameSearcher.collectTypeNames(new Processor<String>()
{
private int count = 0;
@Override
public boolean process(String key)
{
if(count++ % 512 == 0)
{
ProgressManager.checkCanceled();
}
if(matcher.prefixMatches(key))
{
names.add(key);
}
return true;
}
}, resolveScope, projectIdFilter);
final Set<DotNetTypeDeclaration> targets = new THashSet<DotNetTypeDeclaration>(names.size());
int i = 0;
for(String key : names)
{
if(i++ % 512 == 0)
{
ProgressManager.checkCanceled();
}
shortNameSearcher.collectTypes(key, resolveScope, projectIdFilter, new Processor<DotNetTypeDeclaration>()
{
@Override
@RequiredReadAction
public boolean process(DotNetTypeDeclaration typeDeclaration)
{
if(inheritorsHolder.alreadyProcessed(typeDeclaration))
{
return true;
}
targets.add(typeDeclaration);
return true;
}
});
}
i = 0;
for(DotNetTypeDeclaration target : targets)
{
if(i++ % 512 == 0)
{
ProgressManager.checkCanceled();
}
consumeType(parameters, parent, result, insideUsing, target);
}
}
@RequiredReadAction
private static void consumeType(final CompletionParameters completionParameters,
CSharpReferenceExpression referenceExpression,
Consumer<LookupElement> consumer,
boolean insideUsingList,
DotNetTypeDeclaration someType)
{
final String parentQName = someType.getPresentableParentQName();
if(StringUtil.isEmpty(parentQName))
{
return;
}
String presentationText = MsilHelper.cutGenericMarker(someType.getName());
int genericCount = 0;
DotNetGenericParameter[] genericParameters = someType.getGenericParameters();
if((genericCount = genericParameters.length) > 0)
{
presentationText += "<" + StringUtil.join(genericParameters, new Function<DotNetGenericParameter, String>()
{
@Override
public String fun(DotNetGenericParameter parameter)
{
return parameter.getName();
}
}, ", ");
presentationText += ">";
}
String lookupString = insideUsingList ? someType.getPresentableQName() : someType.getName();
if(lookupString == null)
{
return;
}
lookupString = MsilHelper.cutGenericMarker(lookupString);
DotNetQualifiedElement targetElementForLookup = someType;
CSharpMethodDeclaration methodDeclaration = someType.getUserData(CSharpResolveUtil.DELEGATE_METHOD_TYPE);
if(methodDeclaration != null)
{
targetElementForLookup = methodDeclaration;
}
LookupElementBuilder builder = LookupElementBuilder.create(targetElementForLookup, lookupString);
builder = builder.withPresentableText(presentationText);
builder = builder.withIcon(IconDescriptorUpdaters.getIcon(targetElementForLookup, Iconable.ICON_FLAG_VISIBILITY));
builder = builder.withTypeText(parentQName, true);
final InsertHandler<LookupElement> ltGtInsertHandler = genericCount == 0 ? null : LtGtInsertHandler.getInstance(genericCount > 0);
if(insideUsingList)
{
builder = builder.withInsertHandler(ltGtInsertHandler);
}
else
{
builder = builder.withInsertHandler(new InsertHandler<LookupElement>()
{
@Override
@RequiredWriteAction
public void handleInsert(InsertionContext context, LookupElement item)
{
if(ltGtInsertHandler != null)
{
ltGtInsertHandler.handleInsert(context, item);
}
context.commitDocument();
new AddUsingAction(completionParameters.getEditor(), context.getFile(), Collections.<NamespaceReference>singleton(new NamespaceReference(parentQName, null))).execute();
}
});
}
if(DotNetAttributeUtil.hasAttribute(someType, DotNetTypes.System.ObsoleteAttribute))
{
builder = builder.withStrikeoutness(true);
}
consumer.consume(CSharpTypeLikeLookupElement.create(builder, DotNetGenericExtractor.EMPTY, referenceExpression));
}
}