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