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