From 88129c335487833aa07751b9f09d0a025af3020c Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Mon, 31 May 2021 17:31:11 +0200 Subject: [PATCH] FIR IDE: implement completion inside string templates --- .../completion/KotlinCompletionContributor.kt | 77 +++------------ .../inStringLiterals/StringTemplateDot2.kt | 1 + .../StringTemplateDotSomething.kt | 1 + .../handlers/basic/stringTemplate/1.kt | 1 + .../handlers/basic/stringTemplate/1.kt.after | 1 + .../handlers/basic/stringTemplate/2.kt | 1 + .../handlers/basic/stringTemplate/2.kt.after | 1 + .../basic/stringTemplate/AfterDot1.kt | 1 + .../basic/stringTemplate/AfterDot1.kt.after | 1 + .../basic/stringTemplate/AfterDot3.kt | 1 + .../basic/stringTemplate/AfterDot3.kt.after | 1 + .../basic/stringTemplate/AfterDot4.kt | 1 + .../basic/stringTemplate/AfterDot4.kt.after | 1 + .../basic/stringTemplate/AfterThisDot.kt | 1 + .../stringTemplate/AfterThisDot.kt.after | 1 + .../basic/stringTemplate/GlobalVal.kt | 1 + .../basic/stringTemplate/GlobalVal.kt.after | 1 + .../InsertCurlyBracesBeforeLetter.kt | 1 + .../InsertCurlyBracesBeforeLetter.kt.after | 1 + .../basic/stringTemplate/NotEmptyPrefix.kt | 1 + .../stringTemplate/NotEmptyPrefix.kt.after | 1 + .../handlers/basic/stringTemplate/Replace.kt | 1 + .../basic/stringTemplate/Replace.kt.after | 1 + .../TopLevelValImportInStringTemplate-1.kt | 1 + .../KotlinFirCompletionContributor.kt | 35 ++++++- .../KotlinFirCompletionParameters.kt | 45 +++++++++ .../idea/completion/LookupElementSink.kt | 99 +++++++++++++++++++ .../idea/completion/LookupElementWrapper.kt | 16 +++ .../context/FirBasicCompletionContext.kt | 9 +- .../FirCompletionContributorBase.kt | 9 +- .../FirKeywordCompletionContributor.kt | 2 +- .../FirPackageCompletionContributor.kt | 2 +- ...FileClassifierNameCompletionContributor.kt | 2 +- .../contributors/FirSuperEntryContributor.kt | 2 +- ...tNameInWhereClauseCompletionContributor.kt | 2 +- .../FirWhenWithSubjectConditionContributor.kt | 6 +- .../factories/ClassLookupElementFactory.kt | 2 +- .../factories/FunctionLookupElementFactory.kt | 2 - .../factories/VariableLookupElementFactory.kt | 1 - ...ompletionDummyIdentifierProviderService.kt | 21 ++++ ...ingTemplateBracesLookupElementDecorator.kt | 46 +++++++++ .../StringTemplateCompletion.kt | 38 +++++++ .../kotlin/test/utils/IgnoreTests.kt | 2 +- 43 files changed, 350 insertions(+), 91 deletions(-) create mode 100644 idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionParameters.kt create mode 100644 idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/LookupElementSink.kt create mode 100644 idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/LookupElementWrapper.kt create mode 100644 idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/stringTemplates/InsertStringTemplateBracesLookupElementDecorator.kt create mode 100644 idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/stringTemplates/StringTemplateCompletion.kt diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt index ced95e3e633..52eb856ea2e 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt @@ -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().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) { - 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 diff --git a/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDot2.kt b/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDot2.kt index d22c70f4577..b3deb99b274 100644 --- a/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDot2.kt +++ b/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDot2.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON class Foo(val bar: Bar) class Bar(val baz: String) diff --git a/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDotSomething.kt b/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDotSomething.kt index c2ede404cee..b545e7fd821 100644 --- a/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDotSomething.kt +++ b/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDotSomething.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun foo(param: String) { val s = "$param.lbla-bla-bla" } diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/1.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/1.kt index b059b941b48..2aae13dcf18 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/1.kt +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/1.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON package ppp fun foo(): String { diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/1.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/1.kt.after index d71d69bb93f..cb14fe01093 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/1.kt.after +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/1.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON package ppp fun foo(): String { diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/2.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/2.kt index 119f4bb7a90..c7a001fa0c9 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/2.kt +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/2.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON package ppp fun foo(p: String): String { diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/2.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/2.kt.after index 058bf96456c..4b28c43be76 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/2.kt.after +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/2.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON package ppp fun foo(p: String): String { diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt index 88f219f89e5..4bfe8c5b66f 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun foo(param: String) { val s = "$param.bla-bla-bla" } diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt.after index d322af287f4..e69a8ca805e 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt.after +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun foo(param: String) { val s = "${param.equals()}bla-bla-bla" } diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt index 1ded59365fb..f12f81724d7 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun foo(param: String) { val s = "$param.eqbla-bla-bla" } diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt.after index d322af287f4..e69a8ca805e 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt.after +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun foo(param: String) { val s = "${param.equals()}bla-bla-bla" } diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot4.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot4.kt index 0c2e683f55a..08df021053a 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot4.kt +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot4.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON class Foo(val bar: Bar) class Bar(val baz: String) diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot4.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot4.kt.after index 23a3a65ee84..0a14ccb7c38 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot4.kt.after +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot4.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON class Foo(val bar: Bar) class Bar(val baz: String) diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterThisDot.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterThisDot.kt index e71d19566e8..afe115c775d 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterThisDot.kt +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterThisDot.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun String.foo() { val s = "$this." } diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterThisDot.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterThisDot.kt.after index 2830314b149..06bec3a5155 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterThisDot.kt.after +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterThisDot.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun String.foo() { val s = "${this.equals()}" } diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/GlobalVal.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/GlobalVal.kt index 22b0fbbba65..a53f9975bf9 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/GlobalVal.kt +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/GlobalVal.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON package p val vvv = 1 diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/GlobalVal.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/GlobalVal.kt.after index 01f13022e12..c5af602c636 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/GlobalVal.kt.after +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/GlobalVal.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON package p val vvv = 1 diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/InsertCurlyBracesBeforeLetter.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/InsertCurlyBracesBeforeLetter.kt index f9f840d571e..ba488f95c2b 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/InsertCurlyBracesBeforeLetter.kt +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/InsertCurlyBracesBeforeLetter.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON package p val vvv = 1 diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/InsertCurlyBracesBeforeLetter.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/InsertCurlyBracesBeforeLetter.kt.after index 955aae82b1e..d6663cfdebf 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/InsertCurlyBracesBeforeLetter.kt.after +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/InsertCurlyBracesBeforeLetter.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON package p val vvv = 1 diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/NotEmptyPrefix.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/NotEmptyPrefix.kt index f68380e7fd6..6f16e3d5f7d 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/NotEmptyPrefix.kt +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/NotEmptyPrefix.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun bar() {} fun test(){ diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/NotEmptyPrefix.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/NotEmptyPrefix.kt.after index b546ad5d4c2..814fa497785 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/NotEmptyPrefix.kt.after +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/NotEmptyPrefix.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun bar() {} fun test(){ diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/Replace.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/Replace.kt index 59cee4df8d8..0377313b8e0 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/Replace.kt +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/Replace.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun foo(): String { val s = "$xxx" } diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/Replace.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/Replace.kt.after index 2358c2ba92d..b5ffc2d4e5b 100644 --- a/idea/idea-completion/testData/handlers/basic/stringTemplate/Replace.kt.after +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/Replace.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun foo(): String { val s = "${foo()}" } diff --git a/idea/idea-completion/testData/handlers/multifile/TopLevelValImportInStringTemplate-1.kt b/idea/idea-completion/testData/handlers/multifile/TopLevelValImportInStringTemplate-1.kt index cde36a1312e..39fd1e01cc9 100644 --- a/idea/idea-completion/testData/handlers/multifile/TopLevelValImportInStringTemplate-1.kt +++ b/idea/idea-completion/testData/handlers/multifile/TopLevelValImportInStringTemplate-1.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON package some fun other() { 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 2c595e69c4f..628106f8187 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 @@ -37,14 +37,29 @@ class KotlinFirCompletionContributor : CompletionContributor() { val psiFile = context.file if (psiFile !is KtFile) return - context.dummyIdentifier = service().provideDummyIdentifier(context) + val identifierProviderService = service() + + 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() { 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? { + 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 + } +} + diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/LookupElementSink.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/LookupElementSink.kt new file mode 100644 index 00000000000..119c8c9675c --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/LookupElementSink.kt @@ -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) { + 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 { + return when (parameters.type) { + KotlinFirCompletionParameters.CorrectionType.BRACES_FOR_STRING_TEMPLATE -> { + listOf(LookupElementWrapper(::InsertStringTemplateBracesLookupElementDecorator)) + } + else -> listOf(LookupElementWrapper(::WrapSingleStringTemplateEntryWithBraces)) + } + } +} + +private class WrapSingleStringTemplateEntryWithBraces(lookupElement: LookupElement) : LookupElementDecorator(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 + } +} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/LookupElementWrapper.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/LookupElementWrapper.kt new file mode 100644 index 00000000000..471ea83e619 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/LookupElementWrapper.kt @@ -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.wrap(element: LookupElement): LookupElement = + fold(element) { currentElement, wrapper -> wrapper.wrap(currentElement) } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirBasicCompletionContext.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirBasicCompletionContext.kt index 6c0a3899c26..90355f3bd6f 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirBasicCompletionContext.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirBasicCompletionContext.kt @@ -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, diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCompletionContributorBase.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCompletionContributorBase.kt index a469b41a1e9..320466148c3 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCompletionContributorBase.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCompletionContributorBase.kt @@ -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(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 createLookupElement(symbol) } } ?: return - result.addElement(lookup) + sink.addElement(lookup) } protected fun KtAnalysisSession.addCallableSymbolToCompletion( @@ -74,7 +75,7 @@ internal abstract class FirCompletionContributorBase - result.addElement(lookupElementFactory.createPackagePartLookupElement(packageSymbol.fqName)) + sink.addElement(lookupElementFactory.createPackagePartLookupElement(packageSymbol.fqName)) } } } \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSameAsFileClassifierNameCompletionContributor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSameAsFileClassifierNameCompletionContributor.kt index 75d5f5869ca..7ae34d210ec 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSameAsFileClassifierNameCompletionContributor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSameAsFileClassifierNameCompletionContributor.kt @@ -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) = diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSuperEntryContributor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSuperEntryContributor.kt index c6f315b5e92..5bc7fadd361 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSuperEntryContributor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirSuperEntryContributor.kt @@ -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) } } } } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirTypeParameterConstraintNameInWhereClauseCompletionContributor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirTypeParameterConstraintNameInWhereClauseCompletionContributor.kt index 62f0fb2088c..8b09ec3442c 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirTypeParameterConstraintNameInWhereClauseCompletionContributor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirTypeParameterConstraintNameInWhereClauseCompletionContributor.kt @@ -33,7 +33,7 @@ internal class FirTypeParameterConstraintNameInWhereClauseCompletionContributor( .withInsertHandler(TypeParameterInWhenClauseInsertionHandler) .withPsiElement(typeParameter.psi) .withIcon(icon) - .let(result::addElement) + .let(sink::addElement) } } } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirWhenWithSubjectConditionContributor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirWhenWithSubjectConditionContributor.kt index d15f13a0c4a..af31bf522df 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirWhenWithSubjectConditionContributor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirWhenWithSubjectConditionContributor.kt @@ -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) } } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/ClassLookupElementFactory.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/ClassLookupElementFactory.kt index f9e035cdd7c..077a5472200 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/ClassLookupElementFactory.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/ClassLookupElementFactory.kt @@ -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. diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/FunctionLookupElementFactory.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/FunctionLookupElementFactory.kt index b49d542f186..5d1f5f17dc9 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/FunctionLookupElementFactory.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/FunctionLookupElementFactory.kt @@ -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 diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/VariableLookupElementFactory.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/VariableLookupElementFactory.kt index 714b502a540..370c8af9dc5 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/VariableLookupElementFactory.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/VariableLookupElementFactory.kt @@ -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 diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/CompletionDummyIdentifierProviderService.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/CompletionDummyIdentifierProviderService.kt index 805e939ae72..606763f8426 100644 --- a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/CompletionDummyIdentifierProviderService.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/CompletionDummyIdentifierProviderService.kt @@ -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) { diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/stringTemplates/InsertStringTemplateBracesLookupElementDecorator.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/stringTemplates/InsertStringTemplateBracesLookupElementDecorator.kt new file mode 100644 index 00000000000..bdab18f17ae --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/stringTemplates/InsertStringTemplateBracesLookupElementDecorator.kt @@ -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) { + 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) + } +} diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/stringTemplates/StringTemplateCompletion.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/stringTemplates/StringTemplateCompletion.kt new file mode 100644 index 00000000000..65b093bd0ff --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/stringTemplates/StringTemplateCompletion.kt @@ -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 + } + +} \ No newline at end of file diff --git a/idea/idea-frontend-independent/tests/org/jetbrains/kotlin/test/utils/IgnoreTests.kt b/idea/idea-frontend-independent/tests/org/jetbrains/kotlin/test/utils/IgnoreTests.kt index 034d0b6a1f9..aeb1e64bf20 100644 --- a/idea/idea-frontend-independent/tests/org/jetbrains/kotlin/test/utils/IgnoreTests.kt +++ b/idea/idea-frontend-independent/tests/org/jetbrains/kotlin/test/utils/IgnoreTests.kt @@ -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(