/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.completion;
import com.intellij.codeInsight.completion.CompletionContributor;
import com.intellij.codeInsight.completion.CompletionInitializationContext;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.completion.CompletionSorter;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementWeigher;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.patterns.ElementPattern;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.ProcessingContext;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.module.IModule;
import gw.plugin.ij.completion.handlers.*;
import gw.plugin.ij.lang.parser.GosuElementTypes;
import gw.plugin.ij.lang.parser.GosuRawPsiElement;
import gw.plugin.ij.lang.psi.IGosuPsiElement;
import gw.plugin.ij.lang.psi.impl.AbstractGosuClassFileImpl;
import gw.plugin.ij.lang.psi.impl.GosuClassFileImpl;
import gw.plugin.ij.lang.psi.impl.expressions.GosuIdentifierExpressionImpl;
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.GosuNotAStatementImpl;
import gw.plugin.ij.lang.psi.impl.statements.typedef.GosuAnonymousClassDefinitionImpl;
import gw.plugin.ij.lang.psi.impl.statements.typedef.GosuClassDefinitionImpl;
import gw.plugin.ij.lang.psi.impl.statements.typedef.GosuSyntheticClassDefinitionImpl;
import gw.plugin.ij.lang.psi.impl.types.CompletionVoter;
import gw.plugin.ij.util.ExceptionUtil;
import gw.plugin.ij.util.GosuModuleUtil;
import org.jetbrains.annotations.NotNull;
import static com.intellij.patterns.PlatformPatterns.psiElement;
import static com.intellij.patterns.StandardPatterns.*;
import static com.google.common.base.Objects.firstNonNull;
public class GosuCompletionContributor extends CompletionContributor {
private static final ElementPattern<PsiElement> AFTER_DOT = psiElement().afterLeaf(".");
private static final ElementPattern<PsiElement> AFTER_SHARP = psiElement().afterLeaf("#");
private static final ElementPattern<PsiElement> AFTER_QUESTION_DOT = psiElement().afterLeaf("?.");
private static final ElementPattern<PsiElement> AFTER_STAR_DOT = psiElement().afterLeaf("*.");
private static final ElementPattern<PsiElement> AFTER_COLON = psiElement().afterLeaf(":").withParent(IGosuPsiElement.class);
public GosuCompletionContributor() {
// You can use this to debug where certain code completions are being invoked
// extend(CompletionType.BASIC,
// psiElement().withParents(PsiElement.class),
// new CompletionProvider<CompletionParameters>() {
// @Override
// protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {
// System.out.println("here");
// }
// });
extend(CompletionType.BASIC,
psiElement(),
new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {
safeComplete(new TemplateSyntaxHandler(parameters, updateResult(parameters, result)));
}
});
extend(CompletionType.BASIC,
psiElement().withParent(GosuTypeLiteralImpl.class),
new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull final CompletionResultSet result) {
safeComplete(new TypeCompletionHandler(parameters, updateResult(parameters, result), 0));
}
}
);
extend(CompletionType.BASIC,
or(psiElement().withParents(PsiElement.class, GosuClassDefinitionImpl.class),
psiElement().withParents(PsiElement.class, GosuClassFileImpl.class),
psiElement().withParents(PsiElement.class, GosuAnonymousClassDefinitionImpl.class),
psiElement().withParents(PsiElement.class, GosuNotAStatementImpl.class, GosuSyntheticClassDefinitionImpl.class), // for Gosu Scratch Pad
psiElement().withParents(PsiElement.class, GosuRawPsiElement.class, GosuSyntheticClassDefinitionImpl.class)), // for *.gsp
new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {
safeComplete(new ClassKeywordsHandler(parameters, updateResult(parameters, result)));
}
});
extend(CompletionType.BASIC,
and(
or(psiElement().withParent(GosuIdentifierExpressionImpl.class),
psiElement().withParent(GosuIdentifierImpl.class)),
not(AFTER_COLON)),
new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull final CompletionResultSet result) {
safeComplete(new SymbolCompletionHandler(parameters, updateResult(parameters, result)));
}
}
);
extend(
CompletionType.BASIC,
and(psiElement().withParent(GosuRawPsiElement.class),
not(AFTER_COLON)),
new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull final CompletionResultSet result) {
if (((GosuRawPsiElement) parameters.getPosition().getParent()).getNode().getElementType() == GosuElementTypes.ELEM_TYPE_NoOpStatement) {
safeComplete(new SymbolCompletionHandler(parameters, updateResult(parameters, result)));
}
}
}
);
extend(CompletionType.BASIC,
or(AFTER_DOT, AFTER_QUESTION_DOT, AFTER_STAR_DOT),
new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
safeComplete(new MemberPathCompletionHandler(parameters, updateResult(parameters, result)));
}
});
extend(CompletionType.BASIC, AFTER_DOT, new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
safeComplete(new PackageCompletionHandler(parameters, updateResult(parameters, result)));
}
});
extend(CompletionType.BASIC, AFTER_COLON, new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
safeComplete(new InitializerCompletionHandler(parameters, updateResult(parameters, result)));
}
});
extend(CompletionType.BASIC, AFTER_SHARP, new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
safeComplete(new FeatureRefCompletionHandler(parameters, updateResult(parameters, result)));
}
});
}
@NotNull
private CompletionResultSet updateResult(CompletionParameters params, @NotNull CompletionResultSet result) {
CompletionResultSet completionResultSet = result;
CompletionSorter completionSorter =
CompletionSorter.defaultSorter(params, completionResultSet.getPrefixMatcher())
.weighBefore("priority", new LookupElementWeigher("gosuWeight") {
@NotNull
@Override
public Comparable weigh(@NotNull LookupElement element) {
final Integer weight = element.getUserData(AbstractCompletionHandler.COMPLETION_WEIGHT);
return firstNonNull(weight, 0);
}
});
completionResultSet = completionResultSet.withRelevanceSorter(completionSorter);
return completionResultSet;
}
private static boolean relevant(AbstractCompletionHandler handler) {
CompletionParameters params = handler.getContext();
PsiFile file = params.getPosition().getContainingFile();
if (file instanceof CompletionVoter) {
return ((CompletionVoter) file).isCompletionAllowed(handler);
}
return true;
}
private static void safeComplete(@NotNull AbstractCompletionHandler handler) {
if (!relevant(handler)) {
return;
}
// hack to short circuit completion
if (atNumber(handler.getContext())) {
return;
}
final IModule module = GosuModuleUtil.findModuleForPsiElement(handler.getContext().getPosition());
TypeSystem.pushModule(module);
try {
PsiFile psiFile = handler.getContext().getOriginalFile();
if( psiFile instanceof AbstractGosuClassFileImpl ) {
((AbstractGosuClassFileImpl)psiFile).reparsePsiFromContent();
}
handler.handleCompletePath();
} catch (RuntimeException e) {
if (ExceptionUtil.isWrappedCanceled(e)) {
throw e instanceof ProcessCanceledException ? e : new ProcessCanceledException(e);
}
throw e;
} finally {
TypeSystem.popModule(module);
}
}
private static boolean atNumber(@NotNull CompletionParameters parameters) {
PsiElement originalPosition = parameters.getOriginalPosition();
if (originalPosition != null) {
ASTNode node = originalPosition.getNode();
if (node != null) {
ASTNode treePrev = node.getTreePrev();
if (treePrev != null && treePrev.getElementType() != GosuElementTypes.ELEM_TYPE_NumericLiteral) {
treePrev = treePrev.getLastChildNode();
}
if (treePrev != null && treePrev.getElementType() == GosuElementTypes.ELEM_TYPE_NumericLiteral) {
ASTNode lastChildNode = treePrev.getLastChildNode();
if (lastChildNode != null && lastChildNode.getText().equals(".")) {
return true;
}
}
}
}
return false;
}
@Override
public void duringCompletion(@NotNull CompletionInitializationContext context) {
Document document = context.getEditor().getDocument();
String toReplace = document.getText().substring(context.getStartOffset(), context.getIdentifierEndOffset());
int newLine = toReplace.indexOf("\n");
if (newLine >= 0) {
context.getOffsetMap().addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, context.getStartOffset() + newLine);
}
}
}