PY-20633 Allow to configure wrapping policy for names in a "from" import

This commit is contained in:
Mikhail Golubev
2017-01-23 17:41:20 +03:00
parent 657c0f6981
commit 2e90002d69
7 changed files with 56 additions and 20 deletions
@@ -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
@@ -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<PyBlock> mySubBlocks = null;
private Map<ASTNode, PyBlock> 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) {
@@ -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
@@ -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")
@@ -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)
@@ -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)
@@ -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);
}