/****************************************************************************** * Copyright (C) 2013 Fabio Zadrozny * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Fabio Zadrozny <fabiofz@gmail.com> - initial API and implementation ******************************************************************************/ package org.python.pydev.shared_core.partitioner; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentPartitionerExtension2; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.TypedPosition; import org.eclipse.jface.text.rules.ICharacterScanner; import org.python.pydev.shared_core.log.Log; /** * A reader that'll only read based on a given partition type. * * @author Fabio Zadrozny */ public class PartitionCodeReader implements ICharacterScanner, IMarkScanner { /** * Note: not suitable for sub-partitions. */ public static final String ALL_CONTENT_TYPES_AVAILABLE = "ALL_CONTENT_TYPES_AVAILABLE"; /** The EOF character */ public static final int EOF = -1; private boolean fForward = false; private IDocument fDocument; private int fOffset; private int fEnd = -1; private final String contentType; private int fcurrentPositionI = 0; private Position fCurrentPosition = null; private Position[] fPositions; private char[][] fDelimiters; private int fStartForwardOffset; private boolean fSupportKeepPositions; public PartitionCodeReader(String contentType) { this.contentType = contentType; } /** * 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 + 1; } public void configureForwardReaderKeepingPositions(int offset, int end) throws IOException, BadPositionCategoryException { if (!fSupportKeepPositions) { throw new AssertionError("configureForwardReader must be called with supportKeepPositions=true."); } fOffset = offset; fStartForwardOffset = offset; fForward = true; fEnd = Math.min(fDocument.getLength(), end); fcurrentPositionI = 0; if (fPositions.length > 0) { fCurrentPosition = fPositions[0]; } else { fCurrentPosition = null; } } public void configureForwardReader(IDocument document, int offset, int end) throws IOException, BadPositionCategoryException { configureForwardReader(document, offset, end, false); } public void configureForwardReader(IDocument document, int offset, int end, boolean supportKeepPositions) throws IOException, BadPositionCategoryException { fSupportKeepPositions = supportKeepPositions; fDocument = document; fOffset = offset; fStartForwardOffset = offset; fForward = true; fEnd = Math.min(fDocument.getLength(), end); fcurrentPositionI = 0; fPositions = createPositions(document); if (fPositions.length > 0) { fCurrentPosition = fPositions[0]; } else { fCurrentPosition = null; } } public void configureBackwardReader(IDocument document, int offset) throws IOException, BadPositionCategoryException { fStartForwardOffset = 0; fDocument = document; fOffset = offset; fForward = false; fcurrentPositionI = 0; fPositions = createPositions(document); if (fPositions.length > 0) { fCurrentPosition = fPositions[0]; } else { fCurrentPosition = null; } } private Position[] createPositions(IDocument document) throws BadPositionCategoryException { Position[] positions = getDocumentTypedPositions(document, contentType); List<TypedPosition> typedPositions = PartitionMerger.sortAndMergePositions(positions, document.getLength()); int size = typedPositions.size(); List<Position> list = new ArrayList<Position>(size); for (int i = 0; i < size; i++) { Position position = typedPositions.get(i); if (isPositionValid(position, contentType)) { list.add(position); } } if (!fForward) { Collections.reverse(list); } Position[] ret = list.toArray(new Position[list.size()]); return ret; } private boolean isPositionValid(Position position, String contentType) { if (fSupportKeepPositions || (fForward && position.getOffset() + position.getLength() >= fOffset || !fForward && position.getOffset() <= fOffset)) { if (position instanceof TypedPosition) { TypedPosition typedPosition = (TypedPosition) position; if (contentType != null && !contentType.equals(ALL_CONTENT_TYPES_AVAILABLE)) { if (!contentType.equals(typedPosition.getType())) { return false; } } } return true; } return false; } /** * Note: this just gets the positions in the document. To cover for holes, use * StringUtils.sortAndMergePositions with the result of this call. */ public static Position[] getDocumentTypedPositions(IDocument document, String defaultContentType) { if (ALL_CONTENT_TYPES_AVAILABLE.equals(defaultContentType)) { //Consider the whole document return new Position[] { new TypedPosition(0, document.getLength(), defaultContentType) }; } Position[] positions; try { IDocumentPartitionerExtension2 partitioner = (IDocumentPartitionerExtension2) document .getDocumentPartitioner(); String[] managingPositionCategories = partitioner.getManagingPositionCategories(); Assert.isTrue(managingPositionCategories.length == 1); positions = document.getPositions(managingPositionCategories[0]); if (positions == null) { positions = new Position[] { new TypedPosition(0, document.getLength(), defaultContentType) }; } } catch (Exception e) { Log.log("Unable to get positions for: " + defaultContentType, e); //Shouldn't happen, but if it does, consider the whole doc. positions = new Position[] { new TypedPosition(0, document.getLength(), defaultContentType) }; } return positions; } /* * @see Reader#close() */ public void close() throws IOException { fDocument = null; } /* * @see SingleCharReader#read() */ @Override public int read() { try { return fForward ? readForwards() : readBackwards(); } catch (BadLocationException x) { return EOF; //Document may have changed... } } @Override public char[][] getLegalLineDelimiters() { if (fDelimiters == null) { String[] delimiters = fDocument.getLegalLineDelimiters(); fDelimiters = new char[delimiters.length][]; for (int i = 0; i < delimiters.length; i++) { fDelimiters[i] = delimiters[i].toCharArray(); } } return fDelimiters; } @Override public int getColumn() { try { final int offset = getOffset(); final int line = fDocument.getLineOfOffset(offset); final int start = fDocument.getLineOffset(line); return offset - start; } catch (BadLocationException e) { } return -1; } @Override public void unread() { if (fForward) { if (fCurrentPosition == null) { //unread EOF if (fPositions.length > 0) { fcurrentPositionI = fPositions.length - 1; fCurrentPosition = fPositions[fcurrentPositionI]; fOffset = fCurrentPosition.offset + fCurrentPosition.length; if (fOffset < fStartForwardOffset) { fOffset = fStartForwardOffset; } } return; } fOffset--; if (fOffset < fStartForwardOffset) { fOffset = fStartForwardOffset; return; } while (true) { if (fOffset < fCurrentPosition.offset) { if (fcurrentPositionI > 0) { fcurrentPositionI--; fCurrentPosition = fPositions[fcurrentPositionI]; if (fCurrentPosition.offset + fCurrentPosition.length <= fOffset) { fOffset = fCurrentPosition.offset + fCurrentPosition.length - 1; if (fOffset < fStartForwardOffset) { fOffset = fStartForwardOffset; } return; } } else { return; } } else { break; } } } else { throw new AssertionError("Unread when going backward not supported yet"); } } private int readForwards() throws BadLocationException { while (true) { if (fCurrentPosition == null) { return EOF; } if (fCurrentPosition.offset + fCurrentPosition.length <= fOffset) { fcurrentPositionI++; if (fcurrentPositionI >= fPositions.length) { fCurrentPosition = null; return EOF; } fCurrentPosition = fPositions[fcurrentPositionI]; } else { break; } } if (fCurrentPosition.offset > fOffset) { fOffset = fCurrentPosition.offset; } if (fOffset < fEnd) { char current = fDocument.getChar(fOffset); fOffset++; return current; } else { fCurrentPosition = null; } return EOF; } private int readBackwards() throws BadLocationException { while (true) { if (fCurrentPosition == null) { return EOF; } if (fCurrentPosition.offset > fOffset) { fcurrentPositionI++; if (fcurrentPositionI >= fPositions.length) { return EOF; } fCurrentPosition = fPositions[fcurrentPositionI]; } else { break; } } if (fCurrentPosition.offset + fCurrentPosition.length <= fOffset) { fOffset = fCurrentPosition.offset + fCurrentPosition.length - 1; } if (fOffset >= 0) { char current = fDocument.getChar(fOffset); fOffset--; return current; } return EOF; } @Override public int getMark() { return fOffset; } @Override public void setMark(int offset) { if (fForward) { fCurrentPosition = null; for (int i = 0; i < fPositions.length; i++) { Position p = fPositions[i]; if (p.offset + p.length > offset) { fcurrentPositionI = i; fCurrentPosition = p; fOffset = offset; break; } } } else { fCurrentPosition = null; for (int i = 0; i < fPositions.length; i++) { //note: it's already backwards Position p = fPositions[i]; if (p.offset < offset) { fcurrentPositionI = i; fCurrentPosition = p; fOffset = offset; break; } } } } }