FIR Completion: Add function insert handler
- It is a specialized copy from `KotlinFunctionInsertHandler.Normal`
This commit is contained in:
+99
-1
@@ -5,16 +5,22 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.completion
|
package org.jetbrains.kotlin.idea.completion
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.AutoPopupController
|
||||||
import com.intellij.codeInsight.completion.InsertHandler
|
import com.intellij.codeInsight.completion.InsertHandler
|
||||||
import com.intellij.codeInsight.completion.InsertionContext
|
import com.intellij.codeInsight.completion.InsertionContext
|
||||||
|
import com.intellij.codeInsight.lookup.Lookup
|
||||||
import com.intellij.codeInsight.lookup.LookupElement
|
import com.intellij.codeInsight.lookup.LookupElement
|
||||||
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||||
import com.intellij.openapi.editor.Document
|
import com.intellij.openapi.editor.Document
|
||||||
import com.intellij.openapi.util.TextRange
|
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.symbols.*
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtDenotableType
|
import org.jetbrains.kotlin.idea.frontend.api.types.KtDenotableType
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
|
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.name.Name
|
||||||
|
import org.jetbrains.kotlin.psi.KtTypeArgumentList
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
import org.jetbrains.kotlin.renderer.render
|
import org.jetbrains.kotlin.renderer.render
|
||||||
|
|
||||||
internal class HighLevelApiLookupElementFactory {
|
internal class HighLevelApiLookupElementFactory {
|
||||||
@@ -68,7 +74,87 @@ private class FunctionLookupElementFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createInsertHandler(symbol: KtFunctionSymbol): InsertHandler<LookupElement> {
|
private fun createInsertHandler(symbol: KtFunctionSymbol): InsertHandler<LookupElement> {
|
||||||
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) =
|
private fun Document.isTextAt(offset: Int, text: String) =
|
||||||
offset + text.length <= textLength && getText(TextRange(offset, offset + text.length)) == text
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user