Mercurial > projects > dwt-addons
annotate dwtx/text/edits/TextEdit.d @ 160:3678e4f1a766
toHash, toString
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Wed, 27 Aug 2008 02:07:22 +0200 |
parents | 7926b636c282 |
children | f8d52b926852 |
rev | line source |
---|---|
129 | 1 /******************************************************************************* |
2 * Copyright (c) 2000, 2008 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 module dwtx.text.edits.TextEdit; | |
14 | |
131 | 15 import dwtx.text.edits.TextEditCopier; // packageimport |
16 import dwtx.text.edits.TextEditProcessor; // packageimport | |
17 import dwtx.text.edits.TextEditVisitor; // packageimport | |
18 import dwtx.text.edits.UndoEdit; // packageimport | |
159 | 19 import dwtx.text.edits.MalformedTreeException; // packageimport |
20 import dwtx.text.edits.TextEditMessages; // packageimport | |
131 | 21 |
129 | 22 import dwt.dwthelper.utils; |
153
f70d9508c95c
Fix java Collection imports
Frank Benoit <benoit@tionex.de>
parents:
150
diff
changeset
|
23 import dwtx.dwtxhelper.Collection; |
159 | 24 import tango.text.convert.Format; |
129 | 25 |
26 import dwtx.core.runtime.Assert; | |
27 import dwtx.jface.text.BadLocationException; | |
28 import dwtx.jface.text.IDocument; | |
29 import dwtx.jface.text.IRegion; | |
30 import dwtx.jface.text.Region; | |
31 | |
32 | |
33 /** | |
34 * A text edit describes an elementary text manipulation operation. Edits are | |
35 * executed by applying them to a document (e.g. an instance of <code>IDocument | |
36 * </code>). | |
37 * <p> | |
38 * Text edits form a tree. Clients can navigate the tree upwards, from child to | |
39 * parent, as well as downwards. Newly created edits are un-parented. New edits | |
40 * are added to the tree by calling one of the <code>add</code> methods on a parent | |
41 * edit. | |
42 * </p> | |
43 * <p> | |
44 * An edit tree is well formed in the following sense: | |
45 * <ul> | |
46 * <li>a parent edit covers all its children</li> | |
47 * <li>children don't overlap</li> | |
48 * <li>an edit with length 0 can't have any children</li> | |
49 * </ul> | |
50 * Any manipulation of the tree that violates one of the above requirements results | |
51 * in a <code>MalformedTreeException</code>. | |
52 * </p> | |
53 * <p> | |
54 * Insert edits are represented by an edit of length 0. If more than one insert | |
55 * edit exists at the same offset then the edits are executed in the order in which | |
56 * they have been added to a parent. The following code example: | |
57 * <pre> | |
58 * IDocument document= new Document("org"); | |
59 * MultiTextEdit edit= new MultiTextEdit(); | |
60 * edit.addChild(new InsertEdit(0, "www.")); | |
61 * edit.addChild(new InsertEdit(0, "eclipse.")); | |
62 * edit.apply(document); | |
63 * </pre> | |
64 * therefore results in string: "www.eclipse.org". | |
65 * </p> | |
66 * <p> | |
67 * Text edits can be executed in a mode where the edit's region is updated to | |
68 * reflect the edit's position in the changed document. Region updating is enabled | |
69 * by default or can be requested by passing <code>UPDATE_REGIONS</code> to the | |
70 * {@link #apply(IDocument, int) apply(IDocument, int)} method. In the above example | |
71 * the region of the <code>InsertEdit(0, "eclipse.")</code> edit after executing | |
72 * the root edit is <code>[3, 8]</code>. If the region of an edit got deleted during | |
73 * change execution the region is set to <code>[-1, -1]</code> and the method {@link | |
74 * #isDeleted() isDeleted} returns <code>true</code>. | |
75 * </p> | |
76 * This class isn't intended to be subclassed outside of the edit framework. Clients | |
77 * are only allowed to subclass <code>MultiTextEdit</code>. | |
78 * | |
79 * @since 3.0 | |
80 * @noextend This class is not intended to be subclassed by clients. | |
81 */ | |
82 public abstract class TextEdit { | |
83 | |
84 /** | |
85 * Flags indicating that neither <code>CREATE_UNDO</code> nor | |
86 * <code>UPDATE_REGIONS</code> is set. | |
87 */ | |
147 | 88 public static const int NONE= 0; |
129 | 89 |
90 /** | |
91 * Flags indicating that applying an edit tree to a document | |
92 * is supposed to create a corresponding undo edit. If not | |
93 * specified <code>null</code> is returned from method <code> | |
94 * apply</code>. | |
95 */ | |
147 | 96 public static const int CREATE_UNDO= 1 << 0; |
129 | 97 |
98 /** | |
99 * Flag indicating that the edit's region will be updated to | |
100 * reflect its position in the changed document. If not specified | |
101 * when applying an edit tree to a document the edit's region will | |
102 * be arbitrary. It is even not guaranteed that the tree is still | |
103 * well formed. | |
104 */ | |
147 | 105 public static const int UPDATE_REGIONS= 1 << 1; |
129 | 106 |
107 private static class InsertionComparator : Comparator { | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
108 public int compare(Object o1, Object o2) { |
134 | 109 TextEdit edit1= cast(TextEdit)o1; |
110 TextEdit edit2= cast(TextEdit)o2; | |
129 | 111 |
112 int offset1= edit1.getOffset(); | |
113 int length1= edit1.getLength(); | |
114 | |
115 int offset2= edit2.getOffset(); | |
116 int length2= edit2.getLength(); | |
117 | |
118 if (offset1 is offset2 && length1 is 0 && length2 is 0) { | |
119 return 0; | |
120 } | |
121 if (offset1 + length1 <= offset2) { | |
122 return -1; | |
123 } | |
124 if (offset2 + length2 <= offset1) { | |
125 return 1; | |
126 } | |
127 throw new MalformedTreeException( | |
128 null, edit1, | |
129 TextEditMessages.getString("TextEdit.overlapping")); //$NON-NLS-1$ | |
130 } | |
131 } | |
132 | |
147 | 133 private static const TextEdit[] EMPTY_ARRAY= new TextEdit[0]; |
134 private static const InsertionComparator INSERTION_COMPARATOR= new InsertionComparator(); | |
129 | 135 |
147 | 136 private static const int DELETED_VALUE= -1; |
129 | 137 |
138 private int fOffset; | |
139 private int fLength; | |
140 | |
141 private TextEdit fParent; | |
142 private List fChildren; | |
143 | |
144 int fDelta; | |
145 | |
146 /** | |
147 * Create a new text edit. Parent is initialized to <code> | |
148 * null<code> and the edit doesn't have any children. | |
149 * | |
150 * @param offset the edit's offset | |
151 * @param length the edit's length | |
152 */ | |
130 | 153 protected this(int offset, int length) { |
129 | 154 Assert.isTrue(offset >= 0 && length >= 0); |
155 fOffset= offset; | |
156 fLength= length; | |
157 fDelta= 0; | |
158 } | |
159 | |
160 /** | |
161 * Copy constructor | |
162 * | |
163 * @param source the source to copy form | |
164 */ | |
130 | 165 protected this(TextEdit source) { |
129 | 166 fOffset= source.fOffset; |
167 fLength= source.fLength; | |
168 fDelta= 0; | |
169 } | |
170 | |
171 //---- Region management ----------------------------------------------- | |
172 | |
173 /** | |
174 * Returns the range that this edit is manipulating. The returned | |
175 * <code>IRegion</code> contains the edit's offset and length at | |
176 * the point in time when this call is made. Any subsequent changes | |
177 * to the edit's offset and length aren't reflected in the returned | |
178 * region object. | |
179 * <p> | |
180 * Creating a region for a deleted edit will result in an assertion | |
181 * failure. | |
182 * | |
183 * @return the manipulated region | |
184 */ | |
185 public final IRegion getRegion() { | |
186 return new Region(getOffset(), getLength()); | |
187 } | |
188 | |
189 /** | |
190 * Returns the offset of the edit. An offset is a 0-based | |
191 * character index. Returns <code>-1</code> if the edit | |
192 * is marked as deleted. | |
193 * | |
194 * @return the offset of the edit | |
195 */ | |
196 public int getOffset() { | |
197 return fOffset; | |
198 } | |
199 | |
200 /** | |
201 * Returns the length of the edit. Returns <code>-1</code> | |
202 * if the edit is marked as deleted. | |
203 * | |
204 * @return the length of the edit | |
205 */ | |
206 public int getLength() { | |
207 return fLength; | |
208 } | |
209 | |
210 /** | |
211 * Returns the inclusive end position of this edit. The inclusive end | |
212 * position denotes the last character of the region manipulated by | |
213 * this edit. The returned value is the result of the following | |
214 * calculation: | |
215 * <pre> | |
216 * getOffset() + getLength() - 1; | |
217 * <pre> | |
218 * | |
219 * @return the inclusive end position | |
220 */ | |
221 public final int getInclusiveEnd() { | |
222 return getOffset() + getLength() - 1; | |
223 } | |
224 | |
225 /** | |
226 * Returns the exclusive end position of this edit. The exclusive end | |
227 * position denotes the next character of the region manipulated by | |
228 * this edit. The returned value is the result of the following | |
229 * calculation: | |
230 * <pre> | |
231 * getOffset() + getLength(); | |
232 * </pre> | |
233 * | |
234 * @return the exclusive end position | |
235 */ | |
236 public final int getExclusiveEnd() { | |
237 return getOffset() + getLength(); | |
238 } | |
239 | |
240 /** | |
241 * Returns whether this edit has been deleted or not. | |
242 * | |
243 * @return <code>true</code> if the edit has been | |
244 * deleted; otherwise <code>false</code> is returned. | |
245 */ | |
246 public final bool isDeleted() { | |
247 return fOffset is DELETED_VALUE && fLength is DELETED_VALUE; | |
248 } | |
249 | |
250 /** | |
251 * Move all offsets in the tree by the given delta. This node must be a | |
252 * root node. The resulting offsets must be greater or equal to zero. | |
253 * | |
254 * @param delta the delta | |
255 * @since 3.1 | |
256 */ | |
257 public final void moveTree(int delta) { | |
258 Assert.isTrue(fParent is null); | |
259 Assert.isTrue(getOffset() + delta >= 0); | |
260 internalMoveTree(delta); | |
261 } | |
262 | |
263 /** | |
264 * Returns <code>true</code> if the edit covers the given edit | |
265 * <code>other</code>. It is up to the concrete text edit to | |
266 * decide if a edit of length zero can cover another edit. | |
267 * | |
268 * @param other the other edit | |
269 * @return <code>true<code> if the edit covers the other edit; | |
270 * otherwise <code>false</code> is returned. | |
271 */ | |
272 public bool covers(TextEdit other) { | |
273 if (getLength() is 0 && !canZeroLengthCover()) | |
274 return false; | |
275 | |
276 if (!other.isDefined()) | |
277 return true; | |
278 | |
279 int thisOffset= getOffset(); | |
280 int otherOffset= other.getOffset(); | |
281 return thisOffset <= otherOffset && otherOffset + other.getLength() <= thisOffset + getLength(); | |
282 } | |
283 | |
284 /** | |
285 * Returns <code>true</code> if an edit with length zero can cover | |
286 * another edit. Returns <code>false</code> otherwise. | |
287 * | |
288 * @return whether an edit of length zero can cover another edit | |
289 */ | |
290 protected bool canZeroLengthCover() { | |
291 return false; | |
292 } | |
293 | |
294 /** | |
295 * Returns whether the region of this edit is defined or not. | |
296 * | |
297 * @return whether the region of this edit is defined or not | |
298 * | |
299 * @since 3.1 | |
300 */ | |
301 bool isDefined() { | |
302 return true; | |
303 } | |
304 | |
305 //---- parent and children management ----------------------------- | |
306 | |
307 /** | |
308 * Returns the edit's parent. The method returns <code>null</code> | |
309 * if this edit hasn't been add to another edit. | |
310 * | |
311 * @return the edit's parent | |
312 */ | |
313 public final TextEdit getParent() { | |
314 return fParent; | |
315 } | |
316 | |
317 /** | |
318 * Returns the root edit of the edit tree. | |
319 * | |
320 * @return the root edit of the edit tree | |
321 * @since 3.1 | |
322 */ | |
323 public final TextEdit getRoot() { | |
324 TextEdit result= this; | |
325 while (result.fParent !is null) { | |
326 result= result.fParent; | |
327 } | |
328 return result; | |
329 } | |
330 | |
331 /** | |
332 * Adds the given edit <code>child</code> to this edit. | |
333 * | |
334 * @param child the child edit to add | |
335 * @exception MalformedTreeException is thrown if the child | |
336 * edit can't be added to this edit. This is the case if the child | |
337 * overlaps with one of its siblings or if the child edit's region | |
338 * isn't fully covered by this edit. | |
339 */ | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
340 public final void addChild(TextEdit child) { |
129 | 341 internalAdd(child); |
342 } | |
343 | |
344 /** | |
345 * Adds all edits in <code>edits</code> to this edit. | |
346 * | |
347 * @param edits the text edits to add | |
348 * @exception MalformedTreeException is thrown if one of | |
349 * the given edits can't be added to this edit. | |
350 * | |
351 * @see #addChild(TextEdit) | |
352 */ | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
353 public final void addChildren(TextEdit[] edits) { |
129 | 354 for (int i= 0; i < edits.length; i++) { |
355 internalAdd(edits[i]); | |
356 } | |
357 } | |
358 | |
359 /** | |
360 * Removes the edit specified by the given index from the list | |
361 * of children. Returns the child edit that was removed from | |
362 * the list of children. The parent of the returned edit is | |
363 * set to <code>null</code>. | |
364 * | |
365 * @param index the index of the edit to remove | |
366 * @return the removed edit | |
367 * @exception IndexOutOfBoundsException if the index | |
368 * is out of range | |
369 */ | |
370 public final TextEdit removeChild(int index) { | |
371 if (fChildren is null) | |
159 | 372 throw new IndexOutOfBoundsException(Format("Index: {} Size: 0", index )); //$NON-NLS-1$//$NON-NLS-2$ |
134 | 373 TextEdit result= cast(TextEdit)fChildren.remove(index); |
129 | 374 result.internalSetParent(null); |
375 if (fChildren.isEmpty()) | |
376 fChildren= null; | |
377 return result; | |
378 } | |
379 | |
380 /** | |
381 * Removes the first occurrence of the given child from the list | |
382 * of children. | |
383 * | |
384 * @param child the child to be removed | |
385 * @return <code>true</code> if the edit contained the given | |
386 * child; otherwise <code>false</code> is returned | |
387 */ | |
388 public final bool removeChild(TextEdit child) { | |
389 Assert.isNotNull(child); | |
390 if (fChildren is null) | |
391 return false; | |
392 bool result= fChildren.remove(child); | |
393 if (result) { | |
394 child.internalSetParent(null); | |
395 if (fChildren.isEmpty()) | |
396 fChildren= null; | |
397 } | |
398 return result; | |
399 } | |
400 | |
401 /** | |
402 * Removes all child edits from and returns them. The parent | |
403 * of the removed edits is set to <code>null</code>. | |
404 * | |
405 * @return an array of the removed edits | |
406 */ | |
407 public final TextEdit[] removeChildren() { | |
408 if (fChildren is null) | |
409 return EMPTY_ARRAY; | |
410 int size= fChildren.size(); | |
411 TextEdit[] result= new TextEdit[size]; | |
412 for (int i= 0; i < size; i++) { | |
134 | 413 result[i]= cast(TextEdit)fChildren.get(i); |
129 | 414 result[i].internalSetParent(null); |
415 } | |
416 fChildren= null; | |
417 return result; | |
418 } | |
419 | |
420 /** | |
421 * Returns <code>true</code> if this edit has children. Otherwise | |
422 * <code>false</code> is returned. | |
423 * | |
424 * @return <code>true</code> if this edit has children; otherwise | |
425 * <code>false</code> is returned | |
426 */ | |
427 public final bool hasChildren() { | |
428 return fChildren !is null && ! fChildren.isEmpty(); | |
429 } | |
430 | |
431 /** | |
432 * Returns the edit's children. If the edit doesn't have any | |
433 * children an empty array is returned. | |
434 * | |
435 * @return the edit's children | |
436 */ | |
437 public final TextEdit[] getChildren() { | |
438 if (fChildren is null) | |
439 return EMPTY_ARRAY; | |
156 | 440 return arraycast!(TextEdit)(fChildren.toArray()); |
129 | 441 } |
442 | |
443 /** | |
444 * Returns the size of the managed children. | |
445 * | |
446 * @return the size of the children | |
447 */ | |
448 public final int getChildrenSize() { | |
449 if (fChildren is null) | |
450 return 0; | |
451 return fChildren.size(); | |
452 } | |
453 | |
454 /** | |
455 * Returns the text range spawned by the given array of text edits. | |
456 * The method requires that the given array contains at least one | |
457 * edit. If all edits passed are deleted the method returns <code> | |
458 * null</code>. | |
459 * | |
460 * @param edits an array of edits | |
461 * @return the text range spawned by the given array of edits or | |
462 * <code>null</code> if all edits are marked as deleted | |
463 */ | |
464 public static IRegion getCoverage(TextEdit[] edits) { | |
465 Assert.isTrue(edits !is null && edits.length > 0); | |
466 | |
467 int offset= Integer.MAX_VALUE; | |
468 int end= Integer.MIN_VALUE; | |
469 int deleted= 0; | |
470 for (int i= 0; i < edits.length; i++) { | |
471 TextEdit edit= edits[i]; | |
472 if (edit.isDeleted()) { | |
473 deleted++; | |
474 } else { | |
475 offset= Math.min(offset, edit.getOffset()); | |
476 end= Math.max(end, edit.getExclusiveEnd()); | |
477 } | |
478 } | |
479 if (edits.length is deleted) | |
480 return null; | |
481 | |
482 return new Region(offset, end - offset); | |
483 } | |
484 | |
485 /* | |
486 * Hook called before this edit gets added to the passed | |
487 * parent. | |
488 */ | |
489 void aboutToBeAdded(TextEdit parent) { | |
490 } | |
491 | |
492 //---- Object methods ------------------------------------------------------ | |
493 | |
494 /** | |
495 * The <code>Edit</code> implementation of this <code>Object</code> | |
496 * method uses object identity (is). | |
497 * | |
498 * @param obj the other object | |
499 * @return <code>true</code> iff <code>this is obj</code>; otherwise | |
500 * <code>false</code> is returned | |
501 * | |
502 * @see Object#equals(java.lang.Object) | |
503 */ | |
504 public final bool equals(Object obj) { | |
505 return this is obj; // equivalent to Object.equals | |
506 } | |
507 | |
508 /** | |
509 * The <code>Edit</code> implementation of this <code>Object</code> | |
510 * method calls uses <code>Object#hashCode()</code> to compute its | |
511 * hash code. | |
512 * | |
513 * @return the object's hash code value | |
514 * | |
515 * @see Object#hashCode() | |
516 */ | |
160 | 517 public final override hash_t toHash() { |
518 return super.toHash(); | |
129 | 519 } |
520 | |
521 /* | |
522 * @see java.lang.Object#toString() | |
523 */ | |
160 | 524 public override String toString() { |
129 | 525 StringBuffer buffer= new StringBuffer(); |
526 toStringWithChildren(buffer, 0); | |
527 return buffer.toString(); | |
528 } | |
529 | |
530 /** | |
531 * Adds the string representation of this text edit without | |
532 * children information to the given string buffer. | |
130 | 533 * |
129 | 534 * @param buffer the string buffer |
535 * @param indent the indent level in number of spaces | |
536 * @since 3.3 | |
537 */ | |
538 void internalToString(StringBuffer buffer, int indent) { | |
539 for (int i= indent; i > 0; i--) { | |
540 buffer.append(" "); //$NON-NLS-1$ | |
541 } | |
542 buffer.append("{"); //$NON-NLS-1$ | |
543 String name= getClass().getName(); | |
544 int index= name.lastIndexOf('.'); | |
545 if (index !is -1) { | |
546 buffer.append(name.substring(index + 1)); | |
547 } else { | |
548 buffer.append(name); | |
549 } | |
550 buffer.append("} "); //$NON-NLS-1$ | |
551 if (isDeleted()) { | |
552 buffer.append("[deleted]"); //$NON-NLS-1$ | |
553 } else { | |
554 buffer.append("["); //$NON-NLS-1$ | |
555 buffer.append(getOffset()); | |
556 buffer.append(","); //$NON-NLS-1$ | |
557 buffer.append(getLength()); | |
558 buffer.append("]"); //$NON-NLS-1$ | |
559 } | |
560 } | |
561 | |
562 /** | |
130 | 563 * Adds the string representation for this text edit |
129 | 564 * and its children to the given string buffer. |
130 | 565 * |
129 | 566 * @param buffer the string buffer |
567 * @param indent the indent level in number of spaces | |
568 * @since 3.3 | |
569 */ | |
570 private void toStringWithChildren(StringBuffer buffer, int indent) { | |
571 internalToString(buffer, indent); | |
572 if (fChildren !is null) { | |
573 for (Iterator iterator= fChildren.iterator(); iterator.hasNext();) { | |
134 | 574 TextEdit child= cast(TextEdit) iterator.next(); |
129 | 575 buffer.append('\n'); |
576 child.toStringWithChildren(buffer, indent + 1); | |
577 } | |
578 } | |
579 } | |
130 | 580 |
129 | 581 //---- Copying ------------------------------------------------------------- |
582 | |
583 /** | |
584 * Creates a deep copy of the edit tree rooted at this | |
585 * edit. | |
586 * | |
587 * @return a deep copy of the edit tree | |
588 * @see #doCopy() | |
589 */ | |
590 public final TextEdit copy() { | |
591 TextEditCopier copier= new TextEditCopier(this); | |
592 return copier.perform(); | |
593 } | |
594 | |
595 /** | |
596 * Creates and returns a copy of this edit. The copy method should be | |
597 * implemented in a way so that the copy can executed without causing | |
598 * any harm to the original edit. Implementors of this method are | |
599 * responsible for creating deep or shallow copies of referenced | |
600 * object to fulfill this requirement. | |
601 * <p> | |
602 * Implementers of this method should use the copy constructor <code> | |
603 * Edit#Edit(Edit source) to initialize the edit part of the copy. | |
604 * Implementors aren't responsible to actually copy the children or | |
605 * to set the right parent. | |
606 * <p> | |
607 * This method <b>should not be called</b> from outside the framework. | |
608 * Please use <code>copy</code> to create a copy of a edit tree. | |
609 * | |
610 * @return a copy of this edit. | |
611 * @see #copy() | |
612 * @see #postProcessCopy(TextEditCopier) | |
613 * @see TextEditCopier | |
614 */ | |
615 protected abstract TextEdit doCopy(); | |
159 | 616 package TextEdit doCopy_package(){ |
617 return doCopy(); | |
618 } | |
129 | 619 |
620 /** | |
621 * This method is called on every edit of the copied tree to do some | |
622 * post-processing like connected an edit to a different edit in the tree. | |
623 * <p> | |
624 * This default implementation does nothing | |
625 * | |
626 * @param copier the copier that manages a map between original and | |
627 * copied edit. | |
628 * @see TextEditCopier | |
629 */ | |
630 protected void postProcessCopy(TextEditCopier copier) { | |
631 } | |
159 | 632 package void postProcessCopy_package(TextEditCopier copier) { |
633 postProcessCopy(copier); | |
634 } | |
129 | 635 |
636 //---- Visitor support ------------------------------------------------- | |
637 | |
638 /** | |
639 * Accepts the given visitor on a visit of the current edit. | |
640 * | |
641 * @param visitor the visitor object | |
642 * @exception IllegalArgumentException if the visitor is null | |
643 */ | |
644 public final void accept(TextEditVisitor visitor) { | |
645 Assert.isNotNull(visitor); | |
646 // begin with the generic pre-visit | |
647 visitor.preVisit(this); | |
648 // dynamic dispatch to internal method for type-specific visit/endVisit | |
649 accept0(visitor); | |
650 // end with the generic post-visit | |
651 visitor.postVisit(this); | |
652 } | |
653 | |
654 /** | |
655 * Accepts the given visitor on a type-specific visit of the current edit. | |
656 * This method must be implemented in all concrete text edits. | |
657 * <p> | |
658 * General template for implementation on each concrete TextEdit class: | |
659 * <pre> | |
660 * <code> | |
661 * bool visitChildren= visitor.visit(this); | |
662 * if (visitChildren) { | |
663 * acceptChildren(visitor); | |
664 * } | |
665 * </code> | |
666 * </pre> | |
667 * Note that the caller (<code>accept</code>) takes care of invoking | |
668 * <code>visitor.preVisit(this)</code> and <code>visitor.postVisit(this)</code>. | |
669 * </p> | |
670 * | |
671 * @param visitor the visitor object | |
672 */ | |
673 protected abstract void accept0(TextEditVisitor visitor); | |
674 | |
675 | |
676 /** | |
677 * Accepts the given visitor on the edits children. | |
678 * <p> | |
679 * This method must be used by the concrete implementations of | |
680 * <code>accept</code> to traverse list-values properties; it | |
681 * encapsulates the proper handling of on-the-fly changes to the list. | |
682 * </p> | |
683 * | |
684 * @param visitor the visitor object | |
685 */ | |
686 protected final void acceptChildren(TextEditVisitor visitor) { | |
687 if (fChildren is null) | |
688 return; | |
689 Iterator iterator= fChildren.iterator(); | |
690 while (iterator.hasNext()) { | |
134 | 691 TextEdit curr= cast(TextEdit) iterator.next(); |
129 | 692 curr.accept(visitor); |
693 } | |
694 } | |
695 | |
696 //---- Execution ------------------------------------------------------- | |
697 | |
698 /** | |
699 * Applies the edit tree rooted by this edit to the given document. To check | |
700 * if the edit tree can be applied to the document either catch <code> | |
701 * MalformedTreeException</code> or use <code>TextEditProcessor</code> to | |
702 * execute an edit tree. | |
703 * | |
704 * @param document the document to be manipulated | |
705 * @param style flags controlling the execution of the edit tree. Valid | |
706 * flags are: <code>CREATE_UNDO</code> and </code>UPDATE_REGIONS</code>. | |
707 * @return a undo edit, if <code>CREATE_UNDO</code> is specified. Otherwise | |
708 * <code>null</code> is returned. | |
709 * | |
710 * @exception MalformedTreeException is thrown if the tree isn't | |
711 * in a valid state. This exception is thrown before any edit | |
712 * is executed. So the document is still in its original state. | |
713 * @exception BadLocationException is thrown if one of the edits | |
714 * in the tree can't be executed. The state of the document is | |
715 * undefined if this exception is thrown. | |
716 * | |
717 * @see TextEditProcessor#performEdits() | |
718 */ | |
140
26688fec6d23
Following dsss compile errors
Frank Benoit <benoit@tionex.de>
parents:
139
diff
changeset
|
719 public final UndoEdit apply(IDocument document, int style) { |
129 | 720 try { |
721 TextEditProcessor processor= new TextEditProcessor(document, this, style); | |
722 return processor.performEdits(); | |
723 } finally { | |
724 // disconnect from text edit processor | |
725 fParent= null; | |
726 } | |
727 } | |
728 | |
729 /** | |
730 * Applies the edit tree rooted by this edit to the given document. This | |
731 * method is a convenience method for <code>apply(document, CREATE_UNDO | UPDATE_REGIONS) | |
732 * </code> | |
733 * | |
734 * @param document the document to which to apply this edit | |
735 * @return a undo edit, if <code>CREATE_UNDO</code> is specified. Otherwise | |
736 * <code>null</code> is returned. | |
737 * @exception MalformedTreeException is thrown if the tree isn't | |
738 * in a valid state. This exception is thrown before any edit | |
739 * is executed. So the document is still in its original state. | |
740 * @exception BadLocationException is thrown if one of the edits | |
741 * in the tree can't be executed. The state of the document is | |
742 * undefined if this exception is thrown. | |
743 * @see #apply(IDocument, int) | |
744 */ | |
140
26688fec6d23
Following dsss compile errors
Frank Benoit <benoit@tionex.de>
parents:
139
diff
changeset
|
745 public final UndoEdit apply(IDocument document) { |
129 | 746 return apply(document, CREATE_UNDO | UPDATE_REGIONS); |
747 } | |
748 | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
749 UndoEdit dispatchPerformEdits(TextEditProcessor processor) { |
129 | 750 return processor.executeDo(); |
751 } | |
752 | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
753 void dispatchCheckIntegrity(TextEditProcessor processor) { |
129 | 754 processor.checkIntegrityDo(); |
755 } | |
756 | |
757 //---- internal state accessors ---------------------------------------------------------- | |
758 | |
759 void internalSetParent(TextEdit parent) { | |
760 if (parent !is null) | |
761 Assert.isTrue(fParent is null); | |
762 fParent= parent; | |
763 } | |
764 | |
765 void internalSetOffset(int offset) { | |
766 Assert.isTrue(offset >= 0); | |
767 fOffset= offset; | |
768 } | |
769 | |
770 void internalSetLength(int length) { | |
771 Assert.isTrue(length >= 0); | |
772 fLength= length; | |
773 } | |
774 | |
775 List internalGetChildren() { | |
776 return fChildren; | |
777 } | |
778 | |
779 void internalSetChildren(List children) { | |
780 fChildren= children; | |
781 } | |
782 | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
783 void internalAdd(TextEdit child) { |
129 | 784 child.aboutToBeAdded(this); |
785 if (child.isDeleted()) | |
786 throw new MalformedTreeException(this, child, TextEditMessages.getString("TextEdit.deleted_edit")); //$NON-NLS-1$ | |
787 if (!covers(child)) | |
788 throw new MalformedTreeException(this, child, TextEditMessages.getString("TextEdit.range_outside")); //$NON-NLS-1$ | |
789 if (fChildren is null) { | |
790 fChildren= new ArrayList(2); | |
791 } | |
792 int index= computeInsertionIndex(child); | |
793 fChildren.add(index, child); | |
794 child.internalSetParent(this); | |
795 } | |
796 | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
797 private int computeInsertionIndex(TextEdit edit) { |
129 | 798 int size= fChildren.size(); |
799 if (size is 0) | |
800 return 0; | |
801 int lastIndex= size - 1; | |
134 | 802 TextEdit last= cast(TextEdit)fChildren.get(lastIndex); |
129 | 803 if (last.getExclusiveEnd() <= edit.getOffset()) |
804 return size; | |
805 try { | |
806 | |
807 int index= Collections.binarySearch(fChildren, edit, INSERTION_COMPARATOR); | |
808 | |
809 if (index < 0) | |
810 // edit is not in fChildren | |
811 return -index - 1; | |
812 | |
813 // edit is already in fChildren | |
814 // make sure that multiple insertion points at the same offset are inserted last. | |
815 while (index < lastIndex && INSERTION_COMPARATOR.compare(fChildren.get(index), fChildren.get(index + 1)) is 0) | |
816 index++; | |
817 | |
818 return index + 1; | |
819 | |
820 } catch(MalformedTreeException e) { | |
821 e.setParent(this); | |
822 throw e; | |
823 } | |
824 } | |
825 | |
826 //---- Offset & Length updating ------------------------------------------------- | |
827 | |
828 /** | |
829 * Adjusts the edits offset according to the given | |
830 * delta. This method doesn't update any children. | |
831 * | |
832 * @param delta the delta of the text replace operation | |
833 */ | |
834 void adjustOffset(int delta) { | |
835 if (isDeleted()) | |
836 return; | |
837 fOffset+= delta; | |
838 Assert.isTrue(fOffset >= 0); | |
839 } | |
840 | |
841 /** | |
842 * Adjusts the edits length according to the given | |
843 * delta. This method doesn't update any children. | |
844 * | |
845 * @param delta the delta of the text replace operation | |
846 */ | |
847 void adjustLength(int delta) { | |
848 if (isDeleted()) | |
849 return; | |
850 fLength+= delta; | |
851 Assert.isTrue(fLength >= 0); | |
852 } | |
853 | |
854 /** | |
855 * Marks the edit as deleted. This method doesn't update | |
856 * any children. | |
857 */ | |
858 void markAsDeleted() { | |
859 fOffset= DELETED_VALUE; | |
860 fLength= DELETED_VALUE; | |
861 } | |
862 | |
863 //---- Edit processing ---------------------------------------------- | |
864 | |
865 /** | |
866 * Traverses the edit tree to perform the consistency check. | |
867 * | |
868 * @param processor the text edit processor | |
869 * @param document the document to be manipulated | |
870 * @param sourceEdits the list of source edits to be performed before | |
871 * the actual tree is applied to the document | |
872 * | |
873 * @return the number of indirect move or copy target edit children | |
874 */ | |
875 int traverseConsistencyCheck(TextEditProcessor processor, IDocument document, List sourceEdits) { | |
876 int result= 0; | |
877 if (fChildren !is null) { | |
878 for (int i= fChildren.size() - 1; i >= 0; i--) { | |
134 | 879 TextEdit child= cast(TextEdit)fChildren.get(i); |
129 | 880 result= Math.max(result, child.traverseConsistencyCheck(processor, document, sourceEdits)); |
881 } | |
882 } | |
883 if (processor.considerEdit(this)) { | |
884 performConsistencyCheck(processor, document); | |
885 } | |
886 return result; | |
887 } | |
888 | |
889 void performConsistencyCheck(TextEditProcessor processor, IDocument document) { | |
890 } | |
891 | |
892 void traverseSourceComputation(TextEditProcessor processor, IDocument document) { | |
893 } | |
894 | |
895 void performSourceComputation(TextEditProcessor processor, IDocument document) { | |
896 } | |
897 | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
898 int traverseDocumentUpdating(TextEditProcessor processor, IDocument document) { |
129 | 899 int delta= 0; |
900 if (fChildren !is null) { | |
901 for (int i= fChildren.size() - 1; i >= 0; i--) { | |
134 | 902 TextEdit child= cast(TextEdit)fChildren.get(i); |
129 | 903 delta+= child.traverseDocumentUpdating(processor, document); |
904 childDocumentUpdated(); | |
905 } | |
906 } | |
907 if (processor.considerEdit(this)) { | |
908 if (delta !is 0) | |
909 adjustLength(delta); | |
910 int r= performDocumentUpdating(document); | |
911 if (r !is 0) | |
912 adjustLength(r); | |
913 delta+= r; | |
914 } | |
915 return delta; | |
916 } | |
917 | |
918 /** | |
919 * Hook method called when the document updating of a child edit has been | |
920 * completed. When a client calls {@link #apply(IDocument)} or | |
921 * {@link #apply(IDocument, int)} this method is called | |
922 * {@link #getChildrenSize()} times. | |
923 * <p> | |
924 * May be overridden by subclasses of {@link MultiTextEdit}. | |
925 * | |
926 * @since 3.1 | |
927 */ | |
928 protected void childDocumentUpdated() { | |
929 } | |
930 | |
139
93a6ec48fd28
Regexp throws removal in interfaces
Frank Benoit <benoit@tionex.de>
parents:
136
diff
changeset
|
931 abstract int performDocumentUpdating(IDocument document) ; |
129 | 932 |
150 | 933 int traverseRegionUpdating(TextEditProcessor processor, IDocument document, int accumulatedDelta, bool delete_) { |
934 performRegionUpdating(accumulatedDelta, delete_); | |
129 | 935 if (fChildren !is null) { |
150 | 936 bool childDelete= delete_ || deleteChildren(); |
129 | 937 for (Iterator iter= fChildren.iterator(); iter.hasNext();) { |
134 | 938 TextEdit child= cast(TextEdit)iter.next(); |
129 | 939 accumulatedDelta= child.traverseRegionUpdating(processor, document, accumulatedDelta, childDelete); |
940 childRegionUpdated(); | |
941 } | |
942 } | |
943 return accumulatedDelta + fDelta; | |
944 } | |
945 | |
946 /** | |
947 * Hook method called when the region updating of a child edit has been | |
948 * completed. When a client calls {@link #apply(IDocument)} this method is | |
949 * called {@link #getChildrenSize()} times. When calling | |
950 * {@link #apply(IDocument, int)} this method is called | |
951 * {@link #getChildrenSize()} times, when the style parameter contains the | |
952 * {@link #UPDATE_REGIONS} flag. | |
953 * <p> | |
954 * May be overridden by subclasses of {@link MultiTextEdit}. | |
955 * | |
956 * @since 3.1 | |
957 */ | |
958 protected void childRegionUpdated() { | |
959 } | |
960 | |
150 | 961 void performRegionUpdating(int accumulatedDelta, bool delete_) { |
962 if (delete_) | |
129 | 963 markAsDeleted(); |
964 else | |
965 adjustOffset(accumulatedDelta); | |
966 } | |
967 | |
968 abstract bool deleteChildren(); | |
969 | |
970 void internalMoveTree(int delta) { | |
971 adjustOffset(delta); | |
972 if (fChildren !is null) { | |
973 for (Iterator iter= fChildren.iterator(); iter.hasNext();) { | |
134 | 974 (cast(TextEdit)iter.next()).internalMoveTree(delta); |
129 | 975 } |
976 } | |
977 } | |
978 | |
979 void deleteTree() { | |
980 markAsDeleted(); | |
981 if (fChildren !is null) { | |
982 for (Iterator iter= fChildren.iterator(); iter.hasNext();) { | |
134 | 983 TextEdit child= cast(TextEdit)iter.next(); |
129 | 984 child.deleteTree(); |
985 } | |
986 } | |
987 } | |
988 } | |
989 |