Package org.intellij.erlang.formatter

Source Code of org.intellij.erlang.formatter.BinaryExpressionSequenceBlocksBuilder

/*
* Copyright 2012-2014 Sergey Ignatov
*
* 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 org.intellij.erlang.formatter;

import com.intellij.formatting.*;
import com.intellij.formatting.alignment.AlignmentStrategy;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.TokenType;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.formatter.WrappingUtil;
import com.intellij.psi.formatter.common.AbstractBlock;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.erlang.formatter.settings.ErlangCodeStyleSettings;
import org.intellij.erlang.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static org.intellij.erlang.ErlangParserDefinition.COMMENTS;
import static org.intellij.erlang.ErlangTypes.*;

public class ErlangFormattingBlock extends AbstractBlock {
  public static final TokenSet BLOCKS_TOKEN_SET = TokenSet.create(
    ERL_CLAUSE_BODY,
    ERL_MACROS_BODY,
    ERL_TUPLE_EXPRESSION,
    ERL_LIST_EXPRESSION,
    ERL_TRY_CLAUSES,
    ERL_CATCH_EXPRESSION,
    ERL_BEGIN_END_BODY,
    ERL_TOP_TYPE_CLAUSE,
    ERL_FUN_CLAUSES,
    ERL_TRY_EXPRESSIONS_CLAUSE,
    ERL_TYPE_SIG_GUARD,
    ERL_AFTER_CLAUSE,
    ERL_TOP_TYPE_CLAUSE
  );
  public static final TokenSet CURLY_CONTAINERS = TokenSet.create(
    ERL_TUPLE_EXPRESSION, ERL_RECORD_TUPLE, ERL_TYPED_RECORD_FIELDS, ERL_RECORD_LIKE_TYPE, ERL_MAP_TUPLE
  );
  public static final TokenSet PARENTHESIS_CONTAINERS = TokenSet.create(
    ERL_PARENTHESIZED_EXPRESSION, ERL_ARGUMENT_LIST, ERL_ARGUMENT_DEFINITION_LIST, ERL_FUN_TYPE, ERL_FUN_TYPE_ARGUMENTS
  );
  public static final TokenSet BRACKETS_CONTAINERS = TokenSet.create(
    ERL_LIST_EXPRESSION, ERL_EXPORT_FUNCTIONS, ERL_EXPORT_TYPES, ERL_BINARY_EXPRESSION
  );
  private static final TokenSet BINARY_EXPRESSIONS = TokenSet.create(
    ERL_LIST_OP_EXPRESSION, ERL_ASSIGNMENT_EXPRESSION, ERL_SEND_EXPRESSION, //right-assoc
    ERL_ADDITIVE_EXPRESSION, ERL_MULTIPLICATIVE_EXPRESSION
  );

  private final Indent myIndent;
  private final AlignmentStrategy myAlignmentStrategy;
  private final CommonCodeStyleSettings mySettings;
  private final ErlangCodeStyleSettings myErlangSettings;
  private final SpacingBuilder mySpacingBuilder;
  private List<Block> mySubBlocks;

  public ErlangFormattingBlock(@NotNull ASTNode node,
                               @Nullable Alignment alignment,
                               @Nullable AlignmentStrategy alignmentStrategy,
                               @Nullable Wrap wrap,
                               @NotNull CommonCodeStyleSettings settings,
                               @NotNull ErlangCodeStyleSettings erlangSettings,
                               @NotNull SpacingBuilder spacingBuilder,
                               int binaryExpressionIndex) {
    super(node, wrap, alignment);
    myAlignmentStrategy = alignmentStrategy;
    mySettings = settings;
    myErlangSettings = erlangSettings;
    mySpacingBuilder = spacingBuilder;
    myIndent = new ErlangIndentProcessor(erlangSettings).getChildIndent(node, binaryExpressionIndex);
  }

  @Override
  public Indent getIndent() {
    return myIndent;
  }

  @NotNull
  @Override
  protected List<Block> buildChildren() {
    if (mySubBlocks == null) {
      mySubBlocks = buildSubBlocks();
    }
    return new ArrayList<Block>(mySubBlocks);
  }

  private List<Block> buildSubBlocks() {
    final List<Block> blocks = new ArrayList<Block>();
    final Alignment baseAlignment = Alignment.createAlignment(true);
    final Alignment baseAlignment2 = Alignment.createAlignment(true);
    final AlignmentStrategy alignmentStrategy = createOrGetAlignmentStrategy();
    final Ref<Wrap> chopDownIfLongWrap = new Ref<Wrap>();

    // if uniform binary expressions option is enabled, blocks for binary expression sequences are built flat, that is
    // for an expression like 1 + 1 + 1 a single parent block with 5 children in it is constructed.
    if (myErlangSettings.UNIFORM_BINARY_EXPRESSIONS && BINARY_EXPRESSIONS.contains(myNode.getElementType())) {
      class BinaryExpressionSequenceBlocksBuilder {
        private int myBinaryExpressionIndex = 0;
        void build(ASTNode node) {
          for (ASTNode child = node.getFirstChildNode(); child != null; child = child.getTreeNext()) {
            if (!shouldCreateBlockFor(child)) continue;
            IElementType childType = child.getElementType();
            if (BINARY_EXPRESSIONS.contains(childType) ||
              myBinaryExpressionIndex != 0 &&
                childType == ERL_MAX_EXPRESSION &&
                child.getFirstChildNode() != null &&
                child.getFirstChildNode().getElementType() == ERL_STRING_LITERAL) {
              build(child);
            }
            else {
              blocks.add(createChildBlock(node, child, chopDownIfLongWrap, baseAlignment, baseAlignment2, alignmentStrategy, myBinaryExpressionIndex));
              myBinaryExpressionIndex++;
            }
          }
        }
      }
      new BinaryExpressionSequenceBlocksBuilder().build(myNode);
    }
    else {
      for (ASTNode child = myNode.getFirstChildNode(); child != null; child = child.getTreeNext()) {
        if (!shouldCreateBlockFor(child)) continue;
        blocks.add(createChildBlock(myNode, child, chopDownIfLongWrap, baseAlignment, baseAlignment2, alignmentStrategy, -1));
      }
    }
    return Collections.unmodifiableList(blocks);
  }

  private static boolean shouldCreateBlockFor(ASTNode node) {
    return node.getTextRange().getLength() != 0 && node.getElementType() != TokenType.WHITE_SPACE;
  }

  private ErlangFormattingBlock createChildBlock(ASTNode parent,
                                                 ASTNode child,
                                                 Ref<Wrap> chopDownIfLongWrap,
                                                 Alignment baseAlignment,
                                                 Alignment baseAlignment2,
                                                 @Nullable AlignmentStrategy alignmentStrategy,
                                                 int binaryExpressionIndex) {
    Alignment alignment = getAlignment(parent, child, baseAlignment, baseAlignment2, binaryExpressionIndex);
    WrapType wrapType = calculateWrapType(parent, child);
    Wrap wrap;
    if (wrapType == WrapType.CHOP_DOWN_IF_LONG) {
      if (chopDownIfLongWrap.isNull()) {
        chopDownIfLongWrap.set(Wrap.createWrap(wrapType, true));
      }
      wrap = chopDownIfLongWrap.get();
    }
    else if (wrapType == null) {
      wrap = null;
    }
    else {
      wrap = Wrap.createWrap(wrapType, true);
    }
    return new ErlangFormattingBlock(child, alignment, alignmentStrategy, wrap, mySettings, myErlangSettings, mySpacingBuilder, binaryExpressionIndex);
  }

  @Nullable
  private WrapType calculateWrapType(@NotNull ASTNode parent, @NotNull ASTNode node) {
    IElementType parentType = parent.getElementType();
    PsiElement nodePsi = node.getPsi();
    PsiElement parentPsi = parent.getPsi();
    if (parentType == ERL_CLAUSE_BODY && nodePsi instanceof ErlangExpression) {
      return WrappingUtil.getWrapType(myErlangSettings.EXPRESSION_IN_CLAUSE_WRAP);
    }
    if (parentType == ERL_ARGUMENT_LIST && nodePsi instanceof ErlangExpression) {
      return WrappingUtil.getWrapType(mySettings.CALL_PARAMETERS_WRAP);
    }
    if (parentPsi instanceof ErlangFakeBinaryExpression && nodePsi instanceof ErlangExpression) {
      return WrappingUtil.getWrapType(mySettings.BINARY_OPERATION_WRAP);
    }
    return null;
  }

  @Nullable
  private Alignment getAlignment(@NotNull ASTNode parent, @NotNull ASTNode child, @Nullable Alignment baseAlignment, @Nullable Alignment baseAlignment2, int binaryExpressionIndex) {
    IElementType childType = child.getElementType();
    IElementType parentType = parent.getElementType();
    Alignment fromStrategy = calculateAlignmentFromStrategy(parent, child);
    if (fromStrategy != null) return fromStrategy;

    if (PARENTHESIS_CONTAINERS.contains(parentType)) {
      if (childType != ERL_PAR_LEFT && childType != ERL_PAR_RIGHT && childType != ERL_COMMA) {if (myErlangSettings.ALIGN_MULTILINE_BLOCK) return baseAlignment;}
      else if (myErlangSettings.NEW_LINE_BEFORE_COMMA) return baseAlignment2;
    }
    if (CURLY_CONTAINERS.contains(parentType)) {
      if (childType != ERL_CURLY_LEFT && childType != ERL_CURLY_RIGHT && childType != ERL_COMMA && childType != ERL_RADIX) {
        if (myErlangSettings.ALIGN_MULTILINE_BLOCK) return baseAlignment;
      }
      else if (myErlangSettings.NEW_LINE_BEFORE_COMMA) return baseAlignment2;
    }
    if (BRACKETS_CONTAINERS.contains(parentType)) {
      if (childType != ERL_BRACKET_LEFT && childType != ERL_BRACKET_RIGHT && childType != ERL_BIN_START && childType != ERL_BIN_END &&
        childType != ERL_COMMA && childType != ERL_OP_OR) {if (myErlangSettings.ALIGN_MULTILINE_BLOCK) return baseAlignment;}
      else if (myErlangSettings.NEW_LINE_BEFORE_COMMA) return baseAlignment2;
    }
    if (myErlangSettings.ALIGN_MULTILINE_BLOCK) {
      if (parentType == ERL_LIST_COMPREHENSION) {
        boolean bracketsCurliesOrComma = childType == ERL_BRACKET_LEFT || childType == ERL_BRACKET_RIGHT || childType == ERL_COMMA ||
          childType == ERL_CURLY_LEFT || childType == ERL_CURLY_RIGHT || childType == ERL_RADIX;
        if (!bracketsCurliesOrComma && childType != ERL_BIN_START && childType != ERL_BIN_END && childType != ERL_LC_EXPRS) return baseAlignment;
      }
      if (parentType == ERL_FUN_TYPE_SIGS && childType == ERL_TYPE_SIG) {
        return baseAlignment;
      }
      PsiElement psi = parent.getPsi();
      if ((psi instanceof ErlangFakeBinaryExpression || parentType == ERL_MAX_EXPRESSION && childType == ERL_STRING_LITERAL) &&
        (binaryExpressionIndex > 1 || binaryExpressionIndex < 0)) {
        return baseAlignment;
      }
    }
    if (myErlangSettings.ALIGN_GUARDS && parentType == ERL_GUARD && childType != ERL_COMMA) return baseAlignment;
    return null;
  }

  @Nullable
  private Alignment calculateAlignmentFromStrategy(@NotNull ASTNode parent, ASTNode child) {
    @NotNull IElementType childType = child.getElementType();
    @NotNull IElementType parentType = parent.getElementType();
    if (myAlignmentStrategy != null) {
      Alignment alignment = myAlignmentStrategy.getAlignment(parentType, childType);
      if (alignment != null &&
        childType == ERL_CLAUSE_BODY && myErlangSettings.ALIGN_FUNCTION_CLAUSES &&
        (myErlangSettings.NEW_LINE_AFTER_ARROW == ErlangCodeStyleSettings.NewLineAfterArrow.FORCE || StringUtil.countNewLines(child.getText()) > 0)) {
        return null; // redesign this hack
      }
      return alignment;
    }
    return null;
  }

  @Nullable
  private AlignmentStrategy createOrGetAlignmentStrategy() {
    PsiElement psi = getNode().getPsi();
    if (myErlangSettings.ALIGN_FUNCTION_CLAUSES && psi instanceof ErlangFunction) {
      return AlignmentStrategy.createAlignmentPerTypeStrategy(ContainerUtil.list(ERL_CLAUSE_BODY), ERL_FUNCTION_CLAUSE, true);
    }
    if (myErlangSettings.ALIGN_FUN_CLAUSES && psi instanceof ErlangFunExpression) {
      return AlignmentStrategy.createAlignmentPerTypeStrategy(ContainerUtil.list(ERL_FUN_CLAUSE), ERL_FUN_CLAUSES, true);
    }
    if (myErlangSettings.ALIGN_RECORD_FIELD_ASSIGNMENTS && psi instanceof ErlangRecordTuple) {
      return AlignmentStrategy.createAlignmentPerTypeStrategy(ContainerUtil.list(ERL_OP_EQ), ERL_RECORD_FIELD, true);
    }

    return myAlignmentStrategy;
  }

  @Override
  @Nullable
  public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {
    if (child2 instanceof ErlangFormattingBlock) {
      ASTNode node = ((ErlangFormattingBlock) child2).getNode();
      if (COMMENTS.contains(node.getElementType()) && mySettings.KEEP_FIRST_COLUMN_COMMENT) {
        return Spacing.createKeepingFirstColumnSpacing(0, Integer.MAX_VALUE, true, mySettings.KEEP_BLANK_LINES_IN_CODE);
      }
    }
    //adds a space between fun and and fun name var in named fun expressions
    if (child1 instanceof ErlangFormattingBlock && child2 instanceof ErlangFormattingBlock &&
      ((ErlangFormattingBlock) child1).getNode().getElementType() == ERL_FUN &&
      ((ErlangFormattingBlock) child2).getNode().getElementType() == ERL_FUN_CLAUSES) {
      ErlangFunClauses funClauses = (ErlangFunClauses) ((ErlangFormattingBlock) child2).getNode().getPsi();
      List<ErlangFunClause> funClauseList = funClauses.getFunClauseList();
      if (!funClauseList.isEmpty() && funClauseList.get(0).getArgumentDefinition() != null) {
        return Spacing.createSpacing(1, 1, 0, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
      }
    }
    //force newline after arrow
    if (myErlangSettings.NEW_LINE_AFTER_ARROW != ErlangCodeStyleSettings.NewLineAfterArrow.DO_NOT_FORCE &&
      child1 instanceof ErlangFormattingBlock && ((ErlangFormattingBlock) child1).getNode().getElementType() == ERL_ARROW) {
      int spaceAroundArrow = myErlangSettings.SPACE_AROUND_ARROW ? 1 : 0;
      if (myErlangSettings.NEW_LINE_AFTER_ARROW == ErlangCodeStyleSettings.NewLineAfterArrow.FORCE_EXCEPT_ONE_LINE_CLAUSES) {
        TextRange dependency = TextRange.create(child2.getTextRange().getStartOffset(), this.getTextRange().getEndOffset());
        return Spacing.createDependentLFSpacing(spaceAroundArrow, spaceAroundArrow, dependency, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
      }
      return Spacing.createSpacing(spaceAroundArrow, spaceAroundArrow, 1, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
    }
    return mySpacingBuilder.getSpacing(this, child1, child2);
  }

  @NotNull
  @Override
  public ChildAttributes getChildAttributes(int newChildIndex) {
    Indent childIndent = getChildIndent(myNode.getElementType(), newChildIndex);
    IElementType type = newChildIndex > 0 ? getIElementType(newChildIndex) : null;
    Alignment alignment = getChildAlignment(type);
    if (childIndent != null) return new ChildAttributes(childIndent, alignment);
    if (type != null) childIndent = getChildIndent(type, newChildIndex);
    return new ChildAttributes(childIndent == null ? Indent.getNoneIndent() : childIndent, alignment);
  }

  @Nullable
  private Alignment getChildAlignment(@Nullable IElementType type) {
    if (type != ERL_COMMA && myErlangSettings.NEW_LINE_BEFORE_COMMA) {
      IElementType parentType = getNode().getElementType();
      if (BRACKETS_CONTAINERS.contains(parentType) || parentType == ERL_RECORD_TUPLE) {
        return getSubBlocks().get(0).getAlignment();
      }
    }
    return null;
  }

  @Nullable
  private IElementType getIElementType(int newChildIndex) {
    Block block = getSubBlocks().get(newChildIndex - 1);
    while (block instanceof ErlangFormattingBlock && !block.getSubBlocks().isEmpty()) {
      List<Block> subBlocks = block.getSubBlocks();
      Block childBlock = subBlocks.get(subBlocks.size() - 1);
      if (!(childBlock instanceof ErlangFormattingBlock)) break;
      else {
        ASTNode node = ((ErlangFormattingBlock) childBlock).getNode();
        PsiElement psi = node.getPsi();
        IElementType elementType = node.getElementType();
        if (elementType instanceof ErlangTokenType) break;
        if (psi instanceof LeafPsiElement || psi instanceof ErlangQAtom || psi instanceof ErlangQVar) break;
      }
      block = childBlock;
    }
    return block instanceof ErlangFormattingBlock ? ((ErlangFormattingBlock) block).getNode().getElementType() : null;
  }

  @Nullable
  private Indent getChildIndent(@Nullable IElementType type, int newChildIndex) {
    if (getNode().getPsi() instanceof ErlangFunction && type == ERL_SEMI) return Indent.getNoneIndent();
    if (
      type == ERL_IF_EXPRESSION && newChildIndex == 1 ||
      type == ERL_CASE_EXPRESSION && newChildIndex == 1 ||
      type == ERL_BEGIN_END_EXPRESSION && newChildIndex == 1 ||
      type == ERL_AFTER_CLAUSE ||
      type == ERL_FUN_EXPRESSION && newChildIndex == 1 ||
      type == ERL_RECEIVE_EXPRESSION && newChildIndex == 1 ||
      type == ERL_TRY_CATCH && newChildIndex == 1 ||
      type == ERL_TRY_EXPRESSION && newChildIndex == 1 ||
      type == ERL_OF && newChildIndex == 3 ||
      type == ERL_SEMI) {
      return Indent.getNormalIndent(true);
    }

    if (type == ERL_BEGIN_END_BODY) return Indent.getNoneIndent();

    if (type == ERL_TRY_EXPRESSIONS_CLAUSE && newChildIndex == 1) return Indent.getNoneIndent();

    if (BLOCKS_TOKEN_SET.contains(type) ||
      type == ERL_TYPED_RECORD_FIELDS
      ) return Indent.getNormalIndent(false);

    return null;
  }

  @Override
  public boolean isLeaf() {
    return myNode.getFirstChildNode() == null;
  }
}
TOP

Related Classes of org.intellij.erlang.formatter.BinaryExpressionSequenceBlocksBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.