From 9afa34305d94068494a275c5f4dbd6471cc50806 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 11 Oct 2018 15:33:34 +0300 Subject: [PATCH] PY-20909 Add an option that allows to use continuation indent for collection literals This setting also applies to the corresponding comprehensions and parenthesized generator expressions for consistency. Also, I grouped two similar options together in settings. --- .../com/jetbrains/python/PyBundle.properties | 5 +- .../jetbrains/python/formatter/PyBlock.java | 24 ++++++--- .../python/formatter/PyCodeStyleSettings.java | 1 + .../formatter/PyOtherCodeStylePanel.form | 51 +++++++++++++------ .../formatter/PyOtherCodeStylePanel.java | 38 +++++++++----- ...onIndentForCollectionsAndComprehensions.py | 51 +++++++++++++++++++ ...mprehensionsHangingIndentOfClosingBrace.py | 51 +++++++++++++++++++ ...nsionsHangingIndentOfClosingBrace_after.py | 51 +++++++++++++++++++ ...ntForCollectionsAndComprehensions_after.py | 51 +++++++++++++++++++ .../com/jetbrains/python/PyEditingTest.java | 29 ++++++++++- .../com/jetbrains/python/PyFormatterTest.java | 13 +++++ 11 files changed, 330 insertions(+), 35 deletions(-) create mode 100644 python/testData/formatter/continuationIndentForCollectionsAndComprehensions.py create mode 100644 python/testData/formatter/continuationIndentForCollectionsAndComprehensionsHangingIndentOfClosingBrace.py create mode 100644 python/testData/formatter/continuationIndentForCollectionsAndComprehensionsHangingIndentOfClosingBrace_after.py create mode 100644 python/testData/formatter/continuationIndentForCollectionsAndComprehensions_after.py diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index c63097fa8566..de59b81fd8e5 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -1029,7 +1029,10 @@ formatter.panel.dict.alignment.align.on.colon=Align on colon formatter.panel.dict.alignment.align.on.value=Align on value formatter.panel.dict.alignment.label=Dict alignment: formatter.panel.add.trailing.line.feed=Add line feed at the end of file -formatter.panel.use.continuation.indent.for.arguments=Use continuation indent for arguments + +formatter.panel.use.continuation.indent.for.title=Use continuation indent for +formatter.panel.use.continuation.indent.for.arguments=Method call arguments +formatter.panel.use.continuation.indent.for.collection.literals=Collections and comprehensions formatter.left.bracket = Left bracket formatter.around.eq.in.named.parameter = Around = in named parameter diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 638831909e0f..fc9ec3779409 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -58,6 +58,13 @@ public class PyBlock implements ASTBlock { PyElementTypes.SUBSCRIPTION_EXPRESSION, PyElementTypes.GENERATOR_EXPRESSION); + private static final TokenSet ourCollectionLiteralTypes = TokenSet.create(PyElementTypes.LIST_LITERAL_EXPRESSION, + PyElementTypes.LIST_COMP_EXPRESSION, + PyElementTypes.DICT_LITERAL_EXPRESSION, + PyElementTypes.DICT_COMP_EXPRESSION, + PyElementTypes.SET_LITERAL_EXPRESSION, + PyElementTypes.SET_COMP_EXPRESSION); + private static final TokenSet ourBrackets = TokenSet.create(PyTokenTypes.LPAR, PyTokenTypes.RPAR, PyTokenTypes.LBRACE, PyTokenTypes.RBRACE, PyTokenTypes.LBRACKET, PyTokenTypes.RBRACKET); @@ -275,7 +282,7 @@ public class PyBlock implements ASTBlock { childIndent = Indent.getNoneIndent(); } else { - childIndent = Indent.getNormalIndent(); + childIndent = settings.USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS ? Indent.getContinuationIndent() : Indent.getNormalIndent(); } } else if (parentType == PyElementTypes.DICT_LITERAL_EXPRESSION || parentType == PyElementTypes.SET_LITERAL_EXPRESSION || @@ -284,7 +291,7 @@ public class PyBlock implements ASTBlock { childIndent = Indent.getNoneIndent(); } else { - childIndent = Indent.getNormalIndent(); + childIndent = settings.USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS ? Indent.getContinuationIndent() : Indent.getNormalIndent(); } } else if (parentType == PyElementTypes.STRING_LITERAL_EXPRESSION) { @@ -353,7 +360,8 @@ public class PyBlock implements ASTBlock { childIndent = Indent.getNoneIndent(); } else { - childIndent = isIndentNext(child) ? Indent.getContinuationIndent() : Indent.getNormalIndent(); + final boolean useWiderIndent = isIndentNext(child) || settings.USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS; + childIndent = useWiderIndent ? Indent.getContinuationIndent() : Indent.getNormalIndent(); } } else if (parentType == PyElementTypes.ARGUMENT_LIST || parentType == PyElementTypes.PARAMETER_LIST) { @@ -399,7 +407,7 @@ public class PyBlock implements ASTBlock { } if (childType == PyElementTypes.KEY_VALUE_EXPRESSION && isChildOfDictLiteral(child)) { childWrap = myDictWrapping; - childIndent = Indent.getNormalIndent(); + childIndent = settings.USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS ? Indent.getContinuationIndent() : Indent.getNormalIndent(); } if (isAfterStatementList(child) && @@ -1049,11 +1057,15 @@ public class PyBlock implements ASTBlock { final IElementType parentType = myNode.getElementType(); // constructs that imply indent for their children + final PyCodeStyleSettings settings = myContext.getPySettings(); if (parentType == PyElementTypes.PARAMETER_LIST || - (parentType == PyElementTypes.ARGUMENT_LIST && myContext.getPySettings().USE_CONTINUATION_INDENT_FOR_ARGUMENTS)) { + (parentType == PyElementTypes.ARGUMENT_LIST && settings.USE_CONTINUATION_INDENT_FOR_ARGUMENTS)) { return Indent.getContinuationIndent(); } - if (ourListElementTypes.contains(parentType) || myNode.getPsi() instanceof PyStatementPart) { + if (ourCollectionLiteralTypes.contains(parentType) || parentType == PyElementTypes.TUPLE_EXPRESSION) { + return settings.USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS ? Indent.getContinuationIndent() : Indent.getNormalIndent(); + } + else if (ourListElementTypes.contains(parentType) || myNode.getPsi() instanceof PyStatementPart) { return Indent.getNormalIndent(); } diff --git a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java index c5fc3092565c..5553256fa4aa 100644 --- a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java +++ b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java @@ -96,6 +96,7 @@ public class PyCodeStyleSettings extends CustomCodeStyleSettings { * the same indentation level for arguments as for parameters. */ public boolean USE_CONTINUATION_INDENT_FOR_ARGUMENTS = false; + public boolean USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS = false; public boolean OPTIMIZE_IMPORTS_SORT_IMPORTS = true; public boolean OPTIMIZE_IMPORTS_SORT_NAMES_IN_FROM_IMPORTS = false; diff --git a/python/src/com/jetbrains/python/formatter/PyOtherCodeStylePanel.form b/python/src/com/jetbrains/python/formatter/PyOtherCodeStylePanel.form index d169d353ef02..bd8761adc334 100644 --- a/python/src/com/jetbrains/python/formatter/PyOtherCodeStylePanel.form +++ b/python/src/com/jetbrains/python/formatter/PyOtherCodeStylePanel.form @@ -3,7 +3,7 @@ - + @@ -23,7 +23,7 @@ - + @@ -31,15 +31,9 @@ - - - - - - - + @@ -47,20 +41,47 @@ - + - + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/src/com/jetbrains/python/formatter/PyOtherCodeStylePanel.java b/python/src/com/jetbrains/python/formatter/PyOtherCodeStylePanel.java index 396fc1fae737..b573ef8b1fba 100644 --- a/python/src/com/jetbrains/python/formatter/PyOtherCodeStylePanel.java +++ b/python/src/com/jetbrains/python/formatter/PyOtherCodeStylePanel.java @@ -44,6 +44,7 @@ public class PyOtherCodeStylePanel extends CodeStyleAbstractPanel { private JPanel myPanel; private JBCheckBox myAddTrailingBlankLineCheckbox; private JBCheckBox myUseContinuationIndentForArguments; + private JBCheckBox myUseContinuationIndentForCollectionsAndComprehensions; private ComboBox myDictAlignmentCombo; private JPanel myPreviewPanel; @@ -104,14 +105,16 @@ public class PyOtherCodeStylePanel extends CodeStyleAbstractPanel { @Override protected void resetImpl(CodeStyleSettings settings) { + final PyCodeStyleSettings pySettings = getCustomSettings(settings); for (DictAlignment alignment : DictAlignment.values()) { - if (getCustomSettings(settings).DICT_ALIGNMENT == alignment.asInt()) { + if (pySettings.DICT_ALIGNMENT == alignment.asInt()) { myDictAlignmentCombo.setSelectedItem(alignment); break; } } - myAddTrailingBlankLineCheckbox.setSelected(getCustomSettings(settings).BLANK_LINE_AT_FILE_END); - myUseContinuationIndentForArguments.setSelected(getCustomSettings(settings).USE_CONTINUATION_INDENT_FOR_ARGUMENTS); + myAddTrailingBlankLineCheckbox.setSelected(pySettings.BLANK_LINE_AT_FILE_END); + myUseContinuationIndentForArguments.setSelected(pySettings.USE_CONTINUATION_INDENT_FOR_ARGUMENTS); + myUseContinuationIndentForCollectionsAndComprehensions.setSelected(pySettings.USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS); } @Override @@ -120,6 +123,7 @@ public class PyOtherCodeStylePanel extends CodeStyleAbstractPanel { customSettings.DICT_ALIGNMENT = getDictAlignmentAsInt(); customSettings.BLANK_LINE_AT_FILE_END = ensureTrailingBlankLine(); customSettings.USE_CONTINUATION_INDENT_FOR_ARGUMENTS = useContinuationIndentForArguments(); + customSettings.USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS = useContinuationIndentForCollectionLiterals(); } @Override @@ -127,7 +131,8 @@ public class PyOtherCodeStylePanel extends CodeStyleAbstractPanel { final PyCodeStyleSettings customSettings = getCustomSettings(settings); return customSettings.DICT_ALIGNMENT != getDictAlignmentAsInt() || customSettings.BLANK_LINE_AT_FILE_END != ensureTrailingBlankLine() || - customSettings.USE_CONTINUATION_INDENT_FOR_ARGUMENTS != useContinuationIndentForArguments() ; + customSettings.USE_CONTINUATION_INDENT_FOR_ARGUMENTS != useContinuationIndentForArguments() || + customSettings.USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS != useContinuationIndentForCollectionLiterals(); } @Override @@ -152,13 +157,22 @@ public class PyOtherCodeStylePanel extends CodeStyleAbstractPanel { return myUseContinuationIndentForArguments.isSelected(); } + protected boolean useContinuationIndentForCollectionLiterals() { + return myUseContinuationIndentForCollectionsAndComprehensions.isSelected(); + } + public static final String PREVIEW = "x = max(\n" + - " 1,\n" + - " 2,\n" + - " 3)\n" + - "\n" + - "{\n" + - " \"green\": 42,\n" + - " \"eggs and ham\": -0.0e0\n" + - "}"; + " 1,\n" + + " 2,\n" + + " 3)\n" + + "\n" + + "{\n" + + " \"green\": 42,\n" + + " \"eggs and ham\": -0.0e0\n" + + "}\n" + + "\n" + + "odds = [\n" + + " num for num in range(42)\n" + + " if num % 2 != 0 \n" + + "]"; } diff --git a/python/testData/formatter/continuationIndentForCollectionsAndComprehensions.py b/python/testData/formatter/continuationIndentForCollectionsAndComprehensions.py new file mode 100644 index 000000000000..eec05b9189f1 --- /dev/null +++ b/python/testData/formatter/continuationIndentForCollectionsAndComprehensions.py @@ -0,0 +1,51 @@ +l = [ + 1, + 2, + 3 +] + +lc = [ + x + for x + in range(42) + if x +] + +s = { + 1, + 2, + 3 +} + +sc = { + x + for x + in range(42) + if x +} + +t = ( + 1, + 2, + 3 +) + +g = ( + x + for x + in range(42) + if x +) + +d = { + 1: True, + 2: False, + 3: None +} + +dc = { + x: None + for x + in range(42) + if x +} diff --git a/python/testData/formatter/continuationIndentForCollectionsAndComprehensionsHangingIndentOfClosingBrace.py b/python/testData/formatter/continuationIndentForCollectionsAndComprehensionsHangingIndentOfClosingBrace.py new file mode 100644 index 000000000000..eec05b9189f1 --- /dev/null +++ b/python/testData/formatter/continuationIndentForCollectionsAndComprehensionsHangingIndentOfClosingBrace.py @@ -0,0 +1,51 @@ +l = [ + 1, + 2, + 3 +] + +lc = [ + x + for x + in range(42) + if x +] + +s = { + 1, + 2, + 3 +} + +sc = { + x + for x + in range(42) + if x +} + +t = ( + 1, + 2, + 3 +) + +g = ( + x + for x + in range(42) + if x +) + +d = { + 1: True, + 2: False, + 3: None +} + +dc = { + x: None + for x + in range(42) + if x +} diff --git a/python/testData/formatter/continuationIndentForCollectionsAndComprehensionsHangingIndentOfClosingBrace_after.py b/python/testData/formatter/continuationIndentForCollectionsAndComprehensionsHangingIndentOfClosingBrace_after.py new file mode 100644 index 000000000000..46f8ed6ac538 --- /dev/null +++ b/python/testData/formatter/continuationIndentForCollectionsAndComprehensionsHangingIndentOfClosingBrace_after.py @@ -0,0 +1,51 @@ +l = [ + 1, + 2, + 3 + ] + +lc = [ + x + for x + in range(42) + if x + ] + +s = { + 1, + 2, + 3 + } + +sc = { + x + for x + in range(42) + if x + } + +t = ( + 1, + 2, + 3 + ) + +g = ( + x + for x + in range(42) + if x + ) + +d = { + 1: True, + 2: False, + 3: None + } + +dc = { + x: None + for x + in range(42) + if x + } diff --git a/python/testData/formatter/continuationIndentForCollectionsAndComprehensions_after.py b/python/testData/formatter/continuationIndentForCollectionsAndComprehensions_after.py new file mode 100644 index 000000000000..a6e69a452f59 --- /dev/null +++ b/python/testData/formatter/continuationIndentForCollectionsAndComprehensions_after.py @@ -0,0 +1,51 @@ +l = [ + 1, + 2, + 3 +] + +lc = [ + x + for x + in range(42) + if x +] + +s = { + 1, + 2, + 3 +} + +sc = { + x + for x + in range(42) + if x +} + +t = ( + 1, + 2, + 3 +) + +g = ( + x + for x + in range(42) + if x +) + +d = { + 1: True, + 2: False, + 3: None +} + +dc = { + x: None + for x + in range(42) + if x +} diff --git a/python/testSrc/com/jetbrains/python/PyEditingTest.java b/python/testSrc/com/jetbrains/python/PyEditingTest.java index afc77de91bcd..6c10ebfff313 100644 --- a/python/testSrc/com/jetbrains/python/PyEditingTest.java +++ b/python/testSrc/com/jetbrains/python/PyEditingTest.java @@ -308,7 +308,7 @@ public class PyEditingTest extends PyTestCase { public void testEnterNoDocstringStubWhenCodeExampleInDocstring() { doDocStringTypingTest("\n", DocStringFormat.GOOGLE); } - + // PY-15332 public void testEnterDocstringStubNoReturnTagForInit() { doDocStringTypingTest("\n", DocStringFormat.REST); @@ -494,6 +494,33 @@ public class PyEditingTest extends PyTestCase { ")"); } + // PY-20909 + public void testContinuationIndentInEmptyListLiteral() { + getPythonCodeStyleSettings().USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS = true; + doTestEnter("[]", + "[\n" + + " \n" + + "]"); + } + + // PY-20909 + public void testContinuationIndentInEmptyDictLiteral() { + getPythonCodeStyleSettings().USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS = true; + doTestEnter("{}", + "{\n" + + " \n" + + "}"); + } + + // PY-20909 + public void testContinuationIndentInEmptyTupleLiteral() { + getPythonCodeStyleSettings().USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS = true; + doTestEnter("()", + "(\n" + + " \n" + + ")"); + } + // PY-21840 public void testEditInjectedRegexpFragmentWithLongUnicodeEscape() { myFixture.configureByText(PythonFileType.INSTANCE, diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index 05a708e5e331..8d8c039e3d70 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -660,6 +660,19 @@ public class PyFormatterTest extends PyTestCase { doTest(); } + // PY-20909 + public void testContinuationIndentForCollectionsAndComprehensions() { + getPythonCodeStyleSettings().USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS = true; + doTest(); + } + + // PY-20909 + public void testContinuationIndentForCollectionsAndComprehensionsHangingIndentOfClosingBrace() { + getPythonCodeStyleSettings().USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS = true; + getPythonCodeStyleSettings().HANG_CLOSING_BRACKETS = true; + doTest(); + } + // PY-18265 public void testNoSpaceAroundPowerOperator() { getPythonCodeStyleSettings().SPACE_AROUND_POWER_OPERATOR = false;