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