diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/InterpolatedStringInjectorProcessor.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/InterpolatedStringInjectorProcessor.kt index 906bdddd821..8d3962554d7 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/InterpolatedStringInjectorProcessor.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/InterpolatedStringInjectorProcessor.kt @@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.injection import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.Trinity -import com.intellij.psi.PsiElement import com.intellij.psi.PsiLanguageInjectionHost import org.intellij.plugins.intelliLang.inject.InjectedLanguage import org.intellij.plugins.intelliLang.inject.InjectorUtils @@ -48,59 +47,66 @@ fun splitLiteralToInjectionParts(injection: BaseInjection, literal: KtStringTemp result.add(Trinity.create(literal, injectedLanguage, range)) } + fun suffix(i: Int) = if (i == len - 1) injection.suffix else "" + var unparsable = false var prefix = injection.prefix - val lastChild = children.lastOrNull() var i = 0 while (i < len) { val child = children[i] val partOffsetInParent = child.startOffsetInParent - @Suppress("IMPLICIT_CAST_TO_ANY") - val part = when (child) { - is KtLiteralStringTemplateEntry, is KtEscapeStringTemplateEntry -> { - val partSize = children.subList(i, len).asSequence() - .takeWhile { it is KtLiteralStringTemplateEntry || it is KtEscapeStringTemplateEntry } - .count() - i += partSize - 1 - children[i] + if (child is KtSimpleNameStringTemplateEntry || child is KtBlockStringTemplateEntry) { + if (!prefix.isEmpty() || i == 0) { + // Store part with prefix before replacing it + addInjectionRange(TextRange.from(partOffsetInParent, 0), prefix, suffix(i)) } - is KtSimpleNameStringTemplateEntry -> - tryEvaluateConstant(child.expression) ?: run { - unparsable = true - child.expression?.text ?: NO_VALUE_NAME + + prefix = when (child) { + is KtSimpleNameStringTemplateEntry -> { + tryEvaluateConstant(child.expression) ?: run { + unparsable = true + child.expression?.text ?: NO_VALUE_NAME + } } - is KtBlockStringTemplateEntry -> - tryEvaluateConstant(child.expression) ?: run { - unparsable = true - NO_VALUE_NAME + is KtBlockStringTemplateEntry -> { + tryEvaluateConstant(child.expression) ?: run { + unparsable = true + NO_VALUE_NAME + } } - else -> { + else -> { + error("Child type should be KtSimpleNameStringTemplateEntry or KtBlockStringTemplateEntry") + } + } + + if (i == len - 1 && !prefix.isEmpty()) { + // There won't be more elements, so create part with prefix right away + addInjectionRange(TextRange.from(partOffsetInParent + child.textLength, 0), prefix, suffix(i)) + } + } else { + if (child is KtLiteralStringTemplateEntry || child is KtEscapeStringTemplateEntry) { + // Merge all consecutive string literals into one part + i += children.countFromWhile(i + 1) { it is KtLiteralStringTemplateEntry || it is KtEscapeStringTemplateEntry } + } else { unparsable = true - child } + + val lastInPart = children[i] + addInjectionRange( + TextRange.create(partOffsetInParent, lastInPart.startOffsetInParent + lastInPart.textLength), + prefix, + suffix(i) + ) + + prefix = "" } - val suffix = if (i == len - 1) injection.suffix else "" - - if (part is PsiElement) { - addInjectionRange(TextRange.create(partOffsetInParent, part.startOffsetInParent + part.textLength), prefix, suffix) - } - else if (!prefix.isEmpty() || i == 0) { - addInjectionRange(TextRange.from(partOffsetInParent, 0), prefix, suffix) - } - - prefix = part as? String ?: "" i++ } - if (lastChild != null && !prefix.isEmpty()) { - // Last element was interpolated part, need to add a range after it - addInjectionRange(TextRange.from(lastChild.startOffsetInParent + lastChild.textLength, 0), prefix, injection.suffix) - } - return InjectionSplitResult(unparsable, result) } @@ -112,4 +118,16 @@ private fun tryEvaluateConstant(ktExpression: KtExpression?) = ?.safeAs() } -private val NO_VALUE_NAME = "missingValue" +private const val NO_VALUE_NAME = "missingValue" + +private inline fun List.countFromWhile(from: Int, predicate: (T) -> Boolean): Int { + var i = from + while (i < size) { + if (!predicate(get(i))) { + break + } + i++ + } + + return i - from +}