blob: abd74c1d088bb78425f2e979beeddf2e5ceb9ce3 [file] [log] [blame]
package org.eclipse.dltk.ui.text.heredoc;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IPredicateRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.RuleBasedPartitionScanner;
/**
* A slightly modified version of a <code>RuleBasedPartitionScanner</code> that
* knows how to properly scan for heredoc partitions.
*
* <p>
* There is no need to use this partition scanner if heredoc is not supported by
* the underlying dynamic language.
* </p>
*
* <p>
* If you do use this partitioner, you <b>must</b> also use the
* <code>HereDocEnabledPartitioner</code> partitioner as it knows how to
* properly manage heredoc partitions.
* </p>
*
* @see HereDocPartitionRule
* @see HereDocEnabledPartitioner
* @see HereDocEnabledPresentationReconciler
*/
public class HereDocEnabledPartitionScanner extends RuleBasedPartitionScanner
{
private List<TokenContainer> buffer = new ArrayList<TokenContainer>();
private HereDocPartitionRule hereDocRule;
/**
* Creates a new heredoc partition scanner
*
* @param rules list of predicate rules that should be used to create document partitions
* @param hereDocRule heredoc partitioning rule
*/
public HereDocEnabledPartitionScanner(List<IPredicateRule> rules, HereDocPartitionRule hereDocRule)
{
this.hereDocRule = hereDocRule;
IPredicateRule[] result = new IPredicateRule[rules.size()];
setPredicateRules(rules.toArray(result));
}
@Override public IToken nextToken()
{
if (HereDocUtils.isHereDocContent(fContentType))
{
return handleHereDoc();
}
if (!buffer.isEmpty())
{
return buffer.remove(0).getToken();
}
return getNextToken(false);
}
@Override public void setPartialRange(IDocument document, int offset, int length, String contentType,
int partitionOffset)
{
/*
* clear out any tokens that may be sitting in the buffer before the 'nextToken()' is requested. this method is
* only called if the document changes, which means the 'manual' rule should be passed all the information it
* needs to finish it's evaluation piggy-backed off the passed content type.
*/
buffer.clear();
super.setPartialRange(document, offset, length, contentType, partitionOffset);
}
private IToken evalPossibleHereDoc(boolean inScan)
{
// our token is going to start wherever the scanner is
fTokenOffset = fOffset;
IToken token = hereDocRule.evaluate(this);
if (token.isUndefined() || inScan)
{
return token;
}
buffer.add(new TokenContainer(token));
int c;
while ((c = read()) != ICharacterScanner.EOF)
{
unread();
IToken next = getNextToken(true);
buffer.add(new TokenContainer(next));
if (c == '\n')
{
break;
}
}
if (c != ICharacterScanner.EOF)
{
consumeHereDoc();
}
return buffer.remove(0).getToken();
}
private void consumeHereDoc()
{
// need to work w/ a copy otherwise we get concurrent modification exception
for (TokenContainer container : new ArrayList<TokenContainer>(buffer))
{
if (HereDocUtils.isHereDocContent(container.getContentType()))
{
fTokenOffset = fOffset;
fColumn = UNDEFINED;
IToken body = hereDocRule.evaluate(this, container.getContentType());
buffer.add(new TokenContainer(body));
}
}
}
private IToken getNextToken(boolean inScan)
{
IToken token = evalPossibleHereDoc(inScan);
if (token.isUndefined())
{
token = super.nextToken();
}
return token;
}
private IToken handleHereDoc()
{
// reset to partition start so we get all the characters
fTokenOffset = fPartitionOffset;
IToken token = hereDocRule.evaluate(this, fContentType);
// we found our rule, reset the contentType just like our parent would
fContentType = null;
return token;
}
private class TokenContainer
{
private int offset;
private IToken token;
private int tokenOffset;
TokenContainer(IToken token)
{
this.offset = fOffset;
this.tokenOffset = fTokenOffset;
this.token = token;
}
String getContentType()
{
return (String) token.getData();
}
IToken getToken()
{
fOffset = offset;
fTokenOffset = tokenOffset;
return token;
}
}
}