525d6a96da9b317e95386cc80a590cf71fe78dea
[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.lang.LanguageParserDefinitions;
21 import com.intellij.openapi.util.TextRange;
22 import com.intellij.psi.PsiElement;
23 import com.intellij.psi.PsiFile;
24 import com.intellij.psi.codeStyle.CodeStyleSettings;
25 import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
26 import com.intellij.psi.tree.IElementType;
27 import com.intellij.psi.tree.IFileElementType;
28 import com.intellij.psi.tree.TokenSet;
29 import com.jetbrains.python.PythonDialectsTokenSetProvider;
30 import com.jetbrains.python.PythonLanguage;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33
34 import static com.jetbrains.python.PyElementTypes.*;
35 import static com.jetbrains.python.PyTokenTypes.*;
36
37 /**
38  * @author yole
39  */
40 @SuppressWarnings("UseOfSystemOutOrSystemErr")
41 public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, CustomFormattingModelBuilder {
42   private static final boolean DUMP_FORMATTING_AST = false;
43   public static final TokenSet STATEMENT_OR_DECLARATION = PythonDialectsTokenSetProvider.INSTANCE.getStatementTokens();
44
45   @NotNull
46   @Override
47   public FormattingModel createModel(@NotNull PsiElement element,
48                                      @NotNull CodeStyleSettings settings,
49                                      @NotNull FormattingMode mode) {
50     if (DUMP_FORMATTING_AST) {
51       ASTNode fileNode = element.getContainingFile().getNode();
52       System.out.println("AST tree for " + element.getContainingFile().getName() + ":");
53       printAST(fileNode, 0);
54     }
55     final PyBlockContext context = new PyBlockContext(settings, createSpacingBuilder(settings), mode);
56     final PyBlock block = new PyBlock(null, element.getNode(), null, Indent.getNoneIndent(), null, context);
57     if (DUMP_FORMATTING_AST) {
58       FormattingModelDumper.dumpFormattingModel(block, 2, System.out);
59     }
60     return FormattingModelProvider.createFormattingModelForPsiFile(element.getContainingFile(), block, settings);
61   }
62
63   @Nullable
64   @Override
65   public CommonCodeStyleSettings.IndentOptions getIndentOptionsToUse(@NotNull PsiFile file,
66                                                                      @NotNull FormatTextRanges ranges,
67                                                                      @NotNull CodeStyleSettings settings) {
68     return null;
69   }
70
71   @NotNull
72   public FormattingModel createModel(final PsiElement element, final CodeStyleSettings settings) {
73     return createModel(element, settings, FormattingMode.REFORMAT);
74   }
75
76   protected SpacingBuilder createSpacingBuilder(CodeStyleSettings settings) {
77     final IFileElementType file = LanguageParserDefinitions.INSTANCE.forLanguage(PythonLanguage.getInstance()).getFileNodeType();
78     final PyCodeStyleSettings pySettings = settings.getCustomSettings(PyCodeStyleSettings.class);
79
80     final CommonCodeStyleSettings commonSettings = settings.getCommonSettings(PythonLanguage.getInstance());
81     return new SpacingBuilder(commonSettings)
82       .between(CLASS_DECLARATION, STATEMENT_OR_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
83       .between(STATEMENT_OR_DECLARATION, CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
84       .between(FUNCTION_DECLARATION, STATEMENT_OR_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
85       .between(STATEMENT_OR_DECLARATION, FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
86       .after(FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
87       .after(CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
88       // Remove excess blank lines between imports, because ImportOptimizer gets rid of them anyway.
89       // Empty lines between import groups are handles in PyBlock#getSpacing
90       .between(IMPORT_STATEMENTS, IMPORT_STATEMENTS).spacing(0, Integer.MAX_VALUE, 1, false, 0)
91       .between(STATEMENT_OR_DECLARATION, STATEMENT_OR_DECLARATION).spacing(0, Integer.MAX_VALUE, 1, false, 1)
92
93       .between(COLON, STATEMENT_LIST).spacing(1, Integer.MAX_VALUE, 0, true, 0)
94       .afterInside(COLON, TokenSet.create(KEY_VALUE_EXPRESSION, LAMBDA_EXPRESSION)).spaceIf(pySettings.SPACE_AFTER_PY_COLON)
95
96       .afterInside(GT, ANNOTATION).spaces(1)
97       .betweenInside(MINUS, GT, ANNOTATION).none()
98       .beforeInside(ANNOTATION, FUNCTION_DECLARATION).spaces(1)
99       .beforeInside(ANNOTATION, NAMED_PARAMETER).none()
100       .afterInside(COLON, ANNOTATION).spaces(1)
101       .afterInside(RARROW, ANNOTATION).spaces(1)
102
103       .between(allButLambda(), PARAMETER_LIST).spaceIf(commonSettings.SPACE_BEFORE_METHOD_PARENTHESES)
104
105       .before(COLON).spaceIf(pySettings.SPACE_BEFORE_PY_COLON)
106       .after(COMMA).spaceIf(commonSettings.SPACE_AFTER_COMMA)
107       .before(COMMA).spaceIf(commonSettings.SPACE_BEFORE_COMMA)
108       .between(FROM_KEYWORD, DOT).spaces(1)
109       .between(DOT, IMPORT_KEYWORD).spaces(1)
110       .around(DOT).spaces(0)
111       .aroundInside(AT, DECORATOR_CALL).none()
112       .before(SEMICOLON).spaceIf(commonSettings.SPACE_BEFORE_SEMICOLON)
113       .withinPairInside(LPAR, RPAR, ARGUMENT_LIST).spaceIf(commonSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES)
114       .withinPairInside(LPAR, RPAR, PARAMETER_LIST).spaceIf(commonSettings.SPACE_WITHIN_METHOD_PARENTHESES)
115       .withinPairInside(LPAR, RPAR, FROM_IMPORT_STATEMENT).spaces(0)
116       .withinPairInside(LPAR, RPAR, GENERATOR_EXPRESSION).spaces(0)
117       .withinPairInside(LPAR, RPAR, PARENTHESIZED_EXPRESSION).spaces(0)
118       .before(LBRACKET).spaceIf(pySettings.SPACE_BEFORE_LBRACKET)
119
120       .afterInside(LBRACE, DICT_LITERAL_EXPRESSION).spaceIf(pySettings.SPACE_WITHIN_BRACES, pySettings.DICT_NEW_LINE_AFTER_LEFT_BRACE)
121       .beforeInside(RBRACE, DICT_LITERAL_EXPRESSION).spaceIf(pySettings.SPACE_WITHIN_BRACES, pySettings.DICT_NEW_LINE_BEFORE_RIGHT_BRACE)
122       .withinPair(LBRACE, RBRACE).spaceIf(pySettings.SPACE_WITHIN_BRACES)
123       .withinPair(LBRACKET, RBRACKET).spaceIf(commonSettings.SPACE_WITHIN_BRACKETS)
124
125       .before(ARGUMENT_LIST).spaceIf(commonSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES)
126
127       .around(DECORATOR_CALL).spacing(1, Integer.MAX_VALUE, 0, true, 0)
128       .after(DECORATOR_LIST).spacing(1, Integer.MAX_VALUE, 0, true, 0)
129
130       .aroundInside(EQ, ASSIGNMENT_STATEMENT).spaceIf(commonSettings.SPACE_AROUND_ASSIGNMENT_OPERATORS)
131       .aroundInside(EQ, NAMED_PARAMETER).spaceIf(pySettings.SPACE_AROUND_EQ_IN_NAMED_PARAMETER)
132       .aroundInside(EQ, KEYWORD_ARGUMENT_EXPRESSION).spaceIf(pySettings.SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT)
133
134       .around(AUG_ASSIGN_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_ASSIGNMENT_OPERATORS)
135       .aroundInside(ADDITIVE_OPERATIONS, BINARY_EXPRESSION).spaceIf(commonSettings.SPACE_AROUND_ADDITIVE_OPERATORS)
136       .aroundInside(MULTIPLICATIVE_OR_EXP, STAR_PARAMETERS).none()
137       .around(MULTIPLICATIVE_OR_EXP).spaceIf(commonSettings.SPACE_AROUND_MULTIPLICATIVE_OPERATORS)
138       .around(SHIFT_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_SHIFT_OPERATORS)
139       .around(BITWISE_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_BITWISE_OPERATORS)
140       .around(EQUALITY_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_EQUALITY_OPERATORS)
141       .around(RELATIONAL_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_RELATIONAL_OPERATORS)
142       .around(SINGLE_SPACE_KEYWORDS).spaces(1);
143   }
144
145   // should be all keywords?
146   private static final TokenSet SINGLE_SPACE_KEYWORDS = TokenSet.create(IN_KEYWORD, AND_KEYWORD, OR_KEYWORD, IS_KEYWORD,
147                                                                         IF_KEYWORD, ELIF_KEYWORD, FOR_KEYWORD, RETURN_KEYWORD, RAISE_KEYWORD,
148                                                                         ASSERT_KEYWORD, CLASS_KEYWORD, DEF_KEYWORD, DEL_KEYWORD,
149                                                                         EXEC_KEYWORD, GLOBAL_KEYWORD, IMPORT_KEYWORD, LAMBDA_KEYWORD,
150                                                                         NOT_KEYWORD, WHILE_KEYWORD, YIELD_KEYWORD);
151
152   private static TokenSet allButLambda() {
153     final PythonLanguage pythonLanguage = PythonLanguage.getInstance();
154     return TokenSet.create(IElementType.enumerate(new IElementType.Predicate() {
155       @Override
156       public boolean matches(@NotNull IElementType type) {
157         return type != LAMBDA_KEYWORD && type.getLanguage().isKindOf(pythonLanguage);
158       }
159     }));
160   }
161
162   public TextRange getRangeAffectingIndent(PsiFile file, int offset, ASTNode elementAtOffset) {
163     return null;
164   }
165
166   private static void printAST(ASTNode node, int indent) {
167     while (node != null) {
168       for (int i = 0; i < indent; i++) {
169         System.out.print(" ");
170       }
171       System.out.println(node.toString() + " " + node.getTextRange().toString());
172       printAST(node.getFirstChildNode(), indent + 2);
173       node = node.getTreeNext();
174     }
175   }
176
177   public boolean isEngagedToFormat(PsiElement context) {
178     PsiFile file = context.getContainingFile();
179     return file != null && file.getLanguage() == PythonLanguage.getInstance();
180   }
181 }