blob: 4146bd3a5e5ffb6c409cbd4e7c8952ce0cb4e66e [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2005, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ecommons.text;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.TypedRegion;
import org.eclipse.statet.jcommons.collections.IntArrayList;
import org.eclipse.statet.jcommons.collections.IntList;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.ecommons.text.core.PartitionConstraint;
import org.eclipse.statet.ecommons.text.core.sections.DocContentSections;
/**
* Utility methods for heuristic based R manipulations in an incomplete source file.
* <p>
* An instance holds some internal position in the document and is therefore not threadsafe.</p>
*
* @since 0.2
*/
@NonNullByDefault
public class BasicHeuristicTokenScanner implements ITokenScanner {
protected static final PartitionConstraint ALL_PARTITIONS_CONSTRAINT= new PartitionConstraint() {
@Override
public boolean matches(final String partitionType) {
return true;
}
};
/**
* Specifies the stop condition, upon which the <code>scan...</code> methods will decide whether
* to keep scanning or not. This interface may implemented by clients.
*/
protected abstract class StopCondition {
/**
* Instructs the scanner to return the current position.
*
* @return <code>true</code> if the stop condition is met.
*/
public abstract boolean stop();
/**
* Asks the condition to return the next position to query. The default
* is to return the next/previous position.
*
* @return the next position to scan
*/
public int nextPositionForward() {
return BasicHeuristicTokenScanner.this.pos + 1;
}
public int nextPositionBackward() {
return BasicHeuristicTokenScanner.this.pos - 1;
}
}
/**
* Stops upon a character in the default partition that matches the given character list.
*/
protected abstract class PartitionBasedCondition extends StopCondition {
private @Nullable ITypedRegion currentPartition;
private boolean currentPartitionMatched;
private int currentPartitionStart;
private int currentPartitionEnd;
public PartitionBasedCondition() {
this.currentPartitionMatched= false;
}
@Override
public boolean stop() {
if (this.currentPartitionMatched && this.currentPartitionStart <= BasicHeuristicTokenScanner.this.pos && BasicHeuristicTokenScanner.this.pos < this.currentPartitionEnd) {
return matchesChar();
}
final ITypedRegion partition= getPartition();
this.currentPartition= partition;
this.currentPartitionStart= partition.getOffset();
this.currentPartitionEnd= this.currentPartitionStart + partition.getLength();
if (BasicHeuristicTokenScanner.this.partitionConstraint.matches(partition.getType())) {
this.currentPartitionMatched= true;
return matchesChar();
}
else {
this.currentPartitionMatched= false;
return false;
}
}
protected abstract boolean matchesChar();
@Override
public int nextPositionForward() {
if (this.currentPartitionMatched) {
return BasicHeuristicTokenScanner.this.pos + 1;
}
if (BasicHeuristicTokenScanner.this.pos < this.currentPartitionEnd) {
return this.currentPartitionEnd;
}
return BasicHeuristicTokenScanner.this.pos + 1;
}
@Override
public int nextPositionBackward() {
if (this.currentPartitionMatched) {
return BasicHeuristicTokenScanner.this.pos - 1;
}
if (BasicHeuristicTokenScanner.this.pos >= this.currentPartitionStart) {
return this.currentPartitionStart - 1;
}
return BasicHeuristicTokenScanner.this.pos - 1;
}
}
protected class SingleCharacterMatchCondition extends PartitionBasedCondition {
protected final int singleChar;
/**
* Creates a new instance.
* @param ch the single character to match
*/
public SingleCharacterMatchCondition(final char ch) {
this.singleChar= ch;
}
@Override
protected boolean matchesChar() {
return (this.singleChar == BasicHeuristicTokenScanner.this.ch);
}
}
protected class CharacterMatchCondition extends PartitionBasedCondition {
protected final char[] fChars;
/**
* Creates a new instance.
* @param chars the chars to match.
*/
public CharacterMatchCondition(final char[] chars) {
assert (chars != null);
this.fChars= chars;
}
@Override
protected boolean matchesChar() {
for (int i= 0; i < this.fChars.length; i++) {
if (this.fChars[i] == BasicHeuristicTokenScanner.this.ch) {
return true;
}
}
return false;
}
}
protected class ExtCharacterMatchCondition extends CharacterMatchCondition {
private final char fEscapeChar;
private int fLastEscapeOffset= -100;
ExtCharacterMatchCondition(final char[] chars, final char escapeChar) {
super(chars);
this.fEscapeChar= escapeChar;
}
@Override
protected boolean matchesChar() {
if (BasicHeuristicTokenScanner.this.pos == this.fLastEscapeOffset+1) {
return false;
}
if (BasicHeuristicTokenScanner.this.ch == this.fEscapeChar) {
this.fLastEscapeOffset= BasicHeuristicTokenScanner.this.pos;
return false;
}
return super.matchesChar();
}
}
protected class StringMatchCondition extends PartitionBasedCondition {
protected final String[] strings;
private final int escapeChar;
private int lastEscapeOffset= Integer.MIN_VALUE;
/**
* Creates a new instance.
* @param ch the string to match
*/
public StringMatchCondition(final String s, final int escapeChar) {
this.strings= new String[] { s };
this.escapeChar= escapeChar;
}
/**
* Creates a new instance.
* @param ch the string to match
*/
public StringMatchCondition(final String[] s, final int escapeChar) {
this.strings= s;
this.escapeChar= escapeChar;
}
@Override
protected boolean matchesChar() {
if (BasicHeuristicTokenScanner.this.pos == this.lastEscapeOffset + 1) {
return false;
}
if (this.escapeChar == BasicHeuristicTokenScanner.this.ch) {
this.lastEscapeOffset= BasicHeuristicTokenScanner.this.pos;
return false;
}
try {
for (final String string : this.strings) {
if (string.charAt(0) == BasicHeuristicTokenScanner.this.ch) {
if (string.regionMatches(1, BasicHeuristicTokenScanner.this.document.get(
BasicHeuristicTokenScanner.this.pos + 1, string.length() - 1),
0, string.length() - 1 )) {
return true;
}
}
}
} catch (final BadLocationException e) {}
return false;
}
}
/** The partitioning being used for scanning. */
private final String partitioning;
private final PartitionConstraint defaultPartitionConstraint;
/** The document being scanned. */
private IDocument document;
/** The partition to scan in. */
private PartitionConstraint partitionConstraint;
/* internal scan state */
/** the most recently read character. */
protected char ch;
/** the most recently read position. */
protected int pos;
/** the most recently read line of position (only if used). */
private int line;
private @Nullable StopCondition nonWSCondition;
private @Nullable StopCondition nonWSorLRCondition;
@SuppressWarnings("null")
public BasicHeuristicTokenScanner(final DocContentSections documentContentInfo,
final PartitionConstraint defaultContentConstraint) {
this.partitioning= documentContentInfo.getPartitioning();
this.defaultPartitionConstraint= defaultContentConstraint;
}
public final IDocument getDocument() {
return this.document;
}
public final String getDocumentPartitioning() {
return this.partitioning;
}
protected final PartitionConstraint getDefaultPartitionConstraint() {
return this.defaultPartitionConstraint;
}
protected final PartitionConstraint getPartitionConstraint() {
return this.partitionConstraint;
}
public final char getChar() {
return this.ch;
}
protected boolean isWhitespace() {
return (Character.getType(this.ch) == Character.SPACE_SEPARATOR || this.ch == '\t');
}
protected final StopCondition getAnyNonWSCondition() {
StopCondition nonWSCondition= this.nonWSCondition;
if (nonWSCondition == null) {
nonWSCondition= new StopCondition() {
@Override
public boolean stop() {
return (!isWhitespace());
}
};
this.nonWSCondition= nonWSCondition;
}
return nonWSCondition;
}
protected final StopCondition getAnyNonWSorLRCondition() {
StopCondition nonWSorLRCondition= this.nonWSorLRCondition;
if (nonWSorLRCondition == null) {
nonWSorLRCondition= new StopCondition() {
@Override
public boolean stop() {
return (!isWhitespace() && BasicHeuristicTokenScanner.this.ch != '\r' && BasicHeuristicTokenScanner.this.ch != '\n');
}
};
this.nonWSorLRCondition= nonWSorLRCondition;
}
return nonWSorLRCondition;
}
protected final StopCondition getNonWSCondition() {
StopCondition nonWSCondition= this.nonWSCondition;
if (nonWSCondition == null) {
nonWSCondition= new PartitionBasedCondition() {
@Override
protected boolean matchesChar() {
return (!isWhitespace());
}
};
this.nonWSCondition= nonWSCondition;
}
return nonWSCondition;
}
protected final StopCondition getNonWSorLRCondition() {
StopCondition nonWSorLRCondition= this.nonWSorLRCondition;
if (nonWSorLRCondition == null) {
nonWSorLRCondition= new PartitionBasedCondition() {
@Override
protected boolean matchesChar() {
return (!isWhitespace() && BasicHeuristicTokenScanner.this.ch != '\r' && BasicHeuristicTokenScanner.this.ch != '\n');
}
};
this.nonWSorLRCondition= nonWSorLRCondition;
}
return nonWSorLRCondition;
}
/**
* Configures the scanner for the given document
* and the given partition type as partition constraint
*
* @param document the document to scan
* @param partition the partition to scan in
*/
@Override
public void configure(final IDocument document, final String partitionType) {
assert (document != null && partitionType != null);
this.document= document;
this.partitionConstraint= new PartitionConstraint() {
@Override
public boolean matches(final String partitionTypeToTest) {
return partitionType == partitionTypeToTest;
}
};
}
/**
* Configures the scanner for the given document
* and no partition constraint
*
* @param document the document to scan
*/
public void configure(final IDocument document) {
assert (document != null);
this.document= document;
this.partitionConstraint= ALL_PARTITIONS_CONSTRAINT;
}
/**
* Configures the scanner for the given document
* and the partition constraint for default partitions
*
* @param document the document to scan
*/
public void configureDefaultPartitions(final IDocument document) {
assert (document != null);
this.document= document;
this.partitionConstraint= getDefaultPartitionConstraint();
}
/**
* Configures the scanner for the given document
* and the given partition constraint
*
* @param document the document to scan
* @param partition the partition to scan in
*/
public void configure(final IDocument document, final PartitionConstraint partitionConstraint) {
assert (document != null && partitionConstraint != null);
this.document= document;
this.partitionConstraint= partitionConstraint;
}
// public void configure(IDocument document, int offset) throws BadLocationException {
// configure(document, TextUtilities.getContentType(
// document, partitioning, offset, false));
// }
/**
* Returns the most recent internal scan position.
*
* @return the most recent internal scan position.
*/
public int getPosition() {
return this.pos;
}
protected StopCondition createFindPeerStopCondition(final int start, final char[] pair, final char escapeChar) {
return (escapeChar == (char)0) ?
new CharacterMatchCondition(pair) : new ExtCharacterMatchCondition(pair, escapeChar);
}
protected int createForwardBound(final int start) throws BadLocationException {
return this.document.getLength();
}
protected int createBackwardBound(final int start) throws BadLocationException {
return -1;
}
@Override
public int findClosingPeer(final int start, final char[] pair) {
return findClosingPeer(start, pair, (char)0);
}
@Override
public int findClosingPeer(int start, final char[] pair, final char escapeChar) {
Assert.isNotNull(this.document);
Assert.isTrue(start >= 0);
try {
final StopCondition cond= createFindPeerStopCondition(start, pair, escapeChar);
final int bound= createForwardBound(start);
int depth= 1;
start-= 1;
while (true) {
start= scanForward(start + 1, bound, cond);
if (start == NOT_FOUND) {
return NOT_FOUND;
}
if (this.ch == pair[OPENING_PEER]) {
depth++;
} else {
depth--;
}
if (depth == 0) {
return start;
}
}
}
catch (final BadLocationException e) {
return NOT_FOUND;
}
}
@Override
public int findOpeningPeer(int start, final char[] pair) {
if (start >= this.document.getLength()) {
start= this.document.getLength()-1;
}
try {
final StopCondition cond= createFindPeerStopCondition(start, pair, (char)0);
final int bound= createBackwardBound(start);
int depth= 1;
start+= 1;
while (true) {
start= scanBackward(start - 1, bound, cond);
if (start == NOT_FOUND) {
return NOT_FOUND;
}
if (this.ch == pair[CLOSING_PEER]) {
depth++;
} else {
depth--;
}
if (depth == 0) {
return start;
}
}
}
catch (final BadLocationException e) {
return NOT_FOUND;
}
}
@Override
public int findOpeningPeer(int start, final char[] pair, final char escapeChar) {
Assert.isTrue(start < this.document.getLength());
if (escapeChar == (char)0) {
return findOpeningPeer(start, pair);
}
try {
final StopCondition cond= createFindPeerStopCondition(start, pair, escapeChar);
final int bound= createBackwardBound(start);
int depth= 1;
start+= 1;
this.line= this.document.getLineOfOffset(start);
while (true) {
final int[] list= preScanBackward(start - 1, bound, cond);
if (list == null) {
return NOT_FOUND;
}
for (int i= list.length-1; i >= 0; i--) {
start= list[i];
if (this.document.getChar(start) == pair[CLOSING_PEER]) {
depth++;
} else {
depth--;
}
if (depth == 0) {
return start;
}
}
start= this.document.getLineOffset(this.line+1);
}
}
catch (final BadLocationException e) {
return NOT_FOUND;
}
}
public int computePairBalance(int backwardOffset, final int backwardBound,
int forwardOffset, final int forwardBound, final int initial,
final char[] pair, final char escapeChar) {
int balance= 0;
final StopCondition condition= createFindPeerStopCondition(forwardBound, pair, escapeChar);
ITER_BACKWARD : while (--backwardOffset >= 0) {
backwardOffset= scanBackward(backwardOffset, backwardBound, condition);
if (backwardOffset != NOT_FOUND) {
if (this.ch == pair[OPENING_PEER]) {
balance++;
}
else {
balance--;
}
}
else {
break ITER_BACKWARD;
}
}
if (balance < 0) {
balance= 0;
}
balance+= initial;
ITER_FORWARD : while (forwardOffset < forwardBound) {
forwardOffset= scanForward(forwardOffset, forwardBound, condition);
if (forwardOffset != NOT_FOUND) {
if (this.ch == pair[OPENING_PEER]) {
balance++;
}
else {
balance--;
}
if (balance == 0) {
break ITER_FORWARD;
}
forwardOffset++;
}
else {
break ITER_FORWARD;
}
}
return balance;
}
/**
* Finds the smallest position in <code>document</code> such that the position is &gt;= <code>position</code>
* and &lt; <code>bound</code> and <code>Character.isWhitespace(document.getChar(pos))</code> evaluates to <code>false</code>.
*
* @param position the first character position in <code>document</code> to be considered
* @param bound the first position in <code>document</code> to not consider any more, with <code>bound</code> &gt; <code>position</code>, or <code>UNBOUND</code>
* @return the smallest position of a non-whitespace character in [<code>position</code>, <code>bound</code>), or <code>NOT_FOUND</code> if none can be found
*/
public final int findAnyNonBlankForward(final int position, final int bound, final boolean linebreakIsBlank) {
return scanForward(position, bound, linebreakIsBlank ?
getAnyNonWSorLRCondition() : getAnyNonWSCondition());
}
public final int findAnyNonBlankBackward(final int position, final int bound, final boolean linebreakIsBlank) {
return scanBackward(position-1, bound, linebreakIsBlank ?
getAnyNonWSorLRCondition() : getAnyNonWSCondition());
}
public final int findNonBlankForward(final int position, final int bound, final boolean linebreakIsBlank) {
return scanForward(position, bound, linebreakIsBlank ?
getNonWSorLRCondition() : getNonWSCondition());
}
public final int findNonBlankBackward(final int position, final int bound, final boolean linebreakIsBlank) {
return scanBackward(position-1, bound, linebreakIsBlank ?
getNonWSorLRCondition() : getNonWSCondition());
}
public @Nullable IRegion findBlankRegion(final int position, final boolean linebreakIsBlank) {
return findRegion(position, linebreakIsBlank ?
getAnyNonWSorLRCondition() : getAnyNonWSCondition());
}
public boolean isBlankLine(final int position) throws BadLocationException {
final IRegion line= this.document.getLineInformationOfOffset(position);
if (line.getLength() > 0) {
final int nonWhitespace= findAnyNonBlankForward(line.getOffset(), line.getOffset()+line.getLength(), false);
return (nonWhitespace == NOT_FOUND);
}
return true;
}
public final @Nullable IRegion findCommonWord(final int position) {
return findRegion(position, new StopCondition() {
@Override
public boolean stop() {
return (!Character.isLetterOrDigit(BasicHeuristicTokenScanner.this.ch));
}
});
}
public final int getFirstLineOfRegion(final IRegion region) throws BadLocationException {
return this.document.getLineOfOffset(region.getOffset());
}
public final int getLastLineOfRegion(final IRegion region) throws BadLocationException {
if (region.getLength() == 0) {
return this.document.getLineOfOffset(region.getOffset());
}
return this.document.getLineOfOffset(region.getOffset()+region.getLength()-1);
}
private final int @Nullable [] preScanBackward(final int start, final int bound, final StopCondition condition) throws BadLocationException {
final IntList list= new IntArrayList();
int scanEnd= start+1;
NEXT_LINE: while (list.isEmpty() && this.line >= 0) {
final int lineOffset= this.document.getLineOffset(this.line);
int next= lineOffset - 1;
while ((next= scanForward(next + 1, scanEnd, condition)) != NOT_FOUND) {
if (bound < next) {
list.add(next);
}
}
this.line--;
if (lineOffset <= bound) {
break NEXT_LINE;
}
scanEnd= lineOffset;
}
if (!list.isEmpty()) {
return list.toArray();
}
return null;
}
/**
* Finds the lowest position <code>p</code> in <code>document</code> such that <code>start</code> &lt;= p &lt;
* <code>bound</code> and <code>condition.stop(document.getChar(p), p)</code> evaluates to <code>true</code>.
*
* @param start the first character position in <code>document</code> to be considered
* @param bound the first position in <code>document</code> to not consider any more, with <code>bound</code> &gt; <code>start</code>, or <code>UNBOUND</code>
* @param condition the <code>StopCondition</code> to check
* @return the lowest position in [<code>start</code>, <code>bound</code>) for which <code>condition</code> holds, or <code>NOT_FOUND</code> if none can be found
*/
protected final int scanForward(final int start, int bound, final StopCondition condition) {
if (bound == UNBOUND) {
bound= this.document.getLength();
}
assert(bound <= this.document.getLength());
assert(start >= 0);
try {
this.pos= start;
while (this.pos < bound) {
this.ch= this.document.getChar(this.pos);
if (condition.stop()) {
return this.pos;
}
this.pos= condition.nextPositionForward();
}
this.pos= bound;
this.ch= (this.pos >= 0 && this.pos < this.document.getLength()) ? this.document.getChar(this.pos) : (char) -1;
}
catch (final BadLocationException e) {
}
return NOT_FOUND;
}
/**
* Finds the lowest position in <code>document</code> such that the position is &gt;= <code>position</code>
* and &lt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code>
* and the position is in the default partition.
*
* @param position the first character position in <code>document</code> to be considered
* @param bound the first position in <code>document</code> to not consider any more, with <code>bound</code> &gt; <code>position</code>, or <code>UNBOUND</code>
* @param ch the <code>char</code> to search for
* @return the lowest position of <code>ch</code> in (<code>bound</code>, <code>position</code>] that resides in a Java partition, or <code>NOT_FOUND</code> if none can be found
*/
public final int scanForward(final int position, final int bound, final char ch) {
return scanForward(position, bound, new SingleCharacterMatchCondition(ch));
}
/**
* Finds the lowest position in <code>document</code> such that the position is &gt;= <code>position</code>
* and &lt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code> for at least one
* ch in <code>chars</code> and the position is in the default partition.
*
* @param position the first character position in <code>document</code> to be considered
* @param bound the first position in <code>document</code> to not consider any more, with <code>bound</code> &gt; <code>position</code>, or <code>UNBOUND</code>
* @param chars an array of <code>char</code> to search for
* @return the lowest position of a non-whitespace character in [<code>position</code>, <code>bound</code>) that resides in a Java partition, or <code>NOT_FOUND</code> if none can be found
*/
public final int scanForward(final int position, final int bound, final char[] chars) {
return scanForward(position, bound, new CharacterMatchCondition(chars));
}
public final int scanForward(final int position, int bound, final String s, final int escapeChar) {
if (bound == UNBOUND) {
bound= this.document.getLength();
}
bound-= s.length();
return scanForward(position, bound, new StringMatchCondition(s, escapeChar));
}
public final int scanForward(final int position, int bound, final String[] s, final int escapeChar) {
if (bound == UNBOUND) {
bound= this.document.getLength();
}
bound-= s[0].length();
return scanForward(position, bound, new StringMatchCondition(s, escapeChar));
}
/**
* Finds the highest position <code>p</code> in <code>document</code> such that <code>bound</code> &lt; <code>p</code> &lt;= <code>start</code>
* and <code>condition.stop(document.getChar(p), p)</code> evaluates to <code>true</code>.
*
* @param start the first character position in <code>document</code> to be considered
* @param bound the first position in <code>document</code> to not consider any more, with <code>bound</code> &lt; <code>start</code>, or <code>UNBOUND</code>
* @param condition the <code>StopCondition</code> to check
* @return the highest position in (<code>bound</code>, <code>start</code> for which <code>condition</code> holds, or <code>NOT_FOUND</code> if none can be found
*/
protected final int scanBackward(final int start, int bound, final StopCondition condition) {
if (bound == UNBOUND) {
bound= -1;
}
assert(bound >= -1);
// assert(start == 0 || start < document.getLength() );
try {
if (this.document.getLength() > 0) {
this.pos= start;
while (this.pos > bound) {
this.ch= this.document.getChar(this.pos);
if (condition.stop()) {
return this.pos;
}
this.pos= condition.nextPositionBackward();
}
}
this.pos= bound;
this.ch= (this.pos >= 0 && this.pos < this.document.getLength()) ? this.document.getChar(this.pos) : (char) -1;
}
catch (final BadLocationException e) {
}
return NOT_FOUND;
}
/**
* Finds the highest position in <code>document</code> such that the position is &lt;= <code>position</code>
* and &gt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code> for at least one
* ch in <code>chars</code> and the position is in the default partition.
*
* @param position the first character position in <code>document</code> to be considered
* @param bound the first position in <code>document</code> to not consider any more, with <code>bound</code> &lt; <code>position</code>, or <code>UNBOUND</code>
* @param ch the <code>char</code> to search for
* @return the highest position of one element in <code>chars</code> in (<code>bound</code>, <code>position</code>] that resides in a Java partition, or <code>NOT_FOUND</code> if none can be found
*/
public final int scanBackward(final int position, final int bound, final char ch) {
return scanBackward(position, bound, new SingleCharacterMatchCondition(ch));
}
/**
* Finds the highest position in <code>document</code> such that the position is &lt;= <code>position</code>
* and &gt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code> for at least one
* ch in <code>chars</code> and the position is in the default partition.
*
* @param position the first character position in <code>document</code> to be considered
* @param bound the first position in <code>document</code> to not consider any more, with <code>bound</code> &lt; <code>position</code>, or <code>UNBOUND</code>
* @param chars an array of <code>char</code> to search for
* @return the highest position of one element in <code>chars</code> in (<code>bound</code>, <code>position</code>] that resides in a Java partition, or <code>NOT_FOUND</code> if none can be found
*/
public final int scanBackward(final int position, final int bound, final char[] chars) {
return scanBackward(position, bound, new CharacterMatchCondition(chars));
}
public final int count(int start, final int stop, final char c) {
int count= 0;
final SingleCharacterMatchCondition condition= new SingleCharacterMatchCondition(c);
while (start < stop && (start= scanForward(start, stop, condition)) != NOT_FOUND) {
count++;
start++;
}
return count;
}
protected final @Nullable IRegion findRegion(final int position, final StopCondition condition) {
return findRegion(position, condition, false);
}
protected final @Nullable IRegion findRegion(final int position, final StopCondition condition, final boolean allowClosing) {
int start= position;
int end= scanForward(position, UNBOUND, condition);
if (end == NOT_FOUND) {
end= this.pos;
}
if (allowClosing || end > position) {
start= scanBackward(--start, UNBOUND, condition);
if (start == NOT_FOUND) {
start= this.pos;
}
start++;
}
if (start < end) {
return new Region(start, end-start);
}
return null;
}
/**
* Returns the partition at <code>position</code>.
*
* @param position the position to get the partition for
* @return the content type at <code>position</code> or a dummy zero-length
* partition if accessing the document fails
*/
protected final @Nullable String getContentType() {
try {
return TextUtilities.getContentType(this.document, this.partitioning, this.pos, false);
}
catch (final BadLocationException e) {
return null; // ?
}
}
/**
* Returns the partition at current position of the scanner (#fPos).
*
* @param position the position to get the partition for
* @return the partition at <code>position</code> or a dummy zero-length
* partition if accessing the document fails
*/
protected final ITypedRegion getPartition() {
try {
return TextUtilities.getPartition(this.document, this.partitioning, this.pos, false);
}
catch (final BadLocationException e) {
return new TypedRegion(this.pos, 0, "__no_partition_at_all"); //$NON-NLS-1$
}
}
/**
* Returns the partition at <code>position</code>.
*
* @param position the position to get the partition for
* @return the partition at <code>position</code> or a dummy zero-length
* partition if accessing the document fails
*/
public final ITypedRegion getPartition(final int position) {
try {
return TextUtilities.getPartition(this.document, this.partitioning, position, false);
}
catch (final BadLocationException e) {
return new TypedRegion(this.pos, 0, "__no_partition_at_all"); //$NON-NLS-1$
}
}
}