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 1a60d6d24..636cc2982 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 @@ -67,6 +67,8 @@ 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. @@ -78,6 +80,8 @@ public interface ITextRegionAccess { */ XtextResource getResource(); + ITextRegionRewriter getRewriter(); + /** * @return true, if one or more parse error occurred when parsing the document. */ @@ -135,6 +139,10 @@ public interface ITextRegionAccess { */ 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. @@ -147,18 +155,29 @@ public interface ITextRegionAccess { */ 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); + 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); + 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. @@ -172,26 +191,12 @@ public interface ITextRegionAccess { */ 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() - - ITextRegionRewriter getRewriter(); - - IEObjectRegion regionForRootEObject(); - - ITextSegment regionForDocument(); - - ITextSegment regionForOffset(int offset, int length); - - ILineRegion lineForOffset(int offset); - - String textForOffset(int offset, int length); - - List expandToLines(ITextSegment segment, int leadingLinesToAdd, int trailingLinesToAdd); - - List regionsForAllEObjects(); } 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 747e32506..8369f8944 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 @@ -13,6 +13,7 @@ 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; @@ -31,6 +32,7 @@ import org.eclipse.xtext.formatting2.regionaccess.ISequentialRegion; import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess; 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; @@ -154,8 +156,7 @@ public abstract class AbstractRegionAccess implements ITextRegionAccess { @Override public ISemanticRegion regionForFeature(EObject owner, EStructuralFeature feat) { - if (!(feat instanceof EAttribute) && !(feat instanceof EReference && !((EReference) feat).isContainment())) - throw new IllegalStateException("Only EAttributes and CrossReferences allowed."); + assertNoContainment(feat); AbstractEObjectRegion tokens = regionForEObject(owner); if (tokens == null) return null; @@ -167,6 +168,31 @@ public abstract class AbstractRegionAccess implements ITextRegionAccess { return null; } + 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 null; + 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); 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 new file mode 100644 index 000000000..2560a10d6 --- /dev/null +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/regionaccess/internal/RegionAccessAccessTest.xtend @@ -0,0 +1,90 @@ +/******************************************************************************* + * 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 org.eclipse.emf.ecore.EObject +import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess +import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder +import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.Mixed +import org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.Root +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 org.eclipse.xtext.formatting2.regionaccess.internal.regionaccesstestlanguage.AssignedAction + +/** + * @author Moritz Eysholdt - Initial contribution and API + */ +@RunWith(XtextRunner) +@InjectWith(RegionAccessTestLanguageInjectorProvider) +class RegionAccessAccessTest { + @Inject extension ParseHelper parseHelper + @Inject Provider textRegionAccessBuilder + @Inject extension ValidationTestHelper validationTestHelper + + @Test def void regionForFeatureAttribute() { + val mixed = '''6 (foo)'''.parseAs(Mixed) + val access = mixed.toAccess + val actual = access.regionForFeature(mixed, MIXED__NAME) + assertEquals("foo", actual.text) + } + + @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) + assertEquals("foo", actual.text) + } + + @Test(expected=IllegalStateException) def void regionForFeatureContainmentReference() { + val mixed = '''6 (foo) action'''.parseAs(AssignedAction) + val access = mixed.toAccess + val actual = access.regionForFeature(mixed, ASSIGNED_ACTION__CHILD) + assertEquals("foo", actual.text) + } + + @Test def void regionsForFeaturesAttribute() { + val mixed = '''6 (foo)'''.parseAs(Mixed) + val access = mixed.toAccess + val actual = access.regionsForFeatures(mixed, MIXED__NAME) + assertEquals("foo", actual.map[text].join) + } + + @Test def void regionsForFeaturesCrossReference() { + val mixed = '''6 (ref foo) action (foo) end'''.parseAs(AssignedAction) + val access = mixed.toAccess + val actual = access.regionsForFeatures(mixed.child, MIXED__REF) + assertEquals("foo", actual.map[text].join) + } + + @Test(expected=IllegalStateException) def void regionsForFeaturesContainmentReference() { + val mixed = '''6 (foo) action'''.parseAs(AssignedAction) + val access = mixed.toAccess + val actual = access.regionsForFeatures(mixed, ASSIGNED_ACTION__CHILD) + assertEquals("foo", actual.map[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 + } +} \ No newline at end of file