From c9136f1bb8db9873753143f09943c28207053f33 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 26 Jan 2011 13:12:24 +0100 Subject: [PATCH 1/9] PEP 8 compliant option for blank lines between top-level classes/functions (PY-2765) --- .../com/jetbrains/python/formatter/PyBlock.java | 9 +++++++++ .../python/formatter/PyCodeStyleSettings.java | 2 ++ .../PyLanguageCodeStyleSettingsProvider.java | 2 ++ .../testData/formatter/blankLineBetweenMethods.py | 9 ++++----- .../formatter/blankLineBetweenMethods_after.py | 10 ++++------ .../formatter/twoLinesBetweenTopLevelClasses.py | 5 +++++ .../twoLinesBetweenTopLevelClasses_after.py | 6 ++++++ .../formatter/twoLinesBetweenTopLevelFunctions.py | 11 +++++++++++ .../twoLinesBetweenTopLevelFunctions_after.py | 14 ++++++++++++++ python/testData/inspections/AddClass_after.py | 1 + .../refactoring/extractmethod/comment.after.py | 1 + .../refactoring/extractmethod/file.after.py | 1 + .../extractmethod/return_tuple.after.py | 1 + .../refactoring/extractsuperclass/simple.after.py | 1 + .../extractsuperclass/withSuper.after.py | 1 + python/testData/refactoring/pullup/simple.after.py | 1 + .../refactoring/pullup/withComments.after.py | 1 + .../pullup/withMultilineComments.after.py | 1 + .../refactoring/pushdown/multiple.after.py | 2 ++ .../testData/refactoring/pushdown/py346.after.py | 2 ++ .../testData/refactoring/pushdown/simple.after.py | 1 + .../com/jetbrains/python/PyFormatterTest.java | 8 ++++++++ 22 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 python/testData/formatter/twoLinesBetweenTopLevelClasses.py create mode 100644 python/testData/formatter/twoLinesBetweenTopLevelClasses_after.py create mode 100644 python/testData/formatter/twoLinesBetweenTopLevelFunctions.py create mode 100644 python/testData/formatter/twoLinesBetweenTopLevelFunctions_after.py diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 1bc4435d1a48..043bc32468d2 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -263,8 +263,17 @@ public class PyBlock implements ASTBlock { IElementType type2 = childNode2.getElementType(); if (type1 == PyElementTypes.CLASS_DECLARATION) { + if ((type2 == PyElementTypes.CLASS_DECLARATION || type2 == PyElementTypes.FUNCTION_DECLARATION) && + parentType instanceof PyFileElementType) { + return getBlankLinesForOption(getPySettings().BLANK_LINES_BETWEEN_TOP_LEVEL_CLASSES_FUNCTIONS); + } return getBlankLinesForOption(mySettings.BLANK_LINES_AROUND_CLASS); } + if (type1 == PyElementTypes.FUNCTION_DECLARATION && + (type2 == PyElementTypes.CLASS_DECLARATION || type2 == PyElementTypes.FUNCTION_DECLARATION) && + parentType instanceof PyFileElementType) { + return getBlankLinesForOption(getPySettings().BLANK_LINES_BETWEEN_TOP_LEVEL_CLASSES_FUNCTIONS); + } if (type1 == PyElementTypes.FUNCTION_DECLARATION || (type2 == PyElementTypes.FUNCTION_DECLARATION && isStatementOrDeclaration(type1))) { return getBlankLinesForOption(mySettings.BLANK_LINES_AROUND_METHOD); diff --git a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java index 22a67e7053b8..f7232ade3245 100644 --- a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java +++ b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java @@ -14,6 +14,8 @@ public class PyCodeStyleSettings extends CustomCodeStyleSettings { public boolean SPACE_AROUND_EQ_IN_NAMED_PARAMETER = false; public boolean SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT = false; + public int BLANK_LINES_BETWEEN_TOP_LEVEL_CLASSES_FUNCTIONS = 2; + public PyCodeStyleSettings(CodeStyleSettings container) { super("Python", container); } diff --git a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java index 7474cb6197f8..472d6eb224b8 100644 --- a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java +++ b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java @@ -57,6 +57,8 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin "BLANK_LINES_AFTER_IMPORTS", "KEEP_BLANK_LINES_IN_DECLARATIONS", "KEEP_BLANK_LINES_IN_CODE"); + consumer.showCustomOption(PyCodeStyleSettings.class, "BLANK_LINES_BETWEEN_TOP_LEVEL_CLASSES_FUNCTIONS", "Between top-level classes and functions", + BLANK_LINES); } else if (settingsType == SettingsType.WRAPPING_AND_BRACES_SETTINGS) { consumer.showAllStandardOptions(); diff --git a/python/testData/formatter/blankLineBetweenMethods.py b/python/testData/formatter/blankLineBetweenMethods.py index 2f18aaa72111..1c072f1a451e 100644 --- a/python/testData/formatter/blankLineBetweenMethods.py +++ b/python/testData/formatter/blankLineBetweenMethods.py @@ -1,6 +1,5 @@ class C: - pass -def foo(): - pass -def bar(): - pass \ No newline at end of file + def foo(self): + pass + def bar(self): + pass \ No newline at end of file diff --git a/python/testData/formatter/blankLineBetweenMethods_after.py b/python/testData/formatter/blankLineBetweenMethods_after.py index 89a633f6165e..a6b9280d7dbf 100644 --- a/python/testData/formatter/blankLineBetweenMethods_after.py +++ b/python/testData/formatter/blankLineBetweenMethods_after.py @@ -1,8 +1,6 @@ class C: - pass + def foo(self): + pass -def foo(): - pass - -def bar(): - pass \ No newline at end of file + def bar(self): + pass \ No newline at end of file diff --git a/python/testData/formatter/twoLinesBetweenTopLevelClasses.py b/python/testData/formatter/twoLinesBetweenTopLevelClasses.py new file mode 100644 index 000000000000..c2e1ee19ffd3 --- /dev/null +++ b/python/testData/formatter/twoLinesBetweenTopLevelClasses.py @@ -0,0 +1,5 @@ +class C: + pass + +class D: + pass \ No newline at end of file diff --git a/python/testData/formatter/twoLinesBetweenTopLevelClasses_after.py b/python/testData/formatter/twoLinesBetweenTopLevelClasses_after.py new file mode 100644 index 000000000000..55ec52a5afaf --- /dev/null +++ b/python/testData/formatter/twoLinesBetweenTopLevelClasses_after.py @@ -0,0 +1,6 @@ +class C: + pass + + +class D: + pass \ No newline at end of file diff --git a/python/testData/formatter/twoLinesBetweenTopLevelFunctions.py b/python/testData/formatter/twoLinesBetweenTopLevelFunctions.py new file mode 100644 index 000000000000..3a6fd143c7fb --- /dev/null +++ b/python/testData/formatter/twoLinesBetweenTopLevelFunctions.py @@ -0,0 +1,11 @@ +class D: + pass + +def foo(): + pass + +def bar(): + pass + +class C: + pass diff --git a/python/testData/formatter/twoLinesBetweenTopLevelFunctions_after.py b/python/testData/formatter/twoLinesBetweenTopLevelFunctions_after.py new file mode 100644 index 000000000000..99227dec7391 --- /dev/null +++ b/python/testData/formatter/twoLinesBetweenTopLevelFunctions_after.py @@ -0,0 +1,14 @@ +class D: + pass + + +def foo(): + pass + + +def bar(): + pass + + +class C: + pass diff --git a/python/testData/inspections/AddClass_after.py b/python/testData/inspections/AddClass_after.py index 2e177c5b5b46..cecadfbd15f2 100644 --- a/python/testData/inspections/AddClass_after.py +++ b/python/testData/inspections/AddClass_after.py @@ -1,6 +1,7 @@ class Xyzzy(object): pass + class XyzzyTest: def testSimple(self): c = Xyzzy() diff --git a/python/testData/refactoring/extractmethod/comment.after.py b/python/testData/refactoring/extractmethod/comment.after.py index 534163362db3..f4760c22c752 100644 --- a/python/testData/refactoring/extractmethod/comment.after.py +++ b/python/testData/refactoring/extractmethod/comment.after.py @@ -2,5 +2,6 @@ def bar(): print("Hello") + def foo(): bar() diff --git a/python/testData/refactoring/extractmethod/file.after.py b/python/testData/refactoring/extractmethod/file.after.py index 74d8ca43a208..5a313ba35fef 100644 --- a/python/testData/refactoring/extractmethod/file.after.py +++ b/python/testData/refactoring/extractmethod/file.after.py @@ -2,6 +2,7 @@ def bar(base_new, self_new): try: base_new.__init__(self_new) except AttributeError: pass + def __init__(self): for base in self__class__.__bases__: bar(base, self) diff --git a/python/testData/refactoring/extractmethod/return_tuple.after.py b/python/testData/refactoring/extractmethod/return_tuple.after.py index dc924a7a7074..f4ff8f774e74 100644 --- a/python/testData/refactoring/extractmethod/return_tuple.after.py +++ b/python/testData/refactoring/extractmethod/return_tuple.after.py @@ -1,5 +1,6 @@ def bar(p_name_new, params_new): return p_name_new + '(' + ', '.join(params_new) + ')' + def x(p_name, params): return bar(p_name, params), None \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/simple.after.py b/python/testData/refactoring/extractsuperclass/simple.after.py index 937128f2bd30..38248501dc4a 100644 --- a/python/testData/refactoring/extractsuperclass/simple.after.py +++ b/python/testData/refactoring/extractsuperclass/simple.after.py @@ -2,6 +2,7 @@ class Suppa: def foo(self): print "bar" + class Foo(Suppa): def bar(self): print "foo" diff --git a/python/testData/refactoring/extractsuperclass/withSuper.after.py b/python/testData/refactoring/extractsuperclass/withSuper.after.py index 76488d3f4b0f..5f346b590cad 100644 --- a/python/testData/refactoring/extractsuperclass/withSuper.after.py +++ b/python/testData/refactoring/extractsuperclass/withSuper.after.py @@ -2,6 +2,7 @@ class Suppa(object): def foo(self): print "bar" + class Foo(Suppa): def bar(self): print "foo" diff --git a/python/testData/refactoring/pullup/simple.after.py b/python/testData/refactoring/pullup/simple.after.py index 20c544899de8..d8c6cb7a0c2e 100644 --- a/python/testData/refactoring/pullup/simple.after.py +++ b/python/testData/refactoring/pullup/simple.after.py @@ -5,5 +5,6 @@ class Foo: def boo(self): print "rrrrr" + class Boo(Foo): pass \ No newline at end of file diff --git a/python/testData/refactoring/pullup/withComments.after.py b/python/testData/refactoring/pullup/withComments.after.py index 5ddd8d4241f3..ce91117e5413 100644 --- a/python/testData/refactoring/pullup/withComments.after.py +++ b/python/testData/refactoring/pullup/withComments.after.py @@ -6,5 +6,6 @@ class Foo: def boo(self): print "rrrrr" + class Boo(Foo): pass \ No newline at end of file diff --git a/python/testData/refactoring/pullup/withMultilineComments.after.py b/python/testData/refactoring/pullup/withMultilineComments.after.py index 3780dec0eb33..b7c0179871d9 100644 --- a/python/testData/refactoring/pullup/withMultilineComments.after.py +++ b/python/testData/refactoring/pullup/withMultilineComments.after.py @@ -5,6 +5,7 @@ class Foo: def boo(self): print "rrrrr" + class Boo(Foo): ''' this is boo diff --git a/python/testData/refactoring/pushdown/multiple.after.py b/python/testData/refactoring/pushdown/multiple.after.py index 1cdbbe7d84f7..fc67b30f16de 100644 --- a/python/testData/refactoring/pushdown/multiple.after.py +++ b/python/testData/refactoring/pushdown/multiple.after.py @@ -1,6 +1,7 @@ class Foo: pass + class Zope(Foo): def _mine(self): print "zope" @@ -8,6 +9,7 @@ class Zope(Foo): def foo(self): print("a") + class Boo(Foo): def boo(self): print "rrrrr" diff --git a/python/testData/refactoring/pushdown/py346.after.py b/python/testData/refactoring/pushdown/py346.after.py index b60d169e4c9e..278ab4607823 100644 --- a/python/testData/refactoring/pushdown/py346.after.py +++ b/python/testData/refactoring/pushdown/py346.after.py @@ -1,6 +1,7 @@ class A: pass + class B(A): def meth_b1(self): pass @@ -13,6 +14,7 @@ class B(A): def meth_a2(self): pass + class D(A): def meth_d1(self): pass diff --git a/python/testData/refactoring/pushdown/simple.after.py b/python/testData/refactoring/pushdown/simple.after.py index 926f8cf64c19..3d36fa53d6a6 100644 --- a/python/testData/refactoring/pushdown/simple.after.py +++ b/python/testData/refactoring/pushdown/simple.after.py @@ -1,6 +1,7 @@ class Foo: pass + class Boo(Foo): def boo(self): print "rrrrr" diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index 8ed532b07ff1..bacc896ad5b9 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -92,6 +92,14 @@ public class PyFormatterTest extends PyLightFixtureTestCase { doTest(); } + public void testTwoLinesBetweenTopLevelClasses() { // PY-2765 + doTest(); + } + + public void testTwoLinesBetweenTopLevelFunctions() { // PY-2765 + doTest(); + } + private void doTest() { myFixture.configureByFile("formatter/" + getTestName(true) + ".py"); ApplicationManager.getApplication().runWriteAction(new Runnable() { From cb0fca127987fcd0eb11e8cbbf36c29cb444e963 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 26 Jan 2011 17:28:53 +0100 Subject: [PATCH 2/9] consistent behavior for pressing Enter inside a parentheses pair (PY-1947) --- python/src/META-INF/python-plugin-common.xml | 1 + .../editor/PyEnterBetweenBracketsHandler.java | 13 ++++++ .../jetbrains/python/formatter/PyBlock.java | 44 ++++++++++++++++--- .../com/jetbrains/python/PyIndentTest.java | 15 ++++++- 4 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index c9dd720108fe..801590de42f6 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -24,6 +24,7 @@ + diff --git a/python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java b/python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java new file mode 100644 index 000000000000..0ceb1e79eb2e --- /dev/null +++ b/python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java @@ -0,0 +1,13 @@ +package com.jetbrains.python.editor; + +import com.intellij.codeInsight.editorActions.enter.EnterBetweenBracesHandler; + +/** + * @author yole + */ +public class PyEnterBetweenBracketsHandler extends EnterBetweenBracesHandler { + @Override + protected boolean isBracePair(char c1, char c2) { + return c1 == '[' && c2 == ']'; + } +} diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 043bc32468d2..42584d1c7504 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -129,7 +129,7 @@ public class PyBlock implements ASTBlock { !ourBrackets.contains(childType)) { wrap = Wrap.createWrap(WrapType.NORMAL, true); } - if (needListAlignment(child)) { + if (needListAlignment(child) && !isEmptyList(_node.getPsi())) { childAlignment = getAlignmentForChildren(); } } @@ -149,7 +149,7 @@ public class PyBlock implements ASTBlock { childIndent = Indent.getNoneIndent(); } else { - childIndent = Indent.getContinuationIndent(); + childIndent = Indent.getNormalIndent(); } } else if (parentType == PyElementTypes.DICT_LITERAL_EXPRESSION) { @@ -179,6 +179,16 @@ public class PyBlock implements ASTBlock { return new PyBlock(child, childAlignment, childIndent, wrap, mySettings); } + private static boolean isEmptyList(PsiElement psi) { + if (psi instanceof PyDictLiteralExpression) { + return ((PyDictLiteralExpression) psi).getElements().length == 0; + } + if (psi instanceof PySequenceExpression) { + return ((PySequenceExpression) psi).getElements().length == 0; + } + return false; + } + private static boolean isAfterStatementList(ASTNode child) { try { PsiElement prev = sure(child.getPsi().getPrevSibling()); @@ -193,13 +203,26 @@ public class PyBlock implements ASTBlock { } } - private static boolean needListAlignment(ASTNode child) { + private boolean needListAlignment(ASTNode child) { IElementType childType = child.getElementType(); if (PyTokenTypes.OPEN_BRACES.contains(childType)) { return false; } if (PyTokenTypes.CLOSE_BRACES.contains(childType)) { - return PsiTreeUtil.getParentOfType(child.getPsi(), PyArgumentList.class) != null; + PsiElement psi = child.getPsi(); + PyArgumentList argumentList = PsiTreeUtil.getParentOfType(psi, PyArgumentList.class); + if (argumentList != null) { + if (psi != null && psi.getParent() == argumentList && + (child.getElementType() == PyTokenTypes.RPAR || argumentList.getArguments().length == 1)) { + return false; + } + return true; + } + return false; + } + if (_node.getElementType() == PyElementTypes.ARGUMENT_LIST) { + PyArgumentList argList = (PyArgumentList) _node.getPsi(); + return argList != null && argList.getArguments().length > 1; } return true; } @@ -395,7 +418,7 @@ public class PyBlock implements ASTBlock { return null; } - private boolean isImportStatement(IElementType type1) { + private static boolean isImportStatement(IElementType type1) { return (type1 == PyElementTypes.IMPORT_STATEMENT || type1 == PyElementTypes.FROM_IMPORT_STATEMENT); } @@ -495,7 +518,9 @@ public class PyBlock implements ASTBlock { */ - return new ChildAttributes(getChildIndent(newChildIndex), getChildAlignment()); + Indent childIndent = getChildIndent(newChildIndex); + Alignment childAlignment = getChildAlignment(); + return new ChildAttributes(childIndent, childAlignment); } private static boolean dedentAfterLastStatement(PyStatementList statementList) { @@ -507,8 +532,15 @@ public class PyBlock implements ASTBlock { return last instanceof PyReturnStatement || last instanceof PyRaiseStatement || last instanceof PyPassStatement; } + @Nullable private Alignment getChildAlignment() { if (ourListElementTypes.contains(_node.getElementType())) { + if (_node.getPsi() instanceof PyDictLiteralExpression) { + PyKeyValueExpression[] elements = ((PyDictLiteralExpression)_node.getPsi()).getElements(); + if (elements.length == 0) { + return null; + } + } return getAlignmentForChildren(); } return null; diff --git a/python/testSrc/com/jetbrains/python/PyIndentTest.java b/python/testSrc/com/jetbrains/python/PyIndentTest.java index 31a195891490..b46bc7a20ecf 100644 --- a/python/testSrc/com/jetbrains/python/PyIndentTest.java +++ b/python/testSrc/com/jetbrains/python/PyIndentTest.java @@ -77,6 +77,13 @@ public class PyIndentTest extends PyLightFixtureTestCase { " })"); } + public void testAlignInDictInParams() { // PY-1947 + doTest("foobar({})", + "foobar({\n" + + " \n" + + "})"); + } + public void testClass() { doTest("class A:\n" + " print a", "class A:\n" + " print a\n" + " "); } @@ -128,7 +135,7 @@ public class PyIndentTest extends PyLightFixtureTestCase { } public void testEnterInEmptyList() { - doTest("[]", "[\n]"); + doTest("[]", "[\n \n]"); } public void testEnterInEmptyDict() { @@ -149,6 +156,12 @@ public class PyIndentTest extends PyLightFixtureTestCase { ")"); } + public void testEnterInNonEmptyArgList() { // PY-1947 + doTest("Task(params=1)", + "Task(\n" + + " params=1)"); + } + public void testIndentAfterComment() { // PY-641 doTest("def foo():\n #some_call()\n another_call()", "def foo():\n #some_call()\n \n another_call()"); } From 4fc3b64a86307072096bba6f0c49f19e005572e9 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 26 Jan 2011 17:40:25 +0100 Subject: [PATCH 3/9] Ctrl-Enter splits line without adding \ (PY-2442) --- .../src/com/jetbrains/python/editor/PythonEnterHandler.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java index 079e0f40a707..e13480e7cfbe 100644 --- a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java +++ b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java @@ -7,6 +7,7 @@ import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; +import com.intellij.openapi.editor.actions.SplitLineAction; import com.intellij.openapi.util.Ref; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; @@ -46,6 +47,10 @@ public class PythonEnterHandler implements EnterHandlerDelegate { if (!(file instanceof PyFile)) { return Result.Continue; } + final Boolean isSplitLine = DataManager.getInstance().loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY); + if (isSplitLine != null) { + return Result.Continue; + } Document doc = editor.getDocument(); PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc); final int offset = caretOffset.get(); From 38d3fe6504e2016e27f7dffd9a9555c70a70826d Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 26 Jan 2011 18:08:32 +0100 Subject: [PATCH 4/9] fix alignment when pressing Enter in list literal (PY-2407); thankfully the logic for calculating whether a child needs alignment can be much cleaner now --- .../com/jetbrains/python/formatter/PyBlock.java | 17 ++++++++++------- .../com/jetbrains/python/PyIndentTest.java | 10 ++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 42584d1c7504..8b05d34e6155 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -209,13 +209,8 @@ public class PyBlock implements ASTBlock { return false; } if (PyTokenTypes.CLOSE_BRACES.contains(childType)) { - PsiElement psi = child.getPsi(); - PyArgumentList argumentList = PsiTreeUtil.getParentOfType(psi, PyArgumentList.class); - if (argumentList != null) { - if (psi != null && psi.getParent() == argumentList && - (child.getElementType() == PyTokenTypes.RPAR || argumentList.getArguments().length == 1)) { - return false; - } + ASTNode prevNonSpace = findPrevNonSpaceNode(child); + if (prevNonSpace != null && prevNonSpace.getElementType() == PyTokenTypes.COMMA) { return true; } return false; @@ -227,6 +222,14 @@ public class PyBlock implements ASTBlock { return true; } + @Nullable + private static ASTNode findPrevNonSpaceNode(ASTNode node) { + do { + node = node.getTreePrev(); + } while(node != null && (node.getElementType() == TokenType.WHITE_SPACE || PyTokenTypes.WHITESPACE.contains(node.getElementType()))); + return node; + } + private static boolean hasLineBreaksBefore(ASTNode child, int minCount) { return isWhitespaceWithLineBreaks(TreeUtil.findLastLeaf(child.getTreePrev()), minCount) || isWhitespaceWithLineBreaks(child.getFirstChildNode(), minCount); diff --git a/python/testSrc/com/jetbrains/python/PyIndentTest.java b/python/testSrc/com/jetbrains/python/PyIndentTest.java index b46bc7a20ecf..cb224633ef9a 100644 --- a/python/testSrc/com/jetbrains/python/PyIndentTest.java +++ b/python/testSrc/com/jetbrains/python/PyIndentTest.java @@ -71,6 +71,16 @@ public class PyIndentTest extends PyLightFixtureTestCase { doTest("__all__ = [a for", "__all__ = [a for\n" + " "); } + public void testAlignInListOnceMore() { // PY-2407 + doTest("for id in [\"SEARCH_RESULT_ATTRIBUTES\", \n" + + " \"WRITE_SEARCH_RESULT_ATTRIBUTES\", \n" + + " \"IDENTIFIER_UNDER_CARET_ATTRIBUTES\",]:", + "for id in [\"SEARCH_RESULT_ATTRIBUTES\", \n" + + " \"WRITE_SEARCH_RESULT_ATTRIBUTES\", \n" + + " \"IDENTIFIER_UNDER_CARET_ATTRIBUTES\",\n" + + " ]:"); + } + public void testAlignInDict() { doTest("some_call({'aaa': 'v1',})", "some_call({'aaa': 'v1',\n" + From ca1f5b809858c5f7b29d51bc5e8bc13fd3122508 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 26 Jan 2011 19:30:11 +0100 Subject: [PATCH 5/9] 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); } From 13bea54888ba34ef73aac43d4490a57dff583fad Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 26 Jan 2011 20:15:47 +0100 Subject: [PATCH 6/9] slice list parsing improved (PY-1928) --- .../python/parsing/ExpressionParsing.java | 14 +++++++++++--- python/testData/formatter/specialSlice.py | 1 + python/testData/formatter/specialSlice_after.py | 1 + python/testData/psi/SliceList.py | 1 + python/testData/psi/SliceList.txt | 13 +++++++++++++ .../com/jetbrains/python/PyFormatterTest.java | 4 ++++ .../com/jetbrains/python/PythonParsingTest.java | 4 ++++ 7 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 python/testData/formatter/specialSlice.py create mode 100644 python/testData/formatter/specialSlice_after.py create mode 100644 python/testData/psi/SliceList.py create mode 100644 python/testData/psi/SliceList.txt diff --git a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java index 09430796684e..8047264147be 100644 --- a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java +++ b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java @@ -311,10 +311,14 @@ public class ExpressionParsing extends Parsing { parseSliceEnd(expr, sliceItemStart); } else { - parseExpressionOptional(); - if (myBuilder.getTokenType() == PyTokenTypes.COLON) { + parseSingleExpression(false); + if (atToken(PyTokenTypes.COLON)) { parseSliceEnd(expr, sliceItemStart); } + else if (atToken(PyTokenTypes.COMMA)) { + sliceItemStart.done(PyElementTypes.SLICE_ITEM); + parseSliceListTail(expr); + } else { sliceItemStart.drop(); checkMatches(PyTokenTypes.RBRACKET, message("PARSE.expected.rbracket")); @@ -376,9 +380,13 @@ public class ExpressionParsing extends Parsing { } } + parseSliceListTail(exprStart); + } + + private void parseSliceListTail(PsiBuilder.Marker exprStart) { while (atToken(PyTokenTypes.COMMA)) { nextToken(); - sliceItemStart = myBuilder.mark(); + PsiBuilder.Marker sliceItemStart = myBuilder.mark(); parseTestExpression(false, false); if (matchToken(PyTokenTypes.COLON)) { parseTestExpression(false, false); diff --git a/python/testData/formatter/specialSlice.py b/python/testData/formatter/specialSlice.py new file mode 100644 index 000000000000..ba8e7fad0df1 --- /dev/null +++ b/python/testData/formatter/specialSlice.py @@ -0,0 +1 @@ +a[b1,:] \ No newline at end of file diff --git a/python/testData/formatter/specialSlice_after.py b/python/testData/formatter/specialSlice_after.py new file mode 100644 index 000000000000..98b951a3c1f9 --- /dev/null +++ b/python/testData/formatter/specialSlice_after.py @@ -0,0 +1 @@ +a[b1, :] \ No newline at end of file diff --git a/python/testData/psi/SliceList.py b/python/testData/psi/SliceList.py new file mode 100644 index 000000000000..ba8e7fad0df1 --- /dev/null +++ b/python/testData/psi/SliceList.py @@ -0,0 +1 @@ +a[b1,:] \ No newline at end of file diff --git a/python/testData/psi/SliceList.txt b/python/testData/psi/SliceList.txt new file mode 100644 index 000000000000..c0c01a09a89c --- /dev/null +++ b/python/testData/psi/SliceList.txt @@ -0,0 +1,13 @@ +PyFile:SliceList.py + PyExpressionStatement + PySliceExpression + PyReferenceExpression: a + PsiElement(Py:IDENTIFIER)('a') + PsiElement(Py:LBRACKET)('[') + PySliceItem + PyReferenceExpression: b1 + PsiElement(Py:IDENTIFIER)('b1') + PsiElement(Py:COMMA)(',') + PySliceItem + PsiElement(Py:COLON)(':') + PsiElement(Py:RBRACKET)(']') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index bacc896ad5b9..ecb58ad554ae 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -100,6 +100,10 @@ public class PyFormatterTest extends PyLightFixtureTestCase { doTest(); } + public void testSpecialSlice() { // PY-1928 + doTest(); + } + private void doTest() { myFixture.configureByFile("formatter/" + getTestName(true) + ".py"); ApplicationManager.getApplication().runWriteAction(new Runnable() { diff --git a/python/testSrc/com/jetbrains/python/PythonParsingTest.java b/python/testSrc/com/jetbrains/python/PythonParsingTest.java index 78973a36795f..3cd473572f7e 100644 --- a/python/testSrc/com/jetbrains/python/PythonParsingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonParsingTest.java @@ -243,6 +243,10 @@ public class PythonParsingTest extends ParsingTestCase { doTest(); } + public void testSliceList() { // PY-1928 + doTest(); + } + public void doTest() { doTest(LanguageLevel.PYTHON25); } From 13444c3aa27c8870f4f7f03374e0857dc5c5281b Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Thu, 27 Jan 2011 02:01:49 +0300 Subject: [PATCH 7/9] fix compile --- python/src/com/jetbrains/python/editor/PythonEnterHandler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java index e13480e7cfbe..0d1581b4c1a1 100644 --- a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java +++ b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java @@ -7,7 +7,6 @@ import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; -import com.intellij.openapi.editor.actions.SplitLineAction; import com.intellij.openapi.util.Ref; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; @@ -47,7 +46,7 @@ public class PythonEnterHandler implements EnterHandlerDelegate { if (!(file instanceof PyFile)) { return Result.Continue; } - final Boolean isSplitLine = DataManager.getInstance().loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY); + final Boolean isSplitLine = null; // TODO yole, fix compile //DataManager.getInstance().loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY); if (isSplitLine != null) { return Result.Continue; } From 282a6491c740084893109b6b2f54ae754cf368c0 Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Thu, 27 Jan 2011 02:03:57 +0300 Subject: [PATCH 8/9] fix compile --- .../jetbrains/python/editor/PyEnterBetweenBracketsHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java b/python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java index 0ceb1e79eb2e..a97237fb33b2 100644 --- a/python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java +++ b/python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java @@ -6,7 +6,7 @@ import com.intellij.codeInsight.editorActions.enter.EnterBetweenBracesHandler; * @author yole */ public class PyEnterBetweenBracketsHandler extends EnterBetweenBracesHandler { - @Override + // TODO yole, fix compile @Override protected boolean isBracePair(char c1, char c2) { return c1 == '[' && c2 == ']'; } From 1a5a291880530a24513a76ec850ea29950ce45f6 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 27 Jan 2011 12:11:23 +0100 Subject: [PATCH 9/9] restore changes reverted because of failed push --- .../jetbrains/python/editor/PyEnterBetweenBracketsHandler.java | 2 +- python/src/com/jetbrains/python/editor/PythonEnterHandler.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java b/python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java index a97237fb33b2..0ceb1e79eb2e 100644 --- a/python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java +++ b/python/src/com/jetbrains/python/editor/PyEnterBetweenBracketsHandler.java @@ -6,7 +6,7 @@ import com.intellij.codeInsight.editorActions.enter.EnterBetweenBracesHandler; * @author yole */ public class PyEnterBetweenBracketsHandler extends EnterBetweenBracesHandler { - // TODO yole, fix compile @Override + @Override protected boolean isBracePair(char c1, char c2) { return c1 == '[' && c2 == ']'; } diff --git a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java index 0d1581b4c1a1..e13480e7cfbe 100644 --- a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java +++ b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java @@ -7,6 +7,7 @@ import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; +import com.intellij.openapi.editor.actions.SplitLineAction; import com.intellij.openapi.util.Ref; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; @@ -46,7 +47,7 @@ public class PythonEnterHandler implements EnterHandlerDelegate { if (!(file instanceof PyFile)) { return Result.Continue; } - final Boolean isSplitLine = null; // TODO yole, fix compile //DataManager.getInstance().loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY); + final Boolean isSplitLine = DataManager.getInstance().loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY); if (isSplitLine != null) { return Result.Continue; }