From 1e1ce3067f7626a1bffb203bd7cfae3c3394198c Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Thu, 27 May 2021 00:52:11 +0200 Subject: [PATCH] FIR IDE: implement basic classifier name contributor --- .../kotlin/psi/KtClassLikeDeclaration.kt | 2 +- .../GenericExtensionFunTypeArgument.kt | 2 -- .../KotlinFirCompletionContributor.kt | 5 +++ .../context/FirPositionCompletionContext.kt | 31 +++++++++++++--- .../FirKeywordCompletionContributor.kt | 2 +- ...FileClassifierNameCompletionContributor.kt | 36 +++++++++++++++++++ 6 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSameAsFileClassifierNameCompletionContributor.kt diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/KtClassLikeDeclaration.kt b/compiler/psi/src/org/jetbrains/kotlin/psi/KtClassLikeDeclaration.kt index 4e8d5e0c2ce..9246ac16b4f 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/psi/KtClassLikeDeclaration.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/KtClassLikeDeclaration.kt @@ -7,7 +7,7 @@ package org.jetbrains.kotlin.psi import org.jetbrains.kotlin.name.ClassId -interface KtClassLikeDeclaration : KtDeclaration { +interface KtClassLikeDeclaration : KtNamedDeclaration { /** * Return [ClassId], if the class is not local (E.e, if a class can be accessed by that [ClassId] from another context) * diff --git a/idea/idea-completion/testData/basic/common/autoPopup/GenericExtensionFunTypeArgument.kt b/idea/idea-completion/testData/basic/common/autoPopup/GenericExtensionFunTypeArgument.kt index 392f3c37853..a1ab7c6bd7e 100644 --- a/idea/idea-completion/testData/basic/common/autoPopup/GenericExtensionFunTypeArgument.kt +++ b/idea/idea-completion/testData/basic/common/autoPopup/GenericExtensionFunTypeArgument.kt @@ -4,6 +4,4 @@ fun Strange(){} fun Annotations // INVOCATION_COUNT: 0 -// EXIST: String -// EXIST: Set // ABSENT: Strange diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt index 5550db4359c..9e5e29fad7f 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt @@ -70,6 +70,7 @@ private object KotlinFirCompletionProvider : CompletionProvider { @@ -112,6 +113,10 @@ private object KotlinFirCompletionProvider : CompletionProvider { + complete(classifierNameContributor, positionContext) + } + is FirIncorrectPositionContext -> { // do nothing, completion is not suposed to be called here } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirPositionCompletionContext.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirPositionCompletionContext.kt index f328b1ac4c7..4ce79020ea7 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirPositionCompletionContext.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirPositionCompletionContext.kt @@ -20,9 +20,15 @@ internal sealed class FirRawPositionCompletionContext { abstract val position: PsiElement } +internal class FirClassifierNamePositionContext( + override val position: PsiElement, + val classLikeDeclaration: KtClassLikeDeclaration, +) : FirRawPositionCompletionContext() + + internal class FirIncorrectPositionContext( override val position: PsiElement -): FirRawPositionCompletionContext() +) : FirRawPositionCompletionContext() internal class FirTypeConstraintNameInWhereClausePositionContext( override val position: PsiElement, @@ -89,10 +95,26 @@ internal class FirUnknownPositionContext( internal object FirPositionCompletionContextDetector { fun detect(basicContext: FirBasicCompletionContext): FirRawPositionCompletionContext { val position = basicContext.parameters.position + return detectForPositionWithReference(position) + ?: detectForPositionWithoutReference(position) + ?: FirUnknownPositionContext(position) + } + + private fun detectForPositionWithoutReference(position: PsiElement): FirRawPositionCompletionContext? { + val parent = position.parent + return when { + parent is KtClassLikeDeclaration && parent.nameIdentifier == position -> { + FirClassifierNamePositionContext(position, parent) + } + else -> null + } + } + + private fun detectForPositionWithReference(position: PsiElement): FirRawPositionCompletionContext? { val reference = (position.parent as? KtSimpleNameExpression)?.mainReference - ?: return FirUnknownPositionContext(position) + ?: return null val nameExpression = reference.expression.takeIf { it !is KtLabelReferenceExpression } - ?: return FirUnknownPositionContext(position) + ?: return null val explicitReceiver = nameExpression.getReceiverExpression() val parent = nameExpression.parent @@ -190,7 +212,8 @@ internal object FirPositionCompletionContextDetector { is FirImportDirectivePositionContext, is FirPackageDirectivePositionContext, is FirTypeConstraintNameInWhereClausePositionContext, - is FirIncorrectPositionContext -> { + is FirIncorrectPositionContext, + is FirClassifierNamePositionContext -> { analyse(basicContext.originalKtFile, action) } } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirKeywordCompletionContributor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirKeywordCompletionContributor.kt index d0924bb5285..410b2420a8d 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirKeywordCompletionContributor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirKeywordCompletionContributor.kt @@ -46,7 +46,7 @@ internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionC else -> reference.expression } } - is FirTypeConstraintNameInWhereClausePositionContext, is FirIncorrectPositionContext -> { + is FirTypeConstraintNameInWhereClausePositionContext, is FirIncorrectPositionContext, is FirClassifierNamePositionContext -> { error("keyword completion should not be called for ${positionContext::class.simpleName}") } is FirUnknownPositionContext -> null diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSameAsFileClassifierNameCompletionContributor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSameAsFileClassifierNameCompletionContributor.kt new file mode 100644 index 00000000000..75d5f5869ca --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSameAsFileClassifierNameCompletionContributor.kt @@ -0,0 +1,36 @@ +/* + * 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.contributors + +import com.intellij.codeInsight.lookup.LookupElementBuilder +import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext +import org.jetbrains.kotlin.idea.completion.context.FirClassifierNamePositionContext +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.renderer.render + +internal class FirSameAsFileClassifierNameCompletionContributor( + basicContext: FirBasicCompletionContext +) : FirCompletionContributorBase(basicContext) { + + override fun KtAnalysisSession.complete(positionContext: FirClassifierNamePositionContext) { + if (positionContext.classLikeDeclaration is KtClassOrObject) { + completeTopLevelClassName(positionContext.classLikeDeclaration) + } + } + + private fun completeTopLevelClassName(classOrObject: KtClassOrObject) { + if (!classOrObject.isTopLevel()) return + val name = originalKtFile.virtualFile.nameWithoutExtension + if (!isValidUpperCapitalizedClassName(name)) return + if (originalKtFile.declarations.any { it is KtClassOrObject && it.name == name }) return + result.addElement(LookupElementBuilder.create(name)) + } + + private fun isValidUpperCapitalizedClassName(name: String) = + Name.isValidIdentifier(name) && Name.identifier(name).render() == name && name[0].isUpperCase() +}