FIR IDE: implement completion inside string templates

This commit is contained in:
Ilya Kirillov
2021-05-31 17:31:11 +02:00
committed by TeamCityServer
parent 59b22fadad
commit 88129c3354
43 changed files with 350 additions and 91 deletions
@@ -18,6 +18,8 @@ import com.intellij.psi.PsiComment
import com.intellij.util.ProcessingContext
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletionSession
import org.jetbrains.kotlin.idea.completion.stringTemplates.InsertStringTemplateBracesLookupElementDecorator
import org.jetbrains.kotlin.idea.completion.stringTemplates.StringTemplateCompletion
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector.completionStatsData
import org.jetbrains.kotlin.idea.statistics.CompletionTypeStats
import org.jetbrains.kotlin.idea.statistics.FileTypeStats
@@ -55,28 +57,17 @@ class KotlinCompletionContributor : CompletionContributor() {
}
override fun beforeCompletion(context: CompletionInitializationContext) {
val offset = context.startOffset
val psiFile = context.file
if (psiFile !is KtFile) return
val tokenBefore = psiFile.findElementAt(max(0, offset - 1))
// this code will make replacement offset "modified" and prevents altering it by the code in CompletionProgressIndicator
context.replacementOffset = context.replacementOffset
val offset = context.startOffset
val tokenBefore = psiFile.findElementAt(max(0, offset - 1))
if (offset > 0 && tokenBefore!!.node.elementType == KtTokens.REGULAR_STRING_PART && tokenBefore.text.startsWith(".")) {
val prev = tokenBefore.parent.prevSibling
if (prev != null && prev is KtSimpleNameStringTemplateEntry) {
val expression = prev.expression
if (expression != null) {
val prefix = tokenBefore.text.substring(0, offset - tokenBefore.startOffset)
context.dummyIdentifier = "{" + expression.text + prefix + CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + "}"
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, expression.startOffset)
return
}
}
val dummyIdentifierCorrected = service<CompletionDummyIdentifierProviderService>().correctPositionForStringTemplateEntry(context)
if (dummyIdentifierCorrected) {
return
}
context.dummyIdentifier = when {
context.completionType == CompletionType.SMART -> DEFAULT_DUMMY_IDENTIFIER
@@ -156,24 +147,10 @@ class KotlinCompletionContributor : CompletionContributor() {
val toFromOriginalFileMapper = ToFromOriginalFileMapper.create(parameters)
if (position.node.elementType == KtTokens.LONG_TEMPLATE_ENTRY_START) {
val expression = (position.parent as? KtBlockStringTemplateEntry)?.expression
if (expression is KtDotQualifiedExpression) {
val correctedPosition = (expression.selectorExpression as? KtNameReferenceExpression)?.firstChild
if (correctedPosition != null) {
// Workaround for KT-16848
// ex:
// expression: some.IntellijIdeaRulezzz
// correctedOffset: ^
// expression: some.funcIntellijIdeaRulezzz
// correctedOffset ^
val correctedOffset = correctedPosition.endOffset - CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED.length
val correctedParameters = parameters.withPosition(correctedPosition, correctedOffset)
doComplete(correctedParameters, toFromOriginalFileMapper, result,
lookupElementPostProcessor = { wrapLookupElementForStringTemplateAfterDotCompletion(it) })
return
}
}
StringTemplateCompletion.correctParametersForInStringTemplateCompletion(parameters)?.let { correctedParameters ->
doComplete(correctedParameters, toFromOriginalFileMapper, result,
lookupElementPostProcessor = { InsertStringTemplateBracesLookupElementDecorator(it) })
return
}
doComplete(parameters, toFromOriginalFileMapper, result)
@@ -257,38 +234,6 @@ class KotlinCompletionContributor : CompletionContributor() {
}
}
private fun wrapLookupElementForStringTemplateAfterDotCompletion(lookupElement: LookupElement): LookupElement {
return object : LookupElementDecorator<LookupElement>(lookupElement) {
override fun handleInsert(context: InsertionContext) {
val document = context.document
val startOffset = context.startOffset
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
psiDocumentManager.commitAllDocuments()
val token = getToken(context.file, document.charsSequence, startOffset)
val nameRef = token.parent as KtNameReferenceExpression
document.insertString(nameRef.startOffset, "{")
val tailOffset = context.tailOffset
document.insertString(tailOffset, "}")
context.tailOffset = tailOffset
super.handleInsert(context)
}
private fun getToken(file: PsiFile, charsSequence: CharSequence, startOffset: Int): PsiElement {
assert(startOffset > 1 && charsSequence[startOffset - 1] == '.')
val token = file.findElementAt(startOffset - 2)!!
return if (token.node.elementType == KtTokens.IDENTIFIER || token.node.elementType == KtTokens.THIS_KEYWORD)
token
else
getToken(file, charsSequence, token.startOffset + 1)
}
}
}
private fun shouldSuppressCompletion(parameters: CompletionParameters, prefixMatcher: PrefixMatcher): Boolean {
val position = parameters.position
val invocationCount = parameters.invocationCount
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class Foo(val bar: Bar)
class Bar(val baz: String)
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun foo(param: String) {
val s = "$param.l<caret>bla-bla-bla"
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package ppp
fun foo(): String {
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package ppp
fun foo(): String {
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package ppp
fun foo(p: String): String {
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package ppp
fun foo(p: String): String {
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun foo(param: String) {
val s = "$param.<caret>bla-bla-bla"
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun foo(param: String) {
val s = "${param.equals(<caret>)}bla-bla-bla"
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun foo(param: String) {
val s = "$param.eq<caret>bla-bla-bla"
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun foo(param: String) {
val s = "${param.equals(<caret>)}bla-bla-bla"
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class Foo(val bar: Bar)
class Bar(val baz: String)
@@ -1,3 +1,4 @@
// FIR_COMPARISON
class Foo(val bar: Bar)
class Bar(val baz: String)
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun String.foo() {
val s = "$this.<caret>"
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun String.foo() {
val s = "${this.equals(<caret>)}"
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package p
val vvv = 1
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package p
val vvv = 1
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package p
val vvv = 1
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package p
val vvv = 1
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun bar() {}
fun test(){
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun bar() {}
fun test(){
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun foo(): String {
val s = "$<caret>xxx"
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun foo(): String {
val s = "${foo()<caret>}"
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
package some
fun other() {
@@ -37,14 +37,29 @@ class KotlinFirCompletionContributor : CompletionContributor() {
val psiFile = context.file
if (psiFile !is KtFile) return
context.dummyIdentifier = service<CompletionDummyIdentifierProviderService>().provideDummyIdentifier(context)
val identifierProviderService = service<CompletionDummyIdentifierProviderService>()
correctPositionAndDummyIdentifier(identifierProviderService, context)
}
private fun correctPositionAndDummyIdentifier(
identifierProviderService: CompletionDummyIdentifierProviderService,
context: CompletionInitializationContext
) {
val dummyIdentifierCorrected = identifierProviderService.correctPositionForStringTemplateEntry(context)
if (dummyIdentifierCorrected) {
return
}
context.dummyIdentifier = identifierProviderService.provideDummyIdentifier(context)
}
}
private object KotlinFirCompletionProvider : CompletionProvider<CompletionParameters>() {
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
if (shouldSuppressCompletion(parameters, result.prefixMatcher)) return
@Suppress("NAME_SHADOWING") val parameters = KotlinFirCompletionParametersProvider.provide(parameters)
if (shouldSuppressCompletion(parameters.ijParameters, result.prefixMatcher)) return
val resultSet = createResultSet(parameters, result)
val basicContext = FirBasicCompletionContext.createFromParameters(parameters, resultSet) ?: return
@@ -68,7 +83,8 @@ private object KotlinFirCompletionProvider : CompletionProvider<CompletionParame
val annotationsContributor = FirAnnotationCompletionContributor(basicContext)
val packageCompletionContributor = FirPackageCompletionContributor(basicContext)
val importDirectivePackageMembersCompletionContributor = FirImportDirectivePackageMembersCompletionContributor(basicContext)
val typeParameterConstraintNameInWhereClauseContributor = FirTypeParameterConstraintNameInWhereClauseCompletionContributor(basicContext)
val typeParameterConstraintNameInWhereClauseContributor =
FirTypeParameterConstraintNameInWhereClauseCompletionContributor(basicContext)
val classifierNameContributor = FirSameAsFileClassifierNameCompletionContributor(basicContext)
val whenWithSubjecConditionContributor = FirWhenWithSubjectConditionContributor(basicContext)
@@ -134,8 +150,17 @@ private object KotlinFirCompletionProvider : CompletionProvider<CompletionParame
fakeFile.originalKtFile = originalFile
}
private fun createResultSet(parameters: CompletionParameters, result: CompletionResultSet): CompletionResultSet =
result.withRelevanceSorter(createSorter(parameters, result))
private fun createResultSet(parameters: KotlinFirCompletionParameters, result: CompletionResultSet): CompletionResultSet {
@Suppress("NAME_SHADOWING") var result = result.withRelevanceSorter(createSorter(parameters.ijParameters, result))
if (parameters is KotlinFirCompletionParameters.Corrected) {
val replaced = parameters.ijParameters
@Suppress("UnstableApiUsage", "DEPRECATION")
val originalPrefix = CompletionData.findPrefixStatic(replaced.position, replaced.offset)
result = result.withPrefixMatcher(originalPrefix)
}
return result
}
private fun createSorter(parameters: CompletionParameters, result: CompletionResultSet): CompletionSorter =
CompletionSorter.defaultSorter(parameters, result.prefixMatcher)
@@ -0,0 +1,45 @@
/*
* 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
import com.intellij.codeInsight.completion.CompletionParameters
import org.jetbrains.kotlin.idea.completion.stringTemplates.StringTemplateCompletion
internal object KotlinFirCompletionParametersProvider {
fun provide(parameters: CompletionParameters): KotlinFirCompletionParameters {
val (corrected, type) = correctParameters(parameters) ?: return KotlinFirCompletionParameters.Original(parameters)
return KotlinFirCompletionParameters.Corrected(corrected, parameters, type)
}
private fun correctParameters(parameters: CompletionParameters): Pair<CompletionParameters, KotlinFirCompletionParameters.CorrectionType>? {
val correctParametersForInStringTemplateCompletion =
StringTemplateCompletion.correctParametersForInStringTemplateCompletion(parameters)
?: return null
return correctParametersForInStringTemplateCompletion to KotlinFirCompletionParameters.CorrectionType.BRACES_FOR_STRING_TEMPLATE
}
}
internal sealed class KotlinFirCompletionParameters {
abstract val ijParameters: CompletionParameters
abstract val type: CorrectionType?
internal class Original(
override val ijParameters: CompletionParameters,
) : KotlinFirCompletionParameters() {
override val type: CorrectionType? get() = null
}
internal class Corrected(
override val ijParameters: CompletionParameters,
val original: CompletionParameters,
override val type: CorrectionType,
) : KotlinFirCompletionParameters()
enum class CorrectionType {
BRACES_FOR_STRING_TEMPLATE
}
}
@@ -0,0 +1,99 @@
/*
* 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
import com.intellij.codeInsight.completion.CompletionInitializationContext
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.openapi.editor.Document
import com.intellij.psi.util.elementType
import org.jetbrains.kotlin.idea.completion.stringTemplates.InsertStringTemplateBracesLookupElementDecorator
import org.jetbrains.kotlin.idea.core.canDropBraces
import org.jetbrains.kotlin.idea.core.dropBraces
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBlockStringTemplateEntry
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtSimpleNameStringTemplateEntry
import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression
internal class LookupElementSink(
private val resultSet: CompletionResultSet,
private val parameters: KotlinFirCompletionParameters,
) {
fun addElement(element: LookupElement) {
resultSet.addElement(applyWrappersToLookupElement(element))
}
fun addAllElements(elements: Iterable<LookupElement>) {
resultSet.addAllElements(elements.map(::applyWrappersToLookupElement))
}
private fun applyWrappersToLookupElement(lookupElement: LookupElement): LookupElement {
val wrappers = WrappersProvider.getWrapperForLookupElement(parameters)
return wrappers.wrap(lookupElement)
}
}
private object WrappersProvider {
fun getWrapperForLookupElement(parameters: KotlinFirCompletionParameters): List<LookupElementWrapper> {
return when (parameters.type) {
KotlinFirCompletionParameters.CorrectionType.BRACES_FOR_STRING_TEMPLATE -> {
listOf(LookupElementWrapper(::InsertStringTemplateBracesLookupElementDecorator))
}
else -> listOf(LookupElementWrapper(::WrapSingleStringTemplateEntryWithBraces))
}
}
}
private class WrapSingleStringTemplateEntryWithBraces(lookupElement: LookupElement) : LookupElementDecorator<LookupElement>(lookupElement) {
override fun handleInsert(context: InsertionContext) {
val document = context.document
context.commitDocument()
if (needInsertBraces(context)) {
insertBraces(context, document)
context.commitDocument()
super.handleInsert(context)
removeUnneededBraces(context)
context.commitDocument()
} else {
super.handleInsert(context)
}
}
private fun insertBraces(context: InsertionContext, document: Document) {
val startOffset = context.startOffset
document.insertString(context.startOffset, "{")
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, startOffset + 1)
val tailOffset = context.tailOffset
document.insertString(tailOffset, "}")
context.tailOffset = tailOffset
}
private fun removeUnneededBraces(context: InsertionContext) {
val templateEntry = getContainingTemplateEntry(context) as? KtBlockStringTemplateEntry ?: return
if (templateEntry.canDropBraces()) {
templateEntry.dropBraces()
}
}
private fun needInsertBraces(context: InsertionContext): Boolean =
getContainingTemplateEntry(context) is KtSimpleNameStringTemplateEntry
private fun getContainingTemplateEntry(context: InsertionContext): KtStringTemplateEntryWithExpression? {
val file = context.file
val element = file.findElementAt(context.startOffset) ?: return null
if (element.elementType != KtTokens.IDENTIFIER) return null
val identifier = element.parent as? KtNameReferenceExpression ?: return null
return identifier.parent as? KtStringTemplateEntryWithExpression
}
}
@@ -0,0 +1,16 @@
/*
* 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
import com.intellij.codeInsight.lookup.LookupElement
internal fun interface LookupElementWrapper {
fun wrap(element: LookupElement): LookupElement
}
internal fun List<LookupElementWrapper>.wrap(element: LookupElement): LookupElement =
fold(element) { currentElement, wrapper -> wrapper.wrap(currentElement) }
@@ -11,6 +11,8 @@ import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.idea.completion.KotlinFirCompletionParameters
import org.jetbrains.kotlin.idea.completion.LookupElementSink
import org.jetbrains.kotlin.idea.completion.lookups.factories.KotlinFirLookupElementFactory
import org.jetbrains.kotlin.idea.fir.low.level.api.IndexHelper
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
@@ -20,7 +22,7 @@ import org.jetbrains.kotlin.psi.KtFile
internal class FirBasicCompletionContext(
val parameters: CompletionParameters,
val result: CompletionResultSet,
val sink: LookupElementSink,
val prefixMatcher: PrefixMatcher,
val originalKtFile: KtFile,
val fakeKtFile: KtFile,
@@ -33,8 +35,9 @@ internal class FirBasicCompletionContext(
val moduleInfo: IdeaModuleInfo = originalKtFile.getModuleInfo()
companion object {
fun createFromParameters(parameters: CompletionParameters, result: CompletionResultSet): FirBasicCompletionContext? {
fun createFromParameters(firParameters: KotlinFirCompletionParameters, result: CompletionResultSet): FirBasicCompletionContext? {
val prefixMatcher = result.prefixMatcher
val parameters = firParameters.ijParameters
val originalKtFile = parameters.originalFile as? KtFile ?: return null
val fakeKtFile = parameters.position.containingFile as? KtFile ?: return null
val targetPlatform = TargetPlatformDetector.getPlatform(originalKtFile)
@@ -42,7 +45,7 @@ internal class FirBasicCompletionContext(
val indexHelper = createIndexHelper(parameters)
return FirBasicCompletionContext(
parameters,
result,
LookupElementSink(result, firParameters),
prefixMatcher,
originalKtFile,
fakeKtFile,
@@ -10,6 +10,7 @@ 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
@@ -31,7 +32,7 @@ import org.jetbrains.kotlin.psi.KtFile
internal abstract class FirCompletionContributorBase<C : FirRawPositionCompletionContext>(protected val basicContext: FirBasicCompletionContext) {
protected val prefixMatcher: PrefixMatcher get() = basicContext.prefixMatcher
protected val parameters: CompletionParameters get() = basicContext.parameters
protected val result: CompletionResultSet get() = basicContext.result
protected val sink: LookupElementSink get() = basicContext.sink
protected val originalKtFile: KtFile get() = basicContext.originalKtFile
protected val fakeKtFile: KtFile get() = basicContext.fakeKtFile
protected val project: Project get() = basicContext.project
@@ -50,7 +51,7 @@ internal abstract class FirCompletionContributorBase<C : FirRawPositionCompletio
with(lookupElementFactory) {
createLookupElement(symbol)
?.let { applyWeighers(it, symbol, expectedType) }
?.let(result::addElement)
?.let(sink::addElement)
}
}
@@ -62,7 +63,7 @@ internal abstract class FirCompletionContributorBase<C : FirRawPositionCompletio
is KtTypeParameterSymbol -> createLookupElement(symbol)
}
} ?: return
result.addElement(lookup)
sink.addElement(lookup)
}
protected fun KtAnalysisSession.addCallableSymbolToCompletion(
@@ -74,7 +75,7 @@ internal abstract class FirCompletionContributorBase<C : FirRawPositionCompletio
val lookup = with(lookupElementFactory) {
createCallableLookupElement(symbol, importingStrategy, insertionStrategy)
} ?: return
result.addElement(lookup)
sink.addElement(lookup)
}
protected fun KtExpression.reference() = when (this) {
@@ -63,7 +63,7 @@ internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionC
createLookups(parameters, expression, lookupElement, project)
}
?: listOf(lookupElement)
result.addAllElements(lookups)
sink.addAllElements(lookups)
}
}
@@ -29,7 +29,7 @@ internal class FirPackageCompletionContributor(
packageName.fqName.isExcludedFromAutoImport(project, originalKtFile, originalKtFile.languageVersionSettings)
}
.forEach { packageSymbol ->
result.addElement(lookupElementFactory.createPackagePartLookupElement(packageSymbol.fqName))
sink.addElement(lookupElementFactory.createPackagePartLookupElement(packageSymbol.fqName))
}
}
}
@@ -28,7 +28,7 @@ internal class FirSameAsFileClassifierNameCompletionContributor(
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))
sink.addElement(LookupElementBuilder.create(name))
}
private fun isValidUpperCapitalizedClassName(name: String) =
@@ -25,7 +25,7 @@ internal class FirSuperEntryContributor(
LookupElementBuilder.create(SuperLookupObject(superType.name, superType.classIdIfNonLocal), superType.name.asString())
.withTailText(tailText)
.withInsertHandler(SuperCallInsertionHandler)
.let { result.addElement(it) }
.let { sink.addElement(it) }
}
}
}
@@ -33,7 +33,7 @@ internal class FirTypeParameterConstraintNameInWhereClauseCompletionContributor(
.withInsertHandler(TypeParameterInWhenClauseInsertionHandler)
.withPsiElement(typeParameter.psi)
.withIcon(icon)
.let(result::addElement)
.let(sink::addElement)
}
}
}
@@ -68,7 +68,7 @@ internal class FirWhenWithSubjectConditionContributor(
private fun KtAnalysisSession.addNullIfWhenExpressionCanReturnNull(type: KtType?) {
if (type?.canBeNull == true) {
val lookupElement = createKeywordElement(keyword = KtTokens.NULL_KEYWORD.value)
result.addElement(lookupElement)
sink.addElement(lookupElement)
}
}
@@ -155,7 +155,7 @@ internal class FirWhenWithSubjectConditionContributor(
val whenEntry = whenCondition.parent as? KtWhenEntry ?: return
if (whenEntry.conditions.size > 1) return
val lookupElement = createKeywordElement(keyword = KtTokens.ELSE_KEYWORD.value, tail = " -> ")
result.addElement(lookupElement)
sink.addElement(lookupElement)
}
@@ -191,7 +191,7 @@ internal class FirWhenWithSubjectConditionContributor(
.withIcon(getIconFor(symbol))
.withPsiElement(symbol.psi)
.withInsertHandler(WhenConditionInsertionHandler)
.let(result::addElement)
.let(sink::addElement)
}
}
@@ -35,7 +35,7 @@ private data class ClassifierLookupObject(
override val shortName: Name,
val classId: ClassId?,
val insertFqName: Boolean
) : KotlinLookupObject
) : KotlinLookupObject {}
/**
* The simplest implementation of the insertion handler for a classifiers.
@@ -18,14 +18,12 @@ import org.jetbrains.kotlin.idea.completion.lookups.*
import org.jetbrains.kotlin.idea.completion.lookups.CallableImportStrategy
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionStrategy
import org.jetbrains.kotlin.idea.completion.lookups.CompletionShortNamesRenderer
import org.jetbrains.kotlin.idea.completion.lookups.KotlinLookupObject
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.idea.frontend.api.types.KtFunctionalType
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
@@ -12,7 +12,6 @@ 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.KotlinLookupObject
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
@@ -19,6 +19,27 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import kotlin.math.max
abstract class CompletionDummyIdentifierProviderService {
fun correctPositionForStringTemplateEntry(context: CompletionInitializationContext): Boolean {
val offset = context.startOffset
val psiFile = context.file
val tokenBefore = psiFile.findElementAt(max(0, offset - 1))
if (offset > 0 && tokenBefore!!.node.elementType == KtTokens.REGULAR_STRING_PART && tokenBefore.text.startsWith(".")) {
val prev = tokenBefore.parent.prevSibling
if (prev != null && prev is KtSimpleNameStringTemplateEntry) {
val expression = prev.expression
if (expression != null) {
val prefix = tokenBefore.text.substring(0, offset - tokenBefore.startOffset)
context.dummyIdentifier = "{" + expression.text + prefix + CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + "}"
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, expression.startOffset)
return true
}
}
}
return false
}
fun provideDummyIdentifier(context: CompletionInitializationContext): String {
val psiFile = context.file
if (psiFile !is KtFile) {
@@ -0,0 +1,46 @@
/*
* 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.stringTemplates
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.startOffset
class InsertStringTemplateBracesLookupElementDecorator(lookupElement: LookupElement) : LookupElementDecorator<LookupElement>(lookupElement) {
override fun handleInsert(context: InsertionContext) {
val document = context.document
val startOffset = context.startOffset
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
psiDocumentManager.commitAllDocuments()
val token = getToken(context.file, document.charsSequence, startOffset)
val nameRef = token.parent as KtNameReferenceExpression
document.insertString(nameRef.startOffset, "{")
val tailOffset = context.tailOffset
document.insertString(tailOffset, "}")
context.tailOffset = tailOffset
super.handleInsert(context)
}
private fun getToken(file: PsiFile, charsSequence: CharSequence, startOffset: Int): PsiElement {
assert(startOffset > 1 && charsSequence[startOffset - 1] == '.')
val token = file.findElementAt(startOffset - 2)!!
return if (token.node.elementType == KtTokens.IDENTIFIER || token.node.elementType == KtTokens.THIS_KEYWORD)
token
else
getToken(file, charsSequence, token.startOffset + 1)
}
}
@@ -0,0 +1,38 @@
/*
* 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.stringTemplates
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionUtilCore
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBlockStringTemplateEntry
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.endOffset
object StringTemplateCompletion {
fun correctParametersForInStringTemplateCompletion(parameters: CompletionParameters): CompletionParameters? {
val position = parameters.position
if (position.node.elementType == KtTokens.LONG_TEMPLATE_ENTRY_START) {
val expression = (position.parent as? KtBlockStringTemplateEntry)?.expression
if (expression is KtDotQualifiedExpression) {
val correctedPosition = (expression.selectorExpression as? KtNameReferenceExpression)?.firstChild
if (correctedPosition != null) {
// Workaround for KT-16848
// ex:
// expression: some.IntellijIdeaRulezzz
// correctedOffset: ^
// expression: some.funcIntellijIdeaRulezzz
// correctedOffset ^
val correctedOffset = correctedPosition.endOffset - CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED.length
return parameters.withPosition(correctedPosition, correctedOffset)
}
}
}
return null
}
}
@@ -14,7 +14,7 @@ import java.nio.file.Files
import java.nio.file.Path
object IgnoreTests {
private const val INSERT_DIRECTIVE_AUTOMATICALLY = false // TODO use environment variable instead
private const val INSERT_DIRECTIVE_AUTOMATICALLY = true // TODO use environment variable instead
private const val ALWAYS_CONSIDER_TEST_AS_PASSING = false // TODO use environment variable instead
fun runTestIfEnabledByFileDirective(