diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToStringTemplateIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToStringTemplateIntention.kt index cbbc98082f1..418b6512b8f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToStringTemplateIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToStringTemplateIntention.kt @@ -57,7 +57,7 @@ open class ConvertToStringTemplateIntention : SelfTargetingOffsetIndependentInte } @JvmStatic - fun buildReplacement(expression: KtBinaryExpression): KtStringTemplateExpression { + protected fun buildReplacement(expression: KtBinaryExpression): KtStringTemplateExpression { val rightText = buildText(expression.right, false) return fold(expression.left, rightText, KtPsiFactory(expression)) } @@ -75,7 +75,7 @@ open class ConvertToStringTemplateIntention : SelfTargetingOffsetIndependentInte } } - private fun buildText(expr: KtExpression?, forceBraces: Boolean): String { + fun buildText(expr: KtExpression?, forceBraces: Boolean): String { if (expr == null) return "" val expression = KtPsiUtil.safeDeparenthesize(expr) val expressionText = expression.text @@ -130,7 +130,7 @@ open class ConvertToStringTemplateIntention : SelfTargetingOffsetIndependentInte return "\${$expressionText}" } - fun isApplicableToNoParentCheck(expression: KtBinaryExpression): Boolean { + private fun isApplicableToNoParentCheck(expression: KtBinaryExpression): Boolean { if (expression.operationToken != KtTokens.PLUS) return false val expressionType = expression.analyze(BodyResolveMode.PARTIAL).getType(expression) if (!KotlinBuiltIns.isString(expressionType)) return false diff --git a/idea/src/org/jetbrains/kotlin/idea/joinLines/JoinToStringTemplateHandler.kt b/idea/src/org/jetbrains/kotlin/idea/joinLines/JoinToStringTemplateHandler.kt index c38581b068e..42c714d4b7a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/joinLines/JoinToStringTemplateHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/joinLines/JoinToStringTemplateHandler.kt @@ -20,10 +20,10 @@ import com.intellij.codeInsight.editorActions.JoinRawLinesHandlerDelegate import com.intellij.openapi.editor.Document import com.intellij.psi.PsiFile import org.jetbrains.kotlin.idea.intentions.ConvertToStringTemplateIntention +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.lineCount +import org.jetbrains.kotlin.idea.refactoring.getLineCount import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.KtBinaryExpression -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.endOffset class JoinToStringTemplateHandler : JoinRawLinesHandlerDelegate { @@ -35,16 +35,61 @@ class JoinToStringTemplateHandler : JoinRawLinesHandlerDelegate { val index = if (c == '\n') start - 1 else start val plus = file.findElementAt(index)?.takeIf { it.node?.elementType == KtTokens.PLUS } ?: return -1 - val expression = (plus.parent.parent as? KtBinaryExpression) ?: return -1 - val left = expression.left ?: return -1 - val right = expression.right ?: return -1 - if (left !is KtStringTemplateExpression || right !is KtStringTemplateExpression) return -1 - if (!ConvertToStringTemplateIntention.isApplicableToNoParentCheck(expression)) return -1 + var binaryExpr = (plus.parent.parent as? KtBinaryExpression) ?: return -1 + if (!binaryExpr.joinable()) return -1 - val offset = left.endOffset - 1 - expression.replace(ConvertToStringTemplateIntention.buildReplacement(expression)) - return offset + val lineCount = binaryExpr.getLineCount() + val nextLineCount = lineCount + 1 + + var parent = binaryExpr.parent + while (parent is KtBinaryExpression && parent.joinable() && parent.lineCount() == nextLineCount) { + binaryExpr = parent + parent = parent.parent + } + + var rightText = ConvertToStringTemplateIntention.buildText(binaryExpr.right, false) + var left = binaryExpr.left + while (left is KtBinaryExpression && left.joinable() && left.parent.getLineCount() >= lineCount) { + rightText = ConvertToStringTemplateIntention.buildText(left.right, false) + rightText + left = left.left + } + + return when (left) { + is KtStringTemplateExpression -> { + val offset = left.endOffset - 1 + binaryExpr.replace(createStringTemplate(left, rightText)) + offset + } + is KtBinaryExpression -> { + val leftRight = left.right + if (leftRight is KtStringTemplateExpression) { + val offset = leftRight.endOffset - 1 + leftRight.replace(createStringTemplate(leftRight, rightText)) + binaryExpr.replace(left) + offset + } else { + -1 + } + } + else -> -1 + } + } + + private fun createStringTemplate(left: KtStringTemplateExpression, rightText: String): KtStringTemplateExpression { + val leftText = ConvertToStringTemplateIntention.buildText(left, false) + return KtPsiFactory(left).createExpression("\"$leftText$rightText\"") as KtStringTemplateExpression } override fun tryJoinLines(document: Document, file: PsiFile, start: Int, end: Int): Int = -1 -} \ No newline at end of file +} + +private fun KtBinaryExpression.joinable(): Boolean { + if (operationToken != KtTokens.PLUS) return false + if (right !is KtStringTemplateExpression) return false + val left = left + return when (left) { + is KtStringTemplateExpression -> true + is KtBinaryExpression -> left.right is KtStringTemplateExpression + else -> false + } +} diff --git a/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteral.kt b/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteral.kt new file mode 100644 index 00000000000..8bdbbe78c0b --- /dev/null +++ b/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteral.kt @@ -0,0 +1,4 @@ +val s = "1" + "2" + + "3" + "4" + + "5" + "6" + + "7" + "8" \ No newline at end of file diff --git a/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteral.kt.after b/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteral.kt.after new file mode 100644 index 00000000000..55bb7034c02 --- /dev/null +++ b/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteral.kt.after @@ -0,0 +1,3 @@ +val s = "1" + "2" + + "3456" + + "7" + "8" \ No newline at end of file diff --git a/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteralAndVariable.kt b/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteralAndVariable.kt new file mode 100644 index 00000000000..6fd36b38e63 --- /dev/null +++ b/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteralAndVariable.kt @@ -0,0 +1,5 @@ +val foo = "test" +val s = "1" + "2" + + foo + "4" + + "5" + foo + + "7" + "8" \ No newline at end of file diff --git a/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteralAndVariable.kt.after b/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteralAndVariable.kt.after new file mode 100644 index 00000000000..04234dc1bdd --- /dev/null +++ b/idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteralAndVariable.kt.after @@ -0,0 +1,4 @@ +val foo = "test" +val s = "1" + "2" + + foo + "45" + foo + + "7" + "8" \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/declarations/JoinLinesTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/declarations/JoinLinesTestGenerated.java index 615790b9e6c..b04df438544 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/declarations/JoinLinesTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/declarations/JoinLinesTestGenerated.java @@ -429,6 +429,18 @@ public class JoinLinesTestGenerated extends AbstractJoinLinesTest { doTest(fileName); } + @TestMetadata("lineWithMultiStringLiteral.kt") + public void testLineWithMultiStringLiteral() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteral.kt"); + doTest(fileName); + } + + @TestMetadata("lineWithMultiStringLiteralAndVariable.kt") + public void testLineWithMultiStringLiteralAndVariable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/stringTemplate/lineWithMultiStringLiteralAndVariable.kt"); + doTest(fileName); + } + @TestMetadata("secondLineVariable.kt") public void testSecondLineVariable() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/stringTemplate/secondLineVariable.kt");