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
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpeclipse.phpeditor.php;
13 import java.util.ArrayList;
14 import java.util.List;
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;
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.
39 * @see IPartitionTokenScanner
42 public class DefaultPHPPartitioner_delete_it implements IDocumentPartitioner, IDocumentPartitionerExtension, IDocumentPartitionerExtension2 {
45 * The position category this partitioner uses to store the document's partitioning information.
47 * @deprecated As of 3.0, use <code>getManagingPositionCategories()</code> instead.
49 public final static String CONTENT_TYPES_CATEGORY = "__content_types_category"; //$NON-NLS-1$
51 /** The HTML areas partitioner's scanner */
52 protected IPartitionTokenScanner fHTMLScanner;
54 /** The PHP areas partitioner's scanner */
55 protected IPartitionTokenScanner fPHPScanner;
57 /** The legal content types of both partitioners */
58 protected String[] fLegalContentTypes;
60 /** The legal content types of the HTML partitioner */
61 protected String[] fLegalHTMLContentTypes;
63 /** The legal content types of the PHP partitioner */
64 protected String[] fLegalPHPContentTypes;
66 /** The partitioner's document */
67 protected IDocument fDocument;
69 /** The document length before a document change occurred */
70 protected int fPreviousDocumentLength;
72 /** The position updater used to for the default updating of partitions */
73 protected DefaultPositionUpdater fPositionUpdater;
75 /** The offset at which the first changed partition starts */
76 protected int fStartOffset;
78 /** The offset at which the last changed partition ends */
79 protected int fEndOffset;
81 /** The offset at which a partition has been deleted */
82 protected int fDeleteOffset;
85 * The position category this partitioner uses to store the document's partitioning information.
89 private String fPositionCategory;
92 * Creates a new partitioner that uses the given scanner and may return partitions of the given legal content types.
95 * the scanner this partitioner is supposed to use
96 * @param legalContentTypes
97 * the legal content types of this partitioner
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;
107 fPositionCategory = CONTENT_TYPES_CATEGORY + hashCode();
108 fPositionUpdater = new DefaultPositionUpdater(fPositionCategory);
112 * @see org.eclipse.jface.text.IDocumentPartitionerExtension2#getManagingPositionCategories()
115 public String[] getManagingPositionCategories() {
116 return new String[] { fPositionCategory };
120 * @see IDocumentPartitioner#connect(IDocument)
122 public void connect(IDocument document) {
123 Assert.isNotNull(document);
124 Assert.isTrue(!document.containsPositionCategory(fPositionCategory));
126 fDocument = document;
127 fDocument.addPositionCategory(fPositionCategory);
129 initialize(fDocument.getLength());
133 * Performs the initial partitioning of the partitioner's document.
135 protected void initialize(int initialLength) {
137 boolean htmlMode = true;
138 int startPosition = 0;
143 while (i < initialLength) {
144 ch = fDocument.getChar(i++);
146 if (ch == '<' && fDocument.getChar(i) == '?' && fDocument.getChar(i + 1) == ' ') {
147 length = i - startPosition - 1;
149 initializeHTML(startPosition, length);
150 startPosition = i - 1;
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;
159 initializeHTML(startPosition, length);
160 startPosition = i - 1;
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) != '\\') {
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) != '\\') {
186 case '/': // line comment
187 if (fDocument.getChar(i) == '/') {
189 // read until end of line
190 while (i < fDocument.getLength()) {
191 if (fDocument.getChar(i++) == '\n') {
195 } else if (fDocument.getChar(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) == '/') {
209 case '#': // line comment
210 // read until end of line
211 while (i < fDocument.getLength()) {
212 if (fDocument.getChar(i++) == '\n') {
218 if (fDocument.getChar(i) == '>') {
219 length = i - startPosition + 1;
221 initializePHP(startPosition, length);
222 startPosition = i + 1;
230 } catch (BadLocationException x) {
231 // can happen but ignored
233 if (startPosition<fDocument.getLength()) {
234 length = fDocument.getLength()-startPosition;
236 initializeHTML(startPosition, length);
238 initializePHP(startPosition, length);
244 protected void initializeHTML(int startOffset, int length) {
246 fHTMLScanner.setRange(fDocument, startOffset, length);
249 IToken token = fHTMLScanner.nextToken();
250 while (!token.isEOF()) {
252 String contentType = getTokenContentType(token);
254 if (isSupportedHTMLContentType(contentType)) {
255 TypedPosition p = new TypedPosition(fHTMLScanner.getTokenOffset(), fHTMLScanner.getTokenLength(), contentType);
256 fDocument.addPosition(fPositionCategory, p);
259 token = fHTMLScanner.nextToken();
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
268 protected void initializePHP(int startOffset, int length) {
270 fPHPScanner.setRange(fDocument, startOffset, length);
273 IToken token = fPHPScanner.nextToken();
274 while (!token.isEOF()) {
276 String contentType = getTokenContentType(token);
278 if (isSupportedHTMLContentType(contentType)) {
279 TypedPosition p = new TypedPosition(fPHPScanner.getTokenOffset(), fPHPScanner.getTokenLength(), contentType);
280 fDocument.addPosition(fPositionCategory, p);
283 token = fPHPScanner.nextToken();
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
293 * @see IDocumentPartitioner#disconnect()
295 public void disconnect() {
297 Assert.isTrue(fDocument.containsPositionCategory(fPositionCategory));
300 fDocument.removePositionCategory(fPositionCategory);
301 } catch (BadPositionCategoryException x) {
302 // can not happen because of Assert
307 * @see IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
309 public void documentAboutToBeChanged(DocumentEvent e) {
311 Assert.isTrue(e.getDocument() == fDocument);
313 fPreviousDocumentLength = e.getDocument().getLength();
320 * @see IDocumentPartitioner#documentChanged(DocumentEvent)
322 public boolean documentChanged(DocumentEvent e) {
323 IRegion region = documentChanged2(e);
324 return (region != null);
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.
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;
344 // remember end offset
345 int endOffset = offset + length;
346 if (fEndOffset == -1)
347 fEndOffset = endOffset;
348 else if (endOffset > fEndOffset)
349 fEndOffset = endOffset;
353 * Remembers the given offset as the deletion offset.
358 private void rememberDeletedOffset(int offset) {
359 fDeleteOffset = offset;
363 * Creates the minimal region containing all partition changes using the remembered offset, end offset, and deletion offset.
365 * @return the minimal region containing all the partition changes
367 private IRegion createRegion() {
368 if (fDeleteOffset == -1) {
369 if (fStartOffset == -1 || fEndOffset == -1)
371 return new Region(fStartOffset, fEndOffset - fStartOffset);
372 } else if (fStartOffset == -1 || fEndOffset == -1) {
373 return new Region(fDeleteOffset, 0);
375 int offset = Math.min(fDeleteOffset, fStartOffset);
376 int endOffset = Math.max(fDeleteOffset, fEndOffset);
377 return new Region(offset, endOffset - offset);
382 * @see IDocumentPartitionerExtension#documentChanged2(DocumentEvent)
385 public IRegion documentChanged2(DocumentEvent e) {
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();
397 int first = d.computeIndexInCategory(fPositionCategory, reparseStart);
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;
406 } else if (reparseStart == e.getOffset() && reparseStart == partition.getOffset() + partition.getLength()) {
407 partitionStart = partition.getOffset();
408 contentType = partition.getType();
409 reparseStart = partitionStart;
412 partitionStart = partition.getOffset() + partition.getLength();
413 contentType = IDocument.DEFAULT_CONTENT_TYPE;
417 fPositionUpdater.update(e);
418 for (int i = first; i < category.length; i++) {
419 Position p = category[i];
421 rememberDeletedOffset(e.getOffset());
425 category = d.getPositions(fPositionCategory);
428 fHTMLScanner.setPartialRange(d, reparseStart, d.getLength() - reparseStart, contentType, partitionStart);
430 int lastScannedPosition = reparseStart;
431 IToken token = fHTMLScanner.nextToken();
433 while (!token.isEOF()) {
435 contentType = getTokenContentType(token);
437 if (!isSupportedContentType(contentType)) {
438 token = fHTMLScanner.nextToken();
442 int start = fHTMLScanner.getTokenOffset();
443 int length = fHTMLScanner.getTokenLength();
445 lastScannedPosition = start + length - 1;
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
454 rememberRegion(p.offset, p.length);
455 d.removePosition(fPositionCategory, p);
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();
469 // insert the new type position
471 d.addPosition(fPositionCategory, new TypedPosition(start, length, contentType));
472 rememberRegion(start, length);
473 } catch (BadPositionCategoryException x) {
474 } catch (BadLocationException x) {
478 token = fHTMLScanner.nextToken();
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;
486 first = d.computeIndexInCategory(fPositionCategory, lastScannedPosition);
489 while (first < category.length) {
490 p = (TypedPosition) category[first++];
491 d.removePosition(fPositionCategory, p);
492 rememberRegion(p.offset, p.length);
495 } catch (BadPositionCategoryException x) {
496 // should never happen on connected documents
497 } catch (BadLocationException x) {
500 return createRegion();
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.
509 * the offset for which to search the closest position
510 * @return the closest position in the partitioner's category
512 protected TypedPosition findClosestPosition(int offset) {
516 int index = fDocument.computeIndexInCategory(fPositionCategory, offset);
517 Position[] category = fDocument.getPositions(fPositionCategory);
519 if (category.length == 0)
522 if (index < category.length) {
523 if (offset == category[index].offset)
524 return (TypedPosition) category[index];
530 return (TypedPosition) category[index];
532 } catch (BadPositionCategoryException x) {
533 } catch (BadLocationException x) {
540 * @see IDocumentPartitioner#getContentType(int)
542 public String getContentType(int offset) {
544 TypedPosition p = findClosestPosition(offset);
545 if (p != null && p.includes(offset))
548 return IDocument.DEFAULT_CONTENT_TYPE;
552 * @see IDocumentPartitioner#getPartition(int)
554 public ITypedRegion getPartition(int offset) {
558 Position[] category = fDocument.getPositions(fPositionCategory);
560 if (category == null || category.length == 0)
561 return new TypedRegion(0, fDocument.getLength(), IDocument.DEFAULT_CONTENT_TYPE);
563 int index = fDocument.computeIndexInCategory(fPositionCategory, offset);
565 if (index < category.length) {
567 TypedPosition next = (TypedPosition) category[index];
569 if (offset == next.offset)
570 return new TypedRegion(next.getOffset(), next.getLength(), next.getType());
573 return new TypedRegion(0, next.offset, IDocument.DEFAULT_CONTENT_TYPE);
575 TypedPosition previous = (TypedPosition) category[index - 1];
576 if (previous.includes(offset))
577 return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType());
579 int endOffset = previous.getOffset() + previous.getLength();
580 return new TypedRegion(endOffset, next.getOffset() - endOffset, IDocument.DEFAULT_CONTENT_TYPE);
583 TypedPosition previous = (TypedPosition) category[category.length - 1];
584 if (previous.includes(offset))
585 return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType());
587 int endOffset = previous.getOffset() + previous.getLength();
588 return new TypedRegion(endOffset, fDocument.getLength() - endOffset, IDocument.DEFAULT_CONTENT_TYPE);
590 } catch (BadPositionCategoryException x) {
591 } catch (BadLocationException x) {
594 return new TypedRegion(0, fDocument.getLength(), IDocument.DEFAULT_CONTENT_TYPE);
598 * @see IDocumentPartitioner#computePartitioning(int, int)
600 public ITypedRegion[] computePartitioning(int offset, int length) {
601 return computePartitioning(offset, length, false);
605 * @see IDocumentPartitioner#getLegalContentTypes()
607 public String[] getLegalContentTypes() {
608 return fLegalContentTypes;
611 public String[] getLegalHTMLContentTypes() {
612 return fLegalHTMLContentTypes;
615 public String[] getLegalPHPContentTypes() {
616 return fLegalPHPContentTypes;
620 * Returns whether the given type is one of the legal content types.
623 * the content type to check
624 * @return <code>true</code> if the content type is a legal content type
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))
638 * Returns whether the given type is one of the legal content types.
641 * the content type to check
642 * @return <code>true</code> if the content type is a legal content type
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))
656 * Returns whether the given type is one of the legal content types.
659 * the content type to check
660 * @return <code>true</code> if the content type is a legal content type
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))
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.
678 * the token whose content type is to be determined
679 * @return the token's content type
681 protected String getTokenContentType(IToken token) {
682 Object data = token.getData();
683 if (data instanceof String)
684 return (String) data;
688 /* zero-length partition support */
691 * @see org.eclipse.jface.text.IDocumentPartitionerExtension2#getContentType(int)
694 public String getContentType(int offset, boolean preferOpenPartitions) {
695 return getPartition(offset, preferOpenPartitions).getType();
699 * @see org.eclipse.jface.text.IDocumentPartitionerExtension2#getPartition(int)
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)) {
707 region = getPartition(offset - 1);
708 if (region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE))
711 return new TypedRegion(offset, 0, IDocument.DEFAULT_CONTENT_TYPE);
718 * @see org.eclipse.jface.text.IDocumentPartitionerExtension2#computePartitioning(int, int, boolean)
721 public ITypedRegion[] computePartitioning(int offset, int length, boolean includeZeroLengthPartitions) {
722 List list = new ArrayList();
726 int endOffset = offset + length;
728 Position[] category = fDocument.getPositions(fPositionCategory);
730 TypedPosition previous = null, current = null;
731 int start, end, gapOffset;
732 Position gap = new Position(0);
734 int startIndex = getFirstIndexEndingAfterOffset(category, offset);
735 int endIndex = getFirstIndexStartingAfterOffset(category, endOffset);
736 for (int i = startIndex; i < endIndex; i++) {
738 current = (TypedPosition) category[i];
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));
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()));
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));
772 list.add(new TypedRegion(offset, length, IDocument.DEFAULT_CONTENT_TYPE));
774 } catch (BadPositionCategoryException x) {
777 TypedRegion[] result = new TypedRegion[list.size()];
778 list.toArray(result);
783 * Returns <code>true</code> if the given ranges overlap with or touch each other.
788 * the offset of the second range
790 * the length of the second range
791 * @return <code>true</code> if the given ranges overlap with or touch each other
794 private boolean overlapsOrTouches(Position gap, int offset, int length) {
795 return gap.getOffset() <= offset + length && offset <= gap.getOffset() + gap.getLength();
799 * Returns the index of the first position which ends after the given offset.
802 * the positions in linear order
805 * @return the index of the first position which ends after the offset
809 private int getFirstIndexEndingAfterOffset(Position[] positions, int offset) {
810 int i = -1, j = positions.length;
812 int k = (i + j) >> 1;
813 Position p = positions[k];
814 if (p.getOffset() + p.getLength() > offset)
823 * Returns the index of the first position which starts at or after the given offset.
826 * the positions in linear order
829 * @return the index of the first position which starts after the offset
833 private int getFirstIndexStartingAfterOffset(Position[] positions, int offset) {
834 int i = -1, j = positions.length;
836 int k = (i + j) >> 1;
837 Position p = positions[k];
838 if (p.getOffset() >= offset)