2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.jetbrains.python.formatter;
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;
34 import static com.jetbrains.python.PyElementTypes.*;
35 import static com.jetbrains.python.PyTokenTypes.*;
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();
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);
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);
60 return FormattingModelProvider.createFormattingModelForPsiFile(element.getContainingFile(), block, settings);
65 public CommonCodeStyleSettings.IndentOptions getIndentOptionsToUse(@NotNull PsiFile file,
66 @NotNull FormatTextRanges ranges,
67 @NotNull CodeStyleSettings settings) {
72 public FormattingModel createModel(final PsiElement element, final CodeStyleSettings settings) {
73 return createModel(element, settings, FormattingMode.REFORMAT);
76 protected SpacingBuilder createSpacingBuilder(CodeStyleSettings settings) {
77 final IFileElementType file = LanguageParserDefinitions.INSTANCE.forLanguage(PythonLanguage.getInstance()).getFileNodeType();
78 final PyCodeStyleSettings pySettings = settings.getCustomSettings(PyCodeStyleSettings.class);
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)
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)
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)
103 .between(allButLambda(), PARAMETER_LIST).spaceIf(commonSettings.SPACE_BEFORE_METHOD_PARENTHESES)
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)
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)
125 .before(ARGUMENT_LIST).spaceIf(commonSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES)
127 .around(DECORATOR_CALL).spacing(1, Integer.MAX_VALUE, 0, true, 0)
128 .after(DECORATOR_LIST).spacing(1, Integer.MAX_VALUE, 0, true, 0)
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)
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);
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);
152 private static TokenSet allButLambda() {
153 final PythonLanguage pythonLanguage = PythonLanguage.getInstance();
154 return TokenSet.create(IElementType.enumerate(new IElementType.Predicate() {
156 public boolean matches(@NotNull IElementType type) {
157 return type != LAMBDA_KEYWORD && type.getLanguage().isKindOf(pythonLanguage);
162 public TextRange getRangeAffectingIndent(PsiFile file, int offset, ASTNode elementAtOffset) {
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(" ");
171 System.out.println(node.toString() + " " + node.getTextRange().toString());
172 printAST(node.getFirstChildNode(), indent + 2);
173 node = node.getTreeNext();
177 public boolean isEngagedToFormat(PsiElement context) {
178 PsiFile file = context.getContainingFile();
179 return file != null && file.getLanguage() == PythonLanguage.getInstance();