package com.jetbrains.lang.dart.ide.formatter;
import com.intellij.formatting.FormattingMode;
import com.intellij.formatting.Indent;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.formatter.FormatterUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.jetbrains.lang.dart.util.UsefulPsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import static com.jetbrains.lang.dart.DartTokenTypes.*;
import static com.jetbrains.lang.dart.DartTokenTypesSets.*;
public class DartIndentProcessor {
public static final TokenSet EXPRESSIONS = TokenSet
.create(ADDITIVE_EXPRESSION, ARRAY_ACCESS_EXPRESSION, ASSIGN_EXPRESSION, AS_EXPRESSION, AWAIT_EXPRESSION, BITWISE_EXPRESSION,
CALL_EXPRESSION, CASCADE_REFERENCE_EXPRESSION, COMPARE_EXPRESSION, EXPRESSION, FUNCTION_EXPRESSION, IS_EXPRESSION,
LIBRARY_COMPONENT_REFERENCE_EXPRESSION, LIST_LITERAL_EXPRESSION, LITERAL_EXPRESSION, LOGIC_AND_EXPRESSION, LOGIC_OR_EXPRESSION,
MAP_LITERAL_EXPRESSION, MULTIPLICATIVE_EXPRESSION, NEW_EXPRESSION, PARAMETER_NAME_REFERENCE_EXPRESSION,
PARENTHESIZED_EXPRESSION, PREFIX_EXPRESSION, REFERENCE_EXPRESSION, SHIFT_EXPRESSION, STRING_LITERAL_EXPRESSION,
SUFFIX_EXPRESSION, SUPER_EXPRESSION, SYMBOL_LITERAL_EXPRESSION, TERNARY_EXPRESSION, THIS_EXPRESSION, THROW_EXPRESSION,
VALUE_EXPRESSION, IF_NULL_EXPRESSION);
private final CommonCodeStyleSettings settings;
public DartIndentProcessor(CommonCodeStyleSettings settings) {
this.settings = settings;
}
public Indent getChildIndent(final ASTNode node, final FormattingMode mode) {
final IElementType elementType = node.getElementType();
final ASTNode prevSibling = UsefulPsiTreeUtil.getPrevSiblingSkipWhiteSpacesAndComments(node);
final IElementType prevSiblingType = prevSibling == null ? null : prevSibling.getElementType();
final ASTNode parent = node.getTreeParent();
final IElementType parentType = parent != null ? parent.getElementType() : null;
final ASTNode superParent = parent == null ? null : parent.getTreeParent();
final IElementType superParentType = superParent == null ? null : superParent.getElementType();
final int braceStyle = superParentType == FUNCTION_BODY ? settings.METHOD_BRACE_STYLE : settings.BRACE_STYLE;
if (parent == null || parent.getTreeParent() == null || parentType == EMBEDDED_CONTENT) {
return Indent.getNoneIndent();
}
if (elementType == MULTI_LINE_COMMENT_BODY) {
return Indent.getContinuationIndent();
}
if (elementType == DOC_COMMENT_LEADING_ASTERISK || elementType == MULTI_LINE_COMMENT_END) {
return Indent.getSpaceIndent(1, true);
}
if (settings.KEEP_FIRST_COLUMN_COMMENT && (elementType == SINGLE_LINE_COMMENT || elementType == MULTI_LINE_COMMENT)) {
final ASTNode previousNode = node.getTreePrev();
if (previousNode != null && previousNode.getElementType() == WHITE_SPACE && previousNode.getText().endsWith("\n")) {
return Indent.getAbsoluteNoneIndent();
}
}
if (COMMENTS.contains(elementType) && prevSiblingType == LBRACE && parentType == CLASS_BODY) {
return Indent.getNormalIndent();
}
if (parentType == ENUM_DEFINITION && isBetweenBraces(node)) {
// instead of isBetweenBraces(node) we can parse enum block as a separate ASTNode, or build formatter blocks not tied to AST.
return Indent.getNormalIndent();
}
if (parentType == MAP_LITERAL_EXPRESSION || parentType == LIST_LITERAL_EXPRESSION) {
if (elementType == LBRACE || elementType == RBRACE || elementType == LBRACKET || elementType == RBRACKET) {
return Indent.getNoneIndent();
}
if (elementType == TYPE_ARGUMENTS) {
return Indent.getNoneIndent();
}
// Be careful to preserve typing behavior.
if (elementType == MAP_LITERAL_ENTRY || elementType == EXPRESSION_LIST || elementType == COMMA) {
return Indent.getNormalIndent();
}
if (COMMENTS.contains(elementType)) {
return Indent.getNormalIndent();
}
return Indent.getNoneIndent();
}
if (elementType == LBRACE || elementType == RBRACE) {
switch (braceStyle) {
case CommonCodeStyleSettings.END_OF_LINE:
if (elementType == LBRACE && FormatterUtil.isPrecededBy(parent, SINGLE_LINE_COMMENT, WHITE_SPACE)) {
// Use Nystrom style rather than Allman.
return Indent.getContinuationIndent();
} // FALL THROUGH
case CommonCodeStyleSettings.NEXT_LINE:
case CommonCodeStyleSettings.NEXT_LINE_IF_WRAPPED:
return Indent.getNoneIndent();
case CommonCodeStyleSettings.NEXT_LINE_SHIFTED:
case CommonCodeStyleSettings.NEXT_LINE_SHIFTED2:
return Indent.getNormalIndent();
default:
return Indent.getNoneIndent();
}
}
if (parentType == PARENTHESIZED_EXPRESSION) {
if (elementType == LPAREN || elementType == RPAREN) {
return Indent.getNoneIndent();
}
return Indent.getContinuationIndent();
}
if (elementType == CLASS_MEMBERS) {
return Indent.getNormalIndent();
}
if (BLOCKS.contains(parentType)) {
final PsiElement psi = node.getPsi();
if (psi.getParent() instanceof PsiFile) {
return Indent.getNoneIndent();
}
return Indent.getNormalIndent();
}
if (elementType == LPAREN && (superParentType == METADATA || parentType == ARGUMENTS)) {
return Indent.getNormalIndent();
}
if (parentType == ARGUMENTS) {
if (COMMENTS.contains(elementType)) {
return Indent.getNormalIndent();
}
return Indent.getNoneIndent();
}
if (parentType == ARGUMENT_LIST) {
// see https://github.com/dart-lang/dart_style/issues/551
return parent.getLastChildNode().getElementType() == COMMA ? Indent.getNormalIndent() : Indent.getContinuationIndent();
}
if (parentType == FORMAL_PARAMETER_LIST || parentType == PARAMETER_TYPE_LIST) {
return Indent.getContinuationIndent();
}
if (parentType == OPTIONAL_FORMAL_PARAMETERS &&
elementType != LBRACE && elementType != RBRACE &&
elementType != LBRACKET && elementType != RBRACKET) {
return Indent.getNormalIndent();
}
if (parentType == FOR_STATEMENT && prevSiblingType == FOR_LOOP_PARTS_IN_BRACES && !BLOCKS.contains(elementType)) {
return Indent.getNormalIndent();
}
if (parentType == SWITCH_STATEMENT && (elementType == SWITCH_CASE || elementType == DEFAULT_CASE)) {
return Indent.getNormalIndent();
}
if ((parentType == SWITCH_CASE || parentType == DEFAULT_CASE) && elementType == STATEMENTS) {
return Indent.getNormalIndent();
}
if (parentType == WHILE_STATEMENT && prevSiblingType == RPAREN && !BLOCKS.contains(elementType)) {
return Indent.getNormalIndent();
}
if (parentType == DO_WHILE_STATEMENT && prevSiblingType == DO && !BLOCKS.contains(elementType)) {
return Indent.getNormalIndent();
}
if ((parentType == RETURN_STATEMENT) &&
prevSiblingType == RETURN &&
!BLOCKS.contains(elementType)) {
return Indent.getNormalIndent();
}
if (parentType == IF_STATEMENT && !BLOCKS.contains(elementType) &&
(prevSiblingType == RPAREN || (prevSiblingType == ELSE && elementType != IF_STATEMENT))) {
return Indent.getNormalIndent();
}
if (elementType == CASCADE_REFERENCE_EXPRESSION) {
return Indent.getNormalIndent();
}
if (elementType == OPEN_QUOTE && prevSiblingType == CLOSING_QUOTE && parentType == STRING_LITERAL_EXPRESSION) {
return Indent.getContinuationIndent();
}
if (BINARY_EXPRESSIONS.contains(parentType) && prevSibling != null) {
return Indent.getContinuationIndent();
}
if (elementType == COLON || parentType == TERNARY_EXPRESSION && elementType == QUEST) {
return Indent.getContinuationIndent();
}
if (elementType == HIDE_COMBINATOR || elementType == SHOW_COMBINATOR) {
return Indent.getContinuationIndent();
}
if (parentType == FUNCTION_BODY) {
if (FormatterUtil.isPrecededBy(node, EXPRESSION_BODY_DEF)) {
return Indent.getContinuationIndent();
}
}
if (elementType == CALL_EXPRESSION) {
if (FormatterUtil.isPrecededBy(node, EXPRESSION_BODY_DEF)) {
return Indent.getContinuationIndent();
}
if (FormatterUtil.isPrecededBy(node, ASSIGNMENT_OPERATOR)) {
return Indent.getContinuationIndent();
}
}
if ((elementType == REFERENCE_EXPRESSION || BINARY_EXPRESSIONS.contains(elementType)) &&
(FormatterUtil.isPrecededBy(node, ASSIGNMENT_OPERATOR) || FormatterUtil.isPrecededBy(node, EQ))) {
return Indent.getContinuationIndent();
}
if (elementType == VAR_DECLARATION_LIST_PART) {
return Indent.getContinuationIndent();
}
if (elementType == SUPER_CALL_OR_FIELD_INITIALIZER) {
return Indent.getContinuationIndent();
}
if (parentType == SUPER_CALL_OR_FIELD_INITIALIZER && elementType != COLON) {
return Indent.getNormalIndent();
}
if (parentType == CLASS_DEFINITION) {
if (elementType == SUPERCLASS || elementType == INTERFACES || elementType == MIXINS) {
return Indent.getContinuationIndent();
}
}
if (parentType == MIXIN_APPLICATION && elementType == MIXINS) {
return Indent.getContinuationIndent();
}
if (parentType == LIBRARY_NAME_ELEMENT) {
return Indent.getContinuationIndent();
}
if (elementType == SEMICOLON && FormatterUtil.isPrecededBy(node, SINGLE_LINE_COMMENT, WHITE_SPACE)) {
return Indent.getContinuationIndent();
}
if (elementType == DOT || elementType == QUEST_DOT) {
return Indent.getContinuationIndent();
}
if (parentType == TYPE_LIST && elementType == TYPE) {
return Indent.getContinuationIndent();
}
if (elementType == OPEN_QUOTE && parentType == STRING_LITERAL_EXPRESSION && superParentType == VAR_INIT) {
if (node.getText().length() < 3) {
return Indent.getContinuationIndent();
}
}
if (elementType == RAW_SINGLE_QUOTED_STRING && parentType == STRING_LITERAL_EXPRESSION && superParentType == VAR_INIT) {
return Indent.getContinuationIndent();
}
if (parentType == LONG_TEMPLATE_ENTRY && EXPRESSIONS.contains(elementType)) {
return Indent.getContinuationIndent();
}
return Indent.getNoneIndent();
}
private static boolean isBetweenBraces(@NotNull final ASTNode node) {
final IElementType elementType = node.getElementType();
if (elementType == LBRACE || elementType == RBRACE) return false;
for (ASTNode sibling = node.getTreePrev(); sibling != null; sibling = sibling.getTreePrev()) {
if (sibling.getElementType() == LBRACE) return true;
}
return false;
}
}