diff --git a/plugins/groovy/resources/intentionDescriptions/ConvertStringToMultilineIntention/after.groovy.template b/plugins/groovy/resources/intentionDescriptions/ConvertStringToMultilineIntention/after.groovy.template
new file mode 100644
index 000000000000..d39ed06ecad8
--- /dev/null
+++ b/plugins/groovy/resources/intentionDescriptions/ConvertStringToMultilineIntention/after.groovy.template
@@ -0,0 +1,2 @@
+print '''first
+ second'''
\ No newline at end of file
diff --git a/plugins/groovy/resources/intentionDescriptions/ConvertStringToMultilineIntention/before.groovy.template b/plugins/groovy/resources/intentionDescriptions/ConvertStringToMultilineIntention/before.groovy.template
new file mode 100644
index 000000000000..588872062d48
--- /dev/null
+++ b/plugins/groovy/resources/intentionDescriptions/ConvertStringToMultilineIntention/before.groovy.template
@@ -0,0 +1 @@
+print 'first\n second'
\ No newline at end of file
diff --git a/plugins/groovy/resources/intentionDescriptions/ConvertStringToMultilineIntention/description.html b/plugins/groovy/resources/intentionDescriptions/ConvertStringToMultilineIntention/description.html
new file mode 100644
index 000000000000..5bc803413371
--- /dev/null
+++ b/plugins/groovy/resources/intentionDescriptions/ConvertStringToMultilineIntention/description.html
@@ -0,0 +1,5 @@
+
+
+This intention converts singleline string literal to multiline literal.
+
+
\ No newline at end of file
diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml
index 65d5a32f4603..84ec2b08e529 100644
--- a/plugins/groovy/src/META-INF/plugin.xml
+++ b/plugins/groovy/src/META-INF/plugin.xml
@@ -1106,6 +1106,11 @@
intention.category.groovy/intention.category.conversions
org.jetbrains.plugins.groovy.intentions.conversions.ConvertMethodToClosureIntention
+
+ org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle
+ intention.category.groovy/intention.category.conversions
+ org.jetbrains.plugins.groovy.intentions.conversions.ConvertStringToMultilineIntention
+
org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle
intention.category.groovy/intention.category.conversions
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/GroovyIntentionsBundle.properties b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/GroovyIntentionsBundle.properties
index 41a0e471bd6c..6bcd52cb3ec8 100644
--- a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/GroovyIntentionsBundle.properties
+++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/GroovyIntentionsBundle.properties
@@ -81,6 +81,8 @@ convert.closure.to.method.intention.name=Convert to method
convert.closure.to.method.intention.family.name=Convert property to method
convert.method.to.closure.intention.name=Convert to property
convert.method.to.closure.intention.family.name=Convert method to property of closure type
+convert.string.to.multiline.intention.name=Convert to Multiline
+convert.string.to.multiline.intention.family.name=Convert literal to multiline literal
convert.junit.assertion.to.assert.statement.intention.name=Convert JUnit assertion to assert statement
convert.junit.assertion.to.assert.statement.intention.family.name=Convert JUnit assertion to assert statement
###########################################################################################################
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/ConvertStringToMultilineIntention.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/ConvertStringToMultilineIntention.java
new file mode 100644
index 000000000000..62b5b8d43e03
--- /dev/null
+++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/ConvertStringToMultilineIntention.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.groovy.intentions.conversions;
+
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.intentions.base.Intention;
+import org.jetbrains.plugins.groovy.intentions.base.PsiElementPredicate;
+import org.jetbrains.plugins.groovy.lang.editor.actions.GroovyEditorActionUtil;
+import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
+import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
+import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.literals.GrLiteralImpl;
+import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.literals.GrStringImpl;
+import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil;
+
+/**
+ * @author Max Medvedev
+ */
+public class ConvertStringToMultilineIntention extends Intention {
+ private static final Logger LOG = Logger.getInstance(ConvertStringToMultilineIntention.class);
+
+ public static final String hint = "Convert to Multiline";
+
+ @Override
+ protected void processIntention(@NotNull PsiElement element, Project project, Editor editor) throws IncorrectOperationException {
+ String quote = element.getText().startsWith("'") ? "'''" : "\"\"\"";
+
+ StringBuilder buffer = new StringBuilder();
+ buffer.append(quote);
+
+ GrExpression old;
+
+ if (element instanceof GrLiteralImpl) {
+ appendSimpleStringValue(element, buffer, quote);
+ old = (GrExpression)element;
+ }
+ else {
+ final GrStringImpl gstring = PsiTreeUtil.getParentOfType(element, GrStringImpl.class);
+ for (ASTNode child = gstring.getNode().getFirstChildNode(); child != null; child = child.getTreeNext()) {
+ if (child.getElementType() == GroovyTokenTypes.mGSTRING_CONTENT) {
+ appendGStringContent(child, buffer);
+ }
+ else if (child.getElementType() == GroovyElementTypes.GSTRING_INJECTION) {
+ buffer.append(child.getText());
+ }
+ }
+ old = gstring;
+ }
+
+ buffer.append(quote);
+ try {
+ final GrExpression newLiteral = GroovyPsiElementFactory.getInstance(project).createExpressionFromText(buffer.toString());
+ old.replaceWithExpression(newLiteral, true);
+ }
+ catch (IncorrectOperationException e) {
+ LOG.error(e);
+ }
+ }
+
+ private static void appendGStringContent(ASTNode child, StringBuilder buffer) {
+ final String text = child.getText();
+ final StringBuilder parsed = new StringBuilder();
+ if (GrStringUtil.parseStringCharacters(text, parsed, null)) {
+ buffer.append(parsed);
+ }
+ else {
+ buffer.append(text);
+ }
+ }
+
+ private static void appendSimpleStringValue(PsiElement element, StringBuilder buffer, String quote) {
+ final Object value = ((GrLiteralImpl)element).getValue();
+ if (value instanceof String) {
+ if ("'''".equals(quote)) {
+ GrStringUtil.escapeStringCharacters(((String)value).length(), (String)value, "", false, true, buffer);
+ }
+ else {
+ GrStringUtil.escapeSymbolsForGString((CharSequence)value, false, false, buffer);
+ }
+ }
+ else {
+ buffer.append(GrStringUtil.removeQuotes(element.getText()));
+ }
+ }
+
+ @NotNull
+ @Override
+ protected PsiElementPredicate getElementPredicate() {
+ return new PsiElementPredicate() {
+ @Override
+ public boolean satisfiedBy(PsiElement element) {
+ if (element instanceof GrLiteralImpl) {
+ final ASTNode node = element.getFirstChild().getNode();
+ final IElementType type = node.getElementType();
+ if (type == GroovyTokenTypes.mSTRING_LITERAL) {
+ return GroovyEditorActionUtil.isPlainStringLiteral(node);
+ }
+ if (type == GroovyTokenTypes.mGSTRING_LITERAL) {
+ return GroovyEditorActionUtil.isPlainGString(node);
+ }
+ }
+ else {
+ final GrStringImpl gstring = PsiTreeUtil.getParentOfType(element, GrStringImpl.class, false, GrMember.class, GroovyFile.class);
+ if (gstring == null) return false;
+ return gstring.isPlainString();
+ }
+ return false;
+ }
+ };
+ }
+}
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java
index 7e942d05510d..8def3caf30ad 100644
--- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java
+++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java
@@ -248,7 +248,7 @@ public class GrStringUtil {
return b.toString();
}
- private static void escapeSymbolsForGString(CharSequence s, boolean isSingleLine, boolean forInjection, StringBuilder b) {
+ public static void escapeSymbolsForGString(CharSequence s, boolean isSingleLine, boolean forInjection, StringBuilder b) {
escapeStringCharacters(s.length(), s, isSingleLine ? "$\"" : "$", isSingleLine, true, b);
if (!forInjection) {
unescapeCharacters(b, isSingleLine ? "'" : "'\"", true);
@@ -828,4 +828,28 @@ public class GrStringUtil {
return false;
}
+
+ public static void getOperandText(@NotNull GrLiteral operand, @NotNull StringBuilder builder) {
+ if (operand instanceof GrRegex) {
+ StringBuilder b = new StringBuilder();
+ parseRegexCharacters(removeQuotes(operand.getText()), b, null, operand.getText().startsWith("/"));
+ escapeSymbolsForGString(b, false, false);
+ }
+ else if (operand instanceof GrString) {
+ builder.append(removeQuotes(operand.getText()));
+ }
+ else {
+ Object value = operand.getValue();
+ if (value == null) {
+ value = removeQuotes(operand.getText());
+ }
+
+ String text = value.toString();
+ StringBuilder buffer = new StringBuilder(text.length());
+ boolean containsLineFeeds = text.indexOf('\n') >= 0 || text.indexOf('\r') >= 0;
+ escapeStringCharacters(text.length(), text, "$", false, true, buffer);
+ unescapeCharacters(buffer, containsLineFeeds?"'\"":"'", containsLineFeeds);
+ builder.append(buffer);
+ }
+ }
}
diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/ConvertStringToMultilineTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/ConvertStringToMultilineTest.groovy
new file mode 100644
index 000000000000..144dc9884b40
--- /dev/null
+++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/ConvertStringToMultilineTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.groovy.intentions
+
+import com.intellij.openapi.application.WriteAction
+import org.jetbrains.annotations.Nullable
+import org.jetbrains.plugins.groovy.LightGroovyTestCase
+import org.jetbrains.plugins.groovy.intentions.conversions.ConvertStringToMultilineIntention
+
+/**
+ * @author Max Medvedev
+ */
+public class ConvertStringToMultilineTest extends LightGroovyTestCase {
+ @Override
+ protected String getBasePath() {''}
+
+ void testPlainString() {
+ doTest("print 'abc'", "print '''abc'''")
+ }
+
+ void testGString() {
+ doTest('print "abc"', 'print """abc"""')
+ }
+
+ void testPlainString2() {
+ doTest("print 'a\\nbc'", "print '''a\nbc'''")
+ }
+
+ void testGString2() {
+ doTest('print "a\\nbc"', 'print """a\nbc"""')
+ }
+
+ void testGString3() {
+ doTest('print "a\\nb${a}c"', 'print """a\nb${a}c"""')
+ }
+
+ void testAlreadyMultiline() {
+ doTest('print """abc"""', null)
+ }
+
+ private doTest(String before, @Nullable String after) {
+ myFixture.with {
+ configureByText('_.groovy', before)
+ def intentions = filterAvailableIntentions(ConvertStringToMultilineIntention.hint)
+ if (!after) {
+ assertEmpty(intentions)
+ return
+ }
+
+ assertOneElement(intentions)
+ def accessToken = WriteAction.start()
+ try {
+ intentions[0].invoke(project, editor, file)
+ }
+ finally {
+ accessToken.finish()
+ }
+ checkResult(after)
+ }
+ }
+}
diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/CreateFieldFromParameterTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/CreateFieldFromParameterTest.groovy
index 516ae9c842a7..8156066ce4b5 100644
--- a/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/CreateFieldFromParameterTest.groovy
+++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/CreateFieldFromParameterTest.groovy
@@ -51,7 +51,7 @@ class CreateFieldFromParameterTest extends LightCodeInsightFixtureTestCase {
void testTypeParameter() {doTest()}
private void doTest() {
- myFixture.configureByFile("before${getTestName(true)}.groovy")
+ myFixture.configureByFile("before${getTestName(false)}.groovy")
def intentions = myFixture.availableIntentions
for (intention in intentions) {
if (intention instanceof IntentionActionWrapper) intention = intention.delegate
@@ -61,6 +61,6 @@ class CreateFieldFromParameterTest extends LightCodeInsightFixtureTestCase {
break
}
}
- myFixture.checkResultByFile("after${getTestName(true)}.groovy")
+ myFixture.checkResultByFile("after${getTestName(false)}.groovy")
}
}