/* * 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 static com.intellij.patterns.StandardPatterns.or; import static com.intellij.patterns.StandardPatterns.psiElement; import org.jetbrains.annotations.NotNull; import consulo.csharp.ide.completion.insertHandler.CSharpTailInsertHandler; import consulo.csharp.ide.completion.patterns.CSharpPatterns; import consulo.csharp.ide.completion.util.ExpressionOrStatementInsertHandler; import consulo.csharp.ide.completion.util.SpaceInsertHandler; import consulo.csharp.lang.psi.CSharpLocalVariable; import consulo.csharp.lang.psi.CSharpSimpleLikeMethodAsElement; import consulo.csharp.lang.psi.CSharpSoftTokens; import consulo.csharp.lang.psi.CSharpTokenSets; import consulo.csharp.lang.psi.CSharpTokens; import consulo.csharp.lang.psi.UsefulPsiTreeUtil; import consulo.csharp.lang.psi.impl.CSharpImplicitReturnModel; import consulo.csharp.lang.psi.impl.source.*; import consulo.csharp.module.extension.CSharpLanguageVersion; import consulo.csharp.module.extension.CSharpModuleUtil; import com.intellij.codeInsight.AutoPopupController; import com.intellij.codeInsight.TailType; import com.intellij.codeInsight.completion.CompletionContributor; import com.intellij.codeInsight.completion.CompletionParameters; import com.intellij.codeInsight.completion.CompletionResultSet; import com.intellij.codeInsight.completion.CompletionType; 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.openapi.editor.Editor; import com.intellij.openapi.util.Condition; import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ProcessingContext; import consulo.annotations.RequiredReadAction; import consulo.codeInsight.completion.CompletionProvider; import consulo.dotnet.DotNetTypes; import consulo.dotnet.psi.DotNetStatement; import consulo.dotnet.resolve.DotNetTypeRef; import consulo.dotnet.resolve.DotNetTypeRefUtil; import consulo.util.NotNullPairFunction; /** * @author VISTALL * @since 12.06.14 */ public class CSharpStatementCompletionContributor extends CompletionContributor implements CSharpTokenSets { private static class ReturnStatementInsertHandler implements InsertHandler<LookupElement> { private final CSharpSimpleLikeMethodAsElement myPseudoMethod; public ReturnStatementInsertHandler(CSharpSimpleLikeMethodAsElement pseudoMethod) { myPseudoMethod = pseudoMethod; } @Override public void handleInsert(InsertionContext insertionContext, LookupElement item) { Editor editor = insertionContext.getEditor(); int offset = editor.getCaretModel().getOffset(); boolean isVoidReturnType = DotNetTypeRefUtil.isVmQNameEqual(myPseudoMethod.getReturnTypeRef(), myPseudoMethod, DotNetTypes.System.Void); if(!isVoidReturnType) { TailType.insertChar(editor, offset, ' '); TailType.insertChar(editor, offset + 1, ';'); } else { TailType.insertChar(editor, offset, ';'); } insertionContext.getEditor().getCaretModel().moveToOffset(offset + 1); if(!isVoidReturnType) { AutoPopupController.getInstance(editor.getProject()).autoPopupMemberLookup(editor, null); } } } private static final TokenSet ourParStatementKeywords = TokenSet.create(IF_KEYWORD, FOR_KEYWORD, FOREACH_KEYWORD, FOREACH_KEYWORD, FIXED_KEYWORD, UNCHECKED_KEYWORD, CHECKED_KEYWORD, SWITCH_KEYWORD, USING_KEYWORD, WHILE_KEYWORD, DO_KEYWORD, UNSAFE_KEYWORD, TRY_KEYWORD, LOCK_KEYWORD); private static final TokenSet ourCaseAndDefaultKeywords = TokenSet.create(CASE_KEYWORD, DEFAULT_KEYWORD); private static final TokenSet ourContinueAndBreakKeywords = TokenSet.create(BREAK_KEYWORD, CONTINUE_KEYWORD, GOTO_KEYWORD); private static final TokenSet ourCatchFinallyKeywords = TokenSet.create(CATCH_KEYWORD, FINALLY_KEYWORD); public CSharpStatementCompletionContributor() { extend(CompletionType.BASIC, CSharpPatterns.statementStart().inside(or(psiElement().inside(CSharpForeachStatementImpl.class), psiElement().inside(CSharpForStatementImpl.class), psiElement().inside(CSharpWhileStatementImpl.class), psiElement().inside(CSharpDoWhileStatementImpl.class))), new CompletionProvider() { @RequiredReadAction @Override public void addCompletions(@NotNull final CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { CSharpCompletionUtil.tokenSetToLookup(result, ourContinueAndBreakKeywords, new NotNullPairFunction<LookupElementBuilder, IElementType, LookupElement>() { @NotNull @Override public LookupElementBuilder fun(LookupElementBuilder t, IElementType v) { t = t.withInsertHandler(new InsertHandler<LookupElement>() { @Override public void handleInsert(InsertionContext insertionContext, LookupElement item) { int offset = insertionContext.getEditor().getCaretModel().getOffset(); insertionContext.getDocument().insertString(offset, ";"); insertionContext.getEditor().getCaretModel().moveToOffset(offset + 1); } }); return t; } }, null); } }); extend(CompletionType.BASIC, CSharpPatterns.statementStart().inside(psiElement().inside(CSharpSimpleLikeMethodAsElement.class)).andNot(psiElement().inside(CSharpFinallyStatementImpl.class)), new CompletionProvider() { @RequiredReadAction @Override public void addCompletions(@NotNull final CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { final CSharpSimpleLikeMethodAsElement pseudoMethod = PsiTreeUtil.getParentOfType(parameters.getPosition(), CSharpSimpleLikeMethodAsElement.class); assert pseudoMethod != null; CSharpCompletionUtil.elementToLookup(result, CSharpTokens.RETURN_KEYWORD, new NotNullPairFunction<LookupElementBuilder, IElementType, LookupElement>() { @NotNull @Override public LookupElement fun(LookupElementBuilder t, IElementType v) { t = t.withInsertHandler(new ReturnStatementInsertHandler(pseudoMethod)); return t; } }, null); } }); extend(CompletionType.BASIC, CSharpPatterns.statementStart().inside(CSharpLabeledStatementImpl.class), new CompletionProvider() { @RequiredReadAction @Override public void addCompletions(@NotNull final CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { CSharpCompletionUtil.tokenSetToLookup(result, ourContinueAndBreakKeywords, new NotNullPairFunction<LookupElementBuilder, IElementType, LookupElement>() { @NotNull @Override public LookupElement fun(LookupElementBuilder t, IElementType v) { t = t.withInsertHandler(new InsertHandler<LookupElement>() { @Override public void handleInsert(InsertionContext insertionContext, LookupElement item) { int offset = insertionContext.getEditor().getCaretModel().getOffset(); insertionContext.getDocument().insertString(offset, " ;"); insertionContext.getEditor().getCaretModel().moveToOffset(offset + 1); Editor editor = parameters.getEditor(); AutoPopupController.getInstance(editor.getProject()).autoPopupMemberLookup(editor, null); } }); return t; } }, null); } }); extend(CompletionType.BASIC, CSharpPatterns.statementStart().withSuperParent(6, CSharpSwitchStatementImpl.class), new CompletionProvider() { @RequiredReadAction @Override public void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { CSharpCompletionUtil.elementToLookup(result, CSharpTokens.BREAK_KEYWORD, null, null); CSharpCompletionUtil.tokenSetToLookup(result, ourCaseAndDefaultKeywords, new NotNullPairFunction<LookupElementBuilder, IElementType, LookupElement>() { @NotNull @Override public LookupElement fun(LookupElementBuilder t, final IElementType v) { if(v == CSharpTokens.DEFAULT_KEYWORD) { t = t.withInsertHandler(new CSharpTailInsertHandler(TailType.CASE_COLON)); } else { t = t.withInsertHandler(SpaceInsertHandler.INSTANCE); } return t; } }, null); } }); extend(CompletionType.BASIC, CSharpPatterns.statementStart(), new CompletionProvider() { @Override @RequiredReadAction public void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { CSharpLocalVariable localVariable = PsiTreeUtil.getParentOfType(parameters.getPosition(), CSharpLocalVariable.class); assert localVariable != null; if(!CSharpPsiUtilImpl.isNullOrEmpty(localVariable)) { return; } CSharpCompletionUtil.tokenSetToLookup(result, ourParStatementKeywords, new NotNullPairFunction<LookupElementBuilder, IElementType, LookupElement>() { @NotNull @Override public LookupElement fun(LookupElementBuilder t, final IElementType v) { t = t.withInsertHandler(buildInsertHandler(v)); return t; } }, null); CSharpCompletionUtil.elementToLookup(result, CSharpTokens.THROW_KEYWORD, CSharpCompletionUtil.ourSpaceInsert, null); } }); extend(CompletionType.BASIC, CSharpPatterns.statementStart(), new CompletionProvider() { @RequiredReadAction @Override public void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { final PsiElement position = parameters.getPosition(); CSharpCompletionUtil.elementToLookup(result, CSharpSoftTokens.YIELD_KEYWORD, new NotNullPairFunction<LookupElementBuilder, IElementType, LookupElement>() { @NotNull @Override public LookupElement fun(LookupElementBuilder t, final IElementType v) { t = t.withInsertHandler(new InsertHandler<LookupElement>() { @Override public void handleInsert(InsertionContext insertionContext, LookupElement item) { Editor editor = insertionContext.getEditor(); if(insertionContext.getCompletionChar() == ';') { insertionContext.getDocument().insertString(insertionContext.getTailOffset(), " "); editor.getCaretModel().moveToOffset(insertionContext.getTailOffset() - 1); } else if(insertionContext.getCompletionChar() == ' ') { TailType.insertChar(editor, editor.getCaretModel().getOffset(), ';'); editor.getCaretModel().moveToOffset(editor.getCaretModel().getOffset() - 1); } else { insertionContext.getDocument().insertString(insertionContext.getTailOffset(), " ;"); editor.getCaretModel().moveToOffset(insertionContext.getTailOffset() - 1); } AutoPopupController.getInstance(insertionContext.getProject()).autoPopupMemberLookup(editor, null); } }); return t; } }, new Condition<IElementType>() { @Override public boolean value(IElementType elementType) { CSharpSimpleLikeMethodAsElement methodAsElement = PsiTreeUtil.getParentOfType(position, CSharpSimpleLikeMethodAsElement.class); if(methodAsElement == null) { return false; } DotNetTypeRef returnTypeRef = methodAsElement.getReturnTypeRef(); if(CSharpImplicitReturnModel.YieldEnumerable.extractTypeRef(returnTypeRef, position) != DotNetTypeRef.ERROR_TYPE) { return true; } if(CSharpImplicitReturnModel.YieldEnumerator.extractTypeRef(returnTypeRef, position) != DotNetTypeRef.ERROR_TYPE) { return true; } return false; } } ); } }); extend(CompletionType.BASIC, CSharpPatterns.statementStart(), new CompletionProvider() { @RequiredReadAction @Override public void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { CSharpCompletionUtil.elementToLookup(result, CSharpTokens.CONST_KEYWORD, CSharpCompletionUtil.ourSpaceInsert, null); } }); extend(CompletionType.BASIC, psiElement().afterLeaf(psiElement().withElementType(CSharpSoftTokens.YIELD_KEYWORD)), new CompletionProvider() { @RequiredReadAction @Override public void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { final PsiElement position = parameters.getPosition(); final CSharpSimpleLikeMethodAsElement methodAsElement = PsiTreeUtil.getParentOfType(position, CSharpSimpleLikeMethodAsElement.class); if(methodAsElement == null) { return; } CSharpCompletionUtil.elementToLookup(result, CSharpTokens.BREAK_KEYWORD, null, null); CSharpCompletionUtil.elementToLookup(result, CSharpTokens.RETURN_KEYWORD, new NotNullPairFunction<LookupElementBuilder, IElementType, LookupElement>() { @NotNull @Override public LookupElement fun(LookupElementBuilder t, IElementType v) { t = t.withInsertHandler(new ReturnStatementInsertHandler(methodAsElement)); return t; } }, null); } }); extend(CompletionType.BASIC, psiElement().afterLeaf(psiElement().withElementType(CSharpTokens.ELSE_KEYWORD)).withSuperParent(2, CSharpExpressionStatementImpl.class), new CompletionProvider() { @RequiredReadAction @Override public void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { CSharpCompletionUtil.elementToLookup(result, CSharpTokens.IF_KEYWORD, new NotNullPairFunction<LookupElementBuilder, IElementType, LookupElement>() { @NotNull @Override public LookupElement fun(LookupElementBuilder t, final IElementType v) { t = t.withInsertHandler(buildInsertHandler(v)); return t; } }, null); } }); extend(CompletionType.BASIC, CSharpPatterns.statementStart(), new CompletionProvider() { @RequiredReadAction @Override public void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { DotNetStatement statement = PsiTreeUtil.getParentOfType(parameters.getPosition(), DotNetStatement.class); if(statement == null) { return; } final PsiElement maybeTryStatement = UsefulPsiTreeUtil.getPrevSiblingSkipWhiteSpacesAndComments(statement, true); if(maybeTryStatement instanceof CSharpTryStatementImpl) { CSharpCompletionUtil.tokenSetToLookup(result, ourCatchFinallyKeywords, new NotNullPairFunction<LookupElementBuilder, IElementType, LookupElement>() { @NotNull @Override public LookupElementBuilder fun(LookupElementBuilder t, IElementType v) { t = t.withInsertHandler(buildInsertHandler(v)); return t; } }, new Condition<IElementType>() { @Override public boolean value(IElementType elementType) { CSharpTryStatementImpl st = (CSharpTryStatementImpl) maybeTryStatement; return st.getFinallyStatement() == null; } } ); } else if(maybeTryStatement instanceof CSharpIfStatementImpl) { CSharpCompletionUtil.elementToLookup(result, CSharpTokens.ELSE_KEYWORD, null, null); } } }); extend(CompletionType.BASIC, psiElement().afterLeaf(psiElement().withElementType(CSharpTokens.RPAR).withParent(CSharpCatchStatementImpl .class)), new CompletionProvider() { @RequiredReadAction @Override public void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { if(CSharpModuleUtil.findLanguageVersion(parameters.getPosition()).isAtLeast(CSharpLanguageVersion._6_0)) { CSharpCompletionUtil.elementToLookup(result, CSharpSoftTokens.WHEN_KEYWORD, new NotNullPairFunction<LookupElementBuilder, IElementType, LookupElement>() { @NotNull @Override public LookupElement fun(LookupElementBuilder t, IElementType v) { t = t.withInsertHandler(buildInsertHandler(v)); return t; } }, null); } } }); } @RequiredReadAction @Override public void fillCompletionVariants(CompletionParameters parameters, CompletionResultSet result) { super.fillCompletionVariants(parameters, CSharpCompletionSorting.modifyResultSet(parameters, result)); } @NotNull public static InsertHandler<LookupElement> buildInsertHandler(final IElementType elementType) { char open = '('; char close = ')'; if(elementType == DO_KEYWORD || elementType == TRY_KEYWORD || elementType == CATCH_KEYWORD || elementType == UNSAFE_KEYWORD || elementType == FINALLY_KEYWORD) { open = '{'; close = '}'; } return new ExpressionOrStatementInsertHandler<LookupElement>(open, close); } }