/**
* Copyright (c) 2009, 2010 Mark Feber, MulgaSoft
*
* 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
*
*/
package com.mulgasoft.emacsplus;
import java.util.List;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
/**
* Ensure any matching bracket stays within the comment or string if necessary
* Also, invalidate character constants (using java syntax) as candidates for a match
*
* @author Mark Feber - initial API and implementation
*/
public class SexpCharacterPairMatcher extends HackDefaultCharacterPairMatcher {
private static char CQUOTE_CHAR = '\'';
private Position exPosition = null;
private List<Position> exPositions = null;
public SexpCharacterPairMatcher(char[] chars) {
super(chars);
}
protected synchronized IRegion performMatch(IDocument doc, int caretOffset) throws BadLocationException {
int charOffset = caretOffset - 1;
boolean forward = fPairs.isStartCharacter(doc.getChar(charOffset));
// simple documents (e.g. .txt) don't have type categories
if (EmacsPlusUtils.getTypeCategory(doc) != null) {
exPositions = EmacsPlusUtils.getExclusions(doc, EmacsPlusUtils.ALL_POS);
// remember category position if first character is in a comment or string
exPosition = EmacsPlusUtils.inPosition(doc, exPositions, charOffset);
}
IRegion reg = super.performMatch(doc, caretOffset);
// if we started in a category position, make sure we end in the same one
if (reg == null ||
(exPosition != null && !exPosition.includes(forward ? reg.getOffset() + reg.getLength() : reg.getOffset()))) {
return new Region(charOffset, 1);
}
return reg;
}
/**
* 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
*/
protected int findMatchingPeer(DocumentPartitionAccessor 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.isMatch(pos, end) && !isExcluded(doc.getDocument(),pos,false)) {
return pos;
} else if (c == start && doc.inPartition(pos) && !isExcluded(doc.getDocument(),pos,searchForward)) {
pos= findMatchingPeer(doc, start, end, searchForward, boundary,
doc.getNextPosition(pos, searchForward));
if (pos == -1) return -1;
}
pos= doc.getNextPosition(pos, searchForward);
}
return -1;
}
/**
* Don't match with bracket if it is a character constant
*
* @param doc
* @param offset
* @param initOffset
*
* @return true if character at offset is not just a character constant
*/
private boolean isExcluded(IDocument doc,int offset,boolean initOffset) {
// if we didn't start in a string or comment, exclude any in a string or comment
if (exPosition == null && exPositions != null && EmacsPlusUtils.inPosition(doc, exPositions, offset)!= null){
return true;
}
try {
// don't match bracket if it is a character constant
if (doc.getChar(offset-1) == CQUOTE_CHAR && doc.getChar(offset+ 1) == CQUOTE_CHAR){
return true;
}
} catch (BadLocationException e) {}
return false;
}
}