PY-18486 Better solution: on enter generate content for empty docstrings too

I reverted previously modified test data in PyEditingTest, since
this fix doesn't affect top-level string literals treated as module
docstrings. They are processed in BaseQuoteHandler as earlier.
This commit is contained in:
Mikhail Golubev
2016-03-30 15:47:28 +03:00
parent c2524b15a2
commit 6c78cf08ab
4 changed files with 48 additions and 27 deletions
@@ -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);
}
@@ -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;
}
}
@@ -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));
@@ -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