mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-16 16:58:56 +00:00
[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:
parent
f7a8042a9a
commit
5882670c44
4 changed files with 207 additions and 12 deletions
|
@ -44,7 +44,6 @@ public class DefaultHiddenTokenHelper extends AbstractHiddenTokenHelper {
|
|||
}
|
||||
|
||||
@Inject
|
||||
@SuppressWarnings("unused")
|
||||
private void setGrammar(IGrammarAccess grammar) {
|
||||
wsRule = GrammarUtil.findRuleForName(grammar.getGrammar(), "WS");
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
Loading…
Reference in a new issue