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