From 2e90002d699962323a2cc50aecd83ede26f6e796 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 17 Jan 2017 18:14:20 +0300 Subject: [PATCH] PY-20633 Allow to configure wrapping policy for names in a "from" import --- .../com/jetbrains/python/PyBundle.properties | 2 +- .../jetbrains/python/formatter/PyBlock.java | 37 ++++++++++++------- .../python/formatter/PyCodeStyleSettings.java | 5 +++ .../PyLanguageCodeStyleSettingsProvider.java | 13 ++++--- .../fromImportsWrappingChopDownIfLong.py | 4 ++ ...fromImportsWrappingChopDownIfLong_after.py | 8 ++++ .../com/jetbrains/python/PyFormatterTest.java | 7 ++++ 7 files changed, 56 insertions(+), 20 deletions(-) create mode 100644 python/testData/formatter/fromImportsWrappingChopDownIfLong.py create mode 100644 python/testData/formatter/fromImportsWrappingChopDownIfLong_after.py diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 2faa0aa3aa90..97e301e7dd52 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -1033,7 +1033,7 @@ formatter.multi.clause.statements=Multi-clause statements formatter.force.new.line.after.colon=Force new line after colon formatter.align.when.multiline=Align when multiline formatter.collections.and.comprehensions=Collections and Comprehensions -formatter.import.statements=Import Statements +formatter.from.import.statements="From" Import Statements formatter.dictionary.literals=Dictionary literals formatter.hang.closing.brackets=Hang closing brackets diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 4d3be1c9bf87..46810209418a 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -21,7 +21,6 @@ import com.intellij.openapi.editor.Document; 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; @@ -90,10 +89,13 @@ public class PyBlock implements ASTBlock { private final PyBlockContext myContext; private List mySubBlocks = null; private Map mySubBlockByNode = null; - private Alignment myChildAlignment; - private final Alignment myDictAlignment; - private final Wrap myDictWrapping; private final boolean myEmptySequence; + + // Shared among multiple children sub-blocks + private Alignment myChildAlignment = null; + private Alignment myDictAlignment = null; + private Wrap myDictWrapping = null; + private Wrap myFromImportWrapping = null; public PyBlock(@Nullable PyBlock parent, @NotNull ASTNode node, @@ -109,13 +111,13 @@ public class PyBlock implements ASTBlock { myContext = context; myEmptySequence = isEmptySequence(node); + final PyCodeStyleSettings pySettings = myContext.getPySettings(); if (node.getElementType() == PyElementTypes.DICT_LITERAL_EXPRESSION) { myDictAlignment = Alignment.createAlignment(true); - myDictWrapping = Wrap.createWrap(myContext.getPySettings().DICT_WRAPPING, true); + myDictWrapping = Wrap.createWrap(pySettings.DICT_WRAPPING, true); } - else { - myDictAlignment = null; - myDictWrapping = null; + else if (node.getElementType() == PyElementTypes.FROM_IMPORT_STATEMENT) { + myFromImportWrapping = Wrap.createWrap(pySettings.FROM_IMPORT_WRAPPING, false); } } @@ -184,9 +186,11 @@ public class PyBlock implements ASTBlock { final IElementType grandparentType = grandParentNode == null ? null : grandParentNode.getElementType(); final IElementType childType = child.getElementType(); - Wrap wrap = null; + Wrap childWrap = null; Indent childIndent = Indent.getNoneIndent(); Alignment childAlignment = null; + + final PyCodeStyleSettings settings = myContext.getPySettings(); if (parentType == PyElementTypes.BINARY_EXPRESSION && !isInControlStatement()) { //Setup alignments for binary expression @@ -220,7 +224,12 @@ public class PyBlock implements ASTBlock { } } else if (childType == PyElementTypes.IMPORT_ELEMENT) { - wrap = Wrap.createWrap(WrapType.NORMAL, true); + if (parentType == PyElementTypes.FROM_IMPORT_STATEMENT) { + childWrap = myFromImportWrapping; + } + else { + childWrap = Wrap.createWrap(WrapType.NORMAL, true); + } childIndent = Indent.getNormalIndent(); } if (childType == PyTokenTypes.END_OF_LINE_COMMENT && parentType == PyElementTypes.FROM_IMPORT_STATEMENT) { @@ -232,7 +241,7 @@ public class PyBlock implements ASTBlock { !ourBrackets.contains(childType) && childType != PyTokenTypes.COMMA && !isSliceOperand(child) /*&& !isSubscriptionOperand(child)*/) { - wrap = Wrap.createWrap(WrapType.NORMAL, true); + childWrap = Wrap.createWrap(WrapType.NORMAL, true); } if (needListAlignment(child) && !myEmptySequence) { childAlignment = getAlignmentForChildren(); @@ -251,7 +260,7 @@ public class PyBlock implements ASTBlock { } } - final PyCodeStyleSettings settings = CodeStyleSettingsManager.getSettings(child.getPsi().getProject()).getCustomSettings(PyCodeStyleSettings.class); + if (parentType == PyElementTypes.LIST_LITERAL_EXPRESSION || parentType == PyElementTypes.LIST_COMP_EXPRESSION) { if ((childType == PyTokenTypes.RBRACKET && !settings.HANG_CLOSING_BRACKETS) || childType == PyTokenTypes.LBRACKET) { childIndent = Indent.getNoneIndent(); @@ -370,7 +379,7 @@ public class PyBlock implements ASTBlock { } } if (childType == PyElementTypes.KEY_VALUE_EXPRESSION && isChildOfDictLiteral(child)) { - wrap = myDictWrapping; + childWrap = myDictWrapping; childIndent = Indent.getNormalIndent(); } @@ -406,7 +415,7 @@ public class PyBlock implements ASTBlock { prev = prev.getTreePrev(); } - return new PyBlock(this, child, childAlignment, childIndent, wrap, myContext); + return new PyBlock(this, child, childAlignment, childIndent, childWrap, myContext); } private static boolean isValueOfKeyValuePairOfDictLiteral(@NotNull ASTNode node) { diff --git a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java index 26133f0842ce..e103352a5cd5 100644 --- a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java +++ b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java @@ -93,6 +93,11 @@ public class PyCodeStyleSettings extends CustomCodeStyleSettings { public boolean OPTIMIZE_IMPORTS_SORT_BY_TYPE_FIRST = true; public boolean OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE = false; + /** + * Affects wrapping of multiple imported names in a single "from" import. + */ + public int FROM_IMPORT_WRAPPING = WrapType.NORMAL.getLegacyRepresentation(); + /** * Corresponds to the option of pycodestyle.py "--hang-closing". Basically, it means that the closing brace of a collection literal, * a comprehension, an argument list, a parameter list or parentheses in "from" import statement should have the same indent as the items diff --git a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java index 47b61fa9394a..94c2037903e1 100644 --- a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java +++ b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java @@ -119,9 +119,11 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin consumer.showCustomOption(PyCodeStyleSettings.class, "ALIGN_COLLECTIONS_AND_COMPREHENSIONS", PyBundle.message("formatter.align.when.multiline"), PyBundle.message("formatter.collections.and.comprehensions")); + consumer.showCustomOption(PyCodeStyleSettings.class, "FROM_IMPORT_WRAPPING", + PyBundle.message("formatter.from.import.statements"), null, WRAP_OPTIONS, WRAP_VALUES); consumer.showCustomOption(PyCodeStyleSettings.class, "ALIGN_MULTILINE_IMPORTS", PyBundle.message("formatter.align.when.multiline"), - PyBundle.message("formatter.import.statements")); + PyBundle.message("formatter.from.import.statements")); consumer.showCustomOption(PyCodeStyleSettings.class, "DICT_WRAPPING", PyBundle.message("formatter.dictionary.literals"), null, WRAP_OPTIONS, WRAP_VALUES); @@ -177,14 +179,14 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin " def foo(self):\n" + " pass"; @SuppressWarnings("FieldCanBeLocal") - private static String WRAP_SETTINGS_PREVIEW = "from foo import (bar,\n" + - " baz)\n" + + private static String WRAP_SETTINGS_PREVIEW = "from module import (foo, bar, baz, quux,)\n" + "\n" + "long_expression = component_one + component_two + component_three + component_four + component_five + component_six\n" + "\n" + "def xyzzy(long_parameter_1,\n" + "long_parameter_2):\n" + - " pass\n\n" + + " pass\n" + + "\n" + "xyzzy('long_string_constant1',\n" + " 'long_string_constant2')\n" + "\n" + @@ -201,7 +203,8 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin " 'eggs',\n" + "]\n" + "\n" + - "if True: pass\n\n" + + "if True: pass\n" + + "\n" + "try: pass\n" + "finally: pass\n"; @SuppressWarnings("FieldCanBeLocal") diff --git a/python/testData/formatter/fromImportsWrappingChopDownIfLong.py b/python/testData/formatter/fromImportsWrappingChopDownIfLong.py new file mode 100644 index 000000000000..3d1efc572df2 --- /dev/null +++ b/python/testData/formatter/fromImportsWrappingChopDownIfLong.py @@ -0,0 +1,4 @@ +from module import foo, bar +from module import foo, bar, baz +from module import (foo, bar) +from module import (foo, bar, baz) diff --git a/python/testData/formatter/fromImportsWrappingChopDownIfLong_after.py b/python/testData/formatter/fromImportsWrappingChopDownIfLong_after.py new file mode 100644 index 000000000000..7fb0c1208f6e --- /dev/null +++ b/python/testData/formatter/fromImportsWrappingChopDownIfLong_after.py @@ -0,0 +1,8 @@ +from module import foo, bar +from module import foo, \ + bar, \ + baz +from module import (foo, bar) +from module import (foo, + bar, + baz) diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index 0f217ea665d6..551b5297ca12 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -733,6 +733,13 @@ public class PyFormatterTest extends PyTestCase { doTest(); } + // PY-20633 + public void testFromImportWrappingChopDownIfLong() { + getPythonCodeStyleSettings().FROM_IMPORT_WRAPPING = WrapType.CHOP_DOWN_IF_LONG.getLegacyRepresentation(); + getCodeStyleSettings().setRightMargin(PythonLanguage.INSTANCE, 30); + doTest(); + } + public void testVariableAnnotations() { runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest); }