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;
+ }
}
}
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));
+ }
}
}
}
}
}
+
+ 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.
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;
}
}
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;
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));
}
public void testPairedQuotesInRawString() { // PY-263
- assertEquals("x = r''", doTestTyping("x = r", 5, '\''));
+ assertEquals("r''", doTestTyping("r", 1, '\''));
}
public void testQuotesInString() { // PY-5041
}
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() {
}
public void testOvertypeFromInside() {
- assertEquals("x = ''", doTestTyping("x = ''", 5, '\''));
+ assertEquals("''", doTestTyping("''", 1, '\''));
}
public void testGreedyBackspace() { // PY-254