From 147f805c56976f3940424d09e15387e6f736c56d Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Mon, 29 Jul 2019 14:06:32 +0900 Subject: [PATCH] Paste inside indented `.trimIndent()` raw string: respect indentation (KT-31810) #KT-31810 Fixed --- .../editor/KotlinLiteralCopyPasteProcessor.kt | 28 ++++++++++++++----- .../plainTextLiteral/TrimIndent.expected.kt | 9 ++++++ .../copyPaste/plainTextLiteral/TrimIndent.kt | 7 +++++ .../copyPaste/plainTextLiteral/TrimIndent.txt | 3 ++ .../plainTextLiteral/TrimIndent2.expected.kt | 9 ++++++ .../copyPaste/plainTextLiteral/TrimIndent2.kt | 7 +++++ .../plainTextLiteral/TrimIndent2.txt | 3 ++ ...ralTextToKotlinCopyPasteTestGenerated.java | 10 +++++++ 8 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 idea/testData/copyPaste/plainTextLiteral/TrimIndent.expected.kt create mode 100644 idea/testData/copyPaste/plainTextLiteral/TrimIndent.kt create mode 100644 idea/testData/copyPaste/plainTextLiteral/TrimIndent.txt create mode 100644 idea/testData/copyPaste/plainTextLiteral/TrimIndent2.expected.kt create mode 100644 idea/testData/copyPaste/plainTextLiteral/TrimIndent2.kt create mode 100644 idea/testData/copyPaste/plainTextLiteral/TrimIndent2.txt diff --git a/idea/src/org/jetbrains/kotlin/idea/editor/KotlinLiteralCopyPasteProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/editor/KotlinLiteralCopyPasteProcessor.kt index 8f12e76fe88..928ca5956e0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/editor/KotlinLiteralCopyPasteProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/editor/KotlinLiteralCopyPasteProcessor.kt @@ -28,8 +28,11 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.editor.fixers.range +import org.jetbrains.kotlin.idea.inspections.collections.isCalling +import org.jetbrains.kotlin.idea.intentions.callExpression import org.jetbrains.kotlin.lexer.KotlinLexer import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtEscapeStringTemplateEntry import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtStringTemplateExpression @@ -41,8 +44,8 @@ private val PsiElement.templateContentRange: TextRange? } -private fun PsiFile.getTemplateIfAtLiteral(offset: Int): KtStringTemplateExpression? { - val at = this.findElementAt(offset) ?: return null +private fun PsiFile.getTemplateIfAtLiteral(offset: Int, at: PsiElement? = findElementAt(offset)): KtStringTemplateExpression? { + if (at == null) return null return when (at.node?.elementType) { KtTokens.REGULAR_STRING_PART, KtTokens.ESCAPE_SEQUENCE, KtTokens.LONG_TEMPLATE_ENTRY_START, KtTokens.SHORT_TEMPLATE_ENTRY_START -> at.parent.parent as? KtStringTemplateExpression KtTokens.CLOSING_QUOTE -> if (offset == at.startOffset) at.parent as? KtStringTemplateExpression else null @@ -123,7 +126,8 @@ class KotlinLiteralCopyPasteProcessor : CopyPastePreProcessor { } PsiDocumentManager.getInstance(project).commitDocument(editor.document) val selectionModel = editor.selectionModel - val beginTp = file.getTemplateIfAtLiteral(selectionModel.selectionStart) ?: return text + val begin = file.findElementAt(selectionModel.selectionStart) ?: return text + val beginTp = file.getTemplateIfAtLiteral(selectionModel.selectionStart, begin) ?: return text val endTp = file.getTemplateIfAtLiteral(selectionModel.selectionEnd) ?: return text if (beginTp.isSingleQuoted() != endTp.isSingleQuoted()) { return text @@ -147,13 +151,23 @@ class KotlinLiteralCopyPasteProcessor : CopyPastePreProcessor { res.toString() } } else { + val indent = if (beginTp.firstChild?.text == "\"\"\"" && + beginTp.getQualifiedExpressionForReceiver()?.callExpression?.isCalling(FqName("kotlin.text.trimIndent")) == true + ) { + begin.parent?.prevSibling?.takeIf { it.text != "\n" }?.text ?: "" + } else { + "" + } val tripleQuoteRe = Regex("[\"]{3,}") - TemplateTokenSequence(text).map { chunk -> + TemplateTokenSequence(text).mapIndexed { index, chunk -> when (chunk) { - is LiteralChunk -> chunk.text.replace("\$", "\${'$'}").let { escapedDollar -> - tripleQuoteRe.replace(escapedDollar) { "\"\"" + "\${'\"'}".repeat(it.value.count() - 2) } + is LiteralChunk -> { + val replaced = chunk.text.replace("\$", "\${'$'}").let { escapedDollar -> + tripleQuoteRe.replace(escapedDollar) { "\"\"" + "\${'\"'}".repeat(it.value.count() - 2) } + } + if (index == 0) replaced else indent + replaced } - is EntryChunk -> chunk.text + is EntryChunk -> if (index == 0) chunk.text else indent + chunk.text is NewLineChunk -> "\n" } }.joinToString(separator = "") diff --git a/idea/testData/copyPaste/plainTextLiteral/TrimIndent.expected.kt b/idea/testData/copyPaste/plainTextLiteral/TrimIndent.expected.kt new file mode 100644 index 00000000000..89c48435423 --- /dev/null +++ b/idea/testData/copyPaste/plainTextLiteral/TrimIndent.expected.kt @@ -0,0 +1,9 @@ +fun test() = doTest(""" + def foo(a) + a ? 0 : 1 + end +""".trimIndent()) + +fun doTest(rubyCode: String) { + // some code here +} diff --git a/idea/testData/copyPaste/plainTextLiteral/TrimIndent.kt b/idea/testData/copyPaste/plainTextLiteral/TrimIndent.kt new file mode 100644 index 00000000000..b8943a9d820 --- /dev/null +++ b/idea/testData/copyPaste/plainTextLiteral/TrimIndent.kt @@ -0,0 +1,7 @@ +fun test() = doTest(""" + +""".trimIndent()) + +fun doTest(rubyCode: String) { + // some code here +} diff --git a/idea/testData/copyPaste/plainTextLiteral/TrimIndent.txt b/idea/testData/copyPaste/plainTextLiteral/TrimIndent.txt new file mode 100644 index 00000000000..17859595532 --- /dev/null +++ b/idea/testData/copyPaste/plainTextLiteral/TrimIndent.txt @@ -0,0 +1,3 @@ +def foo(a) + a ? 0 : 1 +end \ No newline at end of file diff --git a/idea/testData/copyPaste/plainTextLiteral/TrimIndent2.expected.kt b/idea/testData/copyPaste/plainTextLiteral/TrimIndent2.expected.kt new file mode 100644 index 00000000000..868a995efe9 --- /dev/null +++ b/idea/testData/copyPaste/plainTextLiteral/TrimIndent2.expected.kt @@ -0,0 +1,9 @@ +fun test() = doTest(""" +def foo(a) + a ? 0 : 1 +end +""".trimIndent()) + +fun doTest(rubyCode: String) { + // some code here +} diff --git a/idea/testData/copyPaste/plainTextLiteral/TrimIndent2.kt b/idea/testData/copyPaste/plainTextLiteral/TrimIndent2.kt new file mode 100644 index 00000000000..fb5f104c551 --- /dev/null +++ b/idea/testData/copyPaste/plainTextLiteral/TrimIndent2.kt @@ -0,0 +1,7 @@ +fun test() = doTest(""" + +""".trimIndent()) + +fun doTest(rubyCode: String) { + // some code here +} diff --git a/idea/testData/copyPaste/plainTextLiteral/TrimIndent2.txt b/idea/testData/copyPaste/plainTextLiteral/TrimIndent2.txt new file mode 100644 index 00000000000..17859595532 --- /dev/null +++ b/idea/testData/copyPaste/plainTextLiteral/TrimIndent2.txt @@ -0,0 +1,3 @@ +def foo(a) + a ? 0 : 1 +end \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/LiteralTextToKotlinCopyPasteTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/LiteralTextToKotlinCopyPasteTestGenerated.java index 07026780cdf..f400a8fa6f9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/LiteralTextToKotlinCopyPasteTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/LiteralTextToKotlinCopyPasteTestGenerated.java @@ -59,6 +59,16 @@ public class LiteralTextToKotlinCopyPasteTestGenerated extends AbstractLiteralTe runTest("idea/testData/copyPaste/plainTextLiteral/TrailingLines.txt"); } + @TestMetadata("TrimIndent.txt") + public void testTrimIndent() throws Exception { + runTest("idea/testData/copyPaste/plainTextLiteral/TrimIndent.txt"); + } + + @TestMetadata("TrimIndent2.txt") + public void testTrimIndent2() throws Exception { + runTest("idea/testData/copyPaste/plainTextLiteral/TrimIndent2.txt"); + } + @TestMetadata("WithBackslashes.txt") public void testWithBackslashes() throws Exception { runTest("idea/testData/copyPaste/plainTextLiteral/WithBackslashes.txt");