FIR IDE: implement completion for callable references
This commit is contained in:
committed by
teamcityserver
parent
a42e9c236a
commit
2ffc7ad0a0
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
import java.io.File
|
||||
|
||||
val v = File::<caret>
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
val v = "a"::<caret>
|
||||
|
||||
// EXIST: extFun
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
package ppp
|
||||
|
||||
class X(p: Int)
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
package ppp
|
||||
|
||||
class X(p: Int)
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun globalFun(p: Int) {}
|
||||
|
||||
class C {
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun globalFun(p: Int) {}
|
||||
|
||||
class C {
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
class C {
|
||||
fun foo() {
|
||||
val v = D::<caret>
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
class C {
|
||||
fun foo() {
|
||||
val v = D::memberFun<caret>
|
||||
|
||||
+7
@@ -79,7 +79,9 @@ private object KotlinFirCompletionProvider : CompletionProvider<CompletionParame
|
||||
) {
|
||||
val keywordContributor = FirKeywordCompletionContributor(basicContext)
|
||||
val callableContributor = FirCallableCompletionContributor(basicContext)
|
||||
val callableReferenceContributor = FirCallableReferenceCompletionContributor(basicContext)
|
||||
val classifierContributor = FirClassifierCompletionContributor(basicContext)
|
||||
val classifierReferenceContributor = FirClassifierReferenceCompletionContributor(basicContext)
|
||||
val annotationsContributor = FirAnnotationCompletionContributor(basicContext)
|
||||
val packageCompletionContributor = FirPackageCompletionContributor(basicContext)
|
||||
val importDirectivePackageMembersCompletionContributor = FirImportDirectivePackageMembersCompletionContributor(basicContext)
|
||||
@@ -137,6 +139,11 @@ private object KotlinFirCompletionProvider : CompletionProvider<CompletionParame
|
||||
complete(whenWithSubjecConditionContributor, positionContext)
|
||||
}
|
||||
|
||||
is FirCallableReferencePositionContext -> {
|
||||
complete(callableReferenceContributor, positionContext)
|
||||
complete(classifierReferenceContributor, positionContext)
|
||||
}
|
||||
|
||||
is FirIncorrectPositionContext -> {
|
||||
// do nothing, completion is not suposed to be called here
|
||||
}
|
||||
|
||||
+12
@@ -96,6 +96,13 @@ internal class FirWithSubjectEntryPositionContext(
|
||||
val whenCondition: KtWhenCondition,
|
||||
) : FirNameReferencePositionContext()
|
||||
|
||||
internal class FirCallableReferencePositionContext(
|
||||
override val position: PsiElement,
|
||||
override val reference: KtSimpleNameReference,
|
||||
override val nameExpression: KtSimpleNameExpression,
|
||||
override val explicitReceiver: KtExpression?
|
||||
) : FirNameReferencePositionContext()
|
||||
|
||||
internal class FirUnknownPositionContext(
|
||||
override val position: PsiElement
|
||||
) : FirRawPositionCompletionContext()
|
||||
@@ -130,6 +137,11 @@ internal object FirPositionCompletionContextDetector {
|
||||
parent is KtUserType -> {
|
||||
detectForTypeContext(parent, position, reference, nameExpression, explicitReceiver)
|
||||
}
|
||||
parent is KtCallableReferenceExpression -> {
|
||||
FirCallableReferencePositionContext(
|
||||
position, reference, nameExpression, parent.receiverExpression
|
||||
)
|
||||
}
|
||||
parent is KtWhenCondition && parent.isConditionOnWhenWithSubject() -> {
|
||||
FirWithSubjectEntryPositionContext(
|
||||
position,
|
||||
|
||||
+46
-11
@@ -9,22 +9,27 @@ import org.jetbrains.kotlin.idea.completion.checkers.CompletionVisibilityChecker
|
||||
import org.jetbrains.kotlin.idea.completion.checkers.ExtensionApplicabilityChecker
|
||||
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
|
||||
import org.jetbrains.kotlin.idea.completion.context.FirNameReferencePositionContext
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
|
||||
import org.jetbrains.kotlin.idea.fir.HLIndexHelper
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.frontend.api.components.KtScopeContext
|
||||
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtCompositeScope
|
||||
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtScope
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithVisibility
|
||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtClassType
|
||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtNonErrorClassType
|
||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
|
||||
internal class FirCallableCompletionContributor(
|
||||
internal open class FirCallableCompletionContributor(
|
||||
basicContext: FirBasicCompletionContext,
|
||||
) : FirContextCompletionContributorBase<FirNameReferencePositionContext>(basicContext) {
|
||||
private val typeNamesProvider = TypeNamesProvider(indexHelper)
|
||||
|
||||
protected open val insertionStrategy: CallableInsertionStrategy = CallableInsertionStrategy.AS_CALL
|
||||
|
||||
private val shouldCompleteTopLevelCallablesFromIndex: Boolean
|
||||
get() = prefixMatcher.prefix.isNotEmpty()
|
||||
|
||||
@@ -67,22 +72,22 @@ internal class FirCallableCompletionContributor(
|
||||
val availableNonExtensions = collectNonExtensions(implicitScopes, visibilityChecker)
|
||||
val extensionsWhichCanBeCalled = collectSuitableExtensions(implicitScopes, extensionChecker, visibilityChecker)
|
||||
|
||||
availableNonExtensions.forEach { addSymbolToCompletion(expectedType, it) }
|
||||
extensionsWhichCanBeCalled.forEach { addSymbolToCompletion(expectedType, it) }
|
||||
availableNonExtensions.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
|
||||
extensionsWhichCanBeCalled.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
|
||||
|
||||
if (shouldCompleteTopLevelCallablesFromIndex) {
|
||||
val topLevelCallables = indexHelper.getTopLevelCallables(scopeNameFilter)
|
||||
topLevelCallables.asSequence()
|
||||
.map { it.getSymbol() as KtCallableSymbol }
|
||||
.filter { with(visibilityChecker) { isVisible(it) } }
|
||||
.forEach { addSymbolToCompletion(expectedType, it) }
|
||||
.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
|
||||
}
|
||||
|
||||
collectTopLevelExtensionsFromIndices(implicitReceiversTypes, extensionChecker, visibilityChecker)
|
||||
.forEach { addSymbolToCompletion(expectedType, it) }
|
||||
.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
|
||||
}
|
||||
|
||||
private fun KtAnalysisSession.collectDotCompletion(
|
||||
protected open fun KtAnalysisSession.collectDotCompletion(
|
||||
implicitScopes: KtCompositeScope,
|
||||
explicitReceiver: KtExpression,
|
||||
expectedType: KtType?,
|
||||
@@ -111,11 +116,11 @@ internal class FirCallableCompletionContributor(
|
||||
.filterNot { it.isExtension }
|
||||
.filter { with(visibilityChecker) { isVisible(it) } }
|
||||
.forEach { callable ->
|
||||
addSymbolToCompletion(expectedType, callable)
|
||||
addCallableSymbolToCompletion(expectedType, callable, insertionStrategy = insertionStrategy)
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtAnalysisSession.collectDotCompletionForCallableReceiver(
|
||||
protected fun KtAnalysisSession.collectDotCompletionForCallableReceiver(
|
||||
implicitScopes: KtCompositeScope,
|
||||
explicitReceiver: KtExpression,
|
||||
expectedType: KtType?,
|
||||
@@ -128,11 +133,11 @@ internal class FirCallableCompletionContributor(
|
||||
val nonExtensionMembers = collectNonExtensions(possibleReceiverScope, visibilityChecker)
|
||||
val extensionNonMembers = collectSuitableExtensions(implicitScopes, extensionChecker, visibilityChecker)
|
||||
|
||||
nonExtensionMembers.forEach { addSymbolToCompletion(expectedType, it) }
|
||||
extensionNonMembers.forEach { addSymbolToCompletion(expectedType, it) }
|
||||
nonExtensionMembers.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
|
||||
extensionNonMembers.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
|
||||
|
||||
collectTopLevelExtensionsFromIndices(listOf(typeOfPossibleReceiver), extensionChecker, visibilityChecker)
|
||||
.forEach { addSymbolToCompletion(expectedType, it) }
|
||||
.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
|
||||
}
|
||||
|
||||
private fun KtAnalysisSession.collectTopLevelExtensionsFromIndices(
|
||||
@@ -168,6 +173,36 @@ internal class FirCallableCompletionContributor(
|
||||
implicitReceiversTypes.flatMapTo(hashSetOf()) { with(typeNamesProvider) { findAllNames(it) } }
|
||||
}
|
||||
|
||||
internal class FirCallableReferenceCompletionContributor(
|
||||
basicContext: FirBasicCompletionContext
|
||||
) : FirCallableCompletionContributor(basicContext) {
|
||||
override val insertionStrategy: CallableInsertionStrategy = CallableInsertionStrategy.AS_IDENTIFIER
|
||||
|
||||
override fun KtAnalysisSession.collectDotCompletion(
|
||||
implicitScopes: KtCompositeScope,
|
||||
explicitReceiver: KtExpression,
|
||||
expectedType: KtType?,
|
||||
extensionChecker: ExtensionApplicabilityChecker,
|
||||
visibilityChecker: CompletionVisibilityChecker
|
||||
) {
|
||||
when (val resolved = explicitReceiver.reference()?.resolveToSymbol()) {
|
||||
is KtPackageSymbol -> return
|
||||
is KtNamedClassOrObjectSymbol -> {
|
||||
resolved.getMemberScope()
|
||||
.getCallableSymbols(scopeNameFilter)
|
||||
.filter { with(visibilityChecker) { isVisible(it) } }
|
||||
.forEach { symbol ->
|
||||
addCallableSymbolToCompletion(expectedType = null, symbol, insertionStrategy = insertionStrategy)
|
||||
}
|
||||
|
||||
}
|
||||
else -> {
|
||||
collectDotCompletionForCallableReceiver(implicitScopes, explicitReceiver, expectedType, extensionChecker, visibilityChecker)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeNamesProvider(private val indexHelper: HLIndexHelper) {
|
||||
fun KtAnalysisSession.findAllNames(type: KtType): Set<String> {
|
||||
if (type !is KtNonErrorClassType) return emptySet()
|
||||
|
||||
+18
-3
@@ -14,6 +14,8 @@ import org.jetbrains.kotlin.idea.frontend.api.symbols.*
|
||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtClassType
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.idea.completion.contributors.helpers.FirClassifierProvider.getAvailableClassifiersCurrentScope
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.detectImportStrategy
|
||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtNonErrorClassType
|
||||
|
||||
internal open class FirClassifierCompletionContributor(
|
||||
@@ -22,6 +24,9 @@ internal open class FirClassifierCompletionContributor(
|
||||
|
||||
protected open fun KtAnalysisSession.filterClassifiers(classifierSymbol: KtClassifierSymbol): Boolean = true
|
||||
|
||||
protected open fun KtAnalysisSession.getImportingStrategy(classifierSymbol: KtClassifierSymbol): ImportStrategy =
|
||||
detectImportStrategy(classifierSymbol)
|
||||
|
||||
override fun KtAnalysisSession.complete(positionContext: FirNameReferencePositionContext) {
|
||||
val visibilityChecker = CompletionVisibilityChecker.create(basicContext, positionContext)
|
||||
|
||||
@@ -45,7 +50,7 @@ internal open class FirClassifierCompletionContributor(
|
||||
.getClassifierSymbols(scopeNameFilter)
|
||||
.filter { filterClassifiers(it) }
|
||||
.filter { with(visibilityChecker) { isVisible(it) } }
|
||||
.forEach { addClassifierSymbolToCompletion(it, insertFqName = false) }
|
||||
.forEach { addClassifierSymbolToCompletion(it, ImportStrategy.DoNothing) }
|
||||
}
|
||||
|
||||
private fun KtAnalysisSession.completeWithoutReceiver(
|
||||
@@ -60,7 +65,9 @@ internal open class FirClassifierCompletionContributor(
|
||||
visibilityChecker
|
||||
)
|
||||
.filter { filterClassifiers(it) }
|
||||
.forEach { addClassifierSymbolToCompletion(it, insertFqName = true) }
|
||||
.forEach { classifierSymbol ->
|
||||
addClassifierSymbolToCompletion(classifierSymbol, getImportingStrategy(classifierSymbol))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,4 +94,12 @@ internal class FirAnnotationCompletionContributor(
|
||||
expendedClass?.let { filterClassifiers(it) } == true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class FirClassifierReferenceCompletionContributor(
|
||||
basicContext: FirBasicCompletionContext
|
||||
) : FirClassifierCompletionContributor(basicContext) {
|
||||
|
||||
override fun KtAnalysisSession.getImportingStrategy(classifierSymbol: KtClassifierSymbol): ImportStrategy =
|
||||
ImportStrategy.DoNothing
|
||||
}
|
||||
|
||||
+11
-7
@@ -6,15 +6,15 @@
|
||||
package org.jetbrains.kotlin.idea.completion.contributors
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionParameters
|
||||
import com.intellij.codeInsight.completion.CompletionResultSet
|
||||
import com.intellij.codeInsight.completion.PrefixMatcher
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.idea.completion.LookupElementSink
|
||||
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
|
||||
import org.jetbrains.kotlin.idea.completion.context.FirRawPositionCompletionContext
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CallableImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.detectImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.factories.KotlinFirLookupElementFactory
|
||||
import org.jetbrains.kotlin.idea.completion.weighers.Weighers
|
||||
import org.jetbrains.kotlin.idea.fir.HLIndexHelper
|
||||
@@ -50,16 +50,18 @@ internal abstract class FirCompletionContributorBase<C : FirRawPositionCompletio
|
||||
if (symbol !is KtNamedSymbol) return
|
||||
with(lookupElementFactory) {
|
||||
createLookupElement(symbol)
|
||||
.let { applyWeighers(it, symbol, expectedType) }
|
||||
.let(sink::addElement)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun KtAnalysisSession.addClassifierSymbolToCompletion(symbol: KtClassifierSymbol, insertFqName: Boolean) {
|
||||
protected fun KtAnalysisSession.addClassifierSymbolToCompletion(
|
||||
symbol: KtClassifierSymbol,
|
||||
importingStrategy: ImportStrategy = detectImportStrategy(symbol)
|
||||
) {
|
||||
if (symbol !is KtNamedSymbol) return
|
||||
val lookup = with(lookupElementFactory) {
|
||||
when (symbol) {
|
||||
is KtClassLikeSymbol -> createLookupElementForClassLikeSymbol(symbol, insertFqName)
|
||||
is KtClassLikeSymbol -> createLookupElementForClassLikeSymbol(symbol, importingStrategy)
|
||||
is KtTypeParameterSymbol -> createLookupElement(symbol)
|
||||
}
|
||||
} ?: return
|
||||
@@ -67,14 +69,16 @@ internal abstract class FirCompletionContributorBase<C : FirRawPositionCompletio
|
||||
}
|
||||
|
||||
protected fun KtAnalysisSession.addCallableSymbolToCompletion(
|
||||
expectedType: KtType?,
|
||||
symbol: KtCallableSymbol,
|
||||
importingStrategy: CallableImportStrategy,
|
||||
importingStrategy: ImportStrategy = detectImportStrategy(symbol),
|
||||
insertionStrategy: CallableInsertionStrategy,
|
||||
) {
|
||||
if (symbol !is KtNamedSymbol) return
|
||||
val lookup = with(lookupElementFactory) {
|
||||
createCallableLookupElement(symbol, importingStrategy, insertionStrategy)
|
||||
} ?: return
|
||||
}
|
||||
applyWeighers(lookup, symbol, expectedType)
|
||||
sink.addElement(lookup)
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -9,7 +9,7 @@ import org.jetbrains.kotlin.idea.completion.checkers.CompletionVisibilityChecker
|
||||
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
|
||||
import org.jetbrains.kotlin.idea.completion.context.FirImportDirectivePositionContext
|
||||
import org.jetbrains.kotlin.idea.completion.contributors.helpers.getStaticScope
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CallableImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionStrategy
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
|
||||
@@ -23,14 +23,15 @@ internal class FirImportDirectivePackageMembersCompletionContributor(
|
||||
|
||||
scope.getClassifierSymbols(scopeNameFilter)
|
||||
.filter { with(visibilityChecker) { isVisible(it) } }
|
||||
.forEach { addClassifierSymbolToCompletion(it, insertFqName = false) }
|
||||
.forEach { addClassifierSymbolToCompletion(it, ImportStrategy.DoNothing) }
|
||||
|
||||
scope.getCallableSymbols(scopeNameFilter)
|
||||
.filter { with(visibilityChecker) { isVisible(it) } }
|
||||
.forEach {
|
||||
addCallableSymbolToCompletion(
|
||||
expectedType = null,
|
||||
it,
|
||||
importingStrategy = CallableImportStrategy.DoNothing,
|
||||
importingStrategy = ImportStrategy.DoNothing,
|
||||
insertionStrategy = CallableInsertionStrategy.AS_IDENTIFIER
|
||||
)
|
||||
}
|
||||
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* 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.lookups
|
||||
|
||||
import org.jetbrains.kotlin.idea.frontend.api.analyse
|
||||
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.addImportToFile
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtKotlinPropertySymbol
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtPossibleMemberSymbol
|
||||
import org.jetbrains.kotlin.name.CallableId
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
internal sealed class CallableImportStrategy {
|
||||
object DoNothing : CallableImportStrategy()
|
||||
data class AddImport(val nameToImport: CallableId) : CallableImportStrategy()
|
||||
data class InsertFqNameAndShorten(val callableId: CallableId) : CallableImportStrategy()
|
||||
}
|
||||
|
||||
internal fun detectImportStrategy(symbol: KtCallableSymbol): CallableImportStrategy {
|
||||
if (symbol !is KtPossibleMemberSymbol || symbol.dispatchType != null) return CallableImportStrategy.DoNothing
|
||||
|
||||
val propertyId = symbol.callableIdIfNonLocal ?: return CallableImportStrategy.DoNothing
|
||||
|
||||
return if (symbol.isExtension) {
|
||||
CallableImportStrategy.AddImport(propertyId)
|
||||
} else {
|
||||
CallableImportStrategy.InsertFqNameAndShorten(propertyId)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun addCallableImportIfRequired(targetFile: KtFile, nameToImport: CallableId) {
|
||||
if (!alreadyHasImport(targetFile, nameToImport)) {
|
||||
addImportToFile(targetFile.project, targetFile, nameToImport)
|
||||
}
|
||||
}
|
||||
|
||||
private fun alreadyHasImport(file: KtFile, nameToImport: CallableId): Boolean {
|
||||
if (file.importDirectives.any { it.importPath?.fqName == nameToImport.asSingleFqName() }) return true
|
||||
|
||||
withAllowedResolve {
|
||||
analyse(file) {
|
||||
val scopes = file.getScopeContextForFile().scopes
|
||||
if (!scopes.mayContainName(nameToImport.callableName)) return false
|
||||
|
||||
return scopes
|
||||
.getCallableSymbols { it == nameToImport.callableName }
|
||||
.any {
|
||||
it is KtKotlinPropertySymbol && it.callableIdIfNonLocal == nameToImport ||
|
||||
it is KtFunctionSymbol && it.callableIdIfNonLocal == nameToImport
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.lookups
|
||||
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.frontend.api.analyse
|
||||
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.addImportToFile
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtPossibleMemberSymbol
|
||||
import org.jetbrains.kotlin.name.CallableId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
internal sealed class ImportStrategy {
|
||||
object DoNothing : ImportStrategy()
|
||||
data class AddImport(val nameToImport: FqName) : ImportStrategy()
|
||||
data class InsertFqNameAndShorten(val fqName: FqName) : ImportStrategy()
|
||||
}
|
||||
|
||||
internal fun KtAnalysisSession.detectImportStrategy(symbol: KtSymbol): ImportStrategy = when (symbol) {
|
||||
is KtCallableSymbol -> detectImportStrategyForCallableSymbol(symbol)
|
||||
is KtClassLikeSymbol -> detectImportStrategyForClassLikeSymbol(symbol)
|
||||
else -> ImportStrategy.DoNothing
|
||||
}
|
||||
|
||||
internal fun KtAnalysisSession.detectImportStrategyForCallableSymbol(symbol: KtCallableSymbol): ImportStrategy {
|
||||
if (symbol !is KtPossibleMemberSymbol || symbol.dispatchType != null) return ImportStrategy.DoNothing
|
||||
|
||||
val propertyId = symbol.callableIdIfNonLocal?.asSingleFqName() ?: return ImportStrategy.DoNothing
|
||||
|
||||
return if (symbol.isExtension) {
|
||||
ImportStrategy.AddImport(propertyId)
|
||||
} else {
|
||||
ImportStrategy.InsertFqNameAndShorten(propertyId)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun KtAnalysisSession.detectImportStrategyForClassLikeSymbol(symbol: KtClassLikeSymbol): ImportStrategy {
|
||||
val classId = symbol.classIdIfNonLocal ?: return ImportStrategy.DoNothing
|
||||
return ImportStrategy.InsertFqNameAndShorten(classId.asSingleFqName())
|
||||
}
|
||||
|
||||
internal fun addCallableImportIfRequired(targetFile: KtFile, nameToImport: FqName) {
|
||||
if (!alreadyHasImport(targetFile, nameToImport)) {
|
||||
addImportToFile(targetFile.project, targetFile, nameToImport)
|
||||
}
|
||||
}
|
||||
|
||||
private fun alreadyHasImport(file: KtFile, nameToImport: FqName): Boolean {
|
||||
if (file.importDirectives.any { it.importPath?.fqName == nameToImport }) return true
|
||||
|
||||
withAllowedResolve {
|
||||
analyse(file) {
|
||||
val scopes = file.getScopeContextForFile().scopes
|
||||
if (!scopes.mayContainName(nameToImport.shortName())) return false
|
||||
|
||||
val anyCallableSymbolMatches = scopes
|
||||
.getCallableSymbols { it == nameToImport.shortName() }
|
||||
.any { callable ->
|
||||
val callableFqName = callable.callableIdIfNonLocal?.asSingleFqName()
|
||||
callable is KtKotlinPropertySymbol && callableFqName == nameToImport ||
|
||||
callable is KtFunctionSymbol && callableFqName == nameToImport
|
||||
}
|
||||
if (anyCallableSymbolMatches) return true
|
||||
|
||||
return scopes.getClassifierSymbols { it == nameToImport.shortName() }.any { classifier ->
|
||||
val classId = (classifier as? KtClassLikeSymbol)?.classIdIfNonLocal
|
||||
classId?.asSingleFqName() == nameToImport
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-8
@@ -10,6 +10,8 @@ import com.intellij.codeInsight.completion.InsertionContext
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.*
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.KotlinLookupObject
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.shortenReferencesForFirCompletion
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.withSymbolInfo
|
||||
@@ -21,10 +23,13 @@ import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.renderer.render
|
||||
|
||||
class ClassLookupElementFactory {
|
||||
fun KtAnalysisSession.createLookup(symbol: KtClassLikeSymbol, insertFqName: Boolean = true): LookupElementBuilder {
|
||||
internal class ClassLookupElementFactory {
|
||||
fun KtAnalysisSession.createLookup(
|
||||
symbol: KtClassLikeSymbol,
|
||||
importingStrategy: ImportStrategy = detectImportStrategy(symbol)
|
||||
): LookupElementBuilder {
|
||||
val name = symbol.nameOrAnonymous
|
||||
return LookupElementBuilder.create(ClassifierLookupObject(name, symbol.classIdIfNonLocal, insertFqName), name.asString())
|
||||
return LookupElementBuilder.create(ClassifierLookupObject(name, importingStrategy), name.asString())
|
||||
.withInsertHandler(ClassifierInsertionHandler)
|
||||
.let { withSymbolInfo(symbol, it) }
|
||||
}
|
||||
@@ -33,9 +38,8 @@ class ClassLookupElementFactory {
|
||||
|
||||
private data class ClassifierLookupObject(
|
||||
override val shortName: Name,
|
||||
val classId: ClassId?,
|
||||
val insertFqName: Boolean
|
||||
) : KotlinLookupObject {}
|
||||
val importingStrategy: ImportStrategy
|
||||
) : KotlinLookupObject
|
||||
|
||||
/**
|
||||
* The simplest implementation of the insertion handler for a classifiers.
|
||||
@@ -45,8 +49,8 @@ private object ClassifierInsertionHandler : InsertHandler<LookupElement> {
|
||||
val targetFile = context.file as? KtFile ?: return
|
||||
val lookupObject = item.`object` as ClassifierLookupObject
|
||||
|
||||
if (lookupObject.classId != null && lookupObject.insertFqName) {
|
||||
val fqName = lookupObject.classId.asSingleFqName()
|
||||
if (lookupObject.importingStrategy is ImportStrategy.InsertFqNameAndShorten) {
|
||||
val fqName = lookupObject.importingStrategy.fqName
|
||||
|
||||
context.document.replaceString(context.startOffset, context.tailOffset, fqName.render())
|
||||
context.commitDocument()
|
||||
|
||||
+7
-9
@@ -10,18 +10,15 @@ import com.intellij.codeInsight.completion.InsertionContext
|
||||
import com.intellij.codeInsight.lookup.Lookup
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||
import com.intellij.openapi.diagnostic.ControlFlowException
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.*
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CallableImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CompletionShortNamesRenderer
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.QuotedNamesAwareInsertionHandler
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.addCallableImportIfRequired
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.shortenReferencesForFirCompletion
|
||||
import org.jetbrains.kotlin.idea.core.asFqNameWithRootPrefixIfNeeded
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
@@ -34,11 +31,12 @@ import org.jetbrains.kotlin.idea.completion.lookups.TailTextProvider.getTailText
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.TailTextProvider.insertLambdaBraces
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CompletionShortNamesRenderer.renderFunctionParameters
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CompletionShortNamesRenderer.TYPE_RENDERING_OPTIONS
|
||||
import org.jetbrains.kotlin.idea.core.withRootPrefixIfNeeded
|
||||
|
||||
internal class FunctionLookupElementFactory {
|
||||
fun KtAnalysisSession.createLookup(
|
||||
symbol: KtFunctionSymbol,
|
||||
importStrategy: CallableImportStrategy,
|
||||
importStrategy: ImportStrategy,
|
||||
insertionStrategy: CallableInsertionStrategy
|
||||
): LookupElementBuilder {
|
||||
val lookupObject = FunctionLookupObject(
|
||||
@@ -68,7 +66,7 @@ internal class FunctionLookupElementFactory {
|
||||
*/
|
||||
private data class FunctionLookupObject(
|
||||
override val shortName: Name,
|
||||
val importStrategy: CallableImportStrategy,
|
||||
val importStrategy: ImportStrategy,
|
||||
val inputValueArguments: Boolean,
|
||||
val insertEmptyLambda: Boolean,
|
||||
// for distinction between different overloads
|
||||
@@ -167,11 +165,11 @@ private object FunctionInsertionHandler : QuotedNamesAwareInsertionHandler() {
|
||||
val element = context.file.findElementAt(startOffset) ?: return
|
||||
|
||||
val importStrategy = lookupObject.importStrategy
|
||||
if (importStrategy is CallableImportStrategy.InsertFqNameAndShorten) {
|
||||
if (importStrategy is ImportStrategy.InsertFqNameAndShorten) {
|
||||
context.document.replaceString(
|
||||
context.startOffset,
|
||||
context.tailOffset,
|
||||
importStrategy.callableId.asFqNameWithRootPrefixIfNeeded().render()
|
||||
importStrategy.fqName.withRootPrefixIfNeeded().render()
|
||||
)
|
||||
context.commitDocument()
|
||||
|
||||
@@ -183,7 +181,7 @@ private object FunctionInsertionHandler : QuotedNamesAwareInsertionHandler() {
|
||||
addArguments(context, element, lookupObject)
|
||||
context.commitDocument()
|
||||
|
||||
if (importStrategy is CallableImportStrategy.AddImport) {
|
||||
if (importStrategy is ImportStrategy.AddImport) {
|
||||
addCallableImportIfRequired(targetFile, importStrategy.nameToImport)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.idea.completion.lookups.factories
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CallableImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.detectImportStrategy
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
@@ -37,7 +37,7 @@ internal class KotlinFirLookupElementFactory {
|
||||
|
||||
fun KtAnalysisSession.createCallableLookupElement(
|
||||
symbol: KtCallableSymbol,
|
||||
importingStrategy: CallableImportStrategy,
|
||||
importingStrategy: ImportStrategy,
|
||||
insertionStrategy: CallableInsertionStrategy,
|
||||
): LookupElementBuilder {
|
||||
return when (symbol) {
|
||||
@@ -50,9 +50,12 @@ internal class KotlinFirLookupElementFactory {
|
||||
fun createPackagePartLookupElement(packagePartFqName: FqName): LookupElement =
|
||||
packagePartLookupElementFactory.createPackagePartLookupElement(packagePartFqName)
|
||||
|
||||
fun KtAnalysisSession.createLookupElementForClassLikeSymbol(symbol: KtClassLikeSymbol, insertFqName: Boolean = true): LookupElement? {
|
||||
fun KtAnalysisSession.createLookupElementForClassLikeSymbol(
|
||||
symbol: KtClassLikeSymbol,
|
||||
importingStrategy: ImportStrategy = detectImportStrategy(symbol)
|
||||
): LookupElement? {
|
||||
if (symbol !is KtNamedSymbol) return null
|
||||
return with(classLookupElementFactory) { createLookup(symbol, insertFqName) }
|
||||
return with(classLookupElementFactory) { createLookup(symbol, importingStrategy) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -11,12 +11,12 @@ import com.intellij.codeInsight.lookup.LookupElement
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.*
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.CallableImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.TailTextProvider.getTailText
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.addCallableImportIfRequired
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.detectImportStrategy
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.shortenReferencesForFirCompletion
|
||||
import org.jetbrains.kotlin.idea.core.asFqNameWithRootPrefixIfNeeded
|
||||
import org.jetbrains.kotlin.idea.core.withRootPrefixIfNeeded
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeRendererOptions
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSyntheticJavaPropertySymbol
|
||||
@@ -28,7 +28,7 @@ import org.jetbrains.kotlin.renderer.render
|
||||
internal class VariableLookupElementFactory {
|
||||
fun KtAnalysisSession.createLookup(
|
||||
symbol: KtVariableLikeSymbol,
|
||||
importStrategy: CallableImportStrategy = detectImportStrategy(symbol)
|
||||
importStrategy: ImportStrategy = detectImportStrategy(symbol)
|
||||
): LookupElementBuilder {
|
||||
val lookupObject = VariableLookupObject(
|
||||
symbol.name,
|
||||
@@ -63,7 +63,7 @@ internal class VariableLookupElementFactory {
|
||||
*/
|
||||
private data class VariableLookupObject(
|
||||
override val shortName: Name,
|
||||
val importStrategy: CallableImportStrategy,
|
||||
val importStrategy: ImportStrategy,
|
||||
override val renderedReceiverType: String?,
|
||||
) : KotlinCallableLookupObject()
|
||||
|
||||
@@ -74,22 +74,22 @@ private object VariableInsertionHandler : InsertHandler<LookupElement> {
|
||||
val lookupObject = item.`object` as VariableLookupObject
|
||||
|
||||
when (val importStrategy = lookupObject.importStrategy) {
|
||||
is CallableImportStrategy.AddImport -> {
|
||||
is ImportStrategy.AddImport -> {
|
||||
addCallableImportIfRequired(targetFile, importStrategy.nameToImport)
|
||||
}
|
||||
|
||||
is CallableImportStrategy.InsertFqNameAndShorten -> {
|
||||
is ImportStrategy.InsertFqNameAndShorten -> {
|
||||
context.document.replaceString(
|
||||
context.startOffset,
|
||||
context.tailOffset,
|
||||
importStrategy.callableId.asFqNameWithRootPrefixIfNeeded().render()
|
||||
importStrategy.fqName.withRootPrefixIfNeeded().render()
|
||||
)
|
||||
|
||||
context.commitDocument()
|
||||
shortenReferencesForFirCompletion(targetFile, TextRange(context.startOffset, context.tailOffset))
|
||||
}
|
||||
|
||||
is CallableImportStrategy.DoNothing -> {
|
||||
is ImportStrategy.DoNothing -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,17 @@ interface KtScope : ValidityTokenOwner {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a sequence of all [KtSymbol] which current scope contain if declaration name matches [nameFilter]
|
||||
* Constructors are not included here as they have no name
|
||||
*/
|
||||
fun getAllNamedSymbols(nameFilter: KtScopeNameFilter = { true }): Sequence<KtSymbol> = withValidityAssertion {
|
||||
sequence {
|
||||
yieldAll(getCallableSymbols(nameFilter))
|
||||
yieldAll(getClassifierSymbols(nameFilter))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a sequence of [KtCallableSymbol] which current scope contain if declaration name matches [nameFilter]
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user