diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/completion/HighLevelApiLookupElementFactory.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/completion/HighLevelApiLookupElementFactory.kt index ff9850747fb..b0457d84719 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/completion/HighLevelApiLookupElementFactory.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/completion/HighLevelApiLookupElementFactory.kt @@ -5,16 +5,22 @@ package org.jetbrains.kotlin.idea.completion +import com.intellij.codeInsight.AutoPopupController import com.intellij.codeInsight.completion.InsertHandler import com.intellij.codeInsight.completion.InsertionContext +import com.intellij.codeInsight.lookup.Lookup import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.openapi.editor.Document import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.idea.frontend.api.symbols.* import org.jetbrains.kotlin.idea.frontend.api.types.KtDenotableType import org.jetbrains.kotlin.idea.frontend.api.types.KtType +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtTypeArgumentList +import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.renderer.render internal class HighLevelApiLookupElementFactory { @@ -68,7 +74,87 @@ private class FunctionLookupElementFactory { } private fun createInsertHandler(symbol: KtFunctionSymbol): InsertHandler { - return QuotedNamesAwareInsertionHandler(symbol.name) + return FunctionInsertionHandler(symbol.name, inputValueArguments = symbol.valueParameters.isNotEmpty()) + } +} + +/** + * A partial copy from `KotlinFunctionInsertHandler.Normal`. + */ +private class FunctionInsertionHandler( + name: Name, + private val inputValueArguments: Boolean +) : QuotedNamesAwareInsertionHandler(name) { + override fun handleInsert(context: InsertionContext, item: LookupElement) { + super.handleInsert(context, item) + + val startOffset = context.startOffset + val element = context.file.findElementAt(startOffset) ?: return + + addArguments(context, element) + } + + private fun addArguments(context: InsertionContext, offsetElement: PsiElement) { + val completionChar = context.completionChar + if (completionChar == '(') { //TODO: more correct behavior related to braces type + context.setAddCompletionChar(false) + } + + var offset = context.tailOffset + val document = context.document + val editor = context.editor + val project = context.project + val chars = document.charsSequence + + val isSmartEnterCompletion = completionChar == Lookup.COMPLETE_STATEMENT_SELECT_CHAR + val isReplaceCompletion = completionChar == Lookup.REPLACE_SELECT_CHAR + + val openingBracket = '(' + val closingBracket = ')' + + if (isReplaceCompletion) { + val offset1 = chars.skipSpaces(offset) + if (offset1 < chars.length) { + if (chars[offset1] == '<') { + val token = context.file.findElementAt(offset1)!! + if (token.node.elementType == KtTokens.LT) { + val parent = token.parent + /* if type argument list is on multiple lines this is more likely wrong parsing*/ + if (parent is KtTypeArgumentList && parent.getText().indexOf('\n') < 0) { + offset = parent.endOffset + } + } + } + } + } + + var openingBracketOffset = chars.indexOfSkippingSpace(openingBracket, offset) + var closeBracketOffset = openingBracketOffset?.let { chars.indexOfSkippingSpace(closingBracket, it + 1) } + + if (openingBracketOffset == null) { + if (isSmartEnterCompletion) { + document.insertString(offset, "(") + } else { + document.insertString(offset, "()") + } + context.commitDocument() + + openingBracketOffset = document.charsSequence.indexOfSkippingSpace(openingBracket, offset)!! + closeBracketOffset = document.charsSequence.indexOfSkippingSpace(closingBracket, openingBracketOffset + 1) + } + + if (shouldPlaceCaretInBrackets(completionChar) || closeBracketOffset == null) { + editor.caretModel.moveToOffset(openingBracketOffset + 1) + AutoPopupController.getInstance(project)?.autoPopupParameterInfo(editor, offsetElement) + } else { + editor.caretModel.moveToOffset(closeBracketOffset + 1) + } + } + + private fun shouldPlaceCaretInBrackets(completionChar: Char): Boolean { + if (completionChar == ',' || completionChar == '.' || completionChar == '=') return false + if (completionChar == '(') return true + return inputValueArguments } } @@ -97,3 +183,15 @@ private object ShortNamesRenderer { private fun Document.isTextAt(offset: Int, text: String) = offset + text.length <= textLength && getText(TextRange(offset, offset + text.length)) == text + +private fun CharSequence.skipSpaces(index: Int): Int = + (index until length).firstOrNull { val c = this[it]; c != ' ' && c != '\t' } ?: this.length + +private fun CharSequence.indexOfSkippingSpace(c: Char, startIndex: Int): Int? { + for (i in startIndex until this.length) { + val currentChar = this[i] + if (c == currentChar) return i + if (currentChar != ' ' && currentChar != '\t') return null + } + return null +}