blob: 4b71d6abec256505c08c1955129f18d23687748b [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.formatter;
import com.intellij.formatting.*;
import com.intellij.lang.ASTNode;
import com.intellij.lang.LanguageParserDefinitions;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.IFileElementType;
import com.intellij.psi.tree.TokenSet;
import com.jetbrains.python.PythonDialectsTokenSetProvider;
import com.jetbrains.python.PythonLanguage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.jetbrains.python.PyElementTypes.*;
import static com.jetbrains.python.PyTokenTypes.*;
/**
* @author yole
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, CustomFormattingModelBuilder {
private static final boolean DUMP_FORMATTING_AST = false;
@NotNull
@Override
public FormattingModel createModel(@NotNull PsiElement element,
@NotNull CodeStyleSettings settings,
@NotNull FormattingMode mode) {
final ASTNode fileNode = element.getContainingFile().getNode();
if (DUMP_FORMATTING_AST) {
System.out.println("AST tree for " + element.getContainingFile().getName() + ":");
printAST(fileNode, 0);
}
final PyBlockContext context = new PyBlockContext(settings, createSpacingBuilder(settings), mode);
final PyBlock block = new PyBlock(null, fileNode, null, Indent.getNoneIndent(), null, context);
if (DUMP_FORMATTING_AST) {
FormattingModelDumper.dumpFormattingModel(block, 2, System.out);
}
return FormattingModelProvider.createFormattingModelForPsiFile(element.getContainingFile(), block, settings);
}
@Nullable
@Override
public CommonCodeStyleSettings.IndentOptions getIndentOptionsToUse(@NotNull PsiFile file,
@NotNull FormatTextRanges ranges,
@NotNull CodeStyleSettings settings)
{
return null;
}
@NotNull
public FormattingModel createModel(final PsiElement element, final CodeStyleSettings settings) {
return createModel(element, settings, FormattingMode.REFORMAT);
}
protected SpacingBuilder createSpacingBuilder(CodeStyleSettings settings) {
final IFileElementType file = LanguageParserDefinitions.INSTANCE.forLanguage(PythonLanguage.getInstance()).getFileNodeType();
final PyCodeStyleSettings pySettings = settings.getCustomSettings(PyCodeStyleSettings.class);
final TokenSet STATEMENT_OR_DECLARATION =
TokenSet.orSet(PythonDialectsTokenSetProvider.INSTANCE.getStatementTokens(), CLASS_OR_FUNCTION);
final CommonCodeStyleSettings commonSettings = settings.getCommonSettings(PythonLanguage.getInstance());
return new SpacingBuilder(commonSettings)
.betweenInside(STATEMENT_OR_DECLARATION, CLASS_OR_FUNCTION, file).blankLines(pySettings.BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS)
.betweenInside(CLASS_OR_FUNCTION, STATEMENT_OR_DECLARATION, file).blankLines(pySettings.BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS)
.between(IMPORT_STATEMENTS, TokenSet.andNot(STATEMENT_OR_DECLARATION, IMPORT_STATEMENTS)).blankLines(commonSettings.BLANK_LINES_AFTER_IMPORTS)
.betweenInside(CLASS_OR_FUNCTION, CLASS_OR_FUNCTION, file).blankLines(pySettings.BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS)
.between(CLASS_DECLARATION, STATEMENT_OR_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
.between(STATEMENT_OR_DECLARATION, CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
.between(FUNCTION_DECLARATION, STATEMENT_OR_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
.between(STATEMENT_OR_DECLARATION, FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
.after(FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
.after(CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
.between(STATEMENT_OR_DECLARATION, STATEMENT_OR_DECLARATION).spacing(0, Integer.MAX_VALUE, 1, false, 1)
.between(COLON, STATEMENT_LIST).spacing(1, Integer.MAX_VALUE, 0, true, 0)
.afterInside(COLON, TokenSet.create(KEY_VALUE_EXPRESSION, LAMBDA_EXPRESSION)).spaceIf(pySettings.SPACE_AFTER_PY_COLON)
.afterInside(GT, ANNOTATION).spaces(1)
.betweenInside(MINUS, GT, ANNOTATION).none()
.beforeInside(ANNOTATION, FUNCTION_DECLARATION).spaces(1)
.beforeInside(ANNOTATION, NAMED_PARAMETER).none()
.between(allButLambda(), PARAMETER_LIST).spaceIf(commonSettings.SPACE_BEFORE_METHOD_PARENTHESES)
.before(COLON).spaceIf(pySettings.SPACE_BEFORE_PY_COLON)
.after(COMMA).spaceIf(commonSettings.SPACE_AFTER_COMMA)
.before(COMMA).spaceIf(commonSettings.SPACE_BEFORE_COMMA)
.between(FROM_KEYWORD, DOT).spaces(1)
.between(DOT, IMPORT_KEYWORD).spaces(1)
.around(DOT).spaces(0)
.before(SEMICOLON).spaceIf(commonSettings.SPACE_BEFORE_SEMICOLON)
.withinPairInside(LPAR, RPAR, ARGUMENT_LIST).spaceIf(commonSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES)
.withinPairInside(LPAR, RPAR, PARAMETER_LIST).spaceIf(commonSettings.SPACE_WITHIN_METHOD_PARENTHESES)
.withinPairInside(LPAR, RPAR, FROM_IMPORT_STATEMENT).spaces(0)
.before(LBRACKET).spaceIf(pySettings.SPACE_BEFORE_LBRACKET)
.withinPair(LBRACE, RBRACE).spaceIf(pySettings.SPACE_WITHIN_BRACES)
.withinPair(LBRACKET, RBRACKET).spaceIf(commonSettings.SPACE_WITHIN_BRACKETS)
.before(ARGUMENT_LIST).spaceIf(commonSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES)
.around(DECORATOR_CALL).spacing(1, Integer.MAX_VALUE, 0, true, 0)
.after(DECORATOR_LIST).spacing(1, Integer.MAX_VALUE, 0, true, 0)
.aroundInside(EQ, ASSIGNMENT_STATEMENT).spaceIf(commonSettings.SPACE_AROUND_ASSIGNMENT_OPERATORS)
.aroundInside(EQ, NAMED_PARAMETER).spaceIf(pySettings.SPACE_AROUND_EQ_IN_NAMED_PARAMETER)
.aroundInside(EQ, KEYWORD_ARGUMENT_EXPRESSION).spaceIf(pySettings.SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT)
.around(AUG_ASSIGN_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_ASSIGNMENT_OPERATORS)
.aroundInside(ADDITIVE_OPERATIONS, BINARY_EXPRESSION).spaceIf(commonSettings.SPACE_AROUND_ADDITIVE_OPERATORS)
.aroundInside(MULTIPLICATIVE_OR_EXP, STAR_PARAMETERS).none()
.around(MULTIPLICATIVE_OR_EXP).spaceIf(commonSettings.SPACE_AROUND_MULTIPLICATIVE_OPERATORS)
.around(SHIFT_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_SHIFT_OPERATORS)
.around(BITWISE_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_BITWISE_OPERATORS)
.around(EQUALITY_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_EQUALITY_OPERATORS)
.around(RELATIONAL_OPERATIONS).spaceIf(commonSettings.SPACE_AROUND_RELATIONAL_OPERATORS)
.around(SINGLE_SPACE_KEYWORDS).spaces(1);
}
// should be all keywords?
private static final TokenSet SINGLE_SPACE_KEYWORDS = TokenSet.create(IN_KEYWORD, AND_KEYWORD, OR_KEYWORD, IS_KEYWORD,
IF_KEYWORD, ELIF_KEYWORD, FOR_KEYWORD, RETURN_KEYWORD, RAISE_KEYWORD,
ASSERT_KEYWORD, CLASS_KEYWORD, DEF_KEYWORD, DEL_KEYWORD,
EXEC_KEYWORD, GLOBAL_KEYWORD, IMPORT_KEYWORD, LAMBDA_KEYWORD,
NOT_KEYWORD, WHILE_KEYWORD, YIELD_KEYWORD);
private static TokenSet allButLambda() {
final PythonLanguage pythonLanguage = PythonLanguage.getInstance();
return TokenSet.create(IElementType.enumerate(new IElementType.Predicate() {
@Override
public boolean matches(IElementType type) {
return type != LAMBDA_KEYWORD && type.getLanguage().isKindOf(pythonLanguage);
}
}));
}
public TextRange getRangeAffectingIndent(PsiFile file, int offset, ASTNode elementAtOffset) {
return null;
}
private static void printAST(ASTNode node, int indent) {
while (node != null) {
for (int i = 0; i < indent; i++) {
System.out.print(" ");
}
System.out.println(node.toString() + " " + node.getTextRange().toString());
printAST(node.getFirstChildNode(), indent + 2);
node = node.getTreeNext();
}
}
public boolean isEngagedToFormat(PsiElement context) {
PsiFile file = context.getContainingFile();
return file != null && file.getLanguage() == PythonLanguage.getInstance();
}
}