Mercurial > projects > dwt-addons
comparison dwtx/text/undo/DocumentUndoManager.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | b56e9be9fe88 |
comparison
equal
deleted
inserted
replaced
128:8df1d4193877 | 129:eb30df5ca28b |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2006, 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.undo.DocumentUndoManager; | |
14 | |
15 import dwt.dwthelper.utils; | |
16 | |
17 import java.util.ArrayList; | |
18 import java.util.List; | |
19 | |
20 import dwtx.core.commands.ExecutionException; | |
21 import dwtx.core.commands.operations.AbstractOperation; | |
22 import dwtx.core.commands.operations.IContextReplacingOperation; | |
23 import dwtx.core.commands.operations.IOperationHistory; | |
24 import dwtx.core.commands.operations.IOperationHistoryListener; | |
25 import dwtx.core.commands.operations.IUndoContext; | |
26 import dwtx.core.commands.operations.IUndoableOperation; | |
27 import dwtx.core.commands.operations.ObjectUndoContext; | |
28 import dwtx.core.commands.operations.OperationHistoryEvent; | |
29 import dwtx.core.commands.operations.OperationHistoryFactory; | |
30 import dwtx.core.runtime.Assert; | |
31 import dwtx.core.runtime.IAdaptable; | |
32 import dwtx.core.runtime.IProgressMonitor; | |
33 import dwtx.core.runtime.IStatus; | |
34 import dwtx.core.runtime.ListenerList; | |
35 import dwtx.core.runtime.Status; | |
36 import dwtx.jface.text.BadLocationException; | |
37 import dwtx.jface.text.DocumentEvent; | |
38 import dwtx.jface.text.IDocument; | |
39 import dwtx.jface.text.IDocumentExtension4; | |
40 import dwtx.jface.text.IDocumentListener; | |
41 import dwtx.jface.text.TextUtilities; | |
42 | |
43 /** | |
44 * A standard implementation of a document-based undo manager that | |
45 * creates an undo history based on changes to its document. | |
46 * <p> | |
47 * Based on the 3.1 implementation of DefaultUndoManager, it was implemented | |
48 * using the document-related manipulations defined in the original | |
49 * DefaultUndoManager, by separating the document manipulations from the | |
50 * viewer-specific processing.</p> | |
51 * <p> | |
52 * The classes representing individual text edits (formerly text commands) | |
53 * were promoted from inner types to their own classes in order to support | |
54 * reassignment to a different undo manager.<p> | |
55 * <p> | |
56 * This class is not intended to be subclassed. | |
57 * </p> | |
58 * | |
59 * @see IDocumentUndoManager | |
60 * @see DocumentUndoManagerRegistry | |
61 * @see IDocumentUndoListener | |
62 * @see dwtx.jface.text.IDocument | |
63 * @since 3.2 | |
64 * @noextend This class is not intended to be subclassed by clients. | |
65 */ | |
66 public class DocumentUndoManager : IDocumentUndoManager { | |
67 | |
68 | |
69 /** | |
70 * Represents an undo-able text change, described as the | |
71 * replacement of some preserved text with new text. | |
72 * <p> | |
73 * Based on the DefaultUndoManager.TextCommand from R3.1. | |
74 * </p> | |
75 */ | |
76 private static class UndoableTextChange : AbstractOperation { | |
77 | |
78 /** The start index of the replaced text. */ | |
79 protected int fStart= -1; | |
80 | |
81 /** The end index of the replaced text. */ | |
82 protected int fEnd= -1; | |
83 | |
84 /** The newly inserted text. */ | |
85 protected String fText; | |
86 | |
87 /** The replaced text. */ | |
88 protected String fPreservedText; | |
89 | |
90 /** The undo modification stamp. */ | |
91 protected long fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; | |
92 | |
93 /** The redo modification stamp. */ | |
94 protected long fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; | |
95 | |
96 /** The undo manager that generated the change. */ | |
97 protected DocumentUndoManager fDocumentUndoManager; | |
98 | |
99 /** | |
100 * Creates a new text change. | |
101 * | |
102 * @param manager the undo manager for this change | |
103 */ | |
104 UndoableTextChange(DocumentUndoManager manager) { | |
105 super(UndoMessages.getString("DocumentUndoManager.operationLabel")); //$NON-NLS-1$ | |
106 this.fDocumentUndoManager= manager; | |
107 addContext(manager.getUndoContext()); | |
108 } | |
109 | |
110 /** | |
111 * Re-initializes this text change. | |
112 */ | |
113 protected void reinitialize() { | |
114 fStart= fEnd= -1; | |
115 fText= fPreservedText= null; | |
116 fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; | |
117 fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; | |
118 } | |
119 | |
120 /** | |
121 * Sets the start and the end index of this change. | |
122 * | |
123 * @param start the start index | |
124 * @param end the end index | |
125 */ | |
126 protected void set(int start, int end) { | |
127 fStart= start; | |
128 fEnd= end; | |
129 fText= null; | |
130 fPreservedText= null; | |
131 } | |
132 | |
133 /* | |
134 * @see dwtx.core.commands.operations.IUndoableOperation#dispose() | |
135 */ | |
136 public void dispose() { | |
137 reinitialize(); | |
138 } | |
139 | |
140 /** | |
141 * Undo the change described by this change. | |
142 */ | |
143 protected void undoTextChange() { | |
144 try { | |
145 if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4) | |
146 ((IDocumentExtension4) fDocumentUndoManager.fDocument).replace(fStart, fText | |
147 .length(), fPreservedText, fUndoModificationStamp); | |
148 else | |
149 fDocumentUndoManager.fDocument.replace(fStart, fText.length(), | |
150 fPreservedText); | |
151 } catch (BadLocationException x) { | |
152 } | |
153 } | |
154 | |
155 /* | |
156 * @see dwtx.core.commands.operations.IUndoableOperation#canUndo() | |
157 */ | |
158 public bool canUndo() { | |
159 if (isValid()) { | |
160 if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4) { | |
161 long docStamp= ((IDocumentExtension4) fDocumentUndoManager.fDocument) | |
162 .getModificationStamp(); | |
163 | |
164 // Normal case: an undo is valid if its redo will restore | |
165 // document to its current modification stamp | |
166 bool canUndo= docStamp is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP | |
167 || docStamp is getRedoModificationStamp(); | |
168 | |
169 /* | |
170 * Special case to check if the answer is false. If the last | |
171 * document change was empty, then the document's modification | |
172 * stamp was incremented but nothing was committed. The | |
173 * operation being queried has an older stamp. In this case | |
174 * only, the comparison is different. A sequence of document | |
175 * changes that include an empty change is handled correctly | |
176 * when a valid commit follows the empty change, but when | |
177 * #canUndo() is queried just after an empty change, we must | |
178 * special case the check. The check is very specific to prevent | |
179 * false positives. see | |
180 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=98245 | |
181 */ | |
182 if (!canUndo | |
183 && this is fDocumentUndoManager.fHistory | |
184 .getUndoOperation(fDocumentUndoManager.fUndoContext) | |
185 // this is the latest operation | |
186 && this !is fDocumentUndoManager.fCurrent | |
187 // there is a more current operation not on the stack | |
188 && !fDocumentUndoManager.fCurrent.isValid() | |
189 // the current operation is not a valid document | |
190 // modification | |
191 && fDocumentUndoManager.fCurrent.fUndoModificationStamp !is | |
192 // the invalid current operation has a document stamp | |
193 IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) { | |
194 canUndo= fDocumentUndoManager.fCurrent.fRedoModificationStamp is docStamp; | |
195 } | |
196 /* | |
197 * When the composite is the current operation, it may hold the | |
198 * timestamp of a no-op change. We check this here rather than | |
199 * in an override of canUndo() in UndoableCompoundTextChange simply to | |
200 * keep all the special case checks in one place. | |
201 */ | |
202 if (!canUndo | |
203 && this is fDocumentUndoManager.fHistory | |
204 .getUndoOperation(fDocumentUndoManager.fUndoContext) | |
205 && // this is the latest operation | |
206 this instanceof UndoableCompoundTextChange | |
207 && this is fDocumentUndoManager.fCurrent | |
208 && // this is the current operation | |
209 this.fStart is -1 | |
210 && // the current operation text is not valid | |
211 fDocumentUndoManager.fCurrent.fRedoModificationStamp !is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) { | |
212 // but it has a redo stamp | |
213 canUndo= fDocumentUndoManager.fCurrent.fRedoModificationStamp is docStamp; | |
214 } | |
215 return canUndo; | |
216 | |
217 } | |
218 // if there is no timestamp to check, simply return true per the | |
219 // 3.0.1 behavior | |
220 return true; | |
221 } | |
222 return false; | |
223 } | |
224 | |
225 /* | |
226 * @see dwtx.core.commands.operations.IUndoableOperation#canRedo() | |
227 */ | |
228 public bool canRedo() { | |
229 if (isValid()) { | |
230 if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4) { | |
231 long docStamp= ((IDocumentExtension4) fDocumentUndoManager.fDocument) | |
232 .getModificationStamp(); | |
233 return docStamp is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP | |
234 || docStamp is getUndoModificationStamp(); | |
235 } | |
236 // if there is no timestamp to check, simply return true per the | |
237 // 3.0.1 behavior | |
238 return true; | |
239 } | |
240 return false; | |
241 } | |
242 | |
243 /* | |
244 * @see dwtx.core.commands.operations.IUndoableOperation#canExecute() | |
245 */ | |
246 public bool canExecute() { | |
247 return fDocumentUndoManager.isConnected(); | |
248 } | |
249 | |
250 /* | |
251 * @see dwtx.core.commands.operations.IUndoableOperation.IUndoableOperation#execute(IProgressMonitor, IAdaptable) | |
252 */ | |
253 public IStatus execute(IProgressMonitor monitor, IAdaptable uiInfo) { | |
254 // Text changes execute as they are typed, so executing one has no | |
255 // effect. | |
256 return Status.OK_STATUS; | |
257 } | |
258 | |
259 /** | |
260 * {@inheritDoc} | |
261 * Notifies clients about the undo. | |
262 */ | |
263 public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) { | |
264 if (isValid()) { | |
265 fDocumentUndoManager.fireDocumentUndo(fStart, fPreservedText, fText, uiInfo, DocumentUndoEvent.ABOUT_TO_UNDO, false); | |
266 undoTextChange(); | |
267 fDocumentUndoManager.resetProcessChangeState(); | |
268 fDocumentUndoManager.fireDocumentUndo(fStart, fPreservedText, fText, uiInfo, DocumentUndoEvent.UNDONE, false); | |
269 return Status.OK_STATUS; | |
270 } | |
271 return IOperationHistory.OPERATION_INVALID_STATUS; | |
272 } | |
273 | |
274 /** | |
275 * Re-applies the change described by this change. | |
276 */ | |
277 protected void redoTextChange() { | |
278 try { | |
279 if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4) | |
280 ((IDocumentExtension4) fDocumentUndoManager.fDocument).replace(fStart, fEnd - fStart, fText, fRedoModificationStamp); | |
281 else | |
282 fDocumentUndoManager.fDocument.replace(fStart, fEnd - fStart, fText); | |
283 } catch (BadLocationException x) { | |
284 } | |
285 } | |
286 | |
287 /** | |
288 * Re-applies the change described by this change that was previously | |
289 * undone. Also notifies clients about the redo. | |
290 * | |
291 * @param monitor the progress monitor to use if necessary | |
292 * @param uiInfo an adaptable that can provide UI info if needed | |
293 * @return the status | |
294 */ | |
295 public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) { | |
296 if (isValid()) { | |
297 fDocumentUndoManager.fireDocumentUndo(fStart, fText, fPreservedText, uiInfo, DocumentUndoEvent.ABOUT_TO_REDO, false); | |
298 redoTextChange(); | |
299 fDocumentUndoManager.resetProcessChangeState(); | |
300 fDocumentUndoManager.fireDocumentUndo(fStart, fText, fPreservedText, uiInfo, DocumentUndoEvent.REDONE, false); | |
301 return Status.OK_STATUS; | |
302 } | |
303 return IOperationHistory.OPERATION_INVALID_STATUS; | |
304 } | |
305 | |
306 /** | |
307 * Update the change in response to a commit. | |
308 */ | |
309 | |
310 protected void updateTextChange() { | |
311 fText= fDocumentUndoManager.fTextBuffer.toString(); | |
312 fDocumentUndoManager.fTextBuffer.setLength(0); | |
313 fPreservedText= fDocumentUndoManager.fPreservedTextBuffer.toString(); | |
314 fDocumentUndoManager.fPreservedTextBuffer.setLength(0); | |
315 } | |
316 | |
317 /** | |
318 * Creates a new uncommitted text change depending on whether a compound | |
319 * change is currently being executed. | |
320 * | |
321 * @return a new, uncommitted text change or a compound text change | |
322 */ | |
323 protected UndoableTextChange createCurrent() { | |
324 if (fDocumentUndoManager.fFoldingIntoCompoundChange) | |
325 return new UndoableCompoundTextChange(fDocumentUndoManager); | |
326 return new UndoableTextChange(fDocumentUndoManager); | |
327 } | |
328 | |
329 /** | |
330 * Commits the current change into this one. | |
331 */ | |
332 protected void commit() { | |
333 if (fStart < 0) { | |
334 if (fDocumentUndoManager.fFoldingIntoCompoundChange) { | |
335 fDocumentUndoManager.fCurrent= createCurrent(); | |
336 } else { | |
337 reinitialize(); | |
338 } | |
339 } else { | |
340 updateTextChange(); | |
341 fDocumentUndoManager.fCurrent= createCurrent(); | |
342 } | |
343 fDocumentUndoManager.resetProcessChangeState(); | |
344 } | |
345 | |
346 /** | |
347 * Updates the text from the buffers without resetting the buffers or adding | |
348 * anything to the stack. | |
349 */ | |
350 protected void pretendCommit() { | |
351 if (fStart > -1) { | |
352 fText= fDocumentUndoManager.fTextBuffer.toString(); | |
353 fPreservedText= fDocumentUndoManager.fPreservedTextBuffer.toString(); | |
354 } | |
355 } | |
356 | |
357 /** | |
358 * Attempt a commit of this change and answer true if a new fCurrent was | |
359 * created as a result of the commit. | |
360 * | |
361 * @return <code>true</code> if the change was committed and created | |
362 * a new <code>fCurrent</code>, <code>false</code> if not | |
363 */ | |
364 protected bool attemptCommit() { | |
365 pretendCommit(); | |
366 if (isValid()) { | |
367 fDocumentUndoManager.commit(); | |
368 return true; | |
369 } | |
370 return false; | |
371 } | |
372 | |
373 /** | |
374 * Checks whether this text change is valid for undo or redo. | |
375 * | |
376 * @return <code>true</code> if the change is valid for undo or redo | |
377 */ | |
378 protected bool isValid() { | |
379 return fStart > -1 && fEnd > -1 && fText !is null; | |
380 } | |
381 | |
382 /* | |
383 * @see java.lang.Object#toString() | |
384 */ | |
385 public String toString() { | |
386 String delimiter= ", "; //$NON-NLS-1$ | |
387 StringBuffer text= new StringBuffer(super.toString()); | |
388 text.append("\n"); //$NON-NLS-1$ | |
389 text.append(this.getClass().getName()); | |
390 text.append(" undo modification stamp: "); //$NON-NLS-1$ | |
391 text.append(fUndoModificationStamp); | |
392 text.append(" redo modification stamp: "); //$NON-NLS-1$ | |
393 text.append(fRedoModificationStamp); | |
394 text.append(" start: "); //$NON-NLS-1$ | |
395 text.append(fStart); | |
396 text.append(delimiter); | |
397 text.append("end: "); //$NON-NLS-1$ | |
398 text.append(fEnd); | |
399 text.append(delimiter); | |
400 text.append("text: '"); //$NON-NLS-1$ | |
401 text.append(fText); | |
402 text.append('\''); | |
403 text.append(delimiter); | |
404 text.append("preservedText: '"); //$NON-NLS-1$ | |
405 text.append(fPreservedText); | |
406 text.append('\''); | |
407 return text.toString(); | |
408 } | |
409 | |
410 /** | |
411 * Return the undo modification stamp | |
412 * | |
413 * @return the undo modification stamp for this change | |
414 */ | |
415 protected long getUndoModificationStamp() { | |
416 return fUndoModificationStamp; | |
417 } | |
418 | |
419 /** | |
420 * Return the redo modification stamp | |
421 * | |
422 * @return the redo modification stamp for this change | |
423 */ | |
424 protected long getRedoModificationStamp() { | |
425 return fRedoModificationStamp; | |
426 } | |
427 } | |
428 | |
429 | |
430 /** | |
431 * Represents an undo-able text change consisting of several individual | |
432 * changes. | |
433 */ | |
434 private static class UndoableCompoundTextChange : UndoableTextChange { | |
435 | |
436 /** The list of individual changes */ | |
437 private List fChanges= new ArrayList(); | |
438 | |
439 /** | |
440 * Creates a new compound text change. | |
441 * | |
442 * @param manager | |
443 * the undo manager for this change | |
444 */ | |
445 UndoableCompoundTextChange(DocumentUndoManager manager) { | |
446 super(manager); | |
447 } | |
448 | |
449 /** | |
450 * Adds a new individual change to this compound change. | |
451 * | |
452 * @param change the change to be added | |
453 */ | |
454 protected void add(UndoableTextChange change) { | |
455 fChanges.add(change); | |
456 } | |
457 | |
458 /* | |
459 * @see dwtx.text.undo.UndoableTextChange#undo(dwtx.core.runtime.IProgressMonitor, dwtx.core.runtime.IAdaptable) | |
460 */ | |
461 public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) { | |
462 | |
463 int size= fChanges.size(); | |
464 if (size > 0) { | |
465 UndoableTextChange c; | |
466 | |
467 c= (UndoableTextChange) fChanges.get(0); | |
468 fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fPreservedText, c.fText, uiInfo, DocumentUndoEvent.ABOUT_TO_UNDO, true); | |
469 | |
470 for (int i= size - 1; i >= 0; --i) { | |
471 c= (UndoableTextChange) fChanges.get(i); | |
472 c.undoTextChange(); | |
473 } | |
474 fDocumentUndoManager.resetProcessChangeState(); | |
475 fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fPreservedText, c.fText, uiInfo, | |
476 DocumentUndoEvent.UNDONE, true); | |
477 } | |
478 return Status.OK_STATUS; | |
479 } | |
480 | |
481 /* | |
482 * @see dwtx.text.undo.UndoableTextChange#redo(dwtx.core.runtime.IProgressMonitor, dwtx.core.runtime.IAdaptable) | |
483 */ | |
484 public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) { | |
485 | |
486 int size= fChanges.size(); | |
487 if (size > 0) { | |
488 | |
489 UndoableTextChange c; | |
490 c= (UndoableTextChange) fChanges.get(size - 1); | |
491 fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fText, c.fPreservedText, uiInfo, DocumentUndoEvent.ABOUT_TO_REDO, true); | |
492 | |
493 for (int i= 0; i <= size - 1; ++i) { | |
494 c= (UndoableTextChange) fChanges.get(i); | |
495 c.redoTextChange(); | |
496 } | |
497 fDocumentUndoManager.resetProcessChangeState(); | |
498 fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fText, c.fPreservedText, uiInfo, DocumentUndoEvent.REDONE, true); | |
499 } | |
500 | |
501 return Status.OK_STATUS; | |
502 } | |
503 | |
504 /* | |
505 * @see dwtx.text.undo.UndoableTextChange#updateTextChange() | |
506 */ | |
507 protected void updateTextChange() { | |
508 // first gather the data from the buffers | |
509 super.updateTextChange(); | |
510 | |
511 // the result of the update is stored as a child change | |
512 UndoableTextChange c= new UndoableTextChange(fDocumentUndoManager); | |
513 c.fStart= fStart; | |
514 c.fEnd= fEnd; | |
515 c.fText= fText; | |
516 c.fPreservedText= fPreservedText; | |
517 c.fUndoModificationStamp= fUndoModificationStamp; | |
518 c.fRedoModificationStamp= fRedoModificationStamp; | |
519 add(c); | |
520 | |
521 // clear out all indexes now that the child is added | |
522 reinitialize(); | |
523 } | |
524 | |
525 /* | |
526 * @see dwtx.text.undo.UndoableTextChange#createCurrent() | |
527 */ | |
528 protected UndoableTextChange createCurrent() { | |
529 | |
530 if (!fDocumentUndoManager.fFoldingIntoCompoundChange) | |
531 return new UndoableTextChange(fDocumentUndoManager); | |
532 | |
533 reinitialize(); | |
534 return this; | |
535 } | |
536 | |
537 /* | |
538 * @see dwtx.text.undo.UndoableTextChange#commit() | |
539 */ | |
540 protected void commit() { | |
541 // if there is pending data, update the text change | |
542 if (fStart > -1) | |
543 updateTextChange(); | |
544 fDocumentUndoManager.fCurrent= createCurrent(); | |
545 fDocumentUndoManager.resetProcessChangeState(); | |
546 } | |
547 | |
548 /* | |
549 * @see dwtx.text.undo.UndoableTextChange#isValid() | |
550 */ | |
551 protected bool isValid() { | |
552 return fStart > -1 || fChanges.size() > 0; | |
553 } | |
554 | |
555 /* | |
556 * @see dwtx.text.undo.UndoableTextChange#getUndoModificationStamp() | |
557 */ | |
558 protected long getUndoModificationStamp() { | |
559 if (fStart > -1) | |
560 return super.getUndoModificationStamp(); | |
561 else if (fChanges.size() > 0) | |
562 return ((UndoableTextChange) fChanges.get(0)) | |
563 .getUndoModificationStamp(); | |
564 | |
565 return fUndoModificationStamp; | |
566 } | |
567 | |
568 /* | |
569 * @see dwtx.text.undo.UndoableTextChange#getRedoModificationStamp() | |
570 */ | |
571 protected long getRedoModificationStamp() { | |
572 if (fStart > -1) | |
573 return super.getRedoModificationStamp(); | |
574 else if (fChanges.size() > 0) | |
575 return ((UndoableTextChange) fChanges.get(fChanges.size() - 1)) | |
576 .getRedoModificationStamp(); | |
577 | |
578 return fRedoModificationStamp; | |
579 } | |
580 } | |
581 | |
582 | |
583 /** | |
584 * Internal listener to document changes. | |
585 */ | |
586 private class DocumentListener : IDocumentListener { | |
587 | |
588 private String fReplacedText; | |
589 | |
590 /* | |
591 * @see dwtx.jface.text.IDocumentListener#documentAboutToBeChanged(dwtx.jface.text.DocumentEvent) | |
592 */ | |
593 public void documentAboutToBeChanged(DocumentEvent event) { | |
594 try { | |
595 fReplacedText= event.getDocument().get(event.getOffset(), | |
596 event.getLength()); | |
597 fPreservedUndoModificationStamp= event.getModificationStamp(); | |
598 } catch (BadLocationException x) { | |
599 fReplacedText= null; | |
600 } | |
601 } | |
602 | |
603 /* | |
604 * @see dwtx.jface.text.IDocumentListener#documentChanged(dwtx.jface.text.DocumentEvent) | |
605 */ | |
606 public void documentChanged(DocumentEvent event) { | |
607 fPreservedRedoModificationStamp= event.getModificationStamp(); | |
608 | |
609 // record the current valid state for the top operation in case it | |
610 // remains the | |
611 // top operation but changes state. | |
612 IUndoableOperation op= fHistory.getUndoOperation(fUndoContext); | |
613 bool wasValid= false; | |
614 if (op !is null) | |
615 wasValid= op.canUndo(); | |
616 // Process the change, providing the before and after timestamps | |
617 processChange(event.getOffset(), event.getOffset() | |
618 + event.getLength(), event.getText(), fReplacedText, | |
619 fPreservedUndoModificationStamp, | |
620 fPreservedRedoModificationStamp); | |
621 | |
622 // now update fCurrent with the latest buffers from the document | |
623 // change. | |
624 fCurrent.pretendCommit(); | |
625 | |
626 if (op is fCurrent) { | |
627 // if the document change did not cause a new fCurrent to be | |
628 // created, then we should | |
629 // notify the history that the current operation changed if its | |
630 // validity has changed. | |
631 if (wasValid !is fCurrent.isValid()) | |
632 fHistory.operationChanged(op); | |
633 } else { | |
634 // if the change created a new fCurrent that we did not yet add | |
635 // to the | |
636 // stack, do so if it's valid and we are not in the middle of a | |
637 // compound change. | |
638 if (fCurrent !is fLastAddedTextEdit && fCurrent.isValid()) { | |
639 addToOperationHistory(fCurrent); | |
640 } | |
641 } | |
642 } | |
643 } | |
644 | |
645 /* | |
646 * @see IOperationHistoryListener | |
647 */ | |
648 private class HistoryListener : IOperationHistoryListener { | |
649 | |
650 private IUndoableOperation fOperation; | |
651 | |
652 public void historyNotification(final OperationHistoryEvent event) { | |
653 final int type= event.getEventType(); | |
654 switch (type) { | |
655 case OperationHistoryEvent.ABOUT_TO_UNDO: | |
656 case OperationHistoryEvent.ABOUT_TO_REDO: | |
657 // if this is one of our operations | |
658 if (event.getOperation().hasContext(fUndoContext)) { | |
659 // if we are undoing/redoing an operation we generated, then | |
660 // ignore | |
661 // the document changes associated with this undo or redo. | |
662 if (event.getOperation() instanceof UndoableTextChange) { | |
663 listenToTextChanges(false); | |
664 | |
665 // in the undo case only, make sure compounds are closed | |
666 if (type is OperationHistoryEvent.ABOUT_TO_UNDO) { | |
667 if (fFoldingIntoCompoundChange) { | |
668 endCompoundChange(); | |
669 } | |
670 } | |
671 } else { | |
672 // the undo or redo has our context, but it is not one | |
673 // of our edits. We will listen to the changes, but will | |
674 // reset the state that tracks the undo/redo history. | |
675 commit(); | |
676 fLastAddedTextEdit= null; | |
677 } | |
678 fOperation= event.getOperation(); | |
679 } | |
680 break; | |
681 case OperationHistoryEvent.UNDONE: | |
682 case OperationHistoryEvent.REDONE: | |
683 case OperationHistoryEvent.OPERATION_NOT_OK: | |
684 if (event.getOperation() is fOperation) { | |
685 listenToTextChanges(true); | |
686 fOperation= null; | |
687 } | |
688 break; | |
689 } | |
690 } | |
691 | |
692 } | |
693 | |
694 | |
695 /** | |
696 * The undo context for this document undo manager. | |
697 */ | |
698 private ObjectUndoContext fUndoContext; | |
699 | |
700 /** | |
701 * The document whose changes are being tracked. | |
702 */ | |
703 private IDocument fDocument; | |
704 | |
705 /** | |
706 * The currently constructed edit. | |
707 */ | |
708 private UndoableTextChange fCurrent; | |
709 | |
710 /** | |
711 * The internal document listener. | |
712 */ | |
713 private DocumentListener fDocumentListener; | |
714 | |
715 /** | |
716 * Indicates whether the current change belongs to a compound change. | |
717 */ | |
718 private bool fFoldingIntoCompoundChange= false; | |
719 | |
720 /** | |
721 * The operation history being used to store the undo history. | |
722 */ | |
723 private IOperationHistory fHistory; | |
724 | |
725 /** | |
726 * The operation history listener used for managing undo and redo before and | |
727 * after the individual edits are performed. | |
728 */ | |
729 private IOperationHistoryListener fHistoryListener; | |
730 | |
731 /** | |
732 * The text edit last added to the operation history. This must be tracked | |
733 * internally instead of asking the history, since outside parties may be | |
734 * placing items on our undo/redo history. | |
735 */ | |
736 private UndoableTextChange fLastAddedTextEdit= null; | |
737 | |
738 /** | |
739 * The document modification stamp for redo. | |
740 */ | |
741 private long fPreservedRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; | |
742 | |
743 /** | |
744 * Text buffer to collect viewer content which has been replaced | |
745 */ | |
746 private StringBuffer fPreservedTextBuffer; | |
747 | |
748 /** | |
749 * The document modification stamp for undo. | |
750 */ | |
751 private long fPreservedUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; | |
752 | |
753 /** | |
754 * The last delete text edit. | |
755 */ | |
756 private UndoableTextChange fPreviousDelete; | |
757 | |
758 /** | |
759 * Text buffer to collect text which is inserted into the viewer | |
760 */ | |
761 private StringBuffer fTextBuffer; | |
762 | |
763 /** Indicates inserting state. */ | |
764 private bool fInserting= false; | |
765 | |
766 /** Indicates overwriting state. */ | |
767 private bool fOverwriting= false; | |
768 | |
769 /** The registered document listeners. */ | |
770 private ListenerList fDocumentUndoListeners; | |
771 | |
772 /** The list of clients connected. */ | |
773 private List fConnected; | |
774 | |
775 /** | |
776 * | |
777 * Create a DocumentUndoManager for the given document. | |
778 * | |
779 * @param document the document whose undo history is being managed. | |
780 */ | |
781 public DocumentUndoManager(IDocument document) { | |
782 super(); | |
783 Assert.isNotNull(document); | |
784 fDocument= document; | |
785 fHistory= OperationHistoryFactory.getOperationHistory(); | |
786 fUndoContext= new ObjectUndoContext(fDocument); | |
787 fConnected= new ArrayList(); | |
788 fDocumentUndoListeners= new ListenerList(ListenerList.IDENTITY); | |
789 } | |
790 | |
791 /* | |
792 * @see dwtx.jface.text.IDocumentUndoManager#addDocumentUndoListener(dwtx.jface.text.IDocumentUndoListener) | |
793 */ | |
794 public void addDocumentUndoListener(IDocumentUndoListener listener) { | |
795 fDocumentUndoListeners.add(listener); | |
796 } | |
797 | |
798 /* | |
799 * @see dwtx.jface.text.IDocumentUndoManager#removeDocumentUndoListener(dwtx.jface.text.IDocumentUndoListener) | |
800 */ | |
801 public void removeDocumentUndoListener(IDocumentUndoListener listener) { | |
802 fDocumentUndoListeners.remove(listener); | |
803 } | |
804 | |
805 /* | |
806 * @see dwtx.jface.text.IDocumentUndoManager#getUndoContext() | |
807 */ | |
808 public IUndoContext getUndoContext() { | |
809 return fUndoContext; | |
810 } | |
811 | |
812 /* | |
813 * @see dwtx.jface.text.IDocumentUndoManager#commit() | |
814 */ | |
815 public void commit() { | |
816 // if fCurrent has never been placed on the history, do so now. | |
817 // this can happen when there are multiple programmatically commits in a | |
818 // single document change. | |
819 if (fLastAddedTextEdit !is fCurrent) { | |
820 fCurrent.pretendCommit(); | |
821 if (fCurrent.isValid()) | |
822 addToOperationHistory(fCurrent); | |
823 } | |
824 fCurrent.commit(); | |
825 } | |
826 | |
827 /* | |
828 * @see dwtx.text.undo.IDocumentUndoManager#reset() | |
829 */ | |
830 public void reset() { | |
831 if (isConnected()) { | |
832 shutdown(); | |
833 initialize(); | |
834 } | |
835 } | |
836 | |
837 /* | |
838 * @see dwtx.text.undo.IDocumentUndoManager#redoable() | |
839 */ | |
840 public bool redoable() { | |
841 return OperationHistoryFactory.getOperationHistory().canRedo(fUndoContext); | |
842 } | |
843 | |
844 /* | |
845 * @see dwtx.text.undo.IDocumentUndoManager#undoable() | |
846 */ | |
847 public bool undoable() { | |
848 return OperationHistoryFactory.getOperationHistory().canUndo(fUndoContext); | |
849 } | |
850 | |
851 /* | |
852 * @see dwtx.text.undo.IDocumentUndoManager#undo() | |
853 */ | |
854 public void redo() throws ExecutionException { | |
855 if (isConnected() && redoable()) | |
856 OperationHistoryFactory.getOperationHistory().redo(getUndoContext(), null, null); | |
857 } | |
858 | |
859 /* | |
860 * @see dwtx.text.undo.IDocumentUndoManager#undo() | |
861 */ | |
862 public void undo() throws ExecutionException { | |
863 if (undoable()) | |
864 OperationHistoryFactory.getOperationHistory().undo(fUndoContext, null, null); | |
865 } | |
866 | |
867 /* | |
868 * @see dwtx.jface.text.IDocumentUndoManager#connect(java.lang.Object) | |
869 */ | |
870 public void connect(Object client) { | |
871 if (!isConnected()) { | |
872 initialize(); | |
873 } | |
874 if (!fConnected.contains(client)) | |
875 fConnected.add(client); | |
876 } | |
877 | |
878 /* | |
879 * @see dwtx.jface.text.IDocumentUndoManager#disconnect(java.lang.Object) | |
880 */ | |
881 public void disconnect(Object client) { | |
882 fConnected.remove(client); | |
883 if (!isConnected()) { | |
884 shutdown(); | |
885 } | |
886 } | |
887 | |
888 /* | |
889 * @see dwtx.jface.text.IDocumentUndoManager#beginCompoundChange() | |
890 */ | |
891 public void beginCompoundChange() { | |
892 if (isConnected()) { | |
893 fFoldingIntoCompoundChange= true; | |
894 commit(); | |
895 } | |
896 } | |
897 | |
898 /* | |
899 * @see dwtx.jface.text.IDocumentUndoManager#endCompoundChange() | |
900 */ | |
901 public void endCompoundChange() { | |
902 if (isConnected()) { | |
903 fFoldingIntoCompoundChange= false; | |
904 commit(); | |
905 } | |
906 } | |
907 | |
908 /* | |
909 * @see dwtx.jface.text.IDocumentUndoManager#setUndoLimit(int) | |
910 */ | |
911 public void setMaximalUndoLevel(int undoLimit) { | |
912 fHistory.setLimit(fUndoContext, undoLimit); | |
913 } | |
914 | |
915 /** | |
916 * Fires a document undo event to all registered document undo listeners. | |
917 * Uses a robust iterator. | |
918 * | |
919 * @param offset the document offset | |
920 * @param text the text that was inserted | |
921 * @param preservedText the text being replaced | |
922 * @param source the source which triggered the event | |
923 * @param eventType the type of event causing the change | |
924 * @param isCompound a flag indicating whether the change is a compound change | |
925 * @see IDocumentUndoListener | |
926 */ | |
927 void fireDocumentUndo(int offset, String text, String preservedText, Object source, int eventType, bool isCompound) { | |
928 eventType= isCompound ? eventType | DocumentUndoEvent.COMPOUND : eventType; | |
929 DocumentUndoEvent event= new DocumentUndoEvent(fDocument, offset, text, preservedText, eventType, source); | |
930 Object[] listeners= fDocumentUndoListeners.getListeners(); | |
931 for (int i= 0; i < listeners.length; i++) { | |
932 ((IDocumentUndoListener)listeners[i]).documentUndoNotification(event); | |
933 } | |
934 } | |
935 | |
936 /** | |
937 * Adds any listeners needed to track the document and the operations | |
938 * history. | |
939 */ | |
940 private void addListeners() { | |
941 fHistoryListener= new HistoryListener(); | |
942 fHistory.addOperationHistoryListener(fHistoryListener); | |
943 listenToTextChanges(true); | |
944 } | |
945 | |
946 /** | |
947 * Removes any listeners that were installed by the document. | |
948 */ | |
949 private void removeListeners() { | |
950 listenToTextChanges(false); | |
951 fHistory.removeOperationHistoryListener(fHistoryListener); | |
952 fHistoryListener= null; | |
953 } | |
954 | |
955 /** | |
956 * Adds the given text edit to the operation history if it is not part of a | |
957 * compound change. | |
958 * | |
959 * @param edit | |
960 * the edit to be added | |
961 */ | |
962 private void addToOperationHistory(UndoableTextChange edit) { | |
963 if (!fFoldingIntoCompoundChange | |
964 || edit instanceof UndoableCompoundTextChange) { | |
965 fHistory.add(edit); | |
966 fLastAddedTextEdit= edit; | |
967 } | |
968 } | |
969 | |
970 /** | |
971 * Disposes the undo history. | |
972 */ | |
973 private void disposeUndoHistory() { | |
974 fHistory.dispose(fUndoContext, true, true, true); | |
975 } | |
976 | |
977 /** | |
978 * Initializes the undo history. | |
979 */ | |
980 private void initializeUndoHistory() { | |
981 if (fHistory !is null && fUndoContext !is null) | |
982 fHistory.dispose(fUndoContext, true, true, false); | |
983 | |
984 } | |
985 | |
986 /** | |
987 * Checks whether the given text starts with a line delimiter and | |
988 * subsequently contains a white space only. | |
989 * | |
990 * @param text the text to check | |
991 * @return <code>true</code> if the text is a line delimiter followed by | |
992 * whitespace, <code>false</code> otherwise | |
993 */ | |
994 private bool isWhitespaceText(String text) { | |
995 | |
996 if (text is null || text.length() is 0) | |
997 return false; | |
998 | |
999 String[] delimiters= fDocument.getLegalLineDelimiters(); | |
1000 int index= TextUtilities.startsWith(delimiters, text); | |
1001 if (index > -1) { | |
1002 char c; | |
1003 int length= text.length(); | |
1004 for (int i= delimiters[index].length(); i < length; i++) { | |
1005 c= text.charAt(i); | |
1006 if (c !is ' ' && c !is '\t') | |
1007 return false; | |
1008 } | |
1009 return true; | |
1010 } | |
1011 | |
1012 return false; | |
1013 } | |
1014 | |
1015 /** | |
1016 * Switches the state of whether there is a text listener or not. | |
1017 * | |
1018 * @param listen the state which should be established | |
1019 */ | |
1020 private void listenToTextChanges(bool listen) { | |
1021 if (listen) { | |
1022 if (fDocumentListener is null && fDocument !is null) { | |
1023 fDocumentListener= new DocumentListener(); | |
1024 fDocument.addDocumentListener(fDocumentListener); | |
1025 } | |
1026 } else if (!listen) { | |
1027 if (fDocumentListener !is null && fDocument !is null) { | |
1028 fDocument.removeDocumentListener(fDocumentListener); | |
1029 fDocumentListener= null; | |
1030 } | |
1031 } | |
1032 } | |
1033 | |
1034 private void processChange(int modelStart, int modelEnd, | |
1035 String insertedText, String replacedText, | |
1036 long beforeChangeModificationStamp, | |
1037 long afterChangeModificationStamp) { | |
1038 | |
1039 if (insertedText is null) | |
1040 insertedText= ""; //$NON-NLS-1$ | |
1041 | |
1042 if (replacedText is null) | |
1043 replacedText= ""; //$NON-NLS-1$ | |
1044 | |
1045 int length= insertedText.length(); | |
1046 int diff= modelEnd - modelStart; | |
1047 | |
1048 if (fCurrent.fUndoModificationStamp is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) | |
1049 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp; | |
1050 | |
1051 // normalize | |
1052 if (diff < 0) { | |
1053 int tmp= modelEnd; | |
1054 modelEnd= modelStart; | |
1055 modelStart= tmp; | |
1056 } | |
1057 | |
1058 if (modelStart is modelEnd) { | |
1059 // text will be inserted | |
1060 if ((length is 1) || isWhitespaceText(insertedText)) { | |
1061 // by typing or whitespace | |
1062 if (!fInserting | |
1063 || (modelStart !is fCurrent.fStart | |
1064 + fTextBuffer.length())) { | |
1065 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp; | |
1066 if (fCurrent.attemptCommit()) | |
1067 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp; | |
1068 | |
1069 fInserting= true; | |
1070 } | |
1071 if (fCurrent.fStart < 0) | |
1072 fCurrent.fStart= fCurrent.fEnd= modelStart; | |
1073 if (length > 0) | |
1074 fTextBuffer.append(insertedText); | |
1075 } else if (length > 0) { | |
1076 // by pasting or model manipulation | |
1077 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp; | |
1078 if (fCurrent.attemptCommit()) | |
1079 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp; | |
1080 | |
1081 fCurrent.fStart= fCurrent.fEnd= modelStart; | |
1082 fTextBuffer.append(insertedText); | |
1083 fCurrent.fRedoModificationStamp= afterChangeModificationStamp; | |
1084 if (fCurrent.attemptCommit()) | |
1085 fCurrent.fUndoModificationStamp= afterChangeModificationStamp; | |
1086 | |
1087 } | |
1088 } else { | |
1089 if (length is 0) { | |
1090 // text will be deleted by backspace or DEL key or empty | |
1091 // clipboard | |
1092 length= replacedText.length(); | |
1093 String[] delimiters= fDocument.getLegalLineDelimiters(); | |
1094 | |
1095 if ((length is 1) | |
1096 || TextUtilities.equals(delimiters, replacedText) > -1) { | |
1097 | |
1098 // whereby selection is empty | |
1099 | |
1100 if (fPreviousDelete.fStart is modelStart | |
1101 && fPreviousDelete.fEnd is modelEnd) { | |
1102 // repeated DEL | |
1103 | |
1104 // correct wrong settings of fCurrent | |
1105 if (fCurrent.fStart is modelEnd | |
1106 && fCurrent.fEnd is modelStart) { | |
1107 fCurrent.fStart= modelStart; | |
1108 fCurrent.fEnd= modelEnd; | |
1109 } | |
1110 // append to buffer && extend edit range | |
1111 fPreservedTextBuffer.append(replacedText); | |
1112 ++fCurrent.fEnd; | |
1113 | |
1114 } else if (fPreviousDelete.fStart is modelEnd) { | |
1115 // repeated backspace | |
1116 | |
1117 // insert in buffer and extend edit range | |
1118 fPreservedTextBuffer.insert(0, replacedText); | |
1119 fCurrent.fStart= modelStart; | |
1120 | |
1121 } else { | |
1122 // either DEL or backspace for the first time | |
1123 | |
1124 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp; | |
1125 if (fCurrent.attemptCommit()) | |
1126 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp; | |
1127 | |
1128 // as we can not decide whether it was DEL or backspace | |
1129 // we initialize for backspace | |
1130 fPreservedTextBuffer.append(replacedText); | |
1131 fCurrent.fStart= modelStart; | |
1132 fCurrent.fEnd= modelEnd; | |
1133 } | |
1134 | |
1135 fPreviousDelete.set(modelStart, modelEnd); | |
1136 | |
1137 } else if (length > 0) { | |
1138 // whereby selection is not empty | |
1139 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp; | |
1140 if (fCurrent.attemptCommit()) | |
1141 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp; | |
1142 | |
1143 fCurrent.fStart= modelStart; | |
1144 fCurrent.fEnd= modelEnd; | |
1145 fPreservedTextBuffer.append(replacedText); | |
1146 } | |
1147 } else { | |
1148 // text will be replaced | |
1149 | |
1150 if (length is 1) { | |
1151 length= replacedText.length(); | |
1152 String[] delimiters= fDocument.getLegalLineDelimiters(); | |
1153 | |
1154 if ((length is 1) | |
1155 || TextUtilities.equals(delimiters, replacedText) > -1) { | |
1156 // because of overwrite mode or model manipulation | |
1157 if (!fOverwriting | |
1158 || (modelStart !is fCurrent.fStart | |
1159 + fTextBuffer.length())) { | |
1160 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp; | |
1161 if (fCurrent.attemptCommit()) | |
1162 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp; | |
1163 | |
1164 fOverwriting= true; | |
1165 } | |
1166 | |
1167 if (fCurrent.fStart < 0) | |
1168 fCurrent.fStart= modelStart; | |
1169 | |
1170 fCurrent.fEnd= modelEnd; | |
1171 fTextBuffer.append(insertedText); | |
1172 fPreservedTextBuffer.append(replacedText); | |
1173 fCurrent.fRedoModificationStamp= afterChangeModificationStamp; | |
1174 return; | |
1175 } | |
1176 } | |
1177 // because of typing or pasting whereby selection is not empty | |
1178 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp; | |
1179 if (fCurrent.attemptCommit()) | |
1180 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp; | |
1181 | |
1182 fCurrent.fStart= modelStart; | |
1183 fCurrent.fEnd= modelEnd; | |
1184 fTextBuffer.append(insertedText); | |
1185 fPreservedTextBuffer.append(replacedText); | |
1186 } | |
1187 } | |
1188 // in all cases, the redo modification stamp is updated on the open | |
1189 // text edit | |
1190 fCurrent.fRedoModificationStamp= afterChangeModificationStamp; | |
1191 } | |
1192 | |
1193 /** | |
1194 * Initialize the receiver. | |
1195 */ | |
1196 private void initialize() { | |
1197 initializeUndoHistory(); | |
1198 | |
1199 // open up the current text edit | |
1200 fCurrent= new UndoableTextChange(this); | |
1201 fPreviousDelete= new UndoableTextChange(this); | |
1202 fTextBuffer= new StringBuffer(); | |
1203 fPreservedTextBuffer= new StringBuffer(); | |
1204 | |
1205 addListeners(); | |
1206 } | |
1207 | |
1208 /** | |
1209 * Reset processChange state. | |
1210 * | |
1211 * @since 3.2 | |
1212 */ | |
1213 private void resetProcessChangeState() { | |
1214 fInserting= false; | |
1215 fOverwriting= false; | |
1216 fPreviousDelete.reinitialize(); | |
1217 } | |
1218 | |
1219 /** | |
1220 * Shutdown the receiver. | |
1221 */ | |
1222 private void shutdown() { | |
1223 removeListeners(); | |
1224 | |
1225 fCurrent= null; | |
1226 fPreviousDelete= null; | |
1227 fTextBuffer= null; | |
1228 fPreservedTextBuffer= null; | |
1229 | |
1230 disposeUndoHistory(); | |
1231 } | |
1232 | |
1233 /** | |
1234 * Return whether or not any clients are connected to the receiver. | |
1235 * | |
1236 * @return <code>true</code> if the receiver is connected to | |
1237 * clients, <code>false</code> if it is not | |
1238 */ | |
1239 bool isConnected() { | |
1240 if (fConnected is null) | |
1241 return false; | |
1242 return !fConnected.isEmpty(); | |
1243 } | |
1244 | |
1245 /* | |
1246 * @see dwtx.jface.text.IDocumentUndoManager#transferUndoHistory(IDocumentUndoManager) | |
1247 */ | |
1248 public void transferUndoHistory(IDocumentUndoManager manager) { | |
1249 IUndoContext oldUndoContext= manager.getUndoContext(); | |
1250 // Get the history for the old undo context. | |
1251 IUndoableOperation [] operations= OperationHistoryFactory.getOperationHistory().getUndoHistory(oldUndoContext); | |
1252 for (int i= 0; i < operations.length; i++) { | |
1253 // First replace the undo context | |
1254 IUndoableOperation op= operations[i]; | |
1255 if (op instanceof IContextReplacingOperation) { | |
1256 ((IContextReplacingOperation)op).replaceContext(oldUndoContext, getUndoContext()); | |
1257 } else { | |
1258 op.addContext(getUndoContext()); | |
1259 op.removeContext(oldUndoContext); | |
1260 } | |
1261 // Now update the manager that owns the text edit. | |
1262 if (op instanceof UndoableTextChange) { | |
1263 ((UndoableTextChange)op).fDocumentUndoManager= this; | |
1264 } | |
1265 } | |
1266 | |
1267 IUndoableOperation op= OperationHistoryFactory.getOperationHistory().getUndoOperation(getUndoContext()); | |
1268 if (op !is null && !(op instanceof UndoableTextChange)) | |
1269 return; | |
1270 | |
1271 // Record the transfer itself as an undoable change. | |
1272 // If the transfer results from some open operation, recording this change will | |
1273 // cause our undo context to be added to the outer operation. If there is no | |
1274 // outer operation, there will be a local change to signify the transfer. | |
1275 // This also serves to synchronize the modification stamps with the documents. | |
1276 UndoableTextChange cmd= new UndoableTextChange(this); | |
1277 cmd.fStart= cmd.fEnd= 0; | |
1278 cmd.fText= cmd.fPreservedText= ""; //$NON-NLS-1$ | |
1279 if (fDocument instanceof IDocumentExtension4) { | |
1280 cmd.fRedoModificationStamp= ((IDocumentExtension4)fDocument).getModificationStamp(); | |
1281 if (op !is null) | |
1282 cmd.fUndoModificationStamp= ((UndoableTextChange)op).fRedoModificationStamp; | |
1283 } | |
1284 addToOperationHistory(cmd); | |
1285 } | |
1286 | |
1287 | |
1288 } |