Merge pull request #188 from eclipse/me/formatter4

[formatter] made mechanism for finding text regions more powerful
This commit is contained in:
Moritz Eysholdt 2015-04-24 14:02:05 +02:00
commit 1910d44964
35 changed files with 1366 additions and 726 deletions

View file

@ -29,6 +29,7 @@ import org.eclipse.xtext.formatting2.internal.TextReplacerContext;
import org.eclipse.xtext.formatting2.internal.TextReplacerMerger;
import org.eclipse.xtext.formatting2.internal.WhitespaceReplacer;
import org.eclipse.xtext.formatting2.regionaccess.IComment;
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionExtensions;
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegionPart;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
@ -126,9 +127,9 @@ import com.google.common.collect.Lists;
* </p>
*
* <p>
* The methods {@code regionForFeature()} and {@code regionForKeyword} are extension methods:
* {@link ITextRegionAccess#regionForFeature(EObject, EStructuralFeature)} and
* {@link ITextRegionAccess#regionForKeyword(EObject, String)}. They return an {@link ISemanticRegion}.
* The methods {@code regionForFeature()} and {@code regionForKeyword} are extension methods: {link
* ITextRegionAccess#regionForFeature(EObject, EStructuralFeature)} and {link
* ITextRegionAccess#regionForKeyword(EObject, String)}. They return an {@link ISemanticRegion}.
* </p>
*
* <p>
@ -163,7 +164,7 @@ public abstract class AbstractFormatter2 implements IFormatter2 {
* Offer subclasses access to the methods from {@link ITextRegionAccess} as extension methods.
*/
@Extension
protected ITextRegionAccess regionAccess;
protected ITextRegionExtensions textRegionExtensions;
private FormatterRequest request = null;
@ -297,17 +298,21 @@ public abstract class AbstractFormatter2 implements IFormatter2 {
return request;
}
public ITextRegionAccess getTextRegionAccess() {
return request.getTextRegionAccess();
}
/**
* Overwrite this method to initialize member fields before {@link #format(Object, IFormattableDocument)} is called.
*/
protected void initalize(FormatterRequest request) {
this.request = request;
this.regionAccess = request.getTextRegionAccess();
this.textRegionExtensions = request.getTextRegionAccess().getExtensions();
}
protected List<ITextReplacement> postProcess(IFormattableDocument document, List<ITextReplacement> replacements) {
List<ITextSegment> expected = Lists.newArrayList();
IHiddenRegion current = regionAccess.regionForRootEObject().getPreviousHiddenRegion();
IHiddenRegion current = getTextRegionAccess().regionForRootEObject().getPreviousHiddenRegion();
while (current != null) {
if (current.isUndefined())
expected.addAll(current.getMergedSpaces());
@ -339,7 +344,7 @@ public abstract class AbstractFormatter2 implements IFormatter2 {
*/
protected void reset() {
this.request = null;
this.regionAccess = null;
this.textRegionExtensions = null;
}
}

View file

@ -22,17 +22,26 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/**
* <p>A request tells the formatter what and how to format.</p>
* <p>
* A request tells the formatter what and how to format.
* </p>
*
* <p>When invoking the formatter, the request is passed into {@link IFormatter2#format(FormatterRequest)}.</p>
* <p>
* When invoking the formatter, the request is passed into {@link IFormatter2#format(FormatterRequest)}.
* </p>
*
* <p>A request carries information about:<p>
* <p>
* A request carries information about:
* <p>
* <ul>
* <li>The {@link #textRegionAccess} which allows to obtain the to-be-formatted semantic model with text regions.</li>
* <li>{@link #preferences Preferences} with keys from e.g. {@link FormatterPreferenceKeys}.</li>
* <li>{@link #regions} that describe how to restrict the text regions for which {@link ITextReplacement replacements} are produced.</li>
* <li>An option to {@link #allowIdentityEdits()} which will disable automated suppression of text replacements that do not cause changes.</li>
* <li>A setting for green-field formatting ({@link #formatUndefinedHiddenRegionsOnly}): only format regions that have no whitespace information yet.</li>
* <li>{@link #regions} that describe how to restrict the text regions for which {@link ITextReplacement replacements}
* are produced.</li>
* <li>An option to {@link #allowIdentityEdits()} which will disable automated suppression of text replacements that do
* not cause changes.</li>
* <li>A setting for green-field formatting ({@link #formatUndefinedHiddenRegionsOnly}): only format regions that have
* no whitespace information yet.</li>
* </ul>
*
* @author Moritz Eysholdt - Initial contribution and API
@ -41,8 +50,8 @@ import com.google.common.collect.Maps;
public class FormatterRequest {
/**
* Restrict the formatter to produce {@link ITextReplacement replacements} inside the specified regions only. If no regions are
* specified, the whole document is formatted.
* Restrict the formatter to produce {@link ITextReplacement replacements} inside the specified regions only. If no
* regions are specified, the whole document is formatted.
*/
private Collection<ITextRegion> regions = Lists.newArrayList();
@ -82,9 +91,8 @@ public class FormatterRequest {
}
/**
* Sets the {@link #textRegionAccess}. If the region has syntax errors and no explicit
* {@link ExceptionAcceptor} is configured yet, the {@link ExceptionAcceptor#IGNORING ignoring acceptor}
* will be configured.
* Sets the {@link #textRegionAccess}. If the region has syntax errors and no explicit {@link ExceptionAcceptor} is
* configured yet, the {@link ExceptionAcceptor#IGNORING ignoring acceptor} will be configured.
*/
public FormatterRequest setTextRegionAccess(ITextRegionAccess tokens) {
if (tokens.hasSyntaxError() && this.exceptionHandler == null)
@ -94,9 +102,9 @@ public class FormatterRequest {
}
/**
* Allow the formatter to produce {@link ITextReplacement replacements} that replace regions with text equal to the text of the
* region. Since these replacements do not cause text changes, one usually doens't want to have them in a
* production environment. However, they are useful to test if a formatter considers all significant regions, e.g.
* Allow the formatter to produce {@link ITextReplacement replacements} that replace regions with text equal to the
* text of the region. Since these replacements do not cause text changes, one usually doens't want to have them in
* a production environment. However, they are useful to test if a formatter considers all significant regions, e.g.
* all {@link IHiddenRegion hidden regions}.
*/
private boolean allowIdentityEdits;
@ -139,14 +147,14 @@ public class FormatterRequest {
}
/**
* {@link IHiddenRegion Hidden regions} are considered undefined when their whitespace/comments are unknown. This happens for
* regions that emerged between programmatically created (not parsed!) model elements.
* {@link IHiddenRegion Hidden regions} are considered undefined when their whitespace/comments are unknown. This
* happens for regions that emerged between programmatically created (not parsed!) model elements.
*
* Enable this options if, for example, you serialize a model after applying a quick fix, refactoring or have it
* edited in a graphical editor and you want to keep the whitespace-changes to a minimum.
*/
private boolean formatUndefinedHiddenRegionsOnly;
/**
* @see #formatUndefinedHiddenRegionsOnly
*/
@ -163,16 +171,26 @@ public class FormatterRequest {
}
/**
* <p>Exceptions that occur during formatting are passed to this handler. The handler may choose to throw them, log
* them, or ignore them. Formatting continues, unless the handler throws an exception.</p>
* <p>
* Exceptions that occur during formatting are passed to this handler. The handler may choose to throw them, log
* them, or ignore them. Formatting continues, unless the handler throws an exception.
* </p>
*
* <p>Logging exceptions and continuing formatting is the default behavior.</p>
* <p>
* Logging exceptions and continuing formatting is the default behavior.
* </p>
*
* <p>Throwing exceptions is useful in unit tests.</p>
* <p>
* Throwing exceptions is useful in unit tests.
* </p>
*
* <p>Ignoring exceptions is useful when formatting a document with syntax errors.</p>
* <p>
* Ignoring exceptions is useful when formatting a document with syntax errors.
* </p>
*
* <p>Defaults to the {@link ExceptionAcceptor#LOGGING Logging Acceptor}</p>
* <p>
* Defaults to the {@link ExceptionAcceptor#LOGGING Logging Acceptor}
* </p>
*
* @see ExceptionAcceptor#LOGGING
* @see ExceptionAcceptor#THROWING

View file

@ -12,11 +12,13 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.formatting2.regionaccess.IAstRegion;
import org.eclipse.xtext.formatting2.regionaccess.IComment;
import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion;
@ -120,11 +122,14 @@ public class TextRegionAccessToString {
Multimap<IHiddenRegion, IEObjectRegion> hiddens = LinkedListMultimap.create();
List<String> errors = Lists.newArrayList();
ITextRegionAccess access = list.get(0).getTextRegionAccess();
List<IEObjectRegion> objects = access.regionsForAllEObjects();
for (IEObjectRegion obj : objects) {
TreeIterator<EObject> all = EcoreUtil2.eAll(access.regionForRootEObject().getSemanticElement());
while (all.hasNext()) {
EObject element = all.next();
IEObjectRegion obj = access.regionForEObject(element);
if (obj == null)
continue;
IHiddenRegion previous = obj.getPreviousHiddenRegion();
IHiddenRegion next = obj.getNextHiddenRegion();
EObject element = obj.getSemanticElement();
if (previous == null)
errors.add("ERROR: " + EmfFormatter.objPath(element) + " has no leading HiddenRegion.");
else

View file

@ -66,7 +66,7 @@ public class TextRegionsInTextToString {
ITextRegionAccess access = getTextRegionAccess();
if (access != null) {
ITextSegment impactRegion = TextRegions.merge(this.items);
List<ILineRegion> expandToLines = access.expandToLines(impactRegion, getLeadingLines(), getTrailingLines());
List<ILineRegion> expandToLines = TextRegions.expandToLines(impactRegion, getLeadingLines(), getTrailingLines());
return TextRegions.merge(expandToLines);
}
return null;

View file

@ -7,6 +7,8 @@
*******************************************************************************/
package org.eclipse.xtext.formatting2.debug;
import static org.eclipse.xtext.formatting2.regionaccess.internal.TextRegions.*;
import java.util.List;
import org.eclipse.xtext.formatting2.regionaccess.ILineRegion;
@ -14,7 +16,6 @@ import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess;
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionRewriter;
import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement;
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
import org.eclipse.xtext.formatting2.regionaccess.internal.TextRegions;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
@ -81,9 +82,9 @@ public class TextRegionsWithTitleToString {
List<ITextSegment> segments = Lists.newArrayList();
for (Item item : items)
segments.add(item.getRegion());
ITextSegment impactRegion = TextRegions.merge(segments);
List<ILineRegion> expandToLines = access.expandToLines(impactRegion, getLeadingLines(), getTrailingLines());
return TextRegions.merge(expandToLines);
ITextSegment impactRegion = merge(segments);
List<ILineRegion> expandToLines = expandToLines(impactRegion, getLeadingLines(), getTrailingLines());
return merge(expandToLines);
}
return null;
}

View file

@ -87,8 +87,11 @@ public abstract class FormattableDocument implements IFormattableDocument {
@Override
public <T extends EObject> T append(T owner, Procedure1<? super IHiddenRegionFormatter> after) {
if (owner != null) {
IHiddenRegion gap = getTextRegionAccess().trailingHiddenRegion(owner);
set(gap, after);
IEObjectRegion region = getTextRegionAccess().regionForEObject(owner);
if (region != null) {
IHiddenRegion gap = region.getNextHiddenRegion();
set(gap, after);
}
}
return owner;
}
@ -170,10 +173,8 @@ public abstract class FormattableDocument implements IFormattableDocument {
@Override
public void formatConditionally(EObject owner, ISubFormatter... formatters) {
ITextRegionAccess access = getTextRegionAccess();
int offset = access.leadingHiddenRegion(owner).getEndOffset();
int length = access.trailingHiddenRegion(owner).getOffset() - offset;
formatConditionally(offset, length, formatters);
IEObjectRegion region = getTextRegionAccess().regionForEObject(owner);
formatConditionally(region.getOffset(), region.getLength(), formatters);
}
@Override
@ -220,8 +221,11 @@ public abstract class FormattableDocument implements IFormattableDocument {
@Override
public <T extends EObject> T prepend(T owner, Procedure1<? super IHiddenRegionFormatter> before) {
if (owner != null) {
IHiddenRegion gap = getTextRegionAccess().leadingHiddenRegion(owner);
set(gap, before);
IEObjectRegion region = getTextRegionAccess().regionForEObject(owner);
if (region != null) {
IHiddenRegion gap = region.getPreviousHiddenRegion();
set(gap, before);
}
}
return owner;
}

View file

@ -7,12 +7,15 @@
*******************************************************************************/
package org.eclipse.xtext.formatting2.regionaccess;
import java.util.List;
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
public interface IEObjectRegion extends ISequentialRegion, IAstRegion {
ISemanticRegionsFinder getAllRegionsFor();
List<ISemanticRegion> getSemanticLeafRegions();
Iterable<ISemanticRegion> getAllSemanticRegions();
ISemanticRegionsFinder getRegionFor();
Iterable<ISemanticRegion> getSemanticRegions();
}

View file

@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* 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.eclipse.xtext.formatting2.regionaccess;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.RuleCall;
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
public interface ISemanticRegionFinder {
ISemanticRegion assignment(Assignment assignment);
ISemanticRegion crossRef(CrossReference crossReference);
ISemanticRegion element(AbstractElement element);
ISemanticRegion feature(EStructuralFeature feature);
ISemanticRegion keyword(Keyword keyword);
ISemanticRegion keyword(String keyword);
ISemanticRegion ruleCall(RuleCall ruleCall);
ISemanticRegion ruleCallTo(AbstractRule rule);
}

View file

@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* 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.eclipse.xtext.formatting2.regionaccess;
import java.util.List;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.xbase.lib.Pair;
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
public interface ISemanticRegionsFinder extends ISemanticRegionFinder {
List<ISemanticRegion> assignments(Assignment... assignments);
List<ISemanticRegion> crossRefs(CrossReference... crossReferences);
List<ISemanticRegion> elements(AbstractElement... elements);
List<ISemanticRegion> features(EStructuralFeature... features);
List<Pair<ISemanticRegion, ISemanticRegion>> keywordPairs(Keyword kw1, Keyword kw2);
List<Pair<ISemanticRegion, ISemanticRegion>> keywordPairs(String kw1, String kw2);
List<ISemanticRegion> keywords(Keyword... keywords);
List<ISemanticRegion> keywords(String... keywords);
List<ISemanticRegion> ruleCalls(RuleCall... ruleCalls);
List<ISemanticRegion> ruleCallsTo(AbstractRule... rules);
}

View file

@ -8,15 +8,23 @@
package org.eclipse.xtext.formatting2.regionaccess;
/**
* <p>Common interface for {@link IHiddenRegion} and {@link ISemanticRegion}.</p>
* <p>
* Common interface for {@link IHiddenRegion} and {@link ISemanticRegion}.
* </p>
*
* <p>{@link IHiddenRegion} and {@link ISemanticRegion} are arranged strictly alternating in a linked list. This interface
* provides the method to navigate that list.</p>
* <p>
* {@link IHiddenRegion} and {@link ISemanticRegion} are arranged strictly alternating in a linked list. This interface
* provides the method to navigate that list.
* </p>
*
* @author Moritz Eysholdt - Initial contribution and API
*/
public interface ISequentialRegion extends ITextSegment {
ISemanticRegionFinder immediatelyFollowing();
ISemanticRegionFinder immediatelyPreceding();
IHiddenRegion getNextHiddenRegion();
ISemanticRegion getNextSemanticRegion();

View file

@ -7,16 +7,7 @@
*******************************************************************************/
package org.eclipse.xtext.formatting2.regionaccess;
import java.util.List;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Action;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.resource.XtextResource;
@ -69,13 +60,7 @@ import org.eclipse.xtext.resource.XtextResource;
*/
public interface ITextRegionAccess {
List<ILineRegion> expandToLines(ITextSegment segment, int leadingLinesToAdd, int trailingLinesToAdd);
/**
* @return the {@link RuleCall} or the assigned {@link Action} that led to the construction of this EObject. For the
* model's root element, the {@link ParserRule} is returned.
*/
EObject getInvokingGrammarElement(EObject obj);
ITextRegionExtensions getExtensions();
/**
* @return The {@link XtextResource} that backs the document this class provides access to.
@ -84,133 +69,18 @@ public interface ITextRegionAccess {
ITextRegionRewriter getRewriter();
/**
* @return true, if one or more parse error occurred when parsing the document.
*/
boolean hasSyntaxError();
ILineRegion regionForLineAtOffset(int offset);
/**
* @return true, if an parse error occurred during parsing of the provided EObject or any of its children.
*/
boolean hasSyntaxError(EObject object);
/**
* @return the semantic region representing the 'keyword' that immediately follows the EObject or null.
*/
ISemanticRegion immediatelyFollowingKeyword(EObject owner, String keyword);
/**
* @return the semantic region representing the 'keyword' that immediately follows the 'region' or null.
*/
ISemanticRegion immediatelyFollowingKeyword(ISequentialRegion region, String keyword);
/**
* @return the semantic region for a keyword located right before the provided EObject. May be null if there is no
* preceding semantic region or the preceding semantic region does not represent a keyword.
*/
ISemanticRegion immediatelyPrecedingKeyword(EObject owner);
/**
* @return the semantic region for a keyword located right before the provided EObject. May be null if there is no
* preceding semantic region or the preceding semantic region does not represent the provided keyword.
*/
ISemanticRegion immediatelyPrecedingKeyword(EObject owner, String keyword);
/**
* @return the semantic region for a keyword located right before the provided region. May be null if there is no
* preceding semantic region or the preceding semantic region does not represent a keyword.
*/
ISemanticRegion immediatelyPrecedingKeyword(ISequentialRegion region);
/**
* @return the semantic region for a keyword located right before the provided region. May be null if there is no
* preceding semantic region or the preceding semantic region does not represent the provided keyword.
*/
ISemanticRegion immediatelyPrecedingKeyword(ISequentialRegion region, String keyword);
/**
* @return true, if the EObject's text range contains a line-wrap ("\n"). The EObject's text range reaches from the
* beginning of its first semantic region to the end of its last semantic region.
*/
boolean isMultiline(EObject object);
/**
* @return the {@link IHiddenRegion} that precedes the EObject's first {@link ISemanticRegion}.
*
* @see #trailingHiddenRegion(EObject)
*/
IHiddenRegion leadingHiddenRegion(EObject owner);
ILineRegion lineForOffset(int offset);
ITextSegment regionForDocument();
/**
* @return a text region that reaches from the beginning of its first semantic region to the end of its last
* semantic region.
*/
IEObjectRegion regionForEObject(EObject object);
/**
* @return returns the first {@link ISemanticRegion} that represents the value of {@code owner.eGet(feature)}. May
* be null.
*/
ISemanticRegion regionForFeature(EObject owner, EStructuralFeature feature);
/**
* no containment; returned order same as syntactic oder
*/
List<ISemanticRegion> regionsForFeatures(EObject owner, EStructuralFeature... features);
/**
* @return the first {@link ISemanticRegion} that represent 'keyword' and directly belongs to the provided
* 'EObject'. Keywords of child-EObjects are not considered. May be null.
*/
ISemanticRegion regionForKeyword(EObject owner, String keyword);
ISemanticRegion regionForKeyword(EObject owner, Keyword keyword);
ITextSegment regionForDocument();
ITextSegment regionForOffset(int offset, int length);
IEObjectRegion regionForRootEObject();
/**
* @return the first {@link ISemanticRegion} that represent a RuleCall to the provided AbstractRule and directly
* belongs to the provided 'EObject'. RuleCalls of child-EObjects are not considered. May be null.
*/
ISemanticRegion regionForRuleCallTo(EObject owner, AbstractRule rule);
ISemanticRegion regionForRuleCall(EObject owner, RuleCall ruleCall);
ISemanticRegion regionForCrossRef(EObject owner, CrossReference crossRef);
List<ISemanticRegion> regionsForRuleCalls(EObject owner, RuleCall... ruleCalls);
List<IEObjectRegion> regionsForAllEObjects();
/**
* @return All {@link ISemanticRegion semantic regions} that represent one of the provided 'keyword's and directly
* belong to the provided 'EObject'. Keywords of child-EObjects are not considered.
*/
List<ISemanticRegion> regionsForKeywords(EObject owner, String... keywords);
List<ISemanticRegion> regionsForKeywords(EObject owner, Keyword... keywords);
List<ISemanticRegion> regionsForCrossRefs(EObject owner, CrossReference... crossRefs);
/**
* @return All {@link ISemanticRegion semantic regions} that represent a RuleCall to one of the provided
* AbstractRules and directly belong to the provided 'EObject'. RuleCalls of child-EObjects are not
* considered. May be null.
*/
List<ISemanticRegion> regionsForRuleCallsTo(EObject owner, AbstractRule... rule);
String textForOffset(int offset, int length);
/**
* @return the {@link IHiddenRegion} that follows after the EObject's last {@link ISemanticRegion}.
*
* @see #leadingHiddenRegion(EObject)
*/
IHiddenRegion trailingHiddenRegion(EObject owner); // rename to nextHiddenRegion; do same with leading()
boolean hasSyntaxError();
}

View file

@ -0,0 +1,67 @@
/*******************************************************************************
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* 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.eclipse.xtext.formatting2.regionaccess;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.Action;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
public interface ITextRegionExtensions {
ISemanticRegionsFinder allRegionsFor(EObject object);
Iterable<ISemanticRegion> allSemanticRegions(EObject object);
ITextRegionAccess getTextRegionAccess();
/**
* @return the {@link RuleCall} or the assigned {@link Action} that led to the construction of this EObject. For the
* model's root element, the {@link ParserRule} is returned.
*/
EObject grammarElement(EObject obj);
ISemanticRegionFinder immediatelyFollowing(EObject owner);
ISemanticRegionFinder immediatelyPreceding(EObject owner);
/**
* @return true, if the EObject's text range contains a line-wrap ("\n"). The EObject's text range reaches from the
* beginning of its first semantic region to the end of its last semantic region.
*/
boolean isMultiline(EObject object);
/**
* @return the {@link IHiddenRegion} that follows after the EObject's last {@link ISemanticRegion}.
*
* @see #previousHiddenRegion(EObject)
*/
IHiddenRegion nextHiddenRegion(EObject owner);
/**
* @return the {@link IHiddenRegion} that precedes the EObject's first {@link ISemanticRegion}.
*
* @see #nextHiddenRegion(EObject)
*/
IHiddenRegion previousHiddenRegion(EObject owner);
ISemanticRegionsFinder regionFor(EObject object);
/**
* @return a text region that reaches from the beginning of its first semantic region to the end of its last
* semantic region.
*/
IEObjectRegion regionForEObject(EObject object);
Iterable<ISemanticRegion> semanticRegions(EObject object);
}

View file

@ -28,11 +28,7 @@ public class TextRegionAccessBuilder {
}
public ISequenceAcceptor forSequence(EObject ctx, EObject root) {
return this.fromSequencer = createTextRegionAccessBuildingSequencer().withRoot(ctx, root);
}
private TextRegionAccessBuildingSequencer createTextRegionAccessBuildingSequencer() {
return new TextRegionAccessBuildingSequencer();
return this.fromSequencer = new TextRegionAccessBuildingSequencer().withRoot(ctx, root);
}
public ITextRegionAccess create() {

View file

@ -13,6 +13,8 @@ import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion;
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionFinder;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder;
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess;
import com.google.common.collect.Lists;
@ -78,7 +80,7 @@ public abstract class AbstractEObjectRegion extends AbstractTextSegment implemen
}
@Override
public List<ISemanticRegion> getSemanticLeafRegions() {
public List<ISemanticRegion> getSemanticRegions() {
return semanticRegions;
}
@ -91,6 +93,33 @@ public abstract class AbstractEObjectRegion extends AbstractTextSegment implemen
return nextHidden;
}
@Override
public ISemanticRegionFinder immediatelyFollowing() {
return new SemanticRegionMatcher(getNextSemanticRegion());
}
@Override
public ISemanticRegionFinder immediatelyPreceding() {
return new SemanticRegionMatcher(getPreviousSemanticRegion());
}
@Override
public ISemanticRegionsFinder getRegionFor() {
return new SemanticRegionInIterableFinder(semanticRegions);
}
@Override
public Iterable<ISemanticRegion> getAllSemanticRegions() {
ISemanticRegion first = previousHidden.getNextSemanticRegion();
ISemanticRegion last = nextHidden.getPreviousSemanticRegion();
return new SemanticRegionIterable(first, last);
}
@Override
public ISemanticRegionsFinder getAllRegionsFor() {
return new SemanticRegionInIterableFinder(getAllSemanticRegions());
}
protected void setGrammarElement(EObject grammarElement) {
this.grammarElement = grammarElement;
}
@ -106,4 +135,5 @@ public abstract class AbstractEObjectRegion extends AbstractTextSegment implemen
protected void setTrailingHiddenRegion(IHiddenRegion trailing) {
this.nextHidden = trailing;
}
}

View file

@ -15,6 +15,7 @@ import org.eclipse.xtext.formatting2.regionaccess.IComment;
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegionPart;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionFinder;
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess;
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
import org.eclipse.xtext.formatting2.regionaccess.IWhitespace;
@ -151,4 +152,14 @@ public abstract class AbstractHiddenRegion extends AbstractTextSegment implement
public String toString() {
return new TextRegionAccessToString().withOrigin(this).hightlightOrigin().toString();
}
@Override
public ISemanticRegionFinder immediatelyFollowing() {
return new SemanticRegionMatcher(getNextSemanticRegion());
}
@Override
public ISemanticRegionFinder immediatelyPreceding() {
return new SemanticRegionMatcher(getPreviousSemanticRegion());
}
}

View file

@ -7,117 +7,88 @@
*******************************************************************************/
package org.eclipse.xtext.formatting2.regionaccess.internal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.formatting2.debug.TextRegionAccessToString;
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
import org.eclipse.xtext.formatting2.regionaccess.ILineRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISequentialRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionFinder;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder;
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess;
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionExtensions;
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
public abstract class AbstractRegionAccess implements ITextRegionAccess {
public abstract class AbstractRegionAccess implements ITextRegionAccess, ITextRegionExtensions {
@Override
public EObject getInvokingGrammarElement(EObject obj) {
public ITextRegionExtensions getExtensions() {
return this;
}
@Override
public Iterable<ISemanticRegion> allSemanticRegions(EObject object) {
AbstractEObjectRegion region = regionForEObject(object);
if (region == null)
return Collections.emptyList();
return region.getAllSemanticRegions();
}
@Override
public Iterable<ISemanticRegion> semanticRegions(EObject object) {
AbstractEObjectRegion region = regionForEObject(object);
if (region == null)
return Collections.emptyList();
return region.getSemanticRegions();
}
@Override
public ISemanticRegionsFinder allRegionsFor(EObject object) {
AbstractEObjectRegion region = regionForEObject(object);
if (region == null)
return SemanticRegionNullFinder.INSTANCE;
return region.getAllRegionsFor();
}
@Override
public EObject grammarElement(EObject obj) {
AbstractEObjectRegion tokens = regionForEObject(obj);
if (tokens == null)
return null;
return tokens.getGrammarElement();
}
protected abstract String getText();
@Override
public TextRegionRewriter getRewriter() {
return new TextRegionRewriter(this);
}
protected abstract String getText();
@Override
public ISemanticRegion immediatelyFollowingKeyword(EObject owner, String keyword) {
IHiddenRegion trailingHiddenRegion = trailingHiddenRegion(owner);
if (trailingHiddenRegion == null)
return null;
ISemanticRegion lastRegion = trailingHiddenRegion.getPreviousSemanticRegion();
ISemanticRegion result = immediatelyFollowingKeyword(lastRegion, keyword);
return result;
public ITextRegionAccess getTextRegionAccess() {
return this;
}
@Override
public ISemanticRegion immediatelyFollowingKeyword(ISequentialRegion region, String keyword) {
if (region == null)
return null;
ISemanticRegion nextregion = region.getNextSemanticRegion();
if (nextregion == null)
return null;
EObject grammarElement = nextregion.getGrammarElement();
if (grammarElement instanceof Keyword && ((Keyword) grammarElement).getValue().equals(keyword))
return nextregion;
return null;
public ISemanticRegionFinder immediatelyFollowing(EObject owner) {
AbstractEObjectRegion region = regionForEObject(owner);
if (region != null)
return region.immediatelyFollowing();
return SemanticRegionNullFinder.INSTANCE;
}
@Override
public ISemanticRegion immediatelyPrecedingKeyword(EObject owner) {
ISemanticRegion firstRegion = leadingHiddenRegion(owner).getNextSemanticRegion();
ISemanticRegion result = immediatelyPrecedingKeyword(firstRegion);
return result;
}
@Override
public ISemanticRegion immediatelyPrecedingKeyword(EObject owner, String keyword) {
ISemanticRegion firstRegion = leadingHiddenRegion(owner).getNextSemanticRegion();
ISemanticRegion result = immediatelyPrecedingKeyword(firstRegion, keyword);
return result;
}
@Override
public ISemanticRegion immediatelyPrecedingKeyword(ISequentialRegion token) {
if (token == null)
return null;
ISemanticRegion previousRegion = token.getPreviousSemanticRegion();
if (previousRegion == null)
return null;
EObject grammarElement = previousRegion.getGrammarElement();
if (grammarElement instanceof Keyword)
return previousRegion;
return null;
}
@Override
public ISemanticRegion immediatelyPrecedingKeyword(ISequentialRegion token, String keyword) {
if (token == null)
return null;
ISemanticRegion previousRegion = token.getPreviousSemanticRegion();
if (previousRegion == null)
return null;
EObject grammarElement = previousRegion.getGrammarElement();
if (grammarElement instanceof Keyword && ((Keyword) grammarElement).getValue().equals(keyword))
return previousRegion;
return null;
public ISemanticRegionFinder immediatelyPreceding(EObject owner) {
AbstractEObjectRegion region = regionForEObject(owner);
if (region != null)
return region.immediatelyPreceding();
return SemanticRegionNullFinder.INSTANCE;
}
protected Map<? extends EObject, ? extends AbstractEObjectRegion> initMap() {
@ -129,23 +100,11 @@ public abstract class AbstractRegionAccess implements ITextRegionAccess {
AbstractEObjectRegion tokens = regionForEObject(object);
if (tokens == null)
return false;
ISemanticRegion current = tokens.getLeadingHiddenRegion().getNextSemanticRegion();
ISemanticRegion last = tokens.getTrailingHiddenRegion().getPreviousSemanticRegion();
while (current != null) {
if (current.isMultiline())
return true;
if (current == last)
return false;
IHiddenRegion next = current.getNextHiddenRegion();
if (next.isMultiline())
return true;
current = current.getNextSemanticRegion();
}
return false;
return tokens.isMultiline();
}
@Override
public IHiddenRegion leadingHiddenRegion(EObject owner) {
public IHiddenRegion previousHiddenRegion(EObject owner) {
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return null;
@ -153,202 +112,19 @@ public abstract class AbstractRegionAccess implements ITextRegionAccess {
}
@Override
public abstract AbstractEObjectRegion regionForEObject(EObject obj);
@Override
public ISemanticRegion regionForFeature(EObject owner, EStructuralFeature feat) {
assertNoContainment(feat);
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return null;
for (ISemanticRegion region : tokens.getSemanticLeafRegions()) {
Assignment assignment = GrammarUtil.containingAssignment(region.getGrammarElement());
if (assignment != null && assignment.getFeature().equals(feat.getName()))
return region;
}
return null;
public ISemanticRegionsFinder regionFor(EObject object) {
AbstractEObjectRegion region = regionForEObject(object);
if (region != null)
return region.getRegionFor();
else
return SemanticRegionNullFinder.INSTANCE;
}
@Override
public ISemanticRegion regionForCrossRef(EObject owner, CrossReference crossRef) {
return regionForRuleCall(owner, (RuleCall) crossRef.getTerminal());
}
public abstract AbstractEObjectRegion regionForEObject(EObject object);
@Override
public List<ISemanticRegion> regionsForCrossRefs(EObject owner, CrossReference... crossRefs) {
List<RuleCall> rcs = Lists.newArrayList();
for (int i = 0; i < crossRefs.length; i++)
rcs.add((RuleCall) crossRefs[i].getTerminal());
return regionsForRuleCalls(owner, rcs.toArray(new RuleCall[rcs.size()]));
}
protected void assertNoContainment(EStructuralFeature feat) {
if (!(feat instanceof EAttribute) && !(feat instanceof EReference && !((EReference) feat).isContainment()))
throw new IllegalStateException("Only EAttributes and CrossReferences allowed.");
}
@Override
public List<ISemanticRegion> regionsForFeatures(EObject owner, EStructuralFeature... features) {
Set<String> names = Sets.newHashSet();
for (int i = 0; i < features.length; i++) {
EStructuralFeature feat = features[i];
assertNoContainment(feat);
names.add(feat.getName());
}
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return Collections.emptyList();
List<ISemanticRegion> result = Lists.newArrayList();
for (ISemanticRegion region : tokens.getSemanticLeafRegions()) {
Assignment assignment = GrammarUtil.containingAssignment(region.getGrammarElement());
if (assignment != null && names.contains(assignment.getFeature()))
result.add(region);
}
return ImmutableList.copyOf(result);
}
@Override
public ISemanticRegion regionForKeyword(EObject owner, String keyword) {
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return null;
for (ISemanticRegion region : tokens.getSemanticLeafRegions()) {
EObject element = region.getGrammarElement();
if (element instanceof Keyword) {
Keyword kw = (Keyword) element;
if (kw.getValue().equals(keyword))
return region;
}
}
return null;
}
@Override
public ISemanticRegion regionForKeyword(EObject owner, Keyword keyword) {
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return null;
for (ISemanticRegion region : tokens.getSemanticLeafRegions())
if (region.getGrammarElement() == keyword)
return region;
return null;
}
@Override
public ISemanticRegion regionForRuleCall(EObject owner, RuleCall ruleCall) {
assertNoEObjectRuleCall(ruleCall);
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return null;
for (ISemanticRegion region : tokens.getSemanticLeafRegions())
if (region.getGrammarElement() == ruleCall)
return region;
return null;
}
@Override
public List<ISemanticRegion> regionsForRuleCalls(EObject owner, RuleCall... ruleCalls) {
for (int i = 0; i < ruleCalls.length; i++)
assertNoEObjectRuleCall(ruleCalls[i]);
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return Collections.emptyList();
List<ISemanticRegion> result = Lists.newArrayList();
for (ISemanticRegion region : tokens.getSemanticLeafRegions())
for (int i = 0; i < ruleCalls.length; i++)
if (region.getGrammarElement() == ruleCalls[i])
result.add(region);
return ImmutableList.copyOf(result);
}
protected void assertNoEObjectRuleCall(RuleCall ruleCall) {
if (GrammarUtil.isEObjectRuleCall(ruleCall))
throw new IllegalStateException("Only Enum, Datatype and Terminal Rule Calls allowed.");
}
@Override
public ITextSegment regionForOffset(int offset, int length) {
return new TextSegment(this, offset, length);
}
@Override
public ISemanticRegion regionForRuleCallTo(EObject owner, AbstractRule rule) {
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return null;
for (ISemanticRegion token : tokens.getSemanticLeafRegions()) {
EObject element = token.getGrammarElement();
if (element instanceof RuleCall) {
RuleCall rc = (RuleCall) element;
if (rc.getRule() == rule)
return token;
}
}
return null;
}
@Override
public List<ISemanticRegion> regionsForKeywords(EObject owner, String... keywords) {
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return Collections.emptyList();
Collection<String> kwSet = keywords.length <= 1 ? Arrays.asList(keywords) : Sets.newHashSet(keywords);
List<ISemanticRegion> result = Lists.newArrayList();
for (ISemanticRegion token : tokens.getSemanticLeafRegions())
if (token.getGrammarElement() instanceof Keyword) {
Keyword kw = (Keyword) token.getGrammarElement();
if (kwSet.contains(kw.getValue()))
result.add(token);
}
return ImmutableList.copyOf(result);
}
@Override
public List<ISemanticRegion> regionsForKeywords(EObject owner, Keyword... keywords) {
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return Collections.emptyList();
List<ISemanticRegion> result = Lists.newArrayList();
for (ISemanticRegion region : tokens.getSemanticLeafRegions())
for (int i = 0; i < keywords.length; i++)
if (region.getGrammarElement() == keywords[i])
result.add(region);
return ImmutableList.copyOf(result);
}
@Override
public List<ISemanticRegion> regionsForRuleCallsTo(EObject owner, AbstractRule... rule) {
HashSet<AbstractRule> set = Sets.newHashSet(rule);
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return Collections.emptyList();
List<ISemanticRegion> result = Lists.newArrayList();
for (ISemanticRegion region : tokens.getSemanticLeafRegions()) {
EObject element = region.getGrammarElement();
if (element instanceof RuleCall) {
RuleCall rc = (RuleCall) element;
if (set.contains(rc.getRule()))
result.add(region);
}
}
return result;
}
@Override
public String toString() {
return new TextRegionAccessToString().withRegionAccess(this).toString();
}
@Override
public IHiddenRegion trailingHiddenRegion(EObject owner) {
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return null;
return tokens.getTrailingHiddenRegion();
}
@Override
public ILineRegion lineForOffset(int offset) {
public ILineRegion regionForLineAtOffset(int offset) {
String text = getText();
if (offset < 0 || offset > text.length())
return null;
@ -365,19 +141,21 @@ public abstract class AbstractRegionAccess implements ITextRegionAccess {
}
@Override
public List<ILineRegion> expandToLines(ITextSegment segment, int leadingLinesToAdd, int trailingLinesToAdd) {
List<ILineRegion> lines = Lists.newArrayList(segment.getLineRegions());
for (int i = 1; i < leadingLinesToAdd; i++) {
ILineRegion line = lines.get(0).getPreviousLine();
if (line != null)
lines.add(0, line);
}
for (int i = 1; i < trailingLinesToAdd; i++) {
ILineRegion line = lines.get(lines.size() - 1).getNextLine();
if (line != null)
lines.add(line);
}
return lines;
public ITextSegment regionForOffset(int offset, int length) {
return new TextSegment(this, offset, length);
}
@Override
public String toString() {
return new TextRegionAccessToString().withRegionAccess(this).toString();
}
@Override
public IHiddenRegion nextHiddenRegion(EObject owner) {
AbstractEObjectRegion tokens = regionForEObject(owner);
if (tokens == null)
return null;
return tokens.getTrailingHiddenRegion();
}
}

View file

@ -0,0 +1,410 @@
/*******************************************************************************
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* 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.eclipse.xtext.formatting2.regionaccess.internal;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.CompoundElement;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.XtextPackage;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder;
import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch;
import org.eclipse.xtext.xbase.lib.Pair;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
public abstract class AbstractSemanticRegionsFinder implements ISemanticRegionsFinder {
protected static class FeaturePredicate implements Predicate<ISemanticRegion> {
private final String name;
private final EClass type;
public FeaturePredicate(EStructuralFeature feature) {
this.name = feature.getName();
this.type = feature.getEContainingClass();
}
@Override
public boolean apply(ISemanticRegion input) {
EObject element = input.getGrammarElement();
Assignment assignment = GrammarUtil.containingAssignment(element);
if (assignment == null || !name.equals(assignment.getFeature()))
return false;
EObject semanticElement = input.getSemanticElement();
return type.isInstance(semanticElement);
}
}
protected static class GrammarElementPredicate implements Predicate<ISemanticRegion> {
private final EObject grammarElement;
public GrammarElementPredicate(EObject grammarElement) {
super();
this.grammarElement = grammarElement;
}
@Override
public boolean apply(ISemanticRegion input) {
return input.getGrammarElement() == grammarElement;
}
@Override
public String toString() {
String string = new GrammarElementTitleSwitch().showRule().showQualified().doSwitch(grammarElement);
return "Predicate[" + string + "]";
}
}
protected static class GrammarElementsPredicate implements Predicate<ISemanticRegion> {
private final Set<? extends EObject> grammarElements;
public GrammarElementsPredicate(Set<? extends EObject> grammarElements) {
super();
this.grammarElements = grammarElements;
}
@Override
public boolean apply(ISemanticRegion input) {
return grammarElements.contains(input.getGrammarElement());
}
@Override
public String toString() {
List<String> strings = Lists.newArrayList();
GrammarElementTitleSwitch toString = new GrammarElementTitleSwitch().showRule().showQualified();
for (EObject ele : grammarElements)
strings.add(toString.doSwitch(ele));
return "Predicate[" + Joiner.on(", ").join(strings) + "]";
}
}
protected static class KeywordPredicate implements Predicate<ISemanticRegion> {
private final String keyword;
public KeywordPredicate(String keyword) {
super();
this.keyword = keyword;
}
@Override
public boolean apply(ISemanticRegion input) {
EObject element = input.getGrammarElement();
return element instanceof Keyword && keyword.equals(((Keyword) element).getValue());
}
@Override
public String toString() {
return "Predicate[" + keyword + "]";
}
}
protected static class KeywordsPredicate implements Predicate<ISemanticRegion> {
private final Set<String> keywords;
public KeywordsPredicate(Set<String> keywords) {
super();
this.keywords = keywords;
}
@Override
public boolean apply(ISemanticRegion input) {
EObject element = input.getGrammarElement();
return element instanceof Keyword && keywords.contains(((Keyword) element).getValue());
}
@Override
public String toString() {
return "Predicate[" + Joiner.on(", ").join(keywords) + "]";
}
}
protected static class RulePredicate implements Predicate<ISemanticRegion> {
private final AbstractRule rule;
public RulePredicate(AbstractRule rule) {
super();
this.rule = rule;
}
@Override
public boolean apply(ISemanticRegion input) {
EObject element = input.getGrammarElement();
return element instanceof RuleCall && ((RuleCall) element).getRule() == rule;
}
@Override
public String toString() {
return "Predicate[" + rule.getName() + "]";
}
}
protected static class RulesPredicate implements Predicate<ISemanticRegion> {
private final Set<AbstractRule> rules;
public RulesPredicate(Set<AbstractRule> rules) {
super();
this.rules = rules;
}
@Override
public boolean apply(ISemanticRegion input) {
EObject element = input.getGrammarElement();
return element instanceof RuleCall && rules.contains(((RuleCall) element).getRule());
}
@Override
public String toString() {
List<String> strings = Lists.newArrayList();
for (AbstractRule rule : rules)
strings.add(rule.getName());
return "Predicate[" + Joiner.on(", ").join(strings) + "]";
}
}
protected void assertNoContainment(EStructuralFeature feat) {
if (!(feat instanceof EAttribute) && !(feat instanceof EReference && !((EReference) feat).isContainment()))
throw new IllegalStateException("Only EAttributes and CrossReferences allowed.");
}
protected void assertNoEObjectRule(AbstractRule rule) {
if (GrammarUtil.isEObjectRule(rule))
throw new IllegalStateException("Only Enum, Datatype and Terminal Rule Calls allowed.");
}
protected void assertNoEObjectRuleCall(RuleCall ruleCall) {
assertNoEObjectRule(ruleCall.getRule());
}
@Override
public ISemanticRegion assignment(Assignment assignment) {
return findFirst(createPredicate(assignment));
}
@Override
public List<ISemanticRegion> assignments(Assignment... assignments) {
return findAll(createPredicate(assignments));
}
protected void collectMatchableElements(AbstractElement ele, Collection<AbstractElement> result) {
switch (ele.eClass().getClassifierID()) {
case XtextPackage.RULE_CALL:
assertNoEObjectRuleCall((RuleCall) ele);
case XtextPackage.KEYWORD:
result.add(ele);
break;
case XtextPackage.CROSS_REFERENCE:
collectMatchableElements(((CrossReference) ele).getTerminal(), result);
break;
case XtextPackage.ASSIGNMENT:
collectMatchableElements(((Assignment) ele).getTerminal(), result);
break;
case XtextPackage.ALTERNATIVES:
case XtextPackage.GROUP:
case XtextPackage.UNORDERED_GROUP:
for (AbstractElement child : ((CompoundElement) ele).getElements())
collectMatchableElements(child, result);
break;
}
}
protected Predicate<ISemanticRegion> createPredicate(AbstractElement ele) {
switch (ele.eClass().getClassifierID()) {
case XtextPackage.RULE_CALL:
assertNoEObjectRuleCall((RuleCall) ele);
case XtextPackage.KEYWORD:
return new GrammarElementPredicate(ele);
}
return createPredicate(new AbstractElement[] { ele });
}
protected Predicate<ISemanticRegion> createPredicate(AbstractElement... ele) {
Set<AbstractElement> result = Sets.newHashSet();
for (AbstractElement e : ele)
collectMatchableElements(e, result);
switch (result.size()) {
case 0:
return Predicates.alwaysFalse();
case 1:
return new GrammarElementPredicate(result.iterator().next());
default:
return new GrammarElementsPredicate(result);
}
}
@Override
public ISemanticRegion crossRef(CrossReference crossReference) {
return findFirst(createPredicate(crossReference));
}
@Override
public List<ISemanticRegion> crossRefs(CrossReference... crossReferences) {
return findAll(createPredicate(crossReferences));
}
@Override
public ISemanticRegion element(AbstractElement element) {
return findFirst(createPredicate(element));
}
@Override
public List<ISemanticRegion> elements(AbstractElement... elements) {
return findAll(createPredicate(elements));
}
@Override
public ISemanticRegion feature(EStructuralFeature feature) {
assertNoContainment(feature);
return findFirst(new FeaturePredicate(feature));
}
@Override
public List<ISemanticRegion> features(EStructuralFeature... features) {
Set<Predicate<ISemanticRegion>> predicates = Sets.newHashSet();
for (int i = 0; i < features.length; i++) {
EStructuralFeature feat = features[i];
assertNoContainment(feat);
predicates.add(new FeaturePredicate(feat));
}
return findAll(Predicates.or(predicates));
}
protected abstract ImmutableList<ISemanticRegion> findAll(Predicate<ISemanticRegion> predicate);
protected abstract ISemanticRegion findFirst(Predicate<ISemanticRegion> predicate);
@Override
public ISemanticRegion keyword(Keyword keyword) {
return findFirst(createPredicate(keyword));
}
@Override
public ISemanticRegion keyword(String keyword) {
return findFirst(new KeywordPredicate(keyword));
}
@Override
public List<Pair<ISemanticRegion, ISemanticRegion>> keywordPairs(Keyword kw1, Keyword kw2) {
Preconditions.checkNotNull(kw1);
Preconditions.checkNotNull(kw2);
Preconditions.checkArgument(kw1 != kw2);
Predicate<ISemanticRegion> p1 = createPredicate(kw1);
Predicate<ISemanticRegion> p2 = createPredicate(kw2);
List<ISemanticRegion> all = findAll(Predicates.or(p1, p2));
Builder<Pair<ISemanticRegion, ISemanticRegion>> result = ImmutableList.builder();
LinkedList<ISemanticRegion> stack = new LinkedList<ISemanticRegion>();
for (ISemanticRegion region : all) {
if (p1.apply(region))
stack.push(region);
else if (!stack.isEmpty())
result.add(Pair.of(stack.pop(), region));
}
return result.build();
}
@Override
public List<Pair<ISemanticRegion, ISemanticRegion>> keywordPairs(String kw1, String kw2) {
Preconditions.checkNotNull(kw1);
Preconditions.checkNotNull(kw2);
Preconditions.checkArgument(!kw1.equals(kw2));
Predicate<ISemanticRegion> p1 = new KeywordPredicate(kw1);
Predicate<ISemanticRegion> p2 = new KeywordPredicate(kw2);
List<ISemanticRegion> all = findAll(Predicates.or(p1, p2));
Builder<Pair<ISemanticRegion, ISemanticRegion>> result = ImmutableList.builder();
LinkedList<ISemanticRegion> stack = new LinkedList<ISemanticRegion>();
for (ISemanticRegion region : all) {
if (p1.apply(region))
stack.push(region);
else {
AbstractRule regionRule = GrammarUtil.containingRule(region.getGrammarElement());
while (!stack.isEmpty()) {
ISemanticRegion candidate = stack.pop();
if (region.getSemanticElement() == candidate.getSemanticElement()) {
AbstractRule candidateRule = GrammarUtil.containingRule(candidate.getGrammarElement());
if (regionRule == candidateRule) {
result.add(Pair.of(candidate, region));
break;
}
}
}
}
}
return result.build();
}
@Override
public List<ISemanticRegion> keywords(Keyword... keywords) {
return findAll(createPredicate(keywords));
}
@Override
public List<ISemanticRegion> keywords(String... keywords) {
Predicate<ISemanticRegion> predicate;
if (keywords.length == 1)
predicate = new KeywordPredicate(keywords[0]);
else
predicate = new KeywordsPredicate(Sets.newHashSet(keywords));
return findAll(predicate);
}
@Override
public ISemanticRegion ruleCall(RuleCall ruleCall) {
return findFirst(createPredicate(ruleCall));
}
@Override
public List<ISemanticRegion> ruleCalls(RuleCall... ruleCalls) {
return findAll(createPredicate(ruleCalls));
}
@Override
public List<ISemanticRegion> ruleCallsTo(AbstractRule... rules) {
for (int i = 0; i < rules.length; i++)
assertNoEObjectRule(rules[i]);
Predicate<ISemanticRegion> predicate;
if (rules.length == 1)
predicate = new RulePredicate(rules[0]);
else
predicate = new RulesPredicate(Sets.newHashSet(rules));
return findAll(predicate);
}
@Override
public ISemanticRegion ruleCallTo(AbstractRule rule) {
assertNoEObjectRule(rule);
return findFirst(new RulePredicate(rule));
}
}

View file

@ -67,7 +67,7 @@ public abstract class AbstractTextSegment implements ITextSegment {
@Override
public List<ILineRegion> getLineRegions() {
ILineRegion current = getTextRegionAccess().lineForOffset(getOffset());
ILineRegion current = getTextRegionAccess().regionForLineAtOffset(getOffset());
List<ILineRegion> result = Lists.newArrayList();
int endOffset = getEndOffset();
while (current != null) {

View file

@ -7,18 +7,13 @@
*******************************************************************************/
package org.eclipse.xtext.formatting2.regionaccess.internal;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion;
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
import org.eclipse.xtext.nodemodel.BidiTreeIterator;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.XtextResource;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/**
@ -49,21 +44,6 @@ public class NodeModelBasedRegionAccess extends AbstractRegionAccess {
return getResource().getParseResult().getRootNode().getText().substring(offset, offset + length);
}
@Override
public boolean hasSyntaxError() {
return getResource().getParseResult().hasSyntaxErrors();
}
@Override
public boolean hasSyntaxError(EObject object) {
BidiTreeIterator<INode> it = NodeModelUtils.getNode(object).getAsTreeIterable().iterator();
while (it.hasNext()) {
if (it.next().getSyntaxErrorMessage() != null)
return true;
}
return false;
}
@Override
public AbstractEObjectRegion regionForEObject(EObject obj) {
return eObjectToTokens.get(obj);
@ -80,8 +60,8 @@ public class NodeModelBasedRegionAccess extends AbstractRegionAccess {
}
@Override
public List<IEObjectRegion> regionsForAllEObjects() {
return ImmutableList.<IEObjectRegion> copyOf(eObjectToTokens.values());
public boolean hasSyntaxError() {
return resource.getParseResult().hasSyntaxErrors();
}
}

View file

@ -47,7 +47,7 @@ public class NodeModelBasedRegionAccessBuilder {
newHidden.setPrevious(newSemantic);
newSemantic.setLeadingHiddenRegion(lastHidden);
lastHidden.setNext(newSemantic);
eObjectTokens.getSemanticLeafRegions().add(newSemantic);
eObjectTokens.getSemanticRegions().add(newSemantic);
newSemantic.setEObjectTokens(eObjectTokens);
lastHidden = newHidden;
}

View file

@ -12,6 +12,7 @@ import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionFinder;
import org.eclipse.xtext.nodemodel.INode;
/**
@ -19,9 +20,9 @@ import org.eclipse.xtext.nodemodel.INode;
*/
public class NodeSemanticRegion extends NodeRegion implements ISemanticRegion {
private NodeEObjectRegion eObjectTokens;
private IHiddenRegion leading;
private IHiddenRegion trailing;
private NodeEObjectRegion eObjectTokens;
protected NodeSemanticRegion(NodeModelBasedRegionAccess access, INode node) {
super(access, node);
@ -55,6 +56,25 @@ public class NodeSemanticRegion extends NodeRegion implements ISemanticRegion {
return leading != null ? leading.getPreviousSemanticRegion() : null;
}
@Override
public EObject getSemanticElement() {
return eObjectTokens != null ? eObjectTokens.getSemanticElement() : null;
}
@Override
public ISemanticRegionFinder immediatelyFollowing() {
return new SemanticRegionMatcher(getNextSemanticRegion());
}
@Override
public ISemanticRegionFinder immediatelyPreceding() {
return new SemanticRegionMatcher(getPreviousSemanticRegion());
}
protected void setEObjectTokens(NodeEObjectRegion eObjectTokens) {
this.eObjectTokens = eObjectTokens;
}
protected void setLeadingHiddenRegion(IHiddenRegion leading) {
this.leading = leading;
}
@ -62,13 +82,4 @@ public class NodeSemanticRegion extends NodeRegion implements ISemanticRegion {
protected void setTrailingHiddenRegion(IHiddenRegion trailing) {
this.trailing = trailing;
}
protected void setEObjectTokens(NodeEObjectRegion eObjectTokens) {
this.eObjectTokens = eObjectTokens;
}
@Override
public EObject getSemanticElement() {
return eObjectTokens != null ? eObjectTokens.getSemanticElement() : null;
}
}

View file

@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* 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.eclipse.xtext.formatting2.regionaccess.internal;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
public class SemanticRegionInIterableFinder extends AbstractSemanticRegionsFinder {
private final Iterable<ISemanticRegion> regions;
public SemanticRegionInIterableFinder(Iterable<ISemanticRegion> regions) {
super();
this.regions = regions;
}
@Override
protected ImmutableList<ISemanticRegion> findAll(Predicate<ISemanticRegion> predicate) {
Builder<ISemanticRegion> builder = ImmutableList.builder();
for (ISemanticRegion region : regions)
if (predicate.apply(region))
builder.add(region);
return builder.build();
}
@Override
protected ISemanticRegion findFirst(Predicate<ISemanticRegion> predicate) {
for (ISemanticRegion region : regions)
if (predicate.apply(region))
return region;
return null;
}
}

View file

@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* 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.eclipse.xtext.formatting2.regionaccess.internal;
import java.util.Iterator;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import com.google.common.collect.AbstractIterator;
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
public class SemanticRegionIterable implements Iterable<ISemanticRegion> {
private final ISemanticRegion first;
private final ISemanticRegion last;
public SemanticRegionIterable(ISemanticRegion first, ISemanticRegion last) {
super();
this.first = first;
this.last = last;
}
@Override
public Iterator<ISemanticRegion> iterator() {
return new AbstractIterator<ISemanticRegion>() {
private ISemanticRegion next = first;
@Override
protected ISemanticRegion computeNext() {
if (next == null)
return endOfData();
ISemanticRegion result = next;
next = next.getNextSemanticRegion();
if (result == last)
next = null;
return result;
}
};
}
}

View file

@ -0,0 +1,42 @@
/*******************************************************************************
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* 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.eclipse.xtext.formatting2.regionaccess.internal;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
public class SemanticRegionMatcher extends AbstractSemanticRegionsFinder {
private final ISemanticRegion region;
public SemanticRegionMatcher(ISemanticRegion region) {
super();
this.region = region;
}
@Override
protected ImmutableList<ISemanticRegion> findAll(Predicate<ISemanticRegion> predicate) {
ISemanticRegion element = findFirst(predicate);
if (element == null)
return ImmutableList.of();
else
return ImmutableList.of(element);
}
@Override
protected ISemanticRegion findFirst(Predicate<ISemanticRegion> predicate) {
if (predicate.apply(region))
return region;
return null;
}
}

View file

@ -0,0 +1,120 @@
/*******************************************************************************
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* 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.eclipse.xtext.formatting2.regionaccess.internal;
import java.util.Collections;
import java.util.List;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder;
import org.eclipse.xtext.xbase.lib.Pair;
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
public enum SemanticRegionNullFinder implements ISemanticRegionsFinder {
INSTANCE;
@Override
public ISemanticRegion assignment(Assignment assignment) {
return null;
}
@Override
public List<ISemanticRegion> assignments(Assignment... assignments) {
return Collections.emptyList();
}
@Override
public ISemanticRegion crossRef(CrossReference crossReference) {
return null;
}
@Override
public List<ISemanticRegion> crossRefs(CrossReference... crossReferences) {
return Collections.emptyList();
}
@Override
public ISemanticRegion element(AbstractElement element) {
return null;
}
@Override
public List<ISemanticRegion> elements(AbstractElement... elements) {
return Collections.emptyList();
}
@Override
public ISemanticRegion feature(EStructuralFeature feature) {
return null;
}
@Override
public List<ISemanticRegion> features(EStructuralFeature... features) {
return Collections.emptyList();
}
@Override
public ISemanticRegion keyword(Keyword keyword) {
return null;
}
@Override
public ISemanticRegion keyword(String keyword) {
return null;
}
@Override
public List<Pair<ISemanticRegion, ISemanticRegion>> keywordPairs(Keyword kw1, Keyword kw2) {
return Collections.emptyList();
}
@Override
public List<Pair<ISemanticRegion, ISemanticRegion>> keywordPairs(String kw1, String kw2) {
return Collections.emptyList();
}
@Override
public List<ISemanticRegion> keywords(Keyword... keywords) {
return Collections.emptyList();
}
@Override
public List<ISemanticRegion> keywords(String... keywords) {
return Collections.emptyList();
}
@Override
public ISemanticRegion ruleCall(RuleCall ruleCall) {
return null;
}
@Override
public List<ISemanticRegion> ruleCalls(RuleCall... ruleCalls) {
return Collections.emptyList();
}
@Override
public List<ISemanticRegion> ruleCallsTo(AbstractRule... rules) {
return Collections.emptyList();
}
@Override
public ISemanticRegion ruleCallTo(AbstractRule rule) {
return null;
}
}

View file

@ -7,7 +7,6 @@
*******************************************************************************/
package org.eclipse.xtext.formatting2.regionaccess.internal;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.ecore.EObject;
@ -15,7 +14,6 @@ import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion;
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
import org.eclipse.xtext.resource.XtextResource;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
/**
@ -52,16 +50,6 @@ public class StringBasedRegionAccess extends AbstractRegionAccess {
return string.toString();
}
@Override
public boolean hasSyntaxError() {
return false;
}
@Override
public boolean hasSyntaxError(EObject object) {
return false;
}
@Override
public ITextSegment regionForDocument() {
return new TextSegment(this, 0, string.length());
@ -87,8 +75,8 @@ public class StringBasedRegionAccess extends AbstractRegionAccess {
}
@Override
public List<IEObjectRegion> regionsForAllEObjects() {
return ImmutableList.<IEObjectRegion> copyOf(eObjectToTokens.values());
public boolean hasSyntaxError() {
return false;
}
}

View file

@ -19,5 +19,4 @@ public class StringEObjectRegion extends AbstractEObjectRegion {
this.setGrammarElement(grammarElement);
this.setSemantcElement(semanticElement);
}
}

View file

@ -11,6 +11,7 @@ import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionFinder;
/**
* @author Moritz Eysholdt - Initial contribution and API
@ -59,6 +60,16 @@ public class StringSemanticRegion extends StringRegion implements ISemanticRegio
return semanticElement;
}
@Override
public ISemanticRegionFinder immediatelyFollowing() {
return new SemanticRegionMatcher(getNextSemanticRegion());
}
@Override
public ISemanticRegionFinder immediatelyPreceding() {
return new SemanticRegionMatcher(getPreviousSemanticRegion());
}
protected void setLeadingHiddenRegion(IHiddenRegion leading) {
this.leading = leading;
}

View file

@ -123,7 +123,7 @@ public class TextRegionAccessBuildingSequencer implements ISequenceAcceptor {
last.setPrevious(semantic);
semantic.setTrailingHiddenRegion(last);
if (tokens != null) {
tokens.getSemanticLeafRegions().add(semantic);
tokens.getSemanticRegions().add(semantic);
tokens.setTrailingHiddenRegion(last);
}
}

View file

@ -13,6 +13,7 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.eclipse.xtext.formatting2.regionaccess.ILineRegion;
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
import org.eclipse.xtext.util.ITextRegion;
@ -87,4 +88,19 @@ public class TextRegions {
return new TextSegment(first.getTextRegionAccess(), minOffset, maxEndOffset - minOffset);
}
public static List<ILineRegion> expandToLines(ITextSegment segment, int leadingLinesToAdd, int trailingLinesToAdd) {
List<ILineRegion> lines = Lists.newArrayList(segment.getLineRegions());
for (int i = 1; i < leadingLinesToAdd; i++) {
ILineRegion line = lines.get(0).getPreviousLine();
if (line != null)
lines.add(0, line);
}
for (int i = 1; i < trailingLinesToAdd; i++) {
ILineRegion line = lines.get(lines.size() - 1).getNextLine();
if (line != null)
lines.add(line);
}
return lines;
}
}

View file

@ -33,7 +33,7 @@ class FormattableDocumentTest {
idlist a
'''
formatter = [ IDList model, extension regions, extension document |
model.regionForKeyword("idlist").append[oneSpace]
model.regionFor.keyword("idlist").append[oneSpace]
]
expectation = '''
idlist a
@ -50,7 +50,7 @@ class FormattableDocumentTest {
idlist aaa bbb ccc ddd eee fff
'''
formatter = [ IDList model, extension regions, extension document |
model.regionsForRuleCallsTo(IDRule).forEach[prepend[autowrap; oneSpace]]
model.regionFor.ruleCallsTo(IDRule).forEach[prepend[autowrap; oneSpace]]
]
expectation = '''
idlist aaa
@ -69,11 +69,11 @@ class FormattableDocumentTest {
kwlist kw1 kw2 kw3 kw4
'''
formatter = [ KWList model, extension regions, extension document |
model.regionForKeyword("kwlist").append[autowrap(6); oneSpace]
model.regionForKeyword("kw1").append[autowrap; oneSpace]
model.regionForKeyword("kw2").append[autowrap; oneSpace]
model.regionForKeyword("kw3").append[autowrap; oneSpace]
model.regionForKeyword("kw4").append[autowrap; newLine]
model.regionFor.keyword("kwlist").append[autowrap(6); oneSpace]
model.regionFor.keyword("kw1").append[autowrap; oneSpace]
model.regionFor.keyword("kw2").append[autowrap; oneSpace]
model.regionFor.keyword("kw3").append[autowrap; oneSpace]
model.regionFor.keyword("kw4").append[autowrap; newLine]
]
expectation = '''
kwlist
@ -95,12 +95,12 @@ class FormattableDocumentTest {
model.formatConditionally(
[ doc |
val extension fits = doc.requireFitsInLine
model.regionForKeyword("kwlist").append[oneSpace]
model.regionForKeyword("kw1").append[oneSpace]
model.regionFor.keyword("kwlist").append[oneSpace]
model.regionFor.keyword("kw1").append[oneSpace]
],
[ extension doc |
model.regionForKeyword("kwlist").append[newLine]
model.regionForKeyword("kw1").append[newLine]
model.regionFor.keyword("kwlist").append[newLine]
model.regionFor.keyword("kw1").append[newLine]
])
]
expectation = '''
@ -123,12 +123,12 @@ class FormattableDocumentTest {
model.formatConditionally(
[ doc |
val extension fits = doc.requireFitsInLine
model.regionForKeyword("kwlist").append[oneSpace]
model.regionForKeyword("kw1").append[oneSpace]
model.regionFor.keyword("kwlist").append[oneSpace]
model.regionFor.keyword("kw1").append[oneSpace]
],
[ extension doc |
model.regionForKeyword("kwlist").append[newLine]
model.regionForKeyword("kw1").append[newLine]
model.regionFor.keyword("kwlist").append[newLine]
model.regionFor.keyword("kw1").append[newLine]
])
]
expectation = '''

View file

@ -52,7 +52,7 @@ class FormatterSerializerIntegrationTest {
static class Formatter extends AbstractFormatter2 {
def dispatch format(IDList model, extension IFormattableDocument document) {
model.regionForKeyword("idlist").append[space = " "]
model.regionFor.keyword("idlist").append[space = " "]
}
}

View file

@ -16,6 +16,7 @@ import org.eclipse.xtext.formatting2.IFormattableDocument
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess
import org.eclipse.xtext.junit4.formatter.FormatterTestRequest
import org.eclipse.xtext.junit4.formatter.FormatterTester
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionExtensions
/**
* @author Moritz Eysholdt - Initial contribution and API
@ -53,8 +54,8 @@ class GenericFormatterTestRequest extends FormatterTestRequest {
@Accessors
abstract class GenericFormatter<T extends EObject> extends AbstractFormatter2 {
def dispatch format(EObject obj, IFormattableDocument document) {
format(obj as T, request.textRegionAccess, document)
format(obj as T, request.textRegionAccess.extensions, document)
}
def protected abstract void format(T model, ITextRegionAccess regionAccess, IFormattableDocument document)
def protected abstract void format(T model, ITextRegionExtensions regionAccess, IFormattableDocument document)
}

View file

@ -1,170 +0,0 @@
/*******************************************************************************
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* 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.eclipse.xtext.formatting2.regionaccess.internal
import com.google.inject.Inject
import com.google.inject.Provider
import java.util.Collection
import org.eclipse.emf.ecore.EObject
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment
import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder
import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.AssignedAction
import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.Mixed
import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.Root
import org.eclipse.xtext.formatting2.regionaccess.internal.services.RegionAccessTestLanguageGrammarAccess
import org.eclipse.xtext.junit4.InjectWith
import org.eclipse.xtext.junit4.XtextRunner
import org.eclipse.xtext.junit4.util.ParseHelper
import org.eclipse.xtext.junit4.validation.ValidationTestHelper
import org.eclipse.xtext.resource.XtextResource
import org.junit.Test
import org.junit.runner.RunWith
import static org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.RegionaccesstestlanguagePackage.Literals.*
import static org.junit.Assert.*
import com.google.common.collect.ImmutableList
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
@RunWith(XtextRunner)
@InjectWith(RegionAccessTestLanguageInjectorProvider)
class RegionAccessAccessTest {
@Inject extension ParseHelper<Root> parseHelper
@Inject Provider<TextRegionAccessBuilder> textRegionAccessBuilder
@Inject extension ValidationTestHelper validationTestHelper
@Inject extension RegionAccessTestLanguageGrammarAccess
@Test def void regionForFeatureAttribute() {
val mixed = '''6 (foo)'''.parseAs(Mixed)
val access = mixed.toAccess
val actual = access.regionForFeature(mixed, MIXED__NAME)
val actuals = access.regionsForFeatures(mixed, MIXED__NAME)
assertEquals("foo", actual, actuals)
}
@Test def void regionForFeatureCrossReference() {
val mixed = '''6 (ref foo) action (foo) end'''.parseAs(AssignedAction)
val access = mixed.toAccess
val actual = access.regionForFeature(mixed.child, MIXED__REF)
val actuals = access.regionsForFeatures(mixed.child, MIXED__REF)
assertEquals("foo", actual, actuals)
}
@Test def void regionForFeatureContainmentReference() {
val mixed = '''6 (foo) action'''.parseAs(AssignedAction)
val access = mixed.toAccess
try {
access.regionForFeature(mixed, ASSIGNED_ACTION__CHILD)
fail()
} catch (IllegalStateException e) {
}
try {
access.regionsForFeatures(mixed, ASSIGNED_ACTION__CHILD)
fail()
} catch (IllegalStateException e) {
}
}
@Test def void regionForRuleCallUnassignedTerminal() {
val mixed = '''6 (unassigned foo)'''.parseAs(Mixed)
val access = mixed.toAccess
val actual = access.regionForRuleCall(mixed, mixedAccess.IDTerminalRuleCall_1_1_0)
val actuals = access.regionsForRuleCalls(mixed, mixedAccess.IDTerminalRuleCall_1_1_0)
assertEquals("foo", actual, actuals)
}
@Test def void regionForRuleCallUnassignedDataType() {
val mixed = '''6 (unassigned datatype foo)'''.parseAs(Mixed)
val access = mixed.toAccess
val actual = access.regionForRuleCall(mixed, mixedAccess.datatypeParserRuleCall_1_1_1)
val actuals = access.regionsForRuleCalls(mixed, mixedAccess.datatypeParserRuleCall_1_1_1)
assertEquals("datatype foo", actual, actuals)
}
@Test def void regionForRuleCallAssignedTerminal() {
val mixed = '''6 (foo)'''.parseAs(Mixed)
val access = mixed.toAccess
val actual = access.regionForRuleCall(mixed, mixedAccess.nameIDTerminalRuleCall_2_2_0_0)
val actuals = access.regionForRuleCall(mixed, mixedAccess.nameIDTerminalRuleCall_2_2_0_0)
assertEquals("foo", actual, actuals)
}
@Test def void regionForRuleCallAssignedDataType() {
val mixed = '''6 (datatype foo)'''.parseAs(Mixed)
val access = mixed.toAccess
val actual = access.regionForRuleCall(mixed, mixedAccess.datatypeDatatypeParserRuleCall_2_2_2_0)
val actuals = access.regionForRuleCall(mixed, mixedAccess.datatypeDatatypeParserRuleCall_2_2_2_0)
assertEquals("datatype foo", actual, actuals)
}
@Test def void regionForRuleCallCrossReference() {
val mixed = '''6 (ref foo) action (foo) end'''.parseAs(AssignedAction)
val access = mixed.toAccess
val actual = access.regionForRuleCall(mixed.child, mixedAccess.refMixedIDTerminalRuleCall_2_2_3_1_0_1)
val actuals = access.regionsForRuleCalls(mixed.child, mixedAccess.refMixedIDTerminalRuleCall_2_2_3_1_0_1)
assertEquals("foo", actual, actuals)
}
@Test def void regionForRuleCallEObjectParserRule() {
val mixed = '''6 (child (foo))'''.parseAs(Mixed)
val access = mixed.toAccess
try {
access.regionForRuleCall(mixed, mixedAccess.eobjMixedParserRuleCall_2_2_1_1_0)
fail()
} catch (IllegalStateException e) {
}
try {
access.regionsForRuleCalls(mixed, mixedAccess.eobjMixedParserRuleCall_2_2_1_1_0)
fail()
} catch (IllegalStateException e) {
}
}
@Test def void regionForKeywordString() {
val mixed = '''6 (foo)'''.parseAs(Mixed)
val access = mixed.toAccess
val actual = access.regionForKeyword(mixed, "(")
val actuals = access.regionsForKeywords(mixed, "(")
assertEquals("(", actual, actuals)
}
@Test def void regionForKeyword() {
val mixed = '''6 (foo)'''.parseAs(Mixed)
val access = mixed.toAccess
val actual = access.regionForKeyword(mixed, mixedAccess.leftParenthesisKeyword_0)
val actuals = access.regionsForKeywords(mixed, mixedAccess.leftParenthesisKeyword_0)
assertEquals("(", actual, actuals)
}
@Test def void regionForCrossReference() {
val mixed = '''6 (ref foo) action (foo) end'''.parseAs(AssignedAction)
val access = mixed.toAccess
val actual = access.regionForCrossRef(mixed.child, mixedAccess.refMixedCrossReference_2_2_3_1_0)
val actuals = access.regionsForCrossRefs(mixed.child, mixedAccess.refMixedCrossReference_2_2_3_1_0)
assertEquals("foo", actual, actuals)
}
def private <T extends EObject> parseAs(CharSequence seq, Class<T> cls) {
val result = seq.parse
result.assertNoErrors
return cls.cast(result)
}
def private ITextRegionAccess toAccess(EObject obj) {
return textRegionAccessBuilder.get.forNodeModel(obj.eResource as XtextResource).create
}
def private void assertEquals(String expected, ITextSegment single, Collection<? extends ITextSegment> regions) {
assertEquals(expected, single.text)
assertEquals(1, regions.size)
assertEquals(expected, regions.head.text)
assertTrue(regions instanceof ImmutableList<?>)
}
}

View file

@ -0,0 +1,261 @@
/*******************************************************************************
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* 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.eclipse.xtext.formatting2.regionaccess.internal
import com.google.inject.Inject
import com.google.inject.Provider
import java.util.Collection
import org.eclipse.emf.ecore.EObject
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment
import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder
import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.AssignedAction
import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.Mixed
import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.Root
import org.eclipse.xtext.formatting2.regionaccess.internal.services.RegionAccessTestLanguageGrammarAccess
import org.eclipse.xtext.junit4.InjectWith
import org.eclipse.xtext.junit4.XtextRunner
import org.eclipse.xtext.junit4.util.ParseHelper
import org.eclipse.xtext.junit4.validation.ValidationTestHelper
import org.eclipse.xtext.resource.XtextResource
import org.junit.Test
import org.junit.runner.RunWith
import static org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.RegionaccesstestlanguagePackage.Literals.*
import static org.junit.Assert.*
import com.google.common.collect.ImmutableList
import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.Expression
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
@RunWith(XtextRunner)
@InjectWith(RegionAccessTestLanguageInjectorProvider)
class SemanticRegionFinderTest {
@Inject extension ParseHelper<Root> parseHelper
@Inject Provider<TextRegionAccessBuilder> textRegionAccessBuilder
@Inject extension ValidationTestHelper validationTestHelper
@Inject extension RegionAccessTestLanguageGrammarAccess
@Test def void regionForFeatureAttribute() {
val mixed = '''6 (foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.feature(MIXED__NAME)
val actuals = finder.features(MIXED__NAME)
assertEquals("foo", actual, actuals)
}
@Test def void regionForFeatureCrossReference() {
val mixed = '''6 (ref foo) action (foo) end'''.parseAs(AssignedAction)
val finder = mixed.toAccess.regionForEObject(mixed.child)
val actual = finder.regionFor.feature(MIXED__REF)
val actuals = finder.regionFor.features(MIXED__REF)
assertEquals("foo", actual, actuals)
}
@Test def void regionForFeatureContainmentReference() {
val mixed = '''6 (foo) action'''.parseAs(AssignedAction)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
try {
finder.feature(ASSIGNED_ACTION__CHILD)
fail()
} catch (IllegalStateException e) {
}
try {
finder.features(ASSIGNED_ACTION__CHILD)
fail()
} catch (IllegalStateException e) {
}
}
@Test def void regionForRuleCallUnassignedTerminal() {
val mixed = '''6 (unassigned foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.ruleCall(mixedAccess.IDTerminalRuleCall_1_1_0)
val actuals = finder.ruleCalls(mixedAccess.IDTerminalRuleCall_1_1_0)
assertEquals("foo", actual, actuals)
}
@Test def void regionForRuleCallToUnassignedTerminal() {
val mixed = '''6 (unassigned foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.ruleCallTo(IDRule)
val actuals = finder.ruleCallsTo(IDRule)
assertEquals("foo", actual, actuals)
}
@Test def void regionForRuleCallUnassignedDataType() {
val mixed = '''6 (unassigned datatype foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.ruleCall(mixedAccess.datatypeParserRuleCall_1_1_1)
val actuals = finder.ruleCalls(mixedAccess.datatypeParserRuleCall_1_1_1)
assertEquals("datatype foo", actual, actuals)
}
@Test def void regionForRuleCallToUnassignedDataType() {
val mixed = '''6 (unassigned datatype foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.ruleCallTo(datatypeRule)
val actuals = finder.ruleCallsTo(datatypeRule)
assertEquals("datatype foo", actual, actuals)
}
@Test def void regionForRuleCallAssignedTerminal() {
val mixed = '''6 (foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.ruleCall(mixedAccess.nameIDTerminalRuleCall_2_2_0_0)
val actuals = finder.ruleCall(mixedAccess.nameIDTerminalRuleCall_2_2_0_0)
assertEquals("foo", actual, actuals)
}
@Test def void regionForRuleCallToAssignedTerminal() {
val mixed = '''6 (foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.ruleCallTo(IDRule)
val actuals = finder.ruleCallTo(IDRule)
assertEquals("foo", actual, actuals)
}
@Test def void regionForRuleCallAssignedDataType() {
val mixed = '''6 (datatype foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.ruleCall(mixedAccess.datatypeDatatypeParserRuleCall_2_2_2_0)
val actuals = finder.ruleCall(mixedAccess.datatypeDatatypeParserRuleCall_2_2_2_0)
assertEquals("datatype foo", actual, actuals)
}
@Test def void regionForRuleCallToAssignedDataType() {
val mixed = '''6 (datatype foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.ruleCallTo(datatypeRule)
val actuals = finder.ruleCallTo(datatypeRule)
assertEquals("datatype foo", actual, actuals)
}
@Test def void regionForRuleCallCrossReference() {
val mixed = '''6 (ref foo) action (foo) end'''.parseAs(AssignedAction)
val finder = mixed.toAccess.regionForEObject(mixed.child).regionFor
val actual = finder.ruleCall(mixedAccess.refMixedIDTerminalRuleCall_2_2_3_1_0_1)
val actuals = finder.ruleCalls(mixedAccess.refMixedIDTerminalRuleCall_2_2_3_1_0_1)
assertEquals("foo", actual, actuals)
}
@Test def void regionForRuleCallToCrossReference() {
val mixed = '''6 (ref foo) action (foo) end'''.parseAs(AssignedAction)
val finder = mixed.toAccess.regionForEObject(mixed.child).regionFor
val actual = finder.ruleCallTo(IDRule)
val actuals = finder.ruleCallsTo(IDRule)
assertEquals("foo", actual, actuals)
}
@Test def void regionForRuleCallEObjectParserRule() {
val mixed = '''6 (child (foo))'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
try {
finder.ruleCall(mixedAccess.eobjMixedParserRuleCall_2_2_1_1_0)
fail()
} catch (IllegalStateException e) {
}
try {
finder.ruleCalls(mixedAccess.eobjMixedParserRuleCall_2_2_1_1_0)
fail()
} catch (IllegalStateException e) {
}
}
@Test def void regionForRuleCallToEObjectParserRule() {
val mixed = '''6 (child (foo))'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
try {
finder.ruleCallTo(mixedRule)
fail()
} catch (IllegalStateException e) {
}
try {
finder.ruleCallsTo(mixedRule)
fail()
} catch (IllegalStateException e) {
}
}
@Test def void regionForKeywordString() {
val mixed = '''6 (foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.keyword("(")
val actuals = finder.keywords("(")
assertEquals("(", actual, actuals)
}
@Test def void regionForKeyword() {
val mixed = '''6 (foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.keyword(mixedAccess.leftParenthesisKeyword_0)
val actuals = finder.keywords(mixedAccess.leftParenthesisKeyword_0)
assertEquals("(", actual, actuals)
}
@Test def void regionForCrossReference() {
val mixed = '''6 (ref foo) action (foo) end'''.parseAs(AssignedAction)
val finder = mixed.toAccess.regionForEObject(mixed.child).regionFor
val actual = finder.crossRef(mixedAccess.refMixedCrossReference_2_2_3_1_0)
val actuals = finder.crossRefs(mixedAccess.refMixedCrossReference_2_2_3_1_0)
assertEquals("foo", actual, actuals)
}
@Test def void regionForAssignment() {
val mixed = '''6 (foo)'''.parseAs(Mixed)
val finder = mixed.toAccess.regionForEObject(mixed).regionFor
val actual = finder.assignment(mixedAccess.nameAssignment_2_2_0)
val actuals = finder.assignments(mixedAccess.nameAssignment_2_2_0)
assertEquals("foo", actual, actuals)
}
@Test def void regionForKeywordPairs() {
val extension rule = parenthesizedAccess
val expr = '''5 (foo)'''.parseAs(Expression)
val finder = expr.toAccess.regionForEObject(expr).regionFor
val actual1 = finder.keywordPairs("(", ")").pairsToString
val actual2 = finder.keywordPairs(leftParenthesisKeyword_0, rightParenthesisKeyword_2).pairsToString
val expected = "(foo)"
assertEquals(expected, actual1)
assertEquals(expected, actual2)
}
@Test def void regionForKeywordPairs2() {
val extension rule = parenthesizedAccess
val expr = '''5 (a + ((b) + c) + d)'''.parseAs(Expression)
val finder = expr.toAccess.regionForRootEObject.allRegionsFor
val actual1 = finder.keywordPairs("(", ")").pairsToString
val actual2 = finder.keywordPairs(leftParenthesisKeyword_0, rightParenthesisKeyword_2).pairsToString
val expected = "(b); ((b) + c); (a + ((b) + c) + d)"
assertEquals(expected, actual1)
assertEquals(expected, actual2)
}
def private String pairsToString(Iterable<Pair<ISemanticRegion, ISemanticRegion>> pairs) {
pairs.map[key.merge(value).text].join("; ")
}
def private <T extends EObject> parseAs(CharSequence seq, Class<T> cls) {
val result = seq.parse
result.assertNoErrors
return cls.cast(result)
}
def private ITextRegionAccess toAccess(EObject obj) {
return textRegionAccessBuilder.get.forNodeModel(obj.eResource as XtextResource).create
}
def private void assertEquals(String expected, ITextSegment single, Collection<? extends ITextSegment> regions) {
assertEquals(expected, single.text)
assertEquals(1, regions.size)
assertEquals(expected, regions.head.text)
assertTrue(regions instanceof ImmutableList<?>)
}
}