129
|
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 }
|