/* * Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan * * 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 com.goide.completion; import com.goide.GoTypes; import com.goide.psi.*; import com.goide.psi.impl.GoPsiImplUtil; import com.goide.template.GoLiveTemplateContextType; import com.intellij.codeInsight.completion.*; import com.intellij.codeInsight.lookup.AutoCompletionPolicy; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.openapi.project.DumbAware; import com.intellij.patterns.*; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiErrorElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; import java.util.Collection; import static com.goide.completion.GoCompletionUtil.CONTEXT_KEYWORD_PRIORITY; import static com.goide.completion.GoCompletionUtil.KEYWORD_PRIORITY; import static com.goide.completion.GoKeywordCompletionProvider.EMPTY_INSERT_HANDLER; import static com.intellij.patterns.PlatformPatterns.psiElement; import static com.intellij.patterns.PlatformPatterns.psiFile; import static com.intellij.patterns.StandardPatterns.*; public class GoKeywordCompletionContributor extends CompletionContributor implements DumbAware { private static final InsertHandler<LookupElement> ADD_BRACKETS_INSERT_HANDLER = new AddBracketsInsertHandler(); public GoKeywordCompletionContributor() { extend(CompletionType.BASIC, packagePattern(), new GoKeywordCompletionProvider(KEYWORD_PRIORITY, AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE, "package")); extend(CompletionType.BASIC, importPattern(), new GoKeywordCompletionProvider(KEYWORD_PRIORITY, "import")); extend(CompletionType.BASIC, topLevelPattern(), new GoKeywordCompletionProvider(KEYWORD_PRIORITY, "const", "var", "func", "type")); extend(CompletionType.BASIC, insideBlockPattern(GoTypes.IDENTIFIER), new GoKeywordCompletionProvider(KEYWORD_PRIORITY, "type", "for", "const", "var", "return", "if", "switch", "go", "defer", "goto")); extend(CompletionType.BASIC, insideBlockPattern(GoTypes.IDENTIFIER), new GoKeywordCompletionProvider(KEYWORD_PRIORITY, EMPTY_INSERT_HANDLER, "fallthrough")); extend(CompletionType.BASIC, insideBlockPattern(GoTypes.IDENTIFIER), new GoKeywordCompletionProvider(KEYWORD_PRIORITY, BracesInsertHandler.INSTANCE, "select")); extend(CompletionType.BASIC, typeDeclaration(), new GoKeywordCompletionProvider(KEYWORD_PRIORITY, BracesInsertHandler.INSTANCE, "interface", "struct")); extend(CompletionType.BASIC, typeExpression(), new GoKeywordCompletionProvider(KEYWORD_PRIORITY, BracesInsertHandler.ONE_LINER, "interface", "struct")); extend(CompletionType.BASIC, insideForStatement(GoTypes.IDENTIFIER), new GoKeywordCompletionProvider(CONTEXT_KEYWORD_PRIORITY, EMPTY_INSERT_HANDLER, "continue")); extend(CompletionType.BASIC, insideBreakStatementOwner(GoTypes.IDENTIFIER), new GoKeywordCompletionProvider(CONTEXT_KEYWORD_PRIORITY, EMPTY_INSERT_HANDLER, "break")); extend(CompletionType.BASIC, typeExpression(), new GoKeywordCompletionProvider(CONTEXT_KEYWORD_PRIORITY, "chan")); extend(CompletionType.BASIC, typeExpression(), new GoKeywordCompletionProvider(CONTEXT_KEYWORD_PRIORITY, ADD_BRACKETS_INSERT_HANDLER, "map")); extend(CompletionType.BASIC, referenceExpression(), new GoKeywordCompletionProvider(CONTEXT_KEYWORD_PRIORITY, ADD_BRACKETS_INSERT_HANDLER, "map")); extend(CompletionType.BASIC, afterIfBlock(GoTypes.IDENTIFIER), new GoKeywordCompletionProvider(CONTEXT_KEYWORD_PRIORITY, "else")); extend(CompletionType.BASIC, afterElseKeyword(), new GoKeywordCompletionProvider(CONTEXT_KEYWORD_PRIORITY, "if")); extend(CompletionType.BASIC, insideSwitchStatement(), new GoKeywordCompletionProvider(CONTEXT_KEYWORD_PRIORITY, "case", "default")); extend(CompletionType.BASIC, rangeClause(), new GoKeywordCompletionProvider(CONTEXT_KEYWORD_PRIORITY, AddSpaceInsertHandler.INSTANCE_WITH_AUTO_POPUP, "range")); } @Override public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) { super.fillCompletionVariants(parameters, result); PsiElement position = parameters.getPosition(); if (insideGoOrDeferStatements().accepts(position) || anonymousFunction().accepts(position)) { InsertHandler<LookupElement> insertHandler = GoKeywordCompletionProvider.createTemplateBasedInsertHandler("go_lang_anonymous_func"); result.addElement(GoKeywordCompletionProvider.createKeywordLookupElement("func", CONTEXT_KEYWORD_PRIORITY, insertHandler)); } if (referenceExpression().accepts(position)) { InsertHandler<LookupElement> insertHandler = GoKeywordCompletionProvider.createTemplateBasedInsertHandler("go_lang_anonymous_struct"); result.addElement(GoKeywordCompletionProvider.createKeywordLookupElement("struct", CONTEXT_KEYWORD_PRIORITY, insertHandler)); } } private static ElementPattern<? extends PsiElement> afterIfBlock(@NotNull IElementType tokenType) { PsiElementPattern.Capture<GoStatement> statement = psiElement(GoStatement.class).afterSiblingSkipping(psiElement().whitespaceCommentEmptyOrError(), psiElement(GoIfStatement.class)); PsiElementPattern.Capture<GoLeftHandExprList> lh = psiElement(GoLeftHandExprList.class).withParent(statement); return psiElement(tokenType).withParent(psiElement(GoReferenceExpressionBase.class).with(new GoNonQualifiedReference()).withParent(lh)) .andNot(afterElseKeyword()).afterLeaf(psiElement(GoTypes.RBRACE)); } private static ElementPattern<? extends PsiElement> rangeClause() { return psiElement(GoTypes.IDENTIFIER).andOr( // for a := ran<caret> | for a = ran<caret> psiElement().withParent(psiElement(GoReferenceExpression.class).withParent(GoRangeClause.class).afterLeaf("=", ":=")), // for ran<caret> psiElement().afterLeaf(psiElement(GoTypes.FOR)) ); } private static ElementPattern<? extends PsiElement> afterElseKeyword() { return psiElement(GoTypes.IDENTIFIER).afterLeafSkipping(psiElement().whitespaceCommentEmptyOrError(), psiElement(GoTypes.ELSE)); } private static ElementPattern<? extends PsiElement> insideForStatement(@NotNull IElementType tokenType) { return insideBlockPattern(tokenType).inside(false, psiElement(GoForStatement.class), psiElement(GoFunctionLit.class)); } private static ElementPattern<? extends PsiElement> insideBreakStatementOwner(@NotNull IElementType tokenType) { return insideBlockPattern(tokenType).with(new InsideBreakStatementOwner()); } private static ElementPattern<? extends PsiElement> insideConstSpec() { return psiElement().inside(false, psiElement(GoConstSpec.class), psiElement(GoStatement.class)); } private static PsiElementPattern.Capture<PsiElement> insideSelectorExpression() { return psiElement().inside(true, psiElement(GoSelectorExpr.class), psiElement(GoStatement.class)); } private static ElementPattern<? extends PsiElement> typeExpression() { return psiElement(GoTypes.IDENTIFIER).withParent( psiElement(GoTypeReferenceExpression.class) .with(new GoNonQualifiedReference()) .with(new GoNotInsideReceiver())); } private static ElementPattern<? extends PsiElement> referenceExpression() { return psiElement(GoTypes.IDENTIFIER).withParent( psiElement(GoReferenceExpression.class) .andNot(insideConstSpec()) .andNot(insideSelectorExpression()) .without(new FieldNameInStructLiteral()) .withParent(not(psiElement(GoSelectorExpr.class))).with(new GoNonQualifiedReference())); } private static ElementPattern<? extends PsiElement> insideSwitchStatement() { return onStatementBeginning(GoTypes.IDENTIFIER, GoTypes.CASE, GoTypes.DEFAULT) .inside(false, psiElement(GoCaseClause.class), psiElement(GoFunctionLit.class)); } private static ElementPattern<? extends PsiElement> typeDeclaration() { return psiElement(GoTypes.IDENTIFIER) .withParent(psiElement(GoTypeReferenceExpression.class).withParent(psiElement(GoType.class).withParent(GoSpecType.class))); } private static PsiElementPattern.Capture<PsiElement> insideGoOrDeferStatements() { return psiElement(GoTypes.IDENTIFIER) .withParent(psiElement(GoExpression.class).withParent(or(psiElement(GoDeferStatement.class), psiElement(GoGoStatement.class)))); } private static ElementPattern<? extends PsiElement> anonymousFunction() { return and(referenceExpression(), psiElement().withParent(psiElement(GoReferenceExpression.class) .withParent(or(psiElement(GoArgumentList.class), not(psiElement(GoLeftHandExprList.class)))))); } private static PsiElementPattern.Capture<PsiElement> insideBlockPattern(@NotNull IElementType tokenType) { return onStatementBeginning(tokenType) .withParent(psiElement(GoExpression.class).withParent(psiElement(GoLeftHandExprList.class).withParent( psiElement(GoStatement.class).inside(GoBlock.class)))); } private static PsiElementPattern.Capture<PsiElement> topLevelPattern() { return onStatementBeginning(GoTypes.IDENTIFIER).withParent( or(psiElement(PsiErrorElement.class).withParent(goFileWithPackage()), psiElement(GoFile.class))); } private static PsiElementPattern.Capture<PsiElement> importPattern() { return onStatementBeginning(GoTypes.IDENTIFIER).withParent(psiElement(GoFile.class)) .afterSiblingSkipping(psiElement().whitespaceCommentOrError(), psiElement(GoImportList.class)); } private static PsiElementPattern.Capture<PsiElement> packagePattern() { return psiElement(GoTypes.IDENTIFIER) .withParent(psiElement(PsiErrorElement.class).withParent(goFileWithoutPackage()).isFirstAcceptedChild(psiElement())); } private static PsiElementPattern.Capture<PsiElement> onStatementBeginning(@NotNull IElementType... tokenTypes) { return psiElement().withElementType(TokenSet.create(tokenTypes)).with(new OnStatementBeginning()); } private static PsiFilePattern.Capture<GoFile> goFileWithPackage() { CollectionPattern<PsiElement> collection = collection(PsiElement.class); CollectionPattern<PsiElement> packageIsFirst = collection.first(psiElement(GoTypes.PACKAGE_CLAUSE)); return psiFile(GoFile.class).withChildren(collection.filter(not(psiElement().whitespaceCommentEmptyOrError()), packageIsFirst)); } private static PsiFilePattern.Capture<GoFile> goFileWithoutPackage() { CollectionPattern<PsiElement> collection = collection(PsiElement.class); ElementPattern<Collection<PsiElement>> emptyOrPackageIsNotFirst = or(collection.empty(), collection.first(not(psiElement(GoTypes.PACKAGE_CLAUSE)))); return psiFile(GoFile.class).withChildren(collection.filter(not(psiElement().whitespaceCommentEmptyOrError()), emptyOrPackageIsNotFirst)); } private static class GoNonQualifiedReference extends PatternCondition<GoReferenceExpressionBase> { public GoNonQualifiedReference() { super("non qualified type"); } @Override public boolean accepts(@NotNull GoReferenceExpressionBase element, ProcessingContext context) { return element.getQualifier() == null; } } private static class GoNotInsideReceiver extends PatternCondition<GoReferenceExpressionBase> { public GoNotInsideReceiver() { super("noi inside receiver"); } @Override public boolean accepts(@NotNull GoReferenceExpressionBase element, ProcessingContext context) { return PsiTreeUtil.getParentOfType(element, GoReceiver.class) == null; } } private static class FieldNameInStructLiteral extends PatternCondition<GoReferenceExpression> { public FieldNameInStructLiteral() { super("field name in struct literal"); } @Override public boolean accepts(@NotNull GoReferenceExpression expression, ProcessingContext context) { GoStructLiteralCompletion.Variants variants = GoStructLiteralCompletion.allowedVariants(expression); return variants == GoStructLiteralCompletion.Variants.FIELD_NAME_ONLY; } } private static class OnStatementBeginning extends PatternCondition<PsiElement> { public OnStatementBeginning() { super("on statement beginning"); } @Override public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext context) { return GoLiveTemplateContextType.Statement.onStatementBeginning(psiElement); } } private static class InsideBreakStatementOwner extends PatternCondition<PsiElement> { public InsideBreakStatementOwner() {super("inside break statement owner");} @Override public boolean accepts(@NotNull PsiElement element, ProcessingContext context) { return GoPsiImplUtil.getBreakStatementOwner(element) != null; } } }