package com.redhat.ceylon.eclipse.code.editor;
import static com.redhat.ceylon.eclipse.util.Nodes.getTokenIndexAtCharacter;
import java.util.List;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonToken;
import org.antlr.runtime.CommonTokenStream;
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;
import org.eclipse.jface.text.source.ICharacterPairMatcherExtension;
import com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer;
import com.redhat.ceylon.compiler.typechecker.util.NewlineFixingStringStream;
import com.redhat.ceylon.eclipse.util.Nodes;
public class CeylonCharacterPairMatcher
implements ICharacterPairMatcher,
ICharacterPairMatcherExtension {
int anchor = -1;
@Override
public void dispose() {}
@Override
public void clear() {
anchor = -1;
}
private int matching(int type) {
switch (type) {
case CeylonLexer.LPAREN:
return CeylonLexer.RPAREN;
case CeylonLexer.LBRACE:
return CeylonLexer.RBRACE;
case CeylonLexer.LBRACKET:
return CeylonLexer.RBRACKET;
case CeylonLexer.RPAREN:
return CeylonLexer.LPAREN;
case CeylonLexer.RBRACE:
return CeylonLexer.LBRACE;
case CeylonLexer.RBRACKET:
return CeylonLexer.LBRACKET;
}
return -1;
}
@Override
public IRegion match(IDocument document, int offset) {
List<CommonToken> tokens = getTokens(document);
int index = getTokenIndexAtCharacter(tokens, offset);
if (index<0) index=-index;
CommonToken token = tokens.get(index);
if (index>0 &&
matching(token.getType())<0 &&
offset==token.getStartIndex()) {
index--;
token = tokens.get(index);
}
return getRegion(tokens, index, token);
}
private List<CommonToken> getTokens(IDocument document) {
ANTLRStringStream stream =
new NewlineFixingStringStream(document.get());
CeylonLexer lexer = new CeylonLexer(stream);
CommonTokenStream ts = new CommonTokenStream(lexer);
ts.fill();
return ts.getTokens();
}
private IRegion getRegion(List<CommonToken> tokens, int index,
CommonToken token) {
int type = token.getType();
int matching = matching(type);
if (isOpening(type)) {
anchor = LEFT;
int count = 1;
for (int i=index+1; i<tokens.size(); i++) {
CommonToken t = tokens.get(i);
int tt = t.getType();
if (tt==type) {
count++;
}
if (tt==matching) {
count--;
if (count==0) {
return new Region(token.getStartIndex(),
t.getStopIndex()-token.getStartIndex()+1);
}
}
}
}
if (isClosing(type)) {
anchor = RIGHT;
int count = 1;
for (int i=index-1; i>=0; i--) {
CommonToken t = tokens.get(i);
int tt = t.getType();
if (tt==type) {
count++;
}
if (tt==matching) {
count--;
if (count==0) {
return new Region(t.getStartIndex(),
token.getStopIndex()-t.getStartIndex()+1);
}
}
}
}
return null;
}
private boolean isClosing(int type) {
return type==CeylonLexer.RPAREN ||
type==CeylonLexer.RBRACE ||
type==CeylonLexer.RBRACKET;
}
private boolean isOpening(int type) {
return type==CeylonLexer.LPAREN ||
type==CeylonLexer.LBRACE ||
type==CeylonLexer.LBRACKET;
}
@Override
public int getAnchor() {
return anchor;
}
@Override
public IRegion match(IDocument document, int offset, int length) {
return match(document,offset);
}
@Override
public IRegion findEnclosingPeerCharacters(IDocument document, int offset,
int length) {
List<CommonToken> tokens = getTokens(document);
int index = Nodes.getTokenIndexAtCharacter(tokens, offset);
if (index<0) index=-index;
int count = 1;
for (int i=index; i>=0; i--) {
CommonToken t = tokens.get(i);
int tt = t.getType();
if (isOpening(tt)) {
count--;
if (count==0) {
return getRegion(tokens, i, t);
}
}
if (isClosing(tt)) {
count++;
}
}
return null;
}
@Override
public boolean isMatchedChar(char ch) {
return ch=='('||ch=='{'||ch=='['||ch==')'||ch=='}'||ch==']';
}
@Override
public boolean isMatchedChar(char ch, IDocument document, int offset) {
return isMatchedChar(ch);
}
@Override
public boolean isRecomputationOfEnclosingPairRequired(IDocument document,
IRegion currentSelection, IRegion previousSelection) {
return currentSelection.getOffset()!=previousSelection.getOffset();
}
}