Mercurial > projects > dwt-addons
comparison dwtx/jface/text/rules/RuleBasedPartitioner.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
comparison
equal
deleted
inserted
replaced
128:8df1d4193877 | 129:eb30df5ca28b |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2000, 2006 IBM Corporation and others. | |
3 * All rights reserved. This program and the accompanying materials | |
4 * are made available under the terms of the Eclipse Public License v1.0 | |
5 * which accompanies this distribution, and is available at | |
6 * http://www.eclipse.org/legal/epl-v10.html | |
7 * | |
8 * Contributors: | |
9 * IBM Corporation - initial API and implementation | |
10 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 | |
14 module dwtx.jface.text.rules.RuleBasedPartitioner; | |
15 | |
16 import dwt.dwthelper.utils; | |
17 | |
18 | |
19 import java.util.ArrayList; | |
20 import java.util.List; | |
21 | |
22 import dwtx.core.runtime.Assert; | |
23 import dwtx.jface.text.BadLocationException; | |
24 import dwtx.jface.text.BadPositionCategoryException; | |
25 import dwtx.jface.text.DefaultPositionUpdater; | |
26 import dwtx.jface.text.DocumentEvent; | |
27 import dwtx.jface.text.IDocument; | |
28 import dwtx.jface.text.IDocumentPartitioner; | |
29 import dwtx.jface.text.IDocumentPartitionerExtension; | |
30 import dwtx.jface.text.IDocumentPartitionerExtension2; | |
31 import dwtx.jface.text.IRegion; | |
32 import dwtx.jface.text.ITypedRegion; | |
33 import dwtx.jface.text.Position; | |
34 import dwtx.jface.text.Region; | |
35 import dwtx.jface.text.TextUtilities; | |
36 import dwtx.jface.text.TypedPosition; | |
37 import dwtx.jface.text.TypedRegion; | |
38 | |
39 | |
40 | |
41 /** | |
42 * A standard implementation of a syntax driven document partitioner. | |
43 * It uses a rule based scanner to scan the document and to determine | |
44 * the document's partitioning. The tokens returned by the rules the | |
45 * scanner is configured with are supposed to return the partition type | |
46 * as their data. The partitioner remembers the document's partitions | |
47 * in the document itself rather than maintaining its own data structure. | |
48 * | |
49 * @see IRule | |
50 * @see RuleBasedScanner | |
51 * | |
52 * @deprecated use <code>FastPartitioner</code> instead | |
53 */ | |
54 public class RuleBasedPartitioner : IDocumentPartitioner, IDocumentPartitionerExtension, IDocumentPartitionerExtension2 { | |
55 | |
56 /** | |
57 * The position category this partitioner uses to store the document's partitioning information | |
58 * @deprecated As of 3.0, use <code>getManagingPositionCategories()</code>. | |
59 */ | |
60 public final static String CONTENT_TYPES_CATEGORY= "__content_types_category"; //$NON-NLS-1$ | |
61 | |
62 | |
63 /** The partitioner's scanner */ | |
64 protected RuleBasedScanner fScanner; | |
65 /** The legal content types of this partitioner */ | |
66 protected String[] fLegalContentTypes; | |
67 /** The partitioner's document */ | |
68 protected IDocument fDocument; | |
69 /** The document length before a document change occurred */ | |
70 protected int fPreviousDocumentLength; | |
71 /** The position updater used to for the default updating of partitions */ | |
72 protected DefaultPositionUpdater fPositionUpdater; | |
73 /** The offset at which the first changed partition starts */ | |
74 protected int fStartOffset; | |
75 /** The offset at which the last changed partition ends */ | |
76 protected int fEndOffset; | |
77 /**The offset at which a partition has been deleted */ | |
78 protected int fDeleteOffset; | |
79 /** | |
80 * The position category for managing partitioning information. | |
81 * @since 3.0 | |
82 */ | |
83 private String fPositionCategory; | |
84 | |
85 | |
86 /** | |
87 * Creates a new partitioner that uses the given scanner and may return | |
88 * partitions of the given legal content types. | |
89 * | |
90 * @param scanner the scanner this partitioner is supposed to use | |
91 * @param legalContentTypes the legal content types of this partitioner | |
92 */ | |
93 public RuleBasedPartitioner(RuleBasedScanner scanner, String[] legalContentTypes) { | |
94 fScanner= scanner; | |
95 fLegalContentTypes= TextUtilities.copy(legalContentTypes); | |
96 fPositionCategory= CONTENT_TYPES_CATEGORY + hashCode(); | |
97 fPositionUpdater= new DefaultPositionUpdater(fPositionCategory); | |
98 } | |
99 | |
100 /* | |
101 * @see dwtx.jface.text.IDocumentPartitionerExtension2#getManagingPositionCategories() | |
102 * @since 3.0 | |
103 */ | |
104 public String[] getManagingPositionCategories() { | |
105 return new String[] { fPositionCategory }; | |
106 } | |
107 | |
108 /* | |
109 * @see IDocumentPartitioner#connect | |
110 */ | |
111 public void connect(IDocument document) { | |
112 Assert.isNotNull(document); | |
113 Assert.isTrue(!document.containsPositionCategory(fPositionCategory)); | |
114 | |
115 fDocument= document; | |
116 fDocument.addPositionCategory(fPositionCategory); | |
117 | |
118 initialize(); | |
119 } | |
120 | |
121 /** | |
122 * Performs the initial partitioning of the partitioner's document. | |
123 */ | |
124 protected void initialize() { | |
125 | |
126 fScanner.setRange(fDocument, 0, fDocument.getLength()); | |
127 | |
128 try { | |
129 IToken token= fScanner.nextToken(); | |
130 while (!token.isEOF()) { | |
131 | |
132 String contentType= getTokenContentType(token); | |
133 | |
134 if (isSupportedContentType(contentType)) { | |
135 TypedPosition p= new TypedPosition(fScanner.getTokenOffset(), fScanner.getTokenLength(), contentType); | |
136 fDocument.addPosition(fPositionCategory, p); | |
137 } | |
138 | |
139 token= fScanner.nextToken(); | |
140 } | |
141 } catch (BadLocationException x) { | |
142 // cannot happen as offsets come from scanner | |
143 } catch (BadPositionCategoryException x) { | |
144 // cannot happen if document has been connected before | |
145 } | |
146 } | |
147 | |
148 /* | |
149 * @see IDocumentPartitioner#disconnect | |
150 */ | |
151 public void disconnect() { | |
152 | |
153 Assert.isTrue(fDocument.containsPositionCategory(fPositionCategory)); | |
154 | |
155 try { | |
156 fDocument.removePositionCategory(fPositionCategory); | |
157 } catch (BadPositionCategoryException x) { | |
158 // can not happen because of Assert | |
159 } | |
160 } | |
161 | |
162 /* | |
163 * @see IDocumentPartitioner#documentAboutToBeChanged | |
164 */ | |
165 public void documentAboutToBeChanged(DocumentEvent e) { | |
166 | |
167 Assert.isTrue(e.getDocument() is fDocument); | |
168 | |
169 fPreviousDocumentLength= e.getDocument().getLength(); | |
170 fStartOffset= -1; | |
171 fEndOffset= -1; | |
172 fDeleteOffset= -1; | |
173 } | |
174 | |
175 /* | |
176 * @see IDocumentPartitioner#documentChanged | |
177 */ | |
178 public bool documentChanged(DocumentEvent e) { | |
179 IRegion region= documentChanged2(e); | |
180 return (region !is null); | |
181 } | |
182 | |
183 /** | |
184 * Helper method for tracking the minimal region containing all partition changes. | |
185 * If <code>offset</code> is smaller than the remembered offset, <code>offset</code> | |
186 * will from now on be remembered. If <code>offset + length</code> is greater than | |
187 * the remembered end offset, it will be remembered from now on. | |
188 * | |
189 * @param offset the offset | |
190 * @param length the length | |
191 */ | |
192 private void rememberRegion(int offset, int length) { | |
193 // remember start offset | |
194 if (fStartOffset is -1) | |
195 fStartOffset= offset; | |
196 else if (offset < fStartOffset) | |
197 fStartOffset= offset; | |
198 | |
199 // remember end offset | |
200 int endOffset= offset + length; | |
201 if (fEndOffset is -1) | |
202 fEndOffset= endOffset; | |
203 else if (endOffset > fEndOffset) | |
204 fEndOffset= endOffset; | |
205 } | |
206 | |
207 /** | |
208 * Remembers the given offset as the deletion offset. | |
209 * | |
210 * @param offset the offset | |
211 */ | |
212 private void rememberDeletedOffset(int offset) { | |
213 fDeleteOffset= offset; | |
214 } | |
215 | |
216 /** | |
217 * Creates the minimal region containing all partition changes using the | |
218 * remembered offset, end offset, and deletion offset. | |
219 * @return the minimal region containing all the partition changes | |
220 */ | |
221 private IRegion createRegion() { | |
222 if (fDeleteOffset is -1) { | |
223 if (fStartOffset is -1 || fEndOffset is -1) | |
224 return null; | |
225 return new Region(fStartOffset, fEndOffset - fStartOffset); | |
226 } else if (fStartOffset is -1 || fEndOffset is -1) { | |
227 return new Region(fDeleteOffset, 0); | |
228 } else { | |
229 int offset= Math.min(fDeleteOffset, fStartOffset); | |
230 int endOffset= Math.max(fDeleteOffset, fEndOffset); | |
231 return new Region(offset, endOffset - offset); | |
232 } | |
233 } | |
234 | |
235 /* | |
236 * @see IDocumentPartitionerExtension#documentChanged2(DocumentEvent) | |
237 * @since 2.0 | |
238 */ | |
239 public IRegion documentChanged2(DocumentEvent e) { | |
240 | |
241 try { | |
242 | |
243 IDocument d= e.getDocument(); | |
244 Position[] category= d.getPositions(fPositionCategory); | |
245 int first= 0; | |
246 int reparseStart= 0; | |
247 int originalSize= category.length; | |
248 | |
249 if (originalSize > 0) { | |
250 | |
251 /* | |
252 * determine character position at which the scanner starts: | |
253 * first position behind the last non-default partition the actual position is not involved with | |
254 */ | |
255 | |
256 first= d.computeIndexInCategory(fPositionCategory, e.getOffset()); | |
257 | |
258 Position p= null; | |
259 do { | |
260 --first; | |
261 if (first < 0) | |
262 break; | |
263 | |
264 p= category[first]; | |
265 | |
266 } while (p.overlapsWith(e.getOffset(), e.getLength()) || | |
267 (e.getOffset() is fPreviousDocumentLength && | |
268 (p.getOffset() + p.getLength() is fPreviousDocumentLength))); | |
269 | |
270 fPositionUpdater.update(e); | |
271 for (int i= 0; i < category.length; i++) { | |
272 p= category[i]; | |
273 if (p.isDeleted) { | |
274 rememberDeletedOffset(e.getOffset()); | |
275 break; | |
276 } | |
277 } | |
278 category= d.getPositions(fPositionCategory); | |
279 | |
280 if (first >= 0) { | |
281 p= category[first]; | |
282 reparseStart= p.getOffset() + p.getLength(); | |
283 } | |
284 | |
285 ++first; | |
286 } | |
287 | |
288 fScanner.setRange(d, reparseStart, d.getLength() - reparseStart); | |
289 | |
290 int lastScannedPosition= reparseStart; | |
291 IToken token= fScanner.nextToken(); | |
292 | |
293 while (!token.isEOF()) { | |
294 | |
295 | |
296 String contentType= getTokenContentType(token); | |
297 | |
298 if (!isSupportedContentType(contentType)) { | |
299 token= fScanner.nextToken(); | |
300 continue; | |
301 } | |
302 | |
303 int start= fScanner.getTokenOffset(); | |
304 int length= fScanner.getTokenLength(); | |
305 | |
306 lastScannedPosition= start + length - 1; | |
307 | |
308 // remove all affected positions | |
309 while (first < category.length) { | |
310 TypedPosition p= (TypedPosition) category[first]; | |
311 if (lastScannedPosition >= p.offset + p.length || | |
312 (p.overlapsWith(start, length) && | |
313 (!d.containsPosition(fPositionCategory, start, length) || | |
314 !contentType.equals(p.getType())))) { | |
315 | |
316 rememberRegion(p.offset, p.length); | |
317 d.removePosition(fPositionCategory, p); | |
318 ++ first; | |
319 | |
320 } else | |
321 break; | |
322 } | |
323 | |
324 // if position already exists we are done | |
325 if (d.containsPosition(fPositionCategory, start, length)) | |
326 return createRegion(); | |
327 | |
328 // insert the new type position | |
329 try { | |
330 d.addPosition(fPositionCategory, new TypedPosition(start, length, contentType)); | |
331 rememberRegion(start, length); | |
332 } catch (BadPositionCategoryException x) { | |
333 } catch (BadLocationException x) { | |
334 } | |
335 | |
336 token= fScanner.nextToken(); | |
337 } | |
338 | |
339 | |
340 // remove all positions behind lastScannedPosition since there aren't any further types | |
341 if (lastScannedPosition !is reparseStart) { | |
342 // if this condition is not met, nothing has been scanned because of a delete | |
343 ++ lastScannedPosition; | |
344 } | |
345 first= d.computeIndexInCategory(fPositionCategory, lastScannedPosition); | |
346 | |
347 TypedPosition p; | |
348 while (first < category.length) { | |
349 p= (TypedPosition) category[first++]; | |
350 d.removePosition(fPositionCategory, p); | |
351 rememberRegion(p.offset, p.length); | |
352 } | |
353 | |
354 } catch (BadPositionCategoryException x) { | |
355 // should never happen on connected documents | |
356 } catch (BadLocationException x) { | |
357 } | |
358 | |
359 return createRegion(); | |
360 } | |
361 | |
362 | |
363 /** | |
364 * Returns the position in the partitoner's position category which is | |
365 * close to the given offset. This is, the position has either an offset which | |
366 * is the same as the given offset or an offset which is smaller than the given | |
367 * offset. This method profits from the knowledge that a partitioning is | |
368 * a ordered set of disjoint position. | |
369 * | |
370 * @param offset the offset for which to search the closest position | |
371 * @return the closest position in the partitioner's category | |
372 */ | |
373 protected TypedPosition findClosestPosition(int offset) { | |
374 | |
375 try { | |
376 | |
377 int index= fDocument.computeIndexInCategory(fPositionCategory, offset); | |
378 Position[] category= fDocument.getPositions(fPositionCategory); | |
379 | |
380 if (category.length is 0) | |
381 return null; | |
382 | |
383 if (index < category.length) { | |
384 if (offset is category[index].offset) | |
385 return (TypedPosition) category[index]; | |
386 } | |
387 | |
388 if (index > 0) | |
389 index--; | |
390 | |
391 return (TypedPosition) category[index]; | |
392 | |
393 } catch (BadPositionCategoryException x) { | |
394 } catch (BadLocationException x) { | |
395 } | |
396 | |
397 return null; | |
398 } | |
399 | |
400 | |
401 /* | |
402 * @see IDocumentPartitioner#getContentType | |
403 */ | |
404 public String getContentType(int offset) { | |
405 | |
406 TypedPosition p= findClosestPosition(offset); | |
407 if (p !is null && p.includes(offset)) | |
408 return p.getType(); | |
409 | |
410 return IDocument.DEFAULT_CONTENT_TYPE; | |
411 } | |
412 | |
413 /* | |
414 * @see IDocumentPartitioner#getPartition | |
415 */ | |
416 public ITypedRegion getPartition(int offset) { | |
417 | |
418 try { | |
419 | |
420 Position[] category = fDocument.getPositions(fPositionCategory); | |
421 | |
422 if (category is null || category.length is 0) | |
423 return new TypedRegion(0, fDocument.getLength(), IDocument.DEFAULT_CONTENT_TYPE); | |
424 | |
425 int index= fDocument.computeIndexInCategory(fPositionCategory, offset); | |
426 | |
427 if (index < category.length) { | |
428 | |
429 TypedPosition next= (TypedPosition) category[index]; | |
430 | |
431 if (offset is next.offset) | |
432 return new TypedRegion(next.getOffset(), next.getLength(), next.getType()); | |
433 | |
434 if (index is 0) | |
435 return new TypedRegion(0, next.offset, IDocument.DEFAULT_CONTENT_TYPE); | |
436 | |
437 TypedPosition previous= (TypedPosition) category[index - 1]; | |
438 if (previous.includes(offset)) | |
439 return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType()); | |
440 | |
441 int endOffset= previous.getOffset() + previous.getLength(); | |
442 return new TypedRegion(endOffset, next.getOffset() - endOffset, IDocument.DEFAULT_CONTENT_TYPE); | |
443 } | |
444 | |
445 TypedPosition previous= (TypedPosition) category[category.length - 1]; | |
446 if (previous.includes(offset)) | |
447 return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType()); | |
448 | |
449 int endOffset= previous.getOffset() + previous.getLength(); | |
450 return new TypedRegion(endOffset, fDocument.getLength() - endOffset, IDocument.DEFAULT_CONTENT_TYPE); | |
451 | |
452 } catch (BadPositionCategoryException x) { | |
453 } catch (BadLocationException x) { | |
454 } | |
455 | |
456 return new TypedRegion(0, fDocument.getLength(), IDocument.DEFAULT_CONTENT_TYPE); | |
457 } | |
458 | |
459 /* | |
460 * @see IDocumentPartitioner#computePartitioning | |
461 */ | |
462 public ITypedRegion[] computePartitioning(int offset, int length) { | |
463 return computePartitioning(offset, length, false); | |
464 } | |
465 | |
466 /* | |
467 * @see IDocumentPartitioner#getLegalContentTypes | |
468 */ | |
469 public String[] getLegalContentTypes() { | |
470 return TextUtilities.copy(fLegalContentTypes); | |
471 } | |
472 | |
473 /** | |
474 * Returns whether the given type is one of the legal content types. | |
475 * | |
476 * @param contentType the content type to check | |
477 * @return <code>true</code> if the content type is a legal content type | |
478 */ | |
479 protected bool isSupportedContentType(String contentType) { | |
480 if (contentType !is null) { | |
481 for (int i= 0; i < fLegalContentTypes.length; i++) { | |
482 if (fLegalContentTypes[i].equals(contentType)) | |
483 return true; | |
484 } | |
485 } | |
486 | |
487 return false; | |
488 } | |
489 | |
490 /** | |
491 * Returns a content type encoded in the given token. If the token's | |
492 * data is not <code>null</code> and a string it is assumed that | |
493 * it is the encoded content type. | |
494 * | |
495 * @param token the token whose content type is to be determined | |
496 * @return the token's content type | |
497 */ | |
498 protected String getTokenContentType(IToken token) { | |
499 Object data= token.getData(); | |
500 if (data instanceof String) | |
501 return (String) data; | |
502 return null; | |
503 } | |
504 | |
505 /* zero-length partition support */ | |
506 | |
507 /* | |
508 * @see dwtx.jface.text.IDocumentPartitionerExtension2#getContentType(int) | |
509 * @since 3.0 | |
510 */ | |
511 public String getContentType(int offset, bool preferOpenPartitions) { | |
512 return getPartition(offset, preferOpenPartitions).getType(); | |
513 } | |
514 | |
515 /* | |
516 * @see dwtx.jface.text.IDocumentPartitionerExtension2#getPartition(int) | |
517 * @since 3.0 | |
518 */ | |
519 public ITypedRegion getPartition(int offset, bool preferOpenPartitions) { | |
520 ITypedRegion region= getPartition(offset); | |
521 if (preferOpenPartitions) { | |
522 if (region.getOffset() is offset && !region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE)) { | |
523 if (offset > 0) { | |
524 region= getPartition(offset - 1); | |
525 if (region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE)) | |
526 return region; | |
527 } | |
528 return new TypedRegion(offset, 0, IDocument.DEFAULT_CONTENT_TYPE); | |
529 } | |
530 } | |
531 return region; | |
532 } | |
533 | |
534 /* | |
535 * @see dwtx.jface.text.IDocumentPartitionerExtension2#computePartitioning(int, int) | |
536 * @since 3.0 | |
537 */ | |
538 public ITypedRegion[] computePartitioning(int offset, int length, bool includeZeroLengthPartitions) { | |
539 List list= new ArrayList(); | |
540 | |
541 try { | |
542 | |
543 int endOffset= offset + length; | |
544 | |
545 Position[] category= fDocument.getPositions(fPositionCategory); | |
546 | |
547 TypedPosition previous= null, current= null; | |
548 int start, end, gapOffset; | |
549 Position gap= null; | |
550 | |
551 for (int i= 0; i < category.length; i++) { | |
552 | |
553 current= (TypedPosition) category[i]; | |
554 | |
555 gapOffset= (previous !is null) ? previous.getOffset() + previous.getLength() : 0; | |
556 gap= new Position(gapOffset, current.getOffset() - gapOffset); | |
557 if ((includeZeroLengthPartitions || gap.getLength() > 0) && gap.overlapsWith(offset, length)) { | |
558 start= Math.max(offset, gapOffset); | |
559 end= Math.min(endOffset, gap.getOffset() + gap.getLength()); | |
560 list.add(new TypedRegion(start, end - start, IDocument.DEFAULT_CONTENT_TYPE)); | |
561 } | |
562 | |
563 if (current.overlapsWith(offset, length)) { | |
564 start= Math.max(offset, current.getOffset()); | |
565 end= Math.min(endOffset, current.getOffset() + current.getLength()); | |
566 list.add(new TypedRegion(start, end - start, current.getType())); | |
567 } | |
568 | |
569 previous= current; | |
570 } | |
571 | |
572 if (previous !is null) { | |
573 gapOffset= previous.getOffset() + previous.getLength(); | |
574 gap= new Position(gapOffset, fDocument.getLength() - gapOffset); | |
575 if ((includeZeroLengthPartitions || gap.getLength() > 0) && ((includeZeroLengthPartitions && offset + length is gapOffset && gap.length is 0) || gap.overlapsWith(offset, length))) { | |
576 start= Math.max(offset, gapOffset); | |
577 end= Math.min(endOffset, fDocument.getLength()); | |
578 list.add(new TypedRegion(start, end - start, IDocument.DEFAULT_CONTENT_TYPE)); | |
579 } | |
580 } | |
581 | |
582 if (list.isEmpty()) | |
583 list.add(new TypedRegion(offset, length, IDocument.DEFAULT_CONTENT_TYPE)); | |
584 | |
585 } catch (BadPositionCategoryException x) { | |
586 } | |
587 | |
588 TypedRegion[] result= new TypedRegion[list.size()]; | |
589 list.toArray(result); | |
590 return result; | |
591 } | |
592 } |