diff --git a/python/src/com/jetbrains/python/PyTokenTypes.java b/python/src/com/jetbrains/python/PyTokenTypes.java index c25d33085146..7df4b49c4807 100644 --- a/python/src/com/jetbrains/python/PyTokenTypes.java +++ b/python/src/com/jetbrains/python/PyTokenTypes.java @@ -137,6 +137,9 @@ public class PyTokenTypes { public static final TokenSet ADDITIVE_OPERATIONS = TokenSet.create(PLUS, MINUS); public static final TokenSet MULTIPLICATIVE_OPERATIONS = TokenSet.create(MULT, FLOORDIV, DIV, PERC); public static final TokenSet UNARY_OPERATIONS = TokenSet.create(PLUS, MINUS, TILDE); + public static final TokenSet BITWISE_OPERATIONS = TokenSet.create(AND, OR, XOR); + public static final TokenSet EQUALITY_OPERATIONS = TokenSet.create(EQEQ, NE, NE_OLD); + public static final TokenSet RELATIONAL_OPERATIONS = TokenSet.create(LT, GT, LE, GE); public static final TokenSet END_OF_STATEMENT = TokenSet.create(STATEMENT_BREAK, SEMICOLON); public static final TokenSet WHITESPACE = TokenSet.create(SPACE, TAB, FORMFEED); public static final TokenSet WHITESPACE_OR_LINEBREAK = TokenSet.create(SPACE, TAB, FORMFEED, LINE_BREAK); diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/StatementMover.java b/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/StatementMover.java index fa3eed0b3f22..2c2a0aa0bd2f 100644 --- a/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/StatementMover.java +++ b/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/StatementMover.java @@ -106,7 +106,9 @@ public class StatementMover extends LineMover { } @Nullable - private static LineRange expandLineRange(LineRange range, Editor editor, PsiFile file) { + private static LineRange expandLineRange(@NotNull final LineRange range, + @NotNull final Editor editor, + @NotNull final PsiFile file) { final SelectionModel selectionModel = editor.getSelectionModel(); Pair psiRange; if (selectionModel.hasSelection()) { @@ -136,11 +138,14 @@ public class StatementMover extends LineMover { return null; } - if (elementRange.getFirst() == elementRange.getSecond() && elementRange.getFirst() instanceof PyPassStatement) { + final PsiElement first = elementRange.getFirst(); + final PsiElement second = elementRange.getSecond(); + if (first == second && first instanceof PyPassStatement) { return null; } - final int endOffset = elementRange.getSecond().getTextRange().getEndOffset(); + int startOffset = first.getTextOffset(); + int endOffset = second.getTextRange().getEndOffset(); final Document document = editor.getDocument(); if (endOffset > document.getTextLength()) { LOG.assertTrue(!PsiDocumentManager.getInstance(file.getProject()).isUncommited(document)); @@ -156,7 +161,7 @@ public class StatementMover extends LineMover { endLine = Math.min(endLine, document.getLineCount()); } endLine = Math.max(endLine, range.endLine); - final int startLine = Math.min(range.startLine, editor.offsetToLogicalPosition(elementRange.getFirst().getTextOffset()).line); + final int startLine = Math.min(range.startLine, editor.offsetToLogicalPosition(startOffset).line); return new LineRange(startLine, endLine); } @@ -257,22 +262,27 @@ public class StatementMover extends LineMover { } info.toMove2 = new LineRange(statement, statement, document); - final PyStatementPart[] statementParts = PsiTreeUtil.getChildrenOfType(statement, PyStatementPart.class); + // next/previous statement has a statement parts + // move inside statement part if (statementParts != null) { - // move inside statement part + int startLineNumber; + int endLineNumber; + PyStatementPart statementPart; if (down) { - final PyStatementPart statementPart = statementParts[0]; - final int lineNumber = document.getLineNumber(statementPart.getTextRange().getStartOffset()); - info.toMove2 = new LineRange(lineNumber, lineNumber + 1); - myStatementListToRemovePass = statementPart.getStatementList(); - myElementsToIncreaseIndent = statements; + statementPart = statementParts[0]; + startLineNumber = document.getLineNumber(statementPart.getTextRange().getStartOffset()); + endLineNumber = document.getLineNumber(statementPart.getTextRange().getEndOffset()); } else { - final PyStatementPart statementPart = statementParts[statementParts.length - 1]; - final int lineNumber = document.getLineNumber(statementPart.getTextRange().getEndOffset()); - info.toMove2 = new LineRange(lineNumber, lineNumber + 1); + statementPart = statementParts[statementParts.length - 1]; + startLineNumber = document.getLineNumber(statementPart.getTextRange().getEndOffset()); + endLineNumber = document.getLineNumber(statementPart.getTextRange().getStartOffset()); + } + + if (startLineNumber != endLineNumber) { + info.toMove2 = new LineRange(startLineNumber, startLineNumber + 1); myStatementListToRemovePass = statementPart.getStatementList(); myElementsToIncreaseIndent = statements; } diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 2a2c6d5f84e6..c39d7a911745 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -212,15 +212,71 @@ public class PyBlock implements ASTBlock { if (type1 == PyTokenTypes.COLON && type2 == PyElementTypes.STATEMENT_LIST) { return Spacing.createSpacing(1, Integer.MAX_VALUE, 0, true, 0); } + if (type2 == PyTokenTypes.COLON) { + return getSpacingForOption(getPySettings().SPACE_BEFORE_COLON); + } if (type1 == PyTokenTypes.COMMA) { return getSpacingForOption(mySettings.SPACE_AFTER_COMMA); } -/* - if (type1 == PyTokenTypes.COLON && type2 == PyElementTypes.STATEMENT_LIST) { - return Spacing.createSpacing(0, Integer.MAX_VALUE, 1, true, Integer.MAX_VALUE); - } -*/ + if (type2 == PyTokenTypes.COMMA) { + return getSpacingForOption(mySettings.SPACE_BEFORE_COMMA); + } + if (type2 == PyTokenTypes.SEMICOLON) { + return getSpacingForOption(mySettings.SPACE_BEFORE_SEMICOLON); + } + + if (type1 == PyTokenTypes.LPAR || type2 == PyTokenTypes.RPAR) { + if (parentType == PyElementTypes.ARGUMENT_LIST) { + return getSpacingForOption(mySettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES); + } + } + if (type2 == PyTokenTypes.LBRACKET) { + return getSpacingForOption(getPySettings().SPACE_BEFORE_LBRACKET); + } + if (type1 == PyTokenTypes.LBRACKET || type2 == PyTokenTypes.RBRACKET) { + return getSpacingForOption(mySettings.SPACE_WITHIN_BRACKETS); + } + if (type1 == PyTokenTypes.LBRACE || type2 == PyTokenTypes.RBRACE) { + return getSpacingForOption(getPySettings().SPACE_WITHIN_BRACES); + } + if (type2 == PyElementTypes.ARGUMENT_LIST) { + return getSpacingForOption(mySettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES); + } + + if (type1 == PyTokenTypes.EQ || type2 == PyTokenTypes.EQ) { + if (parentType == PyElementTypes.ASSIGNMENT_STATEMENT) { + return getSpacingForOption(mySettings.SPACE_AROUND_ASSIGNMENT_OPERATORS); + } + if (parentType == PyElementTypes.NAMED_PARAMETER) { + return getSpacingForOption(getPySettings().SPACE_AROUND_EQ_IN_NAMED_PARAMETER); + } + if (parentType == PyElementTypes.KEYWORD_ARGUMENT_EXPRESSION) { + return getSpacingForOption(getPySettings().SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT); + } + } + if (isAround(type1, type2, PyTokenTypes.AUG_ASSIGN_OPERATIONS)) { + return getSpacingForOption(mySettings.SPACE_AROUND_ASSIGNMENT_OPERATORS); + } + if (isAround(type1, type2, PyTokenTypes.ADDITIVE_OPERATIONS)) { + return getSpacingForOption(mySettings.SPACE_AROUND_ADDITIVE_OPERATORS); + } + if (isAround(type1, type2, PyTokenTypes.MULTIPLICATIVE_OPERATIONS)) { + return getSpacingForOption(mySettings.SPACE_AROUND_MULTIPLICATIVE_OPERATORS); + } + if (isAround(type1, type2, PyTokenTypes.SHIFT_OPERATIONS)) { + return getSpacingForOption(mySettings.SPACE_AROUND_SHIFT_OPERATORS); + } + if (isAround(type1, type2, PyTokenTypes.BITWISE_OPERATIONS)) { + return getSpacingForOption(mySettings.SPACE_AROUND_BITWISE_OPERATORS); + } + if (isAround(type1, type2, PyTokenTypes.EQUALITY_OPERATIONS)) { + return getSpacingForOption(mySettings.SPACE_AROUND_EQUALITY_OPERATORS); + } + if (isAround(type1, type2, PyTokenTypes.RELATIONAL_OPERATIONS)) { + return getSpacingForOption(mySettings.SPACE_AROUND_RELATIONAL_OPERATORS); + } + //if (parentType == PyElementTypes.ARGUMENT_LIST // || parentType == PyElementTypes.LIST_LITERAL_EXPRESSION) { @@ -240,6 +296,14 @@ public class PyBlock implements ASTBlock { return null; } + private static boolean isAround(IElementType type1, IElementType type2, final TokenSet tokenSet) { + return tokenSet.contains(type1) || tokenSet.contains(type2); + } + + private PyCodeStyleSettings getPySettings() { + return mySettings.getCustomSettings(PyCodeStyleSettings.class); + } + private Spacing getSpacingForOption(boolean isOptionSet) { int spaces = isOptionSet ? 1 : 0; return Spacing.createSpacing(spaces, spaces, 0, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE); diff --git a/python/src/com/jetbrains/python/formatter/PyCodeStylePanel.form b/python/src/com/jetbrains/python/formatter/PyCodeStylePanel.form new file mode 100644 index 000000000000..d776fac9f890 --- /dev/null +++ b/python/src/com/jetbrains/python/formatter/PyCodeStylePanel.form @@ -0,0 +1,12 @@ + +
+ + + + + + + + + +
diff --git a/python/src/com/jetbrains/python/formatter/PyCodeStylePanel.java b/python/src/com/jetbrains/python/formatter/PyCodeStylePanel.java new file mode 100644 index 000000000000..00834f861eef --- /dev/null +++ b/python/src/com/jetbrains/python/formatter/PyCodeStylePanel.java @@ -0,0 +1,69 @@ +package com.jetbrains.python.formatter; + +import com.intellij.application.options.CodeStyleAbstractPanel; +import com.intellij.ide.highlighter.HighlighterFactory; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.jetbrains.python.PyHighlighter; +import com.jetbrains.python.PythonFileType; +import com.jetbrains.python.psi.LanguageLevel; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * @author yole + */ +public class PyCodeStylePanel extends CodeStyleAbstractPanel { + private JPanel myPanel; + + protected PyCodeStylePanel(CodeStyleSettings settings) { + super(settings); + } + + @Override + protected EditorHighlighter createHighlighter(EditorColorsScheme scheme) { + return HighlighterFactory.createHighlighter(new PyHighlighter(LanguageLevel.PYTHON26), scheme); + } + + @Override + protected int getRightMargin() { + return 80; + } + + @Override + protected void prepareForReformat(PsiFile psiFile) { + } + + @NotNull + @Override + protected FileType getFileType() { + return PythonFileType.INSTANCE; + } + + @Override + protected String getPreviewText() { + return ""; + } + + @Override + protected void resetImpl(CodeStyleSettings settings) { + } + + @Override + public void apply(CodeStyleSettings settings) { + } + + @Override + public boolean isModified(CodeStyleSettings settings) { + return false; + } + + @Override + public JComponent getPanel() { + return myPanel; + } +} diff --git a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java new file mode 100644 index 000000000000..2d77a950c261 --- /dev/null +++ b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java @@ -0,0 +1,19 @@ +package com.jetbrains.python.formatter; + +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CustomCodeStyleSettings; + +/** + * @author yole + */ +public class PyCodeStyleSettings extends CustomCodeStyleSettings { + public boolean SPACE_WITHIN_BRACES = false; + public boolean SPACE_BEFORE_COLON = false; + public boolean SPACE_BEFORE_LBRACKET = false; + public boolean SPACE_AROUND_EQ_IN_NAMED_PARAMETER = false; + public boolean SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT = false; + + protected PyCodeStyleSettings(CodeStyleSettings container) { + super("Python", container); + } +} diff --git a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettingsProvider.java b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettingsProvider.java new file mode 100644 index 000000000000..ee3ddff246f6 --- /dev/null +++ b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettingsProvider.java @@ -0,0 +1,38 @@ +package com.jetbrains.python.formatter; + +import com.intellij.application.options.CodeStyleAbstractConfigurable; +import com.intellij.application.options.CodeStyleAbstractPanel; +import com.intellij.openapi.options.Configurable; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsProvider; +import com.intellij.psi.codeStyle.CustomCodeStyleSettings; +import org.jetbrains.annotations.NotNull; + +/** + * @author yole + */ +public class PyCodeStyleSettingsProvider extends CodeStyleSettingsProvider { + @Override + public CustomCodeStyleSettings createCustomSettings(CodeStyleSettings settings) { + return new PyCodeStyleSettings(settings); + } + + @NotNull + @Override + public Configurable createSettingsPage(CodeStyleSettings settings, CodeStyleSettings originalSettings) { + return new CodeStyleAbstractConfigurable(settings, originalSettings, "Ruby") { + protected CodeStyleAbstractPanel createPanel(final CodeStyleSettings settings) { + return new PyCodeStylePanel(settings); + } + + public String getHelpTopic() { + return null; + } + }; + } + + @Override + public String getConfigurableDisplayName() { + return "Python"; + } +} diff --git a/python/testData/formatter/pep8ExtraneousWhitespace.py b/python/testData/formatter/pep8ExtraneousWhitespace.py new file mode 100644 index 000000000000..3e8f49476605 --- /dev/null +++ b/python/testData/formatter/pep8ExtraneousWhitespace.py @@ -0,0 +1,4 @@ +spam( ham[ 1 ], { eggs: 2 } ) +if x == 4 : print x , y ; x , y = y , x +spam (1) +dict ['key'] = list [index] \ No newline at end of file diff --git a/python/testData/formatter/pep8ExtraneousWhitespace_after.py b/python/testData/formatter/pep8ExtraneousWhitespace_after.py new file mode 100644 index 000000000000..8e8435abf61e --- /dev/null +++ b/python/testData/formatter/pep8ExtraneousWhitespace_after.py @@ -0,0 +1,4 @@ +spam(ham[1], {eggs: 2}) +if x == 4: print x, y; x, y = y, x +spam(1) +dict['key'] = list[index] \ No newline at end of file diff --git a/python/testData/formatter/pep8KeywordArguments.py b/python/testData/formatter/pep8KeywordArguments.py new file mode 100644 index 000000000000..f64329217080 --- /dev/null +++ b/python/testData/formatter/pep8KeywordArguments.py @@ -0,0 +1,2 @@ +def complex(real, imag = 0.0): + return magic(r = real, i = imag) diff --git a/python/testData/formatter/pep8KeywordArguments_after.py b/python/testData/formatter/pep8KeywordArguments_after.py new file mode 100644 index 000000000000..c1b77345a065 --- /dev/null +++ b/python/testData/formatter/pep8KeywordArguments_after.py @@ -0,0 +1,2 @@ +def complex(real, imag=0.0): + return magic(r=real, i=imag) diff --git a/python/testData/formatter/pep8Operators.py b/python/testData/formatter/pep8Operators.py new file mode 100644 index 000000000000..6b175f27ef40 --- /dev/null +++ b/python/testData/formatter/pep8Operators.py @@ -0,0 +1,3 @@ +i=i+1 +submitted+=1 +x = x*2-1 diff --git a/python/testData/formatter/pep8Operators_after.py b/python/testData/formatter/pep8Operators_after.py new file mode 100644 index 000000000000..886392da79f6 --- /dev/null +++ b/python/testData/formatter/pep8Operators_after.py @@ -0,0 +1,3 @@ +i = i + 1 +submitted += 1 +x = x * 2 - 1 diff --git a/python/testData/mover/py950.py b/python/testData/mover/py950.py new file mode 100644 index 000000000000..ca1a926e4f47 --- /dev/null +++ b/python/testData/mover/py950.py @@ -0,0 +1,7 @@ +class A: + def foo(self): + if a: pass + else: pass + a = 0 + if a: pass + else: pass \ No newline at end of file diff --git a/python/testData/mover/py950_afterDown.py b/python/testData/mover/py950_afterDown.py new file mode 100644 index 000000000000..7ef13131f14a --- /dev/null +++ b/python/testData/mover/py950_afterDown.py @@ -0,0 +1,7 @@ +class A: + def foo(self): + if a: pass + else: pass + if a: pass + else: pass + a = 0 diff --git a/python/testData/mover/py950_afterUp.py b/python/testData/mover/py950_afterUp.py new file mode 100644 index 000000000000..d6e3ed85ab57 --- /dev/null +++ b/python/testData/mover/py950_afterUp.py @@ -0,0 +1,7 @@ +class A: + def foo(self): + a = 0 + if a: pass + else: pass + if a: pass + else: pass \ No newline at end of file diff --git a/python/testData/mover/simpleBlankLines.py b/python/testData/mover/simpleBlankLines.py new file mode 100644 index 000000000000..f8deb51f225f --- /dev/null +++ b/python/testData/mover/simpleBlankLines.py @@ -0,0 +1,9 @@ +if a: + a = 1 + + + b = 2 + + + + c = 3 \ No newline at end of file diff --git a/python/testData/mover/simpleBlankLines_afterDown.py b/python/testData/mover/simpleBlankLines_afterDown.py new file mode 100644 index 000000000000..91b5334ed539 --- /dev/null +++ b/python/testData/mover/simpleBlankLines_afterDown.py @@ -0,0 +1,9 @@ +if a: + a = 1 + + + c = 3 + + + + b = 2 diff --git a/python/testData/mover/simpleBlankLines_afterUp.py b/python/testData/mover/simpleBlankLines_afterUp.py new file mode 100644 index 000000000000..fcfdc80996a5 --- /dev/null +++ b/python/testData/mover/simpleBlankLines_afterUp.py @@ -0,0 +1,9 @@ +if a: + b = 2 + + + a = 1 + + + + c = 3 \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index d80e5f00710d..ff1185e012c1 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -20,6 +20,18 @@ public class PyFormatterTest extends PyLightFixtureTestCase { doTest(); } + public void testPep8ExtraneousWhitespace() throws Exception { + doTest(); + } + + public void testPep8Operators() throws Exception { + doTest(); + } + + public void testPep8KeywordArguments() throws Exception { + doTest(); + } + private void doTest() throws Exception { myFixture.configureByFile("formatter/" + getTestName(true) + ".py"); ApplicationManager.getApplication().runWriteAction(new Runnable() { diff --git a/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java b/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java index 2fa8ead98a2c..7aa018ca6202 100644 --- a/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java +++ b/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java @@ -60,4 +60,12 @@ public class PyStatementMoverTest extends PyLightFixtureTestCase { public void testSelection() throws Exception { doTest(); } + + public void testSimpleBlankLines() throws Exception { + doTest(); + } + + public void testPY950() throws Exception { + doTest(); + } }