129
|
1 /*******************************************************************************
|
|
2 * Copyright (c) 2000, 2008 IBM Corporation and others.
|
|
3 * All rights reserved. This program and the accompanying materials
|
|
4 * are made available under the terms of the Eclipse Public License v1.0
|
|
5 * which accompanies this distribution, and is available at
|
|
6 * http://www.eclipse.org/legal/epl-v10.html
|
|
7 *
|
|
8 * Contributors:
|
|
9 * IBM Corporation - initial API and implementation
|
|
10 * Port to the D programming language:
|
|
11 * Frank Benoit <benoit@tionex.de>
|
|
12 *******************************************************************************/
|
|
13
|
131
|
14
|
|
15 import dwtx.jface.text.IDocumentPartitioningListener; // packageimport
|
|
16 import dwtx.jface.text.DefaultTextHover; // packageimport
|
|
17 import dwtx.jface.text.AbstractInformationControl; // packageimport
|
|
18 import dwtx.jface.text.TextUtilities; // packageimport
|
|
19 import dwtx.jface.text.IInformationControlCreatorExtension; // packageimport
|
|
20 import dwtx.jface.text.AbstractInformationControlManager; // packageimport
|
|
21 import dwtx.jface.text.ITextViewerExtension2; // packageimport
|
|
22 import dwtx.jface.text.IDocumentPartitioner; // packageimport
|
|
23 import dwtx.jface.text.DefaultIndentLineAutoEditStrategy; // packageimport
|
|
24 import dwtx.jface.text.ITextSelection; // packageimport
|
|
25 import dwtx.jface.text.Document; // packageimport
|
|
26 import dwtx.jface.text.FindReplaceDocumentAdapterContentProposalProvider; // packageimport
|
|
27 import dwtx.jface.text.ITextListener; // packageimport
|
|
28 import dwtx.jface.text.BadPartitioningException; // packageimport
|
|
29 import dwtx.jface.text.ITextViewerExtension5; // packageimport
|
|
30 import dwtx.jface.text.IDocumentPartitionerExtension3; // packageimport
|
|
31 import dwtx.jface.text.IUndoManager; // packageimport
|
|
32 import dwtx.jface.text.ITextHoverExtension2; // packageimport
|
|
33 import dwtx.jface.text.IRepairableDocument; // packageimport
|
|
34 import dwtx.jface.text.IRewriteTarget; // packageimport
|
|
35 import dwtx.jface.text.DefaultPositionUpdater; // packageimport
|
|
36 import dwtx.jface.text.RewriteSessionEditProcessor; // packageimport
|
|
37 import dwtx.jface.text.TextViewerHoverManager; // packageimport
|
|
38 import dwtx.jface.text.DocumentRewriteSession; // packageimport
|
|
39 import dwtx.jface.text.TextViewer; // packageimport
|
|
40 import dwtx.jface.text.ITextViewerExtension8; // packageimport
|
|
41 import dwtx.jface.text.RegExMessages; // packageimport
|
|
42 import dwtx.jface.text.IDelayedInputChangeProvider; // packageimport
|
|
43 import dwtx.jface.text.ITextOperationTargetExtension; // packageimport
|
|
44 import dwtx.jface.text.IWidgetTokenOwner; // packageimport
|
|
45 import dwtx.jface.text.IViewportListener; // packageimport
|
|
46 import dwtx.jface.text.GapTextStore; // packageimport
|
|
47 import dwtx.jface.text.MarkSelection; // packageimport
|
|
48 import dwtx.jface.text.IDocumentPartitioningListenerExtension; // packageimport
|
|
49 import dwtx.jface.text.IDocumentAdapterExtension; // packageimport
|
|
50 import dwtx.jface.text.IInformationControlExtension; // packageimport
|
|
51 import dwtx.jface.text.IDocumentPartitioningListenerExtension2; // packageimport
|
|
52 import dwtx.jface.text.DefaultDocumentAdapter; // packageimport
|
|
53 import dwtx.jface.text.ITextViewerExtension3; // packageimport
|
|
54 import dwtx.jface.text.IInformationControlCreator; // packageimport
|
|
55 import dwtx.jface.text.TypedRegion; // packageimport
|
|
56 import dwtx.jface.text.ISynchronizable; // packageimport
|
|
57 import dwtx.jface.text.IMarkRegionTarget; // packageimport
|
|
58 import dwtx.jface.text.TextViewerUndoManager; // packageimport
|
|
59 import dwtx.jface.text.IRegion; // packageimport
|
|
60 import dwtx.jface.text.IInformationControlExtension2; // packageimport
|
|
61 import dwtx.jface.text.IDocumentExtension4; // packageimport
|
|
62 import dwtx.jface.text.IDocumentExtension2; // packageimport
|
|
63 import dwtx.jface.text.IDocumentPartitionerExtension2; // packageimport
|
|
64 import dwtx.jface.text.Assert; // packageimport
|
|
65 import dwtx.jface.text.DefaultInformationControl; // packageimport
|
|
66 import dwtx.jface.text.IWidgetTokenOwnerExtension; // packageimport
|
|
67 import dwtx.jface.text.DocumentClone; // packageimport
|
|
68 import dwtx.jface.text.IFindReplaceTarget; // packageimport
|
|
69 import dwtx.jface.text.IAutoEditStrategy; // packageimport
|
|
70 import dwtx.jface.text.ILineTrackerExtension; // packageimport
|
|
71 import dwtx.jface.text.IUndoManagerExtension; // packageimport
|
|
72 import dwtx.jface.text.TextSelection; // packageimport
|
|
73 import dwtx.jface.text.DefaultAutoIndentStrategy; // packageimport
|
|
74 import dwtx.jface.text.IAutoIndentStrategy; // packageimport
|
|
75 import dwtx.jface.text.IPainter; // packageimport
|
|
76 import dwtx.jface.text.IInformationControl; // packageimport
|
|
77 import dwtx.jface.text.IInformationControlExtension3; // packageimport
|
|
78 import dwtx.jface.text.ITextViewerExtension6; // packageimport
|
|
79 import dwtx.jface.text.IInformationControlExtension4; // packageimport
|
|
80 import dwtx.jface.text.DefaultLineTracker; // packageimport
|
|
81 import dwtx.jface.text.IDocumentInformationMappingExtension; // packageimport
|
|
82 import dwtx.jface.text.IRepairableDocumentExtension; // packageimport
|
|
83 import dwtx.jface.text.ITextHover; // packageimport
|
|
84 import dwtx.jface.text.FindReplaceDocumentAdapter; // packageimport
|
|
85 import dwtx.jface.text.ILineTracker; // packageimport
|
|
86 import dwtx.jface.text.Line; // packageimport
|
|
87 import dwtx.jface.text.ITextViewerExtension; // packageimport
|
|
88 import dwtx.jface.text.IDocumentAdapter; // packageimport
|
|
89 import dwtx.jface.text.TextEvent; // packageimport
|
|
90 import dwtx.jface.text.BadLocationException; // packageimport
|
|
91 import dwtx.jface.text.AbstractDocument; // packageimport
|
|
92 import dwtx.jface.text.AbstractLineTracker; // packageimport
|
|
93 import dwtx.jface.text.TreeLineTracker; // packageimport
|
|
94 import dwtx.jface.text.ITextPresentationListener; // packageimport
|
|
95 import dwtx.jface.text.Region; // packageimport
|
|
96 import dwtx.jface.text.ITextViewer; // packageimport
|
|
97 import dwtx.jface.text.IDocumentInformationMapping; // packageimport
|
|
98 import dwtx.jface.text.MarginPainter; // packageimport
|
|
99 import dwtx.jface.text.IPaintPositionManager; // packageimport
|
|
100 import dwtx.jface.text.TextPresentation; // packageimport
|
|
101 import dwtx.jface.text.IFindReplaceTargetExtension; // packageimport
|
|
102 import dwtx.jface.text.ISlaveDocumentManagerExtension; // packageimport
|
|
103 import dwtx.jface.text.ISelectionValidator; // packageimport
|
|
104 import dwtx.jface.text.IDocumentExtension; // packageimport
|
|
105 import dwtx.jface.text.PropagatingFontFieldEditor; // packageimport
|
|
106 import dwtx.jface.text.ConfigurableLineTracker; // packageimport
|
|
107 import dwtx.jface.text.SlaveDocumentEvent; // packageimport
|
|
108 import dwtx.jface.text.IDocumentListener; // packageimport
|
|
109 import dwtx.jface.text.PaintManager; // packageimport
|
|
110 import dwtx.jface.text.IFindReplaceTargetExtension3; // packageimport
|
|
111 import dwtx.jface.text.ITextDoubleClickStrategy; // packageimport
|
|
112 import dwtx.jface.text.IDocumentExtension3; // packageimport
|
|
113 import dwtx.jface.text.Position; // packageimport
|
|
114 import dwtx.jface.text.TextMessages; // packageimport
|
|
115 import dwtx.jface.text.CopyOnWriteTextStore; // packageimport
|
|
116 import dwtx.jface.text.WhitespaceCharacterPainter; // packageimport
|
|
117 import dwtx.jface.text.IPositionUpdater; // packageimport
|
|
118 import dwtx.jface.text.DefaultTextDoubleClickStrategy; // packageimport
|
|
119 import dwtx.jface.text.ListLineTracker; // packageimport
|
|
120 import dwtx.jface.text.ITextInputListener; // packageimport
|
|
121 import dwtx.jface.text.BadPositionCategoryException; // packageimport
|
|
122 import dwtx.jface.text.IWidgetTokenKeeperExtension; // packageimport
|
|
123 import dwtx.jface.text.IInputChangedListener; // packageimport
|
|
124 import dwtx.jface.text.ITextOperationTarget; // packageimport
|
|
125 import dwtx.jface.text.IDocumentInformationMappingExtension2; // packageimport
|
|
126 import dwtx.jface.text.ITextViewerExtension7; // packageimport
|
|
127 import dwtx.jface.text.IInformationControlExtension5; // packageimport
|
|
128 import dwtx.jface.text.IDocumentRewriteSessionListener; // packageimport
|
|
129 import dwtx.jface.text.JFaceTextUtil; // packageimport
|
|
130 import dwtx.jface.text.AbstractReusableInformationControlCreator; // packageimport
|
|
131 import dwtx.jface.text.TabsToSpacesConverter; // packageimport
|
|
132 import dwtx.jface.text.CursorLinePainter; // packageimport
|
|
133 import dwtx.jface.text.ITextHoverExtension; // packageimport
|
|
134 import dwtx.jface.text.IEventConsumer; // packageimport
|
|
135 import dwtx.jface.text.IDocument; // packageimport
|
|
136 import dwtx.jface.text.IWidgetTokenKeeper; // packageimport
|
|
137 import dwtx.jface.text.DocumentCommand; // packageimport
|
|
138 import dwtx.jface.text.TypedPosition; // packageimport
|
|
139 import dwtx.jface.text.IEditingSupportRegistry; // packageimport
|
|
140 import dwtx.jface.text.IDocumentPartitionerExtension; // packageimport
|
|
141 import dwtx.jface.text.AbstractHoverInformationControlManager; // packageimport
|
|
142 import dwtx.jface.text.IEditingSupport; // packageimport
|
|
143 import dwtx.jface.text.IMarkSelection; // packageimport
|
|
144 import dwtx.jface.text.ISlaveDocumentManager; // packageimport
|
|
145 import dwtx.jface.text.DocumentEvent; // packageimport
|
|
146 import dwtx.jface.text.DocumentPartitioningChangedEvent; // packageimport
|
|
147 import dwtx.jface.text.ITextStore; // packageimport
|
|
148 import dwtx.jface.text.JFaceTextMessages; // packageimport
|
|
149 import dwtx.jface.text.DocumentRewriteSessionEvent; // packageimport
|
|
150 import dwtx.jface.text.SequentialRewriteTextStore; // packageimport
|
|
151 import dwtx.jface.text.DocumentRewriteSessionType; // packageimport
|
|
152 import dwtx.jface.text.TextAttribute; // packageimport
|
|
153 import dwtx.jface.text.ITextViewerExtension4; // packageimport
|
|
154 import dwtx.jface.text.ITypedRegion; // packageimport
|
|
155
|
129
|
156 module dwtx.jface.text.DefaultUndoManager;
|
|
157
|
|
158 import dwt.dwthelper.utils;
|
|
159
|
|
160
|
|
161 import java.util.ArrayList;
|
|
162 import java.util.List;
|
|
163
|
|
164 import dwt.DWT;
|
|
165 import dwt.custom.StyledText;
|
|
166 import dwt.events.KeyEvent;
|
|
167 import dwt.events.KeyListener;
|
|
168 import dwt.events.MouseEvent;
|
|
169 import dwt.events.MouseListener;
|
|
170 import dwt.widgets.Display;
|
|
171 import dwt.widgets.Shell;
|
|
172 import dwtx.core.commands.ExecutionException;
|
|
173 import dwtx.core.commands.operations.AbstractOperation;
|
|
174 import dwtx.core.commands.operations.IOperationHistory;
|
|
175 import dwtx.core.commands.operations.IOperationHistoryListener;
|
|
176 import dwtx.core.commands.operations.IUndoContext;
|
|
177 import dwtx.core.commands.operations.IUndoableOperation;
|
|
178 import dwtx.core.commands.operations.ObjectUndoContext;
|
|
179 import dwtx.core.commands.operations.OperationHistoryEvent;
|
|
180 import dwtx.core.commands.operations.OperationHistoryFactory;
|
|
181 import dwtx.core.runtime.IAdaptable;
|
|
182 import dwtx.core.runtime.IProgressMonitor;
|
|
183 import dwtx.core.runtime.IStatus;
|
|
184 import dwtx.core.runtime.Status;
|
|
185 import dwtx.jface.dialogs.MessageDialog;
|
|
186
|
|
187
|
|
188 /**
|
|
189 * Standard implementation of {@link dwtx.jface.text.IUndoManager}.
|
|
190 * <p>
|
|
191 * It registers with the connected text viewer as text input listener and
|
|
192 * document listener and logs all changes. It also monitors mouse and keyboard
|
|
193 * activities in order to partition the stream of text changes into undo-able
|
|
194 * edit commands.
|
|
195 * </p>
|
|
196 * <p>
|
|
197 * Since 3.1 this undo manager is a facade to the global operation history.
|
|
198 * </p>
|
|
199 * <p>
|
|
200 * The usage of {@link dwtx.core.runtime.IAdaptable} in the JFace
|
|
201 * layer has been approved by Platform UI, see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=87669#c9
|
|
202 * </p>
|
|
203 * <p>
|
|
204 * This class is not intended to be subclassed.
|
|
205 * </p>
|
|
206 *
|
|
207 * @see dwtx.jface.text.ITextViewer
|
|
208 * @see dwtx.jface.text.ITextInputListener
|
|
209 * @see dwtx.jface.text.IDocumentListener
|
|
210 * @see dwtx.core.commands.operations.IUndoableOperation
|
|
211 * @see dwtx.core.commands.operations.IOperationHistory
|
|
212 * @see MouseListener
|
|
213 * @see KeyListener
|
|
214 * @deprecated As of 3.2, replaced by {@link TextViewerUndoManager}
|
|
215 * @noextend This class is not intended to be subclassed by clients.
|
|
216 */
|
|
217 public class DefaultUndoManager : IUndoManager, IUndoManagerExtension {
|
|
218
|
|
219 /**
|
|
220 * Represents an undo-able edit command.
|
|
221 * <p>
|
|
222 * Since 3.1 this implements the interface for IUndoableOperation.
|
|
223 * </p>
|
|
224 */
|
|
225 class TextCommand : AbstractOperation {
|
|
226
|
|
227 /** The start index of the replaced text. */
|
|
228 protected int fStart= -1;
|
|
229 /** The end index of the replaced text. */
|
|
230 protected int fEnd= -1;
|
|
231 /** The newly inserted text. */
|
|
232 protected String fText;
|
|
233 /** The replaced text. */
|
|
234 protected String fPreservedText;
|
|
235
|
|
236 /** The undo modification stamp. */
|
|
237 protected long fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
|
|
238 /** The redo modification stamp. */
|
|
239 protected long fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
|
|
240
|
|
241 /**
|
|
242 * Creates a new text command.
|
|
243 *
|
|
244 * @param context the undo context for this command
|
|
245 * @since 3.1
|
|
246 */
|
|
247 TextCommand(IUndoContext context) {
|
|
248 super(JFaceTextMessages.getString("DefaultUndoManager.operationLabel")); //$NON-NLS-1$
|
|
249 addContext(context);
|
|
250 }
|
|
251
|
|
252 /**
|
|
253 * Re-initializes this text command.
|
|
254 */
|
|
255 protected void reinitialize() {
|
|
256 fStart= fEnd= -1;
|
|
257 fText= fPreservedText= null;
|
|
258 fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
|
|
259 fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
|
|
260 }
|
|
261
|
|
262 /**
|
|
263 * Sets the start and the end index of this command.
|
|
264 *
|
|
265 * @param start the start index
|
|
266 * @param end the end index
|
|
267 */
|
|
268 protected void set(int start, int end) {
|
|
269 fStart= start;
|
|
270 fEnd= end;
|
|
271 fText= null;
|
|
272 fPreservedText= null;
|
|
273 }
|
|
274
|
|
275 /*
|
|
276 * @see dwtx.core.commands.operations.IUndoableOperation#dispose()
|
|
277 * @since 3.1
|
|
278 */
|
|
279 public void dispose() {
|
|
280 reinitialize();
|
|
281 }
|
|
282
|
|
283 /**
|
|
284 * Undo the change described by this command.
|
|
285 *
|
|
286 * @since 2.0
|
|
287 */
|
|
288 protected void undoTextChange() {
|
|
289 try {
|
|
290 IDocument document= fTextViewer.getDocument();
|
|
291 if (document instanceof IDocumentExtension4)
|
|
292 ((IDocumentExtension4)document).replace(fStart, fText.length(), fPreservedText, fUndoModificationStamp);
|
|
293 else
|
|
294 document.replace(fStart, fText.length(), fPreservedText);
|
|
295 } catch (BadLocationException x) {
|
|
296 }
|
|
297 }
|
|
298
|
|
299 /*
|
|
300 * @see dwtx.core.commands.operations.IUndoableOperation#canUndo()
|
|
301 * @since 3.1
|
|
302 */
|
|
303 public bool canUndo() {
|
|
304
|
|
305 if (isConnected() && isValid()) {
|
|
306 IDocument doc= fTextViewer.getDocument();
|
|
307 if (doc instanceof IDocumentExtension4) {
|
|
308 long docStamp= ((IDocumentExtension4)doc).getModificationStamp();
|
|
309
|
|
310 // Normal case: an undo is valid if its redo will restore document
|
|
311 // to its current modification stamp
|
|
312 bool canUndo= docStamp is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP ||
|
|
313 docStamp is getRedoModificationStamp();
|
|
314
|
|
315 /* Special case to check if the answer is false.
|
|
316 * If the last document change was empty, then the document's
|
|
317 * modification stamp was incremented but nothing was committed.
|
|
318 * The operation being queried has an older stamp. In this case only,
|
|
319 * the comparison is different. A sequence of document changes that
|
|
320 * include an empty change is handled correctly when a valid commit
|
|
321 * follows the empty change, but when #canUndo() is queried just after
|
|
322 * an empty change, we must special case the check. The check is very
|
|
323 * specific to prevent false positives.
|
|
324 * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=98245
|
|
325 */
|
|
326 if (!canUndo &&
|
|
327 this is fHistory.getUndoOperation(fUndoContext) && // this is the latest operation
|
|
328 this !is fCurrent && // there is a more current operation not on the stack
|
|
329 !fCurrent.isValid() && // the current operation is not a valid document modification
|
|
330 fCurrent.fUndoModificationStamp !is // the invalid current operation has a document stamp
|
|
331 IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) {
|
|
332 canUndo= fCurrent.fRedoModificationStamp is docStamp;
|
|
333 }
|
|
334 /*
|
|
335 * When the composite is the current command, it may hold the timestamp
|
|
336 * of a no-op change. We check this here rather than in an override of
|
|
337 * canUndo() in CompoundTextCommand simply to keep all the special case checks
|
|
338 * in one place.
|
|
339 */
|
|
340 if (!canUndo &&
|
|
341 this is fHistory.getUndoOperation(fUndoContext) && // this is the latest operation
|
|
342 this instanceof CompoundTextCommand &&
|
|
343 this is fCurrent && // this is the current operation
|
|
344 this.fStart is -1 && // the current operation text is not valid
|
|
345 fCurrent.fRedoModificationStamp !is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) { // but it has a redo stamp
|
|
346 canUndo= fCurrent.fRedoModificationStamp is docStamp;
|
|
347 }
|
|
348
|
|
349 }
|
|
350 // if there is no timestamp to check, simply return true per the 3.0.1 behavior
|
|
351 return true;
|
|
352 }
|
|
353 return false;
|
|
354 }
|
|
355
|
|
356 /*
|
|
357 * @see dwtx.core.commands.operations.IUndoableOperation#canRedo()
|
|
358 * @since 3.1
|
|
359 */
|
|
360 public bool canRedo() {
|
|
361 if (isConnected() && isValid()) {
|
|
362 IDocument doc= fTextViewer.getDocument();
|
|
363 if (doc instanceof IDocumentExtension4) {
|
|
364 long docStamp= ((IDocumentExtension4)doc).getModificationStamp();
|
|
365 return docStamp is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP ||
|
|
366 docStamp is getUndoModificationStamp();
|
|
367 }
|
|
368 // if there is no timestamp to check, simply return true per the 3.0.1 behavior
|
|
369 return true;
|
|
370 }
|
|
371 return false;
|
|
372 }
|
|
373
|
|
374 /*
|
|
375 * @see dwtx.core.commands.operations.IUndoableOperation#canExecute()
|
|
376 * @since 3.1
|
|
377 */
|
|
378 public bool canExecute() {
|
|
379 return isConnected();
|
|
380 }
|
|
381
|
|
382 /*
|
|
383 * @see dwtx.core.commands.operations.IUndoableOperation#execute(dwtx.core.runtime.IProgressMonitor, dwtx.core.runtime.IAdaptable)
|
|
384 * @since 3.1
|
|
385 */
|
|
386 public IStatus execute(IProgressMonitor monitor, IAdaptable uiInfo) {
|
|
387 // Text commands execute as they are typed, so executing one has no effect.
|
|
388 return Status.OK_STATUS;
|
|
389 }
|
|
390
|
|
391 /*
|
|
392 * Undo the change described by this command. Also selects and
|
|
393 * reveals the change.
|
|
394 */
|
|
395
|
|
396 /**
|
|
397 * Undo the change described by this command. Also selects and
|
|
398 * reveals the change.
|
|
399 *
|
|
400 * @param monitor the progress monitor to use if necessary
|
|
401 * @param uiInfo an adaptable that can provide UI info if needed
|
|
402 * @return the status
|
|
403 */
|
|
404 public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) {
|
|
405 if (isValid()) {
|
|
406 undoTextChange();
|
|
407 selectAndReveal(fStart, fPreservedText is null ? 0 : fPreservedText.length());
|
|
408 resetProcessChangeSate();
|
|
409 return Status.OK_STATUS;
|
|
410 }
|
|
411 return IOperationHistory.OPERATION_INVALID_STATUS;
|
|
412 }
|
|
413
|
|
414 /**
|
|
415 * Re-applies the change described by this command.
|
|
416 *
|
|
417 * @since 2.0
|
|
418 */
|
|
419 protected void redoTextChange() {
|
|
420 try {
|
|
421 IDocument document= fTextViewer.getDocument();
|
|
422 if (document instanceof IDocumentExtension4)
|
|
423 ((IDocumentExtension4)document).replace(fStart, fEnd - fStart, fText, fRedoModificationStamp);
|
|
424 else
|
|
425 fTextViewer.getDocument().replace(fStart, fEnd - fStart, fText);
|
|
426 } catch (BadLocationException x) {
|
|
427 }
|
|
428 }
|
|
429
|
|
430 /**
|
|
431 * Re-applies the change described by this command that previously been
|
|
432 * rolled back. Also selects and reveals the change.
|
|
433 *
|
|
434 * @param monitor the progress monitor to use if necessary
|
|
435 * @param uiInfo an adaptable that can provide UI info if needed
|
|
436 * @return the status
|
|
437 */
|
|
438 public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) {
|
|
439 if (isValid()) {
|
|
440 redoTextChange();
|
|
441 resetProcessChangeSate();
|
|
442 selectAndReveal(fStart, fText is null ? 0 : fText.length());
|
|
443 return Status.OK_STATUS;
|
|
444 }
|
|
445 return IOperationHistory.OPERATION_INVALID_STATUS;
|
|
446 }
|
|
447
|
|
448 /**
|
|
449 * Update the command in response to a commit.
|
|
450 *
|
|
451 * @since 3.1
|
|
452 */
|
|
453
|
|
454 protected void updateCommand() {
|
|
455 fText= fTextBuffer.toString();
|
|
456 fTextBuffer.setLength(0);
|
|
457 fPreservedText= fPreservedTextBuffer.toString();
|
|
458 fPreservedTextBuffer.setLength(0);
|
|
459 }
|
|
460
|
|
461 /**
|
|
462 * Creates a new uncommitted text command depending on whether
|
|
463 * a compound change is currently being executed.
|
|
464 *
|
|
465 * @return a new, uncommitted text command or a compound text command
|
|
466 */
|
|
467 protected TextCommand createCurrent() {
|
|
468 return fFoldingIntoCompoundChange ? new CompoundTextCommand(fUndoContext) : new TextCommand(fUndoContext);
|
|
469 }
|
|
470
|
|
471 /**
|
|
472 * Commits the current change into this command.
|
|
473 */
|
|
474 protected void commit() {
|
|
475 if (fStart < 0) {
|
|
476 if (fFoldingIntoCompoundChange) {
|
|
477 fCurrent= createCurrent();
|
|
478 } else {
|
|
479 reinitialize();
|
|
480 }
|
|
481 } else {
|
|
482 updateCommand();
|
|
483 fCurrent= createCurrent();
|
|
484 }
|
|
485 resetProcessChangeSate();
|
|
486 }
|
|
487
|
|
488 /**
|
|
489 * Updates the text from the buffers without resetting
|
|
490 * the buffers or adding anything to the stack.
|
|
491 *
|
|
492 * @since 3.1
|
|
493 */
|
|
494 protected void pretendCommit() {
|
|
495 if (fStart > -1) {
|
|
496 fText= fTextBuffer.toString();
|
|
497 fPreservedText= fPreservedTextBuffer.toString();
|
|
498 }
|
|
499 }
|
|
500
|
|
501 /**
|
|
502 * Attempt a commit of this command and answer true if a new
|
|
503 * fCurrent was created as a result of the commit.
|
|
504 *
|
|
505 * @return true if the command was committed and created a
|
|
506 * new fCurrent, false if not.
|
|
507 * @since 3.1
|
|
508 */
|
|
509 protected bool attemptCommit() {
|
|
510 pretendCommit();
|
|
511 if (isValid()) {
|
|
512 DefaultUndoManager.this.commit();
|
|
513 return true;
|
|
514 }
|
|
515 return false;
|
|
516 }
|
|
517
|
|
518 /**
|
|
519 * Checks whether this text command is valid for undo or redo.
|
|
520 *
|
|
521 * @return <code>true</code> if the command is valid for undo or redo
|
|
522 * @since 3.1
|
|
523 */
|
|
524 protected bool isValid() {
|
|
525 return fStart > -1 &&
|
|
526 fEnd > -1 &&
|
|
527 fText !is null;
|
|
528 }
|
|
529
|
|
530 /*
|
|
531 * @see java.lang.Object#toString()
|
|
532 * @since 3.1
|
|
533 */
|
|
534 public String toString() {
|
|
535 String delimiter= ", "; //$NON-NLS-1$
|
|
536 StringBuffer text= new StringBuffer(super.toString());
|
|
537 text.append("\n"); //$NON-NLS-1$
|
|
538 text.append(this.getClass().getName());
|
|
539 text.append(" undo modification stamp: "); //$NON-NLS-1$
|
|
540 text.append(fUndoModificationStamp);
|
|
541 text.append(" redo modification stamp: "); //$NON-NLS-1$
|
|
542 text.append(fRedoModificationStamp);
|
|
543 text.append(" start: "); //$NON-NLS-1$
|
|
544 text.append(fStart);
|
|
545 text.append(delimiter);
|
|
546 text.append("end: "); //$NON-NLS-1$
|
|
547 text.append(fEnd);
|
|
548 text.append(delimiter);
|
|
549 text.append("text: '"); //$NON-NLS-1$
|
|
550 text.append(fText);
|
|
551 text.append('\'');
|
|
552 text.append(delimiter);
|
|
553 text.append("preservedText: '"); //$NON-NLS-1$
|
|
554 text.append(fPreservedText);
|
|
555 text.append('\'');
|
|
556 return text.toString();
|
|
557 }
|
|
558
|
|
559 /**
|
|
560 * Return the undo modification stamp
|
|
561 *
|
|
562 * @return the undo modification stamp for this command
|
|
563 * @since 3.1
|
|
564 */
|
|
565 protected long getUndoModificationStamp() {
|
|
566 return fUndoModificationStamp;
|
|
567 }
|
|
568
|
|
569 /**
|
|
570 * Return the redo modification stamp
|
|
571 *
|
|
572 * @return the redo modification stamp for this command
|
|
573 * @since 3.1
|
|
574 */
|
|
575 protected long getRedoModificationStamp() {
|
|
576 return fRedoModificationStamp;
|
|
577 }
|
|
578 }
|
|
579
|
|
580 /**
|
|
581 * Represents an undo-able edit command consisting of several
|
|
582 * individual edit commands.
|
|
583 */
|
|
584 class CompoundTextCommand : TextCommand {
|
|
585
|
|
586 /** The list of individual commands */
|
|
587 private List fCommands= new ArrayList();
|
|
588
|
|
589 /**
|
|
590 * Creates a new compound text command.
|
|
591 *
|
|
592 * @param context the undo context for this command
|
|
593 * @since 3.1
|
|
594 */
|
|
595 CompoundTextCommand(IUndoContext context) {
|
|
596 super(context);
|
|
597 }
|
|
598
|
|
599 /**
|
|
600 * Adds a new individual command to this compound command.
|
|
601 *
|
|
602 * @param command the command to be added
|
|
603 */
|
|
604 protected void add(TextCommand command) {
|
|
605 fCommands.add(command);
|
|
606 }
|
|
607
|
|
608 /*
|
|
609 * @see dwtx.jface.text.DefaultUndoManager.TextCommand#undo()
|
|
610 */
|
|
611 public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) {
|
|
612 resetProcessChangeSate();
|
|
613
|
|
614 int size= fCommands.size();
|
|
615 if (size > 0) {
|
|
616
|
|
617 TextCommand c;
|
|
618
|
|
619 for (int i= size -1; i > 0; --i) {
|
|
620 c= (TextCommand) fCommands.get(i);
|
|
621 c.undoTextChange();
|
|
622 }
|
|
623
|
|
624 c= (TextCommand) fCommands.get(0);
|
|
625 c.undo(monitor, uiInfo);
|
|
626 }
|
|
627
|
|
628 return Status.OK_STATUS;
|
|
629 }
|
|
630
|
|
631 /*
|
|
632 * @see dwtx.jface.text.DefaultUndoManager.TextCommand#redo()
|
|
633 */
|
|
634 public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) {
|
|
635 resetProcessChangeSate();
|
|
636
|
|
637 int size= fCommands.size();
|
|
638 if (size > 0) {
|
|
639
|
|
640 TextCommand c;
|
|
641
|
|
642 for (int i= 0; i < size -1; ++i) {
|
|
643 c= (TextCommand) fCommands.get(i);
|
|
644 c.redoTextChange();
|
|
645 }
|
|
646
|
|
647 c= (TextCommand) fCommands.get(size -1);
|
|
648 c.redo(monitor, uiInfo);
|
|
649 }
|
|
650 return Status.OK_STATUS;
|
|
651 }
|
|
652
|
|
653 /*
|
|
654 * @see TextCommand#updateCommand
|
|
655
|
|
656 */
|
|
657
|
|
658 protected void updateCommand() {
|
|
659 // first gather the data from the buffers
|
|
660 super.updateCommand();
|
|
661
|
|
662 // the result of the command update is stored as a child command
|
|
663 TextCommand c= new TextCommand(fUndoContext);
|
|
664 c.fStart= fStart;
|
|
665 c.fEnd= fEnd;
|
|
666 c.fText= fText;
|
|
667 c.fPreservedText= fPreservedText;
|
|
668 c.fUndoModificationStamp= fUndoModificationStamp;
|
|
669 c.fRedoModificationStamp= fRedoModificationStamp;
|
|
670 add(c);
|
|
671
|
|
672 // clear out all indexes now that the child is added
|
|
673 reinitialize();
|
|
674 }
|
|
675
|
|
676 /*
|
|
677 * @see TextCommand#createCurrent
|
|
678 */
|
|
679 protected TextCommand createCurrent() {
|
|
680
|
|
681 if (!fFoldingIntoCompoundChange)
|
|
682 return new TextCommand(fUndoContext);
|
|
683
|
|
684 reinitialize();
|
|
685 return this;
|
|
686 }
|
|
687
|
|
688 /*
|
|
689 * @see dwtx.jface.text.DefaultUndoManager.TextCommand#commit()
|
|
690 */
|
|
691 protected void commit() {
|
|
692 // if there is pending data, update the command
|
|
693 if (fStart > -1)
|
|
694 updateCommand();
|
|
695 fCurrent= createCurrent();
|
|
696 resetProcessChangeSate();
|
|
697 }
|
|
698
|
|
699 /**
|
|
700 * Checks whether the command is valid for undo or redo.
|
|
701 *
|
|
702 * @return true if the command is valid.
|
|
703 * @since 3.1
|
|
704 */
|
|
705 protected bool isValid() {
|
|
706 if (isConnected())
|
|
707 return (fStart > -1 || fCommands.size() > 0);
|
|
708 return false;
|
|
709 }
|
|
710
|
|
711 /**
|
|
712 * Returns the undo modification stamp.
|
|
713 *
|
|
714 * @return the undo modification stamp
|
|
715 * @since 3.1
|
|
716 */
|
|
717 protected long getUndoModificationStamp() {
|
|
718 if (fStart > -1)
|
|
719 return super.getUndoModificationStamp();
|
|
720 else if (fCommands.size() > 0)
|
|
721 return ((TextCommand)fCommands.get(0)).getUndoModificationStamp();
|
|
722
|
|
723 return fUndoModificationStamp;
|
|
724 }
|
|
725
|
|
726 /**
|
|
727 * Returns the redo modification stamp.
|
|
728 *
|
|
729 * @return the redo modification stamp
|
|
730 * @since 3.1
|
|
731 */
|
|
732 protected long getRedoModificationStamp() {
|
|
733 if (fStart > -1)
|
|
734 return super.getRedoModificationStamp();
|
|
735 else if (fCommands.size() > 0)
|
|
736 return ((TextCommand)fCommands.get(fCommands.size()-1)).getRedoModificationStamp();
|
|
737
|
|
738 return fRedoModificationStamp;
|
|
739 }
|
|
740 }
|
|
741
|
|
742 /**
|
|
743 * Internal listener to mouse and key events.
|
|
744 */
|
|
745 class KeyAndMouseListener : MouseListener, KeyListener {
|
|
746
|
|
747 /*
|
|
748 * @see MouseListener#mouseDoubleClick
|
|
749 */
|
|
750 public void mouseDoubleClick(MouseEvent e) {
|
|
751 }
|
|
752
|
|
753 /*
|
|
754 * If the right mouse button is pressed, the current editing command is closed
|
|
755 * @see MouseListener#mouseDown
|
|
756 */
|
|
757 public void mouseDown(MouseEvent e) {
|
|
758 if (e.button is 1)
|
|
759 commit();
|
|
760 }
|
|
761
|
|
762 /*
|
|
763 * @see MouseListener#mouseUp
|
|
764 */
|
|
765 public void mouseUp(MouseEvent e) {
|
|
766 }
|
|
767
|
|
768 /*
|
|
769 * @see KeyListener#keyPressed
|
|
770 */
|
|
771 public void keyReleased(KeyEvent e) {
|
|
772 }
|
|
773
|
|
774 /*
|
|
775 * On cursor keys, the current editing command is closed
|
|
776 * @see KeyListener#keyPressed
|
|
777 */
|
|
778 public void keyPressed(KeyEvent e) {
|
|
779 switch (e.keyCode) {
|
|
780 case DWT.ARROW_UP:
|
|
781 case DWT.ARROW_DOWN:
|
|
782 case DWT.ARROW_LEFT:
|
|
783 case DWT.ARROW_RIGHT:
|
|
784 commit();
|
|
785 break;
|
|
786 }
|
|
787 }
|
|
788 }
|
|
789
|
|
790 /**
|
|
791 * Internal listener to document changes.
|
|
792 */
|
|
793 class DocumentListener : IDocumentListener {
|
|
794
|
|
795 private String fReplacedText;
|
|
796
|
|
797 /*
|
|
798 * @see dwtx.jface.text.IDocumentListener#documentAboutToBeChanged(dwtx.jface.text.DocumentEvent)
|
|
799 */
|
|
800 public void documentAboutToBeChanged(DocumentEvent event) {
|
|
801 try {
|
|
802 fReplacedText= event.getDocument().get(event.getOffset(), event.getLength());
|
|
803 fPreservedUndoModificationStamp= event.getModificationStamp();
|
|
804 } catch (BadLocationException x) {
|
|
805 fReplacedText= null;
|
|
806 }
|
|
807 }
|
|
808
|
|
809 /*
|
|
810 * @see dwtx.jface.text.IDocumentListener#documentChanged(dwtx.jface.text.DocumentEvent)
|
|
811 */
|
|
812 public void documentChanged(DocumentEvent event) {
|
|
813 fPreservedRedoModificationStamp= event.getModificationStamp();
|
|
814
|
|
815 // record the current valid state for the top operation in case it remains the
|
|
816 // top operation but changes state.
|
|
817 IUndoableOperation op= fHistory.getUndoOperation(fUndoContext);
|
|
818 bool wasValid= false;
|
|
819 if (op !is null)
|
|
820 wasValid= op.canUndo();
|
|
821 // Process the change, providing the before and after timestamps
|
|
822 processChange(event.getOffset(), event.getOffset() + event.getLength(), event.getText(), fReplacedText, fPreservedUndoModificationStamp, fPreservedRedoModificationStamp);
|
|
823
|
|
824 // now update fCurrent with the latest buffers from the document change.
|
|
825 fCurrent.pretendCommit();
|
|
826
|
|
827 if (op is fCurrent) {
|
|
828 // if the document change did not cause a new fCurrent to be created, then we should
|
|
829 // notify the history that the current operation changed if its validity has changed.
|
|
830 if (wasValid !is fCurrent.isValid())
|
|
831 fHistory.operationChanged(op);
|
|
832 }
|
|
833 else {
|
|
834 // if the change created a new fCurrent that we did not yet add to the
|
|
835 // stack, do so if it's valid and we are not in the middle of a compound change.
|
|
836 if (fCurrent !is fLastAddedCommand && fCurrent.isValid()) {
|
|
837 addToCommandStack(fCurrent);
|
|
838 }
|
|
839 }
|
|
840 }
|
|
841 }
|
|
842
|
|
843 /**
|
|
844 * Internal text input listener.
|
|
845 */
|
|
846 class TextInputListener : ITextInputListener {
|
|
847
|
|
848 /*
|
|
849 * @see dwtx.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(dwtx.jface.text.IDocument, dwtx.jface.text.IDocument)
|
|
850 */
|
|
851 public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
|
|
852 if (oldInput !is null && fDocumentListener !is null) {
|
|
853 oldInput.removeDocumentListener(fDocumentListener);
|
|
854 commit();
|
|
855 }
|
|
856 }
|
|
857
|
|
858 /*
|
|
859 * @see dwtx.jface.text.ITextInputListener#inputDocumentChanged(dwtx.jface.text.IDocument, dwtx.jface.text.IDocument)
|
|
860 */
|
|
861 public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
|
|
862 if (newInput !is null) {
|
|
863 if (fDocumentListener is null)
|
|
864 fDocumentListener= new DocumentListener();
|
|
865 newInput.addDocumentListener(fDocumentListener);
|
|
866 }
|
|
867 }
|
|
868
|
|
869 }
|
|
870
|
|
871 /*
|
|
872 * @see IOperationHistoryListener
|
|
873 * @since 3.1
|
|
874 */
|
|
875 class HistoryListener : IOperationHistoryListener {
|
|
876 private IUndoableOperation fOperation;
|
|
877
|
|
878 public void historyNotification(final OperationHistoryEvent event) {
|
|
879 final int type= event.getEventType();
|
|
880 switch (type) {
|
|
881 case OperationHistoryEvent.ABOUT_TO_UNDO:
|
|
882 case OperationHistoryEvent.ABOUT_TO_REDO:
|
|
883 // if this is one of our operations
|
|
884 if (event.getOperation().hasContext(fUndoContext)) {
|
|
885 fTextViewer.getTextWidget().getDisplay().syncExec(new Runnable() {
|
|
886 public void run() {
|
|
887 // if we are undoing/redoing a command we generated, then ignore
|
|
888 // the document changes associated with this undo or redo.
|
|
889 if (event.getOperation() instanceof TextCommand) {
|
|
890 if (fTextViewer instanceof TextViewer)
|
|
891 ((TextViewer)fTextViewer).ignoreAutoEditStrategies(true);
|
|
892 listenToTextChanges(false);
|
|
893
|
|
894 // in the undo case only, make sure compounds are closed
|
|
895 if (type is OperationHistoryEvent.ABOUT_TO_UNDO) {
|
|
896 if (fFoldingIntoCompoundChange) {
|
|
897 endCompoundChange();
|
|
898 }
|
|
899 }
|
|
900 } else {
|
|
901 // the undo or redo has our context, but it is not one of
|
|
902 // our commands. We will listen to the changes, but will
|
|
903 // reset the state that tracks the undo/redo history.
|
|
904 commit();
|
|
905 fLastAddedCommand= null;
|
|
906 }
|
|
907 }
|
|
908 });
|
|
909 fOperation= event.getOperation();
|
|
910 }
|
|
911 break;
|
|
912 case OperationHistoryEvent.UNDONE:
|
|
913 case OperationHistoryEvent.REDONE:
|
|
914 case OperationHistoryEvent.OPERATION_NOT_OK:
|
|
915 if (event.getOperation() is fOperation) {
|
|
916 fTextViewer.getTextWidget().getDisplay().syncExec(new Runnable() {
|
|
917 public void run() {
|
|
918 listenToTextChanges(true);
|
|
919 fOperation= null;
|
|
920 if (fTextViewer instanceof TextViewer)
|
|
921 ((TextViewer)fTextViewer).ignoreAutoEditStrategies(false);
|
|
922 }
|
|
923 });
|
|
924 }
|
|
925 break;
|
|
926 }
|
|
927 }
|
|
928
|
|
929 }
|
|
930
|
|
931 /** Text buffer to collect text which is inserted into the viewer */
|
|
932 private StringBuffer fTextBuffer= new StringBuffer();
|
|
933 /** Text buffer to collect viewer content which has been replaced */
|
|
934 private StringBuffer fPreservedTextBuffer= new StringBuffer();
|
|
935 /** The document modification stamp for undo. */
|
|
936 protected long fPreservedUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
|
|
937 /** The document modification stamp for redo. */
|
|
938 protected long fPreservedRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
|
|
939 /** The internal key and mouse event listener */
|
|
940 private KeyAndMouseListener fKeyAndMouseListener;
|
|
941 /** The internal document listener */
|
|
942 private DocumentListener fDocumentListener;
|
|
943 /** The internal text input listener */
|
|
944 private TextInputListener fTextInputListener;
|
|
945
|
|
946
|
|
947 /** Indicates inserting state */
|
|
948 private bool fInserting= false;
|
|
949 /** Indicates overwriting state */
|
|
950 private bool fOverwriting= false;
|
|
951 /** Indicates whether the current change belongs to a compound change */
|
|
952 private bool fFoldingIntoCompoundChange= false;
|
|
953
|
|
954 /** The text viewer the undo manager is connected to */
|
|
955 private ITextViewer fTextViewer;
|
|
956
|
|
957 /** Supported undo level */
|
|
958 private int fUndoLevel;
|
|
959 /** The currently constructed edit command */
|
|
960 private TextCommand fCurrent;
|
|
961 /** The last delete edit command */
|
|
962 private TextCommand fPreviousDelete;
|
|
963
|
|
964 /**
|
|
965 * The undo context.
|
|
966 * @since 3.1
|
|
967 */
|
|
968 private IOperationHistory fHistory;
|
|
969 /**
|
|
970 * The operation history.
|
|
971 * @since 3.1
|
|
972 */
|
|
973 private IUndoContext fUndoContext;
|
|
974 /**
|
|
975 * The operation history listener used for managing undo and redo before
|
|
976 * and after the individual commands are performed.
|
|
977 * @since 3.1
|
|
978 */
|
|
979 private IOperationHistoryListener fHistoryListener= new HistoryListener();
|
|
980
|
|
981 /**
|
|
982 * The command last added to the operation history. This must be tracked
|
|
983 * internally instead of asking the history, since outside parties may be placing
|
|
984 * items on our undo/redo history.
|
|
985 */
|
|
986 private TextCommand fLastAddedCommand= null;
|
|
987
|
|
988 /**
|
|
989 * Creates a new undo manager who remembers the specified number of edit commands.
|
|
990 *
|
|
991 * @param undoLevel the length of this manager's history
|
|
992 */
|
|
993 public DefaultUndoManager(int undoLevel) {
|
|
994 fHistory= OperationHistoryFactory.getOperationHistory();
|
|
995 setMaximalUndoLevel(undoLevel);
|
|
996 }
|
|
997
|
|
998 /**
|
|
999 * Returns whether this undo manager is connected to a text viewer.
|
|
1000 *
|
|
1001 * @return <code>true</code> if connected, <code>false</code> otherwise
|
|
1002 * @since 3.1
|
|
1003 */
|
|
1004 private bool isConnected() {
|
|
1005 return fTextViewer !is null;
|
|
1006 }
|
|
1007
|
|
1008 /*
|
|
1009 * @see IUndoManager#beginCompoundChange
|
|
1010 */
|
|
1011 public void beginCompoundChange() {
|
|
1012 if (isConnected()) {
|
|
1013 fFoldingIntoCompoundChange= true;
|
|
1014 commit();
|
|
1015 }
|
|
1016 }
|
|
1017
|
|
1018
|
|
1019 /*
|
|
1020 * @see IUndoManager#endCompoundChange
|
|
1021 */
|
|
1022 public void endCompoundChange() {
|
|
1023 if (isConnected()) {
|
|
1024 fFoldingIntoCompoundChange= false;
|
|
1025 commit();
|
|
1026 }
|
|
1027 }
|
|
1028
|
|
1029 /**
|
|
1030 * Registers all necessary listeners with the text viewer.
|
|
1031 */
|
|
1032 private void addListeners() {
|
|
1033 StyledText text= fTextViewer.getTextWidget();
|
|
1034 if (text !is null) {
|
|
1035 fKeyAndMouseListener= new KeyAndMouseListener();
|
|
1036 text.addMouseListener(fKeyAndMouseListener);
|
|
1037 text.addKeyListener(fKeyAndMouseListener);
|
|
1038 fTextInputListener= new TextInputListener();
|
|
1039 fTextViewer.addTextInputListener(fTextInputListener);
|
|
1040 fHistory.addOperationHistoryListener(fHistoryListener);
|
|
1041 listenToTextChanges(true);
|
|
1042 }
|
|
1043 }
|
|
1044
|
|
1045 /**
|
|
1046 * Unregister all previously installed listeners from the text viewer.
|
|
1047 */
|
|
1048 private void removeListeners() {
|
|
1049 StyledText text= fTextViewer.getTextWidget();
|
|
1050 if (text !is null) {
|
|
1051 if (fKeyAndMouseListener !is null) {
|
|
1052 text.removeMouseListener(fKeyAndMouseListener);
|
|
1053 text.removeKeyListener(fKeyAndMouseListener);
|
|
1054 fKeyAndMouseListener= null;
|
|
1055 }
|
|
1056 if (fTextInputListener !is null) {
|
|
1057 fTextViewer.removeTextInputListener(fTextInputListener);
|
|
1058 fTextInputListener= null;
|
|
1059 }
|
|
1060 listenToTextChanges(false);
|
|
1061 fHistory.removeOperationHistoryListener(fHistoryListener);
|
|
1062 }
|
|
1063 }
|
|
1064
|
|
1065 /**
|
|
1066 * Adds the given command to the operation history if it is not part of
|
|
1067 * a compound change.
|
|
1068 *
|
|
1069 * @param command the command to be added
|
|
1070 * @since 3.1
|
|
1071 */
|
|
1072 private void addToCommandStack(TextCommand command){
|
|
1073 if (!fFoldingIntoCompoundChange || command instanceof CompoundTextCommand) {
|
|
1074 fHistory.add(command);
|
|
1075 fLastAddedCommand= command;
|
|
1076 }
|
|
1077 }
|
|
1078
|
|
1079 /**
|
|
1080 * Disposes the command stack.
|
|
1081 *
|
|
1082 * @since 3.1
|
|
1083 */
|
|
1084 private void disposeCommandStack() {
|
|
1085 fHistory.dispose(fUndoContext, true, true, true);
|
|
1086 }
|
|
1087
|
|
1088 /**
|
|
1089 * Initializes the command stack.
|
|
1090 *
|
|
1091 * @since 3.1
|
|
1092 */
|
|
1093 private void initializeCommandStack() {
|
|
1094 if (fHistory !is null && fUndoContext !is null)
|
|
1095 fHistory.dispose(fUndoContext, true, true, false);
|
|
1096
|
|
1097 }
|
|
1098
|
|
1099 /**
|
|
1100 * Switches the state of whether there is a text listener or not.
|
|
1101 *
|
|
1102 * @param listen the state which should be established
|
|
1103 */
|
|
1104 private void listenToTextChanges(bool listen) {
|
|
1105 if (listen) {
|
|
1106 if (fDocumentListener is null && fTextViewer.getDocument() !is null) {
|
|
1107 fDocumentListener= new DocumentListener();
|
|
1108 fTextViewer.getDocument().addDocumentListener(fDocumentListener);
|
|
1109 }
|
|
1110 } else if (!listen) {
|
|
1111 if (fDocumentListener !is null && fTextViewer.getDocument() !is null) {
|
|
1112 fTextViewer.getDocument().removeDocumentListener(fDocumentListener);
|
|
1113 fDocumentListener= null;
|
|
1114 }
|
|
1115 }
|
|
1116 }
|
|
1117
|
|
1118 /**
|
|
1119 * Closes the current editing command and opens a new one.
|
|
1120 */
|
|
1121 private void commit() {
|
|
1122 // if fCurrent has never been placed on the command stack, do so now.
|
|
1123 // this can happen when there are multiple programmatically commits in a single
|
|
1124 // document change.
|
|
1125 if (fLastAddedCommand !is fCurrent) {
|
|
1126 fCurrent.pretendCommit();
|
|
1127 if (fCurrent.isValid())
|
|
1128 addToCommandStack(fCurrent);
|
|
1129 }
|
|
1130 fCurrent.commit();
|
|
1131 }
|
|
1132
|
|
1133 /**
|
|
1134 * Reset processChange state.
|
|
1135 *
|
|
1136 * @since 3.2
|
|
1137 */
|
|
1138 private void resetProcessChangeSate() {
|
|
1139 fInserting= false;
|
|
1140 fOverwriting= false;
|
|
1141 fPreviousDelete.reinitialize();
|
|
1142 }
|
|
1143
|
|
1144 /**
|
|
1145 * Checks whether the given text starts with a line delimiter and
|
|
1146 * subsequently contains a white space only.
|
|
1147 *
|
|
1148 * @param text the text to check
|
|
1149 * @return <code>true</code> if the text is a line delimiter followed by whitespace, <code>false</code> otherwise
|
|
1150 */
|
|
1151 private bool isWhitespaceText(String text) {
|
|
1152
|
|
1153 if (text is null || text.length() is 0)
|
|
1154 return false;
|
|
1155
|
|
1156 String[] delimiters= fTextViewer.getDocument().getLegalLineDelimiters();
|
|
1157 int index= TextUtilities.startsWith(delimiters, text);
|
|
1158 if (index > -1) {
|
|
1159 char c;
|
|
1160 int length= text.length();
|
|
1161 for (int i= delimiters[index].length(); i < length; i++) {
|
|
1162 c= text.charAt(i);
|
|
1163 if (c !is ' ' && c !is '\t')
|
|
1164 return false;
|
|
1165 }
|
|
1166 return true;
|
|
1167 }
|
|
1168
|
|
1169 return false;
|
|
1170 }
|
|
1171
|
|
1172 private void processChange(int modelStart, int modelEnd, String insertedText, String replacedText, long beforeChangeModificationStamp, long afterChangeModificationStamp) {
|
|
1173
|
|
1174 if (insertedText is null)
|
|
1175 insertedText= ""; //$NON-NLS-1$
|
|
1176
|
|
1177 if (replacedText is null)
|
|
1178 replacedText= ""; //$NON-NLS-1$
|
|
1179
|
|
1180 int length= insertedText.length();
|
|
1181 int diff= modelEnd - modelStart;
|
|
1182
|
|
1183 if (fCurrent.fUndoModificationStamp is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP)
|
|
1184 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
|
|
1185
|
|
1186 // normalize
|
|
1187 if (diff < 0) {
|
|
1188 int tmp= modelEnd;
|
|
1189 modelEnd= modelStart;
|
|
1190 modelStart= tmp;
|
|
1191 }
|
|
1192
|
|
1193 if (modelStart is modelEnd) {
|
|
1194 // text will be inserted
|
|
1195 if ((length is 1) || isWhitespaceText(insertedText)) {
|
|
1196 // by typing or whitespace
|
|
1197 if (!fInserting || (modelStart !is fCurrent.fStart + fTextBuffer.length())) {
|
|
1198 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
|
|
1199 if (fCurrent.attemptCommit())
|
|
1200 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
|
|
1201
|
|
1202 fInserting= true;
|
|
1203 }
|
|
1204 if (fCurrent.fStart < 0)
|
|
1205 fCurrent.fStart= fCurrent.fEnd= modelStart;
|
|
1206 if (length > 0)
|
|
1207 fTextBuffer.append(insertedText);
|
|
1208 } else if (length >= 0) {
|
|
1209 // by pasting or model manipulation
|
|
1210 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
|
|
1211 if (fCurrent.attemptCommit())
|
|
1212 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
|
|
1213
|
|
1214 fCurrent.fStart= fCurrent.fEnd= modelStart;
|
|
1215 fTextBuffer.append(insertedText);
|
|
1216 fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
|
|
1217 if (fCurrent.attemptCommit())
|
|
1218 fCurrent.fUndoModificationStamp= afterChangeModificationStamp;
|
|
1219
|
|
1220 }
|
|
1221 } else {
|
|
1222 if (length is 0) {
|
|
1223 // text will be deleted by backspace or DEL key or empty clipboard
|
|
1224 length= replacedText.length();
|
|
1225 String[] delimiters= fTextViewer.getDocument().getLegalLineDelimiters();
|
|
1226
|
|
1227 if ((length is 1) || TextUtilities.equals(delimiters, replacedText) > -1) {
|
|
1228
|
|
1229 // whereby selection is empty
|
|
1230
|
|
1231 if (fPreviousDelete.fStart is modelStart && fPreviousDelete.fEnd is modelEnd) {
|
|
1232 // repeated DEL
|
|
1233
|
|
1234 // correct wrong settings of fCurrent
|
|
1235 if (fCurrent.fStart is modelEnd && fCurrent.fEnd is modelStart) {
|
|
1236 fCurrent.fStart= modelStart;
|
|
1237 fCurrent.fEnd= modelEnd;
|
|
1238 }
|
|
1239 // append to buffer && extend command range
|
|
1240 fPreservedTextBuffer.append(replacedText);
|
|
1241 ++fCurrent.fEnd;
|
|
1242
|
|
1243 } else if (fPreviousDelete.fStart is modelEnd) {
|
|
1244 // repeated backspace
|
|
1245
|
|
1246 // insert in buffer and extend command range
|
|
1247 fPreservedTextBuffer.insert(0, replacedText);
|
|
1248 fCurrent.fStart= modelStart;
|
|
1249
|
|
1250 } else {
|
|
1251 // either DEL or backspace for the first time
|
|
1252
|
|
1253 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
|
|
1254 if (fCurrent.attemptCommit())
|
|
1255 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
|
|
1256
|
|
1257 // as we can not decide whether it was DEL or backspace we initialize for backspace
|
|
1258 fPreservedTextBuffer.append(replacedText);
|
|
1259 fCurrent.fStart= modelStart;
|
|
1260 fCurrent.fEnd= modelEnd;
|
|
1261 }
|
|
1262
|
|
1263 fPreviousDelete.set(modelStart, modelEnd);
|
|
1264
|
|
1265 } else if (length > 0) {
|
|
1266 // whereby selection is not empty
|
|
1267 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
|
|
1268 if (fCurrent.attemptCommit())
|
|
1269 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
|
|
1270
|
|
1271 fCurrent.fStart= modelStart;
|
|
1272 fCurrent.fEnd= modelEnd;
|
|
1273 fPreservedTextBuffer.append(replacedText);
|
|
1274 }
|
|
1275 } else {
|
|
1276 // text will be replaced
|
|
1277
|
|
1278 if (length is 1) {
|
|
1279 length= replacedText.length();
|
|
1280 String[] delimiters= fTextViewer.getDocument().getLegalLineDelimiters();
|
|
1281
|
|
1282 if ((length is 1) || TextUtilities.equals(delimiters, replacedText) > -1) {
|
|
1283 // because of overwrite mode or model manipulation
|
|
1284 if (!fOverwriting || (modelStart !is fCurrent.fStart + fTextBuffer.length())) {
|
|
1285 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
|
|
1286 if (fCurrent.attemptCommit())
|
|
1287 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
|
|
1288
|
|
1289 fOverwriting= true;
|
|
1290 }
|
|
1291
|
|
1292 if (fCurrent.fStart < 0)
|
|
1293 fCurrent.fStart= modelStart;
|
|
1294
|
|
1295 fCurrent.fEnd= modelEnd;
|
|
1296 fTextBuffer.append(insertedText);
|
|
1297 fPreservedTextBuffer.append(replacedText);
|
|
1298 fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
|
|
1299 return;
|
|
1300 }
|
|
1301 }
|
|
1302 // because of typing or pasting whereby selection is not empty
|
|
1303 fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
|
|
1304 if (fCurrent.attemptCommit())
|
|
1305 fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
|
|
1306
|
|
1307 fCurrent.fStart= modelStart;
|
|
1308 fCurrent.fEnd= modelEnd;
|
|
1309 fTextBuffer.append(insertedText);
|
|
1310 fPreservedTextBuffer.append(replacedText);
|
|
1311 }
|
|
1312 }
|
|
1313 // in all cases, the redo modification stamp is updated on the open command
|
|
1314 fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
|
|
1315 }
|
|
1316
|
|
1317 /**
|
|
1318 * Shows the given exception in an error dialog.
|
|
1319 *
|
|
1320 * @param title the dialog title
|
|
1321 * @param ex the exception
|
|
1322 * @since 3.1
|
|
1323 */
|
|
1324 private void openErrorDialog(final String title, final Exception ex) {
|
|
1325 Shell shell= null;
|
|
1326 if (isConnected()) {
|
|
1327 StyledText st= fTextViewer.getTextWidget();
|
|
1328 if (st !is null && !st.isDisposed())
|
|
1329 shell= st.getShell();
|
|
1330 }
|
|
1331 if (Display.getCurrent() !is null)
|
|
1332 MessageDialog.openError(shell, title, ex.getLocalizedMessage());
|
|
1333 else {
|
|
1334 Display display;
|
|
1335 final Shell finalShell= shell;
|
|
1336 if (finalShell !is null)
|
|
1337 display= finalShell.getDisplay();
|
|
1338 else
|
|
1339 display= Display.getDefault();
|
|
1340 display.syncExec(new Runnable() {
|
|
1341 public void run() {
|
|
1342 MessageDialog.openError(finalShell, title, ex.getLocalizedMessage());
|
|
1343 }
|
|
1344 });
|
|
1345 }
|
|
1346 }
|
|
1347
|
|
1348 /*
|
|
1349 * @see dwtx.jface.text.IUndoManager#setMaximalUndoLevel(int)
|
|
1350 */
|
|
1351 public void setMaximalUndoLevel(int undoLevel) {
|
|
1352 fUndoLevel= Math.max(0, undoLevel);
|
|
1353 if (isConnected()) {
|
|
1354 fHistory.setLimit(fUndoContext, fUndoLevel);
|
|
1355 }
|
|
1356 }
|
|
1357
|
|
1358 /*
|
|
1359 * @see dwtx.jface.text.IUndoManager#connect(dwtx.jface.text.ITextViewer)
|
|
1360 */
|
|
1361 public void connect(ITextViewer textViewer) {
|
|
1362 if (!isConnected() && textViewer !is null) {
|
|
1363 fTextViewer= textViewer;
|
|
1364 if (fUndoContext is null)
|
|
1365 fUndoContext= new ObjectUndoContext(this);
|
|
1366
|
|
1367 fHistory.setLimit(fUndoContext, fUndoLevel);
|
|
1368
|
|
1369 initializeCommandStack();
|
|
1370
|
|
1371 // open up the current command
|
|
1372 fCurrent= new TextCommand(fUndoContext);
|
|
1373
|
|
1374 fPreviousDelete= new TextCommand(fUndoContext);
|
|
1375 addListeners();
|
|
1376 }
|
|
1377 }
|
|
1378
|
|
1379 /*
|
|
1380 * @see dwtx.jface.text.IUndoManager#disconnect()
|
|
1381 */
|
|
1382 public void disconnect() {
|
|
1383 if (isConnected()) {
|
|
1384
|
|
1385 removeListeners();
|
|
1386
|
|
1387 fCurrent= null;
|
|
1388 fTextViewer= null;
|
|
1389 disposeCommandStack();
|
|
1390 fTextBuffer= null;
|
|
1391 fPreservedTextBuffer= null;
|
|
1392 fUndoContext= null;
|
|
1393 }
|
|
1394 }
|
|
1395
|
|
1396 /*
|
|
1397 * @see dwtx.jface.text.IUndoManager#reset()
|
|
1398 */
|
|
1399 public void reset() {
|
|
1400 if (isConnected()) {
|
|
1401 initializeCommandStack();
|
|
1402 fCurrent= new TextCommand(fUndoContext);
|
|
1403 fFoldingIntoCompoundChange= false;
|
|
1404 fInserting= false;
|
|
1405 fOverwriting= false;
|
|
1406 fTextBuffer.setLength(0);
|
|
1407 fPreservedTextBuffer.setLength(0);
|
|
1408 fPreservedUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
|
|
1409 fPreservedRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
|
|
1410 }
|
|
1411 }
|
|
1412
|
|
1413 /*
|
|
1414 * @see dwtx.jface.text.IUndoManager#redoable()
|
|
1415 */
|
|
1416 public bool redoable() {
|
|
1417 return fHistory.canRedo(fUndoContext);
|
|
1418 }
|
|
1419
|
|
1420 /*
|
|
1421 * @see dwtx.jface.text.IUndoManager#undoable()
|
|
1422 */
|
|
1423 public bool undoable() {
|
|
1424 return fHistory.canUndo(fUndoContext);
|
|
1425 }
|
|
1426
|
|
1427 /*
|
|
1428 * @see dwtx.jface.text.IUndoManager#redo()
|
|
1429 */
|
|
1430 public void redo() {
|
|
1431 if (isConnected() && redoable()) {
|
|
1432 try {
|
|
1433 fHistory.redo(fUndoContext, null, null);
|
|
1434 } catch (ExecutionException ex) {
|
|
1435 openErrorDialog(JFaceTextMessages.getString("DefaultUndoManager.error.redoFailed.title"), ex); //$NON-NLS-1$
|
|
1436 }
|
|
1437 }
|
|
1438 }
|
|
1439
|
|
1440 /*
|
|
1441 * @see dwtx.jface.text.IUndoManager#undo()
|
|
1442 */
|
|
1443 public void undo() {
|
|
1444 if (isConnected() && undoable()) {
|
|
1445 try {
|
|
1446 fHistory.undo(fUndoContext, null, null);
|
|
1447 } catch (ExecutionException ex) {
|
|
1448 openErrorDialog(JFaceTextMessages.getString("DefaultUndoManager.error.undoFailed.title"), ex); //$NON-NLS-1$
|
|
1449 }
|
|
1450 }
|
|
1451 }
|
|
1452
|
|
1453 /**
|
|
1454 * Selects and reveals the specified range.
|
|
1455 *
|
|
1456 * @param offset the offset of the range
|
|
1457 * @param length the length of the range
|
|
1458 * @since 3.0
|
|
1459 */
|
|
1460 protected void selectAndReveal(int offset, int length) {
|
|
1461 if (fTextViewer instanceof ITextViewerExtension5) {
|
|
1462 ITextViewerExtension5 extension= (ITextViewerExtension5) fTextViewer;
|
|
1463 extension.exposeModelRange(new Region(offset, length));
|
|
1464 } else if (!fTextViewer.overlapsWithVisibleRegion(offset, length))
|
|
1465 fTextViewer.resetVisibleRegion();
|
|
1466
|
|
1467 fTextViewer.setSelectedRange(offset, length);
|
|
1468 fTextViewer.revealRange(offset, length);
|
|
1469 }
|
|
1470
|
|
1471 /*
|
|
1472 * @see dwtx.jface.text.IUndoManagerExtension#getUndoContext()
|
|
1473 * @since 3.1
|
|
1474 */
|
|
1475 public IUndoContext getUndoContext() {
|
|
1476 return fUndoContext;
|
|
1477 }
|
|
1478
|
|
1479 }
|