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.lookup.LookupElement
|
||||
import com.intellij.codeInsight.lookup.LookupElementDecorator
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.patterns.PlatformPatterns
|
||||
import com.intellij.patterns.PsiJavaPatterns.elementType
|
||||
import com.intellij.patterns.PsiJavaPatterns.psiElement
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.search.PsiElementProcessor
|
||||
import com.intellij.psi.tree.TokenSet
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.PsiComment
|
||||
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.SmartCompletionSession
|
||||
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.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
import kotlin.math.max
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
else -> service<CompletionDummyIdentifierProviderService>().provideDummyIdentifier(context)
|
||||
}
|
||||
|
||||
val tokenAt = psiFile.findElementAt(max(0, offset))
|
||||
@@ -166,76 +147,6 @@ class KotlinCompletionContributor : CompletionContributor() {
|
||||
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) {
|
||||
val position = parameters.position
|
||||
@@ -402,94 +313,6 @@ class KotlinCompletionContributor : CompletionContributor() {
|
||||
}
|
||||
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 {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun foo() {}
|
||||
|
||||
fun <T> S<caret>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
class Foo{}
|
||||
|
||||
fun foo() {}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
class Test {
|
||||
public class Nested
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
class Test {
|
||||
public class Nested
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
@<caret> annotation class Annotated
|
||||
|
||||
// EXIST: Target
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
class Xyz
|
||||
|
||||
fun X<caret>
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun Strange(){}
|
||||
|
||||
fun Annotations<S<caret>
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun Strange(){}
|
||||
|
||||
fun Map<() -> Unit, Str<caret>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
class Outer {
|
||||
class Nested
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
class Outer {
|
||||
class Nested
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
class Some
|
||||
|
||||
val S<caret>
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
import java.util.HashMap
|
||||
|
||||
fun foo() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun foo() {
|
||||
val v = listOf<<caret>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun <T> genericFoo(p: Int){}
|
||||
fun <T> genericFoo(c: Char){}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// FIR_COMPARISON
|
||||
val v = 1
|
||||
fun f() = 2
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun foo() {
|
||||
val v = HashMap<List<(s: String?) -> Unit>, Set<<caret>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun foo() {
|
||||
val v = HashMap<String, <caret>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
import java.util.HashMap
|
||||
|
||||
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
|
||||
|
||||
import com.intellij.codeInsight.completion.*
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.patterns.PlatformPatterns
|
||||
import com.intellij.patterns.PsiJavaPatterns
|
||||
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.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class KotlinFirCompletionContributor : CompletionContributor() {
|
||||
init {
|
||||
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>() {
|
||||
|
||||
+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 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"/>
|
||||
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupApplicationService" />
|
||||
<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">
|
||||
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.completion.FirCompletionDummyIdentifierProviderService"
|
||||
serviceInterface="org.jetbrains.kotlin.idea.completion.CompletionDummyIdentifierProviderService"/>
|
||||
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService"/>
|
||||
|
||||
Reference in New Issue
Block a user