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<>(); 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<>(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; } } }