package com.redhat.ceylon.eclipse.code.editor;
import static com.redhat.ceylon.eclipse.util.Highlights.getColoring;
import static com.redhat.ceylon.eclipse.util.Highlights.getInterpolationColoring;
import static com.redhat.ceylon.eclipse.util.Highlights.getMemberColoring;
import static com.redhat.ceylon.eclipse.util.Nodes.getTokenIndexAtCharacter;
import java.util.Iterator;
import java.util.List;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonToken;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.presentation.IPresentationDamager;
import org.eclipse.jface.text.presentation.IPresentationRepairer;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Color;
import com.redhat.ceylon.compiler.typechecker.util.NewlineFixingStringStream;
import com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer;
import com.redhat.ceylon.compiler.typechecker.parser.CeylonParser;
class PresentationDamageRepairer implements IPresentationDamager,
IPresentationRepairer {
private volatile List<CommonToken> tokens;
private final CeylonEditor editor;
private IDocument document;
PresentationDamageRepairer(ISourceViewer sourceViewer,
CeylonEditor editor) {
this.editor = editor;
}
public IRegion getDamageRegion(ITypedRegion partition,
DocumentEvent event,
boolean documentPartitioningChanged) {
if (tokens==null) {
//parse and color the whole document the first time!
return partition;
}
if (editor!=null && editor.isInLinkedMode()) {
Position linkedPosition =
getLinkedPosition(event.getOffset(),
event.getLength());
if (linkedPosition == null) {
return partition;
}
else {
return new Region(linkedPosition.getOffset(),
linkedPosition.getLength());
}
}
if (noTextChange(event)) {
//it was a change to annotations - don't reparse
return new Region(event.getOffset(),
event.getLength());
}
Region tokenRegion =
getContainingTokenRegion(event);
if (tokenRegion == null) {
//the change is totally within a token,
//and doesn't break it, return the
//token extent
return partition;
}
else {
return tokenRegion;
}
}
private Region getContainingTokenRegion(DocumentEvent event) {
int tokenIndex =
getTokenIndexAtCharacter(tokens, event.getOffset()-1);
if (tokenIndex<0) tokenIndex=-tokenIndex;
CommonToken t = tokens.get(tokenIndex);
if (isWithinExistingToken(event, t)) {
if (isWithinTokenChange(event, t)) {
//the edit just changes the text inside
//a token, leaving the rest of the
//document structure unchanged
return new Region(event.getOffset(),
event.getText().length());
}
}
return null;
}
public boolean isWithinExistingToken(DocumentEvent event,
CommonToken t) {
int eventStart = event.getOffset();
int eventStop = event.getOffset()+event.getLength();
int tokenStart = t.getStartIndex();
int tokenStop = t.getStopIndex()+1;
switch (t.getType()) {
case CeylonLexer.MULTI_COMMENT:
return tokenStart<=eventStart-2 &&
tokenStop>=eventStop+2;
case CeylonLexer.VERBATIM_STRING:
case CeylonLexer.AVERBATIM_STRING:
return tokenStart<=eventStart-3 &&
tokenStop>=eventStop+3;
case CeylonLexer.CHAR_LITERAL:
case CeylonLexer.STRING_LITERAL:
case CeylonLexer.ASTRING_LITERAL:
case CeylonLexer.STRING_START:
case CeylonLexer.STRING_MID:
case CeylonLexer.STRING_END:
return tokenStart<=event.getOffset()-1 &&
tokenStop>=eventStop+1;
case CeylonLexer.LINE_COMMENT:
return tokenStart<=eventStart-2 &&
tokenStop>=eventStop+1; //account for case where we delete the newline
default:
return tokenStart<=eventStart &&
tokenStop>=eventStop;
}
}
public boolean isWithinTokenChange(DocumentEvent event,
CommonToken t) {
switch (t.getType()) {
case CeylonLexer.WS:
for (char c: event.getText().toCharArray()) {
if (!Character.isWhitespace(c)) {
return false;
}
}
break;
case CeylonLexer.UIDENTIFIER:
case CeylonLexer.LIDENTIFIER:
for (char c: event.getText().toCharArray()) {
if (!Character.isJavaIdentifierPart(c)) {
return false;
}
}
break;
case CeylonLexer.STRING_LITERAL:
case CeylonLexer.ASTRING_LITERAL:
case CeylonLexer.VERBATIM_STRING:
case CeylonLexer.AVERBATIM_STRING:
case CeylonLexer.STRING_START:
case CeylonLexer.STRING_MID:
case CeylonLexer.STRING_END:
for (char c: event.getText().toCharArray()) {
if (c=='"'||c=='`') {
return false;
}
}
break;
case CeylonLexer.CHAR_LITERAL:
for (char c: event.getText().toCharArray()) {
if (c=='\'') {
return false;
}
}
break;
case CeylonLexer.MULTI_COMMENT:
for (char c: event.getText().toCharArray()) {
if (c=='/'||c=='*') {
return false;
}
}
break;
case CeylonLexer.LINE_COMMENT:
for (char c: event.getText().toCharArray()) {
if (c=='\n'||c=='\f'||c=='\r') {
return false;
}
}
break;
default:
return false;
}
return true;
}
public void createPresentation(TextPresentation presentation,
ITypedRegion damage) {
ANTLRStringStream input =
new NewlineFixingStringStream(document.get());
CeylonLexer lexer =
new CeylonLexer(input);
CommonTokenStream tokenStream =
new CommonTokenStream(lexer);
CeylonParser parser =
new CeylonParser(tokenStream);
try {
parser.compilationUnit();
}
catch (RecognitionException e) {
throw new RuntimeException(e);
}
//it sounds strange, but it's better to parse
//and cache here than in getDamageRegion(),
//because these methods get called in strange
//orders
tokens = tokenStream.getTokens();
highlightTokens(presentation, damage);
}
private void highlightTokens(TextPresentation presentation,
ITypedRegion damage) {
//int prevStartOffset= -1;
//int prevEndOffset= -1;
boolean inMetaLiteral=false;
int inInterpolated=0;
boolean afterMemberOp = false;
//start iterating tokens
Iterator<CommonToken> iter = tokens.iterator();
if (iter!=null) {
while (iter.hasNext()) {
CommonToken token= iter.next();
int tt = token.getType();
if (tt==CeylonLexer.EOF) {
break;
}
switch (tt) {
case CeylonParser.BACKTICK:
inMetaLiteral = !inMetaLiteral;
break;
case CeylonParser.STRING_START:
inInterpolated++;
break;
case CeylonParser.STRING_END:
inInterpolated--;
break;
}
int startOffset= token.getStartIndex();
int endOffset= token.getStopIndex()+1;
if (endOffset<damage.getOffset()) continue;
if (startOffset>damage.getOffset()+damage.getLength()) break;
switch (tt) {
case CeylonParser.STRING_MID:
endOffset-=2; startOffset+=2;
break;
case CeylonParser.STRING_START:
endOffset-=2;
break;
case CeylonParser.STRING_END:
startOffset+=2;
break;
}
/*if (startOffset <= prevEndOffset &&
endOffset >= prevStartOffset) {
//this case occurs when applying a
//quick fix, and causes an error
//from SWT if we let it through
continue;
}*/
if (tt==CeylonParser.STRING_MID ||
tt==CeylonParser.STRING_END) {
changeTokenPresentation(presentation,
getInterpolationColoring(),
startOffset-2,startOffset-1,
inInterpolated>1 ? SWT.ITALIC : SWT.NORMAL);
}
changeTokenPresentation(presentation,
afterMemberOp && tt==CeylonLexer.LIDENTIFIER ?
getMemberColoring() : getColoring(token),
startOffset, endOffset,
inMetaLiteral || inInterpolated>1 ||
inInterpolated>0
&& tt!=CeylonParser.STRING_START
&& tt!=CeylonParser.STRING_MID
&& tt!=CeylonParser.STRING_END?
SWT.ITALIC : SWT.NORMAL);
if (tt==CeylonParser.STRING_MID ||
tt==CeylonParser.STRING_START) {
changeTokenPresentation(presentation,
getInterpolationColoring(),
endOffset+1,endOffset+2,
inInterpolated>1 ? SWT.ITALIC : SWT.NORMAL);
}
//prevStartOffset= startOffset;
//prevEndOffset= endOffset;
afterMemberOp = tt==CeylonLexer.MEMBER_OP ||
tt==CeylonLexer.SAFE_MEMBER_OP||
tt==CeylonLexer.SPREAD_OP;
}
}
}
private void changeTokenPresentation(TextPresentation presentation,
TextAttribute attribute, int startOffset, int endOffset,
int extraStyle) {
Color foreground = attribute==null ?
null : attribute.getForeground();
Color background = attribute==null ?
null : attribute.getBackground();
int fontStyle = attribute==null ?
extraStyle : attribute.getStyle()|extraStyle;
StyleRange styleRange = new StyleRange(startOffset,
endOffset-startOffset,
foreground, background, fontStyle);
presentation.addStyleRange(styleRange);
}
private Position getLinkedPosition(int offset, int length) {
LinkedModeModel linkedMode = editor.getLinkedMode();
if (linkedMode.anyPositionContains(offset) ||
linkedMode.anyPositionContains(offset+length)) {
try {
for (Position p:
document.getPositions(linkedMode.toString())) {
if (!p.isDeleted()) {
if (p.includes(offset) &&
p.includes(offset+length)) {
return p;
}
}
}
}
catch (BadPositionCategoryException e) {
e.printStackTrace();
}
}
return null;
}
private boolean noTextChange(DocumentEvent event) {
try {
return document.get(event.getOffset(), event.getLength())
.equals(event.getText());
}
catch (BadLocationException e) {
return false;
}
}
public void setDocument(IDocument document) {
this.document = document;
}
}