package org.rubypeople.rdt.internal.ti.util;
import java.util.Iterator;
import org.jruby.ast.ArgsNode;
import org.jruby.ast.ArgumentNode;
import org.jruby.ast.Colon2Node;
import org.jruby.ast.ConstNode;
import org.jruby.ast.NewlineNode;
import org.jruby.ast.Node;
/**
* Given a JRuby AST Node and a source offset, recursively searches the children of the root Node for the Node that most
* tightly matches the given offset.
*
* @author Jason Morrison
*/
public class OffsetNodeLocator extends NodeLocator
{
// Singleton pattern
private OffsetNodeLocator()
{
}
private static OffsetNodeLocator staticInstance = new OffsetNodeLocator();
public static OffsetNodeLocator Instance()
{
return staticInstance;
}
/** Most closely spanning node yet found. */
private Node locatedNode;
/** Offset sought. */
private int offset;
/**
* Gets the most closely spanning node of the requested offset.
*
* @param rootNode
* Node which should span or have children spanning the offset.
* @param offset
* Offset to locate the node of.
* @return Node most closely spanning the requested offset.
*/
public Node getNodeAtOffset(Node rootNode, int offset)
{
if (rootNode == null)
{
return null;
}
locatedNode = null;
this.offset = offset;
// Traverse to find closest node
rootNode.accept(this);
// Refine the node, if possible, to an inner node not covered by the visitor
// (Why? Nodes such as ArgumentNode don't like being visited, so they must be handled here.)
locatedNode = refine(locatedNode);
// Return the node
return locatedNode;
}
private Node refine(Node node)
{
// If the search returned an ArgsNode, try to find the specific ArgumentNode matched
if (node instanceof ArgsNode)
{
ArgsNode argsNode = (ArgsNode) node;
if (argsNode.getRequiredArgsCount() > 0)
{
for (Iterator<Node> iter = argsNode.getPre().childNodes().iterator(); iter.hasNext();)
{
ArgumentNode argNode = (ArgumentNode) iter.next();
if (nodeDoesSpanOffset(argNode, offset))
{
return argNode;
}
}
}
}
return node;
}
/**
* For each node, see if it spans the desired offset. If so, see if it spans it more closely than any previously
* identified spanning node. If so, record it as the most closely spanning yet.
*/
public Object handleNode(Node iVisited)
{
// Skip the NewlineNode since its position is very unaccurate
if (!(iVisited instanceof NewlineNode) && nodeDoesSpanOffset(iVisited, offset))
{
// note: careful... should this be <=? I think so; since it traverses in-order, this should find the
// "most specific" closest node. i.e.
// def foo;x;end offset at 'x' is a 1-char ScopingNode and 1-char LocalVarNode; it should identify the
// LocalVarNode, which <= does.
if (locatedNode == null || (nodeSpanLength(iVisited) <= nodeSpanLength(locatedNode)))
{
if (!((locatedNode instanceof Colon2Node) && (iVisited instanceof ConstNode)))
{
locatedNode = iVisited;
}
if (iVisited instanceof ArgsNode)
{
ArgsNode args = (ArgsNode) iVisited;
handleNode(args.getRestArgNode());
}
}
}
// TODO Since we are moving in order, if a spanning node has been located, and the current node does
// not span, we can effectively return early since no subsequent nodes should span. Not doing this
// now, just in case InOrderVisitor proves to not quite be in-order (i.e. offsets reported are off.)
return super.handleNode(iVisited);
}
}