From 1eceb898e67c8e4feb10ed3c78454dd2eec12ece Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 3 Mar 2011 13:11:56 +0300 Subject: [PATCH] added PY-2697 Quickfix to transform a triple-quoted multiline literal into a literal with many quoted strings --- python/src/META-INF/python-plugin-common.xml | 5 + .../com/jetbrains/python/PyBundle.properties | 3 + .../PyConvertTripleQuotedStringIntention.java | 113 ++++++++++++++++++ .../afterConvertTripleQuotedString.py | 5 + .../beforeConvertTripleQuotedString.py | 5 + .../com/jetbrains/python/PyIntentionTest.java | 3 + 6 files changed, 134 insertions(+) create mode 100644 python/src/com/jetbrains/python/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java create mode 100644 python/testData/intentions/afterConvertTripleQuotedString.py create mode 100644 python/testData/intentions/beforeConvertTripleQuotedString.py diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 0951383ec8db..c17935b67502 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -176,6 +176,11 @@ Python + + com.jetbrains.python.codeInsight.intentions.PyConvertTripleQuotedStringIntention + Python + + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index d2485e2b2226..95f792d0fde3 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -146,6 +146,9 @@ INTN.convert.lambda.to.function=Convert lambda to function INTN.convert.variadic.param=Convert from variadic to normal parameter(s) +# PyConvertTripleQuotedStringIntention +INTN.triple.quoted.string=Convert triple-quoted string to single-quoted string + # Conflict checker CONFLICT.name.$0.obscured=Name ''{0}'' obscured by local definitions CONFLICT.name.$0.obscured.cannot.convert=Name ''{0}'' obscured. Cannot convert. diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java new file mode 100644 index 000000000000..8bf47ec76fc1 --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java @@ -0,0 +1,113 @@ +package com.jetbrains.python.codeInsight.intentions; + +import com.intellij.codeInsight.intention.impl.BaseIntentionAction; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; + +/** + * User: catherine + * Intention to convert triple quoted string to single-quoted + * For instance: + * from: + * a = """This line is ok, + * but "this" includes far too much + * whitespace at the start""" + * to: + * a = ("This line is ok," "\n" + * "but \"this\" includes far too much" "\n" + * "whitespace at the start") + */ +public class PyConvertTripleQuotedStringIntention extends BaseIntentionAction { + + @NotNull + public String getFamilyName() { + return PyBundle.message("INTN.triple.quoted.string"); + } + + @NotNull + @Override + public String getText() { + return PyBundle.message("INTN.triple.quoted.string"); + } + + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + PyStringLiteralExpression string = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyStringLiteralExpression.class); + if (string != null) { + final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(string, PyDocStringOwner.class); + if (docStringOwner != null) { + if (docStringOwner.getDocStringExpression() == string) return false; + } + String stringText = string.getText(); + if (stringText.length() >= 6) { + if (stringText.startsWith("'''") && stringText.endsWith("'''") || + stringText.startsWith("\"\"\"") && stringText.endsWith("\"\"\"")) return true; + } + } + return false; + } + + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PyStringLiteralExpression string = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyStringLiteralExpression.class); + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + if (string != null) { + StringBuilder strBuilder = new StringBuilder(); + for (ASTNode node : string.getStringNodes()) + strBuilder.append(node.getText()); + String stringText = strBuilder.toString(); + String[] subStrings = stringText.split("\n"); + + Character firstQuote = stringText.charAt(0); + StringBuilder result = new StringBuilder(); + if (subStrings.length != 1) + result.append("("); + boolean lastString = false; + for (String s : subStrings) { + result.append(firstQuote); + String validSubstring = convertToValidSubString(s, firstQuote); + + if (s.endsWith("'''") || s.endsWith("\"\"\"")) { + lastString = true; + } + result.append(validSubstring); + result.append(firstQuote); + if (!lastString) + result.append(" ").append(firstQuote).append("\\n").append(firstQuote).append("\n"); + } + if (subStrings.length != 1) + result.append(")"); + PyExpressionStatement e = elementGenerator.createFromText(LanguageLevel.forElement(string), PyExpressionStatement.class, result.toString()); + string.replace(e.getExpression()); + } + } + + private static String convertToValidSubString(String s, Character firstQuote) { + String subString; + if (s.startsWith("'''") || s.startsWith("\"\"\"")) + subString = convertToValidSubString(s.substring(3), firstQuote); + else if (s.endsWith("'''") || s.endsWith("\"\"\"")) { + String trimmed = s.trim(); + subString = convertToValidSubString(trimmed.substring(0, trimmed.length() - 3), firstQuote); + } + else { + s = s.trim(); + StringBuilder stringBuilder = new StringBuilder(); + for (Character ch : s.toCharArray()) { + if (ch == firstQuote) { + stringBuilder.append("\\").append(firstQuote); + } + else { + stringBuilder.append(ch); + } + } + subString = stringBuilder.toString(); + } + return subString; + } +} diff --git a/python/testData/intentions/afterConvertTripleQuotedString.py b/python/testData/intentions/afterConvertTripleQuotedString.py new file mode 100644 index 000000000000..500ade9f6205 --- /dev/null +++ b/python/testData/intentions/afterConvertTripleQuotedString.py @@ -0,0 +1,5 @@ +if True: + if True: + a = ('\'Hello\' - said man.\n' '\n' + 'Let\'s go.' '\n' + 'And they went out.') \ No newline at end of file diff --git a/python/testData/intentions/beforeConvertTripleQuotedString.py b/python/testData/intentions/beforeConvertTripleQuotedString.py new file mode 100644 index 000000000000..79183b64b073 --- /dev/null +++ b/python/testData/intentions/beforeConvertTripleQuotedString.py @@ -0,0 +1,5 @@ +if True: + if True: + a = ''''Hello' - said man.\n + Let's go. + And they went out.''' \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyIntentionTest.java b/python/testSrc/com/jetbrains/python/PyIntentionTest.java index 6c12e1710939..19e3bed112a7 100644 --- a/python/testSrc/com/jetbrains/python/PyIntentionTest.java +++ b/python/testSrc/com/jetbrains/python/PyIntentionTest.java @@ -161,4 +161,7 @@ public class PyIntentionTest extends PyLightFixtureTestCase { public void testConvertVariadicParam() { //PY-2264 doTest(PyBundle.message("INTN.convert.variadic.param")); } + public void testConvertTripleQuotedString() { //PY-2697 + doTest(PyBundle.message("INTN.triple.quoted.string")); + } }