/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package net.sourceforge.phpeclipse.phpeditor.php;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.text.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.IDocumentPartitionerExtension;
import org.eclipse.jface.text.IDocumentPartitionerExtension2;
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.TypedPosition;
import org.eclipse.jface.text.TypedRegion;
import org.eclipse.jface.text.rules.IPartitionTokenScanner;
import org.eclipse.jface.text.rules.IToken;
/**
* A standard implementation of a document partitioner. It uses a partition token scanner to scan the document and to determine the
* document's partitioning. The tokens returned by the scanner are supposed to return the partition type as their data. The
* partitioner remembers the document's partitions in the document itself rather than maintaining its own data structure.
*
* @see IPartitionTokenScanner
* @since 2.0
*/
public class DefaultPHPPartitioner_delete_it implements IDocumentPartitioner, IDocumentPartitionerExtension, IDocumentPartitionerExtension2 {
/**
* The position category this partitioner uses to store the document's partitioning information.
*
* @deprecated As of 3.0, use getManagingPositionCategories()
instead.
*/
public final static String CONTENT_TYPES_CATEGORY = "__content_types_category"; //$NON-NLS-1$
/** The HTML areas partitioner's scanner */
protected IPartitionTokenScanner fHTMLScanner;
/** The PHP areas partitioner's scanner */
protected IPartitionTokenScanner fPHPScanner;
/** The legal content types of both partitioners */
protected String[] fLegalContentTypes;
/** The legal content types of the HTML partitioner */
protected String[] fLegalHTMLContentTypes;
/** The legal content types of the PHP partitioner */
protected String[] fLegalPHPContentTypes;
/** The partitioner's document */
protected IDocument fDocument;
/** The document length before a document change occurred */
protected int fPreviousDocumentLength;
/** The position updater used to for the default updating of partitions */
protected DefaultPositionUpdater fPositionUpdater;
/** The offset at which the first changed partition starts */
protected int fStartOffset;
/** The offset at which the last changed partition ends */
protected int fEndOffset;
/** The offset at which a partition has been deleted */
protected int fDeleteOffset;
/**
* The position category this partitioner uses to store the document's partitioning information.
*
* @since 3.0
*/
private String fPositionCategory;
/**
* Creates a new partitioner that uses the given scanner and may return partitions of the given legal content types.
*
* @param scanner
* the scanner this partitioner is supposed to use
* @param legalContentTypes
* the legal content types of this partitioner
*/
public DefaultPHPPartitioner_delete_it(IPartitionTokenScanner htmlScanner, IPartitionTokenScanner phpScanner, String[] legalContentTypes,
String[] legalHTMLContentTypes, String[] legalPHPContentTypes) {
fHTMLScanner = htmlScanner;
fPHPScanner = phpScanner;
fLegalContentTypes = legalContentTypes;
fLegalHTMLContentTypes = legalHTMLContentTypes;
fLegalPHPContentTypes = legalPHPContentTypes;
fPositionCategory = CONTENT_TYPES_CATEGORY + hashCode();
fPositionUpdater = new DefaultPositionUpdater(fPositionCategory);
}
/*
* @see org.eclipse.jface.text.IDocumentPartitionerExtension2#getManagingPositionCategories()
* @since 3.0
*/
public String[] getManagingPositionCategories() {
return new String[] { fPositionCategory };
}
/*
* @see IDocumentPartitioner#connect(IDocument)
*/
public void connect(IDocument document) {
Assert.isNotNull(document);
Assert.isTrue(!document.containsPositionCategory(fPositionCategory));
fDocument = document;
fDocument.addPositionCategory(fPositionCategory);
initialize(fDocument.getLength());
}
/**
* Performs the initial partitioning of the partitioner's document.
*/
protected void initialize(int initialLength) {
char ch;
boolean htmlMode = true;
int startPosition = 0;
int length = 0;
int i = 0;
try {
while (i < initialLength) {
ch = fDocument.getChar(i++);
if (htmlMode) {
if (ch == '<' && fDocument.getChar(i) == '?' && fDocument.getChar(i + 1) == ' ') {
length = i - startPosition - 1;
if (length != 0) {
initializeHTML(startPosition, length);
startPosition = i - 1;
}
htmlMode = false;
} else if (ch == '<' && fDocument.getChar(i) == '?'
&& (fDocument.getChar(i + 1) == 'p' || fDocument.getChar(i + 1) == 'P')
&& (fDocument.getChar(i + 2) == 'h' || fDocument.getChar(i + 2) == 'H')
&& (fDocument.getChar(i + 3) == 'p' || fDocument.getChar(i + 3) == 'P')) {
length = i - startPosition - 1;
if (length != 0) {
initializeHTML(startPosition, length);
startPosition = i - 1;
}
htmlMode = false;
}
} else {
switch (ch) {
case '"': // double quoted string
// read until end of double quoted string
while (i < fDocument.getLength()) {
if (fDocument.getChar(i++) == '"') {
if (fDocument.getChar(i - 2) != '\\') {
break;
}
}
}
break;
case '\'': // single quoted string
// read until end of single quoted string
while (i < fDocument.getLength()) {
if (fDocument.getChar(i++) == '\'') {
if (fDocument.getChar(i - 2) != '\\') {
break;
}
}
}
break;
case '/': // line comment
if (fDocument.getChar(i) == '/') {
i++;
// read until end of line
while (i < fDocument.getLength()) {
if (fDocument.getChar(i++) == '\n') {
break;
}
}
} else if (fDocument.getChar(i) == '*') {
i++;
// read until end of comment
while (i < fDocument.getLength()) {
if (fDocument.getChar(i++) == '*') {
if (i < fDocument.getLength()) {
if (fDocument.getChar(i) == '/') {
break;
}
}
}
}
}
break;
case '#': // line comment
// read until end of line
while (i < fDocument.getLength()) {
if (fDocument.getChar(i++) == '\n') {
break;
}
}
break;
case '?':
if (fDocument.getChar(i) == '>') {
length = i - startPosition + 1;
if (length != 0) {
initializePHP(startPosition, length);
startPosition = i + 1;
}
htmlMode = true;
}
break;
}
}
}
} catch (BadLocationException x) {
// can happen but ignored
} finally {
if (startPositionoffset is smaller than the
* remembered offset, offset
will from now on be remembered. If offset + length
is greater than the
* remembered end offset, it will be remembered from now on.
*
* @param offset
* the offset
* @param length
* the length
*/
private void rememberRegion(int offset, int length) {
// remember start offset
if (fStartOffset == -1)
fStartOffset = offset;
else if (offset < fStartOffset)
fStartOffset = offset;
// remember end offset
int endOffset = offset + length;
if (fEndOffset == -1)
fEndOffset = endOffset;
else if (endOffset > fEndOffset)
fEndOffset = endOffset;
}
/**
* Remembers the given offset as the deletion offset.
*
* @param offset
* the offset
*/
private void rememberDeletedOffset(int offset) {
fDeleteOffset = offset;
}
/**
* Creates the minimal region containing all partition changes using the remembered offset, end offset, and deletion offset.
*
* @return the minimal region containing all the partition changes
*/
private IRegion createRegion() {
if (fDeleteOffset == -1) {
if (fStartOffset == -1 || fEndOffset == -1)
return null;
return new Region(fStartOffset, fEndOffset - fStartOffset);
} else if (fStartOffset == -1 || fEndOffset == -1) {
return new Region(fDeleteOffset, 0);
} else {
int offset = Math.min(fDeleteOffset, fStartOffset);
int endOffset = Math.max(fDeleteOffset, fEndOffset);
return new Region(offset, endOffset - offset);
}
}
/*
* @see IDocumentPartitionerExtension#documentChanged2(DocumentEvent)
* @since 2.0
*/
public IRegion documentChanged2(DocumentEvent e) {
try {
IDocument d = e.getDocument();
Position[] category = d.getPositions(fPositionCategory);
IRegion line = d.getLineInformationOfOffset(e.getOffset());
int reparseStart = line.getOffset();
int partitionStart = -1;
String contentType = null;
int newLength = e.getText() == null ? 0 : e.getText().length();
int first = d.computeIndexInCategory(fPositionCategory, reparseStart);
if (first > 0) {
TypedPosition partition = (TypedPosition) category[first - 1];
if (partition.includes(reparseStart)) {
partitionStart = partition.getOffset();
contentType = partition.getType();
if (e.getOffset() == partition.getOffset() + partition.getLength())
reparseStart = partitionStart;
--first;
} else if (reparseStart == e.getOffset() && reparseStart == partition.getOffset() + partition.getLength()) {
partitionStart = partition.getOffset();
contentType = partition.getType();
reparseStart = partitionStart;
--first;
} else {
partitionStart = partition.getOffset() + partition.getLength();
contentType = IDocument.DEFAULT_CONTENT_TYPE;
}
}
fPositionUpdater.update(e);
for (int i = first; i < category.length; i++) {
Position p = category[i];
if (p.isDeleted) {
rememberDeletedOffset(e.getOffset());
break;
}
}
category = d.getPositions(fPositionCategory);
fHTMLScanner.setPartialRange(d, reparseStart, d.getLength() - reparseStart, contentType, partitionStart);
int lastScannedPosition = reparseStart;
IToken token = fHTMLScanner.nextToken();
while (!token.isEOF()) {
contentType = getTokenContentType(token);
if (!isSupportedContentType(contentType)) {
token = fHTMLScanner.nextToken();
continue;
}
int start = fHTMLScanner.getTokenOffset();
int length = fHTMLScanner.getTokenLength();
lastScannedPosition = start + length - 1;
// remove all affected positions
while (first < category.length) {
TypedPosition p = (TypedPosition) category[first];
if (lastScannedPosition >= p.offset + p.length
|| (p.overlapsWith(start, length) && (!d.containsPosition(fPositionCategory, start, length) || !contentType.equals(p
.getType())))) {
rememberRegion(p.offset, p.length);
d.removePosition(fPositionCategory, p);
++first;
} else
break;
}
// if position already exists and we have scanned at least the
// area covered by the event, we are done
if (d.containsPosition(fPositionCategory, start, length)) {
if (lastScannedPosition >= e.getOffset() + newLength)
return createRegion();
++first;
} else {
// insert the new type position
try {
d.addPosition(fPositionCategory, new TypedPosition(start, length, contentType));
rememberRegion(start, length);
} catch (BadPositionCategoryException x) {
} catch (BadLocationException x) {
}
}
token = fHTMLScanner.nextToken();
}
// remove all positions behind lastScannedPosition since there aren't any further types
if (lastScannedPosition != reparseStart) {
// if this condition is not met, nothing has been scanned because of a deletion
++lastScannedPosition;
}
first = d.computeIndexInCategory(fPositionCategory, lastScannedPosition);
TypedPosition p;
while (first < category.length) {
p = (TypedPosition) category[first++];
d.removePosition(fPositionCategory, p);
rememberRegion(p.offset, p.length);
}
} catch (BadPositionCategoryException x) {
// should never happen on connected documents
} catch (BadLocationException x) {
}
return createRegion();
}
/**
* Returns the position in the partitoner's position category which is close to the given offset. This is, the position has either
* an offset which is the same as the given offset or an offset which is smaller than the given offset. This method profits from
* the knowledge that a partitioning is a ordered set of disjoint position.
*
* @param offset
* the offset for which to search the closest position
* @return the closest position in the partitioner's category
*/
protected TypedPosition findClosestPosition(int offset) {
try {
int index = fDocument.computeIndexInCategory(fPositionCategory, offset);
Position[] category = fDocument.getPositions(fPositionCategory);
if (category.length == 0)
return null;
if (index < category.length) {
if (offset == category[index].offset)
return (TypedPosition) category[index];
}
if (index > 0)
index--;
return (TypedPosition) category[index];
} catch (BadPositionCategoryException x) {
} catch (BadLocationException x) {
}
return null;
}
/*
* @see IDocumentPartitioner#getContentType(int)
*/
public String getContentType(int offset) {
TypedPosition p = findClosestPosition(offset);
if (p != null && p.includes(offset))
return p.getType();
return IDocument.DEFAULT_CONTENT_TYPE;
}
/*
* @see IDocumentPartitioner#getPartition(int)
*/
public ITypedRegion getPartition(int offset) {
try {
Position[] category = fDocument.getPositions(fPositionCategory);
if (category == null || category.length == 0)
return new TypedRegion(0, fDocument.getLength(), IDocument.DEFAULT_CONTENT_TYPE);
int index = fDocument.computeIndexInCategory(fPositionCategory, offset);
if (index < category.length) {
TypedPosition next = (TypedPosition) category[index];
if (offset == next.offset)
return new TypedRegion(next.getOffset(), next.getLength(), next.getType());
if (index == 0)
return new TypedRegion(0, next.offset, IDocument.DEFAULT_CONTENT_TYPE);
TypedPosition previous = (TypedPosition) category[index - 1];
if (previous.includes(offset))
return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType());
int endOffset = previous.getOffset() + previous.getLength();
return new TypedRegion(endOffset, next.getOffset() - endOffset, IDocument.DEFAULT_CONTENT_TYPE);
}
TypedPosition previous = (TypedPosition) category[category.length - 1];
if (previous.includes(offset))
return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType());
int endOffset = previous.getOffset() + previous.getLength();
return new TypedRegion(endOffset, fDocument.getLength() - endOffset, IDocument.DEFAULT_CONTENT_TYPE);
} catch (BadPositionCategoryException x) {
} catch (BadLocationException x) {
}
return new TypedRegion(0, fDocument.getLength(), IDocument.DEFAULT_CONTENT_TYPE);
}
/*
* @see IDocumentPartitioner#computePartitioning(int, int)
*/
public ITypedRegion[] computePartitioning(int offset, int length) {
return computePartitioning(offset, length, false);
}
/*
* @see IDocumentPartitioner#getLegalContentTypes()
*/
public String[] getLegalContentTypes() {
return fLegalContentTypes;
}
public String[] getLegalHTMLContentTypes() {
return fLegalHTMLContentTypes;
}
public String[] getLegalPHPContentTypes() {
return fLegalPHPContentTypes;
}
/**
* Returns whether the given type is one of the legal content types.
*
* @param contentType
* the content type to check
* @return true
if the content type is a legal content type
*/
protected boolean isSupportedContentType(String contentType) {
if (contentType != null) {
for (int i = 0; i < fLegalContentTypes.length; i++) {
if (fLegalHTMLContentTypes[i].equals(contentType))
return true;
}
}
return false;
}
/**
* Returns whether the given type is one of the legal content types.
*
* @param contentType
* the content type to check
* @return true
if the content type is a legal content type
*/
protected boolean isSupportedHTMLContentType(String contentType) {
if (contentType != null) {
for (int i = 0; i < fLegalHTMLContentTypes.length; i++) {
if (fLegalHTMLContentTypes[i].equals(contentType))
return true;
}
}
return false;
}
/**
* Returns whether the given type is one of the legal content types.
*
* @param contentType
* the content type to check
* @return true
if the content type is a legal content type
*/
protected boolean isSupportedPHPContentType(String contentType) {
if (contentType != null) {
for (int i = 0; i < fLegalHTMLContentTypes.length; i++) {
if (fLegalHTMLContentTypes[i].equals(contentType))
return true;
}
}
return false;
}
/**
* Returns a content type encoded in the given token. If the token's data is not null
and a string it is assumed
* that it is the encoded content type.
*
* @param token
* the token whose content type is to be determined
* @return the token's content type
*/
protected String getTokenContentType(IToken token) {
Object data = token.getData();
if (data instanceof String)
return (String) data;
return null;
}
/* zero-length partition support */
/*
* @see org.eclipse.jface.text.IDocumentPartitionerExtension2#getContentType(int)
* @since 3.0
*/
public String getContentType(int offset, boolean preferOpenPartitions) {
return getPartition(offset, preferOpenPartitions).getType();
}
/*
* @see org.eclipse.jface.text.IDocumentPartitionerExtension2#getPartition(int)
* @since 3.0
*/
public ITypedRegion getPartition(int offset, boolean preferOpenPartitions) {
ITypedRegion region = getPartition(offset);
if (preferOpenPartitions) {
if (region.getOffset() == offset && !region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE)) {
if (offset > 0) {
region = getPartition(offset - 1);
if (region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE))
return region;
}
return new TypedRegion(offset, 0, IDocument.DEFAULT_CONTENT_TYPE);
}
}
return region;
}
/*
* @see org.eclipse.jface.text.IDocumentPartitionerExtension2#computePartitioning(int, int, boolean)
* @since 3.0
*/
public ITypedRegion[] computePartitioning(int offset, int length, boolean includeZeroLengthPartitions) {
List list = new ArrayList();
try {
int endOffset = offset + length;
Position[] category = fDocument.getPositions(fPositionCategory);
TypedPosition previous = null, current = null;
int start, end, gapOffset;
Position gap = new Position(0);
int startIndex = getFirstIndexEndingAfterOffset(category, offset);
int endIndex = getFirstIndexStartingAfterOffset(category, endOffset);
for (int i = startIndex; i < endIndex; i++) {
current = (TypedPosition) category[i];
gapOffset = (previous != null) ? previous.getOffset() + previous.getLength() : 0;
gap.setOffset(gapOffset);
gap.setLength(current.getOffset() - gapOffset);
if ((includeZeroLengthPartitions && overlapsOrTouches(gap, offset, length))
|| (gap.getLength() > 0 && gap.overlapsWith(offset, length))) {
start = Math.max(offset, gapOffset);
end = Math.min(endOffset, gap.getOffset() + gap.getLength());
list.add(new TypedRegion(start, end - start, IDocument.DEFAULT_CONTENT_TYPE));
}
if (current.overlapsWith(offset, length)) {
start = Math.max(offset, current.getOffset());
end = Math.min(endOffset, current.getOffset() + current.getLength());
list.add(new TypedRegion(start, end - start, current.getType()));
}
previous = current;
}
if (previous != null) {
gapOffset = previous.getOffset() + previous.getLength();
gap.setOffset(gapOffset);
gap.setLength(fDocument.getLength() - gapOffset);
if ((includeZeroLengthPartitions && overlapsOrTouches(gap, offset, length))
|| (gap.getLength() > 0 && gap.overlapsWith(offset, length))) {
start = Math.max(offset, gapOffset);
end = Math.min(endOffset, fDocument.getLength());
list.add(new TypedRegion(start, end - start, IDocument.DEFAULT_CONTENT_TYPE));
}
}
if (list.isEmpty())
list.add(new TypedRegion(offset, length, IDocument.DEFAULT_CONTENT_TYPE));
} catch (BadPositionCategoryException x) {
}
TypedRegion[] result = new TypedRegion[list.size()];
list.toArray(result);
return result;
}
/**
* Returns true
if the given ranges overlap with or touch each other.
*
* @param gap
* the first range
* @param offset
* the offset of the second range
* @param length
* the length of the second range
* @return true
if the given ranges overlap with or touch each other
* @since 3.0
*/
private boolean overlapsOrTouches(Position gap, int offset, int length) {
return gap.getOffset() <= offset + length && offset <= gap.getOffset() + gap.getLength();
}
/**
* Returns the index of the first position which ends after the given offset.
*
* @param positions
* the positions in linear order
* @param offset
* the offset
* @return the index of the first position which ends after the offset
*
* @since 3.0
*/
private int getFirstIndexEndingAfterOffset(Position[] positions, int offset) {
int i = -1, j = positions.length;
while (j - i > 1) {
int k = (i + j) >> 1;
Position p = positions[k];
if (p.getOffset() + p.getLength() > offset)
j = k;
else
i = k;
}
return j;
}
/**
* Returns the index of the first position which starts at or after the given offset.
*
* @param positions
* the positions in linear order
* @param offset
* the offset
* @return the index of the first position which starts after the offset
*
* @since 3.0
*/
private int getFirstIndexStartingAfterOffset(Position[] positions, int offset) {
int i = -1, j = positions.length;
while (j - i > 1) {
int k = (i + j) >> 1;
Position p = positions[k];
if (p.getOffset() >= offset)
j = k;
else
i = k;
}
return j;
}
}