FIR IDE: implement override completion

This commit is contained in:
Ilya Kirillov
2021-05-10 20:27:18 +02:00
committed by TeamCityServer
parent ae212f36f5
commit 8983bde73e
42 changed files with 244 additions and 68 deletions
@@ -16,34 +16,21 @@
package org.jetbrains.kotlin.idea.completion package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.icons.AllIcons import com.intellij.icons.AllIcons
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.ui.RowIcon import com.intellij.ui.RowIcon
import org.jetbrains.kotlin.backend.common.descriptors.isSuspend import org.jetbrains.kotlin.backend.common.descriptors.isSuspend
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.KotlinDescriptorIconProvider import org.jetbrains.kotlin.idea.KotlinDescriptorIconProvider
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.completion.handlers.indexOfSkippingSpace
import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.core.moveCaret
import org.jetbrains.kotlin.idea.core.moveCaretIntoGeneratedElement
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMembersHandler import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMembersHandler
import org.jetbrains.kotlin.idea.core.overrideImplement.generateMember import org.jetbrains.kotlin.idea.core.overrideImplement.generateMember
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import javax.swing.Icon
class OverridesCompletion( class OverridesCompletion(
private val collector: LookupElementsCollector, private val collector: LookupElementsCollector,
@@ -96,10 +83,9 @@ class OverridesCompletion(
baseClassName, baseClassName,
baseClassIcon, baseClassIcon,
isConstructorParameter, isConstructorParameter,
classOrObject,
memberObject.descriptor.isSuspend, memberObject.descriptor.isSuspend,
memberObject::generateMember, generateMember = { memberObject.generateMember(classOrObject, copyDoc = false) },
ShortenReferences.DEFAULT::process, shortenReferences = ShortenReferences.DEFAULT::process,
) )
lookupElement.assignPriority(if (isImplement) ItemPriority.IMPLEMENT else ItemPriority.OVERRIDE) lookupElement.assignPriority(if (isImplement) ItemPriority.IMPLEMENT else ItemPriority.OVERRIDE)
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface Interface { interface Interface {
var field: Int var field: Int
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface Interface { interface Interface {
var field: Int var field: Int
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
fun foo(p: Int) fun foo(p: Int)
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
fun foo(p: Int) fun foo(p: Int)
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
val someVal: String? val someVal: String?
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
val someVal: String? val someVal: String?
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
var someVar: String var someVar: String
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
var someVar: String var someVar: String
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class A { class A {
@Deprecated("") @Deprecated("")
o<caret> o<caret>
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class A { class A {
@Deprecated("") @Deprecated("")
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class A { class A {
@Deprecated("") // it is deprecated @Deprecated("") // it is deprecated
public o<caret> public o<caret>
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class A { class A {
@Deprecated("") // it is deprecated @Deprecated("") // it is deprecated
public override fun equals(other: Any?): Boolean { public override fun equals(other: Any?): Boolean {
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class A { class A {
o<caret> o<caret>
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class A { class A {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
<caret><selection>return super.equals(other)</selection> <caret><selection>return super.equals(other)</selection>
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
val someVal: java.io.File? val someVal: java.io.File?
} }
@@ -1,5 +1,6 @@
import java.io.File import java.io.File
// FIR_COMPARISON
interface I { interface I {
val someVal: java.io.File? val someVal: java.io.File?
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
suspend fun foo() suspend fun foo()
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
suspend fun foo() suspend fun foo()
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class A { class A {
equ<caret> equ<caret>
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class A { class A {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
<caret><selection>return super.equals(other)</selection> <caret><selection>return super.equals(other)</selection>
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
protected var someVar: java.io.File? protected var someVar: java.io.File?
} }
@@ -1,5 +1,6 @@
import java.io.File import java.io.File
// FIR_COMPARISON
interface I { interface I {
protected var someVar: java.io.File? protected var someVar: java.io.File?
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
val someVal: java.io.File? val someVal: java.io.File?
} }
@@ -1,5 +1,6 @@
import java.io.File import java.io.File
// FIR_COMPARISON
interface I { interface I {
val someVal: java.io.File? val someVal: java.io.File?
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
val p: Int val p: Int
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
val p: Int val p: Int
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
val p: Int val p: Int
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I { interface I {
val p: Int val p: Int
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package ppp package ppp
interface I { interface I {
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package ppp package ppp
interface I { interface I {
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface Interface { interface Interface {
fun funA() fun funA()
} }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface Interface { interface Interface {
fun funA() fun funA()
} }
@@ -55,21 +55,15 @@ private object KotlinFirCompletionProvider : CompletionProvider<CompletionParame
val keywordContributor = FirKeywordCompletionContributor(basicContext) val keywordContributor = FirKeywordCompletionContributor(basicContext)
if (positionContext is FirUnknownPositionContext) { FirPositionCompletionContextDetector.analyseInContext(basicContext, positionContext) {
// TODO unify when LL API will be able to handle more context for completion with(keywordContributor) { completeKeywords(positionContext) }
keywordContributor.completeDefaultKeywordsWithoutResolve(positionContext.position, expression = null) when (positionContext) {
} else { is FirNameReferencePositionContext -> with(
FirPositionCompletionContextDetector.analyseInContext(basicContext, positionContext) { KotlinWithNameReferenceCompletionProvider(basicContext, indexHelper)
with(keywordContributor) { completeKeywords(positionContext) } ) {
when (positionContext) { addCompletions(positionContext)
is FirNameReferencePositionContext -> with( }
KotlinWithNameReferenceCompletionProvider(basicContext, indexHelper) is FirUnknownPositionContext -> {
) {
addCompletions(positionContext)
}
is FirUnknownPositionContext -> {
// TODO remove when hack when LL API will be able to handle more context for completion
}
} }
} }
} }
@@ -137,7 +131,6 @@ private class KotlinWithNameReferenceCompletionProvider(
basicContext: FirBasicCompletionContext, basicContext: FirBasicCompletionContext,
private val indexHelper: IndexHelper private val indexHelper: IndexHelper
) : FirCompletionContributorBase(basicContext) { ) : FirCompletionContributorBase(basicContext) {
private val lookupElementFactory = KotlinFirLookupElementFactory()
private val typeNamesProvider = TypeNamesProvider(indexHelper) private val typeNamesProvider = TypeNamesProvider(indexHelper)
private val scopeNameFilter: KtScopeNameFilter = private val scopeNameFilter: KtScopeNameFilter =
@@ -198,13 +191,13 @@ private class KotlinWithNameReferenceCompletionProvider(
.getClassifierSymbols(scopeNameFilter) .getClassifierSymbols(scopeNameFilter)
.filter { visibilityChecker.isVisible(it) } .filter { visibilityChecker.isVisible(it) }
classesFromScopes.forEach { addSymbolToCompletion(result, expectedType, it) } classesFromScopes.forEach { addSymbolToCompletion(expectedType, it) }
val kotlinClassesFromIndices = indexHelper.getKotlinClasses(scopeNameFilter, psiFilter = { it !is KtEnumEntry }) val kotlinClassesFromIndices = indexHelper.getKotlinClasses(scopeNameFilter, psiFilter = { it !is KtEnumEntry })
kotlinClassesFromIndices.asSequence() kotlinClassesFromIndices.asSequence()
.map { it.getSymbol() as KtClassifierSymbol } .map { it.getSymbol() as KtClassifierSymbol }
.filter { visibilityChecker.isVisible(it) } .filter { visibilityChecker.isVisible(it) }
.forEach { addSymbolToCompletion(result, expectedType, it) } .forEach { addSymbolToCompletion(expectedType, it) }
} }
private fun KtAnalysisSession.collectDotCompletion( private fun KtAnalysisSession.collectDotCompletion(
@@ -220,11 +213,11 @@ private class KotlinWithNameReferenceCompletionProvider(
val nonExtensionMembers = possibleReceiverScope.collectNonExtensions(visibilityChecker) val nonExtensionMembers = possibleReceiverScope.collectNonExtensions(visibilityChecker)
val extensionNonMembers = implicitScopes.collectSuitableExtensions(extensionChecker, visibilityChecker) val extensionNonMembers = implicitScopes.collectSuitableExtensions(extensionChecker, visibilityChecker)
nonExtensionMembers.forEach { addSymbolToCompletion(result, expectedType, it) } nonExtensionMembers.forEach { addSymbolToCompletion(expectedType, it) }
extensionNonMembers.forEach { addSymbolToCompletion(result, expectedType, it) } extensionNonMembers.forEach { addSymbolToCompletion(expectedType, it) }
collectTopLevelExtensionsFromIndices(listOf(typeOfPossibleReceiver), extensionChecker, visibilityChecker) collectTopLevelExtensionsFromIndices(listOf(typeOfPossibleReceiver), extensionChecker, visibilityChecker)
.forEach { addSymbolToCompletion(result, expectedType, it) } .forEach { addSymbolToCompletion(expectedType, it) }
} }
private fun KtAnalysisSession.collectDefaultCompletion( private fun KtAnalysisSession.collectDefaultCompletion(
@@ -238,19 +231,19 @@ private class KotlinWithNameReferenceCompletionProvider(
val availableNonExtensions = implicitScopes.collectNonExtensions(visibilityChecker) val availableNonExtensions = implicitScopes.collectNonExtensions(visibilityChecker)
val extensionsWhichCanBeCalled = implicitScopes.collectSuitableExtensions(extensionChecker, visibilityChecker) val extensionsWhichCanBeCalled = implicitScopes.collectSuitableExtensions(extensionChecker, visibilityChecker)
availableNonExtensions.forEach { addSymbolToCompletion(result, expectedType, it) } availableNonExtensions.forEach { addSymbolToCompletion(expectedType, it) }
extensionsWhichCanBeCalled.forEach { addSymbolToCompletion(result, expectedType, it) } extensionsWhichCanBeCalled.forEach { addSymbolToCompletion(expectedType, it) }
if (shouldCompleteTopLevelCallablesFromIndex) { if (shouldCompleteTopLevelCallablesFromIndex) {
val topLevelCallables = indexHelper.getTopLevelCallables(scopeNameFilter) val topLevelCallables = indexHelper.getTopLevelCallables(scopeNameFilter)
topLevelCallables.asSequence() topLevelCallables.asSequence()
.map { it.getSymbol() as KtCallableSymbol } .map { it.getSymbol() as KtCallableSymbol }
.filter { visibilityChecker.isVisible(it) } .filter { visibilityChecker.isVisible(it) }
.forEach { addSymbolToCompletion(result, expectedType, it) } .forEach { addSymbolToCompletion(expectedType, it) }
} }
collectTopLevelExtensionsFromIndices(implicitReceiversTypes, extensionChecker, visibilityChecker) collectTopLevelExtensionsFromIndices(implicitReceiversTypes, extensionChecker, visibilityChecker)
.forEach { addSymbolToCompletion(result, expectedType, it) } .forEach { addSymbolToCompletion(expectedType, it) }
collectTypesCompletion(implicitScopes, expectedType, visibilityChecker) collectTypesCompletion(implicitScopes, expectedType, visibilityChecker)
} }
@@ -9,6 +9,7 @@ import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.completion.PrefixMatcher import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.completion.KotlinFirLookupElementFactory
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
@@ -21,6 +22,7 @@ internal class FirBasicCompletionContext(
val fakeKtFile: KtFile, val fakeKtFile: KtFile,
val project: Project, val project: Project,
val targetPlatform: TargetPlatform, val targetPlatform: TargetPlatform,
val lookupElementFactory: KotlinFirLookupElementFactory = KotlinFirLookupElementFactory(),
) { ) {
companion object { companion object {
fun createFromParameters(parameters: CompletionParameters, result: CompletionResultSet): FirBasicCompletionContext? { fun createFromParameters(parameters: CompletionParameters, result: CompletionResultSet): FirBasicCompletionContext? {
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.completion.context
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.analyse
import org.jetbrains.kotlin.idea.frontend.api.analyseInFakeAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.analyseInFakeAnalysisSession
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.mainReference
@@ -60,7 +61,7 @@ internal object FirPositionCompletionContextDetector {
action action
) )
is FirUnknownPositionContext -> { is FirUnknownPositionContext -> {
// TODO analyse(basicContext.originalKtFile, action)
} }
} }
} }
@@ -29,9 +29,7 @@ internal abstract class FirCompletionContributorBase(protected val basicContext:
protected val fakeKtFile: KtFile get() = basicContext.fakeKtFile protected val fakeKtFile: KtFile get() = basicContext.fakeKtFile
protected val project: Project get() = basicContext.project protected val project: Project get() = basicContext.project
protected val targetPlatform: TargetPlatform get() = basicContext.targetPlatform protected val targetPlatform: TargetPlatform get() = basicContext.targetPlatform
protected val lookupElementFactory: KotlinFirLookupElementFactory get() = basicContext.lookupElementFactory
protected val lookupElementFactory = KotlinFirLookupElementFactory()
protected fun KtAnalysisSession.addSymbolToCompletion(expectedType: KtType?, symbol: KtSymbol) { protected fun KtAnalysisSession.addSymbolToCompletion(expectedType: KtType?, symbol: KtSymbol) {
if (symbol !is KtNamedSymbol) return if (symbol !is KtNamedSymbol) return
@@ -26,6 +26,8 @@ internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionC
override fun getLanguageVersionSetting(module: Module) = LanguageVersionSettingsImpl.DEFAULT // TODO override fun getLanguageVersionSetting(module: Module) = LanguageVersionSettingsImpl.DEFAULT // TODO
}) })
private val resolveDependentCompletionKeywordHandlers = ResolveDependentCompletionKeywordHandlers(basicContext)
fun KtAnalysisSession.completeKeywords( fun KtAnalysisSession.completeKeywords(
positionContext: FirPositionCompletionContext positionContext: FirPositionCompletionContext
) { ) {
@@ -43,10 +45,10 @@ internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionC
} }
fun KtAnalysisSession.completeWithResolve(position: PsiElement, expression: KtExpression?) { fun KtAnalysisSession.completeWithResolve(position: PsiElement, expression: KtExpression?) {
complete(position, expression) { lookupElement, keyword -> complete(position) { lookupElement, keyword ->
val lookups = DefaultCompletionKeywordHandlers.defaultHandlers.getHandlerForKeyword(keyword) val lookups = DefaultCompletionKeywordHandlers.defaultHandlers.getHandlerForKeyword(keyword)
?.createLookups(parameters, expression, lookupElement, project) ?.createLookups(parameters, expression, lookupElement, project)
?: ResolveDependentCompletionKeywordHandlers.handlers.getHandlerForKeyword(keyword)?.run { ?: resolveDependentCompletionKeywordHandlers.handlers.getHandlerForKeyword(keyword)?.run {
createLookups(parameters, expression, lookupElement, project) createLookups(parameters, expression, lookupElement, project)
} }
?: listOf(lookupElement) ?: listOf(lookupElement)
@@ -54,16 +56,7 @@ internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionC
} }
} }
fun completeDefaultKeywordsWithoutResolve(position: PsiElement, expression: KtExpression?) { private inline fun complete(position: PsiElement, crossinline complete: (LookupElement, String) -> Unit) {
complete(position, expression) { lookupElement, keyword ->
val lookups = DefaultCompletionKeywordHandlers.defaultHandlers.getHandlerForKeyword(keyword)
?.createLookups(parameters, expression, lookupElement, project)
?: listOf(lookupElement)
result.addAllElements(lookups)
}
}
private inline fun complete(position: PsiElement, expression: KtExpression?, crossinline complete: (LookupElement, String) -> Unit) {
keywordCompletion.complete(position, prefixMatcher, targetPlatform.isJvm()) { lookupElement -> keywordCompletion.complete(position, prefixMatcher, targetPlatform.isJvm()) { lookupElement ->
val keyword = lookupElement.lookupString val keyword = lookupElement.lookupString
complete(lookupElement, keyword) complete(lookupElement, keyword)
@@ -71,10 +64,9 @@ internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionC
} }
} }
private object ResolveDependentCompletionKeywordHandlers { private class ResolveDependentCompletionKeywordHandlers(basicContext: FirBasicCompletionContext) {
val handlers = CompletionKeywordHandlers( val handlers = CompletionKeywordHandlers(
ReturnKeywordHandler, ReturnKeywordHandler,
OverrideKeywordHandler, OverrideKeywordHandler(basicContext),
) )
} }
@@ -0,0 +1,173 @@
/*
* 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.keywords
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.icons.AllIcons
import com.intellij.openapi.project.Project
import com.intellij.ui.RowIcon
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.idea.completion.*
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
import org.jetbrains.kotlin.idea.core.overrideImplement.*
import org.jetbrains.kotlin.idea.core.overrideImplement.KtClassMember
import org.jetbrains.kotlin.idea.core.overrideImplement.KtGenerateMembersHandler
import org.jetbrains.kotlin.idea.core.overrideImplement.KtOverrideMembersHandler
import org.jetbrains.kotlin.idea.core.overrideImplement.generateMember
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.analyse
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.markers.KtSymbolWithModality
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtIconProvider.getIcon
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.nameOrAnonymous
import org.jetbrains.kotlin.idea.frontend.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.idea.frontend.api.tokens.HackToForceAllowRunningAnalyzeOnEDT
import org.jetbrains.kotlin.idea.frontend.api.tokens.hackyAllowRunningOnEdt
import org.jetbrains.kotlin.idea.util.application.runWriteAction
internal class OverrideKeywordHandler(
private val basicContext: FirBasicCompletionContext
) : CompletionKeywordHandler<KtAnalysisSession>(KtTokens.OVERRIDE_KEYWORD) {
@OptIn(ExperimentalStdlibApi::class)
override fun KtAnalysisSession.createLookups(
parameters: CompletionParameters,
expression: KtExpression?,
lookup: LookupElement,
project: Project
): Collection<LookupElement> {
val result = mutableListOf(lookup)
val position = parameters.position
val isConstructorParameter = position.getNonStrictParentOfType<KtPrimaryConstructor>() != null
val classOrObject = position.getNonStrictParentOfType<KtClassOrObject>() ?: return result
val members = collectMembers(classOrObject, isConstructorParameter)
for (member in members) {
result += createLookupElementToGenerateSingleOverrideMember(member, classOrObject, isConstructorParameter, project)
}
return result
}
private fun KtAnalysisSession.collectMembers(classOrObject: KtClassOrObject, isConstructorParameter: Boolean): List<KtClassMember> {
val allMembers = KtOverrideMembersHandler().collectMembersToGenerate(classOrObject)
return if (isConstructorParameter) {
allMembers.mapNotNull { member ->
if (member.memberInfo.isProperty) {
member.copy(bodyType = BodyType.FROM_TEMPLATE, preferConstructorParameter = true)
} else null
}
} else allMembers.toList()
}
private fun KtAnalysisSession.createLookupElementToGenerateSingleOverrideMember(
member: KtClassMember,
classOrObject: KtClassOrObject,
isConstructorParameter: Boolean,
project: Project
): OverridesCompletionLookupElementDecorator {
val memberSymbol = member.symbol
check(memberSymbol is KtNamedSymbol)
val text = getSymbolTextForLookupElement(memberSymbol)
val baseIcon = getIcon(memberSymbol)
val isImplement = (memberSymbol as? KtSymbolWithModality)?.modality == Modality.ABSTRACT
val additionalIcon = if (isImplement) AllIcons.Gutter.ImplementingMethod else AllIcons.Gutter.OverridingMethod
val icon = RowIcon(baseIcon, additionalIcon)
val baseClass = classOrObject.getClassOrObjectSymbol()
val baseClassIcon = getIcon(baseClass)
val isSuspendFunction = (memberSymbol as? KtFunctionSymbol)?.isSuspend == true
val baseClassName = baseClass.nameOrAnonymous.asString()
val memberPointer = memberSymbol.createPointer()
val baseLookupElement = with(basicContext.lookupElementFactory) { createLookupElement(memberSymbol) }
?: error("Lookup element should be available for override completion")
return OverridesCompletionLookupElementDecorator(
baseLookupElement,
declaration = null,
text,
isImplement,
icon,
baseClassName,
baseClassIcon,
isConstructorParameter,
isSuspendFunction,
generateMember = {
generateMemberInNewAnalysisSession(classOrObject, memberPointer, member, project)
},
shortenReferences = { element ->
@OptIn(HackToForceAllowRunningAnalyzeOnEDT::class)
val shortenings = hackyAllowRunningOnEdt {
analyse(classOrObject) {
collectPossibleReferenceShortenings(element.containingKtFile, element.textRange)
}
}
runWriteAction {
shortenings.invokeShortening()
}
}
)
}
private fun KtAnalysisSession.getSymbolTextForLookupElement(memberSymbol: KtCallableSymbol): String = buildString {
append(KtTokens.OVERRIDE_KEYWORD.value)
.append(" ")
.append(memberSymbol.render(renderingOptionsForLookupElementRendering))
if (memberSymbol is KtFunctionSymbol) {
append(" {...}")
}
}
@OptIn(HackToForceAllowRunningAnalyzeOnEDT::class)
private fun generateMemberInNewAnalysisSession(
classOrObject: KtClassOrObject,
memberPointer: KtSymbolPointer<KtCallableSymbol>,
member: KtClassMember,
project: Project
) = hackyAllowRunningOnEdt {
analyse(classOrObject) {
val memberInCorrectAnalysisSession = createCopyInCurrentAnalysisSession(memberPointer, member)
generateMember(
project,
memberInCorrectAnalysisSession,
classOrObject,
copyDoc = false,
mode = MemberGenerateMode.OVERRIDE
)
}
}
//todo temporary hack until KtSymbolPointer is properly implemented
private fun KtAnalysisSession.createCopyInCurrentAnalysisSession(
memberPointer: KtSymbolPointer<KtCallableSymbol>,
member: KtClassMember
) = KtClassMember(
KtClassMemberInfo(
memberPointer.restoreSymbol()
?: error("Cannot restore symbol from $memberPointer"),
member.memberInfo.memberText,
member.memberInfo.memberIcon,
member.memberInfo.containingSymbolText,
member.memberInfo.containingSymbolIcon,
),
member.bodyType,
member.preferConstructorParameter,
)
companion object {
private val renderingOptionsForLookupElementRendering =
KtGenerateMembersHandler.renderOption.copy(
renderUnitReturnType = false,
renderDeclarationHeader = true
)
}
}
@@ -43,8 +43,8 @@ internal data class KtClassMemberInfo(
val isProperty: Boolean get() = symbol is KtPropertySymbol val isProperty: Boolean get() = symbol is KtPropertySymbol
} }
internal class KtClassMember( internal data class KtClassMember(
private val memberInfo: KtClassMemberInfo, val memberInfo: KtClassMemberInfo,
val bodyType: BodyType, val bodyType: BodyType,
val preferConstructorParameter: Boolean val preferConstructorParameter: Boolean
) : MemberChooserObjectBase( ) : MemberChooserObjectBase(
@@ -19,7 +19,7 @@ import org.jetbrains.kotlin.idea.frontend.api.tokens.hackyAllowRunningOnEdt
import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtClassOrObject
internal class KtOverrideMembersHandler : KtGenerateMembersHandler() { internal open class KtOverrideMembersHandler : KtGenerateMembersHandler() {
@OptIn(HackToForceAllowRunningAnalyzeOnEDT::class) @OptIn(HackToForceAllowRunningAnalyzeOnEDT::class)
override fun collectMembersToGenerate(classOrObject: KtClassOrObject): Collection<KtClassMember> { override fun collectMembersToGenerate(classOrObject: KtClassOrObject): Collection<KtClassMember> {
return hackyAllowRunningOnEdt { return hackyAllowRunningOnEdt {
@@ -29,7 +29,7 @@ internal class KtOverrideMembersHandler : KtGenerateMembersHandler() {
} }
} }
private fun KtAnalysisSession.collectMembers(classOrObject: KtClassOrObject): List<KtClassMember> { fun KtAnalysisSession.collectMembers(classOrObject: KtClassOrObject): List<KtClassMember> {
val classOrObjectSymbol = classOrObject.getClassOrObjectSymbol() val classOrObjectSymbol = classOrObject.getClassOrObjectSymbol()
return getOverridableMembers(classOrObjectSymbol).map { (symbol, bodyType, containingSymbol) -> return getOverridableMembers(classOrObjectSymbol).map { (symbol, bodyType, containingSymbol) ->
KtClassMember( KtClassMember(
@@ -30,9 +30,8 @@ class OverridesCompletionLookupElementDecorator(
private val baseClassName: String, private val baseClassName: String,
private val baseClassIcon: Icon?, private val baseClassIcon: Icon?,
private val isConstructorParameter: Boolean, private val isConstructorParameter: Boolean,
private val classOrObject: KtClassOrObject,
private val isSuspend: Boolean, private val isSuspend: Boolean,
private val generateMember: (targetClass: KtClassOrObject, copyDoc: Boolean) -> KtCallableDeclaration, private val generateMember: () -> KtCallableDeclaration,
private val shortenReferences: (KtElement) -> Unit, private val shortenReferences: (KtElement) -> Unit,
) : LookupElementDecorator<LookupElement>(lookupElement) { ) : LookupElementDecorator<LookupElement>(lookupElement) {
override fun getLookupString() = override fun getLookupString() =
@@ -85,7 +84,7 @@ class OverridesCompletionLookupElementDecorator(
// keep original modifiers // keep original modifiers
val modifierList = KtPsiFactory(context.project).createModifierList(dummyMember.modifierList!!.text) val modifierList = KtPsiFactory(context.project).createModifierList(dummyMember.modifierList!!.text)
val prototype = generateMember(classOrObject, false) val prototype = generateMember()
prototype.modifierList!!.replace(modifierList) prototype.modifierList!!.replace(modifierList)
val insertedMember = dummyMember.replaced(prototype) val insertedMember = dummyMember.replaced(prototype)
if (isSuspend) insertedMember.addModifier(KtTokens.SUSPEND_KEYWORD) if (isSuspend) insertedMember.addModifier(KtTokens.SUSPEND_KEYWORD)