Package com.puppetlabs.geppetto.pp.dsl.formatting

Source Code of com.puppetlabs.geppetto.pp.dsl.formatting.CaseLayout

/**
* Copyright (c) 2013 Puppet Labs, Inc. and other contributors, as listed below.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*   Puppet Labs
*/
package com.puppetlabs.geppetto.pp.dsl.formatting;

import java.util.List;

import com.puppetlabs.geppetto.common.stats.IntegerCluster;
import com.puppetlabs.geppetto.pp.CaseExpression;
import com.puppetlabs.geppetto.pp.dsl.formatting.PPSemanticLayout.StatementStyle;
import com.puppetlabs.geppetto.pp.dsl.services.PPGrammarAccess;
import com.puppetlabs.xtext.dommodel.DomModelUtils;
import com.puppetlabs.xtext.dommodel.IDomNode;
import com.puppetlabs.xtext.dommodel.formatter.DelegatingLayoutContext;
import com.puppetlabs.xtext.dommodel.formatter.DomNodeLayoutFeeder;
import com.puppetlabs.xtext.dommodel.formatter.ILayoutManager.ILayoutContext;
import com.puppetlabs.xtext.dommodel.formatter.css.Alignment;
import com.puppetlabs.xtext.dommodel.formatter.css.IStyleFactory;
import com.puppetlabs.xtext.dommodel.formatter.css.StyleSet;
import com.puppetlabs.xtext.textflow.ITextFlow;
import com.puppetlabs.xtext.textflow.MeasuredTextFlow;
import com.puppetlabs.xtext.textflow.TextFlow;
import org.eclipse.xtext.RuleCall;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Provider;

/**
* A sub layout handler for CaseExpression and Case
*
*/
public class CaseLayout {

  private static class SkipInitialWhitespacePredicate implements Predicate<IDomNode> {

    boolean firstNonWhitespaceSeen = false;

    @Override
    public boolean apply(IDomNode input) {
      if(firstNonWhitespaceSeen == false && DomModelUtils.isWhitespace(input) != true)
        firstNonWhitespaceSeen = true;
      return firstNonWhitespaceSeen;
    }

  }

  @Inject
  private IStyleFactory styles;

  private PPGrammarAccess grammarAccess;

  @Inject
  private DomNodeLayoutFeeder feeder;

  @Inject
  private Provider<IBreakAndAlignAdvice> adviceProvider;

  protected final Predicate<IDomNode> caseColonPredicate = new Predicate<IDomNode>() {

    @Override
    public boolean apply(IDomNode input) {
      return input.getGrammarElement() == grammarAccess.getCaseAccess().getColonKeyword_2();
    }

  };

  /** used to find the case nodes */
  private final RuleCall caseRuleCall;

  @Inject
  public CaseLayout(PPGrammarAccess grammarAccess) {
    this.grammarAccess = grammarAccess;
    caseRuleCall = grammarAccess.getCaseExpressionAccess().getCasesCaseParserRuleCall_3_0();

  }

  protected boolean _format(CaseExpression o, StyleSet styleSet, IDomNode node, ITextFlow flow, ILayoutContext context) {
    // unify the width of case expressions

    // Step 1, must format up to the first case expression to know the correct indentation of the case
    // expression. (At point of entry to this method, the whitespace between a preceding statement and the case
    // expression has not yet been processed, and thus, no WS, break, indent etc. has taken place.
    //
    DelegatingLayoutContext dlc = new DelegatingLayoutContext(context);
    MeasuredTextFlow continuedFlow = new MeasuredTextFlow((MeasuredTextFlow) flow);

    int currentMaxWidth = flow.getPreferredMaxWidth();
    int availableWidth = 0; // set when first case is seen

    IBreakAndAlignAdvice advice = adviceProvider.get();
    final boolean doCompaction = advice.compactCasesWhenPossible();
    final boolean doAlignment = advice.isAlignCases();

    // used to collect the widths of each case's width of its values
    List<Integer> widths = Lists.newArrayList();
    // int maxLastLine = 0;
    boolean firstCaseSeen = false;
    List<IDomNode> colonNodes = Lists.newArrayList();
    IntegerCluster clusters = new IntegerCluster(20);
    boolean allCompactable = true; // until proven wrong
    for(IDomNode n : node.getChildren()) {
      if(n.getGrammarElement() == caseRuleCall) {
        if(!firstCaseSeen) {
          // finish measurement of the position the case will appear at
          //
          continuedFlow.appendBreak();
          continuedFlow.getIndentation();
          availableWidth = currentMaxWidth - (continuedFlow.getIndentation() + 1) *
              continuedFlow.getIndentSize();
        }
        // used to measure output of formatted case values
        // adjust its width to available width (and do not mark items consumed in the given context)
        DelegatingLayoutContext innerContext = new DelegatingLayoutContext(context, availableWidth);
        TextFlow measuredFlow = new TextFlow(innerContext);
        // visit all nodes in case until the colon is hit, and format the output to the measured flow
        IDomNode colonNode = feeder.sequence(n, measuredFlow, innerContext, caseColonPredicate);
        if(doCompaction && !n.getStyleClassifiers().contains(StatementStyle.COMPACTABLE))
          allCompactable = false;

        colonNodes.add(colonNode);

        // collect the width of the last case's values
        int lastLineWidth = measuredFlow.getWidthOfLastLine();
        if(!firstCaseSeen) {
          // the space before the first case triggers case expression indentation of 1, this must be adjusted
          lastLineWidth -= measuredFlow.getIndentSize();
        }
        clusters.add(lastLineWidth);
        widths.add(lastLineWidth);
        firstCaseSeen = true;

      }
      else if(!firstCaseSeen) {
        // continue to feed everything (until first case seen). Exceptional case, there are no cases - then this is just wasted
        feeder.sequence(n, continuedFlow, dlc);
      }
    }
    List<Integer> remainingWidths = markupWidths(
      colonNodes, widths, availableWidth, clusters, doCompaction, doAlignment);

    if(doCompaction && allCompactable)
      markupCompact(colonNodes, remainingWidths, context);

    return false;
  }

  private boolean compactable(List<IDomNode> colonNodes, List<Integer> remainingWidths, ILayoutContext context) {
    // must measure each, stop if not all fits, otherwise prevent compaction
    final Predicate<IDomNode> alwaysFalse = Predicates.<IDomNode> alwaysFalse();
    for(int i = 0; i < colonNodes.size(); i++) {
      IDomNode p = colonNodes.get(i).getParent();
      IDomNode statements = DomModelUtils.nodeForGrammarElement(
        p, grammarAccess.getCaseAccess().getStatementsExpressionListParserRuleCall_4_0());
      DelegatingLayoutContext caseStatementContext = new DelegatingLayoutContext(context, remainingWidths.get(i));
      TextFlow caseStatementFlow = new TextFlow(caseStatementContext);
      if(statements != null)
        feeder.sequence(
          statements, caseStatementFlow, caseStatementContext, new SkipInitialWhitespacePredicate(),
          alwaysFalse);
      // only 1 line high and did not overflow
      if(!(caseStatementFlow.getHeight() <= 1 && caseStatementFlow.getWidthOfLastLine() <= remainingWidths.get(i))) {
        return false;
      }
    }
    return true;
  }

  private void markupCompact(List<IDomNode> colonNodes, List<Integer> remainingWidths, ILayoutContext context) {

    if(compactable(colonNodes, remainingWidths, context))
      // prevent whitespace after '{' and before '}' to break
      for(int i = 0; i < colonNodes.size(); i++) {
        IDomNode p = colonNodes.get(i).getParent();
        for(IDomNode n : p.getChildren()) {
          if(n.getGrammarElement() == grammarAccess.getCaseAccess().getLeftCurlyBracketKeyword_3()) {
            IDomNode ws = DomModelUtils.nextWhitespace(n);
            if(ws != null)
              ws.getStyles().add(StyleSet.withStyles(styles.oneSpace(), styles.noLineBreak()));
          }
          else if(n.getGrammarElement() == grammarAccess.getCaseAccess().getRightCurlyBracketKeyword_5()) {
            IDomNode ws = DomModelUtils.previousWhitespace(n);
            if(ws != null)
              ws.getStyles().add(StyleSet.withStyles(styles.oneSpace(), styles.noLineBreak()));
          }
        }
      }
  }

  /**
   * assign widths and alignment to the colon nodes
   * compute available width for remainder if all cases are compactable
   *
   * @param colonNodes
   * @param widths
   * @param availableWidth
   * @param clusters
   * @param doCompaction
   * @return
   */
  private List<Integer> markupWidths(List<IDomNode> colonNodes, List<Integer> widths, int availableWidth,
      IntegerCluster clusters, boolean doCompaction, boolean doAlignment) {
    // assign widths and alignment to the colon nodes
    // compute available width for remainder if all cases are compactable
    List<Integer> remainingWidths = doCompaction
        ? Lists.<Integer> newArrayList()
        : null;
    for(int i = 0; i < colonNodes.size(); i++) {
      IDomNode c = colonNodes.get(i);
      int w = widths.get(i);
      int mw = doAlignment
          ? clusters.clusterMax(w)
          : w;
      if(doAlignment)
        c.getStyles().add(StyleSet.withStyles(styles.align(Alignment.right), //
          styles.width(1 + mw - w)));
      if(doCompaction)
        remainingWidths.add(availableWidth - mw - 6); // 6 = ": { " +" }"
    }
    return remainingWidths;
  }
}
TOP

Related Classes of com.puppetlabs.geppetto.pp.dsl.formatting.CaseLayout

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.