2 * Copyright (c) 2002-2004 Widespace, OU 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://solareclipse.sourceforge.net/legal/cpl-v10.html
9 * Igor Malinin - initial contribution
11 * $Id: AbstractPartitioner.java,v 1.4 2006-10-21 23:13:53 pombredanne Exp $
14 package net.sourceforge.phpeclipse.ui.text.rules;
16 import java.util.ArrayList;
17 import java.util.List;
20 //import org.eclipse.jface.text.Assert;
21 import org.eclipse.core.runtime.Assert;
22 import org.eclipse.jface.text.DocumentEvent;
23 import org.eclipse.jface.text.IDocument;
24 import org.eclipse.jface.text.IDocumentPartitioner;
25 import org.eclipse.jface.text.IDocumentPartitionerExtension;
26 import org.eclipse.jface.text.IRegion;
27 import org.eclipse.jface.text.ITypedRegion;
28 import org.eclipse.jface.text.Region;
29 import org.eclipse.jface.text.TypedRegion;
30 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
31 import org.eclipse.jface.text.rules.IToken;
34 * Advanced partitioner which maintains partitions as views to connected
35 * document. Views have own partitioners themselves. This class is designed as a
36 * base for complex partitioners such as for JSP, PHP, ASP, etc. languages.
38 * @author Igor Malinin
40 public abstract class AbstractPartitioner implements IDocumentPartitioner,
41 IDocumentPartitionerExtension {
42 public final static boolean DEBUG = false;
44 /** Partition scanner */
45 protected IPartitionTokenScanner scanner;
47 /** Connected document */
48 protected IDocument document;
50 /** Flat structure of the document */
51 protected List nodes = new ArrayList();
53 /** The offset at which the first changed partition starts */
54 protected int regionStart;
56 /** The offset at which the last changed partition ends */
57 protected int regionEnd;
59 public AbstractPartitioner(IPartitionTokenScanner scanner) {
60 this.scanner = scanner;
63 protected FlatNode createNode(String type, int offset, int length) {
65 Assert.isTrue(offset >= 0, Integer.toString(offset));
67 FlatNode node = new FlatNode(type);
73 protected void addInnerRegion(FlatNode position) {
74 nodes.add(computeFlatNodeIndex(position.offset), position);
77 protected void removeInnerRegion(FlatNode position) {
78 nodes.remove(position); // TODO: Indexed remove?
81 protected void deleteInnerRegion(FlatNode position) {
82 nodes.remove(position); // TODO: Indexed remove?
85 protected void resizeInnerRegion(FlatNode position) {
89 * @see org.eclipse.jface.text.IDocumentPartitioner#connect(IDocument)
91 public void connect(IDocument document) {
92 this.document = document;
98 * @see org.eclipse.jface.text.IDocumentPartitioner#disconnect()
100 public void disconnect() {
106 * Performs the initial partitioning of the partitioner's document.
108 protected void initialize() {
109 scanner.setRange(document, 0, document.getLength());
113 IToken token = scanner.nextToken();
114 while (!token.isEOF()) {
115 String contentType = getTokenContentType(token);
117 if (isSupportedContentType(contentType)) {
118 addInnerRegion(createNode(contentType,
119 scanner.getTokenOffset(), scanner.getTokenLength()));
122 token = scanner.nextToken();
127 * @see org.eclipse.jface.text.IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
129 public void documentAboutToBeChanged(DocumentEvent event) {
130 regionStart = regionEnd = -1;
134 * @see org.eclipse.jface.text.IDocumentPartitioner#documentChanged(DocumentEvent)
136 public boolean documentChanged(DocumentEvent event) {
137 return (documentChanged2(event) != null);
141 * @see org.eclipse.jface.text.IDocumentPartitionerExtension#documentChanged2(DocumentEvent)
143 public IRegion documentChanged2(DocumentEvent event) {
144 int first = fixupPartitions(event);
146 FlatNode[] category = (FlatNode[]) nodes.toArray(new FlatNode[nodes
149 // repartition changed region
151 String contentType = IDocument.DEFAULT_CONTENT_TYPE;
156 offset = 0; // Bug #697414: first offset
158 offset = event.getOffset();
160 FlatNode partition = category[first - 1];
161 if (partition.includes(offset)) {
162 offset = partition.offset;
163 contentType = partition.type;
165 } else if (offset == partition.offset + partition.length) {
166 offset = partition.offset;
167 contentType = partition.type;
170 offset = partition.offset + partition.length;
174 // should not be changed since last conversion
175 // category = (FlatNode[]) nodes.toArray(new FlatNode[nodes.size()]);
177 Assert.isTrue(offset >= 0, Integer.toString(offset));
179 scanner.setPartialRange(document, offset, document.getLength(),
180 contentType, offset);
182 int lastScannedPosition = offset;
183 IToken token = scanner.nextToken();
184 while (!token.isEOF()) {
185 contentType = getTokenContentType(token);
187 if (!isSupportedContentType(contentType)) {
188 token = scanner.nextToken();
192 offset = scanner.getTokenOffset();
194 Assert.isTrue(offset >= 0, scanner.toString());
196 int length = scanner.getTokenLength();
198 lastScannedPosition = offset + length;
200 // remove all affected positions
201 while (first < category.length) {
202 FlatNode p = category[first];
203 if (p.offset + p.length < lastScannedPosition
204 || (p.overlapsWith(offset, length) && (!containsPosition(
205 offset, length) || !contentType.equals(p.type)))) {
206 removeInnerRegion(p);
207 rememberRegion(p.offset, p.length);
214 // if position already exists we are done
215 if (containsPosition(offset, length)) {
216 if (lastScannedPosition > event.getOffset()) {
217 // TODO: optional repartition till end of doc
218 return createRegion();
223 // insert the new type position
224 addInnerRegion(createNode(contentType, offset, length));
225 rememberRegion(offset, length);
228 token = scanner.nextToken();
229 // } catch (ArrayIndexOutOfBoundsException e) {
230 // System.out.println(this.getClass().toString());
235 // remove all positions behind lastScannedPosition
236 // since there aren't any further types
238 // Do not need to recalculate (lost remove events)!
239 // first = computeIndexInInnerDocuments(lastScannedPosition);
240 while (first < category.length) {
241 FlatNode p = category[first++];
242 removeInnerRegion(p);
243 rememberRegion(p.offset, p.length);
246 return createRegion();
249 protected int fixupPartitions(DocumentEvent event) {
250 int offset = event.getOffset();
251 int length = event.getLength();
252 int end = offset + length;
254 // fixup flat nodes laying on change boundaries
256 int first = computeFlatNodeIndex(offset);
258 FlatNode p = (FlatNode) nodes.get(first - 1);
260 int right = p.offset + p.length;
261 if (offset < right) {
262 // change overlaps with partition
264 // cahnge completely inside partition
265 String text = event.getText();
268 p.length += text.length();
271 // cut partition at right
272 int cut = p.offset + p.length - offset;
278 int last = computeFlatNodeIndex(end);
280 FlatNode p = (FlatNode) nodes.get(last - 1);
282 int right = p.offset + p.length;
284 // cut partition at left
285 int cut = end - p.offset;
289 String text = event.getText();
291 p.offset += text.length();
298 // fixup flat nodes laying afrer change
300 String text = event.getText();
302 length -= text.length();
305 for (int i = last, size = nodes.size(); i < size; i++) {
306 ((FlatNode) nodes.get(i)).offset -= length;
309 // delete flat nodes laying completely inside change boundaries
313 deleteInnerRegion((FlatNode) nodes.get(--last));
314 } while (first < last);
316 rememberRegion(offset, 0);
323 * Returns whether the given type is one of the legal content types.
326 * the content type to check
327 * @return <code>true</code> if the content type is a legal content type
329 protected boolean isSupportedContentType(String contentType) {
330 /* TODO: implementation */
331 // if (contentType != null) {
332 // for (int i= 0; i < fLegalContentTypes.length; i++) {
333 // if (fLegalContentTypes[i].equals(contentType)) {
339 return (contentType != null);
343 * Returns a content type encoded in the given token. If the token's data is
344 * not <code>null</code> and a string it is assumed that it is the encoded
348 * the token whose content type is to be determined
349 * @return the token's content type
351 protected String getTokenContentType(IToken token) {
352 Object data = token.getData();
353 if (data instanceof String) {
354 return (String) data;
361 * @see org.eclipse.jface.text.IDocumentPartitioner#getLegalContentTypes()
363 public String[] getLegalContentTypes() {
364 // TODO: implementation
369 * @see org.eclipse.jface.text.IDocumentPartitioner#getContentType(int)
371 public String getContentType(int offset) {
372 return getPartition(offset).getType();
376 * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
378 public ITypedRegion getPartition(int offset) {
379 if (nodes.size() == 0) {
380 return new TypedRegion(0, document.getLength(),
381 IDocument.DEFAULT_CONTENT_TYPE);
384 int index = computeFlatNodeIndex(offset);
385 if (index < nodes.size()) {
386 FlatNode next = (FlatNode) nodes.get(index);
388 if (offset == next.offset) {
389 return new TypedRegion(next.offset, next.length, next.type);
393 return new TypedRegion(0, next.offset,
394 IDocument.DEFAULT_CONTENT_TYPE);
397 FlatNode prev = (FlatNode) nodes.get(index - 1);
399 if (prev.includes(offset)) {
400 return new TypedRegion(prev.offset, prev.length, prev.type);
403 int end = prev.offset + prev.length;
404 return new TypedRegion(end, next.offset - end,
405 IDocument.DEFAULT_CONTENT_TYPE);
408 FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1);
410 if (prev.includes(offset)) {
411 return new TypedRegion(prev.offset, prev.length, prev.type);
414 int end = prev.offset + prev.length;
416 return new TypedRegion(end, document.getLength() - end,
417 IDocument.DEFAULT_CONTENT_TYPE);
421 * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int,
424 public ITypedRegion[] computePartitioning(int offset, int length) {
425 List list = new ArrayList();
427 int end = offset + length;
429 int index = computeFlatNodeIndex(offset);
431 FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1)
435 if (prev.overlapsWith(offset, length)) {
436 list.add(new TypedRegion(prev.offset, prev.length,
440 if (end <= prev.offset + prev.length) {
445 FlatNode next = (index < nodes.size()) ? (FlatNode) nodes
448 if (next == null || offset < next.offset) {
450 int off1 = offset + length;
452 if (prev != null && off0 < prev.offset + prev.length) {
453 off0 = prev.offset + prev.length;
456 if (next != null && next.offset < off1) {
461 list.add(new TypedRegion(off0, off1 - off0,
462 IDocument.DEFAULT_CONTENT_TYPE));
473 return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]);
477 * Computes the index in the list of flat nodes at which an flat node with
478 * the given offset would be inserted. The flat node is supposed to become
479 * the first in this list of all flat nodes with the same offset.
482 * the offset for which the index is computed
483 * @return the computed index
485 protected int computeFlatNodeIndex(int offset) {
486 if (nodes.size() == 0) {
490 int left = 0, mid = 0;
491 int right = nodes.size() - 1;
495 while (left < right) {
496 mid = (left + right) / 2;
498 p = (FlatNode) nodes.get(mid);
500 if (offset < p.offset) {
501 right = (left == mid) ? left : mid - 1;
502 } else if (offset > p.offset) {
503 left = (right == mid) ? right : mid + 1;
504 } else if (offset == p.offset) {
510 p = (FlatNode) nodes.get(pos);
511 if (offset > p.offset) {
515 // entry will became the first of all entries with the same offset
521 p = (FlatNode) nodes.get(pos);
522 } while (offset == p.offset);
529 public boolean containsPosition(int offset, int length) {
530 int size = nodes.size();
535 int index = computeFlatNodeIndex(offset);
537 FlatNode p = (FlatNode) nodes.get(index);
539 while (p.offset == offset) {
540 if (p.length == length) {
544 if (++index < size) {
545 p = (FlatNode) nodes.get(index);
556 * Helper method for tracking the minimal region containg all partition
557 * changes. If <code>offset</code> is smaller than the remembered offset,
558 * <code>offset</code> will from now on be remembered. If
559 * <code>offset + length</code> is greater than the remembered end offset,
560 * it will be remembered from now on.
567 protected final void rememberRegion(int offset, int length) {
568 // remember start offset
569 if (regionStart == -1) {
570 regionStart = offset;
571 } else if (offset < regionStart) {
572 regionStart = offset;
575 // remember end offset
576 int endOffset = offset + length;
578 if (regionEnd == -1) {
579 regionEnd = endOffset;
580 } else if (endOffset > regionEnd) {
581 regionEnd = endOffset;
586 * Creates the minimal region containing all partition changes using the
587 * remembered offsets.
589 * @return the minimal region containing all the partition changes
591 protected final IRegion createRegion() {
592 if (regionStart == -1 || regionEnd == -1) {
596 return new Region(regionStart, regionEnd - regionStart);