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.
This commit is contained in:
Mikhail Golubev
2018-10-15 17:18:10 +03:00
parent 1a488694a1
commit 9afa34305d
11 changed files with 330 additions and 35 deletions
@@ -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
@@ -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();
}
@@ -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;
@@ -3,7 +3,7 @@
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="5" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
<xy x="20" y="20" width="1188" height="400"/>
</constraints>
<properties/>
<border type="none"/>
@@ -23,7 +23,7 @@
</vspacer>
</children>
</grid>
<grid id="d3271" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="d3271" layout-manager="GridLayoutManager" row-count="4" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="10" left="10" bottom="10" right="10"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
@@ -31,15 +31,9 @@
<properties/>
<border type="none"/>
<children>
<component id="ca8eb" class="com.intellij.openapi.ui.ComboBox" binding="myDictAlignmentCombo">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<component id="767d3" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="com/jetbrains/python/PyBundle" key="formatter.panel.dict.alignment.label"/>
@@ -47,20 +41,47 @@
</component>
<component id="b4945" class="com.intellij.ui.components.JBCheckBox" binding="myAddTrailingBlankLineCheckbox" default-binding="true">
<constraints>
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="2" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="com/jetbrains/python/PyBundle" key="formatter.panel.add.trailing.line.feed"/>
</properties>
</component>
<component id="9e329" class="com.intellij.ui.components.JBCheckBox" binding="myUseContinuationIndentForArguments" default-binding="true">
<component id="ca8eb" class="com.intellij.openapi.ui.ComboBox" binding="myDictAlignmentCombo">
<constraints>
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="com/jetbrains/python/PyBundle" key="formatter.panel.use.continuation.indent.for.arguments"/>
</properties>
<properties/>
</component>
<grid id="7c517" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<clientProperties>
<BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
</clientProperties>
<border type="etched" title-resource-bundle="com/jetbrains/python/PyBundle" title-key="formatter.panel.use.continuation.indent.for.title"/>
<children>
<component id="9e329" class="com.intellij.ui.components.JBCheckBox" binding="myUseContinuationIndentForArguments" default-binding="true">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="com/jetbrains/python/PyBundle" key="formatter.panel.use.continuation.indent.for.arguments"/>
</properties>
</component>
<component id="44647" class="com.intellij.ui.components.JBCheckBox" binding="myUseContinuationIndentForCollectionsAndComprehensions">
<constraints>
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="com/jetbrains/python/PyBundle" key="formatter.panel.use.continuation.indent.for.collection.literals"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
<vspacer id="4e0f0">
@@ -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" +
"]";
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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("[<caret>]",
"[\n" +
" <caret>\n" +
"]");
}
// PY-20909
public void testContinuationIndentInEmptyDictLiteral() {
getPythonCodeStyleSettings().USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS = true;
doTestEnter("{<caret>}",
"{\n" +
" <caret>\n" +
"}");
}
// PY-20909
public void testContinuationIndentInEmptyTupleLiteral() {
getPythonCodeStyleSettings().USE_CONTINUATION_INDENT_FOR_COLLECTION_AND_COMPREHENSIONS = true;
doTestEnter("(<caret>)",
"(\n" +
" <caret>\n" +
")");
}
// PY-21840
public void testEditInjectedRegexpFragmentWithLongUnicodeEscape() {
myFixture.configureByText(PythonFileType.INSTANCE,
@@ -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;