/**
* Copyright (c) 2012 Cloudsmith 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:
* Cloudsmith
*
*/
package org.cloudsmith.geppetto.pp.dsl.formatting;
import java.util.Iterator;
import java.util.List;
import org.cloudsmith.geppetto.common.stats.IntegerCluster;
import org.cloudsmith.geppetto.pp.AppendExpression;
import org.cloudsmith.geppetto.pp.AssignmentExpression;
import org.cloudsmith.geppetto.pp.dsl.services.PPGrammarAccess;
import org.cloudsmith.xtext.dommodel.DomModelUtils;
import org.cloudsmith.xtext.dommodel.IDomNode;
import org.cloudsmith.xtext.dommodel.formatter.DelegatingLayoutContext;
import org.cloudsmith.xtext.dommodel.formatter.DomNodeLayoutFeeder;
import org.cloudsmith.xtext.dommodel.formatter.ILayoutManager.ILayoutContext;
import org.cloudsmith.xtext.dommodel.formatter.css.Alignment;
import org.cloudsmith.xtext.dommodel.formatter.css.IStyleFactory;
import org.cloudsmith.xtext.dommodel.formatter.css.StyleFactory.WidthStyle;
import org.cloudsmith.xtext.dommodel.formatter.css.StyleSet;
import org.cloudsmith.xtext.textflow.ITextFlow;
import org.cloudsmith.xtext.textflow.TextFlow;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.Action;
import org.eclipse.xtext.Keyword;
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 AssignmentExpression that optionally cluster-aligns a sequence of assignments.
*
*/
public class AssignmentLayout {
private static class FirstLeafWithTextAndTheRest implements Predicate<IDomNode> {
private boolean firstLeafSeen = false;
@Override
public boolean apply(IDomNode input) {
if(!DomModelUtils.isHidden(input))
firstLeafSeen = true;
return firstLeafSeen;
}
}
@Inject
private IStyleFactory styles;
@Inject
private DomNodeLayoutFeeder feeder;
@Inject
private Provider<IBreakAndAlignAdvice> adviceProvider;
private Action theAssignmentExpression;
private Keyword theEqualSign;
private Keyword thePlusEqualSign;
private Action theAppendExpression;
private static Predicate<IDomNode> untilTheEnd = Predicates.alwaysFalse();
@Inject
public AssignmentLayout(PPGrammarAccess grammarAccess) {
// selectorEntryRule = grammarAccess.getSelectorEntryRule();
theAssignmentExpression = grammarAccess.getAssignmentExpressionAccess().getAssignmentExpressionLeftExprAction_1_0();
theEqualSign = grammarAccess.getAssignmentExpressionAccess().getEqualsSignKeyword_1_1();
theAppendExpression = grammarAccess.getAppendExpressionAccess().getAppendExpressionLeftExprAction_1_0();
thePlusEqualSign = grammarAccess.getAppendExpressionAccess().getPlusSignEqualsSignKeyword_1_1();
}
protected boolean _format(AppendExpression o, StyleSet styleSet, IDomNode node, ITextFlow flow,
ILayoutContext context) {
return commonFormat(styleSet, node, flow, context);
}
protected boolean _format(AssignmentExpression o, StyleSet styleSet, IDomNode node, ITextFlow flow,
ILayoutContext context) {
return commonFormat(styleSet, node, flow, context);
}
protected boolean commonFormat(StyleSet styleSet, IDomNode node, ITextFlow flow, ILayoutContext context) {
// unify the width of assignment expression LHS by scanning forward...
int availableWidth = 132; // irrelevant, but a value needed for width
IBreakAndAlignAdvice advice = adviceProvider.get();
final boolean doAlignment = advice.isAlignAssignments();
if(!doAlignment)
return false;
// used to collect the widths of each LHS width (typically just a variable name, but can be complex).
List<Integer> widths = Lists.newArrayList();
List<IDomNode> equalSignNodes = Lists.newArrayList();
IntegerCluster clusters = new IntegerCluster(advice.clusterSize());
boolean containsAppend = false;
// while siblings are assignments, starting with the current one
for(IDomNode aeNode = node; aeNode != null && includeInSequence(aeNode); aeNode = aeNode.getNextSibling()) {
DelegatingLayoutContext innerContext = new DelegatingLayoutContext(context, availableWidth);
TextFlow measuredFlow = new TextFlow(innerContext);
// iterate to format the LHS expression and measure, and find the = sign (which gets padded)
Iterator<IDomNode> itor = aeNode.treeIterator();
while(itor.hasNext()) {
IDomNode n = itor.next();
// forward until assignment expression =, or +=
if(!(n.getGrammarElement() == theAssignmentExpression || n.getGrammarElement() == theAppendExpression))
continue;
// Measure assignment expression
feeder.sequence(n, measuredFlow, innerContext, new FirstLeafWithTextAndTheRest(), untilTheEnd);
// forward to = or += sign
while(itor.hasNext()) {
n = itor.next();
if(n.getGrammarElement() == theEqualSign || n.getGrammarElement() == thePlusEqualSign)
break;
}
if(n.getGrammarElement() == thePlusEqualSign)
containsAppend = true;
if(!itor.hasNext())
break; // WAT, nothing after the '=', give up on this assignment, try to align the others
// If assignment node already has a width, it was processed by a preceding assignment
// and we were done a long time ago...
//
WidthStyle widthStyle = n.getStyles().getStyle(WidthStyle.class, n);
if(widthStyle != null)
return false;
equalSignNodes.add(n);
// collect the width of the last selector entry's values
int lastLineWidth = measuredFlow.getWidthOfLastLine();
clusters.add(lastLineWidth);
widths.add(lastLineWidth);
break;
}
}
markupWidths(equalSignNodes, widths, availableWidth, clusters, doAlignment, containsAppend);
return false;
}
protected boolean includeInSequence(IDomNode node) {
EObject semantic = node.getSemanticObject();
return semantic instanceof AssignmentExpression || semantic instanceof AppendExpression;
}
/**
* assign widths and alignment to the equal sign nodes
*
* @param equalSignNodes
* @param widths
* @param availableWidth
* @param clusters
* @param doCompaction
* @return
*/
private void markupWidths(List<IDomNode> equalSignNodes, List<Integer> widths, int availableWidth,
IntegerCluster clusters, boolean doAlignment, boolean containsAppend) {
// assign widths and alignment to the fat comma nodes
int opW = containsAppend
? 2
: 1;
for(int i = 0; i < equalSignNodes.size(); i++) {
IDomNode c = equalSignNodes.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(opW + mw - w)));
}
}
}