package org.python.pydev.editor;
import java.util.Queue;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.ITokenScanner;
import org.eclipse.jface.text.rules.Token;
import org.python.pydev.core.docutils.ParsingUtils;
import org.python.pydev.core.docutils.SyntaxErrorException;
import org.python.pydev.parser.fastparser.grammar_fstrings_common.FStringsAST;
import org.python.pydev.parser.fastparser.grammar_fstrings_common.SimpleNode;
import org.python.pydev.parser.grammar_fstrings.FStringsGrammar;
import org.python.pydev.parser.jython.FastCharStream;
import org.python.pydev.shared_core.partitioner.SubRuleToken;
import org.python.pydev.shared_core.string.TextSelectionUtils;
import org.python.pydev.shared_core.structure.LinkedListWarningOnSlowOperations;
import org.python.pydev.ui.ColorAndStyleCache;
public class PyFStringScanner implements ITokenScanner {
private Token fStringReturnToken;
private Token fStringExpressionReturnToken;
protected final ColorAndStyleCache colorCache;
public PyFStringScanner(ColorAndStyleCache colorCache) {
this.colorCache = colorCache;
updateColorAndStyle();
}
public void updateColorAndStyle() {
fStringReturnToken = new Token(colorCache.getUnicodeTextAttribute());
fStringExpressionReturnToken = new Token(colorCache.getStringTextAttribute());
}
/*
* @see ITokenScanner#setRange(IDocument, int, int)
*/
@Override
public void setRange(final IDocument document, final int offset, final int length) {
Assert.isLegal(document != null);
final int documentLength = document.getLength();
checkRange(offset, length, documentLength);
cachedSubTokens.clear();
cachedSubTokens.add(new SubRuleToken(fStringReturnToken, offset, length));
String str;
try {
str = document.get(offset, length);
} catch (BadLocationException e) {
throw new RuntimeException(e);
}
FStringInfo fstringInfo = extractFStringInfo(str);
String buf = fstringInfo.buf;
int startInternalStrOffset = fstringInfo.startInternalStrOffset;
int endInternalStrOffet = fstringInfo.endInternalStrOffet;
if (buf.length() < 0) {
return;
}
FastCharStream in = new FastCharStream(buf.toCharArray());
FStringsGrammar fStringsGrammar = new FStringsGrammar(in);
FStringsAST ast = null;
try {
ast = fStringsGrammar.f_string();
} catch (Throwable e) {
// Just ignore any errors for this.
}
if (ast != null && ast.hasChildren()) {
// ast.dump();
// We have children -- clear the one initially set and set the proper children.
cachedSubTokens.clear();
Document doc = new Document(buf.toString());
int currOffset = 0;
if (startInternalStrOffset != 0) {
cachedSubTokens.add(new SubRuleToken(fStringReturnToken,
offset, startInternalStrOffset));
}
for (SimpleNode node : ast.getFStringExpressions()) {
int startRelOffset = TextSelectionUtils.getAbsoluteCursorOffset(doc, node.beginLine - 1,
node.beginColumn - 1);
int endRelOffset = TextSelectionUtils.getAbsoluteCursorOffset(doc, node.endLine - 1, node.endColumn);
if (currOffset != startRelOffset) {
cachedSubTokens.add(new SubRuleToken(fStringReturnToken,
offset + startInternalStrOffset + currOffset, startRelOffset - currOffset));
}
cachedSubTokens.add(new SubRuleToken(fStringExpressionReturnToken,
offset + startInternalStrOffset + startRelOffset, endRelOffset - startRelOffset));
currOffset = endRelOffset;
}
if (currOffset != doc.getLength()) {
cachedSubTokens.add(new SubRuleToken(fStringReturnToken,
offset + startInternalStrOffset + currOffset, doc.getLength() - currOffset));
}
if (endInternalStrOffet != 0) {
cachedSubTokens.add(new SubRuleToken(fStringReturnToken,
offset + startInternalStrOffset + doc.getLength(), endInternalStrOffet));
}
}
}
public static class FStringInfo {
public final int startInternalStrOffset;
public final int endInternalStrOffet;
public final String buf;
public FStringInfo(int startInternalStrOffset, int endInternalStrOffet, String buf) {
this.startInternalStrOffset = startInternalStrOffset;
this.endInternalStrOffet = endInternalStrOffet;
this.buf = buf;
}
}
/**
* May return null;
*/
public static FStringInfo extractFStringInfo(String str) {
ParsingUtils pu = ParsingUtils.create(str);
int len = pu.len();
int startInternalStrOffset = 0;
int endInternalStrOffet = 0;
String buf = "";
for (; startInternalStrOffset < len; startInternalStrOffset++) {
char c = pu.charAt(startInternalStrOffset);
if (c == '\'' || c == '"') {
if (startInternalStrOffset > 2) {
//Something went wrong, this should be after f' or at most fr'
return null;
}
try {
int endPos = pu.getLiteralEnd(startInternalStrOffset, c);
boolean isMulti = pu.isMultiLiteral(startInternalStrOffset, c);
if (isMulti) {
startInternalStrOffset += 3;
boolean reachedEndBecauseOfEndOfString = endPos <= startInternalStrOffset;
if (!reachedEndBecauseOfEndOfString) {
for (int i = endPos - 2; i < endPos; i++) {
if (pu.charAt(i) != c) {
reachedEndBecauseOfEndOfString = true;
break;
}
}
}
if (reachedEndBecauseOfEndOfString) {
buf = str.substring(startInternalStrOffset, endPos);
endInternalStrOffet = 0;
} else {
buf = str.substring(startInternalStrOffset, endPos - 2);
endInternalStrOffet = 3;
}
} else {
startInternalStrOffset += 1;
boolean reachedEndBecauseOfEndOfString = endPos <= startInternalStrOffset || endPos >= pu.len();
buf = str.substring(startInternalStrOffset, endPos);
if (reachedEndBecauseOfEndOfString) {
endInternalStrOffet = 0;
} else {
endInternalStrOffet = 1;
}
}
break;
} catch (SyntaxErrorException e) {
return null; // Something went wrong...
}
}
}
return new FStringInfo(startInternalStrOffset, endInternalStrOffet, buf);
}
private void checkRange(int offset, int length, int documentLength) {
Assert.isLegal(offset > -1);
Assert.isLegal(length > -1);
Assert.isLegal(offset + length <= documentLength);
}
private final Queue<SubRuleToken> cachedSubTokens = new LinkedListWarningOnSlowOperations<SubRuleToken>();
private SubRuleToken curr = null;
private int fOffset;
private int fLen;
/*
* @see ITokenScanner#getTokenOffset()
*/
@Override
public int getTokenOffset() {
return this.fOffset;
}
/*
* @see ITokenScanner#getTokenLength()
*/
@Override
public int getTokenLength() {
return this.fLen;
}
@Override
public IToken nextToken() {
this.curr = cachedSubTokens.poll();
if (this.curr != null) {
this.fOffset = this.curr.offset;
this.fLen = this.curr.len;
return this.curr.token;
}
// Reached end
this.fOffset += fLen;
this.fLen = 0;
return Token.EOF;
}
}