/*******************************************************************************
* Copyright (c) 2010 Martin Schnabel <mb0@mb0.org>.
* 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
******************************************************************************/
package org.axdt.as3.formatting;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import org.axdt.as3.services.As3GrammarAccess;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.XtextPackage;
import org.eclipse.xtext.formatting.IElementMatcherProvider.IElementMatcher;
import org.eclipse.xtext.formatting.impl.AbstractFormattingConfig.ElementLocator;
import org.eclipse.xtext.formatting.impl.AbstractFormattingConfig.ElementPattern;
import org.eclipse.xtext.formatting.impl.FormattingConfig;
import org.eclipse.xtext.formatting.impl.FormattingConfig.IndentationLocatorStart;
import org.eclipse.xtext.formatting.impl.FormattingConfigBasedStream;
import org.eclipse.xtext.parsetree.reconstr.IHiddenTokenHelper;
import org.eclipse.xtext.parsetree.reconstr.ITokenStream;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* Customized FormattingConfigBasedStream that filters out un-hidden line terminators
* and assures correct virtual semicolon formatting
* @author mb0
*/
public class As3FormattingStream extends FormattingConfigBasedStream {
protected static final Set<String> BREAKS = Sets.newHashSet("\n", "\r",
"\r\n");
protected final As3GrammarAccess grammarAccess;
protected ElementLocator afterVSemiLocator;
protected ElementLocator beforeVSemiLocator;
protected List<CommentEntry> queuedComments = Lists.newArrayList();
public As3FormattingStream(ITokenStream out, String indentation,
FormattingConfig cfg, IElementMatcher<ElementPattern> matcher,
IHiddenTokenHelper hiddenTokenHelper, boolean preserveSpaces,
As3GrammarAccess grammarAccess) {
super(out, indentation, cfg, matcher, hiddenTokenHelper, preserveSpaces);
this.grammarAccess = grammarAccess;
}
protected boolean isVSemiRule(EObject ele) {
EAttribute ruleName = XtextPackage.eINSTANCE.getAbstractRule_Name();
return ele != null && ele.eIsSet(ruleName)
&& "VirtualSemi".equals(ele.eGet(ruleName));
}
protected boolean isVSemiPart(EObject ele) {
return ele instanceof RuleCall && isVSemiRule(((RuleCall)ele).getRule());
}
@Override
public void writeSemantic(EObject grammarElement, String value)
throws IOException {
String preserve = null;
if (BREAKS.contains(value)) {
preserve = value;
value = null;
if (isVSemiPart(grammarElement)) {
value = ";";
}
}
if (value != null)
super.writeSemantic(grammarElement, value);
if (preserve != null)
preservedWS = preservedWS != null ? preservedWS + preserve
: preserve;
}
@Override
public void writeHidden(EObject grammarElement, String value)
throws IOException {
if (isComment(grammarElement)) {
Set<ElementLocator> collectLocators = collectLocators(grammarElement);
queuedComments.add(new CommentEntry(grammarElement, value, preservedWS, collectLocators));
preservedWS = null;
} else
super.writeHidden(grammarElement, value);
}
protected boolean isComment(EObject grammarElement) {
return grammarElement instanceof AbstractRule && hiddenTokenHelper.isComment((AbstractRule) grammarElement);
}
protected void writeComment(CommentEntry entry) throws IOException {
LineEntry e = createLineEntry(entry.grammarElement, entry.value, true, entry.locators, entry.preservedWS, indentationLevel,
null);
if (currentLine == null)
currentLine = createLine();
Line newLine = currentLine.add(e);
if (newLine != null)
currentLine = newLine;
}
protected class CommentEntry {
private final EObject grammarElement;
private final String value;
private final String preservedWS;
private final Set<ElementLocator> locators;
public CommentEntry(EObject grammarElement, String value,
String preservedWS, Set<ElementLocator> locators) {
this.grammarElement = grammarElement;
this.value = value;
this.preservedWS = preservedWS;
this.locators = locators;
}
}
@Override
public void flush() throws IOException {
writeQueuedComments(null);
super.flush();
}
protected boolean writeQueuedComments(ElementLocator indentStart) {
boolean addedIndentStart = false;
if (!queuedComments.isEmpty()) {
for (CommentEntry comment:queuedComments) {
try {
if (indentStart != null && !addedIndentStart) {
comment.locators.add(indentStart);
currentLine.flush();
currentLine = null;
addedIndentStart = true;
}
writeComment(comment);
} catch (IOException e) {
}
}
queuedComments.clear();
}
return addedIndentStart;
}
protected Set<ElementLocator> collectLocators(EObject ele) {
boolean curIsVSemi = isVSemiPart(ele);
boolean lastIsVSemi = isVSemiPart(last);
Set<ElementLocator> locators = super.collectLocators(ele);
if (!queuedComments.isEmpty() && !isComment(ele)) {
ElementLocator indentStart = null;
for (ElementLocator locator : locators) {
if (locator instanceof IndentationLocatorStart) {
indentStart = locator;
break;
}
}
if (writeQueuedComments(indentStart))
locators.remove(indentStart);
}
if (curIsVSemi)
locators.add(getBeforeVSemiLocator());
if (lastIsVSemi)
locators.add(getAfterVSemiLocator());
last = ele;
return locators;
}
protected ElementLocator getBeforeVSemiLocator() {
if (beforeVSemiLocator == null)
beforeVSemiLocator = lookupVSemiLocator(false);
return beforeVSemiLocator;
}
protected ElementLocator getAfterVSemiLocator() {
if (afterVSemiLocator == null)
afterVSemiLocator = lookupVSemiLocator(true);
return afterVSemiLocator;
}
protected ElementLocator lookupVSemiLocator(boolean left) {
ParserRule rule = grammarAccess.getVirtualSemiRule();
for (ElementPattern pattern : cfg.getLocatorsForSemanticTokens()) {
ElementLocator locator = pattern.getLocator();
EObject compare = left ? locator.getLeft() : locator.getRight();
if (rule.equals(compare))
return locator;
}
return null;
}
}