diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index ddeeb34472b5..3941f0a83f80 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -22,6 +22,7 @@ import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.impl.source.tree.TreeUtil; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; @@ -683,20 +684,25 @@ public class PyBlock implements ASTBlock { public Spacing getSpacing(Block child1, @NotNull Block child2) { if (child1 instanceof ASTBlock && child2 instanceof ASTBlock) { final ASTNode node1 = ((ASTBlock)child1).getNode(); + final ASTNode node2 = ((ASTBlock)child2).getNode(); final PsiElement psi1 = node1.getPsi(); final PsiElement psi2 = ((ASTBlock)child2).getNode().getPsi(); + + final IElementType childType1 = node1.getElementType(); + final IElementType childType2 = node2.getElementType(); if (psi1 instanceof PyImportStatementBase && psi2 instanceof PyImportStatementBase && psi2.getCopyableUserData(IMPORT_GROUP_BEGIN) != null) { return Spacing.createSpacing(0, 0, 2, true, 1); } - if (node1.getElementType() == PyTokenTypes.COLON && psi2 instanceof PyStatementList) { + final CommonCodeStyleSettings settings = myContext.getSettings(); + if (childType1 == PyTokenTypes.COLON && psi2 instanceof PyStatementList) { if (needLineBreakInStatement()) { - return Spacing.createSpacing(0, 0, 1, true, myContext.getSettings().KEEP_BLANK_LINES_IN_CODE); + return Spacing.createSpacing(0, 0, 1, true, settings.KEEP_BLANK_LINES_IN_CODE); } } - if ((node1.getElementType() == PyElementTypes.FUNCTION_DECLARATION || node1.getElementType() == PyElementTypes.CLASS_DECLARATION) + if ((childType1 == PyElementTypes.FUNCTION_DECLARATION || childType1 == PyElementTypes.CLASS_DECLARATION) && myNode.getElementType() instanceof PyFileElementType) { if (psi2 instanceof PsiComment) { @@ -712,6 +718,14 @@ public class PyBlock implements ASTBlock { } } + if (myNode.getElementType() == PyElementTypes.DICT_LITERAL_EXPRESSION) { + if (shouldInsertNewLineForBrace(childType1) || shouldInsertNewLineForBrace(childType2)) { + final int spaces = settings.SPACE_WITHIN_BRACES ? 1 : 0; + return Spacing.createDependentLFSpacing(spaces, spaces, myNode.getTextRange(), + settings.KEEP_LINE_BREAKS, settings.KEEP_BLANK_LINES_IN_CODE); + } + } + if (psi2 instanceof PsiComment && !hasLineBreaksBefore(psi2.getNode(), 1) && myContext.getPySettings().SPACE_BEFORE_NUMBER_SIGN) { return Spacing.createSpacing(2, 0, 0, false, 0); } @@ -719,6 +733,11 @@ public class PyBlock implements ASTBlock { return myContext.getSpacingBuilder().getSpacing(this, child1, child2); } + private boolean shouldInsertNewLineForBrace(IElementType type) { + return (type == PyTokenTypes.LBRACE && myContext.getPySettings().DICT_NEW_LINE_AFTER_LEFT_BRACE) || + (type == PyTokenTypes.RBRACE && myContext.getPySettings().DICT_NEW_LINE_BEFORE_RIGHT_BRACE); + } + private Spacing getBlankLinesForOption(final int option) { final int blankLines = option + 1; return Spacing diff --git a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java index 20bd954930ed..e1d3a5f511be 100644 --- a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java +++ b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java @@ -74,7 +74,8 @@ public class PyCodeStyleSettings extends CustomCodeStyleSettings { public int DICT_ALIGNMENT = DICT_ALIGNMENT_NONE; public int DICT_WRAPPING = WrapType.NORMAL.getLegacyRepresentation(); - + public boolean DICT_NEW_LINE_AFTER_LEFT_BRACE = false; + public boolean DICT_NEW_LINE_BEFORE_RIGHT_BRACE = false; 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 3cc41a5db282..7313919150d5 100644 --- a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java +++ b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java @@ -104,6 +104,8 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin consumer.showCustomOption(PyCodeStyleSettings.class, "ALIGN_MULTILINE_IMPORTS", "Align when multiline", "Import Statements"); consumer.showCustomOption(PyCodeStyleSettings.class, "DICT_WRAPPING", "Dictionary literals", null, WRAP_OPTIONS, WRAP_VALUES); + consumer.showCustomOption(PyCodeStyleSettings.class, "DICT_NEW_LINE_AFTER_LEFT_BRACE", "New line after '{'", "Dictionary literals"); + consumer.showCustomOption(PyCodeStyleSettings.class, "DICT_NEW_LINE_BEFORE_RIGHT_BRACE", "Place '}' on new line", "Dictionary literals"); } } diff --git a/python/testData/formatter/forceNewLineAfterLeftBraceInDict.py b/python/testData/formatter/forceNewLineAfterLeftBraceInDict.py new file mode 100644 index 000000000000..4ff13fec5d01 --- /dev/null +++ b/python/testData/formatter/forceNewLineAfterLeftBraceInDict.py @@ -0,0 +1,9 @@ +d1 = {'a': 1, + 'b': 2} + +d2 = {'a': 1, 'b': 2} + +d3 = { + 'a': 1, + 'b': 2 +} diff --git a/python/testData/formatter/forceNewLineAfterLeftBraceInDict_after.py b/python/testData/formatter/forceNewLineAfterLeftBraceInDict_after.py new file mode 100644 index 000000000000..b255c322a248 --- /dev/null +++ b/python/testData/formatter/forceNewLineAfterLeftBraceInDict_after.py @@ -0,0 +1,10 @@ +d1 = { + 'a': 1, + 'b': 2} + +d2 = {'a': 1, 'b': 2} + +d3 = { + 'a': 1, + 'b': 2 +} diff --git a/python/testData/formatter/forceNewLineBeforeRightBraceInDict.py b/python/testData/formatter/forceNewLineBeforeRightBraceInDict.py new file mode 100644 index 000000000000..4ff13fec5d01 --- /dev/null +++ b/python/testData/formatter/forceNewLineBeforeRightBraceInDict.py @@ -0,0 +1,9 @@ +d1 = {'a': 1, + 'b': 2} + +d2 = {'a': 1, 'b': 2} + +d3 = { + 'a': 1, + 'b': 2 +} diff --git a/python/testData/formatter/forceNewLineBeforeRightBraceInDict_after.py b/python/testData/formatter/forceNewLineBeforeRightBraceInDict_after.py new file mode 100644 index 000000000000..51222d3853de --- /dev/null +++ b/python/testData/formatter/forceNewLineBeforeRightBraceInDict_after.py @@ -0,0 +1,10 @@ +d1 = {'a': 1, + 'b': 2 + } + +d2 = {'a': 1, 'b': 2} + +d3 = { + 'a': 1, + 'b': 2 +} diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index 50ce134e5819..27d847bf2353 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -587,6 +587,18 @@ public class PyFormatterTest extends PyTestCase { doTest(); } + // PY-14962 + public void testForceNewLineAfterLeftBraceInDict() { + getCustomSettings().DICT_NEW_LINE_AFTER_LEFT_BRACE = true; + doTest(); + } + + // PY-14962 + public void testForceNewLineBeforeRightBraceInDict() { + getCustomSettings().DICT_NEW_LINE_BEFORE_RIGHT_BRACE = true; + doTest(); + } + // PY-15530 public void testAlignmentInArgumentListWhereFirstArgumentIsEmptyCall() { doTest();