blob: eae531114b19c934a0e556d2e422b93de2900d03 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2005, 2019 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 static org.eclipse.statet.ecommons.text.ITokenScanner.CLOSING_PEER;
import static org.eclipse.statet.ecommons.text.ITokenScanner.OPENING_PEER;
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.statet.ecommons.text.core.IPartitionConstraint;
import org.eclipse.statet.ecommons.text.core.sections.IDocContentSections;
/**
* Helper class for match pairs of characters.
*/
public class PairMatcher implements ICharPairMatcher {
private static final char IGNORE= '\n';
private static final byte NOTHING_FOUND= 0;
private static final byte OPENING_NOT_FOUND= 1;
private static final byte CLOSING_NOT_FOUND= 2;
private static final byte PAIR_FOUND= 3;
protected final char[][] pairs;
protected final String partitioning;
protected final String[] applicablePartitions;
protected final char escapeChar;
protected final ITokenScanner scanner;
protected int offset;
protected int beginPos;
protected int endPos;
protected int anchor;
protected String partition;
public PairMatcher(final char[][] pairs, final String partitioning,
final String[] partitions, final ITokenScanner scanner, final char escapeChar) {
this.pairs= pairs;
this.scanner= scanner;
this.partitioning= partitioning;
this.applicablePartitions= partitions;
this.escapeChar= escapeChar;
}
/**
* Constructor using <code>BasicHeuristicTokenScanner</code>.
*/
public PairMatcher(final char[][] pairs, final IDocContentSections documentContentInfo,
final String[] partitions, final char escapeChar) {
this(pairs, documentContentInfo.getPartitioning(), partitions,
new BasicHeuristicTokenScanner(documentContentInfo, new IPartitionConstraint() {
@Override
public boolean matches(final String partitionType) {
for (int i= 0; i < partitions.length; i++) {
if (partitions[i] == partitionType) {
return true;
}
}
return false;
}
}),
escapeChar );
}
/**
* @return Returns the pairs.
*/
public char[][] getPairs(final IDocument document, final int offset) {
return this.pairs;
}
@Override
public IRegion match(final IDocument document, final int offset) {
if (document == null || offset < 0) {
return null;
}
this.offset= offset;
if (matchPairsAt(document, true) == PAIR_FOUND) {
return new Region(this.beginPos, this.endPos - this.beginPos + 1);
}
else {
return null;
}
}
@Override
public IRegion match(final IDocument document, final int offset, final boolean auto) {
if (document == null || offset < 0) {
return null;
}
this.offset= offset;
switch (matchPairsAt(document, auto)) {
case OPENING_NOT_FOUND:
return new Region(this.endPos, -1);
case CLOSING_NOT_FOUND:
return new Region(this.beginPos, -1);
case PAIR_FOUND:
return new Region(this.beginPos, this.endPos - this.beginPos + 1);
default:
return null;
}
}
@Override
public int getAnchor() {
return this.anchor;
}
@Override
public void dispose() {
clear();
}
@Override
public void clear() {
}
/**
* Search Pairs
* @param document
* @param auto
*/
protected byte matchPairsAt(final IDocument document, final boolean auto) {
this.beginPos= -1;
this.endPos= -1;
// get the chars preceding and following the start position
try {
final ITypedRegion thisPartition= TextUtilities.getPartition(document, this.partitioning, this.offset, false);
final ITypedRegion prevPartition= (this.offset > 0) ? TextUtilities.getPartition(document, this.partitioning, this.offset-1, false) : null;
char thisChar= IGNORE;
char prevChar= IGNORE;
final int thisPart= checkPartition(thisPartition.getType());
if (thisPart >= 0 && this.offset < document.getLength()) {
thisChar= document.getChar(this.offset);
}
// check, if escaped
int prevPart= -1;
if (prevPartition != null) {
prevPart= checkPartition(prevPartition.getType());
if (auto && prevPart >= 0) {
prevChar= document.getChar(this.offset-1);
final int partitionOffset= prevPartition.getOffset();
int checkOffset= this.offset-2;
final char escapeChar= getEscapeChar(prevPartition.getType());
while (checkOffset >= partitionOffset) {
if (document.getChar(checkOffset) == escapeChar) {
checkOffset--;
}
else {
break;
}
}
if ( (this.offset - checkOffset) % 2 == 1) {
// prev char is escaped
prevChar= IGNORE;
}
else if (prevPart == thisPart && prevChar == escapeChar) {
// this char is escaped
thisChar= IGNORE;
}
}
}
final int pairIdx= findChar(prevChar, prevPart, thisChar, thisPart);
if (this.beginPos > -1) { // closing peer
this.anchor= LEFT;
this.scanner.configure(document, this.partition);
this.endPos= this.scanner.findClosingPeer(this.beginPos + 1, this.pairs[pairIdx], getEscapeChar(this.partition));
return (this.endPos > -1 && this.beginPos != this.endPos) ? PAIR_FOUND : CLOSING_NOT_FOUND;
}
else if (this.endPos > -1) { // opening peer
this.anchor= RIGHT;
this.scanner.configure(document, this.partition);
this.beginPos= this.scanner.findOpeningPeer(this.endPos - 1, this.pairs[pairIdx], getEscapeChar(this.partition));
return (this.beginPos > -1 && this.beginPos != this.endPos) ? PAIR_FOUND : OPENING_NOT_FOUND;
}
} catch (final BadLocationException x) {
} // ignore
return NOTHING_FOUND;
}
private int checkPartition(final String id) {
for (int i= 0; i < this.applicablePartitions.length; i++) {
if (this.applicablePartitions[i].equals(id)) {
return i;
}
}
return -1;
}
/**
* @param prevChar
* @param thisChar
* @return
*/
private int findChar(final char prevChar, final int prevPart, final char thisChar, final int thisPart) {
// search order 3{2 1}4
for (int i= 0; i < this.pairs.length; i++) {
if (thisChar == this.pairs[i][CLOSING_PEER]) {
this.endPos= this.offset;
this.partition= this.applicablePartitions[thisPart];
return i;
}
}
for (int i= 0; i < this.pairs.length; i++) {
if (prevChar == this.pairs[i][OPENING_PEER]) {
this.beginPos= this.offset-1;
this.partition= this.applicablePartitions[prevPart];
return i;
}
}
for (int i= 0; i < this.pairs.length; i++) {
if (thisChar == this.pairs[i][OPENING_PEER]) {
this.beginPos= this.offset;
this.partition= this.applicablePartitions[thisPart];
return i;
}
}
for (int i= 0; i < this.pairs.length; i++) {
if (prevChar == this.pairs[i][CLOSING_PEER]) {
this.endPos= this.offset-1;
this.partition= this.applicablePartitions[prevPart];
return i;
}
}
this.partition= null;
return -1;
}
protected char getEscapeChar(final String contentType) {
return this.escapeChar;
}
}