/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.completion.handlers;
import com.google.common.collect.ImmutableList;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PsiJavaElementPattern;
import com.intellij.patterns.PsiJavaPatterns;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiIdentifier;
import com.intellij.psi.PsiKeyword;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameterList;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiReferenceList;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeParameter;
import com.intellij.psi.filters.ClassFilter;
import com.intellij.psi.filters.ElementFilter;
import com.intellij.psi.filters.classes.AnyInnerFilter;
import com.intellij.psi.filters.element.ExcludeDeclaredFilter;
import com.intellij.psi.filters.types.AssignableFromFilter;
import gw.lang.reflect.IType;
import gw.lang.reflect.ITypeLoader;
import gw.lang.reflect.TypeSystem;
import gw.plugin.ij.completion.proposals.GosuTypeCompletionProposal;
import gw.plugin.ij.completion.proposals.PrimitiveCompletionProposal;
import gw.plugin.ij.completion.proposals.PsiClassCompletionProposal;
import gw.plugin.ij.completion.proposals.RawCompletionProposal;
import gw.plugin.ij.lang.psi.impl.GosuScratchpadFileImpl;
import gw.plugin.ij.lang.psi.impl.expressions.GosuIdentifierImpl;
import gw.plugin.ij.lang.psi.impl.expressions.GosuTypeLiteralImpl;
import gw.plugin.ij.lang.psi.impl.statements.GosuUsesStatementImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.intellij.patterns.PlatformPatterns.psiElement;
public class TypeCompletionHandler extends AbstractCompletionHandler {
private static final List<PsiPrimitiveType> PRIMITIVE_TYPES = ImmutableList.of(
PsiType.BYTE,
PsiType.CHAR,
PsiType.DOUBLE, PsiType.FLOAT,
PsiType.INT, PsiType.LONG, PsiType.SHORT,
PsiType.BOOLEAN,
PsiType.VOID);
public static final PsiJavaElementPattern.Capture<PsiElement> IN_TYPE_PARAMETER = PsiJavaPatterns.psiElement()
.afterLeaf(PsiKeyword.EXTENDS, PsiKeyword.SUPER, "&")
.withParent(PsiJavaPatterns.psiElement(PsiReferenceList.class).withParent(PsiTypeParameter.class));
public static final PsiJavaElementPattern.Capture<PsiElement> INSIDE_METHOD_THROWS_CLAUSE = PsiJavaPatterns.psiElement()
.afterLeaf(PsiKeyword.THROWS, ",")
.inside(PsiMethod.class)
.andNot(PsiJavaPatterns.psiElement().inside(PsiCodeBlock.class))
.andNot(PsiJavaPatterns.psiElement().inside(PsiParameterList.class));
public static final ElementPattern<PsiElement> AFTER_THROW_NEW = psiElement()
.afterLeaf(psiElement().withText(PsiKeyword.NEW).afterLeaf(psiElement().withText(PsiKeyword.THROW)));
private final ElementFilter _filter;
private int _weight;
@NotNull
private final PrefixMatcher _prefixMatcher;
private final boolean _javaCtx;
public TypeCompletionHandler(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result, int weight) {
super(parameters, result);
_filter = getDefaultFilter(parameters.getPosition());
_weight = weight;
_prefixMatcher = result.getPrefixMatcher();
_javaCtx = parameters.getPosition() instanceof PsiIdentifier;
}
public TypeCompletionHandler(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result, ElementFilter filter, int weight) {
super(parameters, result);
_filter = filter;
_weight = weight;
_prefixMatcher = result.getPrefixMatcher();
_javaCtx = parameters.getPosition() instanceof PsiIdentifier;
}
@NotNull
private ElementFilter getDefaultFilter(PsiElement insertedElement) {
return AFTER_THROW_NEW.accepts(insertedElement)
? new AssignableFromFilter("java.lang.Throwable")
: IN_TYPE_PARAMETER.accepts(insertedElement)
? new ExcludeDeclaredFilter(new ClassFilter(PsiTypeParameter.class))
: INSIDE_METHOD_THROWS_CLAUSE.accepts(insertedElement)
? new AnyInnerFilter(new AssignableFromFilter("java.lang.Throwable"))
: getLocalFilter( insertedElement );
}
@Override
public void handleCompletePath() {
final PsiElement position = getContext().getPosition();
if (position instanceof GosuIdentifierImpl) {
if (position.getParent() instanceof GosuTypeLiteralImpl) {
if (position.getParent().getParent() instanceof GosuUsesStatementImpl) {
return;
}
}
}
// primitives are always available
addPrimitives();
addScratchpadInnerClasses(position);
//## THIS IS DOGASS SLOW, the eventual call to CustomPsiClassCache.getByShortName() is fucking molasses, don't do this here.
// AllClassesGetter.processJavaClasses( _params, _prefixMatcher, _params.getInvocationCount() <= 1, new Consumer<PsiClass>() {
// ...
// } );
addTypeLoaderTypes();
if( getTotalCompletions() == 0 && _params.getInvocationCount() < 2 ) {
stopFilterByInvocationCount();
addTypeLoaderTypes();
}
}
private void addTypeLoaderTypes() {
Set<String> fqnSet = new HashSet<String>();
for( ITypeLoader loader : TypeSystem.getAllTypeLoaders() ) {
if( !loader.showTypeNamesInIDE() ) {
continue;
}
for( CharSequence typeName : loader.getAllTypeNames() ) {
String fqn = typeName.toString();
int iAngle = fqn.indexOf( '<' );
if( iAngle >= 0 ) {
fqn = fqn.substring( 0, iAngle );
}
if( fqnSet.contains( fqn ) ) {
continue;
}
int iDot = fqn.lastIndexOf( '.' );
String relativeName = iDot >= 0 ? fqn.substring( iDot + 1 ) : fqn;
if( _prefixMatcher.prefixMatches( relativeName ) ) {
if( _filter == null || _filter.isAcceptable( fqn, _params.getPosition() ) ) {
IType type = TypeSystem.getByFullNameIfValid( fqn, loader.getModule() );
if( type == null ) {
continue;
}
// Note this second cache lookup exists because there is a many-to-one mapping
// between type names and ITypes e.g., several type names may map to an entity type.
fqn = type.getName();
if( fqnSet.contains( fqn ) ) {
continue;
}
fqnSet.add( fqn );
addCompletion( new GosuTypeCompletionProposal( type, getContext().getPosition(), _weight ), _javaCtx );
}
}
}
}
}
private void addScratchpadInnerClasses(@Nullable PsiElement position) {
if (position == null) {
return;
}
PsiFile psiFile = position.getContainingFile();
if (!(psiFile instanceof GosuScratchpadFileImpl)) {
return;
}
GosuScratchpadFileImpl scratchpadFile = (GosuScratchpadFileImpl) psiFile;
for (PsiClass cls : scratchpadFile.getPsiClass().getInnerClasses()) {
if (cls.getName().startsWith(_prefixMatcher.getPrefix())) {
addCompletion(new PsiClassCompletionProposal(cls, _weight), _javaCtx);
}
}
}
private void addPrimitives() {
for (PsiType type : PRIMITIVE_TYPES) {
if (type.getCanonicalText().startsWith(_prefixMatcher.getPrefix())) {
addCompletion(new PrimitiveCompletionProposal(type), _javaCtx);
}
}
if ("block".startsWith(_prefixMatcher.getPrefix())) {
addCompletion(new RawCompletionProposal("block"));
}
}
}