mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[kotlin] KTIJ-34546 - fixed template completion results being duplicated with chain completion
^KTIJ-34546 fixed Merge-request: IJ-MR-166132 Merged-by: Frederik Haselmeier <Frederik.Haselmeier@jetbrains.com> GitOrigin-RevId: 3744ee428cb1202cd37f37828bb5cd79b2d3f40b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7a1ba51253
commit
1bb53c25f2
+65
-6
@@ -3,7 +3,9 @@ package org.jetbrains.kotlin.idea.completion.impl.k2
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionResultSet
|
||||
import com.intellij.codeInsight.completion.CompletionType
|
||||
import com.intellij.openapi.util.registry.RegistryManager
|
||||
import com.intellij.psi.PsiErrorElement
|
||||
import org.jetbrains.kotlin.analysis.api.KaImplementationDetail
|
||||
import org.jetbrains.kotlin.analysis.api.KaSession
|
||||
import org.jetbrains.kotlin.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KaFirDiagnostic
|
||||
@@ -16,15 +18,18 @@ import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.completion.KotlinFirCompletionParameters
|
||||
import org.jetbrains.kotlin.idea.completion.findValueArgument
|
||||
import org.jetbrains.kotlin.idea.completion.impl.k2.contributors.*
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.factories.ClassifierLookupObject
|
||||
import org.jetbrains.kotlin.idea.completion.weighers.WeighingContext
|
||||
import org.jetbrains.kotlin.idea.completion.weighers.WeighingContext.Companion.getAnnotationLiteralExpectedType
|
||||
import org.jetbrains.kotlin.idea.completion.weighers.WeighingContext.Companion.getEqualityExpectedType
|
||||
import org.jetbrains.kotlin.idea.util.positionContext.*
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.renderer.render
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
internal object Completions {
|
||||
|
||||
fun complete(
|
||||
parameters: KotlinFirCompletionParameters,
|
||||
positionContext: KotlinRawPositionContext,
|
||||
@@ -60,11 +65,17 @@ internal object Completions {
|
||||
else -> WeighingContext.create(parameters, elementInCompletionFile = positionContext.position)
|
||||
}
|
||||
|
||||
complete(
|
||||
positionContext = positionContext,
|
||||
sink = LookupElementSink(resultSet, parameters),
|
||||
weighingContext = weighingContext,
|
||||
)
|
||||
val contributors = CopyOnWriteArrayList<ChainCompletionContributor>() // if needed for the multithreaded completion
|
||||
val sink = LookupElementSink(resultSet, parameters) {
|
||||
contributors.addIfAbsent(it)
|
||||
}
|
||||
complete(positionContext, sink, weighingContext)
|
||||
|
||||
if (positionContext is KotlinNameReferencePositionContext
|
||||
&& contributors.isNotEmpty()
|
||||
&& RegistryManager.getInstance().`is`("kotlin.k2.chain.completion.enabled")) {
|
||||
runChainCompletion(positionContext, sink, contributors)
|
||||
}
|
||||
} finally {
|
||||
after()
|
||||
}
|
||||
@@ -260,6 +271,54 @@ internal object Completions {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(KaImplementationDetail::class)
|
||||
private fun runChainCompletion(
|
||||
positionContext: KotlinNameReferencePositionContext,
|
||||
sink: LookupElementSink,
|
||||
contributors: List<ChainCompletionContributor>,
|
||||
) {
|
||||
val explicitReceiver = positionContext.explicitReceiver ?: return
|
||||
|
||||
sink.runRemainingContributors(sink.parameters.delegate) { completionResult ->
|
||||
val lookupElement = completionResult.lookupElement
|
||||
val classifierLookupObject = lookupElement.`object` as? ClassifierLookupObject
|
||||
val nameToImport = when (val importStrategy = classifierLookupObject?.importingStrategy) {
|
||||
is ImportStrategy.AddImport -> importStrategy.nameToImport
|
||||
is ImportStrategy.InsertFqNameAndShorten -> importStrategy.fqName
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (nameToImport == null) {
|
||||
sink.passResult(completionResult)
|
||||
return@runRemainingContributors
|
||||
}
|
||||
|
||||
val expression = KtPsiFactory.contextual(explicitReceiver)
|
||||
.createExpression(nameToImport.render() + "." + positionContext.nameExpression.text) as KtDotQualifiedExpression
|
||||
|
||||
val receiverExpression = expression.receiverExpression as? KtDotQualifiedExpression
|
||||
val nameExpression = expression.selectorExpression as? KtNameReferenceExpression
|
||||
|
||||
if (receiverExpression == null
|
||||
|| nameExpression == null
|
||||
) {
|
||||
sink.passResult(completionResult)
|
||||
return@runRemainingContributors
|
||||
}
|
||||
|
||||
analyze(nameExpression) {
|
||||
val positionContext = KotlinExpressionNameReferencePositionContext(nameExpression)
|
||||
val importingStrategy = ImportStrategy.AddImport(nameToImport)
|
||||
|
||||
val lookupElements = contributors.asSequence()
|
||||
.flatMap { contributor ->
|
||||
contributor.createChainedLookupElements(positionContext, receiverExpression, importingStrategy)
|
||||
}.asIterable()
|
||||
sink.addAllElements(lookupElements)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KotlinUnknownPositionContext.isAfterRangeToken(): Boolean {
|
||||
|
||||
+4
-2
@@ -11,6 +11,7 @@ import com.intellij.psi.util.elementType
|
||||
import org.jetbrains.kotlin.idea.base.codeInsight.contributorClass
|
||||
import org.jetbrains.kotlin.idea.base.psi.dropCurlyBracketsIfPossible
|
||||
import org.jetbrains.kotlin.idea.completion.KotlinFirCompletionParameters
|
||||
import org.jetbrains.kotlin.idea.completion.impl.k2.contributors.ChainCompletionContributor
|
||||
import org.jetbrains.kotlin.idea.completion.impl.k2.contributors.FirCompletionContributor
|
||||
import org.jetbrains.kotlin.idea.completion.implCommon.handlers.CompletionCharInsertHandler
|
||||
import org.jetbrains.kotlin.idea.completion.implCommon.stringTemplates.InsertStringTemplateBracesInsertHandler
|
||||
@@ -30,16 +31,17 @@ internal class LookupElementSink(
|
||||
internal val parameters: KotlinFirCompletionParameters,
|
||||
private val groupPriority: Int = 0,
|
||||
private val contributorClass: Class<FirCompletionContributor<*>>? = null,
|
||||
internal val registerChainContributor: (ChainCompletionContributor) -> Unit,
|
||||
) {
|
||||
|
||||
val prefixMatcher: PrefixMatcher
|
||||
get() = resultSet.prefixMatcher
|
||||
|
||||
fun withPriority(groupPriority: Int): LookupElementSink =
|
||||
LookupElementSink(resultSet, parameters, groupPriority, contributorClass)
|
||||
LookupElementSink(resultSet, parameters, groupPriority, contributorClass, registerChainContributor)
|
||||
|
||||
fun withContributorClass(contributorClass: Class<FirCompletionContributor<*>>): LookupElementSink =
|
||||
LookupElementSink(resultSet, parameters, groupPriority, contributorClass)
|
||||
LookupElementSink(resultSet, parameters, groupPriority, contributorClass, registerChainContributor)
|
||||
|
||||
fun passResult(result: CompletionResult) {
|
||||
resultSet.passResult(result)
|
||||
|
||||
+33
-25
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.kotlin.idea.completion.impl.k2.contributors
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.childrenOfType
|
||||
@@ -57,7 +58,8 @@ internal open class FirCallableCompletionContributor(
|
||||
sink: LookupElementSink,
|
||||
priority: Int = 0,
|
||||
private val withTrailingLambda: Boolean = false, // TODO find a better solution
|
||||
) : FirCompletionContributorBase<KotlinNameReferencePositionContext>(sink, priority) {
|
||||
) : FirCompletionContributorBase<KotlinNameReferencePositionContext>(sink, priority),
|
||||
ChainCompletionContributor {
|
||||
|
||||
context(KaSession)
|
||||
protected open fun getImportStrategy(signature: KaCallableSignature<*>, isImportDefinitelyNotRequired: Boolean): ImportStrategy =
|
||||
@@ -345,6 +347,34 @@ internal open class FirCallableCompletionContributor(
|
||||
yieldAll(extensionDescriptors)
|
||||
}
|
||||
|
||||
context(KaSession)
|
||||
override fun createChainedLookupElements(
|
||||
positionContext: KotlinNameReferencePositionContext,
|
||||
receiverExpression: KtDotQualifiedExpression,
|
||||
importingStrategy: ImportStrategy
|
||||
): Sequence<LookupElement> {
|
||||
val weighingContext = WeighingContext.create(parameters, positionContext)
|
||||
|
||||
return collectDotCompletion(
|
||||
positionContext = positionContext,
|
||||
scopeContext = weighingContext.scopeContext,
|
||||
explicitReceiver = receiverExpression,
|
||||
extensionChecker = null,
|
||||
showReceiver = true,
|
||||
).flatMap { callableWithMetadata ->
|
||||
val signature = callableWithMetadata.signature
|
||||
|
||||
createCallableLookupElements(
|
||||
context = weighingContext,
|
||||
signature = signature,
|
||||
options = callableWithMetadata.options.copy(importingStrategy = importingStrategy),
|
||||
scopeKind = callableWithMetadata.scopeKind,
|
||||
presentableText = callableWithMetadata.itemText,
|
||||
withTrailingLambda = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context(KaSession)
|
||||
protected open fun collectDotCompletion(
|
||||
positionContext: KotlinNameReferencePositionContext,
|
||||
@@ -374,30 +404,8 @@ internal open class FirCallableCompletionContributor(
|
||||
)
|
||||
}
|
||||
|
||||
if (showReceiver) return@sequence
|
||||
runChainCompletion(positionContext, explicitReceiver) { receiverExpression,
|
||||
positionContext,
|
||||
importingStrategy ->
|
||||
val weighingContext = WeighingContext.create(parameters, positionContext)
|
||||
|
||||
collectDotCompletion(
|
||||
positionContext = positionContext,
|
||||
scopeContext = weighingContext.scopeContext,
|
||||
explicitReceiver = receiverExpression,
|
||||
extensionChecker = null,
|
||||
showReceiver = true,
|
||||
).flatMap { callableWithMetadata ->
|
||||
val signature = callableWithMetadata.signature
|
||||
|
||||
createCallableLookupElements(
|
||||
context = weighingContext,
|
||||
signature = signature,
|
||||
options = callableWithMetadata.options.copy(importingStrategy),
|
||||
scopeKind = callableWithMetadata.scopeKind,
|
||||
presentableText = callableWithMetadata.itemText,
|
||||
withTrailingLambda = true,
|
||||
)
|
||||
}
|
||||
if (!showReceiver) {
|
||||
sink.registerChainContributor(this@FirCallableCompletionContributor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-25
@@ -32,15 +32,16 @@ import org.jetbrains.kotlin.idea.completion.weighers.WeighingContext
|
||||
import org.jetbrains.kotlin.idea.util.positionContext.KotlinNameReferencePositionContext
|
||||
import org.jetbrains.kotlin.idea.util.positionContext.KotlinRawPositionContext
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.renderer.render
|
||||
import org.jetbrains.kotlin.utils.yieldIfNotNull
|
||||
import kotlin.sequences.filter
|
||||
|
||||
internal open class FirClassifierCompletionContributor(
|
||||
sink: LookupElementSink,
|
||||
priority: Int = 0,
|
||||
) : FirCompletionContributorBase<KotlinNameReferencePositionContext>(sink, priority) {
|
||||
) : FirCompletionContributorBase<KotlinNameReferencePositionContext>(sink, priority),
|
||||
ChainCompletionContributor{
|
||||
|
||||
context(KaSession)
|
||||
protected open fun filterClassifiers(classifierSymbol: KaClassifierSymbol): Boolean = true
|
||||
@@ -77,29 +78,7 @@ internal open class FirClassifierCompletionContributor(
|
||||
).map { it.applyWeighs(weighingContext, symbolWithOrigin) }
|
||||
}.forEach(sink::addElement)
|
||||
} else {
|
||||
runChainCompletion(positionContext, explicitReceiver) { receiverExpression,
|
||||
positionContext,
|
||||
importingStrategy ->
|
||||
val selectorExpression = receiverExpression.selectorExpression
|
||||
?: return@runChainCompletion emptySequence()
|
||||
|
||||
val reference = receiverExpression.reference()
|
||||
?: return@runChainCompletion emptySequence()
|
||||
|
||||
val weighingContext = WeighingContext.create(parameters, positionContext)
|
||||
reference.resolveToSymbols()
|
||||
.asSequence()
|
||||
.mapNotNull { it.staticScope }
|
||||
.flatMap { it.completeClassifiers(positionContext) }
|
||||
.flatMap {
|
||||
createClassifierLookupElement(
|
||||
classifierSymbol = it,
|
||||
expectedType = weighingContext.expectedType,
|
||||
importingStrategy = importingStrategy,
|
||||
positionContext = positionContext,
|
||||
)
|
||||
}.map { it.withPresentableText(selectorExpression.text + "." + it.lookupString) }
|
||||
}
|
||||
sink.registerChainContributor(this@FirClassifierCompletionContributor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,6 +158,31 @@ internal open class FirClassifierCompletionContributor(
|
||||
indexClassifiers
|
||||
}
|
||||
|
||||
context(KaSession)
|
||||
override fun createChainedLookupElements(
|
||||
positionContext: KotlinNameReferencePositionContext,
|
||||
receiverExpression: KtDotQualifiedExpression,
|
||||
importingStrategy: ImportStrategy
|
||||
): Sequence<LookupElement> {
|
||||
val selectorExpression = receiverExpression.selectorExpression ?: return emptySequence()
|
||||
|
||||
val reference = receiverExpression.reference() ?: return emptySequence()
|
||||
|
||||
val weighingContext = WeighingContext.create(parameters, positionContext)
|
||||
return reference.resolveToSymbols()
|
||||
.asSequence()
|
||||
.mapNotNull { it.staticScope }
|
||||
.flatMap { it.completeClassifiers(positionContext) }
|
||||
.flatMap {
|
||||
createClassifierLookupElement(
|
||||
classifierSymbol = it,
|
||||
expectedType = weighingContext.expectedType,
|
||||
importingStrategy = importingStrategy,
|
||||
positionContext = positionContext,
|
||||
)
|
||||
}.map { it.withPresentableText(selectorExpression.text + "." + it.lookupString) }
|
||||
}
|
||||
|
||||
|
||||
context(KaSession)
|
||||
private fun createClassifierLookupElement(
|
||||
|
||||
+15
-54
@@ -8,9 +8,8 @@ import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||
import com.intellij.codeInsight.lookup.LookupElementPresentation
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.registry.RegistryManager
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.kotlin.analysis.api.KaSession
|
||||
import org.jetbrains.kotlin.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.analysis.api.components.KaScopeKind
|
||||
import org.jetbrains.kotlin.analysis.api.signatures.KaCallableSignature
|
||||
import org.jetbrains.kotlin.analysis.api.signatures.KaFunctionSignature
|
||||
@@ -32,19 +31,18 @@ import org.jetbrains.kotlin.idea.completion.impl.k2.ImportStrategyDetector
|
||||
import org.jetbrains.kotlin.idea.completion.impl.k2.LookupElementSink
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionOptions
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.factories.ClassifierLookupObject
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.factories.FunctionCallLookupObject
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.factories.FunctionLookupElementFactory
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.factories.KotlinFirLookupElementFactory
|
||||
import org.jetbrains.kotlin.idea.completion.weighers.CallableWeigher.callableWeight
|
||||
import org.jetbrains.kotlin.idea.completion.weighers.Weighers.applyWeighs
|
||||
import org.jetbrains.kotlin.idea.completion.weighers.WeighingContext
|
||||
import org.jetbrains.kotlin.idea.util.positionContext.KotlinExpressionNameReferencePositionContext
|
||||
import org.jetbrains.kotlin.idea.util.positionContext.KotlinNameReferencePositionContext
|
||||
import org.jetbrains.kotlin.idea.util.positionContext.KotlinRawPositionContext
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.renderer.render
|
||||
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
internal abstract class FirCompletionContributorBase<C : KotlinRawPositionContext>(
|
||||
@@ -180,54 +178,6 @@ internal abstract class FirCompletionContributorBase<C : KotlinRawPositionContex
|
||||
}
|
||||
}
|
||||
|
||||
protected fun runChainCompletion(
|
||||
positionContext: KotlinNameReferencePositionContext,
|
||||
explicitReceiver: KtElement,
|
||||
createLookupElements: KaSession.(
|
||||
receiverExpression: KtDotQualifiedExpression,
|
||||
positionContext: KotlinExpressionNameReferencePositionContext,
|
||||
importingStrategy: ImportStrategy.AddImport,
|
||||
) -> Sequence<LookupElement>,
|
||||
) {
|
||||
if (!RegistryManager.getInstance().`is`("kotlin.k2.chain.completion.enabled")) return
|
||||
|
||||
sink.runRemainingContributors(parameters.delegate) { completionResult ->
|
||||
val lookupElement = completionResult.lookupElement
|
||||
val classifierLookupObject = lookupElement.`object` as? ClassifierLookupObject
|
||||
val nameToImport = when (val importStrategy = classifierLookupObject?.importingStrategy) {
|
||||
is ImportStrategy.AddImport -> importStrategy.nameToImport
|
||||
is ImportStrategy.InsertFqNameAndShorten -> importStrategy.fqName
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (nameToImport == null) {
|
||||
sink.passResult(completionResult)
|
||||
return@runRemainingContributors
|
||||
}
|
||||
|
||||
val expression = KtPsiFactory.contextual(explicitReceiver)
|
||||
.createExpression(nameToImport.render() + "." + positionContext.nameExpression.text) as KtDotQualifiedExpression
|
||||
|
||||
val receiverExpression = expression.receiverExpression as? KtDotQualifiedExpression
|
||||
val nameExpression = expression.selectorExpression as? KtNameReferenceExpression
|
||||
|
||||
if (receiverExpression == null
|
||||
|| nameExpression == null
|
||||
) {
|
||||
sink.passResult(completionResult)
|
||||
return@runRemainingContributors
|
||||
}
|
||||
|
||||
analyze(nameExpression) {
|
||||
createLookupElements(
|
||||
/* receiverExpression = */ receiverExpression,
|
||||
/* positionContext = */ KotlinExpressionNameReferencePositionContext(nameExpression),
|
||||
/* importingStrategy = */ ImportStrategy.AddImport(nameToImport),
|
||||
).forEach(sink::addElement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo move out
|
||||
// todo move to the corresponding assignment
|
||||
protected fun LookupElementBuilder.adaptToExplicitReceiver(
|
||||
@@ -270,3 +220,14 @@ internal abstract class FirCompletionContributorBase<C : KotlinRawPositionContex
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental // todo reconsider
|
||||
internal interface ChainCompletionContributor : FirCompletionContributor<KotlinNameReferencePositionContext> {
|
||||
|
||||
context(KaSession)
|
||||
fun createChainedLookupElements(
|
||||
positionContext: KotlinNameReferencePositionContext,
|
||||
receiverExpression: KtDotQualifiedExpression,
|
||||
importingStrategy: ImportStrategy,
|
||||
): Sequence<LookupElement>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package foo
|
||||
|
||||
object Foo {
|
||||
val value: Int = 5
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
|
||||
fun test() {
|
||||
Foo.val<caret>
|
||||
}
|
||||
|
||||
// EXIST: .val
|
||||
// EXIST: value
|
||||
// NUMBER: 2
|
||||
// NOTHING_ELSE
|
||||
+5
@@ -50,6 +50,11 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ
|
||||
runTest("../testData/basic/multifile/CallablesInExcludedPackage/");
|
||||
}
|
||||
|
||||
@TestMetadata("ChainCompletionDontDuplicate")
|
||||
public void testChainCompletionDontDuplicate() throws Exception {
|
||||
runTest("../testData/basic/multifile/ChainCompletionDontDuplicate/");
|
||||
}
|
||||
|
||||
@TestMetadata("ClassInExcludedPackage")
|
||||
public void testClassInExcludedPackage() throws Exception {
|
||||
runTest("../testData/basic/multifile/ClassInExcludedPackage/");
|
||||
|
||||
+5
@@ -50,6 +50,11 @@ public class HighLevelMultiFileJvmBasicCompletionTestGenerated extends AbstractH
|
||||
runTest("../../completion/testData/basic/multifile/CallablesInExcludedPackage/");
|
||||
}
|
||||
|
||||
@TestMetadata("ChainCompletionDontDuplicate")
|
||||
public void testChainCompletionDontDuplicate() throws Exception {
|
||||
runTest("../../completion/testData/basic/multifile/ChainCompletionDontDuplicate/");
|
||||
}
|
||||
|
||||
@TestMetadata("ClassInExcludedPackage")
|
||||
public void testClassInExcludedPackage() throws Exception {
|
||||
runTest("../../completion/testData/basic/multifile/ClassInExcludedPackage/");
|
||||
|
||||
Reference in New Issue
Block a user