PY-17564 Keep blank lines between imports inserted by user
[idea/community.git] / python / src / com / jetbrains / python / formatter / PythonFormattingModelBuilder.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.python.formatter;
17
18 import com.intellij.formatting.*;
19 import com.intellij.lang.ASTNode;
20 import com.intellij.openapi.util.TextRange;
21 import com.intellij.psi.PsiElement;
22 import com.intellij.psi.PsiFile;
23 import com.intellij.psi.codeStyle.CodeStyleSettings;
24 import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
25 import com.intellij.psi.tree.IElementType;
26 import com.intellij.psi.tree.TokenSet;
27 import com.jetbrains.python.PythonDialectsTokenSetProvider;
28 import com.jetbrains.python.PythonLanguage;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31
32 import static com.jetbrains.python.PyElementTypes.*;
33 import static com.jetbrains.python.PyTokenTypes.*;
34
35 /**
36  * @author yole
37  */
38 @SuppressWarnings("UseOfSystemOutOrSystemErr")
39 public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, CustomFormattingModelBuilder {
40   private static final boolean DUMP_FORMATTING_AST = false;
41   public static final TokenSet STATEMENT_OR_DECLARATION = PythonDialectsTokenSetProvider.INSTANCE.getStatementTokens();
42
43   @NotNull
44   @Override
45   public FormattingModel createModel(@NotNull PsiElement element,
46                                      @NotNull CodeStyleSettings settings,
47                                      @NotNull FormattingMode mode) {
48     if (DUMP_FORMATTING_AST) {
49       ASTNode fileNode = element.getContainingFile().getNode();
50       System.out.println("AST tree for " + element.getContainingFile().getName() + ":");
51       printAST(fileNode, 0);
52     }
53     final PyBlockContext context = new PyBlockContext(settings, createSpacingBuilder(settings), mode);
54     final PyBlock block = new PyBlock(null, element.getNode(), null, Indent.getNoneIndent(), null, context);
55     if (DUMP_FORMATTING_AST) {
56       FormattingModelDumper.dumpFormattingModel(block, 2, System.out);
57     }
58     return FormattingModelProvider.createFormattingModelForPsiFile(element.getContainingFile(), block, settings);
59   }
60
61   @Nullable
62   @Override
63   public CommonCodeStyleSettings.IndentOptions getIndentOptionsToUse(@NotNull PsiFile file,
64                                                                      @NotNull FormatTextRanges ranges,
65                                                                      @NotNull CodeStyleSettings settings) {
66     return null;
67   }
68
69   @NotNull
70   public FormattingModel createModel(final PsiElement element, final CodeStyleSettings settings) {
71     return createModel(element, settings, FormattingMode.REFORMAT);
72   }
73
74   protected SpacingBuilder createSpacingBuilder(CodeStyleSettings settings) {
75     final PyCodeStyleSettings pySettings = settings.getCustomSettings(PyCodeStyleSettings.class);
76
77     final CommonCodeStyleSettings commonSettings = settings.getCommonSettings(PythonLanguage.getInstance());
78     return new SpacingBuilder(commonSettings)
79       .between(CLASS_DECLARATION, STATEMENT_OR_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
80       .between(STATEMENT_OR_DECLARATION, CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
81       .between(FUNCTION_DECLARATION, STATEMENT_OR_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
82       .between(STATEMENT_OR_DECLARATION, FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
83       .after(FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
84       .after(CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
85       // Remove excess blank lines between imports (at most one is allowed). 
86       // Note that ImportOptimizer gets rid of them anyway.
87       // Empty lines between import groups are handles in PyBlock#getSpacing
88       .between(IMPORT_STATEMENTS, IMPORT_STATEMENTS).spacing(0, Integer.MAX_VALUE, 1, false, 1)
89       .between(STATEMENT_OR_DECLARATION, STATEMENT_OR_DECLARATION).spacing(0, Integer.MAX_VALUE, 1, false, 1)
90
91       .between(COLON, STATEMENT_LIST).spacing(1, Integer.MAX_VALUE, 0, true, 0)
92       .afterInside(COLON, TokenSet.create(KEY_VALUE_EXPRESSION, LAMBDA_EXPRESSION)).spaceIf(pySettings.SPACE_AFTER_PY_COLON)
93
94       .afterInside(GT, ANNOTATION).spaces(1)
95       .betweenInside(MINUS, GT, ANNOTATION).none()
96       .beforeInside(ANNOTATION, FUNCTION_DECLARATION).spaces(1)
97       .beforeInside(ANNOTATION, NAMED_PARAMETER).none()
98       .afterInside(COLON, ANNOTATION).spaces(1)
99       .afterInside(RARROW, ANNOTATION).spaces(1)
100
101       .between(allButLambda(), PARAMETER_LIST).spaceIf(commonSettings.SPACE_BEFORE_METHOD_PARENTHESES)
102
103       .before(COLON).spaceIf(pySettings.SPACE_BEFORE_PY_COLON)
104       .after(COMMA).spaceIf(commonSettings.SPACE_AFTER_COMMA)
105       .before(COMMA).spaceIf(commonSettings.SPACE_BEFORE_COMMA)
106       .between(FROM_KEYWORD, DOT).spaces(1)
107       .between(DOT, IMPORT_KEYWORD).spaces(1)
108       .around(DOT).spaces(0)
109       .aroundInside(AT, DECORATOR_CALL).none()
110       .before(SEMICOLON).spaceIf(commonSettings.SPACE_BEFORE_SEMICOLON)
111       .withinPairInside(LPAR, RPAR, ARGUMENT_LIST).spaceIf(commonSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES)
112       .withinPairInside(LPAR, RPAR, PARAMETER_LIST).spaceIf(commonSettings.SPACE_WITHIN_METHOD_PARENTHESES)
113       .withinPairInside(LPAR, RPAR, FROM_IMPORT_STATEMENT).spaces(0)
114       .withinPairInside(LPAR, RPAR, GENERATOR_EXPRESSION).spaces(0)
115       .withinPairInside(LPAR, RPAR, PARENTHESIZED_EXPRESSION).spaces(0)
116       .before(LBRACKET).spaceIf(pySettings.SPACE_BEFORE_LBRACKET)
117
118       .afterInside(LBRACE, DICT_LITERAL_EXPRESSION).spaceIf(pySettings.SPACE_WITHIN_BRACES, pySettings.DICT_NEW_LINE_AFTER_LEFT_BRACE)
119       .beforeInside(RBRACE, DICT_LITERAL_EXPRESSION).spaceIf(pySettings.SPACE_WITHIN_BRACES, pySettings.DICT_NEW_LINE_BEFORE_RIGHT_BRACE)
120       .withinPair(LBRACE, RBRACE).spaceIf(pySettings.SPACE_WITHIN_BRACES)
121       .withinPair(LBRACKET, RBRACKET).spaceIf(commonSettings.SPACE_WITHIN_BRACKETS)
122
123       .before(ARGUMENT_LIST).spaceIf(commonSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES)
124
125       .around(DECORATOR_CALL).spacing(1, Integer.MAX_VALUE, 0, true, 0)
126       .after(DECORATOR_LIST).spacing(1, Integer.MAX_VALUE, 0, true, 0)
127
128       .aroundInside(EQ, ASSIGNMENT_STATEMENT).spaceIf(commonSettings.SPACE_AROUND_ASSIGNMENT_OPERATORS)
129       .aroundInside(EQ, NAMED_PARAMETER).spaceIf(pySettings.SPACE_AROUND_EQ_IN_NAMED_PARAMETER)
130       .aroundInside(EQ, KEYWORD_ARGUMENT_EXPRESSION).spaceIf(pySettings.SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT)
131
132       .around(AUG_ASSIGN_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_ASSIGNMENT_OPERATORS)
133       .aroundInside(ADDITIVE_OPERATIONS, BINARY_EXPRESSION).spaceIf(commonSettings.SPACE_AROUND_ADDITIVE_OPERATORS)
134       .aroundInside(MULTIPLICATIVE_OR_EXP, STAR_PARAMETERS).none()
135       .around(MULTIPLICATIVE_OR_EXP).spaceIf(commonSettings.SPACE_AROUND_MULTIPLICATIVE_OPERATORS)
136       .around(SHIFT_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_SHIFT_OPERATORS)
137       .around(BITWISE_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_BITWISE_OPERATORS)
138       .around(EQUALITY_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_EQUALITY_OPERATORS)
139       .around(RELATIONAL_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_RELATIONAL_OPERATORS)
140       .around(SINGLE_SPACE_KEYWORDS).spaces(1);
141   }
142
143   // should be all keywords?
144   private static final TokenSet SINGLE_SPACE_KEYWORDS = TokenSet.create(IN_KEYWORD, AND_KEYWORD, OR_KEYWORD, IS_KEYWORD,
145                                                                         IF_KEYWORD, ELIF_KEYWORD, FOR_KEYWORD, RETURN_KEYWORD, RAISE_KEYWORD,
146                                                                         ASSERT_KEYWORD, CLASS_KEYWORD, DEF_KEYWORD, DEL_KEYWORD,
147                                                                         EXEC_KEYWORD, GLOBAL_KEYWORD, IMPORT_KEYWORD, LAMBDA_KEYWORD,
148                                                                         NOT_KEYWORD, WHILE_KEYWORD, YIELD_KEYWORD);
149
150   private static TokenSet allButLambda() {
151     final PythonLanguage pythonLanguage = PythonLanguage.getInstance();
152     return TokenSet.create(IElementType.enumerate(new IElementType.Predicate() {
153       @Override
154       public boolean matches(@NotNull IElementType type) {
155         return type != LAMBDA_KEYWORD && type.getLanguage().isKindOf(pythonLanguage);
156       }
157     }));
158   }
159
160   public TextRange getRangeAffectingIndent(PsiFile file, int offset, ASTNode elementAtOffset) {
161     return null;
162   }
163
164   private static void printAST(ASTNode node, int indent) {
165     while (node != null) {
166       for (int i = 0; i < indent; i++) {
167         System.out.print(" ");
168       }
169       System.out.println(node.toString() + " " + node.getTextRange().toString());
170       printAST(node.getFirstChildNode(), indent + 2);
171       node = node.getTreeNext();
172     }
173   }
174
175   public boolean isEngagedToFormat(PsiElement context) {
176     PsiFile file = context.getContainingFile();
177     return file != null && file.getLanguage() == PythonLanguage.getInstance();
178   }
179 }