From ad6ba6d6066d2f5efbc615b2dbf99f129997f8fc Mon Sep 17 00:00:00 2001 From: Moritz Eysholdt Date: Thu, 23 Apr 2015 13:45:56 +0200 Subject: [PATCH] [formatter] made mechanism for finding text regions more powerful Signed-off-by: Moritz Eysholdt --- .../xtext/formatting2/AbstractFormatter2.java | 19 +- .../xtext/formatting2/FormatterRequest.java | 64 ++- .../debug/TextRegionAccessToString.java | 11 +- .../debug/TextRegionsInTextToString.java | 2 +- .../debug/TextRegionsWithTitleToString.java | 9 +- .../internal/FormattableDocument.java | 20 +- .../regionaccess/IEObjectRegion.java | 9 +- .../regionaccess/ISemanticRegionFinder.java | 38 ++ .../regionaccess/ISemanticRegionsFinder.java | 46 ++ .../regionaccess/ISequentialRegion.java | 14 +- .../regionaccess/ITextRegionAccess.java | 140 +----- .../regionaccess/ITextRegionExtensions.java | 67 +++ .../regionaccess/TextRegionAccessBuilder.java | 6 +- .../internal/AbstractEObjectRegion.java | 32 +- .../internal/AbstractHiddenRegion.java | 11 + .../internal/AbstractRegionAccess.java | 368 ++++------------ .../AbstractSemanticRegionsFinder.java | 410 ++++++++++++++++++ .../internal/AbstractTextSegment.java | 2 +- .../internal/NodeModelBasedRegionAccess.java | 24 +- .../NodeModelBasedRegionAccessBuilder.java | 2 +- .../internal/NodeSemanticRegion.java | 31 +- .../SemanticRegionInIterableFinder.java | 44 ++ .../internal/SemanticRegionIterable.java | 47 ++ .../internal/SemanticRegionMatcher.java | 42 ++ .../internal/SemanticRegionNullFinder.java | 120 +++++ .../internal/StringBasedRegionAccess.java | 16 +- .../internal/StringEObjectRegion.java | 1 - .../internal/StringSemanticRegion.java | 11 + .../TextRegionAccessBuildingSequencer.java | 2 +- .../regionaccess/internal/TextRegions.java | 16 + .../internal/FormattableDocumentTest.xtend | 30 +- .../FormatterSerializerIntegrationTest.xtend | 2 +- .../internal/GenericFormatterTester.xtend | 5 +- .../internal/RegionAccessAccessTest.xtend | 170 -------- .../internal/SemanticRegionFinderTest.xtend | 261 +++++++++++ 35 files changed, 1366 insertions(+), 726 deletions(-) create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISemanticRegionFinder.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISemanticRegionsFinder.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionExtensions.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractSemanticRegionsFinder.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionInIterableFinder.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionIterable.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionMatcher.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionNullFinder.java delete mode 100644 tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/RegionAccessAccessTest.xtend create mode 100644 tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionFinderTest.xtend diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/AbstractFormatter2.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/AbstractFormatter2.java index a35badc3b..b739f05f7 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/AbstractFormatter2.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/AbstractFormatter2.java @@ -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; *

* *

- * 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}. *

* *

@@ -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 postProcess(IFormattableDocument document, List replacements) { List 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; } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/FormatterRequest.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/FormatterRequest.java index 2c09185ea..80e335457 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/FormatterRequest.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/FormatterRequest.java @@ -22,17 +22,26 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; /** - *

A request tells the formatter what and how to format.

+ *

+ * A request tells the formatter what and how to format. + *

* - *

When invoking the formatter, the request is passed into {@link IFormatter2#format(FormatterRequest)}.

+ *

+ * When invoking the formatter, the request is passed into {@link IFormatter2#format(FormatterRequest)}. + *

* - *

A request carries information about:

+ *

+ * A request carries information about: + *

*

* * @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 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 { } /** - *

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.

+ *

+ * 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. + *

* - *

Logging exceptions and continuing formatting is the default behavior.

+ *

+ * Logging exceptions and continuing formatting is the default behavior. + *

* - *

Throwing exceptions is useful in unit tests.

+ *

+ * Throwing exceptions is useful in unit tests. + *

* - *

Ignoring exceptions is useful when formatting a document with syntax errors.

+ *

+ * Ignoring exceptions is useful when formatting a document with syntax errors. + *

* - *

Defaults to the {@link ExceptionAcceptor#LOGGING Logging Acceptor}

+ *

+ * Defaults to the {@link ExceptionAcceptor#LOGGING Logging Acceptor} + *

* * @see ExceptionAcceptor#LOGGING * @see ExceptionAcceptor#THROWING diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionAccessToString.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionAccessToString.java index 708673a2d..6b01223ea 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionAccessToString.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionAccessToString.java @@ -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 hiddens = LinkedListMultimap.create(); List errors = Lists.newArrayList(); ITextRegionAccess access = list.get(0).getTextRegionAccess(); - List objects = access.regionsForAllEObjects(); - for (IEObjectRegion obj : objects) { + TreeIterator 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 diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionsInTextToString.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionsInTextToString.java index 4a7b0df32..557413128 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionsInTextToString.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionsInTextToString.java @@ -66,7 +66,7 @@ public class TextRegionsInTextToString { ITextRegionAccess access = getTextRegionAccess(); if (access != null) { ITextSegment impactRegion = TextRegions.merge(this.items); - List expandToLines = access.expandToLines(impactRegion, getLeadingLines(), getTrailingLines()); + List expandToLines = TextRegions.expandToLines(impactRegion, getLeadingLines(), getTrailingLines()); return TextRegions.merge(expandToLines); } return null; diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionsWithTitleToString.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionsWithTitleToString.java index 56ab64db5..b49d756c2 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionsWithTitleToString.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/debug/TextRegionsWithTitleToString.java @@ -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 segments = Lists.newArrayList(); for (Item item : items) segments.add(item.getRegion()); - ITextSegment impactRegion = TextRegions.merge(segments); - List expandToLines = access.expandToLines(impactRegion, getLeadingLines(), getTrailingLines()); - return TextRegions.merge(expandToLines); + ITextSegment impactRegion = merge(segments); + List expandToLines = expandToLines(impactRegion, getLeadingLines(), getTrailingLines()); + return merge(expandToLines); } return null; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/internal/FormattableDocument.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/internal/FormattableDocument.java index 7ba939d1e..c2bad9052 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/internal/FormattableDocument.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/internal/FormattableDocument.java @@ -87,8 +87,11 @@ public abstract class FormattableDocument implements IFormattableDocument { @Override public T append(T owner, Procedure1 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 prepend(T owner, Procedure1 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; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/IEObjectRegion.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/IEObjectRegion.java index 0ceada12e..c55fab6ce 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/IEObjectRegion.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/IEObjectRegion.java @@ -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 getSemanticLeafRegions(); + Iterable getAllSemanticRegions(); + + ISemanticRegionsFinder getRegionFor(); + + Iterable getSemanticRegions(); } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISemanticRegionFinder.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISemanticRegionFinder.java new file mode 100644 index 000000000..8057bbebc --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISemanticRegionFinder.java @@ -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); +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISemanticRegionsFinder.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISemanticRegionsFinder.java new file mode 100644 index 000000000..4a8da7d66 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISemanticRegionsFinder.java @@ -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 assignments(Assignment... assignments); + + List crossRefs(CrossReference... crossReferences); + + List elements(AbstractElement... elements); + + List features(EStructuralFeature... features); + + List> keywordPairs(Keyword kw1, Keyword kw2); + + List> keywordPairs(String kw1, String kw2); + + List keywords(Keyword... keywords); + + List keywords(String... keywords); + + List ruleCalls(RuleCall... ruleCalls); + + List ruleCallsTo(AbstractRule... rules); + +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISequentialRegion.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISequentialRegion.java index eeb9f7d17..1bc9e3bdf 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISequentialRegion.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ISequentialRegion.java @@ -8,15 +8,23 @@ package org.eclipse.xtext.formatting2.regionaccess; /** - *

Common interface for {@link IHiddenRegion} and {@link ISemanticRegion}.

+ *

+ * Common interface for {@link IHiddenRegion} and {@link ISemanticRegion}. + *

* - *

{@link IHiddenRegion} and {@link ISemanticRegion} are arranged strictly alternating in a linked list. This interface - * provides the method to navigate that list.

+ *

+ * {@link IHiddenRegion} and {@link ISemanticRegion} are arranged strictly alternating in a linked list. This interface + * provides the method to navigate that list. + *

* * @author Moritz Eysholdt - Initial contribution and API */ public interface ISequentialRegion extends ITextSegment { + ISemanticRegionFinder immediatelyFollowing(); + + ISemanticRegionFinder immediatelyPreceding(); + IHiddenRegion getNextHiddenRegion(); ISemanticRegion getNextSemanticRegion(); diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionAccess.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionAccess.java index 30b2efe16..4c68ce1de 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionAccess.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionAccess.java @@ -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 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 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 regionsForRuleCalls(EObject owner, RuleCall... ruleCalls); - - List 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 regionsForKeywords(EObject owner, String... keywords); - - List regionsForKeywords(EObject owner, Keyword... keywords); - - List 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 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(); + } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionExtensions.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionExtensions.java new file mode 100644 index 000000000..f308db604 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/ITextRegionExtensions.java @@ -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 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 semanticRegions(EObject object); + +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/TextRegionAccessBuilder.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/TextRegionAccessBuilder.java index 698fa15a1..b788ac289 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/TextRegionAccessBuilder.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/TextRegionAccessBuilder.java @@ -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() { diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractEObjectRegion.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractEObjectRegion.java index 32e7b3166..7c36b202f 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractEObjectRegion.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractEObjectRegion.java @@ -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 getSemanticLeafRegions() { + public List 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 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; } + } \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractHiddenRegion.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractHiddenRegion.java index a0f76e8c4..e8c60d6d0 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractHiddenRegion.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractHiddenRegion.java @@ -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()); + } } \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractRegionAccess.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractRegionAccess.java index dffdade92..a7ac3d461 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractRegionAccess.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractRegionAccess.java @@ -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 allSemanticRegions(EObject object) { + AbstractEObjectRegion region = regionForEObject(object); + if (region == null) + return Collections.emptyList(); + return region.getAllSemanticRegions(); + } + + @Override + public Iterable 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 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 regionsForCrossRefs(EObject owner, CrossReference... crossRefs) { - List 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 regionsForFeatures(EObject owner, EStructuralFeature... features) { - Set 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 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 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 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 regionsForKeywords(EObject owner, String... keywords) { - AbstractEObjectRegion tokens = regionForEObject(owner); - if (tokens == null) - return Collections.emptyList(); - Collection kwSet = keywords.length <= 1 ? Arrays.asList(keywords) : Sets.newHashSet(keywords); - List 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 regionsForKeywords(EObject owner, Keyword... keywords) { - AbstractEObjectRegion tokens = regionForEObject(owner); - if (tokens == null) - return Collections.emptyList(); - List 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 regionsForRuleCallsTo(EObject owner, AbstractRule... rule) { - HashSet set = Sets.newHashSet(rule); - AbstractEObjectRegion tokens = regionForEObject(owner); - if (tokens == null) - return Collections.emptyList(); - List 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 expandToLines(ITextSegment segment, int leadingLinesToAdd, int trailingLinesToAdd) { - List 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(); } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractSemanticRegionsFinder.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractSemanticRegionsFinder.java new file mode 100644 index 000000000..31c9f18b8 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractSemanticRegionsFinder.java @@ -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 { + + 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 { + 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 { + private final Set grammarElements; + + public GrammarElementsPredicate(Set grammarElements) { + super(); + this.grammarElements = grammarElements; + } + + @Override + public boolean apply(ISemanticRegion input) { + return grammarElements.contains(input.getGrammarElement()); + } + + @Override + public String toString() { + List 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 { + 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 { + private final Set keywords; + + public KeywordsPredicate(Set 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 { + 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 { + private final Set rules; + + public RulesPredicate(Set 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 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 assignments(Assignment... assignments) { + return findAll(createPredicate(assignments)); + } + + protected void collectMatchableElements(AbstractElement ele, Collection 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 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 createPredicate(AbstractElement... ele) { + Set 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 crossRefs(CrossReference... crossReferences) { + return findAll(createPredicate(crossReferences)); + } + + @Override + public ISemanticRegion element(AbstractElement element) { + return findFirst(createPredicate(element)); + } + + @Override + public List elements(AbstractElement... elements) { + return findAll(createPredicate(elements)); + } + + @Override + public ISemanticRegion feature(EStructuralFeature feature) { + assertNoContainment(feature); + return findFirst(new FeaturePredicate(feature)); + } + + @Override + public List features(EStructuralFeature... features) { + Set> 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 findAll(Predicate predicate); + + protected abstract ISemanticRegion findFirst(Predicate 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> keywordPairs(Keyword kw1, Keyword kw2) { + Preconditions.checkNotNull(kw1); + Preconditions.checkNotNull(kw2); + Preconditions.checkArgument(kw1 != kw2); + Predicate p1 = createPredicate(kw1); + Predicate p2 = createPredicate(kw2); + List all = findAll(Predicates.or(p1, p2)); + Builder> result = ImmutableList.builder(); + LinkedList stack = new LinkedList(); + 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> keywordPairs(String kw1, String kw2) { + Preconditions.checkNotNull(kw1); + Preconditions.checkNotNull(kw2); + Preconditions.checkArgument(!kw1.equals(kw2)); + Predicate p1 = new KeywordPredicate(kw1); + Predicate p2 = new KeywordPredicate(kw2); + List all = findAll(Predicates.or(p1, p2)); + Builder> result = ImmutableList.builder(); + LinkedList stack = new LinkedList(); + 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 keywords(Keyword... keywords) { + return findAll(createPredicate(keywords)); + } + + @Override + public List keywords(String... keywords) { + Predicate 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 ruleCalls(RuleCall... ruleCalls) { + return findAll(createPredicate(ruleCalls)); + } + + @Override + public List ruleCallsTo(AbstractRule... rules) { + for (int i = 0; i < rules.length; i++) + assertNoEObjectRule(rules[i]); + Predicate 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)); + } + +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractTextSegment.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractTextSegment.java index e477edb55..186e89cb9 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractTextSegment.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/AbstractTextSegment.java @@ -67,7 +67,7 @@ public abstract class AbstractTextSegment implements ITextSegment { @Override public List getLineRegions() { - ILineRegion current = getTextRegionAccess().lineForOffset(getOffset()); + ILineRegion current = getTextRegionAccess().regionForLineAtOffset(getOffset()); List result = Lists.newArrayList(); int endOffset = getEndOffset(); while (current != null) { diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeModelBasedRegionAccess.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeModelBasedRegionAccess.java index 48472840a..497608b2b 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeModelBasedRegionAccess.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeModelBasedRegionAccess.java @@ -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 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 regionsForAllEObjects() { - return ImmutableList. copyOf(eObjectToTokens.values()); + public boolean hasSyntaxError() { + return resource.getParseResult().hasSyntaxErrors(); } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeModelBasedRegionAccessBuilder.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeModelBasedRegionAccessBuilder.java index 2205148eb..0900fc262 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeModelBasedRegionAccessBuilder.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeModelBasedRegionAccessBuilder.java @@ -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; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeSemanticRegion.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeSemanticRegion.java index bb70aed69..58c49fb14 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeSemanticRegion.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/NodeSemanticRegion.java @@ -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; - } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionInIterableFinder.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionInIterableFinder.java new file mode 100644 index 000000000..3587ff9ab --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionInIterableFinder.java @@ -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 regions; + + public SemanticRegionInIterableFinder(Iterable regions) { + super(); + this.regions = regions; + } + + @Override + protected ImmutableList findAll(Predicate predicate) { + Builder builder = ImmutableList.builder(); + for (ISemanticRegion region : regions) + if (predicate.apply(region)) + builder.add(region); + return builder.build(); + } + + @Override + protected ISemanticRegion findFirst(Predicate predicate) { + for (ISemanticRegion region : regions) + if (predicate.apply(region)) + return region; + return null; + } +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionIterable.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionIterable.java new file mode 100644 index 000000000..4c3bb9349 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionIterable.java @@ -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 { + + private final ISemanticRegion first; + private final ISemanticRegion last; + + public SemanticRegionIterable(ISemanticRegion first, ISemanticRegion last) { + super(); + this.first = first; + this.last = last; + } + + @Override + public Iterator iterator() { + return new AbstractIterator() { + 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; + } + }; + } +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionMatcher.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionMatcher.java new file mode 100644 index 000000000..2521d8dca --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionMatcher.java @@ -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 findAll(Predicate predicate) { + ISemanticRegion element = findFirst(predicate); + if (element == null) + return ImmutableList.of(); + else + return ImmutableList.of(element); + } + + @Override + protected ISemanticRegion findFirst(Predicate predicate) { + if (predicate.apply(region)) + return region; + return null; + } +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionNullFinder.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionNullFinder.java new file mode 100644 index 000000000..59a0290a9 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionNullFinder.java @@ -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 assignments(Assignment... assignments) { + return Collections.emptyList(); + } + + @Override + public ISemanticRegion crossRef(CrossReference crossReference) { + return null; + } + + @Override + public List crossRefs(CrossReference... crossReferences) { + return Collections.emptyList(); + } + + @Override + public ISemanticRegion element(AbstractElement element) { + return null; + } + + @Override + public List elements(AbstractElement... elements) { + return Collections.emptyList(); + } + + @Override + public ISemanticRegion feature(EStructuralFeature feature) { + return null; + } + + @Override + public List 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> keywordPairs(Keyword kw1, Keyword kw2) { + return Collections.emptyList(); + } + + @Override + public List> keywordPairs(String kw1, String kw2) { + return Collections.emptyList(); + } + + @Override + public List keywords(Keyword... keywords) { + return Collections.emptyList(); + } + + @Override + public List keywords(String... keywords) { + return Collections.emptyList(); + } + + @Override + public ISemanticRegion ruleCall(RuleCall ruleCall) { + return null; + } + + @Override + public List ruleCalls(RuleCall... ruleCalls) { + return Collections.emptyList(); + } + + @Override + public List ruleCallsTo(AbstractRule... rules) { + return Collections.emptyList(); + } + + @Override + public ISemanticRegion ruleCallTo(AbstractRule rule) { + return null; + } + +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringBasedRegionAccess.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringBasedRegionAccess.java index 2444bf0ad..b3d0757cb 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringBasedRegionAccess.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringBasedRegionAccess.java @@ -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 regionsForAllEObjects() { - return ImmutableList. copyOf(eObjectToTokens.values()); + public boolean hasSyntaxError() { + return false; } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringEObjectRegion.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringEObjectRegion.java index 053202d53..ff43decea 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringEObjectRegion.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringEObjectRegion.java @@ -19,5 +19,4 @@ public class StringEObjectRegion extends AbstractEObjectRegion { this.setGrammarElement(grammarElement); this.setSemantcElement(semanticElement); } - } \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringSemanticRegion.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringSemanticRegion.java index f59bdd416..51966761b 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringSemanticRegion.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/StringSemanticRegion.java @@ -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; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/TextRegionAccessBuildingSequencer.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/TextRegionAccessBuildingSequencer.java index a49efe655..e3f82df5f 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/TextRegionAccessBuildingSequencer.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/TextRegionAccessBuildingSequencer.java @@ -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); } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/TextRegions.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/TextRegions.java index b8c1ef700..f50bf9d6a 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/TextRegions.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/regionaccess/internal/TextRegions.java @@ -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 expandToLines(ITextSegment segment, int leadingLinesToAdd, int trailingLinesToAdd) { + List 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; + } + } diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/FormattableDocumentTest.xtend b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/FormattableDocumentTest.xtend index bc03ca26d..8535c552f 100644 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/FormattableDocumentTest.xtend +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/FormattableDocumentTest.xtend @@ -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 = ''' diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/FormatterSerializerIntegrationTest.xtend b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/FormatterSerializerIntegrationTest.xtend index 67bcba0e0..181b43ef9 100644 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/FormatterSerializerIntegrationTest.xtend +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/FormatterSerializerIntegrationTest.xtend @@ -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 = " "] } } diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/GenericFormatterTester.xtend b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/GenericFormatterTester.xtend index d3897ff87..dcfd8c411 100644 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/GenericFormatterTester.xtend +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/GenericFormatterTester.xtend @@ -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 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) } diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/RegionAccessAccessTest.xtend b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/RegionAccessAccessTest.xtend deleted file mode 100644 index 23e0bdeb1..000000000 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/RegionAccessAccessTest.xtend +++ /dev/null @@ -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 parseHelper - @Inject Provider 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 parseAs(CharSequence seq, Class 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 regions) { - assertEquals(expected, single.text) - assertEquals(1, regions.size) - assertEquals(expected, regions.head.text) - assertTrue(regions instanceof ImmutableList) - } -} \ No newline at end of file diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionFinderTest.xtend b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionFinderTest.xtend new file mode 100644 index 000000000..66f20a415 --- /dev/null +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/SemanticRegionFinderTest.xtend @@ -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 parseHelper + @Inject Provider 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> pairs) { + pairs.map[key.merge(value).text].join("; ") + } + + def private parseAs(CharSequence seq, Class 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 regions) { + assertEquals(expected, single.text) + assertEquals(1, regions.size) + assertEquals(expected, regions.head.text) + assertTrue(regions instanceof ImmutableList) + } +} \ No newline at end of file