3m9 compatible;
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / ui / text / link / LinkedPositionManager.java
index 425891b..4fb0216 100644 (file)
@@ -1,7 +1,14 @@
-/*
- * (c) Copyright IBM Corp. 2000, 2001.
- * All Rights Reserved.
- */
+/*******************************************************************************
+ * 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;
@@ -11,18 +18,20 @@ import java.util.Map;
 
 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
 
+import org.eclipse.jface.text.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.util.Assert;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
 
-//import org.eclipse.jdt.internal.ui.JavaPlugin;
 
 /**
  * This class manages linked positions in a document. Positions are linked
@@ -42,20 +51,17 @@ import org.eclipse.jface.util.Assert;
  *       gain control of the same document.
  * </ul>
  */
-public class LinkedPositionManager implements IDocumentListener, IPositionUpdater {
-       
-       private static class PositionComparator implements Comparator {
-               /*
-                * @see Comparator#compare(Object, Object)
-                */
-               public int compare(Object object0, Object object1) {
-                       Position position0= (Position) object0;
-                       Position position1= (Position) object1;
-                       
-                       return position0.getOffset() - position1.getOffset();
-               }
-       }
-       
+public class LinkedPositionManager implements IDocumentListener, IPositionUpdater, IAutoEditStrategy {
+
+       // This class still exists to properly handle code assist. 
+       // This is due to the fact that it cannot be distinguished betweeen document changes which are
+       // issued by code assist and document changes which origin from another text viewer.
+       // There is a conflict in interest since in the latter case the linked mode should be left, but in the former case
+       // the linked mode should remain.
+       // To support content assist, document changes have to be propagated to connected positions
+       // by registering replace commands using IDocumentExtension.
+       // if it wasn't for the support of content assist, the documentChanged() method could be reduced to 
+       // a simple call to leave(true)  
        private class Replace implements IDocumentExtension.IReplace {
                
                private Position fReplacePosition;
@@ -81,36 +87,68 @@ public class LinkedPositionManager implements IDocumentListener, IPositionUpdate
                        document.addDocumentListener(owner);
                }
        }
+       
+       private static class PositionComparator implements Comparator {
+               /*
+                * @see Comparator#compare(Object, Object)
+                */
+               public int compare(Object object0, Object object1) {
+                       Position position0= (Position) object0;
+                       Position position1= (Position) object1;
+                       
+                       return position0.getOffset() - position1.getOffset();
+               }
+       }
 
-       private static final String LINKED_POSITION= "LinkedPositionManager.linked.position"; //$NON-NLS-1$
+       private static final String LINKED_POSITION_PREFIX= "LinkedPositionManager.linked.position"; //$NON-NLS-1$
        private static final Comparator fgPositionComparator= new PositionComparator();
        private static final Map fgActiveManagers= new HashMap();
+       private static int fgCounter= 0;
                
        private IDocument fDocument;
-       
-       private LinkedPositionListener fListener;
+       private ILinkedPositionListener fListener;
+       private String fPositionCategoryName;
+       private boolean fMustLeave;
+       /**     
+        * Flag that records the state of this manager. As there are many different entities that may
+        * call leave or exit, these cannot always be sure whether the linked position infrastructure is
+        * still active. This is especially true for multithreaded situations. 
+        */
+       private boolean fIsActive= false;
+
 
        /**
         * Creates a <code>LinkedPositionManager</code> for a <code>IDocument</code>.
         * 
         * @param document the document to use with linked positions.
+        * @param canCoexist <code>true</code> if this manager can coexist with an already existing one
         */
-       public LinkedPositionManager(IDocument document) {
+       public LinkedPositionManager(IDocument document, boolean canCoexist) {
                Assert.isNotNull(document);
-               
-               fDocument= document;            
-               install();
+               fDocument= document;
+               fPositionCategoryName= LINKED_POSITION_PREFIX + (fgCounter++);
+               install(canCoexist);
        }
-
+       
+       /**
+        * Creates a <code>LinkedPositionManager</code> for a <code>IDocument</code>.
+        * 
+        * @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(LinkedPositionListener listener) {
+       public void setLinkedPositionListener(ILinkedPositionListener listener) {
                fListener= listener;    
        }
        
        /**
-        * Adds a linked position to the manager.
+        * 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:
         * 
         * <ul>
@@ -124,6 +162,26 @@ public class LinkedPositionManager implements IDocumentListener, IPositionUpdate
         * @param length the length of the position.
         */
        public void addPosition(int offset, int length) throws BadLocationException {
+               String type= fDocument.get(offset, length);
+               addPosition(offset, length, type);
+       }
+       
+       /**
+        * Adds a linked position of the specified position type to the manager. 
+        * There are the following constraints for linked positions:
+        * 
+        * <ul>
+        *   <li>Any two positions have spacing of at least one character.
+        *       This implies that two positions must not overlap.</li>
+        *
+        *   <li>The string at any position must not contain line delimiters.</li>
+        * </ul>
+        * 
+        * @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.
+        */
+       public void addPosition(int offset, int length, String type) throws BadLocationException {
                Position[] positions= getPositions(fDocument);
 
                if (positions != null) {
@@ -132,69 +190,176 @@ public class LinkedPositionManager implements IDocumentListener, IPositionUpdate
                                        throw new BadLocationException(LinkedPositionMessages.getString(("LinkedPositionManager.error.position.collision"))); //$NON-NLS-1$
                }
                
-               String type= fDocument.get(offset, length);             
+               String content= fDocument.get(offset, length);          
 
-               if (containsLineDelimiters(type))
+               if (containsLineDelimiters(content))
                        throw new BadLocationException(LinkedPositionMessages.getString(("LinkedPositionManager.error.contains.line.delimiters"))); //$NON-NLS-1$
 
                try {
-                       fDocument.addPosition(LINKED_POSITION, new TypedPosition(offset, length, type));
+                       fDocument.addPosition(fPositionCategoryName, new TypedPosition(offset, length, type));
                } catch (BadPositionCategoryException e) {
-                       PHPeclipsePlugin.log(e);
+                   PHPeclipsePlugin.log(e);
                        Assert.isTrue(false);
                }
        }
+       
+       /**
+        * Adds a linked position to the manager. The current document content at the specified range is
+        * taken as the position type.
+        * <p> 
+        * There are the following constraints for linked positions:
+        * 
+        * <ul>
+        *   <li>Any two positions have spacing of at least one character.
+        *       This implies that two positions must not overlap.</li>
+        *
+        *   <li>The string at any position must not contain line delimiters.</li>
+        * </ul>
+        * 
+        * It is usually best to set the first item in <code>additionalChoices</code> to be equal with
+        * the text inserted at the current position.
+        * </p>
+        * 
+        * @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 <code>type</code>.
+        */
+       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:
+        * 
+        * <ul>
+        *   <li>Any two positions have spacing of at least one character.
+        *       This implies that two positions must not overlap.</li>
+        *
+        *   <li>The string at any position must not contain line delimiters.</li>
+        * </ul>
+        * 
+        * It is usually best to set the first item in <code>additionalChoices</code> 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 <code>type</code>.
+        */
+       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() {
-               LinkedPositionManager manager= (LinkedPositionManager) fgActiveManagers.get(fDocument);
-               if (manager != null)
-                       manager.leave(true);            
-
-               fgActiveManagers.put(fDocument, this);
+       
+       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$
+               }
                
-               fDocument.addPositionCategory(LINKED_POSITION);
+               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) {                        
-               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());
-                               }
-                       }               
+       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);
                        
-                       fDocument.removePositionCategory(LINKED_POSITION);
-
-               } catch (BadLocationException e) {
-                       PHPeclipsePlugin.log(e);
-                       Assert.isTrue(false);
+                       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);             
+                       }
+               }
+               
+       }
 
-               } catch (BadPositionCategoryException e) {
-                       PHPeclipsePlugin.log(e);
-                       Assert.isTrue(false);
+       /**
+        * Returns the position at the given offset, <code>null</code> if there is no position.
+        * @since 2.1
+        */
+       public Position getPosition(int offset) {
+               Position[] positions= getPositions(fDocument);          
+               if (positions == null)
+                       return null;
 
-               } finally {
-                       fDocument.removePositionUpdater(this);          
-                       fgActiveManagers.remove(fDocument);             
+               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;
        }
 
        /**
@@ -205,6 +370,22 @@ public class LinkedPositionManager implements IDocumentListener, IPositionUpdate
        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 <code>offset</code>.
@@ -242,29 +423,38 @@ public class LinkedPositionManager implements IDocumentListener, IPositionUpdate
         * @return returns <code>null</code> if no linked position exist.
         */
        public Position getPreviousPosition(int offset) {
-               Position[] positions= getPositions(fDocument);          
+               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)) {
-                       lastPosition= position;
+
+               while (position != null && position.getOffset() < offset) {
+                       if (!((TypedPosition) position).getType().equals(currentType))
+                               lastPosition= position;
                        position= findNextPosition(positions, position.getOffset());
-               }               
+               }
                
                return lastPosition;
        }
 
-       private static Position[] getPositions(IDocument document) {
+       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(LINKED_POSITION);
+                       Position[] positions= document.getPositions(fPositionCategoryName);
                        Arrays.sort(positions, fgPositionComparator);
                        return positions;
 
                } catch (BadPositionCategoryException e) {
-                       PHPeclipsePlugin.log(e);
+                 PHPeclipsePlugin.log(e);
                        Assert.isTrue(false);
                }
                
@@ -293,39 +483,49 @@ public class LinkedPositionManager implements IDocumentListener, IPositionUpdate
        }
        
        private void leave(boolean success) {
-               uninstall(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(success);                
+                       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= findCurrentEditablePosition(positions, event.getOffset());
+               Position position= findCurrentPosition(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 {
+                       // 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 {
@@ -344,11 +544,15 @@ public class LinkedPositionManager implements IDocumentListener, IPositionUpdate
        /*
         * @see IDocumentListener#documentChanged(DocumentEvent)
         */
-       public void documentChanged(DocumentEvent event) {              
+       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) findCurrentEditablePosition(positions, event.getOffset());
+               TypedPosition currentPosition= (TypedPosition) findCurrentPosition(positions, event.getOffset());
 
                // ignore document changes (assume it won't invalidate constraints)
                if (currentPosition == null)
@@ -356,9 +560,11 @@ public class LinkedPositionManager implements IDocumentListener, IPositionUpdate
                
                int deltaOffset= event.getOffset() - currentPosition.getOffset();               
 
-               if (fListener != null)
-                       fListener.setCurrentPosition(currentPosition, deltaOffset + event.getText().length());
-               
+               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];                  
                        
@@ -373,37 +579,54 @@ public class LinkedPositionManager implements IDocumentListener, IPositionUpdate
         * @see IPositionUpdater#update(DocumentEvent)
         */
        public void update(DocumentEvent event) {
-               int deltaLength= event.getText().length() - event.getLength();
+               
+               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());
-               TypedPosition currentPosition= (TypedPosition) findCurrentPosition(positions, event.getOffset());
-
-               // document change outside positions
-               if (currentPosition == null) {
+               
+               
+               for (int i= 0; i != positions.length; i++) {
                        
-                       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);
-                       }
+                       Position position= positions[i];
                        
-               // 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);
-                               }
-                       }               
+                       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) {
@@ -414,16 +637,11 @@ public class LinkedPositionManager implements IDocumentListener, IPositionUpdate
                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) {
+               
+               if (string == null)
+                       return false;
+               
                String[] delimiters= fDocument.getLegalLineDelimiters();
 
                for (int i= 0; i != delimiters.length; i++)
@@ -439,11 +657,76 @@ public class LinkedPositionManager implements IDocumentListener, IPositionUpdate
        public boolean anyPositionIncludes(int offset, int length) {
                Position[] positions= getPositions(fDocument);
 
-               Position position= findCurrentEditablePosition(positions, offset);
+               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);
+                       }
+               }
+       }
+
 }