FIR IDE: fix completion of type arguments without closing >
This commit is contained in:
committed by
TeamCityServer
parent
d510f0809a
commit
0739f6974f
+22
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.completion
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||||
|
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||||
|
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
|
||||||
|
class FE10CompletionDummyIdentifierProviderService: CompletionDummyIdentifierProviderService() {
|
||||||
|
override fun allTargetsAreFunctionsOrClasses(nameReferenceExpression: KtNameReferenceExpression): Boolean {
|
||||||
|
val bindingContext = nameReferenceExpression.getResolutionFacade().analyze(nameReferenceExpression, BodyResolveMode.PARTIAL)
|
||||||
|
val targets = nameReferenceExpression.getReferenceTargets(bindingContext)
|
||||||
|
return targets.isNotEmpty() && targets.all { it is FunctionDescriptor || it is ClassDescriptor && it.kind == ClassKind.CLASS }
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-182
@@ -7,22 +7,15 @@ package org.jetbrains.kotlin.idea.completion
|
|||||||
|
|
||||||
import com.intellij.codeInsight.completion.*
|
import com.intellij.codeInsight.completion.*
|
||||||
import com.intellij.codeInsight.lookup.LookupElement
|
import com.intellij.codeInsight.lookup.LookupElement
|
||||||
import com.intellij.codeInsight.lookup.LookupElementDecorator
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.editor.Document
|
import com.intellij.openapi.editor.Document
|
||||||
import com.intellij.openapi.extensions.ExtensionPointName
|
import com.intellij.openapi.extensions.ExtensionPointName
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.patterns.PlatformPatterns
|
import com.intellij.patterns.PlatformPatterns
|
||||||
import com.intellij.patterns.PsiJavaPatterns.elementType
|
import com.intellij.patterns.PsiJavaPatterns.elementType
|
||||||
import com.intellij.patterns.PsiJavaPatterns.psiElement
|
import com.intellij.patterns.PsiJavaPatterns.psiElement
|
||||||
import com.intellij.psi.*
|
import com.intellij.psi.PsiComment
|
||||||
import com.intellij.psi.search.PsiElementProcessor
|
|
||||||
import com.intellij.psi.tree.TokenSet
|
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
|
||||||
import com.intellij.util.ProcessingContext
|
import com.intellij.util.ProcessingContext
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
|
||||||
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
|
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
|
||||||
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletionSession
|
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletionSession
|
||||||
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector.completionStatsData
|
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector.completionStatsData
|
||||||
@@ -30,10 +23,8 @@ import org.jetbrains.kotlin.idea.statistics.CompletionTypeStats
|
|||||||
import org.jetbrains.kotlin.idea.statistics.FileTypeStats
|
import org.jetbrains.kotlin.idea.statistics.FileTypeStats
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
|
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
var KtFile.doNotComplete: Boolean? by UserDataProperty(Key.create("DO_NOT_COMPLETE"))
|
var KtFile.doNotComplete: Boolean? by UserDataProperty(Key.create("DO_NOT_COMPLETE"))
|
||||||
@@ -91,17 +82,7 @@ class KotlinCompletionContributor : CompletionContributor() {
|
|||||||
|
|
||||||
PackageDirectiveCompletion.ACTIVATION_PATTERN.accepts(tokenBefore) -> PackageDirectiveCompletion.DUMMY_IDENTIFIER
|
PackageDirectiveCompletion.ACTIVATION_PATTERN.accepts(tokenBefore) -> PackageDirectiveCompletion.DUMMY_IDENTIFIER
|
||||||
|
|
||||||
isInClassHeader(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER // do not add '$' to not interrupt class declaration parsing
|
else -> service<CompletionDummyIdentifierProviderService>().provideDummyIdentifier(context)
|
||||||
|
|
||||||
isInUnclosedSuperQualifier(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + ">"
|
|
||||||
|
|
||||||
isInSimpleStringTemplate(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED
|
|
||||||
|
|
||||||
else -> specialLambdaSignatureDummyIdentifier(tokenBefore)
|
|
||||||
?: specialExtensionReceiverDummyIdentifier(tokenBefore)
|
|
||||||
?: specialInTypeArgsDummyIdentifier(tokenBefore)
|
|
||||||
?: specialInArgumentListDummyIdentifier(tokenBefore)
|
|
||||||
?: DEFAULT_DUMMY_IDENTIFIER
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val tokenAt = psiFile.findElementAt(max(0, offset))
|
val tokenAt = psiFile.findElementAt(max(0, offset))
|
||||||
@@ -166,76 +147,6 @@ class KotlinCompletionContributor : CompletionContributor() {
|
|||||||
return expression.textRange!!.endOffset
|
return expression.textRange!!.endOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isInClassHeader(tokenBefore: PsiElement?): Boolean {
|
|
||||||
val classOrObject = tokenBefore?.parents?.firstIsInstanceOrNull<KtClassOrObject>() ?: return false
|
|
||||||
val name = classOrObject.nameIdentifier ?: return false
|
|
||||||
val headerEnd = classOrObject.body?.startOffset ?: classOrObject.endOffset
|
|
||||||
val offset = tokenBefore.startOffset
|
|
||||||
return name.endOffset <= offset && offset <= headerEnd
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun specialLambdaSignatureDummyIdentifier(tokenBefore: PsiElement?): String? {
|
|
||||||
var leaf = tokenBefore
|
|
||||||
while (leaf is PsiWhiteSpace || leaf is PsiComment) {
|
|
||||||
leaf = leaf.prevLeaf(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
val lambda = leaf?.parents?.firstOrNull { it is KtFunctionLiteral } ?: return null
|
|
||||||
|
|
||||||
val lambdaChild = leaf.parents.takeWhile { it != lambda }.lastOrNull()
|
|
||||||
|
|
||||||
return if (lambdaChild is KtParameterList)
|
|
||||||
CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED
|
|
||||||
else
|
|
||||||
null
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private val declarationKeywords = TokenSet.create(KtTokens.FUN_KEYWORD, KtTokens.VAL_KEYWORD, KtTokens.VAR_KEYWORD)
|
|
||||||
private val declarationTokens = TokenSet.orSet(
|
|
||||||
TokenSet.create(
|
|
||||||
KtTokens.IDENTIFIER, KtTokens.LT, KtTokens.GT,
|
|
||||||
KtTokens.COMMA, KtTokens.DOT, KtTokens.QUEST, KtTokens.COLON,
|
|
||||||
KtTokens.IN_KEYWORD, KtTokens.OUT_KEYWORD,
|
|
||||||
KtTokens.LPAR, KtTokens.RPAR, KtTokens.ARROW,
|
|
||||||
TokenType.ERROR_ELEMENT
|
|
||||||
),
|
|
||||||
KtTokens.WHITE_SPACE_OR_COMMENT_BIT_SET
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun specialExtensionReceiverDummyIdentifier(tokenBefore: PsiElement?): String? {
|
|
||||||
var token = tokenBefore ?: return null
|
|
||||||
var ltCount = 0
|
|
||||||
var gtCount = 0
|
|
||||||
val builder = StringBuilder()
|
|
||||||
while (true) {
|
|
||||||
val tokenType = token.node!!.elementType
|
|
||||||
if (tokenType in declarationKeywords) {
|
|
||||||
val balance = ltCount - gtCount
|
|
||||||
if (balance < 0) return null
|
|
||||||
builder.append(token.text!!.reversed())
|
|
||||||
builder.reverse()
|
|
||||||
|
|
||||||
var tail = "X" + ">".repeat(balance) + ".f"
|
|
||||||
if (tokenType == KtTokens.FUN_KEYWORD) {
|
|
||||||
tail += "()"
|
|
||||||
}
|
|
||||||
builder.append(tail)
|
|
||||||
|
|
||||||
val text = builder.toString()
|
|
||||||
val file = KtPsiFactory(tokenBefore.project).createFile(text)
|
|
||||||
val declaration = file.declarations.singleOrNull() ?: return null
|
|
||||||
if (declaration.textLength != text.length) return null
|
|
||||||
val containsErrorElement = !PsiTreeUtil.processElements(file, PsiElementProcessor<PsiElement> { it !is PsiErrorElement })
|
|
||||||
return if (containsErrorElement) null else "$tail$"
|
|
||||||
}
|
|
||||||
if (tokenType !in declarationTokens) return null
|
|
||||||
if (tokenType == KtTokens.LT) ltCount++
|
|
||||||
if (tokenType == KtTokens.GT) gtCount++
|
|
||||||
builder.append(token.text!!.reversed())
|
|
||||||
token = PsiTreeUtil.prevLeaf(token) ?: return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun performCompletion(parameters: CompletionParameters, result: CompletionResultSet) {
|
private fun performCompletion(parameters: CompletionParameters, result: CompletionResultSet) {
|
||||||
val position = parameters.position
|
val position = parameters.position
|
||||||
@@ -402,94 +313,6 @@ class KotlinCompletionContributor : CompletionContributor() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun specialInTypeArgsDummyIdentifier(tokenBefore: PsiElement?): String? {
|
|
||||||
if (tokenBefore == null) return null
|
|
||||||
|
|
||||||
if (tokenBefore.getParentOfType<KtTypeArgumentList>(true) != null) { // already parsed inside type argument list
|
|
||||||
return CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED // do not insert '$' to not break type argument list parsing
|
|
||||||
}
|
|
||||||
|
|
||||||
val pair = unclosedTypeArgListNameAndBalance(tokenBefore) ?: return null
|
|
||||||
val (nameToken, balance) = pair
|
|
||||||
assert(balance > 0)
|
|
||||||
|
|
||||||
val nameRef = nameToken.parent as? KtNameReferenceExpression ?: return null
|
|
||||||
val bindingContext = nameRef.getResolutionFacade().analyze(nameRef, BodyResolveMode.PARTIAL)
|
|
||||||
val targets = nameRef.getReferenceTargets(bindingContext)
|
|
||||||
return if (targets.isNotEmpty() && targets.all { it is FunctionDescriptor || it is ClassDescriptor && it.kind == ClassKind.CLASS }) {
|
|
||||||
CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + ">".repeat(balance) + "$"
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unclosedTypeArgListNameAndBalance(tokenBefore: PsiElement): Pair<PsiElement, Int>? {
|
|
||||||
val nameToken = findCallNameTokenIfInTypeArgs(tokenBefore) ?: return null
|
|
||||||
val pair = unclosedTypeArgListNameAndBalance(nameToken)
|
|
||||||
return if (pair == null) {
|
|
||||||
Pair(nameToken, 1)
|
|
||||||
} else {
|
|
||||||
Pair(pair.first, pair.second + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val callTypeArgsTokens = TokenSet.orSet(
|
|
||||||
TokenSet.create(
|
|
||||||
KtTokens.IDENTIFIER, KtTokens.LT, KtTokens.GT,
|
|
||||||
KtTokens.COMMA, KtTokens.DOT, KtTokens.QUEST, KtTokens.COLON,
|
|
||||||
KtTokens.LPAR, KtTokens.RPAR, KtTokens.ARROW
|
|
||||||
),
|
|
||||||
KtTokens.WHITE_SPACE_OR_COMMENT_BIT_SET
|
|
||||||
)
|
|
||||||
|
|
||||||
// if the leaf could be located inside type argument list of a call (if parsed properly)
|
|
||||||
// then it returns the call name reference this type argument list would belong to
|
|
||||||
private fun findCallNameTokenIfInTypeArgs(leaf: PsiElement): PsiElement? {
|
|
||||||
var current = leaf
|
|
||||||
while (true) {
|
|
||||||
val tokenType = current.node!!.elementType
|
|
||||||
if (tokenType !in callTypeArgsTokens) return null
|
|
||||||
|
|
||||||
if (tokenType == KtTokens.LT) {
|
|
||||||
val nameToken = current.prevLeaf(skipEmptyElements = true) ?: return null
|
|
||||||
if (nameToken.node!!.elementType != KtTokens.IDENTIFIER) return null
|
|
||||||
return nameToken
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokenType == KtTokens.GT) { // pass nested type argument list
|
|
||||||
val prev = current.prevLeaf(skipEmptyElements = true) ?: return null
|
|
||||||
val typeRef = findCallNameTokenIfInTypeArgs(prev) ?: return null
|
|
||||||
current = typeRef
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.prevLeaf(skipEmptyElements = true) ?: return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun specialInArgumentListDummyIdentifier(tokenBefore: PsiElement?): String? {
|
|
||||||
// If we insert $ in the argument list of a delegation specifier, this will break parsing
|
|
||||||
// and the following block will not be attached as a body to the constructor. Therefore
|
|
||||||
// we need to use a regular identifier.
|
|
||||||
val argumentList = tokenBefore?.getNonStrictParentOfType<KtValueArgumentList>() ?: return null
|
|
||||||
if (argumentList.parent is KtConstructorDelegationCall) return CompletionUtil.DUMMY_IDENTIFIER_TRIMMED
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isInUnclosedSuperQualifier(tokenBefore: PsiElement?): Boolean {
|
|
||||||
if (tokenBefore == null) return false
|
|
||||||
val tokensToSkip = TokenSet.orSet(TokenSet.create(KtTokens.IDENTIFIER, KtTokens.DOT), KtTokens.WHITE_SPACE_OR_COMMENT_BIT_SET)
|
|
||||||
val tokens = generateSequence(tokenBefore) { it.prevLeaf() }
|
|
||||||
val ltToken = tokens.firstOrNull { it.node.elementType !in tokensToSkip } ?: return false
|
|
||||||
if (ltToken.node.elementType != KtTokens.LT) return false
|
|
||||||
val superToken = ltToken.prevLeaf { it !is PsiWhiteSpace && it !is PsiComment }
|
|
||||||
return superToken?.node?.elementType == KtTokens.SUPER_KEYWORD
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isInSimpleStringTemplate(tokenBefore: PsiElement?): Boolean {
|
|
||||||
return tokenBefore?.parents?.firstIsInstanceOrNull<KtStringTemplateExpression>()?.isPlain() ?: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class KotlinCompletionExtension {
|
abstract class KotlinCompletionExtension {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
fun foo() {}
|
fun foo() {}
|
||||||
|
|
||||||
fun <T> S<caret>
|
fun <T> S<caret>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
class Foo{}
|
class Foo{}
|
||||||
|
|
||||||
fun foo() {}
|
fun foo() {}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
class Test {
|
class Test {
|
||||||
public class Nested
|
public class Nested
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
class Test {
|
class Test {
|
||||||
public class Nested
|
public class Nested
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
@<caret> annotation class Annotated
|
@<caret> annotation class Annotated
|
||||||
|
|
||||||
// EXIST: Target
|
// EXIST: Target
|
||||||
|
|||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
class Xyz
|
class Xyz
|
||||||
|
|
||||||
fun X<caret>
|
fun X<caret>
|
||||||
|
|||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
fun Strange(){}
|
fun Strange(){}
|
||||||
|
|
||||||
fun Annotations<S<caret>
|
fun Annotations<S<caret>
|
||||||
|
|||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
fun Strange(){}
|
fun Strange(){}
|
||||||
|
|
||||||
fun Map<() -> Unit, Str<caret>
|
fun Map<() -> Unit, Str<caret>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
class Outer {
|
class Outer {
|
||||||
class Nested
|
class Nested
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
class Outer {
|
class Outer {
|
||||||
class Nested
|
class Nested
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
class Some
|
class Some
|
||||||
|
|
||||||
val S<caret>
|
val S<caret>
|
||||||
|
|||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
fun foo() {
|
fun foo() {
|
||||||
val v = listOf<<caret>
|
val v = listOf<<caret>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
fun <T> genericFoo(p: Int){}
|
fun <T> genericFoo(p: Int){}
|
||||||
fun <T> genericFoo(c: Char){}
|
fun <T> genericFoo(c: Char){}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// FIR_COMPARISON
|
|
||||||
val v = 1
|
val v = 1
|
||||||
fun f() = 2
|
fun f() = 2
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
fun foo() {
|
fun foo() {
|
||||||
val v = HashMap<List<(s: String?) -> Unit>, Set<<caret>
|
val v = HashMap<List<(s: String?) -> Unit>, Set<<caret>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
fun foo() {
|
fun foo() {
|
||||||
val v = HashMap<String, <caret>
|
val v = HashMap<String, <caret>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_COMPARISON
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.completion
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.analyse
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassKind
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
|
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||||
|
|
||||||
|
class FirCompletionDummyIdentifierProviderService : CompletionDummyIdentifierProviderService() {
|
||||||
|
override fun allTargetsAreFunctionsOrClasses(nameReferenceExpression: KtNameReferenceExpression): Boolean {
|
||||||
|
return true
|
||||||
|
// TODO fir cannot handle invalid code and handles listOf< as binary expression
|
||||||
|
// return analyse(nameReferenceExpression) {
|
||||||
|
// val reference = nameReferenceExpression.mainReference
|
||||||
|
// val targets = reference.resolveToSymbols()
|
||||||
|
// targets.isNotEmpty() && targets.all { target ->
|
||||||
|
// target is KtFunctionSymbol || target is KtClassOrObjectSymbol && target.classKind == KtClassKind.CLASS
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.idea.completion
|
package org.jetbrains.kotlin.idea.completion
|
||||||
|
|
||||||
import com.intellij.codeInsight.completion.*
|
import com.intellij.codeInsight.completion.*
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.patterns.PlatformPatterns
|
import com.intellij.patterns.PlatformPatterns
|
||||||
import com.intellij.patterns.PsiJavaPatterns
|
import com.intellij.patterns.PsiJavaPatterns
|
||||||
import com.intellij.util.ProcessingContext
|
import com.intellij.util.ProcessingContext
|
||||||
@@ -28,11 +29,19 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.IndexHelper
|
|||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.util.originalKtFile
|
import org.jetbrains.kotlin.idea.fir.low.level.api.util.originalKtFile
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
class KotlinFirCompletionContributor : CompletionContributor() {
|
class KotlinFirCompletionContributor : CompletionContributor() {
|
||||||
init {
|
init {
|
||||||
extend(CompletionType.BASIC, PlatformPatterns.psiElement(), KotlinFirCompletionProvider)
|
extend(CompletionType.BASIC, PlatformPatterns.psiElement(), KotlinFirCompletionProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun beforeCompletion(context: CompletionInitializationContext) {
|
||||||
|
val psiFile = context.file
|
||||||
|
if (psiFile !is KtFile) return
|
||||||
|
|
||||||
|
context.dummyIdentifier = service<CompletionDummyIdentifierProviderService>().provideDummyIdentifier(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private object KotlinFirCompletionProvider : CompletionProvider<CompletionParameters>() {
|
private object KotlinFirCompletionProvider : CompletionProvider<CompletionParameters>() {
|
||||||
|
|||||||
+215
@@ -0,0 +1,215 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.completion
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.completion.CompletionInitializationContext
|
||||||
|
import com.intellij.codeInsight.completion.CompletionType
|
||||||
|
import com.intellij.codeInsight.completion.CompletionUtil
|
||||||
|
import com.intellij.codeInsight.completion.CompletionUtilCore
|
||||||
|
import com.intellij.psi.*
|
||||||
|
import com.intellij.psi.tree.TokenSet
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
abstract class CompletionDummyIdentifierProviderService {
|
||||||
|
fun provideDummyIdentifier(context: CompletionInitializationContext): String {
|
||||||
|
val psiFile = context.file
|
||||||
|
if (psiFile !is KtFile) {
|
||||||
|
error("CompletionDummyIdentifierProviderService.providerDummyIdentifier should not be called for non KtFile")
|
||||||
|
}
|
||||||
|
|
||||||
|
val offset = context.startOffset
|
||||||
|
val tokenBefore = psiFile.findElementAt(max(0, offset - 1))
|
||||||
|
|
||||||
|
return when {
|
||||||
|
context.completionType == CompletionType.SMART -> DEFAULT_DUMMY_IDENTIFIER
|
||||||
|
|
||||||
|
// TODO package completion
|
||||||
|
|
||||||
|
isInClassHeader(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER // do not add '$' to not interrupt class declaration parsing
|
||||||
|
|
||||||
|
isInUnclosedSuperQualifier(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + ">"
|
||||||
|
|
||||||
|
isInSimpleStringTemplate(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED
|
||||||
|
|
||||||
|
else -> specialLambdaSignatureDummyIdentifier(tokenBefore)
|
||||||
|
?: specialExtensionReceiverDummyIdentifier(tokenBefore)
|
||||||
|
?: specialInTypeArgsDummyIdentifier(tokenBefore)
|
||||||
|
?: specialInArgumentListDummyIdentifier(tokenBefore)
|
||||||
|
?: DEFAULT_DUMMY_IDENTIFIER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun specialLambdaSignatureDummyIdentifier(tokenBefore: PsiElement?): String? {
|
||||||
|
var leaf = tokenBefore
|
||||||
|
while (leaf is PsiWhiteSpace || leaf is PsiComment) {
|
||||||
|
leaf = leaf.prevLeaf(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val lambda = leaf?.parents?.firstOrNull { it is KtFunctionLiteral } ?: return null
|
||||||
|
|
||||||
|
val lambdaChild = leaf.parents.takeWhile { it != lambda }.lastOrNull()
|
||||||
|
|
||||||
|
return if (lambdaChild is KtParameterList)
|
||||||
|
CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED
|
||||||
|
else
|
||||||
|
null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isInClassHeader(tokenBefore: PsiElement?): Boolean {
|
||||||
|
val classOrObject = tokenBefore?.parents?.firstIsInstanceOrNull<KtClassOrObject>() ?: return false
|
||||||
|
val name = classOrObject.nameIdentifier ?: return false
|
||||||
|
val headerEnd = classOrObject.body?.startOffset ?: classOrObject.endOffset
|
||||||
|
val offset = tokenBefore.startOffset
|
||||||
|
return name.endOffset <= offset && offset <= headerEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isInUnclosedSuperQualifier(tokenBefore: PsiElement?): Boolean {
|
||||||
|
if (tokenBefore == null) return false
|
||||||
|
val tokensToSkip = TokenSet.orSet(TokenSet.create(KtTokens.IDENTIFIER, KtTokens.DOT), KtTokens.WHITE_SPACE_OR_COMMENT_BIT_SET)
|
||||||
|
val tokens = generateSequence(tokenBefore) { it.prevLeaf() }
|
||||||
|
val ltToken = tokens.firstOrNull { it.node.elementType !in tokensToSkip } ?: return false
|
||||||
|
if (ltToken.node.elementType != KtTokens.LT) return false
|
||||||
|
val superToken = ltToken.prevLeaf { it !is PsiWhiteSpace && it !is PsiComment }
|
||||||
|
return superToken?.node?.elementType == KtTokens.SUPER_KEYWORD
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isInSimpleStringTemplate(tokenBefore: PsiElement?): Boolean {
|
||||||
|
return tokenBefore?.parents?.firstIsInstanceOrNull<KtStringTemplateExpression>()?.isPlain() ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun specialExtensionReceiverDummyIdentifier(tokenBefore: PsiElement?): String? {
|
||||||
|
var token = tokenBefore ?: return null
|
||||||
|
var ltCount = 0
|
||||||
|
var gtCount = 0
|
||||||
|
val builder = StringBuilder()
|
||||||
|
while (true) {
|
||||||
|
val tokenType = token.node!!.elementType
|
||||||
|
if (tokenType in declarationKeywords) {
|
||||||
|
val balance = ltCount - gtCount
|
||||||
|
if (balance < 0) return null
|
||||||
|
builder.append(token.text!!.reversed())
|
||||||
|
builder.reverse()
|
||||||
|
|
||||||
|
var tail = "X" + ">".repeat(balance) + ".f"
|
||||||
|
if (tokenType == KtTokens.FUN_KEYWORD) {
|
||||||
|
tail += "()"
|
||||||
|
}
|
||||||
|
builder.append(tail)
|
||||||
|
|
||||||
|
val text = builder.toString()
|
||||||
|
val file = KtPsiFactory(tokenBefore.project).createFile(text)
|
||||||
|
val declaration = file.declarations.singleOrNull() ?: return null
|
||||||
|
if (declaration.textLength != text.length) return null
|
||||||
|
val containsErrorElement = !PsiTreeUtil.processElements(file) { it !is PsiErrorElement }
|
||||||
|
return if (containsErrorElement) null else "$tail$"
|
||||||
|
}
|
||||||
|
if (tokenType !in declarationTokens) return null
|
||||||
|
if (tokenType == KtTokens.LT) ltCount++
|
||||||
|
if (tokenType == KtTokens.GT) gtCount++
|
||||||
|
builder.append(token.text!!.reversed())
|
||||||
|
token = PsiTreeUtil.prevLeaf(token) ?: return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun specialInTypeArgsDummyIdentifier(tokenBefore: PsiElement?): String? {
|
||||||
|
if (tokenBefore == null) return null
|
||||||
|
|
||||||
|
if (tokenBefore.getParentOfType<KtTypeArgumentList>(true) != null) { // already parsed inside type argument list
|
||||||
|
return CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED // do not insert '$' to not break type argument list parsing
|
||||||
|
}
|
||||||
|
|
||||||
|
val pair = unclosedTypeArgListNameAndBalance(tokenBefore) ?: return null
|
||||||
|
val (nameToken, balance) = pair
|
||||||
|
assert(balance > 0)
|
||||||
|
|
||||||
|
val nameRef = nameToken.parent as? KtNameReferenceExpression ?: return null
|
||||||
|
return if (allTargetsAreFunctionsOrClasses(nameRef)) {
|
||||||
|
CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + ">".repeat(balance) + "$"
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun allTargetsAreFunctionsOrClasses(nameReferenceExpression: KtNameReferenceExpression): Boolean
|
||||||
|
|
||||||
|
private fun unclosedTypeArgListNameAndBalance(tokenBefore: PsiElement): Pair<PsiElement, Int>? {
|
||||||
|
val nameToken = findCallNameTokenIfInTypeArgs(tokenBefore) ?: return null
|
||||||
|
val pair = unclosedTypeArgListNameAndBalance(nameToken)
|
||||||
|
return if (pair == null) {
|
||||||
|
Pair(nameToken, 1)
|
||||||
|
} else {
|
||||||
|
Pair(pair.first, pair.second + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val callTypeArgsTokens = TokenSet.orSet(
|
||||||
|
TokenSet.create(
|
||||||
|
KtTokens.IDENTIFIER, KtTokens.LT, KtTokens.GT,
|
||||||
|
KtTokens.COMMA, KtTokens.DOT, KtTokens.QUEST, KtTokens.COLON,
|
||||||
|
KtTokens.LPAR, KtTokens.RPAR, KtTokens.ARROW
|
||||||
|
),
|
||||||
|
KtTokens.WHITE_SPACE_OR_COMMENT_BIT_SET
|
||||||
|
)
|
||||||
|
|
||||||
|
// if the leaf could be located inside type argument list of a call (if parsed properly)
|
||||||
|
// then it returns the call name reference this type argument list would belong to
|
||||||
|
private fun findCallNameTokenIfInTypeArgs(leaf: PsiElement): PsiElement? {
|
||||||
|
var current = leaf
|
||||||
|
while (true) {
|
||||||
|
val tokenType = current.node!!.elementType
|
||||||
|
if (tokenType !in callTypeArgsTokens) return null
|
||||||
|
|
||||||
|
if (tokenType == KtTokens.LT) {
|
||||||
|
val nameToken = current.prevLeaf(skipEmptyElements = true) ?: return null
|
||||||
|
if (nameToken.node!!.elementType != KtTokens.IDENTIFIER) return null
|
||||||
|
return nameToken
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenType == KtTokens.GT) { // pass nested type argument list
|
||||||
|
val prev = current.prevLeaf(skipEmptyElements = true) ?: return null
|
||||||
|
val typeRef = findCallNameTokenIfInTypeArgs(prev) ?: return null
|
||||||
|
current = typeRef
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.prevLeaf(skipEmptyElements = true) ?: return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun specialInArgumentListDummyIdentifier(tokenBefore: PsiElement?): String? {
|
||||||
|
// If we insert $ in the argument list of a delegation specifier, this will break parsing
|
||||||
|
// and the following block will not be attached as a body to the constructor. Therefore
|
||||||
|
// we need to use a regular identifier.
|
||||||
|
val argumentList = tokenBefore?.getNonStrictParentOfType<KtValueArgumentList>() ?: return null
|
||||||
|
if (argumentList.parent is KtConstructorDelegationCall) return CompletionUtil.DUMMY_IDENTIFIER_TRIMMED
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DEFAULT_DUMMY_IDENTIFIER: String =
|
||||||
|
CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + "$" // add '$' to ignore context after the caret
|
||||||
|
|
||||||
|
private val declarationKeywords = TokenSet.create(KtTokens.FUN_KEYWORD, KtTokens.VAL_KEYWORD, KtTokens.VAR_KEYWORD)
|
||||||
|
private val declarationTokens = TokenSet.orSet(
|
||||||
|
TokenSet.create(
|
||||||
|
KtTokens.IDENTIFIER, KtTokens.LT, KtTokens.GT,
|
||||||
|
KtTokens.COMMA, KtTokens.DOT, KtTokens.QUEST, KtTokens.COLON,
|
||||||
|
KtTokens.IN_KEYWORD, KtTokens.OUT_KEYWORD,
|
||||||
|
KtTokens.LPAR, KtTokens.RPAR, KtTokens.ARROW,
|
||||||
|
TokenType.ERROR_ELEMENT
|
||||||
|
),
|
||||||
|
KtTokens.WHITE_SPACE_OR_COMMENT_BIT_SET
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -156,6 +156,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
<extensions defaultExtensionNs="com.intellij">
|
||||||
|
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.completion.FE10CompletionDummyIdentifierProviderService"
|
||||||
|
serviceInterface="org.jetbrains.kotlin.idea.completion.CompletionDummyIdentifierProviderService"/>
|
||||||
|
|
||||||
<pathMacroContributor implementation="org.jetbrains.kotlin.idea.KotlinPluginMacros"/>
|
<pathMacroContributor implementation="org.jetbrains.kotlin.idea.KotlinPluginMacros"/>
|
||||||
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupApplicationService" />
|
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupApplicationService" />
|
||||||
<applicationService serviceInterface="org.jetbrains.kotlin.idea.completion.handlers.SmartCompletionTailOffsetProvider"
|
<applicationService serviceInterface="org.jetbrains.kotlin.idea.completion.handlers.SmartCompletionTailOffsetProvider"
|
||||||
|
|||||||
@@ -218,6 +218,8 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu
|
|||||||
|
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
<extensions defaultExtensionNs="com.intellij">
|
||||||
|
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.completion.FirCompletionDummyIdentifierProviderService"
|
||||||
|
serviceInterface="org.jetbrains.kotlin.idea.completion.CompletionDummyIdentifierProviderService"/>
|
||||||
|
|
||||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
|
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
|
||||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService"/>
|
<projectService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user