/**
* 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.Map;
import java.util.Map.Entry;
import org.cloudsmith.geppetto.common.stats.IntegerCluster;
import org.cloudsmith.geppetto.pp.DefinitionArgument;
import org.cloudsmith.geppetto.pp.DefinitionArgumentList;
import org.cloudsmith.geppetto.pp.dsl.formatting.IBreakAndAlignAdvice.WhenToApplyForDefinition;
import org.cloudsmith.geppetto.pp.dsl.services.PPGrammarAccess;
import org.cloudsmith.geppetto.pp.dsl.services.PPGrammarAccess.DefinitionArgumentListElements;
import org.cloudsmith.xtext.dommodel.DomModelUtils;
import org.cloudsmith.xtext.dommodel.IDomNode;
import org.cloudsmith.xtext.dommodel.formatter.ILayoutManager.ILayoutContext;
import org.cloudsmith.xtext.dommodel.formatter.LayoutUtils;
import org.cloudsmith.xtext.dommodel.formatter.css.Alignment;
import org.cloudsmith.xtext.dommodel.formatter.css.IStyleFactory;
import org.cloudsmith.xtext.dommodel.formatter.css.StyleSet;
import org.cloudsmith.xtext.textflow.ITextFlow;
import org.eclipse.emf.ecore.EObject;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Provider;
/**
* <p>
* Performs semantic layout on a DefinitionArgumentList in combination with text-fit check.
* </p>
* <p>
* if the DefinitionArgumentList list does not fit on the same line:
* <ul>
* <li>break after '(' and indent</li>
* <li>break after each item on ',' (but not on end comma)</li>
* <li>align '=' using clustered width padding</li>
* <li>dedent on ')'</li>
* </ul>
* The styling is assigned to the nodes directly to override all other rule based styling.
*/
public class DefinitionArgumentListLayout {
@Inject
private IStyleFactory styles;
@Inject
private PPGrammarAccess grammarAccess;
@Inject
private Provider<IBreakAndAlignAdvice> breakAlignAdviceProvider;
@Inject
private LayoutUtils layoutUtils;
private void assignAlignmentAndWidths(Map<IDomNode, Integer> operatorNodes, IntegerCluster cluster) {
for(Entry<IDomNode, Integer> entry : operatorNodes.entrySet()) {
int w = entry.getValue();
entry.getKey().getStyles().add(
StyleSet.withStyles(styles.align(Alignment.right), styles.width(1 + cluster.clusterMax(w) - w)));
}
}
protected boolean defaultsArePresent(DefinitionArgumentList o) {
for(DefinitionArgument x : o.getArguments()) {
if(x.getValue() != null)
return true;
}
return false;
}
protected boolean format(DefinitionArgumentList o, StyleSet styleSet, IDomNode node, ITextFlow flow,
ILayoutContext context) {
final IBreakAndAlignAdvice advisor = getAlignAdvice();
final WhenToApplyForDefinition advice = advisor.definitionParameterListAdvice();
final int clusterWidth = advisor.clusterSize();
boolean breakAndAlign = false;
switch(advice) {
case Never:
break;
case Always:
breakAndAlign = true;
break;
case DefaultsPresent:
if(defaultsArePresent(o)) {
breakAndAlign = true;
break;
}
// fall through
case OnOverflow:
if(!layoutUtils.fitsOnSameLine(node, flow, context))
breakAndAlign = true;
break;
}
markup(node, breakAndAlign, clusterWidth); // that was easy
return false;
}
protected IBreakAndAlignAdvice getAlignAdvice() {
return breakAlignAdviceProvider.get();
}
protected void markup(IDomNode node, final boolean breakAndAlign, final int clusterWidth) {
Iterator<IDomNode> itor = node.treeIterator();
Map<IDomNode, Integer> operatorNodes = Maps.newHashMap();
IntegerCluster cluster = new IntegerCluster(clusterWidth);
// With a little help From the grammar:
// DefinitionArgumentList returns pp::DefinitionArgumentList : {pp::DefinitionArgumentList}
// '('
// (arguments += DefinitionArgument (',' arguments += DefinitionArgument)*)? ','?
// ')'
// ;
//
// DefinitionArgument returns pp::DefinitionArgument
// : argName = UNION_VARIABLE_OR_NAME ((op = '=' | op = '=>') value = AssignmentExpression)?
// ;
final DefinitionArgumentListElements access = grammarAccess.getDefinitionArgumentListAccess();
while(itor.hasNext()) {
IDomNode n = itor.next();
EObject ge = n.getGrammarElement();
if(ge == access.getLeftParenthesisKeyword_1()) {
IDomNode nextLeaf = DomModelUtils.nextLeaf(n);
if(DomModelUtils.isWhitespace(nextLeaf))
if(breakAndAlign)
nextLeaf.getStyles().add(StyleSet.withStyles(styles.oneLineBreak(), styles.indent()));
else
nextLeaf.getStyles().add(StyleSet.withStyles(styles.indent()));
}
else if(ge == access.getRightParenthesisKeyword_4()) {
IDomNode prevLeaf = DomModelUtils.previousLeaf(n);
if(DomModelUtils.isWhitespace(prevLeaf))
prevLeaf.getStyles().add(StyleSet.withStyles(styles.dedent()));
}
else if(breakAndAlign) {
if(ge == access.getCommaKeyword_2_1_0()) {
IDomNode nextLeaf = DomModelUtils.nextLeaf(n);
if(DomModelUtils.isWhitespace(nextLeaf))
nextLeaf.getStyles().add(StyleSet.withStyles(styles.oneLineBreak()));
}
// Break on endcomma does not look good - here is how it would be done though...
// else if(ge == grammarAccess.getDefinitionArgumentListAccess().getEndCommaParserRuleCall_3()) {
// IDomNode spaceAfterEndComma = DomModelUtils.nextLeaf(DomModelUtils.firstTokenWithText(n));
// if(DomModelUtils.isWhitespace(spaceAfterEndComma))
// spaceAfterEndComma.getStyles().add(StyleSet.withStyles(styles.oneLineBreak()));
//
// }
else if(ge == grammarAccess.getDefinitionArgumentAccess().getOpEqualsSignGreaterThanSignKeyword_1_0_1_0() ||
ge == grammarAccess.getDefinitionArgumentAccess().getOpEqualsSignKeyword_1_0_0_0()) {
EObject semantic = n.getParent().getSemanticObject();
if(semantic != null && semantic instanceof DefinitionArgument) {
DefinitionArgument da = (DefinitionArgument) semantic;
int length = da.getArgName() == null
? 0
: da.getArgName().length();
operatorNodes.put(n, length);
cluster.add(length);
}
}
}
}
if(breakAndAlign)
assignAlignmentAndWidths(operatorNodes, cluster);
}
}