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