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