d746edd561b8bbbfc2df8c333e972e940b21fb4c
[phpeclipse.git] /
1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 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 package net.sourceforge.phpeclipse.phpeditor.php;
12
13 import java.util.ArrayList;
14 import java.util.List;
15
16 import org.eclipse.jface.text.Assert;
17 import org.eclipse.jface.text.BadLocationException;
18 import org.eclipse.jface.text.BadPositionCategoryException;
19 import org.eclipse.jface.text.DefaultPositionUpdater;
20 import org.eclipse.jface.text.DocumentEvent;
21 import org.eclipse.jface.text.IDocument;
22 import org.eclipse.jface.text.IDocumentPartitioner;
23 import org.eclipse.jface.text.IDocumentPartitionerExtension;
24 import org.eclipse.jface.text.IDocumentPartitionerExtension2;
25 import org.eclipse.jface.text.IRegion;
26 import org.eclipse.jface.text.ITypedRegion;
27 import org.eclipse.jface.text.Position;
28 import org.eclipse.jface.text.Region;
29 import org.eclipse.jface.text.TypedPosition;
30 import org.eclipse.jface.text.TypedRegion;
31 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
32 import org.eclipse.jface.text.rules.IToken;
33
34 /**
35  * A standard implementation of a document partitioner. It uses a partition token scanner to scan the document and to determine the
36  * document's partitioning. The tokens returned by the scanner are supposed to return the partition type as their data. The
37  * partitioner remembers the document's partitions in the document itself rather than maintaining its own data structure.
38  * 
39  * @see IPartitionTokenScanner
40  * @since 2.0
41  */
42 public class DefaultPHPPartitioner_delete_it implements IDocumentPartitioner, IDocumentPartitionerExtension, IDocumentPartitionerExtension2 {
43
44   /**
45    * The position category this partitioner uses to store the document's partitioning information.
46    * 
47    * @deprecated As of 3.0, use <code>getManagingPositionCategories()</code> instead.
48    */
49   public final static String CONTENT_TYPES_CATEGORY = "__content_types_category"; //$NON-NLS-1$
50
51   /** The HTML areas partitioner's scanner */
52   protected IPartitionTokenScanner fHTMLScanner;
53
54   /** The PHP areas partitioner's scanner */
55   protected IPartitionTokenScanner fPHPScanner;
56
57   /** The legal content types of both partitioners */
58   protected String[] fLegalContentTypes;
59
60   /** The legal content types of the HTML partitioner */
61   protected String[] fLegalHTMLContentTypes;
62
63   /** The legal content types of the PHP partitioner */
64   protected String[] fLegalPHPContentTypes;
65
66   /** The partitioner's document */
67   protected IDocument fDocument;
68
69   /** The document length before a document change occurred */
70   protected int fPreviousDocumentLength;
71
72   /** The position updater used to for the default updating of partitions */
73   protected DefaultPositionUpdater fPositionUpdater;
74
75   /** The offset at which the first changed partition starts */
76   protected int fStartOffset;
77
78   /** The offset at which the last changed partition ends */
79   protected int fEndOffset;
80
81   /** The offset at which a partition has been deleted */
82   protected int fDeleteOffset;
83
84   /**
85    * The position category this partitioner uses to store the document's partitioning information.
86    * 
87    * @since 3.0
88    */
89   private String fPositionCategory;
90
91   /**
92    * Creates a new partitioner that uses the given scanner and may return partitions of the given legal content types.
93    * 
94    * @param scanner
95    *          the scanner this partitioner is supposed to use
96    * @param legalContentTypes
97    *          the legal content types of this partitioner
98    */
99   public DefaultPHPPartitioner_delete_it(IPartitionTokenScanner htmlScanner, IPartitionTokenScanner phpScanner, String[] legalContentTypes,
100       String[] legalHTMLContentTypes, String[] legalPHPContentTypes) {
101     fHTMLScanner = htmlScanner;
102     fPHPScanner = phpScanner;
103     fLegalContentTypes = legalContentTypes;
104     fLegalHTMLContentTypes = legalHTMLContentTypes;
105     fLegalPHPContentTypes = legalPHPContentTypes;
106
107     fPositionCategory = CONTENT_TYPES_CATEGORY + hashCode();
108     fPositionUpdater = new DefaultPositionUpdater(fPositionCategory);
109   }
110
111   /*
112    * @see org.eclipse.jface.text.IDocumentPartitionerExtension2#getManagingPositionCategories()
113    * @since 3.0
114    */
115   public String[] getManagingPositionCategories() {
116     return new String[] { fPositionCategory };
117   }
118
119   /*
120    * @see IDocumentPartitioner#connect(IDocument)
121    */
122   public void connect(IDocument document) {
123     Assert.isNotNull(document);
124     Assert.isTrue(!document.containsPositionCategory(fPositionCategory));
125
126     fDocument = document;
127     fDocument.addPositionCategory(fPositionCategory);
128
129     initialize(fDocument.getLength());
130   }
131
132   /**
133    * Performs the initial partitioning of the partitioner's document.
134    */
135   protected void initialize(int initialLength) {
136     char ch;
137     boolean htmlMode = true;
138     int startPosition = 0;
139     int length = 0;
140     int i = 0;
141     try {
142       
143       while (i < initialLength) {
144         ch = fDocument.getChar(i++);
145         if (htmlMode) {
146           if (ch == '<' && fDocument.getChar(i) == '?' && fDocument.getChar(i + 1) == ' ') {
147             length = i - startPosition - 1;
148             if (length != 0) {
149               initializeHTML(startPosition, length);
150               startPosition = i - 1;
151             }
152             htmlMode = false;
153           } else if (ch == '<' && fDocument.getChar(i) == '?'
154               && (fDocument.getChar(i + 1) == 'p' || fDocument.getChar(i + 1) == 'P')
155               && (fDocument.getChar(i + 2) == 'h' || fDocument.getChar(i + 2) == 'H')
156               && (fDocument.getChar(i + 3) == 'p' || fDocument.getChar(i + 3) == 'P')) {
157             length = i - startPosition - 1;
158             if (length != 0) {
159               initializeHTML(startPosition, length);
160               startPosition = i - 1;
161             }
162             htmlMode = false;
163           }
164         } else {
165           switch (ch) {
166           case '"': // double quoted string
167             // read until end of double quoted string
168             while (i < fDocument.getLength()) {
169               if (fDocument.getChar(i++) == '"') {
170                 if (fDocument.getChar(i - 2) != '\\') {
171                   break;
172                 }
173               }
174             }
175             break;
176           case '\'': // single quoted string
177             // read until end of single quoted string
178             while (i < fDocument.getLength()) {
179               if (fDocument.getChar(i++) == '\'') {
180                 if (fDocument.getChar(i - 2) != '\\') {
181                   break;
182                 }
183               }
184             }
185             break;
186           case '/': // line comment
187             if (fDocument.getChar(i) == '/') {
188               i++;
189               // read until end of line
190               while (i < fDocument.getLength()) {
191                 if (fDocument.getChar(i++) == '\n') {
192                   break;
193                 }
194               }
195             } else if (fDocument.getChar(i) == '*') {
196               i++;
197               // read until end of comment
198               while (i < fDocument.getLength()) {
199                 if (fDocument.getChar(i++) == '*') {
200                   if (i < fDocument.getLength()) {
201                     if (fDocument.getChar(i) == '/') {
202                       break;
203                     }
204                   }
205                 }
206               }
207             }
208             break;
209           case '#': // line comment
210             // read until end of line
211             while (i < fDocument.getLength()) {
212               if (fDocument.getChar(i++) == '\n') {
213                 break;
214               }
215             }
216             break;
217           case '?':
218             if (fDocument.getChar(i) == '>') {
219               length = i - startPosition + 1;
220               if (length != 0) {
221                 initializePHP(startPosition, length);
222                 startPosition = i + 1;
223               }
224               htmlMode = true;
225             }
226             break;
227           }
228         }
229       }
230     } catch (BadLocationException x) {
231       // can happen but ignored
232     } finally {
233       if (startPosition<fDocument.getLength()) {
234         length = fDocument.getLength()-startPosition;
235         if (htmlMode) {
236           initializeHTML(startPosition, length);
237         } else {
238           initializePHP(startPosition, length);
239         }
240       }
241     }
242   }
243
244   protected void initializeHTML(int startOffset, int length) {
245
246     fHTMLScanner.setRange(fDocument, startOffset, length);
247
248     try {
249       IToken token = fHTMLScanner.nextToken();
250       while (!token.isEOF()) {
251
252         String contentType = getTokenContentType(token);
253
254         if (isSupportedHTMLContentType(contentType)) {
255           TypedPosition p = new TypedPosition(fHTMLScanner.getTokenOffset(), fHTMLScanner.getTokenLength(), contentType);
256           fDocument.addPosition(fPositionCategory, p);
257         }
258
259         token = fHTMLScanner.nextToken();
260       }
261     } catch (BadLocationException x) {
262       // cannot happen as offsets come from scanner
263     } catch (BadPositionCategoryException x) {
264       // cannot happen if document has been connected before
265     }
266   }
267
268   protected void initializePHP(int startOffset, int length) {
269
270     fPHPScanner.setRange(fDocument, startOffset, length);
271
272     try {
273       IToken token = fPHPScanner.nextToken();
274       while (!token.isEOF()) {
275
276         String contentType = getTokenContentType(token);
277
278         if (isSupportedHTMLContentType(contentType)) {
279           TypedPosition p = new TypedPosition(fPHPScanner.getTokenOffset(), fPHPScanner.getTokenLength(), contentType);
280           fDocument.addPosition(fPositionCategory, p);
281         }
282
283         token = fPHPScanner.nextToken();
284       }
285     } catch (BadLocationException x) {
286       // cannot happen as offsets come from scanner
287     } catch (BadPositionCategoryException x) {
288       // cannot happen if document has been connected before
289     }
290   }
291
292   /*
293    * @see IDocumentPartitioner#disconnect()
294    */
295   public void disconnect() {
296
297     Assert.isTrue(fDocument.containsPositionCategory(fPositionCategory));
298
299     try {
300       fDocument.removePositionCategory(fPositionCategory);
301     } catch (BadPositionCategoryException x) {
302       // can not happen because of Assert
303     }
304   }
305
306   /*
307    * @see IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
308    */
309   public void documentAboutToBeChanged(DocumentEvent e) {
310
311     Assert.isTrue(e.getDocument() == fDocument);
312
313     fPreviousDocumentLength = e.getDocument().getLength();
314     fStartOffset = -1;
315     fEndOffset = -1;
316     fDeleteOffset = -1;
317   }
318
319   /*
320    * @see IDocumentPartitioner#documentChanged(DocumentEvent)
321    */
322   public boolean documentChanged(DocumentEvent e) {
323     IRegion region = documentChanged2(e);
324     return (region != null);
325   }
326
327   /**
328    * Helper method for tracking the minimal region containing all partition changes. If <code>offset</code> is smaller than the
329    * remembered offset, <code>offset</code> will from now on be remembered. If <code>offset  + length</code> is greater than the
330    * remembered end offset, it will be remembered from now on.
331    * 
332    * @param offset
333    *          the offset
334    * @param length
335    *          the length
336    */
337   private void rememberRegion(int offset, int length) {
338     // remember start offset
339     if (fStartOffset == -1)
340       fStartOffset = offset;
341     else if (offset < fStartOffset)
342       fStartOffset = offset;
343
344     // remember end offset
345     int endOffset = offset + length;
346     if (fEndOffset == -1)
347       fEndOffset = endOffset;
348     else if (endOffset > fEndOffset)
349       fEndOffset = endOffset;
350   }
351
352   /**
353    * Remembers the given offset as the deletion offset.
354    * 
355    * @param offset
356    *          the offset
357    */
358   private void rememberDeletedOffset(int offset) {
359     fDeleteOffset = offset;
360   }
361
362   /**
363    * Creates the minimal region containing all partition changes using the remembered offset, end offset, and deletion offset.
364    * 
365    * @return the minimal region containing all the partition changes
366    */
367   private IRegion createRegion() {
368     if (fDeleteOffset == -1) {
369       if (fStartOffset == -1 || fEndOffset == -1)
370         return null;
371       return new Region(fStartOffset, fEndOffset - fStartOffset);
372     } else if (fStartOffset == -1 || fEndOffset == -1) {
373       return new Region(fDeleteOffset, 0);
374     } else {
375       int offset = Math.min(fDeleteOffset, fStartOffset);
376       int endOffset = Math.max(fDeleteOffset, fEndOffset);
377       return new Region(offset, endOffset - offset);
378     }
379   }
380
381   /*
382    * @see IDocumentPartitionerExtension#documentChanged2(DocumentEvent)
383    * @since 2.0
384    */
385   public IRegion documentChanged2(DocumentEvent e) {
386
387     try {
388
389       IDocument d = e.getDocument();
390       Position[] category = d.getPositions(fPositionCategory);
391       IRegion line = d.getLineInformationOfOffset(e.getOffset());
392       int reparseStart = line.getOffset();
393       int partitionStart = -1;
394       String contentType = null;
395       int newLength = e.getText() == null ? 0 : e.getText().length();
396
397       int first = d.computeIndexInCategory(fPositionCategory, reparseStart);
398       if (first > 0) {
399         TypedPosition partition = (TypedPosition) category[first - 1];
400         if (partition.includes(reparseStart)) {
401           partitionStart = partition.getOffset();
402           contentType = partition.getType();
403           if (e.getOffset() == partition.getOffset() + partition.getLength())
404             reparseStart = partitionStart;
405           --first;
406         } else if (reparseStart == e.getOffset() && reparseStart == partition.getOffset() + partition.getLength()) {
407           partitionStart = partition.getOffset();
408           contentType = partition.getType();
409           reparseStart = partitionStart;
410           --first;
411         } else {
412           partitionStart = partition.getOffset() + partition.getLength();
413           contentType = IDocument.DEFAULT_CONTENT_TYPE;
414         }
415       }
416
417       fPositionUpdater.update(e);
418       for (int i = first; i < category.length; i++) {
419         Position p = category[i];
420         if (p.isDeleted) {
421           rememberDeletedOffset(e.getOffset());
422           break;
423         }
424       }
425       category = d.getPositions(fPositionCategory);
426
427       
428       fHTMLScanner.setPartialRange(d, reparseStart, d.getLength() - reparseStart, contentType, partitionStart);
429
430       int lastScannedPosition = reparseStart;
431       IToken token = fHTMLScanner.nextToken();
432
433       while (!token.isEOF()) {
434
435         contentType = getTokenContentType(token);
436
437         if (!isSupportedContentType(contentType)) {
438           token = fHTMLScanner.nextToken();
439           continue;
440         }
441
442         int start = fHTMLScanner.getTokenOffset();
443         int length = fHTMLScanner.getTokenLength();
444
445         lastScannedPosition = start + length - 1;
446
447         // remove all affected positions
448         while (first < category.length) {
449           TypedPosition p = (TypedPosition) category[first];
450           if (lastScannedPosition >= p.offset + p.length
451               || (p.overlapsWith(start, length) && (!d.containsPosition(fPositionCategory, start, length) || !contentType.equals(p
452                   .getType())))) {
453
454             rememberRegion(p.offset, p.length);
455             d.removePosition(fPositionCategory, p);
456             ++first;
457
458           } else
459             break;
460         }
461
462         // if position already exists and we have scanned at least the
463         // area covered by the event, we are done
464         if (d.containsPosition(fPositionCategory, start, length)) {
465           if (lastScannedPosition >= e.getOffset() + newLength)
466             return createRegion();
467           ++first;
468         } else {
469           // insert the new type position
470           try {
471             d.addPosition(fPositionCategory, new TypedPosition(start, length, contentType));
472             rememberRegion(start, length);
473           } catch (BadPositionCategoryException x) {
474           } catch (BadLocationException x) {
475           }
476         }
477
478         token = fHTMLScanner.nextToken();
479       }
480
481       // remove all positions behind lastScannedPosition since there aren't any further types
482       if (lastScannedPosition != reparseStart) {
483         // if this condition is not met, nothing has been scanned because of a deletion
484         ++lastScannedPosition;
485       }
486       first = d.computeIndexInCategory(fPositionCategory, lastScannedPosition);
487
488       TypedPosition p;
489       while (first < category.length) {
490         p = (TypedPosition) category[first++];
491         d.removePosition(fPositionCategory, p);
492         rememberRegion(p.offset, p.length);
493       }
494
495     } catch (BadPositionCategoryException x) {
496       // should never happen on connected documents
497     } catch (BadLocationException x) {
498     }
499
500     return createRegion();
501   }
502
503   /**
504    * Returns the position in the partitoner's position category which is close to the given offset. This is, the position has either
505    * an offset which is the same as the given offset or an offset which is smaller than the given offset. This method profits from
506    * the knowledge that a partitioning is a ordered set of disjoint position.
507    * 
508    * @param offset
509    *          the offset for which to search the closest position
510    * @return the closest position in the partitioner's category
511    */
512   protected TypedPosition findClosestPosition(int offset) {
513
514     try {
515
516       int index = fDocument.computeIndexInCategory(fPositionCategory, offset);
517       Position[] category = fDocument.getPositions(fPositionCategory);
518
519       if (category.length == 0)
520         return null;
521
522       if (index < category.length) {
523         if (offset == category[index].offset)
524           return (TypedPosition) category[index];
525       }
526
527       if (index > 0)
528         index--;
529
530       return (TypedPosition) category[index];
531
532     } catch (BadPositionCategoryException x) {
533     } catch (BadLocationException x) {
534     }
535
536     return null;
537   }
538
539   /*
540    * @see IDocumentPartitioner#getContentType(int)
541    */
542   public String getContentType(int offset) {
543
544     TypedPosition p = findClosestPosition(offset);
545     if (p != null && p.includes(offset))
546       return p.getType();
547
548     return IDocument.DEFAULT_CONTENT_TYPE;
549   }
550
551   /*
552    * @see IDocumentPartitioner#getPartition(int)
553    */
554   public ITypedRegion getPartition(int offset) {
555
556     try {
557
558       Position[] category = fDocument.getPositions(fPositionCategory);
559
560       if (category == null || category.length == 0)
561         return new TypedRegion(0, fDocument.getLength(), IDocument.DEFAULT_CONTENT_TYPE);
562
563       int index = fDocument.computeIndexInCategory(fPositionCategory, offset);
564
565       if (index < category.length) {
566
567         TypedPosition next = (TypedPosition) category[index];
568
569         if (offset == next.offset)
570           return new TypedRegion(next.getOffset(), next.getLength(), next.getType());
571
572         if (index == 0)
573           return new TypedRegion(0, next.offset, IDocument.DEFAULT_CONTENT_TYPE);
574
575         TypedPosition previous = (TypedPosition) category[index - 1];
576         if (previous.includes(offset))
577           return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType());
578
579         int endOffset = previous.getOffset() + previous.getLength();
580         return new TypedRegion(endOffset, next.getOffset() - endOffset, IDocument.DEFAULT_CONTENT_TYPE);
581       }
582
583       TypedPosition previous = (TypedPosition) category[category.length - 1];
584       if (previous.includes(offset))
585         return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType());
586
587       int endOffset = previous.getOffset() + previous.getLength();
588       return new TypedRegion(endOffset, fDocument.getLength() - endOffset, IDocument.DEFAULT_CONTENT_TYPE);
589
590     } catch (BadPositionCategoryException x) {
591     } catch (BadLocationException x) {
592     }
593
594     return new TypedRegion(0, fDocument.getLength(), IDocument.DEFAULT_CONTENT_TYPE);
595   }
596
597   /*
598    * @see IDocumentPartitioner#computePartitioning(int, int)
599    */
600   public ITypedRegion[] computePartitioning(int offset, int length) {
601     return computePartitioning(offset, length, false);
602   }
603
604   /*
605    * @see IDocumentPartitioner#getLegalContentTypes()
606    */
607   public String[] getLegalContentTypes() {
608     return fLegalContentTypes;
609   }
610
611   public String[] getLegalHTMLContentTypes() {
612     return fLegalHTMLContentTypes;
613   }
614
615   public String[] getLegalPHPContentTypes() {
616     return fLegalPHPContentTypes;
617   }
618
619   /**
620    * Returns whether the given type is one of the legal content types.
621    * 
622    * @param contentType
623    *          the content type to check
624    * @return <code>true</code> if the content type is a legal content type
625    */
626   protected boolean isSupportedContentType(String contentType) {
627     if (contentType != null) {
628       for (int i = 0; i < fLegalContentTypes.length; i++) {
629         if (fLegalHTMLContentTypes[i].equals(contentType))
630           return true;
631       }
632     }
633
634     return false;
635   }
636   
637   /**
638    * Returns whether the given type is one of the legal content types.
639    * 
640    * @param contentType
641    *          the content type to check
642    * @return <code>true</code> if the content type is a legal content type
643    */
644   protected boolean isSupportedHTMLContentType(String contentType) {
645     if (contentType != null) {
646       for (int i = 0; i < fLegalHTMLContentTypes.length; i++) {
647         if (fLegalHTMLContentTypes[i].equals(contentType))
648           return true;
649       }
650     }
651
652     return false;
653   }
654
655   /**
656    * Returns whether the given type is one of the legal content types.
657    * 
658    * @param contentType
659    *          the content type to check
660    * @return <code>true</code> if the content type is a legal content type
661    */
662   protected boolean isSupportedPHPContentType(String contentType) {
663     if (contentType != null) {
664       for (int i = 0; i < fLegalHTMLContentTypes.length; i++) {
665         if (fLegalHTMLContentTypes[i].equals(contentType))
666           return true;
667       }
668     }
669
670     return false;
671   }
672
673   /**
674    * Returns a content type encoded in the given token. If the token's data is not <code>null</code> and a string it is assumed
675    * that it is the encoded content type.
676    * 
677    * @param token
678    *          the token whose content type is to be determined
679    * @return the token's content type
680    */
681   protected String getTokenContentType(IToken token) {
682     Object data = token.getData();
683     if (data instanceof String)
684       return (String) data;
685     return null;
686   }
687
688   /* zero-length partition support */
689
690   /*
691    * @see org.eclipse.jface.text.IDocumentPartitionerExtension2#getContentType(int)
692    * @since 3.0
693    */
694   public String getContentType(int offset, boolean preferOpenPartitions) {
695     return getPartition(offset, preferOpenPartitions).getType();
696   }
697
698   /*
699    * @see org.eclipse.jface.text.IDocumentPartitionerExtension2#getPartition(int)
700    * @since 3.0
701    */
702   public ITypedRegion getPartition(int offset, boolean preferOpenPartitions) {
703     ITypedRegion region = getPartition(offset);
704     if (preferOpenPartitions) {
705       if (region.getOffset() == offset && !region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE)) {
706         if (offset > 0) {
707           region = getPartition(offset - 1);
708           if (region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE))
709             return region;
710         }
711         return new TypedRegion(offset, 0, IDocument.DEFAULT_CONTENT_TYPE);
712       }
713     }
714     return region;
715   }
716
717   /*
718    * @see org.eclipse.jface.text.IDocumentPartitionerExtension2#computePartitioning(int, int, boolean)
719    * @since 3.0
720    */
721   public ITypedRegion[] computePartitioning(int offset, int length, boolean includeZeroLengthPartitions) {
722     List list = new ArrayList();
723
724     try {
725
726       int endOffset = offset + length;
727
728       Position[] category = fDocument.getPositions(fPositionCategory);
729
730       TypedPosition previous = null, current = null;
731       int start, end, gapOffset;
732       Position gap = new Position(0);
733
734       int startIndex = getFirstIndexEndingAfterOffset(category, offset);
735       int endIndex = getFirstIndexStartingAfterOffset(category, endOffset);
736       for (int i = startIndex; i < endIndex; i++) {
737
738         current = (TypedPosition) category[i];
739
740         gapOffset = (previous != null) ? previous.getOffset() + previous.getLength() : 0;
741         gap.setOffset(gapOffset);
742         gap.setLength(current.getOffset() - gapOffset);
743         if ((includeZeroLengthPartitions && overlapsOrTouches(gap, offset, length))
744             || (gap.getLength() > 0 && gap.overlapsWith(offset, length))) {
745           start = Math.max(offset, gapOffset);
746           end = Math.min(endOffset, gap.getOffset() + gap.getLength());
747           list.add(new TypedRegion(start, end - start, IDocument.DEFAULT_CONTENT_TYPE));
748         }
749
750         if (current.overlapsWith(offset, length)) {
751           start = Math.max(offset, current.getOffset());
752           end = Math.min(endOffset, current.getOffset() + current.getLength());
753           list.add(new TypedRegion(start, end - start, current.getType()));
754         }
755
756         previous = current;
757       }
758
759       if (previous != null) {
760         gapOffset = previous.getOffset() + previous.getLength();
761         gap.setOffset(gapOffset);
762         gap.setLength(fDocument.getLength() - gapOffset);
763         if ((includeZeroLengthPartitions && overlapsOrTouches(gap, offset, length))
764             || (gap.getLength() > 0 && gap.overlapsWith(offset, length))) {
765           start = Math.max(offset, gapOffset);
766           end = Math.min(endOffset, fDocument.getLength());
767           list.add(new TypedRegion(start, end - start, IDocument.DEFAULT_CONTENT_TYPE));
768         }
769       }
770
771       if (list.isEmpty())
772         list.add(new TypedRegion(offset, length, IDocument.DEFAULT_CONTENT_TYPE));
773
774     } catch (BadPositionCategoryException x) {
775     }
776
777     TypedRegion[] result = new TypedRegion[list.size()];
778     list.toArray(result);
779     return result;
780   }
781
782   /**
783    * Returns <code>true</code> if the given ranges overlap with or touch each other.
784    * 
785    * @param gap
786    *          the first range
787    * @param offset
788    *          the offset of the second range
789    * @param length
790    *          the length of the second range
791    * @return <code>true</code> if the given ranges overlap with or touch each other
792    * @since 3.0
793    */
794   private boolean overlapsOrTouches(Position gap, int offset, int length) {
795     return gap.getOffset() <= offset + length && offset <= gap.getOffset() + gap.getLength();
796   }
797
798   /**
799    * Returns the index of the first position which ends after the given offset.
800    * 
801    * @param positions
802    *          the positions in linear order
803    * @param offset
804    *          the offset
805    * @return the index of the first position which ends after the offset
806    * 
807    * @since 3.0
808    */
809   private int getFirstIndexEndingAfterOffset(Position[] positions, int offset) {
810     int i = -1, j = positions.length;
811     while (j - i > 1) {
812       int k = (i + j) >> 1;
813       Position p = positions[k];
814       if (p.getOffset() + p.getLength() > offset)
815         j = k;
816       else
817         i = k;
818     }
819     return j;
820   }
821
822   /**
823    * Returns the index of the first position which starts at or after the given offset.
824    * 
825    * @param positions
826    *          the positions in linear order
827    * @param offset
828    *          the offset
829    * @return the index of the first position which starts after the offset
830    * 
831    * @since 3.0
832    */
833   private int getFirstIndexStartingAfterOffset(Position[] positions, int offset) {
834     int i = -1, j = positions.length;
835     while (j - i > 1) {
836       int k = (i + j) >> 1;
837       Position p = positions[k];
838       if (p.getOffset() >= offset)
839         j = k;
840       else
841         i = k;
842     }
843     return j;
844   }
845 }