KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > text > undo > DocumentUndoManager


1 /*******************************************************************************
2  * Copyright (c) 2006 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  *******************************************************************************/

11 package org.eclipse.text.undo;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.List JavaDoc;
15
16 import org.eclipse.core.commands.ExecutionException;
17 import org.eclipse.core.commands.operations.AbstractOperation;
18 import org.eclipse.core.commands.operations.IContextReplacingOperation;
19 import org.eclipse.core.commands.operations.IOperationHistory;
20 import org.eclipse.core.commands.operations.IOperationHistoryListener;
21 import org.eclipse.core.commands.operations.IUndoContext;
22 import org.eclipse.core.commands.operations.IUndoableOperation;
23 import org.eclipse.core.commands.operations.ObjectUndoContext;
24 import org.eclipse.core.commands.operations.OperationHistoryEvent;
25 import org.eclipse.core.commands.operations.OperationHistoryFactory;
26
27 import org.eclipse.core.runtime.Assert;
28 import org.eclipse.core.runtime.IAdaptable;
29 import org.eclipse.core.runtime.IProgressMonitor;
30 import org.eclipse.core.runtime.IStatus;
31 import org.eclipse.core.runtime.ListenerList;
32 import org.eclipse.core.runtime.Status;
33
34 import org.eclipse.jface.text.BadLocationException;
35 import org.eclipse.jface.text.DocumentEvent;
36 import org.eclipse.jface.text.IDocument;
37 import org.eclipse.jface.text.IDocumentExtension4;
38 import org.eclipse.jface.text.IDocumentListener;
39 import org.eclipse.jface.text.TextUtilities;
40
41 /**
42  * A standard implementation of a document-based undo manager that
43  * creates an undo history based on changes to its document.
44  * <p>
45  * Based on the 3.1 implementation of DefaultUndoManager, it was implemented
46  * using the document-related manipulations defined in the original
47  * DefaultUndoManager, by separating the document manipulations from the
48  * viewer-specific processing.</p>
49  * <p>
50  * The classes representing individual text edits (formerly text commands)
51  * were promoted from inner types to their own classes in order to support
52  * reassignment to a different undo manager.<p>
53  * <p>
54  * This class is not intended to be subclassed.
55  * </p>
56  *
57  * @see IDocumentUndoManager
58  * @see DocumentUndoManagerRegistry
59  * @see IDocumentUndoListener
60  * @see org.eclipse.jface.text.IDocument
61  * @since 3.2
62  */

63 public class DocumentUndoManager implements IDocumentUndoManager {
64     
65     
66     /**
67      * Represents an undo-able text change, described as the
68      * replacement of some preserved text with new text.
69      * <p>
70      * Based on the DefaultUndoManager.TextCommand from R3.1.
71      * </p>
72      */

73     private static class UndoableTextChange extends AbstractOperation {
74
75         /** The start index of the replaced text. */
76         protected int fStart= -1;
77
78         /** The end index of the replaced text. */
79         protected int fEnd= -1;
80
81         /** The newly inserted text. */
82         protected String JavaDoc fText;
83
84         /** The replaced text. */
85         protected String JavaDoc fPreservedText;
86
87         /** The undo modification stamp. */
88         protected long fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
89
90         /** The redo modification stamp. */
91         protected long fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
92
93         /** The undo manager that generated the change. */
94         protected DocumentUndoManager fDocumentUndoManager;
95
96         /**
97          * Creates a new text change.
98          *
99          * @param manager the undo manager for this change
100          */

101         UndoableTextChange(DocumentUndoManager manager) {
102             super(UndoMessages.getString("DocumentUndoManager.operationLabel")); //$NON-NLS-1$
103
this.fDocumentUndoManager= manager;
104             addContext(manager.getUndoContext());
105         }
106
107         /**
108          * Re-initializes this text change.
109          */

110         protected void reinitialize() {
111             fStart= fEnd= -1;
112             fText= fPreservedText= null;
113             fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
114             fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
115         }
116
117         /**
118          * Sets the start and the end index of this change.
119          *
120          * @param start the start index
121          * @param end the end index
122          */

123         protected void set(int start, int end) {
124             fStart= start;
125             fEnd= end;
126             fText= null;
127             fPreservedText= null;
128         }
129
130         /*
131          * @see org.eclipse.core.commands.operations.IUndoableOperation#dispose()
132          */

133         public void dispose() {
134             reinitialize();
135         }
136
137         /**
138          * Undo the change described by this change.
139          */

140         protected void undoTextChange() {
141             try {
142                 if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4)
143                     ((IDocumentExtension4) fDocumentUndoManager.fDocument).replace(fStart, fText
144                             .length(), fPreservedText, fUndoModificationStamp);
145                 else
146                     fDocumentUndoManager.fDocument.replace(fStart, fText.length(),
147                             fPreservedText);
148             } catch (BadLocationException x) {
149             }
150         }
151
152         /*
153          * @see org.eclipse.core.commands.operations.IUndoableOperation#canUndo()
154          */

155         public boolean canUndo() {
156             if (isValid()) {
157                 if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4) {
158                     long docStamp= ((IDocumentExtension4) fDocumentUndoManager.fDocument)
159                             .getModificationStamp();
160
161                     // Normal case: an undo is valid if its redo will restore
162
// document to its current modification stamp
163
boolean canUndo= docStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP
164                             || docStamp == getRedoModificationStamp();
165
166                     /*
167                      * Special case to check if the answer is false. If the last
168                      * document change was empty, then the document's modification
169                      * stamp was incremented but nothing was committed. The
170                      * operation being queried has an older stamp. In this case
171                      * only, the comparison is different. A sequence of document
172                      * changes that include an empty change is handled correctly
173                      * when a valid commit follows the empty change, but when
174                      * #canUndo() is queried just after an empty change, we must
175                      * special case the check. The check is very specific to prevent
176                      * false positives. see
177                      * https://bugs.eclipse.org/bugs/show_bug.cgi?id=98245
178                      */

179                     if (!canUndo
180                             && this == fDocumentUndoManager.fHistory
181                                     .getUndoOperation(fDocumentUndoManager.fUndoContext)
182                                 // this is the latest operation
183
&& this != fDocumentUndoManager.fCurrent
184                                 // there is a more current operation not on the stack
185
&& !fDocumentUndoManager.fCurrent.isValid()
186                             // the current operation is not a valid document
187
// modification
188
&& fDocumentUndoManager.fCurrent.fUndoModificationStamp !=
189                             // the invalid current operation has a document stamp
190
IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) {
191                         canUndo= fDocumentUndoManager.fCurrent.fRedoModificationStamp == docStamp;
192                     }
193                     /*
194                      * When the composite is the current operation, it may hold the
195                      * timestamp of a no-op change. We check this here rather than
196                      * in an override of canUndo() in UndoableCompoundTextChange simply to
197                      * keep all the special case checks in one place.
198                      */

199                     if (!canUndo
200                             && this == fDocumentUndoManager.fHistory
201                                     .getUndoOperation(fDocumentUndoManager.fUndoContext)
202                             && // this is the latest operation
203
this instanceof UndoableCompoundTextChange
204                             && this == fDocumentUndoManager.fCurrent
205                             && // this is the current operation
206
this.fStart == -1
207                             && // the current operation text is not valid
208
fDocumentUndoManager.fCurrent.fRedoModificationStamp != IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) {
209                             // but it has a redo stamp
210
canUndo= fDocumentUndoManager.fCurrent.fRedoModificationStamp == docStamp;
211                     }
212
213                 }
214                 // if there is no timestamp to check, simply return true per the
215
// 3.0.1 behavior
216
return true;
217             }
218             return false;
219         }
220
221         /*
222          * @see org.eclipse.core.commands.operations.IUndoableOperation#canRedo()
223          */

224         public boolean canRedo() {
225             if (isValid()) {
226                 if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4) {
227                     long docStamp= ((IDocumentExtension4) fDocumentUndoManager.fDocument)
228                             .getModificationStamp();
229                     return docStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP
230                             || docStamp == getUndoModificationStamp();
231                 }
232                 // if there is no timestamp to check, simply return true per the
233
// 3.0.1 behavior
234
return true;
235             }
236             return false;
237         }
238
239         /*
240          * @see org.eclipse.core.commands.operations.IUndoableOperation#canExecute()
241          */

242         public boolean canExecute() {
243             return fDocumentUndoManager.isConnected();
244         }
245
246         /*
247          * @see org.eclipse.core.commands.operations.IUndoableOperation.IUndoableOperation#execute(IProgressMonitor, IAdaptable)
248          */

249         public IStatus execute(IProgressMonitor monitor, IAdaptable uiInfo) {
250             // Text changes execute as they are typed, so executing one has no
251
// effect.
252
return Status.OK_STATUS;
253         }
254
255         /**
256          * {@inheritDoc}
257          * Notifies clients about the undo.
258          */

259         public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) {
260             if (isValid()) {
261                 fDocumentUndoManager.fireDocumentUndo(fStart, fPreservedText, fText, uiInfo, DocumentUndoEvent.ABOUT_TO_UNDO, false);
262                 undoTextChange();
263                 fDocumentUndoManager.resetProcessChangeState();
264                 fDocumentUndoManager.fireDocumentUndo(fStart, fPreservedText, fText, uiInfo, DocumentUndoEvent.UNDONE, false);
265                 return Status.OK_STATUS;
266             }
267             return IOperationHistory.OPERATION_INVALID_STATUS;
268         }
269
270         /**
271          * Re-applies the change described by this change.
272          */

273         protected void redoTextChange() {
274             try {
275                 if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4)
276                     ((IDocumentExtension4) fDocumentUndoManager.fDocument).replace(fStart, fEnd - fStart, fText, fRedoModificationStamp);
277                 else
278                     fDocumentUndoManager.fDocument.replace(fStart, fEnd - fStart, fText);
279             } catch (BadLocationException x) {
280             }
281         }
282
283         /**
284          * Re-applies the change described by this change that was previously
285          * undone. Also notifies clients about the redo.
286          *
287          * @param monitor the progress monitor to use if necessary
288          * @param uiInfo an adaptable that can provide UI info if needed
289          * @return the status
290          */

291         public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) {
292             if (isValid()) {
293                 redoTextChange();
294                 fDocumentUndoManager.resetProcessChangeState();
295                 fDocumentUndoManager.fireDocumentUndo(fStart, fText, fPreservedText, uiInfo, DocumentUndoEvent.REDONE, false);
296                 return Status.OK_STATUS;
297             }
298             return IOperationHistory.OPERATION_INVALID_STATUS;
299         }
300
301         /**
302          * Update the change in response to a commit.
303          */

304
305         protected void updateTextChange() {
306             fText= fDocumentUndoManager.fTextBuffer.toString();
307             fDocumentUndoManager.fTextBuffer.setLength(0);
308             fPreservedText= fDocumentUndoManager.fPreservedTextBuffer.toString();
309             fDocumentUndoManager.fPreservedTextBuffer.setLength(0);
310         }
311
312         /**
313          * Creates a new uncommitted text change depending on whether a compound
314          * change is currently being executed.
315          *
316          * @return a new, uncommitted text change or a compound text change
317          */

318         protected UndoableTextChange createCurrent() {
319             if (fDocumentUndoManager.fFoldingIntoCompoundChange)
320                 return new UndoableCompoundTextChange(fDocumentUndoManager);
321             return new UndoableTextChange(fDocumentUndoManager);
322         }
323
324         /**
325          * Commits the current change into this one.
326          */

327         protected void commit() {
328             if (fStart < 0) {
329                 if (fDocumentUndoManager.fFoldingIntoCompoundChange) {
330                     fDocumentUndoManager.fCurrent= createCurrent();
331                 } else {
332                     reinitialize();
333                 }
334             } else {
335                 updateTextChange();
336                 fDocumentUndoManager.fCurrent= createCurrent();
337             }
338             fDocumentUndoManager.resetProcessChangeState();
339         }
340
341         /**
342          * Updates the text from the buffers without resetting the buffers or adding
343          * anything to the stack.
344          */

345         protected void pretendCommit() {
346             if (fStart > -1) {
347                 fText= fDocumentUndoManager.fTextBuffer.toString();
348                 fPreservedText= fDocumentUndoManager.fPreservedTextBuffer.toString();
349             }
350         }
351
352         /**
353          * Attempt a commit of this change and answer true if a new fCurrent was
354          * created as a result of the commit.
355          *
356          * @return <code>true</code> if the change was committed and created
357          * a new <code>fCurrent</code>, <code>false</code> if not
358          */

359         protected boolean attemptCommit() {
360             pretendCommit();
361             if (isValid()) {
362                 fDocumentUndoManager.commit();
363                 return true;
364             }
365             return false;
366         }
367
368         /**
369          * Checks whether this text change is valid for undo or redo.
370          *
371          * @return <code>true</code> if the change is valid for undo or redo
372          */

373         protected boolean isValid() {
374             return fStart > -1 && fEnd > -1 && fText != null;
375         }
376
377         /*
378          * @see java.lang.Object#toString()
379          */

380         public String JavaDoc toString() {
381             String JavaDoc delimiter= ", "; //$NON-NLS-1$
382
StringBuffer JavaDoc text= new StringBuffer JavaDoc(super.toString());
383             text.append("\n"); //$NON-NLS-1$
384
text.append(this.getClass().getName());
385             text.append(" undo modification stamp: "); //$NON-NLS-1$
386
text.append(fUndoModificationStamp);
387             text.append(" redo modification stamp: "); //$NON-NLS-1$
388
text.append(fRedoModificationStamp);
389             text.append(" start: "); //$NON-NLS-1$
390
text.append(fStart);
391             text.append(delimiter);
392             text.append("end: "); //$NON-NLS-1$
393
text.append(fEnd);
394             text.append(delimiter);
395             text.append("text: '"); //$NON-NLS-1$
396
text.append(fText);
397             text.append('\'');
398             text.append(delimiter);
399             text.append("preservedText: '"); //$NON-NLS-1$
400
text.append(fPreservedText);
401             text.append('\'');
402             return text.toString();
403         }
404
405         /**
406          * Return the undo modification stamp
407          *
408          * @return the undo modification stamp for this change
409          */

410         protected long getUndoModificationStamp() {
411             return fUndoModificationStamp;
412         }
413
414         /**
415          * Return the redo modification stamp
416          *
417          * @return the redo modification stamp for this change
418          */

419         protected long getRedoModificationStamp() {
420             return fRedoModificationStamp;
421         }
422     }
423     
424     
425     /**
426      * Represents an undo-able text change consisting of several individual
427      * changes.
428      */

429     private static class UndoableCompoundTextChange extends UndoableTextChange {
430
431         /** The list of individual changes */
432         private List JavaDoc fChanges= new ArrayList JavaDoc();
433
434         /**
435          * Creates a new compound text change.
436          *
437          * @param manager
438          * the undo manager for this change
439          */

440         UndoableCompoundTextChange(DocumentUndoManager manager) {
441             super(manager);
442         }
443
444         /**
445          * Adds a new individual change to this compound change.
446          *
447          * @param change the change to be added
448          */

449         protected void add(UndoableTextChange change) {
450             fChanges.add(change);
451         }
452
453         /*
454          * @see org.eclipse.text.undo.UndoableTextChange#undo(org.eclipse.core.runtime.IProgressMonitor, org.eclipse.core.runtime.IAdaptable)
455          */

456         public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) {
457
458             int size= fChanges.size();
459             if (size > 0) {
460                 UndoableTextChange c;
461
462                 c= (UndoableTextChange) fChanges.get(0);
463                 fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fPreservedText, c.fText, uiInfo, DocumentUndoEvent.ABOUT_TO_UNDO, true);
464
465                 for (int i= size - 1; i >= 0; --i) {
466                     c= (UndoableTextChange) fChanges.get(i);
467                     c.undoTextChange();
468                 }
469                 fDocumentUndoManager.resetProcessChangeState();
470                 fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fPreservedText, c.fText, uiInfo,
471                         DocumentUndoEvent.UNDONE, true);
472             }
473             return Status.OK_STATUS;
474         }
475
476         /*
477          * @see org.eclipse.text.undo.UndoableTextChange#redo(org.eclipse.core.runtime.IProgressMonitor, org.eclipse.core.runtime.IAdaptable)
478          */

479         public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) {
480
481             int size= fChanges.size();
482             if (size > 0) {
483
484                 UndoableTextChange c;
485                 c= (UndoableTextChange) fChanges.get(size - 1);
486                 fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fText, c.fPreservedText, uiInfo, DocumentUndoEvent.ABOUT_TO_REDO, true);
487
488                 for (int i= 0; i <= size - 1; ++i) {
489                     c= (UndoableTextChange) fChanges.get(i);
490                     c.redoTextChange();
491                 }
492                 fDocumentUndoManager.resetProcessChangeState();
493                 fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fText, c.fPreservedText, uiInfo,
494                         DocumentUndoEvent.REDONE, true);
495             }
496
497             return Status.OK_STATUS;
498         }
499
500         /*
501          * @see org.eclipse.text.undo.UndoableTextChange#updateTextChange()
502          */

503         protected void updateTextChange() {
504             // first gather the data from the buffers
505
super.updateTextChange();
506
507             // the result of the update is stored as a child change
508
UndoableTextChange c= new UndoableTextChange(fDocumentUndoManager);
509             c.fStart= fStart;
510             c.fEnd= fEnd;
511             c.fText= fText;
512             c.fPreservedText= fPreservedText;
513             c.fUndoModificationStamp= fUndoModificationStamp;
514             c.fRedoModificationStamp= fRedoModificationStamp;
515             add(c);
516
517             // clear out all indexes now that the child is added
518
reinitialize();
519         }
520
521         /*
522          * @see org.eclipse.text.undo.UndoableTextChange#createCurrent()
523          */

524         protected UndoableTextChange createCurrent() {
525
526             if (!fDocumentUndoManager.fFoldingIntoCompoundChange)
527                 return new UndoableTextChange(fDocumentUndoManager);
528
529             reinitialize();
530             return this;
531         }
532
533         /*
534          * @see org.eclipse.text.undo.UndoableTextChange#commit()
535          */

536         protected void commit() {
537             // if there is pending data, update the text change
538
if (fStart > -1)
539                 updateTextChange();
540             fDocumentUndoManager.fCurrent= createCurrent();
541             fDocumentUndoManager.resetProcessChangeState();
542         }
543         
544         /*
545          * @see org.eclipse.text.undo.UndoableTextChange#isValid()
546          */

547         protected boolean isValid() {
548             return fStart > -1 || fChanges.size() > 0;
549         }
550
551         /*
552          * @see org.eclipse.text.undo.UndoableTextChange#getUndoModificationStamp()
553          */

554         protected long getUndoModificationStamp() {
555             if (fStart > -1)
556                 return super.getUndoModificationStamp();
557             else if (fChanges.size() > 0)
558                 return ((UndoableTextChange) fChanges.get(0))
559                         .getUndoModificationStamp();
560
561             return fUndoModificationStamp;
562         }
563
564         /*
565          * @see org.eclipse.text.undo.UndoableTextChange#getRedoModificationStamp()
566          */

567         protected long getRedoModificationStamp() {
568             if (fStart > -1)
569                 return super.getRedoModificationStamp();
570             else if (fChanges.size() > 0)
571                 return ((UndoableTextChange) fChanges.get(fChanges.size() - 1))
572                         .getRedoModificationStamp();
573
574             return fRedoModificationStamp;
575         }
576     }
577     
578
579     /**
580      * Internal listener to document changes.
581      */

582     private class DocumentListener implements IDocumentListener {
583
584         private String JavaDoc fReplacedText;
585
586         /*
587          * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
588          */

589         public void documentAboutToBeChanged(DocumentEvent event) {
590             try {
591                 fReplacedText= event.getDocument().get(event.getOffset(),
592                         event.getLength());
593                 fPreservedUndoModificationStamp= event.getModificationStamp();
594             } catch (BadLocationException x) {
595                 fReplacedText= null;
596             }
597         }
598
599         /*
600          * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
601          */

602         public void documentChanged(DocumentEvent event) {
603             fPreservedRedoModificationStamp= event.getModificationStamp();
604
605             // record the current valid state for the top operation in case it
606
// remains the
607
// top operation but changes state.
608
IUndoableOperation op= fHistory.getUndoOperation(fUndoContext);
609             boolean wasValid= false;
610             if (op != null)
611                 wasValid= op.canUndo();
612             // Process the change, providing the before and after timestamps
613
processChange(event.getOffset(), event.getOffset()
614                     + event.getLength(), event.getText(), fReplacedText,
615                     fPreservedUndoModificationStamp,
616                     fPreservedRedoModificationStamp);
617
618             // now update fCurrent with the latest buffers from the document
619
// change.
620
fCurrent.pretendCommit();
621
622             if (op == fCurrent) {
623                 // if the document change did not cause a new fCurrent to be
624
// created, then we should
625
// notify the history that the current operation changed if its
626
// validity has changed.
627
if (wasValid != fCurrent.isValid())
628                     fHistory.operationChanged(op);
629             } else {
630                 // if the change created a new fCurrent that we did not yet add
631
// to the
632
// stack, do so if it's valid and we are not in the middle of a
633
// compound change.
634
if (fCurrent != fLastAddedTextEdit && fCurrent.isValid()) {
635                     addToOperationHistory(fCurrent);
636                 }
637             }
638         }
639     }
640
641     /*
642      * @see IOperationHistoryListener
643      */

644     private class HistoryListener implements IOperationHistoryListener {
645         
646         private IUndoableOperation fOperation;
647
648         public void historyNotification(final OperationHistoryEvent event) {
649             final int type= event.getEventType();
650             switch (type) {
651             case OperationHistoryEvent.ABOUT_TO_UNDO:
652             case OperationHistoryEvent.ABOUT_TO_REDO:
653                 // if this is one of our operations
654
if (event.getOperation().hasContext(fUndoContext)) {
655                     // if we are undoing/redoing an operation we generated, then
656
// ignore
657
// the document changes associated with this undo or redo.
658
if (event.getOperation() instanceof UndoableTextChange) {
659                         listenToTextChanges(false);
660
661                         // in the undo case only, make sure compounds are closed
662
if (type == OperationHistoryEvent.ABOUT_TO_UNDO) {
663                             if (fFoldingIntoCompoundChange) {
664                                 endCompoundChange();
665                             }
666                         }
667                     } else {
668                         // the undo or redo has our context, but it is not one
669
// of our edits. We will listen to the changes, but will
670
// reset the state that tracks the undo/redo history.
671
commit();
672                         fLastAddedTextEdit= null;
673                     }
674                     fOperation= event.getOperation();
675                 }
676                 break;
677             case OperationHistoryEvent.UNDONE:
678             case OperationHistoryEvent.REDONE:
679             case OperationHistoryEvent.OPERATION_NOT_OK:
680                 if (event.getOperation() == fOperation) {
681                     listenToTextChanges(true);
682                     fOperation= null;
683                 }
684                 break;
685             }
686         }
687
688     }
689
690     /**
691      * The undo context for this document undo manager.
692      */

693     private ObjectUndoContext fUndoContext;
694
695     /**
696      * The document whose changes are being tracked.
697      */

698     private IDocument fDocument;
699
700     /**
701      * The currently constructed edit.
702      */

703     private UndoableTextChange fCurrent;
704
705     /**
706      * The internal document listener.
707      */

708     private DocumentListener fDocumentListener;
709
710     /**
711      * Indicates whether the current change belongs to a compound change.
712      */

713     private boolean fFoldingIntoCompoundChange= false;
714
715     /**
716      * The operation history being used to store the undo history.
717      */

718     private IOperationHistory fHistory;
719
720     /**
721      * The operation history listener used for managing undo and redo before and
722      * after the individual edits are performed.
723      */

724     private IOperationHistoryListener fHistoryListener;
725
726     /**
727      * The text edit last added to the operation history. This must be tracked
728      * internally instead of asking the history, since outside parties may be
729      * placing items on our undo/redo history.
730      */

731     private UndoableTextChange fLastAddedTextEdit= null;
732
733     /**
734      * The document modification stamp for redo.
735      */

736     private long fPreservedRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
737
738     /**
739      * Text buffer to collect viewer content which has been replaced
740      */

741     private StringBuffer JavaDoc fPreservedTextBuffer;
742
743     /**
744      * The document modification stamp for undo.
745      */

746     private long fPreservedUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
747
748     /**
749      * The last delete text edit.
750      */

751     private UndoableTextChange fPreviousDelete;
752
753     /**
754      * Text buffer to collect text which is inserted into the viewer
755      */

756     private StringBuffer JavaDoc fTextBuffer;
757
758     /** Indicates inserting state. */
759     private boolean fInserting= false;
760
761     /** Indicates overwriting state. */
762     private boolean fOverwriting= false;
763
764     /** The registered document listeners. */
765     private ListenerList fDocumentUndoListeners;
766
767     /** The list of clients connected. */
768     private List JavaDoc fConnected;
769
770     /**
771      *
772      * Create a DocumentUndoManager for the given document.
773      *
774      * @param document the document whose undo history is being managed.
775      */

776     public DocumentUndoManager(IDocument document) {
777         super();
778         Assert.isNotNull(document);
779         fDocument= document;
780         fHistory= OperationHistoryFactory.getOperationHistory();
781         fUndoContext= new ObjectUndoContext(fDocument);
782         fConnected= new ArrayList JavaDoc();
783         fDocumentUndoListeners= new ListenerList();
784     }
785
786     /*
787      * @see org.eclipse.jface.text.IDocumentUndoManager#addDocumentUndoListener(org.eclipse.jface.text.IDocumentUndoListener)
788      */

789     public void addDocumentUndoListener(IDocumentUndoListener listener) {
790         fDocumentUndoListeners.add(listener);
791     }
792
793     /*
794      * @see org.eclipse.jface.text.IDocumentUndoManager#removeDocumentUndoListener(org.eclipse.jface.text.IDocumentUndoListener)
795      */

796     public void removeDocumentUndoListener(IDocumentUndoListener listener) {
797         fDocumentUndoListeners.remove(listener);
798     }
799
800     /*
801      * @see org.eclipse.jface.text.IDocumentUndoManager#getUndoContext()
802      */

803     public IUndoContext getUndoContext() {
804         return fUndoContext;
805     }
806
807     /*
808      * @see org.eclipse.jface.text.IDocumentUndoManager#commit()
809      */

810     public void commit() {
811         // if fCurrent has never been placed on the history, do so now.
812
// this can happen when there are multiple programmatically commits in a
813
// single document change.
814
if (fLastAddedTextEdit != fCurrent) {
815             fCurrent.pretendCommit();
816             if (fCurrent.isValid())
817                 addToOperationHistory(fCurrent);
818         }
819         fCurrent.commit();
820     }
821     
822     /*
823      * @see org.eclipse.text.undo.IDocumentUndoManager#reset()
824      */

825     public void reset() {
826         if (isConnected()) {
827             shutdown();
828             initialize();
829         }
830     }
831
832     /*
833      * @see org.eclipse.text.undo.IDocumentUndoManager#redoable()
834      */

835     public boolean redoable() {
836         return OperationHistoryFactory.getOperationHistory().canRedo(fUndoContext);
837     }
838
839     /*
840      * @see org.eclipse.text.undo.IDocumentUndoManager#undoable()
841      */

842     public boolean undoable() {
843         return OperationHistoryFactory.getOperationHistory().canUndo(fUndoContext);
844     }
845
846     /*
847      * @see org.eclipse.text.undo.IDocumentUndoManager#undo()
848      */

849     public void redo() throws ExecutionException {
850         if (isConnected() && redoable())
851             OperationHistoryFactory.getOperationHistory().redo(getUndoContext(), null, null);
852     }
853
854     /*
855      * @see org.eclipse.text.undo.IDocumentUndoManager#undo()
856      */

857     public void undo() throws ExecutionException {
858         if (undoable())
859             OperationHistoryFactory.getOperationHistory().undo(fUndoContext, null, null);
860     }
861
862     /*
863      * @see org.eclipse.jface.text.IDocumentUndoManager#connect(java.lang.Object)
864      */

865     public void connect(Object JavaDoc client) {
866         if (!isConnected()) {
867             initialize();
868         }
869         if (!fConnected.contains(client))
870             fConnected.add(client);
871     }
872
873     /*
874      * @see org.eclipse.jface.text.IDocumentUndoManager#disconnect(java.lang.Object)
875      */

876     public void disconnect(Object JavaDoc client) {
877         fConnected.remove(client);
878         if (!isConnected()) {
879             shutdown();
880         }
881     }
882
883     /*
884      * @see org.eclipse.jface.text.IDocumentUndoManager#beginCompoundChange()
885      */

886     public void beginCompoundChange() {
887         if (isConnected()) {
888             fFoldingIntoCompoundChange= true;
889             commit();
890         }
891     }
892
893     /*
894      * @see org.eclipse.jface.text.IDocumentUndoManager#endCompoundChange()
895      */

896     public void endCompoundChange() {
897         if (isConnected()) {
898             fFoldingIntoCompoundChange= false;
899             commit();
900         }
901     }
902
903     /*
904      * @see org.eclipse.jface.text.IDocumentUndoManager#setUndoLimit(int)
905      */

906     public void setMaximalUndoLevel(int undoLimit) {
907         fHistory.setLimit(fUndoContext, undoLimit);
908     }
909
910     /**
911      * Fires a document undo event to all registered document undo listeners.
912      * Uses a robust iterator.
913      *
914      * @param offset the document offset
915      * @param text the text that was inserted
916      * @param preservedText the text being replaced
917      * @param source the source which triggered the event
918      * @param eventType the type of event causing the change
919      * @param isCompound a flag indicating whether the change is a compound change
920      * @see IDocumentUndoListener
921      */

922     void fireDocumentUndo(int offset, String JavaDoc text, String JavaDoc preservedText, Object JavaDoc source, int eventType, boolean isCompound) {
923         eventType= isCompound ? eventType | DocumentUndoEvent.COMPOUND : eventType;
924         DocumentUndoEvent event= new DocumentUndoEvent(fDocument, offset, text, preservedText, eventType, source);
925         Object JavaDoc[] listeners= fDocumentUndoListeners.getListeners();
926         for (int i= 0; i < listeners.length; i++) {
927             ((IDocumentUndoListener)listeners[i]).documentUndoNotification(event);
928         }
929     }
930
931     /**
932      * Adds any listeners needed to track the document and the operations
933      * history.
934      */

935     private void addListeners() {
936         fHistoryListener= new HistoryListener();
937         fHistory.addOperationHistoryListener(fHistoryListener);
938         listenToTextChanges(true);
939     }
940
941     /**
942      * Removes any listeners that were installed by the document.
943      */

944     private void removeListeners() {
945         listenToTextChanges(false);
946         fHistory.removeOperationHistoryListener(fHistoryListener);
947         fHistoryListener= null;
948     }
949
950     /**
951      * Adds the given text edit to the operation history if it is not part of a
952      * compound change.
953      *
954      * @param edit
955      * the edit to be added
956      */

957     private void addToOperationHistory(UndoableTextChange edit) {
958         if (!fFoldingIntoCompoundChange
959                 || edit instanceof UndoableCompoundTextChange) {
960             fHistory.add(edit);
961             fLastAddedTextEdit= edit;
962         }
963     }
964
965     /**
966      * Disposes the undo history.
967      */

968     private void disposeUndoHistory() {
969         fHistory.dispose(fUndoContext, true, true, true);
970     }
971
972     /**
973      * Initializes the undo history.
974      */

975     private void initializeUndoHistory() {
976         if (fHistory != null && fUndoContext != null)
977             fHistory.dispose(fUndoContext, true, true, false);
978
979     }
980
981     /**
982      * Checks whether the given text starts with a line delimiter and
983      * subsequently contains a white space only.
984      *
985      * @param text the text to check
986      * @return <code>true</code> if the text is a line delimiter followed by
987      * whitespace, <code>false</code> otherwise
988      */

989     private boolean isWhitespaceText(String JavaDoc text) {
990
991         if (text == null || text.length() == 0)
992             return false;
993
994         String JavaDoc[] delimiters= fDocument.getLegalLineDelimiters();
995         int index= TextUtilities.startsWith(delimiters, text);
996         if (index > -1) {
997             char c;
998             int length= text.length();
999             for (int i= delimiters[index].length(); i < length; i++) {
1000                c= text.charAt(i);
1001                if (c != ' ' && c != '\t')
1002                    return false;
1003            }
1004            return true;
1005        }
1006
1007        return false;
1008    }
1009
1010    /**
1011     * Switches the state of whether there is a text listener or not.
1012     *
1013     * @param listen the state which should be established
1014     */

1015    private void listenToTextChanges(boolean listen) {
1016        if (listen) {
1017            if (fDocumentListener == null && fDocument != null) {
1018                fDocumentListener= new DocumentListener();
1019                fDocument.addDocumentListener(fDocumentListener);
1020            }
1021        } else if (!listen) {
1022            if (fDocumentListener != null && fDocument != null) {
1023                fDocument.removeDocumentListener(fDocumentListener);
1024                fDocumentListener= null;
1025            }
1026        }
1027    }
1028
1029    private void processChange(int modelStart, int modelEnd,
1030            String JavaDoc insertedText, String JavaDoc replacedText,
1031            long beforeChangeModificationStamp,
1032            long afterChangeModificationStamp) {
1033
1034        if (insertedText == null)
1035            insertedText= ""; //$NON-NLS-1$
1036

1037        if (replacedText == null)
1038            replacedText= ""; //$NON-NLS-1$
1039

1040        int length= insertedText.length();
1041        int diff= modelEnd - modelStart;
1042
1043        if (fCurrent.fUndoModificationStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP)
1044            fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1045
1046        // normalize
1047
if (diff < 0) {
1048            int tmp= modelEnd;
1049            modelEnd= modelStart;
1050            modelStart= tmp;
1051        }
1052
1053        if (modelStart == modelEnd) {
1054            // text will be inserted
1055
if ((length == 1) || isWhitespaceText(insertedText)) {
1056                // by typing or whitespace
1057
if (!fInserting
1058                        || (modelStart != fCurrent.fStart
1059                                + fTextBuffer.length())) {
1060                    fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1061                    if (fCurrent.attemptCommit())
1062                        fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1063
1064                    fInserting= true;
1065                }
1066                if (fCurrent.fStart < 0)
1067                    fCurrent.fStart= fCurrent.fEnd= modelStart;
1068                if (length > 0)
1069                    fTextBuffer.append(insertedText);
1070            } else if (length > 0) {
1071                // by pasting or model manipulation
1072
fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1073                if (fCurrent.attemptCommit())
1074                    fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1075
1076                fCurrent.fStart= fCurrent.fEnd= modelStart;
1077                fTextBuffer.append(insertedText);
1078                fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
1079                if (fCurrent.attemptCommit())
1080                    fCurrent.fUndoModificationStamp= afterChangeModificationStamp;
1081
1082            }
1083        } else {
1084            if (length == 0) {
1085                // text will be deleted by backspace or DEL key or empty
1086
// clipboard
1087
length= replacedText.length();
1088                String JavaDoc[] delimiters= fDocument.getLegalLineDelimiters();
1089
1090                if ((length == 1)
1091                        || TextUtilities.equals(delimiters, replacedText) > -1) {
1092
1093                    // whereby selection is empty
1094

1095                    if (fPreviousDelete.fStart == modelStart
1096                            && fPreviousDelete.fEnd == modelEnd) {
1097                        // repeated DEL
1098

1099                        // correct wrong settings of fCurrent
1100
if (fCurrent.fStart == modelEnd
1101                                && fCurrent.fEnd == modelStart) {
1102                            fCurrent.fStart= modelStart;
1103                            fCurrent.fEnd= modelEnd;
1104                        }
1105                        // append to buffer && extend edit range
1106
fPreservedTextBuffer.append(replacedText);
1107                        ++fCurrent.fEnd;
1108
1109                    } else if (fPreviousDelete.fStart == modelEnd) {
1110                        // repeated backspace
1111

1112                        // insert in buffer and extend edit range
1113
fPreservedTextBuffer.insert(0, replacedText);
1114                        fCurrent.fStart= modelStart;
1115
1116                    } else {
1117                        // either DEL or backspace for the first time
1118

1119                        fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1120                        if (fCurrent.attemptCommit())
1121                            fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1122
1123                        // as we can not decide whether it was DEL or backspace
1124
// we initialize for backspace
1125
fPreservedTextBuffer.append(replacedText);
1126                        fCurrent.fStart= modelStart;
1127                        fCurrent.fEnd= modelEnd;
1128                    }
1129
1130                    fPreviousDelete.set(modelStart, modelEnd);
1131
1132                } else if (length > 0) {
1133                    // whereby selection is not empty
1134
fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1135                    if (fCurrent.attemptCommit())
1136                        fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1137
1138                    fCurrent.fStart= modelStart;
1139                    fCurrent.fEnd= modelEnd;
1140                    fPreservedTextBuffer.append(replacedText);
1141                }
1142            } else {
1143                // text will be replaced
1144

1145                if (length == 1) {
1146                    length= replacedText.length();
1147                    String JavaDoc[] delimiters= fDocument.getLegalLineDelimiters();
1148
1149                    if ((length == 1)
1150                            || TextUtilities.equals(delimiters, replacedText) > -1) {
1151                        // because of overwrite mode or model manipulation
1152
if (!fOverwriting
1153                                || (modelStart != fCurrent.fStart
1154                                        + fTextBuffer.length())) {
1155                            fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1156                            if (fCurrent.attemptCommit())
1157                                fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1158
1159                            fOverwriting= true;
1160                        }
1161
1162                        if (fCurrent.fStart < 0)
1163                            fCurrent.fStart= modelStart;
1164
1165                        fCurrent.fEnd= modelEnd;
1166                        fTextBuffer.append(insertedText);
1167                        fPreservedTextBuffer.append(replacedText);
1168                        fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
1169                        return;
1170                    }
1171                }
1172                // because of typing or pasting whereby selection is not empty
1173
fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1174                if (fCurrent.attemptCommit())
1175                    fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1176
1177                fCurrent.fStart= modelStart;
1178                fCurrent.fEnd= modelEnd;
1179                fTextBuffer.append(insertedText);
1180                fPreservedTextBuffer.append(replacedText);
1181            }
1182        }
1183        // in all cases, the redo modification stamp is updated on the open
1184
// text edit
1185
fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
1186    }
1187
1188    /**
1189     * Initialize the receiver.
1190     */

1191    private void initialize() {
1192        initializeUndoHistory();
1193
1194        // open up the current text edit
1195
fCurrent= new UndoableTextChange(this);
1196        fPreviousDelete= new UndoableTextChange(this);
1197        fTextBuffer= new StringBuffer JavaDoc();
1198        fPreservedTextBuffer= new StringBuffer JavaDoc();
1199
1200        addListeners();
1201    }
1202    
1203    /**
1204     * Reset processChange state.
1205     *
1206     * @since 3.2
1207     */

1208    private void resetProcessChangeState() {
1209        fInserting= false;
1210        fOverwriting= false;
1211        fPreviousDelete.reinitialize();
1212    }
1213
1214    /**
1215     * Shutdown the receiver.
1216     */

1217    private void shutdown() {
1218        removeListeners();
1219
1220        fCurrent= null;
1221        fPreviousDelete= null;
1222        fTextBuffer= null;
1223        fPreservedTextBuffer= null;
1224
1225        disposeUndoHistory();
1226    }
1227
1228    /**
1229     * Return whether or not any clients are connected to the receiver.
1230     *
1231     * @return <code>true</code> if the receiver is connected to
1232     * clients, <code>false</code> if it is not
1233     */

1234    boolean isConnected() {
1235        if (fConnected == null)
1236            return false;
1237        return !fConnected.isEmpty();
1238    }
1239
1240    /*
1241     * @see org.eclipse.jface.text.IDocumentUndoManager#transferUndoHistory(IDocumentUndoManager)
1242     */

1243    public void transferUndoHistory(IDocumentUndoManager manager) {
1244        IUndoContext oldUndoContext= manager.getUndoContext();
1245        // Get the history for the old undo context.
1246
IUndoableOperation [] operations= OperationHistoryFactory.getOperationHistory().getUndoHistory(oldUndoContext);
1247        for (int i= 0; i< operations.length; i++) {
1248            // First replace the undo context
1249
IUndoableOperation op= operations[i];
1250            if (op instanceof IContextReplacingOperation) {
1251                ((IContextReplacingOperation)op).replaceContext(oldUndoContext, getUndoContext());
1252            } else {
1253                op.addContext(getUndoContext());
1254                op.removeContext(oldUndoContext);
1255            }
1256            // Now update the manager that owns the text edit.
1257
if (op instanceof UndoableTextChange) {
1258                ((UndoableTextChange)op).fDocumentUndoManager= this;
1259            }
1260        }
1261        
1262        // Record the transfer itself as an undoable change.
1263
// If the transfer results from some open operation, recording this change will
1264
// cause our undo context to be added to the outer operation. If there is no
1265
// outer operation, there will be a local change to signify the transfer.
1266
// This also serves to synchronize the modification stamps with the documents.
1267
IUndoableOperation op= OperationHistoryFactory.getOperationHistory().getUndoOperation(getUndoContext());
1268        UndoableTextChange cmd= new UndoableTextChange(this);
1269        cmd.fStart= cmd.fEnd= 0;
1270        cmd.fText= cmd.fPreservedText= ""; //$NON-NLS-1$
1271
if (fDocument instanceof IDocumentExtension4) {
1272            cmd.fRedoModificationStamp= ((IDocumentExtension4)fDocument).getModificationStamp();
1273            if (op instanceof UndoableTextChange) {
1274                cmd.fUndoModificationStamp= ((UndoableTextChange)op).fRedoModificationStamp;
1275            }
1276        }
1277        addToOperationHistory(cmd);
1278    }
1279
1280}
1281
Popular Tags