/**
* 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.xtext.dommodel.formatter;
import com.puppetlabs.xtext.dommodel.IDomNode;
import com.puppetlabs.xtext.dommodel.IDomNode.NodeClassifier;
import com.puppetlabs.xtext.dommodel.RegionMatch;
import com.puppetlabs.xtext.dommodel.formatter.css.Alignment;
import com.puppetlabs.xtext.dommodel.formatter.css.IFunctionFactory;
import com.puppetlabs.xtext.dommodel.formatter.css.LineBreaks;
import com.puppetlabs.xtext.dommodel.formatter.css.Spacing;
import com.puppetlabs.xtext.dommodel.formatter.css.StyleFactory.AlignedSeparatorIndex;
import com.puppetlabs.xtext.dommodel.formatter.css.StyleFactory.AlignmentStyle;
import com.puppetlabs.xtext.dommodel.formatter.css.StyleFactory.LineBreakStyle;
import com.puppetlabs.xtext.dommodel.formatter.css.StyleFactory.SpacingStyle;
import com.puppetlabs.xtext.dommodel.formatter.css.StyleFactory.TokenTextStyle;
import com.puppetlabs.xtext.dommodel.formatter.css.StyleFactory.VerbatimStyle;
import com.puppetlabs.xtext.dommodel.formatter.css.StyleFactory.WidthStyle;
import com.puppetlabs.xtext.dommodel.formatter.css.StyleSet;
import com.puppetlabs.xtext.textflow.ITextFlow;
import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.name.Named;
/**
* <p>
* A Dom Model Formatter driven by rules in a {@link DomCSS}.
* </p>
* <p>
* If there are no rules for spacing and line breaks in the style sheet produced by the given domProvider, default rules
* for "one space" and "no line break" will be used. This makes this formatter function as a "one space formatter" in
* the default case.
* </p>
* <p>
* Comment formatting is performed by delegating to an instance of {@link ILayout} that is bound to the name
* {@value #COMMENT_LAYOUT_NAME}.
* </p>
*/
public class FlowLayout extends AbstractLayoutManager implements ILayoutManager {
/**
* The name of the wanted ILayoutManager to handle comment formatting.
*/
public static final String COMMENT_LAYOUT_NAME = "CommentsLayout";
private static final Spacing defaultSpacing = new Spacing(1);
private static final LineBreaks defaultLineBreaks = new LineBreaks(0);
@Inject
private IFunctionFactory functions;
@Inject
@Named(COMMENT_LAYOUT_NAME)
protected ILayout commentLayout;
@Override
protected void formatComment(StyleSet styleSet, IDomNode node, ITextFlow output, ILayoutContext context) {
RegionMatch match = intersect(node, context);
if(match.isInside()) {
if(match.isContained() && !context.isWhitespacePreservation()) {
Preconditions.checkState(
commentLayout != null, "setCommentLayout(ILayout) must have been called first.");
commentLayout.format(styleSet, node, output, context);
}
else
// output the part of the text that is inside the region as verbatim text
output.appendText(match.apply().getFirst(), true);
}
}
@Override
protected void formatToken(StyleSet styleSet, IDomNode node, ITextFlow output, ILayoutContext context) {
RegionMatch match = intersect(node, context);
if(match.isInside()) {
if(match.isContained() && !context.isWhitespacePreservation())
formatTokenInternal(styleSet, node, output, context);
else
// output the part of the text that is inside the region as verbatim text
output.appendText(match.apply().getFirst(), true);
}
}
/**
* Formats the node without any checks for whitespace preservation or matching the contexts formatting region.
*
* @param styleSet
* @param node
* @param output
* @param context
*/
private void formatTokenInternal(StyleSet styleSet, IDomNode node, ITextFlow output, ILayoutContext context) {
String text = styleSet.getStyleValue(TokenTextStyle.class, node, functions.textOfNode());
Alignment alignment = styleSet.getStyleValue(AlignmentStyle.class, node);
boolean verbatim = styleSet.getStyleValue(VerbatimStyle.class, node, Boolean.FALSE);
final boolean defaultAlignment = alignment == null;
if(defaultAlignment)
alignment = Alignment.left;
final int textLength = text.length();
final Integer widthObject = styleSet.getStyleValue(WidthStyle.class, node);
final int width = widthObject == null
? textLength
: widthObject.intValue();
final int diff = width - textLength;
switch(alignment) {
case left:
output.appendText(text, verbatim);
// need to output padding separately as a 0 space gives the text flow permission to wrap which is a surprise
// just because everything is left aligned by default.
//
if(diff > 0 || !defaultAlignment)
output.appendSpaces(diff); // ok if 0 or negative = no spaces
break;
case right:
// ok to output 0 space that potentially triggers wrapping
output.appendSpaces(diff).appendText(text);
break;
case center:
int leftpad = (diff) / 2;
output.appendSpaces(leftpad).appendText(text).appendSpaces(diff - leftpad);
break;
case separator:
// width must have been set as it is otherwise meaningless to try to align on
// separator.
if(widthObject == null) {
output.appendText(text);
}
else {
// use first non word char as default
// right align in the given width
int separatorIndex = styleSet.getStyleValue(
AlignedSeparatorIndex.class, node, functions.firstNonWordChar());
if(separatorIndex > -1)
output.appendSpaces(width - separatorIndex).appendText(text);
else
output.appendSpaces(diff).appendText(text);
}
break;
}
}
@Override
protected void formatWhitespace(StyleSet styleSet, IDomNode node, ITextFlow output, ILayoutContext context) {
RegionMatch match = intersect(node, context);
boolean wsp = context.isWhitespacePreservation();
boolean implied = node.getStyleClassifiers().contains(NodeClassifier.IMPLIED);
// TODO: the combination of whitespacePreservation, implied-space AND regional formatting is not
// tested - it implies a priori knowledge of the textual representation of a serialized model without
// any source text nodes; and will probably in practice never be requested. (A serialization pass would
// needed to get the text to figure out where nodes are...)
//
if(match.isInside()) {
if(match.isContained() && (!wsp || implied)) {
// format if contained and formatting is wanted, or the space is implied
Spacing spacing = styleSet.getStyleValue(SpacingStyle.class, node, defaultSpacing);
LineBreaks lineBreaks = styleSet.getStyleValue(LineBreakStyle.class, node, defaultLineBreaks);
String text = styleSet.getStyleValue(TokenTextStyle.class, node);
applySpacingAndLinebreaks(context, node, text, spacing, lineBreaks, output);
}
else {
output.appendText(match.apply().getFirst());
}
}
}
}