diff --git a/python/src/com/jetbrains/python/editor/BaseQuoteHandler.java b/python/src/com/jetbrains/python/editor/BaseQuoteHandler.java index 79ededc37f70..c5d14dfa8b53 100644 --- a/python/src/com/jetbrains/python/editor/BaseQuoteHandler.java +++ b/python/src/com/jetbrains/python/editor/BaseQuoteHandler.java @@ -22,7 +22,6 @@ import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; -import com.jetbrains.python.PyTokenTypes; import org.jetbrains.annotations.Nullable; import java.util.Arrays; @@ -35,7 +34,7 @@ public class BaseQuoteHandler extends SimpleTokenSetQuoteHandler implements Mult private final char[] ourAutoClosingChars; // we add auto-close quotes before these public BaseQuoteHandler(TokenSet tokenSet, char[] autoClosingChars) { - super(TokenSet.andNot(tokenSet, TokenSet.create(PyTokenTypes.DOCSTRING))); + super(tokenSet); ourAutoClosingChars = autoClosingChars; Arrays.sort(ourAutoClosingChars); } diff --git a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java index 209af09f6b40..1ad667c4fe39 100644 --- a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java +++ b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java @@ -107,9 +107,12 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter { comment = file.findElementAt(offset - 1); } int expectedStringStart = editor.getCaretModel().getOffset() - 3; // """ or ''' - if (comment != null && atDocCommentStart(comment, expectedStringStart, doc)) { - insertDocStringStub(editor, comment); - return Result.Continue; + if (comment != null) { + final DocstringState state = canGenerateDocstring(comment, expectedStringStart, doc); + if (state != DocstringState.NONE) { + insertDocStringStub(editor, comment, state); + return Result.Continue; + } } } @@ -259,17 +262,23 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter { return wrappableBefore != wrappableAfter; } - private static void insertDocStringStub(Editor editor, PsiElement element) { + private static void insertDocStringStub(Editor editor, PsiElement element, DocstringState state) { PyDocStringOwner docOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class); if (docOwner != null) { final int caretOffset = editor.getCaretModel().getOffset(); - final String quotes = editor.getDocument().getText(TextRange.from(caretOffset - 3, 3)); + final Document document = editor.getDocument(); + final String quotes = document.getText(TextRange.from(caretOffset - 3, 3)); final String docString = PyDocstringGenerator.forDocStringOwner(docOwner) .withInferredParameters(true) .withQuotes(quotes) .forceNewMode() .buildDocString(); - editor.getDocument().replaceString(caretOffset - 3, caretOffset, docString); + if (state == DocstringState.INCOMPLETE) { + document.insertString(caretOffset, docString.substring(3)); + } + else if (state == DocstringState.EMPTY) { + document.replaceString(caretOffset, caretOffset + 3, docString.substring(3)); + } } } @@ -379,34 +388,46 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter { } } } + + enum DocstringState { + NONE, + INCOMPLETE, + EMPTY + } - public static boolean atDocCommentStart(@NotNull PsiElement element, int firstQuoteOffset, @NotNull Document document) { + @NotNull + public static DocstringState canGenerateDocstring(@NotNull PsiElement element, int firstQuoteOffset, @NotNull Document document) { if (firstQuoteOffset < 0 || firstQuoteOffset > document.getTextLength() - 3) { - return false; + return DocstringState.NONE; } final String quotes = document.getText(TextRange.from(firstQuoteOffset, 3)); if (!quotes.equals("\"\"\"") && !quotes.equals("'''")) { - return false; + return DocstringState.NONE; } final PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); if (pyString != null) { + String nodeText = element.getText(); final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(nodeText); nodeText = nodeText.substring(prefixLength); + final String literalText = pyString.getText(); if (literalText.endsWith(nodeText) && nodeText.startsWith(quotes)) { if (firstQuoteOffset == pyString.getTextOffset() + prefixLength) { PsiErrorElement error = PsiTreeUtil.getNextSiblingOfType(pyString, PsiErrorElement.class); - if (error != null) { - return true; + if (error == null) { + error = PsiTreeUtil.getNextSiblingOfType(pyString.getParent(), PsiErrorElement.class); } - error = PsiTreeUtil.getNextSiblingOfType(pyString.getParent(), PsiErrorElement.class); if (error != null) { - return true; + return DocstringState.INCOMPLETE; } - + + if (nodeText.equals(quotes + quotes)) { + return DocstringState.EMPTY; + } + if (nodeText.length() < 6 || !nodeText.endsWith(quotes)) { - return true; + return DocstringState.INCOMPLETE; } // Sometimes if incomplete docstring is followed by another declaration with a docstring, it might be treated // as complete docstring, because we can't understand that closing quotes actually belong to another docstring. @@ -416,12 +437,12 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter { final String lineContent = line.substring(lineIndent.length()); if ((lineContent.startsWith("def ") || lineContent.startsWith("class ")) && docstringIndent.length() > lineIndent.length() && docstringIndent.startsWith(lineIndent)) { - return true; + return DocstringState.INCOMPLETE; } } } } } - return false; + return DocstringState.NONE; } } diff --git a/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java b/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java index c2bb1d39a5c9..ce01d2500c3d 100644 --- a/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java +++ b/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java @@ -26,6 +26,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.documentation.docstrings.PyDocstringGenerator; +import com.jetbrains.python.editor.PythonEnterHandler.DocstringState; import com.jetbrains.python.psi.PyDocStringOwner; import org.jetbrains.annotations.NotNull; @@ -45,7 +46,7 @@ public class PythonSpaceHandler extends TypedHandlerDelegate { if (element == null) return Result.CONTINUE; int expectedStringStart = offset - 4; // """ or ''' plus space char final Document document = editor.getDocument(); - if (PythonEnterHandler.atDocCommentStart(element, expectedStringStart, document)) { + if (PythonEnterHandler.canGenerateDocstring(element, expectedStringStart, document) == DocstringState.INCOMPLETE) { final PyDocStringOwner docOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class); if (docOwner != null) { final String quotes = document.getText(TextRange.from(expectedStringStart, 3)); diff --git a/python/testSrc/com/jetbrains/python/PyEditingTest.java b/python/testSrc/com/jetbrains/python/PyEditingTest.java index 2c80434e11be..a48309cd5479 100644 --- a/python/testSrc/com/jetbrains/python/PyEditingTest.java +++ b/python/testSrc/com/jetbrains/python/PyEditingTest.java @@ -44,7 +44,7 @@ public class PyEditingTest extends PyTestCase { } public void testPairedQuotesInRawString() { // PY-263 - assertEquals("x = r''", doTestTyping("x = r", 5, '\'')); + assertEquals("r''", doTestTyping("r", 1, '\'')); } public void testQuotesInString() { // PY-5041 @@ -60,23 +60,23 @@ public class PyEditingTest extends PyTestCase { } public void testAutoClosingQuoteAtRBracket() { - assertEquals("x = '']", doTestTyping("x = ]", 4, '\'')); + assertEquals("'']", doTestTyping("]", 0, '\'')); } public void testAutoClosingQuoteAtRParen() { - assertEquals("x = '')", doTestTyping("x = )", 4, '\'')); + assertEquals("'')", doTestTyping(")", 0, '\'')); } public void testAutoClosingQuoteAtComma() { - assertEquals("x = '',", doTestTyping("x = ,", 4, '\'')); + assertEquals("'',", doTestTyping(",", 0, '\'')); } public void testAutoClosingQuoteAtSpace() { - assertEquals("x = '' ", doTestTyping("x = ", 4, '\'')); + assertEquals("'' ", doTestTyping(" ", 0, '\'')); } public void testAutoCloseTriple() { - assertEquals("x = ''''''", doTestTyping("x = ''", 6, '\'')); + assertEquals("''''''", doTestTyping("''", 2, '\'')); } public void testAutoRemoveTriple() { @@ -84,7 +84,7 @@ public class PyEditingTest extends PyTestCase { } public void testOvertypeFromInside() { - assertEquals("x = ''", doTestTyping("x = ''", 5, '\'')); + assertEquals("''", doTestTyping("''", 1, '\'')); } public void testGreedyBackspace() { // PY-254