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"));
+ }
}