/** * 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.parser.fastparser; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.python.pydev.core.Tuple3; import org.python.pydev.core.docutils.ParsingUtils; import org.python.pydev.core.docutils.SyntaxErrorException; import org.python.pydev.core.log.Log; import com.aptana.shared_core.string.FastStringBuffer; /** * Class to help iterating through the document's indentation strings. * * It will yield Tuples with Strings (whitespaces/tabs), starting offset, boolean (true if line has more contents than the spaces/tabs) * * the indentations within literals, [, (, {, after \ are not considered * (only the ones actually considered indentations are yielded through). */ public class TabNannyDocIterator { //Mutable on iteration. private int offset; private Tuple3<String, Integer, Boolean> nextString; private boolean firstPass = true; //Final fields. private final ParsingUtils parsingUtils; private final FastStringBuffer tempBuf = new FastStringBuffer(); private final boolean yieldEmptyIndents; private final boolean yieldOnLinesWithoutContents; private final IDocument doc; public TabNannyDocIterator(IDocument doc) throws BadLocationException { this(doc, false, true); } public TabNannyDocIterator(IDocument doc, boolean yieldEmptyIndents, boolean yieldOnLinesWithoutContents) throws BadLocationException { parsingUtils = ParsingUtils.create(doc, true); this.doc = doc; this.yieldEmptyIndents = yieldEmptyIndents; this.yieldOnLinesWithoutContents = yieldOnLinesWithoutContents; buildNext(true); } public boolean hasNext() { return nextString != null; } /** * @return tuple with the indentation, the offset where it started and a boolean identifying if the line * has more than only whitespaces (i.e.: there are contents in the line). */ public Tuple3<String, Integer, Boolean> next() throws BadLocationException { if (!hasNext()) { throw new RuntimeException("Cannot iterate anymore."); } Tuple3<String, Integer, Boolean> ret = nextString; buildNext(false); return ret; } private void buildNext(boolean first) throws BadLocationException { while (!internalBuildNext()) { //just keep doing it... -- lot's of nothing ;-) } } private boolean internalBuildNext() throws BadLocationException { try { //System.out.println("buildNext"); char c = '\0'; int initial = -1; while (true) { //safeguard... it must walk a bit every time... if (initial == -1) { initial = offset; } else { if (initial == offset) { Log.log("Error: TabNannyDocIterator didn't walk.\n" + "Curr char:" + c + "\n" + "Curr char (as int):" + (int) c + "\n" + "Offset:" + offset + "\n" + "DocLen:" + doc.getLength() + "\n"); offset++; return true; } else { initial = offset; } } //keep in this loop until we finish the document or until we're able to find some indent string... if (offset >= doc.getLength()) { nextString = null; return true; } c = doc.getChar(offset); if (firstPass) { //that could happen if we have comments in the 1st line... firstPass = false; if ((c == ' ' || c == '\t')) { break; } else { if (yieldEmptyIndents) { nextString = new Tuple3<String, Integer, Boolean>(tempBuf.toString(), offset, c != '\r' && c != '\n'); if (!yieldOnLinesWithoutContents) { if (!nextString.o3) { return false; } } return true; } } } if (c == '#') { //comment (doesn't consider the escape char) offset = parsingUtils.eatComments(null, offset); } else if (c == '{' || c == '[' || c == '(') { //starting some call, dict, list, tuple... we're at the same indentation until it is finished try { offset = parsingUtils.eatPar(offset, null, c); } catch (SyntaxErrorException e) { //Ignore unbalanced parens. offset++; } } else if (c == '\r') { //line end (time for a break to see if we have some indentation just after it...) if (!continueAfterIncreaseOffset()) { return true; } c = doc.getChar(offset); if (c == '\n') { if (!continueAfterIncreaseOffset()) { return true; } } break; } else if (c == '\n') { //line end (time for a break to see if we have some indentation just after it...) if (!continueAfterIncreaseOffset()) { return true; } break; } else if (c == '\\') { //escape char found... if it's the last in the line, we don't have a break (we're still in the same line) boolean lastLineChar = false; if (!continueAfterIncreaseOffset()) { return true; } c = doc.getChar(offset); if (c == '\r') { if (!continueAfterIncreaseOffset()) { return true; } c = doc.getChar(offset); lastLineChar = true; } if (c == '\n') { if (!continueAfterIncreaseOffset()) { return true; } lastLineChar = true; } if (!lastLineChar) { break; } } else if (c == '\'' || c == '\"') { //literal found... skip to the end of the literal try { offset = parsingUtils.getLiteralEnd(offset, c) + 1; } catch (SyntaxErrorException e) { //Ignore unbalanced string offset++; } } else { // ok, a char is found... go to the end of the line and gather // the spaces to return if (!continueAfterIncreaseOffset()) { return true; } } } if (offset < doc.getLength()) { c = doc.getChar(offset); } else { nextString = null; return true; } //ok, if we got here, we're in a position to get the indentation string as spaces and tabs... tempBuf.clear(); int startingOffset = offset; while (c == ' ' || c == '\t') { tempBuf.append(c); offset++; if (offset >= doc.getLength()) { break; } c = doc.getChar(offset); } //true if we are in a line that has more contents than only the whitespaces/tabs nextString = new Tuple3<String, Integer, Boolean>(tempBuf.toString(), startingOffset, c != '\r' && c != '\n'); if (!yieldOnLinesWithoutContents) { if (!nextString.o3) { return false; } } //now, if we didn't have any indentation, try to make another build if (nextString.o1.length() == 0) { if (yieldEmptyIndents) { return true; } return false; } } catch (RuntimeException e) { if (e.getCause() instanceof BadLocationException) { throw (BadLocationException) e.getCause(); } throw e; } return true; } /** * Increase the offset and see whether we should continue iterating in the document after that... * @return true if we should continue iterating and false otherwise. */ private boolean continueAfterIncreaseOffset() { offset++; boolean ret = true; if (offset >= doc.getLength()) { nextString = null; ret = false; } return ret; } public void remove() { throw new RuntimeException("Not implemented"); } }