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