IDEA-87824 convert string to multiline

This commit is contained in:
Maxim.Medvedev
2012-06-26 17:57:04 +04:00
parent 2dea356213
commit 41da0d62a7
9 changed files with 251 additions and 3 deletions
@@ -0,0 +1,2 @@
print <spot>'''first
second'''</spot>
@@ -0,0 +1 @@
print <spot>'first\n second'</spot>
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts singleline string literal to multiline literal.
</body>
</html>
+5
View File
@@ -1106,6 +1106,11 @@
<categoryKey>intention.category.groovy/intention.category.conversions</categoryKey>
<className>org.jetbrains.plugins.groovy.intentions.conversions.ConvertMethodToClosureIntention</className>
</intentionAction>
<intentionAction>
<bundleName>org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle</bundleName>
<categoryKey>intention.category.groovy/intention.category.conversions</categoryKey>
<className>org.jetbrains.plugins.groovy.intentions.conversions.ConvertStringToMultilineIntention</className>
</intentionAction>
<intentionAction>
<bundleName>org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle</bundleName>
<categoryKey>intention.category.groovy/intention.category.conversions</categoryKey>
@@ -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
###########################################################################################################
@@ -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;
}
};
}
}
@@ -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);
}
}
}
@@ -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 'ab<caret>c'", "print '''abc'''")
}
void testGString() {
doTest('print "ab<caret>c"', 'print """abc"""')
}
void testPlainString2() {
doTest("print 'a\\nb<caret>c'", "print '''a\nbc'''")
}
void testGString2() {
doTest('print "a\\nb<caret>c"', 'print """a\nbc"""')
}
void testGString3() {
doTest('print "a\\nb${a<caret>}c"', 'print """a\nb${a}c"""')
}
void testAlreadyMultiline() {
doTest('print """a<caret>bc"""', 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)
}
}
}
@@ -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")
}
}