/* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package net.sourceforge.phpdt.internal.ui.text.link; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import net.sourceforge.phpeclipse.PHPeclipsePlugin; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.text.IPositionUpdater; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.TypedPosition; import org.eclipse.jface.util.Assert; //import org.eclipse.jdt.internal.ui.JavaPlugin; /** * This class manages linked positions in a document. Positions are linked * by type names. If positions have the same type name, they are considered * as linked. * * The manager remains active on a document until any of the following actions * occurs: * *
uninstall()
is called.LinkedPositionManager
tries to
* gain control of the same document.
* LinkedPositionManager
for a IDocument
.
*
* @param document the document to use with linked positions.
*/
public LinkedPositionManager(IDocument document) {
Assert.isNotNull(document);
fDocument= document;
install();
}
/**
* Sets a listener to notify changes of current linked position.
*/
public void setLinkedPositionListener(LinkedPositionListener listener) {
fListener= listener;
}
/**
* Adds a linked position to the manager.
* There are the following constraints for linked positions:
*
* null
if no linked position exist.
*/
public Position getFirstPosition() {
return getNextPosition(-1);
}
/**
* Returns the next linked position with an offset greater than offset
.
* If another position with the same type and offset lower than offset
* exists, the position is skipped.
*
* @return returns null
if no linked position exist.
*/
public Position getNextPosition(int offset) {
Position[] positions= getPositions(fDocument);
return findNextPosition(positions, offset);
}
private static Position findNextPosition(Position[] positions, int offset) {
// skip already visited types
for (int i= 0; i != positions.length; i++) {
if (positions[i].getOffset() > offset) {
String type= ((TypedPosition) positions[i]).getType();
int j;
for (j = 0; j != i; j++)
if (((TypedPosition) positions[j]).getType().equals(type))
break;
if (j == i)
return positions[i];
}
}
return null;
}
/**
* Returns the position with the greatest offset smaller than offset
.
*
* @return returns null
if no linked position exist.
*/
public Position getPreviousPosition(int offset) {
Position[] positions= getPositions(fDocument);
if (positions == null)
return null;
Position lastPosition= null;
Position position= getFirstPosition();
while ((position != null) && (position.getOffset() < offset)) {
lastPosition= position;
position= findNextPosition(positions, position.getOffset());
}
return lastPosition;
}
private static Position[] getPositions(IDocument document) {
try {
Position[] positions= document.getPositions(LINKED_POSITION);
Arrays.sort(positions, fgPositionComparator);
return positions;
} catch (BadPositionCategoryException e) {
PHPeclipsePlugin.log(e);
Assert.isTrue(false);
}
return null;
}
public static boolean includes(Position position, int offset, int length) {
return
(offset >= position.getOffset()) &&
(offset + length <= position.getOffset() + position.getLength());
}
public static boolean excludes(Position position, int offset, int length) {
return
(offset + length <= position.getOffset()) ||
(position.getOffset() + position.getLength() <= offset);
}
/*
* Collides if spacing if positions intersect each other or are adjacent.
*/
private static boolean collides(Position position, int offset, int length) {
return
(offset <= position.getOffset() + position.getLength()) &&
(position.getOffset() <= offset + length);
}
private void leave(boolean success) {
uninstall(success);
if (fListener != null)
fListener.exit(success);
}
/*
* @see IDocumentListener#documentAboutToBeChanged(DocumentEvent)
*/
public void documentAboutToBeChanged(DocumentEvent event) {
IDocument document= event.getDocument();
Position[] positions= getPositions(document);
Position position= findCurrentEditablePosition(positions, event.getOffset());
// modification outside editable position
if (position == null) {
position= findCurrentPosition(positions, event.getOffset());
// modification outside any position
if (position == null) {
// check for destruction of constraints (spacing of at least 1)
if ((event.getText().length() == 0) &&
(findCurrentPosition(positions, event.getOffset()) != null) &&
(findCurrentPosition(positions, event.getOffset() + event.getLength()) != null))
{
leave(true);
}
// modification intersects non-editable position
} else {
leave(true);
}
// modification intersects editable position
} else {
// modificaction inside editable position
if (includes(position, event.getOffset(), event.getLength())) {
if (containsLineDelimiters(event.getText()))
leave(true);
// modificaction exceeds editable position
} else {
leave(true);
}
}
}
/*
* @see IDocumentListener#documentChanged(DocumentEvent)
*/
public void documentChanged(DocumentEvent event) {
IDocument document= event.getDocument();
Position[] positions= getPositions(document);
TypedPosition currentPosition= (TypedPosition) findCurrentEditablePosition(positions, event.getOffset());
// ignore document changes (assume it won't invalidate constraints)
if (currentPosition == null)
return;
int deltaOffset= event.getOffset() - currentPosition.getOffset();
if (fListener != null)
fListener.setCurrentPosition(currentPosition, deltaOffset + event.getText().length());
for (int i= 0; i != positions.length; i++) {
TypedPosition p= (TypedPosition) positions[i];
if (p.getType().equals(currentPosition.getType()) && !p.equals(currentPosition)) {
Replace replace= new Replace(p, deltaOffset, event.getLength(), event.getText());
((IDocumentExtension) document).registerPostNotificationReplace(this, replace);
}
}
}
/*
* @see IPositionUpdater#update(DocumentEvent)
*/
public void update(DocumentEvent event) {
int deltaLength= event.getText().length() - event.getLength();
Position[] positions= getPositions(event.getDocument());
TypedPosition currentPosition= (TypedPosition) findCurrentPosition(positions, event.getOffset());
// document change outside positions
if (currentPosition == null) {
for (int i= 0; i != positions.length; i++) {
TypedPosition position= (TypedPosition) positions[i];
int offset= position.getOffset();
if (offset >= event.getOffset())
position.setOffset(offset + deltaLength);
}
// document change within a position
} else {
int length= currentPosition.getLength();
for (int i= 0; i != positions.length; i++) {
TypedPosition position= (TypedPosition) positions[i];
int offset= position.getOffset();
if (position.equals(currentPosition)) {
position.setLength(length + deltaLength);
} else if (offset > currentPosition.getOffset()) {
position.setOffset(offset + deltaLength);
}
}
}
}
private static Position findCurrentPosition(Position[] positions, int offset) {
for (int i= 0; i != positions.length; i++)
if (includes(positions[i], offset, 0))
return positions[i];
return null;
}
private static Position findCurrentEditablePosition(Position[] positions, int offset) {
Position position= positions[0];
while ((position != null) && !includes(position, offset, 0))
position= findNextPosition(positions, position.getOffset());
return position;
}
private boolean containsLineDelimiters(String string) {
String[] delimiters= fDocument.getLegalLineDelimiters();
for (int i= 0; i != delimiters.length; i++)
if (string.indexOf(delimiters[i]) != -1)
return true;
return false;
}
/**
* Test if ok to modify through UI.
*/
public boolean anyPositionIncludes(int offset, int length) {
Position[] positions= getPositions(fDocument);
Position position= findCurrentEditablePosition(positions, offset);
if (position == null)
return false;
return includes(position, offset, length);
}
}