X-Git-Url: http://secure.phpeclipse.com diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionManager.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionManager.java new file mode 100644 index 0000000..6f1157d --- /dev/null +++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionManager.java @@ -0,0 +1,831 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 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.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; + +//incastrix +//import org.eclipse.jface.text.Assert; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.DocumentCommand; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IAutoEditStrategy; +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.text.contentassist.ICompletionProposal; + +/** + * 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.
+ * @param canCoexist
+ * true
if this manager can coexist with an
+ * already existing one
+ */
+ public LinkedPositionManager(IDocument document, boolean canCoexist) {
+ Assert.isNotNull(document);
+ fDocument = document;
+ fPositionCategoryName = LINKED_POSITION_PREFIX + (fgCounter++);
+ install(canCoexist);
+ }
+
+ /**
+ * Creates a LinkedPositionManager
for a
+ * IDocument
.
+ *
+ * @param document
+ * the document to use with linked positions.
+ */
+ public LinkedPositionManager(IDocument document) {
+ this(document, false);
+ }
+
+ /**
+ * Sets a listener to notify changes of current linked position.
+ */
+ public void setLinkedPositionListener(ILinkedPositionListener listener) {
+ fListener = listener;
+ }
+
+ /**
+ * Adds a linked position to the manager with the type being the content of
+ * the document at the specified range. There are the following constraints
+ * for linked positions:
+ *
+ * + * There are the following constraints for linked positions: + * + *
additionalChoices
to be equal with the text inserted at
+ * the current position.
+ *
+ *
+ * @param offset
+ * the offset of the position.
+ * @param length
+ * the length of the position.
+ * @param additionalChoices
+ * a number of additional choices to be displayed when selecting
+ * a position of this type
.
+ */
+// public void addPosition(int offset, int length,
+// ICompletionProposal[] additionalChoices)
+// throws BadLocationException {
+// String type = fDocument.get(offset, length);
+// addPosition(offset, length, type, additionalChoices);
+// }
+
+ /**
+ * Adds a linked position of the specified position type to the manager.
+ * There are the following constraints for linked positions:
+ *
+ * additionalChoices
to be equal with the text inserted at
+ * the current position.
+ *
+ * @param offset
+ * the offset of the position.
+ * @param length
+ * the length of the position.
+ * @param type
+ * the position type name - any positions with the same type are
+ * linked.
+ * @param additionalChoices
+ * a number of additional choices to be displayed when selecting
+ * a position of this type
.
+ */
+// public void addPosition(int offset, int length, String type,
+// ICompletionProposal[] additionalChoices)
+// throws BadLocationException {
+// Position[] positions = getPositions(fDocument);
+//
+// if (positions != null) {
+// for (int i = 0; i < positions.length; i++)
+// if (collides(positions[i], offset, length))
+// throw new BadLocationException(
+// LinkedPositionMessages
+// .getString(("LinkedPositionManager.error.position.collision"))); //$NON-NLS-1$
+// }
+//
+// String content = fDocument.get(offset, length);
+//
+// if (containsLineDelimiters(content))
+// throw new BadLocationException(
+// LinkedPositionMessages
+// .getString(("LinkedPositionManager.error.contains.line.delimiters"))); //$NON-NLS-1$
+//
+// try {
+// fDocument.addPosition(fPositionCategoryName, new ProposalPosition(
+// offset, length, type, additionalChoices));
+// } catch (BadPositionCategoryException e) {
+// PHPeclipsePlugin.log(e);
+// Assert.isTrue(false);
+// }
+// }
+
+ /**
+ * Tests if a manager is already active for a document.
+ */
+// public static boolean hasActiveManager(IDocument document) {
+// return fgActiveManagers.get(document) != null;
+// }
+
+ private void install(boolean canCoexist) {
+
+ if (fIsActive)
+ ;// JavaPlugin.log(new Status(IStatus.WARNING,
+ // JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionManager
+ // is already active: "+fPositionCategoryName, new
+ // IllegalStateException())); //$NON-NLS-1$
+ else {
+ fIsActive = true;
+ // JavaPlugin.log(new Status(IStatus.INFO, JavaPlugin.getPluginId(),
+ // IStatus.OK, "LinkedPositionManager activated:
+ // "+fPositionCategoryName, new Exception())); //$NON-NLS-1$
+ }
+
+ if (!canCoexist) {
+ LinkedPositionManager manager = (LinkedPositionManager) fgActiveManagers
+ .get(fDocument);
+ if (manager != null)
+ manager.leave(true);
+ }
+
+ fgActiveManagers.put(fDocument, this);
+ fDocument.addPositionCategory(fPositionCategoryName);
+ fDocument.addPositionUpdater(this);
+ fDocument.addDocumentListener(this);
+
+ fMustLeave = false;
+ }
+
+ /**
+ * Leaves the linked mode. If unsuccessful, the linked positions are
+ * restored to the values at the time they were added.
+ */
+ public void uninstall(boolean success) {
+
+ if (!fIsActive)
+ // we migth also just return
+ ;// JavaPlugin(new Status(IStatus.WARNING,
+ // JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionManager
+ // activated: "+fPositionCategoryName, new
+ // IllegalStateException())); //$NON-NLS-1$
+ else {
+ fDocument.removeDocumentListener(this);
+
+ try {
+ Position[] positions = getPositions(fDocument);
+ if ((!success) && (positions != null)) {
+ // restore
+ for (int i = 0; i != positions.length; i++) {
+ TypedPosition position = (TypedPosition) positions[i];
+ fDocument.replace(position.getOffset(), position
+ .getLength(), position.getType());
+ }
+ }
+
+ fDocument.removePositionCategory(fPositionCategoryName);
+
+ fIsActive = false;
+ // JavaPlugin.log(new Status(IStatus.INFO,
+ // JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionManager
+ // deactivated: "+fPositionCategoryName, new Exception()));
+ // //$NON-NLS-1$
+
+ } catch (BadLocationException e) {
+ PHPeclipsePlugin.log(e);
+ Assert.isTrue(false);
+
+ } catch (BadPositionCategoryException e) {
+ PHPeclipsePlugin.log(e);
+ Assert.isTrue(false);
+
+ } finally {
+ fDocument.removePositionUpdater(this);
+ fgActiveManagers.remove(fDocument);
+ }
+ }
+
+ }
+
+ /**
+ * Returns the position at the given offset, null
if there is
+ * no position.
+ *
+ * @since 2.1
+ */
+ public Position getPosition(int offset) {
+ Position[] positions = getPositions(fDocument);
+ if (positions == null)
+ return null;
+
+ for (int i = positions.length - 1; i >= 0; i--) {
+ Position position = positions[i];
+ if (offset >= position.getOffset()
+ && offset <= position.getOffset() + position.getLength())
+ return positions[i];
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the first linked position.
+ *
+ * @return returns null
if no linked position exist.
+ */
+ public Position getFirstPosition() {
+ return getNextPosition(-1);
+ }
+
+ public Position getLastPosition() {
+ Position[] positions = getPositions(fDocument);
+ for (int i = positions.length - 1; i >= 0; i--) {
+ 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 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;
+
+ TypedPosition currentPosition = (TypedPosition) findCurrentPosition(
+ positions, offset);
+ String currentType = currentPosition == null ? null : currentPosition
+ .getType();
+
+ Position lastPosition = null;
+ Position position = getFirstPosition();
+
+ while (position != null && position.getOffset() < offset) {
+ if (!((TypedPosition) position).getType().equals(currentType))
+ lastPosition = position;
+ position = findNextPosition(positions, position.getOffset());
+ }
+
+ return lastPosition;
+ }
+
+ private Position[] getPositions(IDocument document) {
+
+ if (!fIsActive)
+ // we migth also just return an empty array
+ ;// JavaPlugin(new Status(IStatus.WARNING,
+ // JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionManager
+ // is not active: "+fPositionCategoryName, new
+ // IllegalStateException())); //$NON-NLS-1$
+
+ try {
+ Position[] positions = document.getPositions(fPositionCategoryName);
+ 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) {
+ try {
+ uninstall(success);
+
+ if (fListener != null)
+ fListener.exit((success ? LinkedPositionUI.COMMIT : 0)
+ | LinkedPositionUI.UPDATE_CARET);
+ } finally {
+ fMustLeave = false;
+ }
+ }
+
+ private void abort() {
+ uninstall(true); // don't revert anything
+
+ if (fListener != null)
+ fListener.exit(LinkedPositionUI.COMMIT); // don't let the UI
+ // restore anything
+
+ // don't set fMustLeave, as we will get re-registered by a document
+ // event
+ }
+
+ /*
+ * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent)
+ */
+ public void documentAboutToBeChanged(DocumentEvent event) {
+
+ if (fMustLeave) {
+ event.getDocument().removeDocumentListener(this);
+ return;
+ }
+
+ IDocument document = event.getDocument();
+
+ Position[] positions = getPositions(document);
+ Position position = findCurrentPosition(positions, event.getOffset());
+
+ // modification outside editable position
+ if (position == null) {
+ // check for destruction of constraints (spacing of at least 1)
+ if ((event.getText() == null || event.getText().length() == 0)
+ && (findCurrentPosition(positions, event.getOffset()) != null)
+ && // will never become true, see condition above
+ (findCurrentPosition(positions, event.getOffset()
+ + event.getLength()) != null)) {
+ 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) {
+
+ // have to handle code assist, so can't just leave the linked mode
+ // leave(true);
+
+ IDocument document = event.getDocument();
+
+ Position[] positions = getPositions(document);
+ TypedPosition currentPosition = (TypedPosition) findCurrentPosition(
+ 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) {
+ int length = event.getText() == null ? 0 : event.getText().length();
+ fListener.setCurrentPosition(currentPosition, deltaOffset + 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 eventOffset = event.getOffset();
+ int eventOldLength = event.getLength();
+ int eventNewLength = event.getText() == null ? 0 : event.getText()
+ .length();
+ int deltaLength = eventNewLength - eventOldLength;
+
+ Position[] positions = getPositions(event.getDocument());
+
+ for (int i = 0; i != positions.length; i++) {
+
+ Position position = positions[i];
+
+ if (position.isDeleted())
+ continue;
+
+ int offset = position.getOffset();
+ int length = position.getLength();
+ int end = offset + length;
+
+ if (offset > eventOffset + eventOldLength) // position comes way
+ // after change - shift
+ position.setOffset(offset + deltaLength);
+ else if (end < eventOffset) // position comes way before change -
+ // leave alone
+ ;
+ else if (offset <= eventOffset
+ && end >= eventOffset + eventOldLength) {
+ // event completely internal to the position - adjust length
+ position.setLength(length + deltaLength);
+ } else if (offset < eventOffset) {
+ // event extends over end of position - adjust length
+ int newEnd = eventOffset + eventNewLength;
+ position.setLength(newEnd - offset);
+ } else if (end > eventOffset + eventOldLength) {
+ // event extends from before position into it - adjust offset
+ // and length
+ // offset becomes end of event, length ajusted acordingly
+ // we want to recycle the overlapping part
+ int newOffset = eventOffset + eventNewLength;
+ position.setOffset(newOffset);
+ position.setLength(length + deltaLength);
+ } else {
+ // event consumes the position - delete it
+ position.delete();
+ // JavaPlugin.log(new Status(IStatus.INFO,
+ // JavaPlugin.getPluginId(), IStatus.OK, "linked position
+ // deleted -> must leave: "+fPositionCategoryName, null));
+ // //$NON-NLS-1$
+ fMustLeave = true;
+ }
+ }
+
+ if (fMustLeave)
+ abort();
+ }
+
+ 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 boolean containsLineDelimiters(String string) {
+
+ if (string == null)
+ return false;
+
+ 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 = findCurrentPosition(positions, offset);
+ if (position == null)
+ return false;
+
+ return includes(position, offset, length);
+ }
+
+ /**
+ * Returns the position that includes the given range.
+ *
+ * @param offset
+ * @param length
+ * @return position that includes the given range
+ */
+ public Position getEmbracingPosition(int offset, int length) {
+ Position[] positions = getPositions(fDocument);
+
+ Position position = findCurrentPosition(positions, offset);
+ if (position != null && includes(position, offset, length))
+ return position;
+
+ return null;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IAutoIndentStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument,
+ * org.eclipse.jface.text.DocumentCommand)
+ */
+ public void customizeDocumentCommand(IDocument document,
+ DocumentCommand command) {
+
+ if (fMustLeave) {
+ leave(true);
+ return;
+ }
+
+ // don't interfere with preceding auto edit strategies
+ if (command.getCommandCount() != 1) {
+ leave(true);
+ return;
+ }
+
+ Position[] positions = getPositions(document);
+ TypedPosition currentPosition = (TypedPosition) findCurrentPosition(
+ positions, command.offset);
+
+ // handle edits outside of a position
+ if (currentPosition == null) {
+ leave(true);
+ return;
+ }
+
+ if (!command.doit)
+ return;
+
+ command.doit = false;
+ command.owner = this;
+ command.caretOffset = command.offset + command.length;
+
+ int deltaOffset = command.offset - currentPosition.getOffset();
+
+ if (fListener != null)
+ fListener.setCurrentPosition(currentPosition, deltaOffset
+ + command.text.length());
+
+ for (int i = 0; i != positions.length; i++) {
+ TypedPosition position = (TypedPosition) positions[i];
+
+ try {
+ if (position.getType().equals(currentPosition.getType())
+ && !position.equals(currentPosition))
+ command.addCommand(position.getOffset() + deltaOffset,
+ command.length, command.text, this);
+ } catch (BadLocationException e) {
+ PHPeclipsePlugin.log(e);
+ }
+ }
+ }
+
+}