blob: ab286453f59b49f459558ccc0ab2d1e3b8d74df4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Christian Plesner Hansen (plesner@quenta.org) - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.docs.intent.client.ui.editor;
import java.util.HashSet;
import java.util.Set;
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.Region;
import org.eclipse.jface.text.source.ICharacterPairMatcher;
/**
* Helper class for match pairs of special characters. Based on
* org.eclipse.jface.text.source.DefaultCharacterPairMatcher, but without partitioning constraints: this allow
* the pair matcher being efficient even without a correct partitioning.
*
* @author <a href="mailto:william.piers@obeo.fr">William Piers</a>
*/
public class IntentPairMatcher implements ICharacterPairMatcher {
// CHECKSTYLE:OFF
/**
* Blocks used by aggregated the matcher.
*/
protected static final char[] BLOCKS = {'(', ')', '[', ']', '{', '}',
};
private int fAnchor = -1;
private final CharPairs fPairs;
/**
* Creates a new character pair matcher that matches the specified characters within the specified
* partitioning. The specified list of characters must have the form <blockquote>{ <i>start</i>,
* <i>end</i>, <i>start</i>, <i>end</i>, ..., <i>start</i>, <i>end</i> }</blockquote> For instance:
*
* <pre>
* char[] chars = new char[] {'(', ')', '{', '}', '[', ']'};
* new DefaultCharacterPairMatcher(chars, ...);
* </pre>
*/
public IntentPairMatcher() {
Assert.isLegal(BLOCKS.length % 2 == 0);
fPairs = new CharPairs(BLOCKS);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.jface.text.source.ICharacterPairMatcher#match(org.eclipse.jface.text.IDocument, int)
*/
public IRegion match(IDocument doc, int offset) {
if (doc == null || offset < 0 || offset > doc.getLength())
return null;
try {
return performMatch(doc, offset);
} catch (BadLocationException ble) {
return null;
}
}
/*
* Performs the actual work of matching for #match(IDocument, int).
*/
private IRegion performMatch(IDocument doc, int caretOffset) throws BadLocationException {
final int charOffset = caretOffset - 1;
final char prevChar = doc.getChar(Math.max(charOffset, 0));
if (!fPairs.contains(prevChar))
return null;
final boolean isForward = fPairs.isStartCharacter(prevChar);
fAnchor = isForward ? ICharacterPairMatcher.LEFT : ICharacterPairMatcher.RIGHT;
final int searchStartPosition = isForward ? caretOffset : caretOffset - 2;
final int adjustedOffset = isForward ? charOffset : caretOffset;
int endOffset = findMatchingPeer(doc, prevChar, fPairs.getMatching(prevChar), isForward,
isForward ? doc.getLength() : -1, searchStartPosition);
if (endOffset == -1)
return null;
final int adjustedEndOffset = isForward ? endOffset + 1 : endOffset;
if (adjustedEndOffset == adjustedOffset)
return null;
return new Region(Math.min(adjustedOffset, adjustedEndOffset), Math.abs(adjustedEndOffset
- adjustedOffset));
}
/**
* Searches <code>doc</code> for the specified end character, <code>end</code>.
*
* @param doc
* the document to search
* @param start
* the opening matching character
* @param end
* the end character to search for
* @param searchForward
* search forwards or backwards?
* @param boundary
* a boundary at which the search should stop
* @param startPos
* the start offset
* @return the index of the end character if it was found, otherwise -1
* @throws BadLocationException
* if the document is accessed with invalid offset or line
*/
private int findMatchingPeer(IDocument doc, char start, char end, boolean searchForward, int boundary,
int startPos) throws BadLocationException {
int pos = startPos;
while (pos != boundary) {
final char c = doc.getChar(pos);
if (doc.getChar(pos) == end) {
return pos;
} else if (c == start) {
pos = findMatchingPeer(doc, start, end, searchForward, boundary,
simpleIncrement(pos, searchForward));
if (pos == -1)
return -1;
}
pos = simpleIncrement(pos, searchForward);
}
return -1;
}
/* @see ICharacterPairMatcher#getAnchor() */
public int getAnchor() {
return fAnchor;
}
/* @see ICharacterPairMatcher#dispose() */
public void dispose() {
}
/* @see ICharacterPairMatcher#clear() */
public void clear() {
fAnchor = -1;
}
/**
* Utility class that encapsulates access to matching character pairs.
*/
private static class CharPairs {
private final char[] fPairs;
public CharPairs(char[] pairs) {
fPairs = pairs;
}
/**
* Returns true if the specified character pair occurs in one of the character pairs.
*
* @param c
* a character
* @return true exactly if the character occurs in one of the pairs
*/
public boolean contains(char c) {
return getAllCharacters().contains(Character.valueOf(c));
}
private Set<Character> fCharsCache = null;
/**
* @return A set containing all characters occurring in character pairs.
*/
private Set<Character> getAllCharacters() {
if (fCharsCache == null) {
Set<Character> set = new HashSet<Character>();
for (int i = 0; i < fPairs.length; i++)
set.add(Character.valueOf(fPairs[i]));
fCharsCache = set;
}
return fCharsCache;
}
/**
* Returns true if the specified character opens a character pair when scanning in the specified
* direction.
*
* @param c
* a character
* @param searchForward
* the direction of the search
* @return whether or not the character opens a character pair
*/
public boolean isOpeningCharacter(char c, boolean searchForward) {
for (int i = 0; i < fPairs.length; i += 2) {
if (searchForward && getStartChar(i) == c)
return true;
else if (!searchForward && getEndChar(i) == c)
return true;
}
return false;
}
/**
* Returns true of the specified character is a start character.
*
* @param c
* a character
* @return true exactly if the character is a start character
*/
public boolean isStartCharacter(char c) {
return this.isOpeningCharacter(c, true);
}
/**
* Returns the matching character for the specified character.
*
* @param c
* a character occurring in a character pair
* @return the matching character
*/
// CHECKSTYLE:OFF
public char getMatching(char c) {
for (int i = 0; i < fPairs.length; i += 2) {
if (getStartChar(i) == c) {
return getEndChar(i);
} else if (getEndChar(i) == c) {
return getStartChar(i);
}
}
Assert.isTrue(false);
return '\0';
}
private char getStartChar(int i) {
return fPairs[i];
}
private char getEndChar(int i) {
return fPairs[i + 1];
}
}
private int simpleIncrement(int pos, boolean searchForward) {
return pos + (searchForward ? 1 : -1);
}
// CHECKSTYLE:ON
}