| /*=============================================================================# |
| # 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; |
| } |
| |
| } |