/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.core.docutils;
import java.io.IOException;
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 com.aptana.shared_core.string.FastStringBuffer;
/**
* The reader works well as long as we are not inside a string at the current offset (this is not enforced here, so,
* use at your own risk).
*
* @author Fabio Zadrozny
*/
public class PythonCodeReader {
/** The EOF character */
public static final int EOF = -1;
private boolean fForward = false;
private IDocument fDocument;
private int fOffset;
private int fEnd = -1;
private boolean fOnlyInCurrentStmt;
private ParsingUtils fParsingUtils;
private FastStringBuffer wordBuffer = new FastStringBuffer();
private int wordBufferOffset = -1;
public PythonCodeReader() {
}
/**
* Returns the offset of the last read character. Should only be called after read has been called.
*/
public int getOffset() {
return fForward ? fOffset - 1 : fOffset;
}
public void configureForwardReader(IDocument document, int offset, int length, boolean skipComments,
boolean skipStrings, boolean onlyInCurrentStmt) throws IOException {
//currently not implemented without skip, so, that's the reason the asserts are here...
Assert.isTrue(skipComments);
Assert.isTrue(skipStrings);
fParsingUtils = ParsingUtils.create(document);
fOnlyInCurrentStmt = onlyInCurrentStmt;
fDocument = document;
fOffset = offset;
fForward = true;
fEnd = Math.min(fDocument.getLength(), fOffset + length);
}
public void configureBackwardReader(IDocument document, int offset, boolean skipComments, boolean skipStrings,
boolean onlyInCurrentStmt) throws IOException {
//currently not implemented without skip, so, that's the reason the asserts are here...
Assert.isTrue(skipComments);
Assert.isTrue(skipStrings);
fParsingUtils = ParsingUtils.create(document);
fOnlyInCurrentStmt = onlyInCurrentStmt;
fDocument = document;
fOffset = offset;
fForward = false;
}
/*
* @see Reader#close()
*/
public void close() throws IOException {
fDocument = null;
}
/*
* @see SingleCharReader#read()
*/
public int read() throws IOException {
try {
return fForward ? readForwards() : readBackwards();
} catch (BadLocationException x) {
return EOF; //Document may have changed...
}
}
private int readForwards() throws BadLocationException {
if (wordBufferOffset >= 0) {
if (wordBufferOffset < wordBuffer.length()) {
fOffset++;
return wordBuffer.charAt(wordBufferOffset++);
}
wordBuffer.clear();
wordBufferOffset = -1;
}
while (fOffset < fEnd) {
char current = fDocument.getChar(fOffset++);
switch (current) {
case '#':
fOffset = fParsingUtils.eatComments(null, fOffset);
return current;
case '"':
case '\'':
try {
fOffset = fParsingUtils.eatLiterals(null, fOffset - 1) + 1;
} catch (SyntaxErrorException e) {
return EOF;
}
//go on to the next loop (returns no char in this step)
continue;
}
if (fOnlyInCurrentStmt) {
if (Character.isJavaIdentifierPart(current)) {
wordBuffer.clear().append(current);
int offset = fOffset;
while (offset < fEnd) {
char c = fDocument.getChar(offset++);
if (Character.isJavaIdentifierPart(c)) {
wordBuffer.append(c);
} else {
break;
}
}
if (PySelection.STATEMENT_TOKENS.contains(wordBuffer.toString())) {
return EOF;
}
wordBufferOffset = 1; //We've just returned the one at pos == 0
return current;
}
}
return current;
}
return EOF;
}
private int readBackwards() throws BadLocationException {
if (wordBufferOffset >= 0) {
if (wordBufferOffset > 0) {
//Note that we already returned the one at pos 0
fOffset--;
return wordBuffer.charAt(--wordBufferOffset);
}
wordBuffer.clear();
wordBufferOffset = -1;
}
while (0 < fOffset) {
--fOffset;
handleComment();
if (fOffset < 0) {
return EOF;
}
char current = fDocument.getChar(fOffset);
switch (current) {
case '"':
case '\'':
try {
fOffset = fParsingUtils.eatLiteralsBackwards(null, fOffset);
} catch (SyntaxErrorException e) {
return EOF;
}
continue;
}
if (fOnlyInCurrentStmt) {
if (Character.isJavaIdentifierPart(current)) {
wordBuffer.clear();
int offset = fOffset;
while (offset >= 0) {
char c = fDocument.getChar(offset--);
if (Character.isJavaIdentifierPart(c)) {
wordBuffer.append(c);
} else {
break;
}
}
wordBuffer.reverse();
if (PySelection.STATEMENT_TOKENS.contains(wordBuffer.toString())) {
return EOF;
}
wordBufferOffset = wordBuffer.length() - 1;
return current;
}
}
return current;
}
return EOF;
}
//works as a cache so that we don't have to handle some line over and over again for comments
private int handledLine = -1;
private void handleComment() throws BadLocationException {
int lineOfOffset = fDocument.getLineOfOffset(fOffset);
if (handledLine == lineOfOffset) {
return;
}
handledLine = lineOfOffset;
String line = PySelection.getLine(fDocument, lineOfOffset);
int i;
int fromIndex = 0;
IRegion lineInformation = null;
//first we check for a comment possibility
while ((i = line.indexOf('#', fromIndex)) != -1) {
fromIndex = i + 1;
if (lineInformation == null) {
lineInformation = fDocument.getLineInformation(lineOfOffset);
}
int offset = lineInformation.getOffset() + i;
String contentType = ParsingUtils.getContentType(fDocument, offset + 1);
if (contentType.equals(ParsingUtils.PY_COMMENT)) {
//We need to check this because it may be that the # is found inside a string (which should be ignored)
if (offset < fOffset) {
fOffset = offset;
return;
}
}
}
}
}