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;