From ca1f5b809858c5f7b29d51bc5e8bc13fd3122508 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 26 Jan 2011 19:30:11 +0100 Subject: [PATCH] tweak parsing of incomplete dict literals, consistent alignment/indentation when pressing Enter between dict key and value (PY-1469) --- .../jetbrains/python/formatter/PyBlock.java | 39 +++++++++++++++---- .../python/parsing/ExpressionParsing.java | 5 ++- python/testData/psi/IncompleteDict.py | 1 + python/testData/psi/IncompleteDict.txt | 18 +++++++++ .../com/jetbrains/python/PyIndentTest.java | 20 ++++++++++ .../jetbrains/python/PythonParsingTest.java | 4 ++ 6 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 python/testData/psi/IncompleteDict.py create mode 100644 python/testData/psi/IncompleteDict.txt diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 8b05d34e6155..10e355f9abb4 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -171,6 +171,12 @@ public class PyBlock implements ASTBlock { childAlignment = getAlignmentForChildren(); } } + else if (parentType == PyElementTypes.KEY_VALUE_EXPRESSION) { + PyKeyValueExpression keyValue = (PyKeyValueExpression) _node.getPsi(); + if (keyValue != null && child.getPsi() == keyValue.getValue()) { + childIndent = Indent.getNormalIndent(); + } + } if (isAfterStatementList(child) && !hasLineBreaksBefore(child, 2)) { // maybe enter was pressed and cut us from a previous (nested) statement list childIndent = Indent.getNormalIndent(); @@ -543,6 +549,10 @@ public class PyBlock implements ASTBlock { if (elements.length == 0) { return null; } + PyKeyValueExpression last = elements[elements.length-1]; + if (last.getValue() == null) { // incomplete + return null; + } } return getAlignmentForChildren(); } @@ -550,17 +560,12 @@ public class PyBlock implements ASTBlock { } private Indent getChildIndent(int newChildIndex) { + ASTNode afterNode = getAfterNode(newChildIndex); ASTNode lastChild = getLastNonSpaceChild(_node, false); if (lastChild != null && lastChild.getElementType() == PyElementTypes.STATEMENT_LIST && _subBlocks.size() >= newChildIndex) { - if (newChildIndex == 0) { // block text contains backslash line wrappings, child block list not built + if (afterNode == null) { return Indent.getNoneIndent(); } - int prevIndex = newChildIndex - 1; - while (prevIndex > 0 && _subBlocks.get(prevIndex).getNode().getElementType() == PyTokenTypes.END_OF_LINE_COMMENT) { - prevIndex--; - } - PyBlock insertAfterBlock = _subBlocks.get(prevIndex); - ASTNode afterNode = insertAfterBlock.getNode(); // handle pressing Enter after colon and before first statement in // existing statement list @@ -614,6 +619,13 @@ public class PyBlock implements ASTBlock { } } + if (afterNode != null && afterNode.getElementType() == PyElementTypes.KEY_VALUE_EXPRESSION) { + PyKeyValueExpression keyValue = (PyKeyValueExpression) afterNode.getPsi(); + if (keyValue != null && keyValue.getValue() == null) { // incomplete + return Indent.getContinuationIndent(true); + } + } + // constructs that imply indent for their children if (ourListElementTypes.contains(_node.getElementType()) || _node.getPsi() instanceof PyStatementPart) { return Indent.getNormalIndent(); @@ -633,6 +645,19 @@ public class PyBlock implements ASTBlock { */ } + @Nullable + private ASTNode getAfterNode(int newChildIndex) { + if (newChildIndex == 0) { // block text contains backslash line wrappings, child block list not built + return null; + } + int prevIndex = newChildIndex - 1; + while (prevIndex > 0 && _subBlocks.get(prevIndex).getNode().getElementType() == PyTokenTypes.END_OF_LINE_COMMENT) { + prevIndex--; + } + PyBlock insertAfterBlock = _subBlocks.get(prevIndex); + return insertAfterBlock.getNode(); + } + private static ASTNode getLastNonSpaceChild(ASTNode node, boolean acceptError) { ASTNode lastChild = node.getLastChildNode(); while (lastChild != null && diff --git a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java index 3c4073bba423..09430796684e 100644 --- a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java +++ b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java @@ -188,7 +188,10 @@ public class ExpressionParsing extends Parsing { private void parseDictLiteralTail(PsiBuilder.Marker startMarker, PsiBuilder.Marker firstKeyValueMarker) { if (!parseSingleExpression(false)) { myBuilder.error("expression expected"); - firstKeyValueMarker.drop(); + firstKeyValueMarker.done(PyElementTypes.KEY_VALUE_EXPRESSION); + if (atToken(PyTokenTypes.RBRACE)) { + myBuilder.advanceLexer(); + } startMarker.done(PyElementTypes.DICT_LITERAL_EXPRESSION); return; } diff --git a/python/testData/psi/IncompleteDict.py b/python/testData/psi/IncompleteDict.py new file mode 100644 index 000000000000..f047ba139c62 --- /dev/null +++ b/python/testData/psi/IncompleteDict.py @@ -0,0 +1 @@ +some_dict = { 'key': } \ No newline at end of file diff --git a/python/testData/psi/IncompleteDict.txt b/python/testData/psi/IncompleteDict.txt new file mode 100644 index 000000000000..e493cb0219c5 --- /dev/null +++ b/python/testData/psi/IncompleteDict.txt @@ -0,0 +1,18 @@ +PyFile:IncompleteDict.py + PyAssignmentStatement + PyTargetExpression: some_dict + PsiElement(Py:IDENTIFIER)('some_dict') + PsiWhiteSpace(' ') + PsiElement(Py:EQ)('=') + PsiWhiteSpace(' ') + PyDictLiteralExpression + PsiElement(Py:LBRACE)('{') + PsiWhiteSpace(' ') + PyKeyValueExpression + PyStringLiteralExpression: key + PsiElement(Py:STRING_LITERAL)(''key'') + PsiElement(Py:COLON)(':') + PsiErrorElement:expression expected + + PsiWhiteSpace(' ') + PsiElement(Py:RBRACE)('}') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyIndentTest.java b/python/testSrc/com/jetbrains/python/PyIndentTest.java index cb224633ef9a..5cd39c0783d6 100644 --- a/python/testSrc/com/jetbrains/python/PyIndentTest.java +++ b/python/testSrc/com/jetbrains/python/PyIndentTest.java @@ -94,6 +94,26 @@ public class PyIndentTest extends PyLightFixtureTestCase { "})"); } + public void testIndentDictMissingValue() { // PY-1469 + doTest("some_dict = {\n" + + " 'key': \n" + + "}", + "some_dict = {\n" + + " 'key': \n" + + " \n" + + "}"); + } + + public void testIndentDictStringValue() { // PY-1469 + doTest("some_dict = {\n" + + " 'key': ''\n" + + "}", + "some_dict = {\n" + + " 'key': \n" + + " ''\n" + + "}"); + } + public void testClass() { doTest("class A:\n" + " print a", "class A:\n" + " print a\n" + " "); } diff --git a/python/testSrc/com/jetbrains/python/PythonParsingTest.java b/python/testSrc/com/jetbrains/python/PythonParsingTest.java index b3d8485f9995..78973a36795f 100644 --- a/python/testSrc/com/jetbrains/python/PythonParsingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonParsingTest.java @@ -239,6 +239,10 @@ public class PythonParsingTest extends ParsingTestCase { doTest(); } + public void testIncompleteDict() { + doTest(); + } + public void doTest() { doTest(LanguageLevel.PYTHON25); }