FIR IDE: fix completion of type arguments without closing >

This commit is contained in:
Ilya Kirillov
2021-05-18 14:35:44 +02:00
committed by TeamCityServer
parent d510f0809a
commit 0739f6974f
25 changed files with 300 additions and 183 deletions
@@ -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 }
}
}
@@ -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,3 +1,4 @@
// FIR_COMPARISON
class Xyz
fun X<caret>
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun Strange(){}
fun Annotations<S<caret>
@@ -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,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() {
@@ -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
// }
// }
}
}
@@ -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>() {
@@ -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"
+2
View File
@@ -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"/>