From 1d64c0789fe0a7adae4b748899afc87021734016 Mon Sep 17 00:00:00 2001 From: Roman Golyshev Date: Fri, 12 Feb 2021 16:02:13 +0300 Subject: [PATCH] FIR IDE: Add implementation of classifiers completion from indices --- .../KotlinFirCompletionContributor.kt | 26 +++- .../KotlinFirLookupElementFactory.kt | 127 +++++++++++++++--- .../api/FirModuleResolveStateForCompletion.kt | 2 +- .../idea/fir/low/level/api/IndexHelper.kt | 17 ++- 4 files changed, 147 insertions(+), 25 deletions(-) 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 c6b56a4133e..09950ffdf75 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 @@ -10,7 +10,9 @@ import com.intellij.codeInsight.lookup.LookupElement import com.intellij.patterns.PlatformPatterns import com.intellij.patterns.PsiJavaPatterns import com.intellij.util.ProcessingContext +import org.jetbrains.kotlin.idea.caches.project.getModuleInfo import org.jetbrains.kotlin.idea.completion.weighers.Weighers +import org.jetbrains.kotlin.idea.fir.low.level.api.IndexHelper import org.jetbrains.kotlin.idea.fir.low.level.api.util.originalKtFile import org.jetbrains.kotlin.idea.frontend.api.InvalidWayOfUsingAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession @@ -39,7 +41,12 @@ private object KotlinFirCompletionProvider : CompletionProvider { + val classFqName = symbol.classIdIfNonLocal?.asSingleFqName() + ?: return QuotedNamesAwareInsertionHandler(symbol.name) + + return ClassifierInsertionHandler(classFqName) } } @@ -115,11 +131,25 @@ private class FunctionLookupElementFactory { } private fun KtAnalysisSession.createInsertHandler(symbol: KtFunctionSymbol): InsertHandler { - return FunctionInsertionHandler( - symbol.name, - inputValueArguments = symbol.valueParameters.isNotEmpty(), - insertEmptyLambda = insertLambdaBraces(symbol) - ) + val functionFqName = symbol.callableIdIfNonLocal + + return if (functionFqName != null && canBeCalledByFqName(symbol)) { + ShorteningFunctionInsertionHandler( + functionFqName, + inputValueArguments = symbol.valueParameters.isNotEmpty(), + insertEmptyLambda = insertLambdaBraces(symbol) + ) + } else { + SimpleFunctionInsertionHandler( + symbol.name, + inputValueArguments = symbol.valueParameters.isNotEmpty(), + insertEmptyLambda = insertLambdaBraces(symbol) + ) + } + } + + private fun canBeCalledByFqName(symbol: KtFunctionSymbol): Boolean { + return !symbol.isExtension && symbol.dispatchType == null } companion object { @@ -128,23 +158,25 @@ private class FunctionLookupElementFactory { } /** - * A partial copy from `KotlinFunctionInsertHandler.Normal`. + * The simplest implementation of the insertion handler for a classifiers. */ -private class FunctionInsertionHandler( +private class ClassifierInsertionHandler(private val fqName: FqName) : InsertHandler { + override fun handleInsert(context: InsertionContext, item: LookupElement) { + val targetFile = context.file as? KtFile ?: return + + context.document.replaceString(context.startOffset, context.tailOffset, fqName.render()) + context.commitDocument() + + shortenReferences(targetFile, TextRange(context.startOffset, context.tailOffset)) + } +} + +private abstract class AbstractFunctionInsertionHandler( name: Name, private val inputValueArguments: Boolean, private val insertEmptyLambda: Boolean ) : QuotedNamesAwareInsertionHandler(name) { - override fun handleInsert(context: InsertionContext, item: LookupElement) { - super.handleInsert(context, item) - - val startOffset = context.startOffset - val element = context.file.findElementAt(startOffset) ?: return - - addArguments(context, element) - } - - private fun addArguments(context: InsertionContext, offsetElement: PsiElement) { + protected fun addArguments(context: InsertionContext, offsetElement: PsiElement) { val completionChar = context.completionChar if (completionChar == '(') { //TODO: more correct behavior related to braces type context.setAddCompletionChar(false) @@ -215,6 +247,47 @@ private class FunctionInsertionHandler( } } +private class SimpleFunctionInsertionHandler( + name: Name, + inputValueArguments: Boolean, + insertEmptyLambda: Boolean +) : AbstractFunctionInsertionHandler(name, inputValueArguments, insertEmptyLambda) { + override fun handleInsert(context: InsertionContext, item: LookupElement) { + super.handleInsert(context, item) + + val startOffset = context.startOffset + val element = context.file.findElementAt(startOffset) ?: return + + addArguments(context, element) + } +} + +private class ShorteningFunctionInsertionHandler( + private val name: FqName, + inputValueArguments: Boolean, + insertEmptyLambda: Boolean, +) : AbstractFunctionInsertionHandler(name.shortName(), inputValueArguments, insertEmptyLambda) { + override fun handleInsert(context: InsertionContext, item: LookupElement) { + val targetFile = context.file as? KtFile ?: return + super.handleInsert(context, item) + + val startOffset = context.startOffset + val element = context.file.findElementAt(startOffset) ?: return + + context.document.replaceString( + context.startOffset, + context.tailOffset, + name.withRootPrefixIfNeeded().render() + ) + context.commitDocument() + + addArguments(context, element) + context.commitDocument() + + shortenReferences(targetFile, TextRange(context.startOffset, context.tailOffset)) + } +} + private open class QuotedNamesAwareInsertionHandler(private val name: Name) : InsertHandler { override fun handleInsert(context: InsertionContext, item: LookupElement) { val startOffset = context.startOffset @@ -250,3 +323,19 @@ private fun CharSequence.indexOfSkippingSpace(c: Char, startIndex: Int): Int? { } return null } + +private fun shortenReferences(targetFile: KtFile, textRange: TextRange) { + val shortenings = withAllowedResolve { + analyze(targetFile) { + collectPossibleReferenceShortenings(targetFile, textRange) + } + } + + shortenings.invokeShortening() +} + +// FIXME: This is a hack, we should think how we can get rid of it +private inline fun withAllowedResolve(action: () -> T): T { + @OptIn(HackToForceAllowRunningAnalyzeOnEDT::class) + return hackyAllowRunningOnEdt(action) +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirModuleResolveStateForCompletion.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirModuleResolveStateForCompletion.kt index 0b120d530d9..075c55f5fc0 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirModuleResolveStateForCompletion.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirModuleResolveStateForCompletion.kt @@ -101,7 +101,7 @@ internal class FirModuleResolveStateForCompletion( @OptIn(InternalForInline::class) override fun findSourceFirDeclaration(ktDeclaration: KtDeclaration): FirDeclaration { - error("Should not be used in completion") + return originalState.findSourceFirDeclaration(ktDeclaration) } @OptIn(InternalForInline::class) diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/IndexHelper.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/IndexHelper.kt index 3c4a05e9d11..ede4ffcb5e8 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/IndexHelper.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/IndexHelper.kt @@ -5,17 +5,18 @@ package org.jetbrains.kotlin.idea.fir.low.level.api +import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.stubs.StubIndex import com.intellij.psi.stubs.StubIndexKey import org.jetbrains.kotlin.name.CallableId -import org.jetbrains.kotlin.idea.fir.low.level.api.IndexHelper.Companion.asStringForIndexes import org.jetbrains.kotlin.idea.stubindex.* import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtProperty @@ -72,6 +73,18 @@ public class IndexHelper(val project: Project, private val scope: GlobalSearchSc .get(packageFqName.asStringForIndexes(), project, scope) .mapNotNullTo(hashSetOf()) { it.nameAsName } + fun getKotlinClasses( + nameFilter: (Name) -> Boolean, + psiFilter: (element: KtClassOrObject) -> Boolean = { true } + ): Collection { + val index = KotlinFullClassNameIndex.getInstance() + return index.getAllKeys(project).asSequence() + .onEach { ProgressManager.checkCanceled() } + .filter { fqName -> nameFilter(getShortName(fqName)) } + .flatMap { fqName -> index[fqName, project, scope] } + .filter(psiFilter) + .toList() + } companion object { private fun CallableId.asStringForIndexes(): String = @@ -82,5 +95,7 @@ public class IndexHelper(val project: Project, private val scope: GlobalSearchSc private fun ClassId.asStringForIndexes(): String = asSingleFqName().asStringForIndexes() + + private fun getShortName(fqName: String) = Name.identifier(fqName.substringAfterLast('.')) } } \ No newline at end of file