[xtext][locationProvider] Introduced extension interface to ILocationInFileProvider which allows to specify the requested region more fine grained

Currently supported regions are: 
SIGNIFICANT,
FULL,
INCLUDING_COMMENTS,
INCLUDING_WHITESPACE
This commit is contained in:
Sebastian Zarnekow 2012-03-05 21:29:41 +01:00
parent f7a8042a9a
commit 5882670c44
4 changed files with 207 additions and 12 deletions

View file

@ -44,7 +44,6 @@ public class DefaultHiddenTokenHelper extends AbstractHiddenTokenHelper {
}
@Inject
@SuppressWarnings("unused")
private void setGrammar(IGrammarAccess grammar) {
wsRule = GrammarUtil.findRuleForName(grammar.getGrammar(), "WS");
}

View file

@ -17,23 +17,32 @@ import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreSwitch;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.parsetree.reconstr.IHiddenTokenHelper;
import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.util.TextRegionWithLineInformation;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
/**
* @author Peter Friese - Implementation
* @author Sven Efftinge - Initial contribution and API
*/
public class DefaultLocationInFileProvider implements ILocationInFileProvider {
public class DefaultLocationInFileProvider implements ILocationInFileProvider, ILocationInFileProviderExtension {
@Inject(optional = true)
private IHiddenTokenHelper hiddenTokenHelper;
public ITextRegion getSignificantTextRegion(EObject obj) {
return getTextRegion(obj, true);
}
@ -43,20 +52,41 @@ public class DefaultLocationInFileProvider implements ILocationInFileProvider {
}
protected ITextRegion getTextRegion(EObject obj, boolean isSignificant) {
return doGetTextRegion(obj, isSignificant ? RegionDescription.SIGNIFICANT : RegionDescription.FULL);
}
/**
* @since 2.3
*/
@Nullable
public ITextRegion getTextRegion(@NonNull EObject object, @NonNull RegionDescription query) {
switch(query) {
// we delegate the implementation to the existing and potentially overridden methods
case SIGNIFICANT: return getSignificantTextRegion(object);
case FULL: return getFullTextRegion(object);
default:
return doGetTextRegion(object, query);
}
}
/**
* @since 2.3
*/
protected ITextRegion doGetTextRegion(EObject obj, @NonNull RegionDescription query) {
ICompositeNode node = NodeModelUtils.findActualNodeFor(obj);
if (node == null) {
if (obj.eContainer() == null)
return ITextRegion.EMPTY_REGION;
return getTextRegion(obj.eContainer(), isSignificant);
return getTextRegion(obj.eContainer(), query);
}
List<INode> nodes = null;
if (isSignificant)
if (query == RegionDescription.SIGNIFICANT)
nodes = getLocationNodes(obj);
if (nodes == null || nodes.isEmpty())
nodes = Collections.<INode>singletonList(node);
return createRegion(nodes);
return createRegion(nodes, query);
}
public ITextRegion getSignificantTextRegion(final EObject owner, EStructuralFeature feature, final int indexInList) {
return getTextRegion(owner, feature, indexInList, true);
}
@ -64,6 +94,22 @@ public class DefaultLocationInFileProvider implements ILocationInFileProvider {
public ITextRegion getFullTextRegion(EObject owner, EStructuralFeature feature, int indexInList) {
return getTextRegion(owner, feature, indexInList, false);
}
/**
* @since 2.3
*/
@NonNullByDefault
@Nullable
public ITextRegion getTextRegion(EObject object, EStructuralFeature feature, int indexInList,
RegionDescription query) {
switch(query) {
// we delegate the implementation to the existing and potentially overridden methods
case SIGNIFICANT: return getSignificantTextRegion(object, feature, indexInList);
case FULL: return getFullTextRegion(object, feature, indexInList);
default:
return doGetTextRegion(object, feature, indexInList, query);
}
}
private ITextRegion getTextRegion(final EObject owner, EStructuralFeature feature, final int indexInList,
final boolean isSignificant) {
@ -84,9 +130,35 @@ public class DefaultLocationInFileProvider implements ILocationInFileProvider {
}.doSwitch(feature);
}
private ITextRegion doGetTextRegion(final EObject owner, EStructuralFeature feature, final int indexInList,
final RegionDescription query) {
if (feature == null)
return null;
if (feature instanceof EAttribute) {
return doGetLocationOfFeature(owner, feature, indexInList, query);
}
if (feature instanceof EReference) {
EReference reference = (EReference) feature;
if (reference.isContainment() || reference.isContainer()) {
return getLocationOfContainmentReference(owner, reference, indexInList, query);
} else {
return doGetLocationOfFeature(owner, reference, indexInList, query);
}
}
return null;
}
protected ITextRegion getLocationOfContainmentReference(final EObject owner, EReference feature,
final int indexInList, boolean isSignificant) {
return getLocationOfContainmentReference(owner, feature, indexInList, isSignificant ? RegionDescription.SIGNIFICANT : RegionDescription.FULL);
}
/**
* @since 2.3
*/
protected ITextRegion getLocationOfContainmentReference(final EObject owner, EReference feature,
final int indexInList, RegionDescription query) {
Object referencedElement = null;
if(feature.isMany()) {
List<?> values = (List<?>) owner.eGet(feature);
@ -98,7 +170,7 @@ public class DefaultLocationInFileProvider implements ILocationInFileProvider {
else {
referencedElement = owner.eGet(feature);
}
return getTextRegion((EObject) referencedElement, isSignificant);
return getTextRegion((EObject) referencedElement, query);
}
protected ITextRegion getLocationOfCrossReference(EObject owner, EReference reference, int indexInList,
@ -113,23 +185,34 @@ public class DefaultLocationInFileProvider implements ILocationInFileProvider {
protected ITextRegion doGetLocationOfFeature(EObject owner, EStructuralFeature feature, int indexInList,
boolean isSignificant) {
return doGetLocationOfFeature(owner, feature, indexInList, isSignificant ? RegionDescription.SIGNIFICANT : RegionDescription.FULL);
}
/**
* @since 2.3
*/
protected ITextRegion doGetLocationOfFeature(EObject owner, EStructuralFeature feature, int indexInList,
RegionDescription query) {
if (!feature.isMany())
indexInList = 0;
if (indexInList >= 0) {
List<INode> findNodesForFeature = NodeModelUtils.findNodesForFeature(owner, feature);
if (indexInList < findNodesForFeature.size()) {
INode node = findNodesForFeature.get(indexInList);
return new TextRegionWithLineInformation(node.getOffset(), node.getLength(), node.getStartLine() - 1, node.getEndLine() - 1);
return createRegion(Collections.singletonList(node), query);
}
return getTextRegion(owner, isSignificant);
return getTextRegion(owner, query);
}
return null;
}
protected List<INode> getLocationNodes(EObject obj) {
final EStructuralFeature nameFeature = getIdentifierFeature(obj);
if (nameFeature != null)
return NodeModelUtils.findNodesForFeature(obj, nameFeature);
if (nameFeature != null) {
List<INode> result = NodeModelUtils.findNodesForFeature(obj, nameFeature);
if (!result.isEmpty())
return result;
}
List<INode> resultNodes = Lists.newArrayList();
final ICompositeNode startNode = NodeModelUtils.getNode(obj);
@ -166,7 +249,6 @@ public class DefaultLocationInFileProvider implements ILocationInFileProvider {
}
protected ITextRegion createRegion(final List<INode> nodes) {
// if we've got more than one ID elements, we want to select them all
ITextRegion result = ITextRegion.EMPTY_REGION;
for (INode node : nodes) {
if (!isHidden(node)) {
@ -177,7 +259,48 @@ public class DefaultLocationInFileProvider implements ILocationInFileProvider {
}
return result;
}
/**
* @since 2.3
*/
protected ITextRegion createRegion(List<INode> nodes, RegionDescription query) {
if (query == RegionDescription.FULL || query == RegionDescription.SIGNIFICANT)
return createRegion(nodes);
ITextRegion result = ITextRegion.EMPTY_REGION;
for (INode node : nodes) {
for(INode leafNode: node.getLeafNodes()) {
if (!isHidden(leafNode, query)) {
int length = leafNode.getLength();
if (length != 0)
result = result.merge(new TextRegionWithLineInformation(leafNode.getOffset(), length, leafNode.getStartLine() - 1, leafNode.getEndLine() - 1));
}
}
}
return result;
}
/**
* @since 2.3
*/
protected boolean isHidden(INode node, RegionDescription query) {
if (query == RegionDescription.INCLUDING_WHITESPACE) {
return false;
}
if (node instanceof ILeafNode && ((ILeafNode) node).isHidden()) {
if (query == RegionDescription.INCLUDING_COMMENTS) {
if (hiddenTokenHelper != null && node.getGrammarElement() instanceof AbstractRule) {
boolean result = hiddenTokenHelper.isWhitespace((AbstractRule) node.getGrammarElement());
return result;
}
boolean result = node.getText().trim().length() == 0;
return result;
} else {
return true;
}
}
return false;
}
protected boolean isHidden(INode node) {
return node instanceof ILeafNode && ((ILeafNode) node).isHidden();
}

View file

@ -18,6 +18,8 @@ import com.google.inject.ImplementedBy;
* Delivers {@link ITextRegion}s for model elements or feature settings. The significant text is the part of the text
* identifying the element, e.g. its name, as opposed to the full region which is the full text representing the
* element.
* In addition to this service, the extension interface {@link ILocationInFileProviderExtension} allows to query
* for the region that is spanned by an {@link EObject} including its comments.
*
* @author Sven Efftinge - Initial contribution and API
* @author Jan Koehnlein - Distinguish significant and full region

View file

@ -0,0 +1,71 @@
/*******************************************************************************
* Copyright (c) 2012 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.resource;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.xtext.util.ITextRegion;
/**
* Extends the functionality of the {@link ILocationInFileProvider} to
* allow clients to query for a region with more fine grained criteria.
*
* @author Sebastian Zarnekow - Initial contribution and API
* @since 2.3
*/
@NonNullByDefault
public interface ILocationInFileProviderExtension {
/**
* Describes the kind of region that is queried.
*/
enum RegionDescription {
/**
* The significant region of an {@link EObject} is basically
* the part of the text that identifies the instance in a
* human readable manner.
*/
SIGNIFICANT,
/**
* The full region spans the outermost visible nodes of an instance.
* Comments and whitespaces are trimmed.
*/
FULL,
/**
* Allows to obtain the range including the associated comments.
*/
INCLUDING_COMMENTS,
/**
* Returns the complete range including leading and trailing whitespace.
*/
INCLUDING_WHITESPACE
}
/**
* Queries for parts of the text region that the given object is originates from.
* @param object the instance whose region should be returned.
* @param query the hint about the requested range.
* @return the text region or <code>null</code> if the object does not have an associated text region.
*/
@Nullable
ITextRegion getTextRegion(EObject object, RegionDescription query);
/**
* Queries for parts of the text region that parts of the given object originate from.
* @param object the instance whose region should be returned.
* @param feature the feature that was set when the requested range was consumed by the parser.
* @param indexInList the index in the list of feature values. <code>-1</code> if all values should be considered.
* @param query the hint about the requested range.
* @return the text region or <code>null</code> if the object does not have an associated text region.
*/
@Nullable
ITextRegion getTextRegion(EObject object, EStructuralFeature feature, int indexInList, RegionDescription query);
}