diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/stringUtil.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/stringUtil.kt new file mode 100644 index 000000000000..ca2edb7be9fa --- /dev/null +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/stringUtil.kt @@ -0,0 +1,28 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.plugins.groovy.lang.psi.util + +fun isValidSingleQuotedStringContent(text: String): Boolean = isValidContent(text, '\'', '\n') + +fun isValidDoubleQuotedStringContent(text: String): Boolean = isValidContent(text, '"', '$', '\n') + +private fun isValidContent(text: String, vararg escapedChars: Char): Boolean { + val length: Int = text.length + var idx = 0 + while (idx < length) { + val ch: Char = text[idx] + if (ch in escapedChars) { + return false + } + if (ch == '\\') { + if (idx + 1 < length) { + val nextCh: Char = text[idx + 1] + if (nextCh == '\\' || nextCh in escapedChars) { + idx += 2 + continue + } + } + } + idx++ + } + return true +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/GroovyLiteralCopyPasteProcessor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/GroovyLiteralCopyPasteProcessor.java index d48605d77091..95adb7268a58 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/GroovyLiteralCopyPasteProcessor.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/GroovyLiteralCopyPasteProcessor.java @@ -21,6 +21,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.GroovyTokenSets; import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil; import org.jetbrains.plugins.groovy.lang.psi.util.StringKind; +import org.jetbrains.plugins.groovy.lang.psi.util.StringUtilKt; import org.jetbrains.plugins.groovy.lang.resolve.GroovyStringLiteralManipulator; import static org.jetbrains.plugins.groovy.lang.psi.GroovyElementTypes.*; @@ -68,6 +69,10 @@ public class GroovyLiteralCopyPasteProcessor extends StringLiteralCopyPasteProce return text; } + if (rawText != null && canPasteRaw(text, rawText.rawText, stringKind)) { + return rawText.rawText; + } + StringBuilder buffer = new StringBuilder(text.length()); @NonNls String breaker = stringKind.getLineBreaker(); final String[] lines = LineTokenizer.tokenize(text.toCharArray(), false, true); @@ -80,6 +85,21 @@ public class GroovyLiteralCopyPasteProcessor extends StringLiteralCopyPasteProce return buffer.toString(); } + private static boolean canPasteRaw(String text, String rawText, StringKind kind) { + if (!text.equals(kind.unescape(rawText))) { + return false; + } + if (kind == StringKind.SINGLE_QUOTED) { + return StringUtilKt.isValidSingleQuotedStringContent(rawText); + } + else if (kind == StringKind.DOUBLE_QUOTED) { + return StringUtilKt.isValidDoubleQuotedStringContent(rawText); + } + else { + return true; + } + } + @NotNull private static String escape(@NotNull StringKind kind, @NotNull String s) { if (kind != StringKind.DOUBLE_QUOTED && kind != StringKind.TRIPLE_DOUBLE_QUOTED) { diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/GroovyCopyPasteStringTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/GroovyCopyPasteStringTest.groovy index 64d1084ccad5..9013cf379a9b 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/GroovyCopyPasteStringTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/GroovyCopyPasteStringTest.groovy @@ -177,6 +177,17 @@ class GroovyCopyPasteStringTest extends GroovyLatestTest implements BaseTest { }.run() } + @Test + void 'paste raw'() { + def data = [ + ['\'\\$ \\\' \\" \\\n \\u2318\'', '""', '"\\$ \\\' \\" \\\n \\u2318"'], + ['"\\$ \\\' \\" \\\n \\u2318"', "''", '\'\\$ \\\' \\" \\\n \\u2318\''] + ] + RunAll.runAll(data) { List entry -> + doCopyPasteTest(entry[0], entry[1], entry[2]) + }.run() + } + @Test void 'multiline paste'() { def from = 'hi\nthere' @@ -193,6 +204,22 @@ class GroovyCopyPasteStringTest extends GroovyLatestTest implements BaseTest { }.run() } + @Test + void 'multiline paste raw'() { + def from = '$/hi\nthere\\u2318/$' + def data = [ + /''/ : "'hi\\n' +\n 'there⌘'", // cannot paste raw + /''''''/: "'''hi\nthere\\u2318'''", + /""/ : '"hi\\n" +\n "there⌘"', // cannot paste raw + /""""""/: '"""hi\nthere\\u2318"""', + '/ /' : '/ hi\nthere\\u2318/', + '$//$' : '$/hi\nthere\\u2318/$', + ] + RunAll.runAll(data) { to, expected -> + doCopyPasteTest(from, to, expected) + }.run() + } + @Test void 'paste injection'() { def data = [