Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
View | Details | Raw Unified | Return to bug 21476 | Differences between
and this patch

Collapse All | Expand All

(-)DefaultUndoManager.java (-57 / +667 lines)
Lines 12-19 Link Here
12
package org.eclipse.jface.text;
12
package org.eclipse.jface.text;
13
13
14
14
15
import java.io.ByteArrayInputStream;
16
import java.io.ByteArrayOutputStream;
17
import java.io.File;
18
import java.io.IOException;
19
import java.io.ObjectInputStream;
20
import java.io.ObjectOutputStream;
21
import java.io.RandomAccessFile;
22
import java.io.Serializable;
15
import java.util.ArrayList;
23
import java.util.ArrayList;
24
import java.util.LinkedHashMap;
16
import java.util.List;
25
import java.util.List;
26
import java.util.Map;
27
28
import org.eclipse.core.runtime.IPath;
29
import org.eclipse.core.runtime.Platform;
17
import org.eclipse.swt.SWT;
30
import org.eclipse.swt.SWT;
18
import org.eclipse.swt.custom.StyledText;
31
import org.eclipse.swt.custom.StyledText;
19
import org.eclipse.swt.events.KeyEvent;
32
import org.eclipse.swt.events.KeyEvent;
Lines 41-51 Link Here
41
 * @see KeyListener
54
 * @see KeyListener
42
 */
55
 */
43
public class DefaultUndoManager implements IUndoManager {
56
public class DefaultUndoManager implements IUndoManager {
57
	
58
	/**
59
	 * A file that holds undo commands on disk and allow random access to
60
	 * commands via their offset in the file.
61
	 */
62
	static class PersistenceFile {
63
		private RandomAccessFile fFile;
64
		
65
		PersistenceFile() throws IOException {
66
			
67
			IPath path= Platform.getLocation().append(".metadata").append(".undo"); //$NON-NLS-1$ //$NON-NLS-2$
68
			File f= path.toFile();
69
			
70
			// Scrap the last session's undo log.
71
			if (f.exists())
72
				f.delete();
73
			
74
			fFile= new RandomAccessFile(f, "rw"); //$NON-NLS-1$	
75
		}
76
		
77
		/**
78
		 * Put an object in the file and return its offset.
79
		 * @param s The object to store
80
		 * @return The offset of the object in the file.
81
		 * @throws IOException
82
		 */
83
		public synchronized Long put(Serializable s) throws IOException {
84
			byte[] bs= toByteArray(s);
85
			long index= fFile.length();
86
			fFile.seek(index);
87
			fFile.writeInt(bs.length);
88
			fFile.write(bs);
89
			return new Long(index);
90
		}
91
		
92
		/**
93
		 * Get the object at the givne offset.
94
		 * @param index The offset of the object to get.
95
		 * @return The object that was at the offset.
96
		 * @throws IOException
97
		 */
98
		public synchronized Serializable get(Long index) throws IOException  {
99
			fFile.seek(index.longValue());
100
			int l= fFile.readInt();
101
			byte[] bs= new byte[l];
102
			fFile.readFully(bs);
103
			return toObject(bs);			
104
		}
105
		
106
		/**
107
		 * Serialize an object into a byte array.
108
		 * @param s The object to serialize.
109
		 * @return The byte array that holds the object.
110
		 * @throws IOException
111
		 */
112
		byte[] toByteArray(Serializable s) throws IOException {
113
			ByteArrayOutputStream baos= new ByteArrayOutputStream();
114
			ObjectOutputStream oos= new ObjectOutputStream(baos);
115
			oos.writeObject(s);
116
			return baos.toByteArray();
117
		}
118
		
119
		/**
120
		 * Convert a serialized object in a byte array to an object.
121
		 * @param bs The byte array holdinghte serialized object.
122
		 * @return The object.
123
		 * @throws IOException
124
		 */
125
		Serializable toObject(byte[] bs) throws IOException {
126
			try {
127
				ByteArrayInputStream bais= new ByteArrayInputStream(bs);
128
				ObjectInputStream ois= new ObjectInputStream(bais);
129
				return (Serializable)ois.readObject();
130
			}
131
			catch (ClassNotFoundException cnfe) {
132
				// Can't happen.
133
				throw new RuntimeException("", cnfe); //$NON-NLS-1$
134
			}
135
		}
136
	}
137
	
138
	/**
139
	 * Caching layer that wraps PersistenceFile to avoid disk lookups 
140
	 * for recent changes.
141
	 */
142
	static class PersistenceCache {
143
		private PersistenceFile fFile;
144
		
145
		/**
146
		 * LRU cache of undo commands.
147
		 */
148
		private LinkedHashMap lru= new LinkedHashMap(25, .75F, true) {
149
	        /**
150
			 * Shut up eclipse's warnings.
151
			 */
152
			private static final long serialVersionUID = 6009335074727417445L;
153
154
			/**
155
			 * Limit the number of entries in the cache.
156
			 */
157
			public boolean removeEldestEntry(Map.Entry eldest) {
158
	            return size() > 25;
159
	        }
160
	    };
161
		
162
		PersistenceCache() {
163
			try {
164
				fFile= new PersistenceFile();
165
			}
166
			catch (IOException ioe) {
167
				ioe.printStackTrace();
168
				
169
				throw new RuntimeException("", ioe); //$NON-NLS-1$
170
			}
171
		}
172
		
173
		/**
174
		 * Put an object in the file and return its offset.
175
		 * @param s The object to store
176
		 * @return The offset of the object in the file.
177
		 * @throws IOException
178
		 */
179
		public synchronized Long put(Serializable s) throws IOException {
180
			Long l= fFile.put(s);
181
			lru.put(l, s);
182
			return l;
183
		}
184
		
185
		/**
186
		 * Get the object at the givne offset.
187
		 * @param index The offset of the object to get.
188
		 * @return The object that was at the offset.
189
		 * @throws IOException
190
		 */
191
		public synchronized Serializable get(Long index) throws IOException  {
192
			Serializable s= (Serializable) lru.get(index);
193
			if (s == null) {
194
				s= fFile.get(index);
195
				lru.put(index, s);
196
			}
197
			return s;
198
		}
199
		
200
		/**
201
		 * Clear the <b>cache</b>. Does <b>not</b> clear/empty the underlying
202
		 * file. Used to remove references that would prevent GC.
203
		 */
204
		public synchronized void clear() {
205
			lru.clear();
206
		}
207
	}
208
	
209
	/**
210
	 * Per document undo stack.
211
	 */
212
	class CommandStack {
213
		/**
214
		 * Undo stack. Contains offsets in the persistence file of the undo
215
		 * commands.
216
		 */
217
		private List fIndex = new ArrayList();
218
		
219
		/**
220
		 * Add an entry to the stack at the given position.
221
		 * @param a The position to add the entry to the stack.
222
		 * @param s The entry to add to the stack.
223
		 */
224
		private void add(int a, Serializable s) {
225
			try	{
226
				Long i = persistenceCache.put(s);
227
				fIndex.add(a, i);
228
			}
229
			catch (IOException ioe) {
230
				ioe.printStackTrace();
231
				
232
				// undo stack is corrupt, wipe it to avoid further damage.
233
				fIndex.clear();
234
			}
235
		}
236
		
237
		/**
238
		 * Add an entry to the top of the stack.
239
		 * @param s The entry to put on top of the stack.
240
		 */
241
		private void add(TextCommand s) {
242
			try	{
243
				Long i = persistenceCache.put(s);
244
				fIndex.add(i);
245
			}
246
			catch (IOException ioe) {
247
				ioe.printStackTrace();
248
				
249
				// undo stack is corrupt, wipe it to avoid further damage.
250
				fIndex.clear();
251
			}
252
		}
253
		
254
		/**
255
		 * Get the undo command at the requested postition in the stack.
256
		 * @param counter The position of the requested command.
257
		 * @return The command at that position.
258
		 */
259
		private TextCommand get(int counter) {
260
			try	{
261
				Long i = (Long)fIndex.get(counter);
262
				TextCommand tc= (TextCommand) persistenceCache.get(i);
263
				
264
				// Replace the transient field that may be lost during serialization.
265
				tc.setUndoManager(DefaultUndoManager.this);
266
				
267
				return tc;
268
			}
269
			catch (IOException ioe) {
270
				ioe.printStackTrace();
271
				
272
				throw new RuntimeException("", ioe); //$NON-NLS-1$
273
			}
274
		}
275
		
276
		/**
277
		 * Truncate the stack to the parameter size. Entries will be
278
		 * removed fro mthe otp of the stack.
279
		 * @param size The size of the stack after the truncation.
280
		 */
281
		private void truncate(int size) {
282
			while (fIndex.size() > size) {
283
				fIndex.remove(size);
284
			}
285
		}
286
		
287
		/**
288
		 * The stack's size.
289
		 * @return The size of the stack.
290
		 */
291
		private int size() {
292
			return fIndex.size();
293
		}
294
	}
295
	
296
	/**
297
	 * The range of chars in the document that was changed by an edit.
298
	 */
299
	static class Range implements Cloneable {
300
		int fStart;
301
		int fEnd;
302
		int fOverwritten;
303
		int fCursor;
304
		
305
		public Object clone() {
306
			try {
307
				return super.clone();
308
			} catch (CloneNotSupportedException clnse) {
309
				throw new RuntimeException(clnse);
310
			}
311
		}
312
	}
44
313
45
	/**
314
	/**
46
	 * Represents an undo-able edit command.
315
	 * Represents an undo-able edit command.
47
	 */
316
	 */
48
	class TextCommand {
317
	static class TextCommand implements Serializable {
318
		
319
        /**
320
		 * Silence Eclipse's warnings.
321
		 */
322
		private static final long serialVersionUID = 6009335074727417445L;
323
324
		/**
325
		 * Psuedo-inner class. Allow us to serialize the command without
326
		 * serializing the undo manager.
327
		 */
328
		transient DefaultUndoManager fParent;
49
		
329
		
50
		/** The start index of the replaced text */
330
		/** The start index of the replaced text */
51
		protected int fStart= -1;
331
		protected int fStart= -1;
Lines 55-60 Link Here
55
		protected String fText;
335
		protected String fText;
56
		/** The replaced text */
336
		/** The replaced text */
57
		protected String fPreservedText;
337
		protected String fPreservedText;
338
		/** Was the text typed rather that pasted */
339
		protected boolean fTyped= false;
340
		
341
		TextCommand(DefaultUndoManager parent) {
342
			this.fParent= parent;
343
		}
344
		
345
		/**
346
		 * Used when deserializing the command.
347
		 * @param um The undo-manager this command is part of.
348
		 */
349
		void setUndoManager(DefaultUndoManager um) {
350
			fParent= um;
351
		}
58
		
352
		
59
		/**
353
		/**
60
		 * Re-initializes this text command.
354
		 * Re-initializes this text command.
Lines 84-90 Link Here
84
		 */
378
		 */
85
		protected void undoTextChange() {
379
		protected void undoTextChange() {
86
			try {
380
			try {
87
				fTextViewer.getDocument().replace(fStart, fText.length(), fPreservedText);
381
				fParent.fTextViewer.getDocument().replace(fStart, fText.length(), fPreservedText);
88
			} catch (BadLocationException x) {
382
			} catch (BadLocationException x) {
89
			}
383
			}
90
		}
384
		}
Lines 95-101 Link Here
95
		 */
389
		 */
96
		protected void undo() {
390
		protected void undo() {
97
			undoTextChange();
391
			undoTextChange();
98
			selectAndReveal(fStart, fPreservedText == null ? 0 : fPreservedText.length());
392
			fParent.selectAndReveal(fStart, fPreservedText == null ? 0 : fPreservedText.length());
99
		}
393
		}
100
		
394
		
101
		/**
395
		/**
Lines 105-111 Link Here
105
		 */
399
		 */
106
		protected void redoTextChange() {
400
		protected void redoTextChange() {
107
			try {
401
			try {
108
				fTextViewer.getDocument().replace(fStart, fEnd - fStart, fText);
402
				fParent.fTextViewer.getDocument().replace(fStart, fEnd - fStart, fText);
109
			} catch (BadLocationException x) {
403
			} catch (BadLocationException x) {
110
			}
404
			}
111
		}
405
		}
Lines 116-122 Link Here
116
		 */
410
		 */
117
		protected void redo() {
411
		protected void redo() {
118
			redoTextChange();
412
			redoTextChange();
119
			selectAndReveal(fStart, fText == null ? 0 : fText.length());
413
			fParent.selectAndReveal(fStart, fText == null ? 0 : fText.length());
120
		}
414
		}
121
				
415
				
122
		/**
416
		/**
Lines 125-140 Link Here
125
		 */
419
		 */
126
		protected void updateCommandStack() {
420
		protected void updateCommandStack() {
127
			
421
			
128
			int length= fCommandStack.size();
422
			fParent.fCommandStack.truncate(fParent.fCommandCounter + 1);
129
			for (int i= fCommandCounter + 1; i < length; i++)
130
				fCommandStack.remove(fCommandCounter + 1);
131
				
423
				
132
			fCommandStack.add(this);
424
			fParent.fCommandStack.add(this);
133
			
425
			
134
			while (fCommandStack.size() > fUndoLevel)
426
			fParent.fCommandCounter++;
135
				fCommandStack.remove(0);
136
			
137
			fCommandCounter= fCommandStack.size() - 1;
138
		}
427
		}
139
		
428
		
140
		/**
429
		/**
Lines 144-150 Link Here
144
		 * @return a new, uncommitted text command or a compound text command
433
		 * @return a new, uncommitted text command or a compound text command
145
		 */
434
		 */
146
		protected TextCommand createCurrent() {
435
		protected TextCommand createCurrent() {
147
			return fFoldingIntoCompoundChange ? new CompoundTextCommand() : new TextCommand();		
436
			return fParent.fFoldingIntoCompoundChange ? new CompoundTextCommand(fParent) : new TextCommand(fParent);		
437
		}
438
		
439
		/**
440
		 * Replaces sequential commands at the top of the stack with a single
441
		 * command if they are on the same line and we have started a new line.
442
		 */
443
		protected void collateStack() {
444
			if (fParent.fCommandCounter < 1)
445
				return;
446
			
447
			// If this previous command is collateable with this one then we
448
			// haven't reached the end of a line and don't want to collate yet.
449
			TextCommand previous= fParent.fCommandStack.get(fParent.fCommandCounter);
450
			if (previous.isCollatable(this))
451
				return;
452
			
453
			// if the current command cannot be collated with any preceding ones
454
			// then collate all the preceding ones that are on the same line.
455
			
456
			CompoundTextCommand compoundTextCommand= new CompoundTextCommand(fParent);
457
			int counter= fParent.fCommandCounter;
458
			
459
			TextCommand lastTc= null;
460
			while (counter >= 0) {
461
				TextCommand top= fParent.fCommandStack.get(counter); 
462
				
463
				if (top.isCollatable(lastTc)) {
464
					counter--;
465
					top.addSelf(compoundTextCommand.fCommands);
466
					lastTc= top;
467
				} else {
468
					break;
469
				}
470
			}
471
			
472
			if (counter < fParent.fCommandCounter - 1) {
473
				// Note: all the entries higher in the stack will be
474
				// overwritten/truncated by updateCommandStack().
475
				fParent.fCommandStack.add(counter + 1, compoundTextCommand);
476
				fParent.fCommandCounter= counter + 1;
477
				
478
				// Keep on collating until there is nothing left to do.
479
				collateStack();
480
			}			
148
		}
481
		}
149
		
482
		
150
		/**
483
		/**
Lines 156-170 Link Here
156
				reinitialize();
489
				reinitialize();
157
			} else {
490
			} else {
158
								
491
								
159
				fText= fTextBuffer.toString();
492
				fText= fParent.fTextBuffer.toString();
160
				fTextBuffer.setLength(0);
493
				fParent.fTextBuffer.setLength(0);
161
				fPreservedText= fPreservedTextBuffer.toString();
494
				fPreservedText= fParent.fPreservedTextBuffer.toString();
162
				fPreservedTextBuffer.setLength(0);
495
				fParent.fPreservedTextBuffer.setLength(0);
163
				
496
				
497
				if (fParent.fAutoCollate) {
498
					collateStack();
499
				}
164
				updateCommandStack();
500
				updateCommandStack();
165
			}
501
			}
166
			
502
			
167
			fCurrent= createCurrent();
503
			fParent.fCurrent= createCurrent();
504
		}
505
		
506
		/**
507
		 * Is this a chunk that can be collated into another to form one line.
508
		 * 
509
		 * @param next a TextCommand to test for collatability with this one.
510
		 * @return true if this TextCommand is collateable with <code>next</code>
511
		 */
512
		protected boolean isCollatable(TextCommand next) {
513
			
514
			if (next != null) {
515
				Range range= new Range();
516
				range.fStart= fStart;
517
				range.fEnd= fStart + fText.length();
518
				if (!next.isCollatable())
519
					return false;
520
				if (!next.isOverlapping(range, false))
521
					return false;
522
			}
523
			
524
			return isCollatable();
525
		}
526
		
527
		/**
528
		 * Is it possible that this command can be collated with others.
529
		 * @return true if it can be collated.
530
		 */
531
		protected boolean isCollatable() {
532
			
533
			// A deletion of text, do not allow this to be
534
			// confused with eating code-complete or lost to over-zelaous
535
			// collation.
536
			if (fPreservedText.length() > 0 && fText.length() == 0) {
537
				return false;
538
			}
539
			
540
			String[] delimiters= fParent.fTextViewer.getDocument().getLegalLineDelimiters();
541
			int[] idxs= TextUtilities.indexOf(delimiters, fText, 0);
542
			if (idxs[0] != -1 || idxs[1] != -1) {
543
				return false;
544
			}
545
			
546
			delimiters= fParent.fTextViewer.getDocument().getLegalLineDelimiters();
547
			idxs= TextUtilities.indexOf(delimiters, fPreservedText, 0);
548
			if (idxs[0] != -1 || idxs[1] != -1) {
549
				return false;
550
			}
551
			
552
			return true;
553
		}
554
		
555
		/**
556
		 * Does the range of chars this command overlap with the range passed 
557
		 * in.
558
		 * 
559
		 * @param range The range to test for overlap with this one.
560
		 * @param start The edit may have changed the positions of chars 
561
		 * after this undo. If true compare the overlap using the original 
562
		 * document (before the edit which created the undo) as a reference. 
563
		 * If false use the document after the edit.   
564
		 * @return Whether the parameter range overlaps this one.
565
		 */
566
		protected boolean isOverlapping(Range range, boolean start) {
567
		
568
			int localEnd;
569
			if (start) {
570
				localEnd= fStart + fPreservedText.length();
571
			}
572
			else {
573
				localEnd= fStart + fText.length();
574
			}
575
			return ((range.fStart <= fStart && fStart <=  range.fEnd) ||
576
					(range.fStart <= localEnd && localEnd <= range.fEnd));
577
		}
578
		
579
		/**
580
		 * Take a range and 'expand' the range to include the range covered by
581
		 * this undo command. In other words: return a range that is the 
582
		 * combination of the parameter range and this undo command's range.
583
		 * 
584
		 * @param range The range to combine with this undo's range.
585
		 * @return A range which includes both the parameter range and this undo's
586
		 * range.
587
		 */
588
		protected Range expandRange(Range range) {
589
			if (range == null) {
590
				range= new Range();
591
				range.fStart= fStart;
592
				range.fEnd= fStart + fText.length();
593
				range.fCursor= fStart;
594
				range.fOverwritten= fPreservedText.length();
595
				
596
				return range;
597
			}
598
			
599
			if (fStart < range.fStart) {
600
				range.fStart= fStart;
601
			}
602
			
603
			if (fStart + fText.length() > range.fCursor) {
604
				int overlap= (fStart + fText.length() - range.fCursor);
605
				
606
				if (overlap > range.fOverwritten) {
607
					range.fEnd= range.fEnd + overlap - range.fOverwritten;
608
					range.fOverwritten= 0;
609
				} else {
610
					range.fOverwritten -= overlap;
611
				}
612
			}
613
			
614
			range.fOverwritten += fPreservedText.length();
615
			
616
			range.fCursor= fStart;
617
			
618
			return range;
619
		}
620
		
621
		/**
622
		 * Add this command to the parameter list.
623
		 * @param commands The list to add this command to.
624
		 */
625
		protected void addSelf(List commands) {
626
			commands.add(0, this);
168
		}
627
		}
169
	}
628
	}
170
	
629
	
Lines 172-182 Link Here
172
	 * Represents an undo-able edit command consisting of several
631
	 * Represents an undo-able edit command consisting of several
173
	 * individual edit commands.
632
	 * individual edit commands.
174
	 */
633
	 */
175
	class CompoundTextCommand extends TextCommand {
634
	static class CompoundTextCommand extends TextCommand {
635
		
636
		/**
637
		 * Silence Eclipse warning.
638
		 */
639
		private static final long serialVersionUID = 6009335074727417445L;
176
		
640
		
177
		/** The list of individual commands */
641
		/** The list of individual commands */
178
		private List fCommands= new ArrayList();
642
		private List fCommands= new ArrayList();
179
		
643
		
644
		CompoundTextCommand(DefaultUndoManager parent) {
645
			super(parent);
646
		}
647
		
648
		/* (non-Javadoc)
649
		 * @see org.eclipse.jface.text.DefaultUndoManager.TextCommand#setUndoManager(org.eclipse.jface.text.DefaultUndoManager)
650
		 */
651
		void setUndoManager(DefaultUndoManager um) {
652
			super.setUndoManager(um);
653
			for (int i= 0; i < fCommands.size(); i++) {
654
				((TextCommand)fCommands.get(i)).setUndoManager(um);
655
			}
656
		}
657
		
180
		/**
658
		/**
181
		 * Adds a new individual command to this compound command.
659
		 * Adds a new individual command to this compound command.
182
		 *
660
		 *
Lines 191-198 Link Here
191
		 */
669
		 */
192
		protected void undo() {
670
		protected void undo() {
193
			ITextViewerExtension extension= null;
671
			ITextViewerExtension extension= null;
194
			if (fTextViewer instanceof ITextViewerExtension)
672
			if (fParent.fTextViewer instanceof ITextViewerExtension)
195
				extension= (ITextViewerExtension) fTextViewer;
673
				extension= (ITextViewerExtension) fParent.fTextViewer;
196
				
674
				
197
			if (extension != null)
675
			if (extension != null)
198
				extension.setRedraw(false);
676
				extension.setRedraw(false);
Lines 225-232 Link Here
225
		protected void redo() {
703
		protected void redo() {
226
			
704
			
227
			ITextViewerExtension extension= null;
705
			ITextViewerExtension extension= null;
228
			if (fTextViewer instanceof ITextViewerExtension)
706
			if (fParent.fTextViewer instanceof ITextViewerExtension)
229
				extension= (ITextViewerExtension) fTextViewer;
707
				extension= (ITextViewerExtension) fParent.fTextViewer;
230
				
708
				
231
			if (extension != null)
709
			if (extension != null)
232
				extension.setRedraw(false);
710
				extension.setRedraw(false);
Lines 257-263 Link Here
257
		 * @see TextCommand#updateCommandStack
735
		 * @see TextCommand#updateCommandStack
258
		 */
736
		 */
259
		protected void updateCommandStack() {
737
		protected void updateCommandStack() {
260
			TextCommand c= new TextCommand();
738
			TextCommand c= new TextCommand(fParent);
261
			c.fStart= fStart;
739
			c.fStart= fStart;
262
			c.fEnd= fEnd;
740
			c.fEnd= fEnd;
263
			c.fText= fText;
741
			c.fText= fText;
Lines 265-271 Link Here
265
			
743
			
266
			add(c);
744
			add(c);
267
			
745
			
268
			if (!fFoldingIntoCompoundChange)
746
			if (!fParent.fFoldingIntoCompoundChange)
269
				super.updateCommandStack();
747
				super.updateCommandStack();
270
		}
748
		}
271
		
749
		
Lines 274-281 Link Here
274
		 */
752
		 */
275
		protected TextCommand createCurrent() {
753
		protected TextCommand createCurrent() {
276
			
754
			
277
			if (!fFoldingIntoCompoundChange)
755
			if (!fParent.fFoldingIntoCompoundChange)
278
				return new TextCommand();
756
				return new TextCommand(fParent);
279
			
757
			
280
			reinitialize();
758
			reinitialize();
281
			return this;
759
			return this;
Lines 286-299 Link Here
286
		 */
764
		 */
287
		protected void commit() {
765
		protected void commit() {
288
			if (fStart < 0) {
766
			if (fStart < 0) {
289
				if (fCommands.size() > 0 && !fFoldingIntoCompoundChange) {
767
				if (fCommands.size() > 0 && !fParent.fFoldingIntoCompoundChange) {
290
					super.updateCommandStack();
768
					super.updateCommandStack();
291
					fCurrent= createCurrent();
769
					fParent.fCurrent= createCurrent();
292
					return;
770
					return;
293
				}
771
				}
294
			}
772
			}
295
			super.commit();
773
			super.commit();
296
		}
774
		}
775
		
776
		/* (non-Javadoc)
777
		 * @see org.eclipse.jface.text.DefaultUndoManager.TextCommand#isCollatable(org.eclipse.jface.text.DefaultUndoManager.TextCommand)
778
		 */
779
		protected boolean isCollatable(TextCommand next) {
780
			
781
			Range currentRange= null;
782
			
783
			if (next != null) {
784
				currentRange= next.expandRange(null);
785
			}
786
			
787
			// See if the param range is collatable with all the component
788
			// commands in order.
789
			for (int i= fCommands.size() - 1; i >= 0; i--) {
790
				TextCommand tc= (TextCommand) fCommands.get(i);
791
				
792
				if (!tc.isCollatable()) {
793
					return false;
794
				}
795
				if (currentRange != null && !tc.isOverlapping(currentRange, false)) {
796
					return false;
797
				}
798
				
799
				currentRange= tc.expandRange(currentRange);
800
			}
801
			
802
			return true;
803
		}
804
		
805
		/* (non-Javadoc)
806
		 * @see org.eclipse.jface.text.DefaultUndoManager.TextCommand#isOverlapping(org.eclipse.jface.text.DefaultUndoManager.Range, boolean)
807
		 */
808
		protected boolean isOverlapping(Range range, boolean start) {
809
			
810
			Range localRange= (Range)range.clone();
811
			
812
			for (int i= fCommands.size() - 1; i >= 0; i--) {
813
				TextCommand tc= (TextCommand) fCommands.get(i);
814
				
815
				if (!tc.isOverlapping(localRange, start)) {
816
					return false;
817
				}
818
				
819
				localRange= tc.expandRange(localRange);
820
			}
821
			
822
			return true;
823
		}
824
		
825
		/* (non-Javadoc)
826
		 * @see org.eclipse.jface.text.DefaultUndoManager.TextCommand#isCollatable()
827
		 */
828
		protected boolean isCollatable() {
829
			
830
			for (int i= fCommands.size() - 1; i >= 0; i--) {
831
				TextCommand tc= (TextCommand) fCommands.get(i);
832
				
833
				if (!tc.isCollatable()) {
834
					return false;
835
				}
836
			}
837
			
838
			return true;
839
		}
840
		
841
		/* (non-Javadoc)
842
		 * @see org.eclipse.jface.text.DefaultUndoManager.TextCommand#expandRange(org.eclipse.jface.text.DefaultUndoManager.Range)
843
		 */
844
		protected Range expandRange(Range range) {
845
			for (int i= fCommands.size() - 1; i >= 0; i--) {
846
				TextCommand tc= (TextCommand) fCommands.get(i);
847
				
848
				range= tc.expandRange(range);
849
			}
850
			
851
			return range;
852
		}
853
		
854
		/* (non-Javadoc)
855
		 * @see org.eclipse.jface.text.DefaultUndoManager.TextCommand#addSelf(java.util.List)
856
		 */
857
		protected void addSelf(List commands) {
858
			int length= fCommands.size();
859
			for (int i= length - 1; i >= 0; i--) {
860
				TextCommand tc= (TextCommand)fCommands.get(i);
861
				tc.addSelf(commands);
862
			}
863
		}
297
	}
864
	}
298
	
865
	
299
	/**
866
	/**
Lines 408-413 Link Here
408
		
975
		
409
	}
976
	}
410
	
977
	
978
	private static PersistenceCache persistenceCache = new PersistenceCache();
979
	
411
	
980
	
412
	/** Text buffer to collect text which is inserted into the viewer */
981
	/** Text buffer to collect text which is inserted into the viewer */
413
	private StringBuffer fTextBuffer= new StringBuffer();
982
	private StringBuffer fTextBuffer= new StringBuffer();
Lines 423-430 Link Here
423
	private TextInputListener fTextInputListener;
992
	private TextInputListener fTextInputListener;
424
	
993
	
425
	
994
	
426
	/** Indicates inserting state */
427
	private boolean fInserting= false;
428
	/** Indicates overwriting state */
995
	/** Indicates overwriting state */
429
	private boolean fOverwriting= false;
996
	private boolean fOverwriting= false;
430
	/** Indicates whether the current change belongs to a compound change */
997
	/** Indicates whether the current change belongs to a compound change */
Lines 433-449 Link Here
433
	/** The text viewer the undo manager is connected to */
1000
	/** The text viewer the undo manager is connected to */
434
	private ITextViewer fTextViewer;
1001
	private ITextViewer fTextViewer;
435
	
1002
	
436
	/** Supported undo level */
437
	private int fUndoLevel;
438
	/** The list of undo-able edit commands */
1003
	/** The list of undo-able edit commands */
439
	private List fCommandStack;
1004
	private CommandStack fCommandStack;
440
	/** The currently constructed edit command */
1005
	/** The currently constructed edit command */
441
	private TextCommand fCurrent;
1006
	private TextCommand fCurrent;
442
	/** The last delete edit command */
1007
	/** The last delete edit command */
443
	private TextCommand fPreviousDelete;
1008
	private TextCommand fPreviousDelete;
444
	/** Command counter into the edit command stack */
1009
	/** Command counter into the edit command stack */
445
	private int fCommandCounter= -1;
1010
	private int fCommandCounter= -1;
446
			
1011
	
1012
	/** Split words into chunks. */
1013
	private boolean fChunkWords;
1014
	/** Split lines into chunks. */
1015
	private boolean fChunkLines;
1016
	/** Join together chunks on line-breaks. */
1017
	private boolean fAutoCollate;			
447
			 
1018
			 
448
	/**
1019
	/**
449
	 * Creates a new undo manager who remembers the specified number of edit commands.
1020
	 * Creates a new undo manager who remembers the specified number of edit commands.
Lines 451-457 Link Here
451
	 * @param undoLevel the length of this manager's history
1022
	 * @param undoLevel the length of this manager's history
452
	 */
1023
	 */
453
	public DefaultUndoManager(int undoLevel) {
1024
	public DefaultUndoManager(int undoLevel) {
454
		setMaximalUndoLevel(undoLevel);
1025
		this(true, true, true);
1026
	}
1027
	
1028
	/**
1029
	 * Creates a new undo manager.
1030
	 * <p>
1031
	 * The only valid combinations of (chunkWords, chunkLines, autoCollate) are:
1032
	 * <ul>
1033
	 * <li>(true, true, false) - Fine-grain undo
1034
	 * <li>(true, true, true) - Smart undo (recommended)
1035
	 * <li>(false, false, false) - Course-grain undo (classic Eclipse undo)
1036
	 * </ul>
1037
	 * Using other combinations may not produce satisfying undos.
1038
	 *
1039
	 * @param chunkWords Split words into chunks.
1040
	 * @param chunkLines Split lines into chunks.
1041
	 * @param autoCollate Join together chunks on line-breaks.
1042
	 */
1043
	public DefaultUndoManager(boolean chunkWords, boolean chunkLines, boolean autoCollate) {
1044
		this.fChunkLines= chunkLines;
1045
		this.fChunkWords= chunkWords;
1046
		this.fAutoCollate= autoCollate;
455
	}
1047
	}
456
	
1048
	
457
	/**
1049
	/**
Lines 542-548 Link Here
542
	 */
1134
	 */
543
	private void commit() {
1135
	private void commit() {
544
		
1136
		
545
		fInserting= false;
546
		fOverwriting= false;
1137
		fOverwriting= false;
547
		fPreviousDelete.reinitialize();
1138
		fPreviousDelete.reinitialize();
548
		
1139
		
Lines 554-580 Link Here
554
	 */
1145
	 */
555
	private void internalRedo() {		
1146
	private void internalRedo() {		
556
		++fCommandCounter;
1147
		++fCommandCounter;
557
		TextCommand cmd= (TextCommand) fCommandStack.get(fCommandCounter);
1148
		TextCommand cmd= fCommandStack.get(fCommandCounter);
558
		
1149
		
559
		listenToTextChanges(false);
1150
		listenToTextChanges(false);
560
		cmd.redo();
1151
		cmd.redo();
561
		listenToTextChanges(true);
1152
		listenToTextChanges(true);
562
		
1153
		
563
		fCurrent= new TextCommand();
1154
		fCurrent= new TextCommand(this);
564
	}
1155
	}
565
	
1156
	
566
	/**
1157
	/**
567
	 * Does undo the last editing command.
1158
	 * Does undo the last editing command.
568
	 */
1159
	 */
569
	private void internalUndo() {		
1160
	private void internalUndo() {		
570
		TextCommand cmd= (TextCommand) fCommandStack.get(fCommandCounter);
1161
		TextCommand cmd= fCommandStack.get(fCommandCounter);
571
		-- fCommandCounter;
1162
		-- fCommandCounter;
572
		
1163
		
573
		listenToTextChanges(false);
1164
		listenToTextChanges(false);
574
		cmd.undo();
1165
		cmd.undo();
575
		listenToTextChanges(true);
1166
		listenToTextChanges(true);
576
		
1167
		
577
		fCurrent= new TextCommand();
1168
		fCurrent= new TextCommand(this);
578
	}
1169
	}
579
	
1170
	
580
	/**
1171
	/**
Lines 616-623 Link Here
616
			fPretendedState.cmdCounter= fCommandCounter;
1207
			fPretendedState.cmdCounter= fCommandCounter;
617
		} else {
1208
		} else {
618
			int sz= Math.max(fCommandCounter, 0) + 1;
1209
			int sz= Math.max(fCommandCounter, 0) + 1;
619
			if (sz > fUndoLevel)
620
				sz -= fUndoLevel;
621
			fPretendedState.stackSize= sz;
1210
			fPretendedState.stackSize= sz;
622
			fPretendedState.cmdCounter= sz - 1;
1211
			fPretendedState.cmdCounter= sz - 1;
623
		}
1212
		}
Lines 646-659 Link Here
646
			// text will be inserted
1235
			// text will be inserted
647
			if ((length == 1) || isWhitespaceText(insertedText)) {
1236
			if ((length == 1) || isWhitespaceText(insertedText)) {
648
				// by typing or model manipulation
1237
				// by typing or model manipulation
649
				if (!fInserting || (modelStart != fCurrent.fStart + fTextBuffer.length())) {
1238
				if (!fCurrent.fTyped || (modelStart != fCurrent.fStart + fTextBuffer.length()) || (isWhitespaceText(insertedText) && fChunkLines)) {
650
					commit();
1239
					commit();
651
					fInserting= true;
1240
					fCurrent.fTyped= true;
652
				} 
1241
				} else if (fTextBuffer.length() > 0 && Character.isLetterOrDigit(fTextBuffer.charAt(fTextBuffer.length() - 1))) {
1242
					// chunk at the end of words
1243
					if (!Character.isLetterOrDigit(insertedText.charAt(0)) && fChunkWords) {
1244
						commit();
1245
						fCurrent.fTyped= true;
1246
					}
1247
				}
1248
				
653
				if (fCurrent.fStart < 0)
1249
				if (fCurrent.fStart < 0)
654
					fCurrent.fStart= fCurrent.fEnd= modelStart;
1250
					fCurrent.fStart= fCurrent.fEnd= modelStart;
655
				if (length > 0)
1251
656
					fTextBuffer.append(insertedText);
1252
				fTextBuffer.append(insertedText);
1253
				if (isWhitespaceText(insertedText) && fChunkLines) {
1254
					commit();
1255
				}
657
			} else if (length > 0) {
1256
			} else if (length > 0) {
658
				// by pasting
1257
				// by pasting
659
				commit();
1258
				commit();
Lines 671-676 Link Here
671
					
1270
					
672
					// whereby selection is empty
1271
					// whereby selection is empty
673
					
1272
					
1273
					// Chunk lines
1274
					if (isWhitespaceText(replacedText) && fChunkLines) {
1275
						commit();
1276
					}
1277
					
674
					if (fPreviousDelete.fStart == modelStart && fPreviousDelete.fEnd == modelEnd) {
1278
					if (fPreviousDelete.fStart == modelStart && fPreviousDelete.fEnd == modelEnd) {
675
						// repeated DEL
1279
						// repeated DEL
676
							
1280
							
Lines 739-744 Link Here
739
				fCurrent.fEnd= modelEnd;
1343
				fCurrent.fEnd= modelEnd;
740
				fTextBuffer.append(insertedText);
1344
				fTextBuffer.append(insertedText);
741
				fPreservedTextBuffer.append(replacedText);
1345
				fPreservedTextBuffer.append(replacedText);
1346
				
1347
				if (insertedText.length() == 1) {
1348
					fCurrent.fTyped= true;
1349
				}
742
			}
1350
			}
743
		}		
1351
		}		
744
	}
1352
	}
Lines 747-753 Link Here
747
	 * @see org.eclipse.jface.text.IUndoManager#setMaximalUndoLevel(int)
1355
	 * @see org.eclipse.jface.text.IUndoManager#setMaximalUndoLevel(int)
748
	 */
1356
	 */
749
	public void setMaximalUndoLevel(int undoLevel) {
1357
	public void setMaximalUndoLevel(int undoLevel) {
750
		fUndoLevel= undoLevel;
1358
		// Ignored
751
	}
1359
	}
752
1360
753
	/*
1361
	/*
Lines 756-764 Link Here
756
	public void connect(ITextViewer textViewer) {
1364
	public void connect(ITextViewer textViewer) {
757
		if (fTextViewer == null && textViewer != null) {
1365
		if (fTextViewer == null && textViewer != null) {
758
			fTextViewer= textViewer;	
1366
			fTextViewer= textViewer;	
759
			fCommandStack= new ArrayList();
1367
			fCommandStack= new CommandStack();
760
			fCurrent= new TextCommand();
1368
			fCurrent= new TextCommand(this);
761
			fPreviousDelete= new TextCommand();
1369
			fPreviousDelete= new TextCommand(this);
762
			addListeners();
1370
			addListeners();
763
		}
1371
		}
764
	}
1372
	}
Lines 773-780 Link Here
773
			
1381
			
774
			fCurrent= null;
1382
			fCurrent= null;
775
			if (fCommandStack != null) {
1383
			if (fCommandStack != null) {
776
				fCommandStack.clear();
1384
				fCommandStack.truncate(0);
777
				fCommandStack= null;
1385
				fCommandStack= null;
1386
				
1387
				// Allow the GC to collect us.
1388
				persistenceCache.clear();
778
			}
1389
			}
779
			fTextBuffer= null;
1390
			fTextBuffer= null;
780
			fPreservedTextBuffer= null;
1391
			fPreservedTextBuffer= null;
Lines 787-799 Link Here
787
	 */
1398
	 */
788
	public void reset() {
1399
	public void reset() {
789
		if (isConnected()) {
1400
		if (isConnected()) {
790
			if (fCommandStack != null)
1401
				if (fCommandStack != null)
791
				fCommandStack.clear();
1402
				fCommandStack.truncate(0);
792
			fCommandCounter= -1;
1403
			fCommandCounter= -1;
793
			if (fCurrent != null)
1404
			if (fCurrent != null)
794
				fCurrent.reinitialize();
1405
				fCurrent.reinitialize();
795
			fFoldingIntoCompoundChange= false;
1406
			fFoldingIntoCompoundChange= false;
796
			fInserting= false;
797
			fOverwriting= false;
1407
			fOverwriting= false;
798
			fTextBuffer.setLength(0);
1408
			fTextBuffer.setLength(0);
799
			fPreservedTextBuffer.setLength(0);
1409
			fPreservedTextBuffer.setLength(0);

Return to bug 21476