3m9 compatible;
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / ui / text / link / LinkedPositionManager.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2003 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11
12 package net.sourceforge.phpdt.internal.ui.text.link;
13
14 import java.util.Arrays;
15 import java.util.Comparator;
16 import java.util.HashMap;
17 import java.util.Map;
18
19 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
20
21 import org.eclipse.jface.text.Assert;
22 import org.eclipse.jface.text.BadLocationException;
23 import org.eclipse.jface.text.BadPositionCategoryException;
24 import org.eclipse.jface.text.DocumentCommand;
25 import org.eclipse.jface.text.DocumentEvent;
26 import org.eclipse.jface.text.IAutoEditStrategy;
27 import org.eclipse.jface.text.IDocument;
28 import org.eclipse.jface.text.IDocumentExtension;
29 import org.eclipse.jface.text.IDocumentListener;
30 import org.eclipse.jface.text.IPositionUpdater;
31 import org.eclipse.jface.text.Position;
32 import org.eclipse.jface.text.TypedPosition;
33 import org.eclipse.jface.text.contentassist.ICompletionProposal;
34
35
36 /**
37  * This class manages linked positions in a document. Positions are linked
38  * by type names. If positions have the same type name, they are considered
39  * as <em>linked</em>.
40  * 
41  * The manager remains active on a document until any of the following actions
42  * occurs:
43  * 
44  * <ul>
45  *   <li>A document change is performed which would invalidate any of the
46  *       above constraints.</li>
47  * 
48  *   <li>The method <code>uninstall()</code> is called.</li>
49  * 
50  *   <li>Another instance of <code>LinkedPositionManager</code> tries to
51  *       gain control of the same document.
52  * </ul>
53  */
54 public class LinkedPositionManager implements IDocumentListener, IPositionUpdater, IAutoEditStrategy {
55
56         // This class still exists to properly handle code assist. 
57         // This is due to the fact that it cannot be distinguished betweeen document changes which are
58         // issued by code assist and document changes which origin from another text viewer.
59         // There is a conflict in interest since in the latter case the linked mode should be left, but in the former case
60         // the linked mode should remain.
61         // To support content assist, document changes have to be propagated to connected positions
62         // by registering replace commands using IDocumentExtension.
63         // if it wasn't for the support of content assist, the documentChanged() method could be reduced to 
64         // a simple call to leave(true)  
65         private class Replace implements IDocumentExtension.IReplace {
66                 
67                 private Position fReplacePosition;
68                 private int fReplaceDeltaOffset;
69                 private int fReplaceLength;
70                 private String fReplaceText;
71                 
72                 public Replace(Position position, int deltaOffset, int length, String text) {
73                         fReplacePosition= position;
74                         fReplaceDeltaOffset= deltaOffset;
75                         fReplaceLength= length;
76                         fReplaceText= text;
77                 }
78                                 
79                 public void perform(IDocument document, IDocumentListener owner) {
80                         document.removeDocumentListener(owner);
81                         try {
82                                 document.replace(fReplacePosition.getOffset() + fReplaceDeltaOffset, fReplaceLength, fReplaceText);
83                         } catch (BadLocationException e) {
84                                 PHPeclipsePlugin.log(e);
85                                 // TBD
86                         }
87                         document.addDocumentListener(owner);
88                 }
89         }
90         
91         private static class PositionComparator implements Comparator {
92                 /*
93                  * @see Comparator#compare(Object, Object)
94                  */
95                 public int compare(Object object0, Object object1) {
96                         Position position0= (Position) object0;
97                         Position position1= (Position) object1;
98                         
99                         return position0.getOffset() - position1.getOffset();
100                 }
101         }
102
103         private static final String LINKED_POSITION_PREFIX= "LinkedPositionManager.linked.position"; //$NON-NLS-1$
104         private static final Comparator fgPositionComparator= new PositionComparator();
105         private static final Map fgActiveManagers= new HashMap();
106         private static int fgCounter= 0;
107                 
108         private IDocument fDocument;
109         private ILinkedPositionListener fListener;
110         private String fPositionCategoryName;
111         private boolean fMustLeave;
112         /**     
113          * Flag that records the state of this manager. As there are many different entities that may
114          * call leave or exit, these cannot always be sure whether the linked position infrastructure is
115          * still active. This is especially true for multithreaded situations. 
116          */
117         private boolean fIsActive= false;
118
119
120         /**
121          * Creates a <code>LinkedPositionManager</code> for a <code>IDocument</code>.
122          * 
123          * @param document the document to use with linked positions.
124          * @param canCoexist <code>true</code> if this manager can coexist with an already existing one
125          */
126         public LinkedPositionManager(IDocument document, boolean canCoexist) {
127                 Assert.isNotNull(document);
128                 fDocument= document;
129                 fPositionCategoryName= LINKED_POSITION_PREFIX + (fgCounter++);
130                 install(canCoexist);
131         }
132         
133         /**
134          * Creates a <code>LinkedPositionManager</code> for a <code>IDocument</code>.
135          * 
136          * @param document the document to use with linked positions.
137          */
138         public LinkedPositionManager(IDocument document) {
139                 this(document, false);
140         }
141         
142         /**
143          * Sets a listener to notify changes of current linked position.
144          */
145         public void setLinkedPositionListener(ILinkedPositionListener listener) {
146                 fListener= listener;    
147         }
148         
149         /**
150          * Adds a linked position to the manager with the type being the content of
151          * the document at the specified range.
152          * There are the following constraints for linked positions:
153          * 
154          * <ul>
155          *   <li>Any two positions have spacing of at least one character.
156          *       This implies that two positions must not overlap.</li>
157          *
158          *   <li>The string at any position must not contain line delimiters.</li>
159          * </ul>
160          * 
161          * @param offset the offset of the position.
162          * @param length the length of the position.
163          */
164         public void addPosition(int offset, int length) throws BadLocationException {
165                 String type= fDocument.get(offset, length);
166                 addPosition(offset, length, type);
167         }
168         
169         /**
170          * Adds a linked position of the specified position type to the manager. 
171          * There are the following constraints for linked positions:
172          * 
173          * <ul>
174          *   <li>Any two positions have spacing of at least one character.
175          *       This implies that two positions must not overlap.</li>
176          *
177          *   <li>The string at any position must not contain line delimiters.</li>
178          * </ul>
179          * 
180          * @param offset the offset of the position.
181          * @param length the length of the position.
182          * @param type the position type name - any positions with the same type are linked.
183          */
184         public void addPosition(int offset, int length, String type) throws BadLocationException {
185                 Position[] positions= getPositions(fDocument);
186
187                 if (positions != null) {
188                         for (int i = 0; i < positions.length; i++)
189                                 if (collides(positions[i], offset, length))
190                                         throw new BadLocationException(LinkedPositionMessages.getString(("LinkedPositionManager.error.position.collision"))); //$NON-NLS-1$
191                 }
192                 
193                 String content= fDocument.get(offset, length);          
194
195                 if (containsLineDelimiters(content))
196                         throw new BadLocationException(LinkedPositionMessages.getString(("LinkedPositionManager.error.contains.line.delimiters"))); //$NON-NLS-1$
197
198                 try {
199                         fDocument.addPosition(fPositionCategoryName, new TypedPosition(offset, length, type));
200                 } catch (BadPositionCategoryException e) {
201                     PHPeclipsePlugin.log(e);
202                         Assert.isTrue(false);
203                 }
204         }
205         
206         /**
207          * Adds a linked position to the manager. The current document content at the specified range is
208          * taken as the position type.
209          * <p> 
210          * There are the following constraints for linked positions:
211          * 
212          * <ul>
213          *   <li>Any two positions have spacing of at least one character.
214          *       This implies that two positions must not overlap.</li>
215          *
216          *   <li>The string at any position must not contain line delimiters.</li>
217          * </ul>
218          * 
219          * It is usually best to set the first item in <code>additionalChoices</code> to be equal with
220          * the text inserted at the current position.
221          * </p>
222          * 
223          * @param offset the offset of the position.
224          * @param length the length of the position.
225          * @param additionalChoices a number of additional choices to be displayed when selecting 
226          * a position of this <code>type</code>.
227          */
228         public void addPosition(int offset, int length, ICompletionProposal[] additionalChoices) throws BadLocationException {
229                 String type= fDocument.get(offset, length);
230                 addPosition(offset, length, type, additionalChoices);
231         }
232         /**
233          * Adds a linked position of the specified position type to the manager. 
234          * There are the following constraints for linked positions:
235          * 
236          * <ul>
237          *   <li>Any two positions have spacing of at least one character.
238          *       This implies that two positions must not overlap.</li>
239          *
240          *   <li>The string at any position must not contain line delimiters.</li>
241          * </ul>
242          * 
243          * It is usually best to set the first item in <code>additionalChoices</code> to be equal with
244          * the text inserted at the current position.
245          * 
246          * @param offset the offset of the position.
247          * @param length the length of the position.
248          * @param type the position type name - any positions with the same type are linked.
249          * @param additionalChoices a number of additional choices to be displayed when selecting 
250          * a position of this <code>type</code>.
251          */
252         public void addPosition(int offset, int length, String type, ICompletionProposal[] additionalChoices) throws BadLocationException {
253                 Position[] positions= getPositions(fDocument);
254
255                 if (positions != null) {
256                         for (int i = 0; i < positions.length; i++)
257                                 if (collides(positions[i], offset, length))
258                                         throw new BadLocationException(LinkedPositionMessages.getString(("LinkedPositionManager.error.position.collision"))); //$NON-NLS-1$
259                 }
260                 
261                 String content= fDocument.get(offset, length);          
262
263                 if (containsLineDelimiters(content))
264                         throw new BadLocationException(LinkedPositionMessages.getString(("LinkedPositionManager.error.contains.line.delimiters"))); //$NON-NLS-1$
265
266                 try {
267                         fDocument.addPosition(fPositionCategoryName, new ProposalPosition(offset, length, type, additionalChoices));
268                 } catch (BadPositionCategoryException e) {
269                     PHPeclipsePlugin.log(e);
270                         Assert.isTrue(false);
271                 }
272         }
273         
274         /**
275          * Tests if a manager is already active for a document.
276          */
277         public static boolean hasActiveManager(IDocument document) {
278                 return fgActiveManagers.get(document) != null;
279         }
280         
281         private void install(boolean canCoexist) {
282                 
283                 if (fIsActive)
284                         ;//JavaPlugin.log(new Status(IStatus.WARNING, JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionManager is already active: "+fPositionCategoryName, new IllegalStateException())); //$NON-NLS-1$
285                 else {
286                         fIsActive= true;
287                         //JavaPlugin.log(new Status(IStatus.INFO, JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionManager activated: "+fPositionCategoryName, new Exception())); //$NON-NLS-1$
288                 }
289                 
290                 if (!canCoexist) {
291                         LinkedPositionManager manager= (LinkedPositionManager) fgActiveManagers.get(fDocument);
292                         if (manager != null)
293                                 manager.leave(true);    
294                 }
295                 
296                 fgActiveManagers.put(fDocument, this);
297                 fDocument.addPositionCategory(fPositionCategoryName);
298                 fDocument.addPositionUpdater(this);             
299                 fDocument.addDocumentListener(this);
300                 
301                 fMustLeave= false;
302         }       
303         
304         /**
305          * Leaves the linked mode. If unsuccessful, the linked positions
306          * are restored to the values at the time they were added.
307          */
308         public void uninstall(boolean success) {        
309                 
310                 if (!fIsActive)
311                         // we migth also just return
312                         ;//JavaPlugin(new Status(IStatus.WARNING, JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionManager activated: "+fPositionCategoryName, new IllegalStateException())); //$NON-NLS-1$
313                 else {
314                         fDocument.removeDocumentListener(this);
315                         
316                         try {
317                                 Position[] positions= getPositions(fDocument);  
318                                 if ((!success) && (positions != null)) {
319                                         // restore
320                                         for (int i= 0; i != positions.length; i++) {
321                                                 TypedPosition position= (TypedPosition) positions[i];                           
322                                                 fDocument.replace(position.getOffset(), position.getLength(), position.getType());
323                                         }
324                                 }               
325                                 
326                                 fDocument.removePositionCategory(fPositionCategoryName);
327                                 
328                                 fIsActive= false;
329                                 // JavaPlugin.log(new Status(IStatus.INFO, JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionManager deactivated: "+fPositionCategoryName, new Exception())); //$NON-NLS-1$
330                                 
331                         } catch (BadLocationException e) {
332                           PHPeclipsePlugin.log(e);
333                                 Assert.isTrue(false);
334                                 
335                         } catch (BadPositionCategoryException e) {
336                           PHPeclipsePlugin.log(e);
337                                 Assert.isTrue(false);
338                                 
339                         } finally {
340                                 fDocument.removePositionUpdater(this);          
341                                 fgActiveManagers.remove(fDocument);             
342                         }
343                 }
344                 
345         }
346
347         /**
348          * Returns the position at the given offset, <code>null</code> if there is no position.
349          * @since 2.1
350          */
351         public Position getPosition(int offset) {
352                 Position[] positions= getPositions(fDocument);          
353                 if (positions == null)
354                         return null;
355
356                 for (int i= positions.length - 1; i >= 0; i--) {
357                         Position position= positions[i];
358                         if (offset >= position.getOffset() && offset <= position.getOffset() + position.getLength())
359                                 return positions[i];
360                 }
361                 
362                 return null;
363         }
364
365         /**
366          * Returns the first linked position.
367          * 
368          * @return returns <code>null</code> if no linked position exist.
369          */
370         public Position getFirstPosition() {
371                 return getNextPosition(-1);
372         }
373         
374         public Position getLastPosition() {
375                 Position[] positions= getPositions(fDocument);
376                 for (int i= positions.length - 1; i >= 0; i--) {                        
377                         String type= ((TypedPosition) positions[i]).getType();
378                         int j;
379                         for (j = 0; j != i; j++)
380                                 if (((TypedPosition) positions[j]).getType().equals(type))
381                                         break;
382
383                         if (j == i)
384                                 return positions[i];                            
385                 }
386
387                 return null;
388         }
389
390         /**
391          * Returns the next linked position with an offset greater than <code>offset</code>.
392          * If another position with the same type and offset lower than <code>offset</code>
393          * exists, the position is skipped.
394          * 
395          * @return returns <code>null</code> if no linked position exist.
396          */
397         public Position getNextPosition(int offset) {
398                 Position[] positions= getPositions(fDocument);
399                 return findNextPosition(positions, offset);
400         }
401
402         private static Position findNextPosition(Position[] positions, int offset) {
403                 // skip already visited types
404                 for (int i= 0; i != positions.length; i++) {                    
405                         if (positions[i].getOffset() > offset) {
406                                 String type= ((TypedPosition) positions[i]).getType();
407                                 int j;
408                                 for (j = 0; j != i; j++)
409                                         if (((TypedPosition) positions[j]).getType().equals(type))
410                                                 break;
411
412                                 if (j == i)
413                                         return positions[i];                            
414                         }
415                 }
416
417                 return null;
418         }
419         
420         /**
421          * Returns the position with the greatest offset smaller than <code>offset</code>.
422          *
423          * @return returns <code>null</code> if no linked position exist.
424          */
425         public Position getPreviousPosition(int offset) {
426                 Position[] positions= getPositions(fDocument);
427                 if (positions == null)
428                         return null;
429
430                 TypedPosition currentPosition= (TypedPosition) findCurrentPosition(positions, offset);
431                 String currentType= currentPosition == null ? null : currentPosition.getType();
432
433                 Position lastPosition= null;
434                 Position position= getFirstPosition();
435
436                 while (position != null && position.getOffset() < offset) {
437                         if (!((TypedPosition) position).getType().equals(currentType))
438                                 lastPosition= position;
439                         position= findNextPosition(positions, position.getOffset());
440                 }
441                 
442                 return lastPosition;
443         }
444
445         private Position[] getPositions(IDocument document) {
446
447                 if (!fIsActive)
448                         // we migth also just return an empty array
449                         ;//JavaPlugin(new Status(IStatus.WARNING, JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionManager is not active: "+fPositionCategoryName, new IllegalStateException())); //$NON-NLS-1$
450                 
451                 try {
452                         Position[] positions= document.getPositions(fPositionCategoryName);
453                         Arrays.sort(positions, fgPositionComparator);
454                         return positions;
455
456                 } catch (BadPositionCategoryException e) {
457                   PHPeclipsePlugin.log(e);
458                         Assert.isTrue(false);
459                 }
460                 
461                 return null;
462         }       
463
464         public static boolean includes(Position position, int offset, int length) {
465                 return
466                         (offset >= position.getOffset()) &&
467                         (offset + length <= position.getOffset() + position.getLength());
468         }
469
470         public static boolean excludes(Position position, int offset, int length) {
471                 return
472                         (offset + length <= position.getOffset()) ||
473                         (position.getOffset() + position.getLength() <= offset);
474         }
475
476         /*
477          * Collides if spacing if positions intersect each other or are adjacent.
478          */
479         private static boolean collides(Position position, int offset, int length) {
480                 return
481                         (offset <= position.getOffset() + position.getLength()) &&
482                         (position.getOffset() <= offset + length);      
483         }
484         
485         private void leave(boolean success) {
486                 try {
487                         uninstall(success);
488         
489                         if (fListener != null)
490                                 fListener.exit((success ? LinkedPositionUI.COMMIT : 0) | LinkedPositionUI.UPDATE_CARET);
491                 } finally {
492                         fMustLeave= false;
493                 }               
494         }
495         
496         private void abort() {
497                 uninstall(true); // don't revert anything
498                 
499                 if (fListener != null)
500                         fListener.exit(LinkedPositionUI.COMMIT); // don't let the UI restore anything
501                 
502                 // don't set fMustLeave, as we will get re-registered by a document event
503         }
504
505         /*
506          * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent)
507          */
508         public void documentAboutToBeChanged(DocumentEvent event) {
509
510                 if (fMustLeave) {
511                         event.getDocument().removeDocumentListener(this);
512                         return;
513                 }
514
515                 IDocument document= event.getDocument();
516
517                 Position[] positions= getPositions(document);
518                 Position position= findCurrentPosition(positions, event.getOffset());
519
520                 // modification outside editable position
521                 if (position == null) {
522                         // check for destruction of constraints (spacing of at least 1)
523                         if ((event.getText() == null || event.getText().length() == 0) &&
524                                 (findCurrentPosition(positions, event.getOffset()) != null) && // will never become true, see condition above
525                                 (findCurrentPosition(positions, event.getOffset() + event.getLength()) != null))
526                         {
527                                 leave(true);
528                         }                               
529
530                 // modification intersects editable position
531                 } else {
532                         // modificaction inside editable position
533                         if (includes(position, event.getOffset(), event.getLength())) {
534                                 if (containsLineDelimiters(event.getText()))
535                                         leave(true);
536
537                         // modificaction exceeds editable position
538                         } else {
539                                 leave(true);
540                         }
541                 }
542         }
543
544         /*
545          * @see IDocumentListener#documentChanged(DocumentEvent)
546          */
547         public void documentChanged(DocumentEvent event) {
548                 
549                 // have to handle code assist, so can't just leave the linked mode 
550                 // leave(true);
551                 
552                 IDocument document= event.getDocument();
553
554                 Position[] positions= getPositions(document);
555                 TypedPosition currentPosition= (TypedPosition) findCurrentPosition(positions, event.getOffset());
556
557                 // ignore document changes (assume it won't invalidate constraints)
558                 if (currentPosition == null)
559                         return;
560                 
561                 int deltaOffset= event.getOffset() - currentPosition.getOffset();               
562
563                 if (fListener != null) {
564                         int length= event.getText() == null ? 0 : event.getText().length();
565                         fListener.setCurrentPosition(currentPosition, deltaOffset + length);            
566                 }
567
568                 for (int i= 0; i != positions.length; i++) {
569                         TypedPosition p= (TypedPosition) positions[i];                  
570                         
571                         if (p.getType().equals(currentPosition.getType()) && !p.equals(currentPosition)) {
572                                 Replace replace= new Replace(p, deltaOffset, event.getLength(), event.getText());
573                                 ((IDocumentExtension) document).registerPostNotificationReplace(this, replace);
574                         }
575                 }
576         }
577         
578         /*
579          * @see IPositionUpdater#update(DocumentEvent)
580          */
581         public void update(DocumentEvent event) {
582                 
583                 int eventOffset= event.getOffset();
584                 int eventOldLength= event.getLength();
585                 int eventNewLength= event.getText() == null ? 0 : event.getText().length();
586                 int deltaLength= eventNewLength - eventOldLength;
587
588                 Position[] positions= getPositions(event.getDocument());
589                 
590                 
591                 for (int i= 0; i != positions.length; i++) {
592                         
593                         Position position= positions[i];
594                         
595                         if (position.isDeleted())
596                                 continue;
597                         
598                         int offset= position.getOffset();
599                         int length= position.getLength();
600                         int end= offset + length;
601                         
602                         if (offset > eventOffset + eventOldLength) // position comes way after change - shift
603                                 position.setOffset(offset + deltaLength);
604                         else if (end < eventOffset) // position comes way before change - leave alone
605                                 ;
606                         else if (offset <= eventOffset && end >= eventOffset + eventOldLength) { 
607                                 // event completely internal to the position - adjust length
608                                 position.setLength(length + deltaLength);
609                         } else if (offset < eventOffset) {
610                                 // event extends over end of position - adjust length
611                                 int newEnd= eventOffset + eventNewLength;
612                                 position.setLength(newEnd - offset);
613                         } else if (end > eventOffset + eventOldLength) { 
614                                 // event extends from before position into it - adjust offset and length
615                                 // offset becomes end of event, length ajusted acordingly
616                                 // we want to recycle the overlapping part
617                                 int newOffset = eventOffset + eventNewLength;
618                                 position.setOffset(newOffset);
619                                 position.setLength(length + deltaLength);
620                         } else {
621                                 // event consumes the position - delete it
622                                 position.delete();
623 //                              JavaPlugin.log(new Status(IStatus.INFO, JavaPlugin.getPluginId(), IStatus.OK, "linked position deleted -> must leave: "+fPositionCategoryName, null)); //$NON-NLS-1$
624                                 fMustLeave= true;
625                         }
626                 }
627                 
628                 if (fMustLeave)
629                         abort();
630         }
631
632         private static Position findCurrentPosition(Position[] positions, int offset) {
633                 for (int i= 0; i != positions.length; i++)
634                         if (includes(positions[i], offset, 0))
635                                 return positions[i];
636                 
637                 return null;                    
638         }
639
640         private boolean containsLineDelimiters(String string) {
641                 
642                 if (string == null)
643                         return false;
644                 
645                 String[] delimiters= fDocument.getLegalLineDelimiters();
646
647                 for (int i= 0; i != delimiters.length; i++)
648                         if (string.indexOf(delimiters[i]) != -1)
649                                 return true;
650
651                 return false;
652         }
653         
654         /**
655          * Test if ok to modify through UI.
656          */
657         public boolean anyPositionIncludes(int offset, int length) {
658                 Position[] positions= getPositions(fDocument);
659
660                 Position position= findCurrentPosition(positions, offset);
661                 if (position == null)
662                         return false;
663                 
664                 return includes(position, offset, length);
665         }
666         
667         /**
668          * Returns the position that includes the given range.
669          * @param offset
670          * @param length
671          * @return position that includes the given range
672          */
673         public Position getEmbracingPosition(int offset, int length) {
674                 Position[] positions= getPositions(fDocument);
675
676                 Position position= findCurrentPosition(positions, offset);
677                 if (position != null && includes(position, offset, length))
678                         return position;
679                         
680                 return null;
681         }
682         
683         /*
684          * @see org.eclipse.jface.text.IAutoIndentStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
685          */
686         public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
687                 
688                 if (fMustLeave) {
689                         leave(true);
690                         return;
691                 }
692
693                 // don't interfere with preceding auto edit strategies
694                 if (command.getCommandCount() != 1) {
695                         leave(true);
696                         return;
697                 }
698
699                 Position[] positions= getPositions(document);
700                 TypedPosition currentPosition= (TypedPosition) findCurrentPosition(positions, command.offset);
701
702                 // handle edits outside of a position
703                 if (currentPosition == null) {
704                         leave(true);
705                         return;
706                 }
707
708                 if (! command.doit)
709                         return;
710
711                 command.doit= false;
712                 command.owner= this;
713                 command.caretOffset= command.offset + command.length;
714
715                 int deltaOffset= command.offset - currentPosition.getOffset();          
716
717                 if (fListener != null)
718                         fListener.setCurrentPosition(currentPosition, deltaOffset + command.text.length());
719                 
720                 for (int i= 0; i != positions.length; i++) {
721                         TypedPosition position= (TypedPosition) positions[i];                   
722                         
723                         try {
724                                 if (position.getType().equals(currentPosition.getType()) && !position.equals(currentPosition))
725                                         command.addCommand(position.getOffset() + deltaOffset, command.length, command.text, this);
726                         } catch (BadLocationException e) {
727                           PHPeclipsePlugin.log(e);
728                         }
729                 }
730         }
731
732 }