mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user