PageRenderTime 218ms CodeModel.GetById 201ms app.highlight 13ms RepoModel.GetById 1ms app.codeStats 1ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/buffer/UndoManager.java

#
Java | 502 lines | 346 code | 65 blank | 91 comment | 68 complexity | 6038d71c340eee3c1bf9ade59f483a67 MD5 | raw file
  1/*
  2 * UndoManager.java - Buffer undo manager
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2001, 2005 Slava Pestov
  7 *
  8 * This program is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU General Public License
 10 * as published by the Free Software Foundation; either version 2
 11 * of the License, or any later version.
 12 *
 13 * This program is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16 * GNU General Public License for more details.
 17 *
 18 * You should have received a copy of the GNU General Public License
 19 * along with this program; if not, write to the Free Software
 20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 21 */
 22
 23package org.gjt.sp.jedit.buffer;
 24
 25//{{{ Imports
 26import org.gjt.sp.util.Log;
 27//}}}
 28
 29/**
 30 * A class internal to jEdit's document model. You should not use it
 31 * directly. To improve performance, none of the methods in this class
 32 * check for out of bounds access, nor are they thread-safe. The
 33 * <code>Buffer</code> class, through which these methods must be
 34 * called through, implements such protection.
 35 *
 36 * @author Slava Pestov
 37 * @version $Id: UndoManager.java 16728 2009-12-25 14:12:38Z shlomy $
 38 * @since jEdit 4.0pre1
 39 */
 40public class UndoManager
 41{
 42	//{{{ UndoManager constructor
 43	public UndoManager(JEditBuffer buffer)
 44	{
 45		this.buffer = buffer;
 46	} //}}}
 47
 48	//{{{ setLimit() method
 49	public void setLimit(int limit)
 50	{
 51		this.limit = limit;
 52	} //}}}
 53
 54	//{{{ clear() method
 55	public void clear()
 56	{
 57		undosFirst = undosLast = redosFirst = null;
 58		undoCount = 0;
 59	} //}}}
 60
 61	//{{{ canUndo() method
 62	public boolean canUndo()
 63	{
 64		return (undosLast != null);
 65	} //}}}
 66
 67	//{{{ undo() method
 68	public int undo()
 69	{
 70		if(insideCompoundEdit())
 71			throw new InternalError("Unbalanced begin/endCompoundEdit()");
 72
 73		if(undosLast == null)
 74			return -1;
 75		else
 76		{
 77			reviseUndoId();
 78			undoCount--;
 79
 80			int caret = undosLast.undo();
 81			redosFirst = undosLast;
 82			undosLast = undosLast.prev;
 83			if(undosLast == null)
 84				undosFirst = null;
 85			return caret;
 86		}
 87	} //}}}
 88
 89	//{{{ canRedo() method
 90	public boolean canRedo()
 91	{
 92		return (redosFirst != null);
 93	} //}}}
 94
 95	//{{{ redo() method
 96	public int redo()
 97	{
 98		if(insideCompoundEdit())
 99			throw new InternalError("Unbalanced begin/endCompoundEdit()");
100
101		if(redosFirst == null)
102			return -1;
103		else
104		{
105			reviseUndoId();
106			undoCount++;
107
108			int caret = redosFirst.redo();
109			undosLast = redosFirst;
110			if(undosFirst == null)
111				undosFirst = undosLast;
112			redosFirst = redosFirst.next;
113			return caret;
114		}
115	} //}}}
116
117	//{{{ beginCompoundEdit() method
118	public void beginCompoundEdit()
119	{
120		if(compoundEditCount == 0)
121		{
122			compoundEdit = new CompoundEdit();
123			reviseUndoId();
124		}
125
126		compoundEditCount++;
127	} //}}}
128
129	//{{{ endCompoundEdit() method
130	public void endCompoundEdit()
131	{
132		if(compoundEditCount == 0)
133		{
134			Log.log(Log.WARNING,this,new Exception("Unbalanced begin/endCompoundEdit()"));
135			return;
136		}
137		else if(compoundEditCount == 1)
138		{
139			if(compoundEdit.first == null)
140				/* nothing done between begin/end calls */;
141			else if(compoundEdit.first == compoundEdit.last)
142				addEdit(compoundEdit.first);
143			else
144				addEdit(compoundEdit);
145
146			compoundEdit = null;
147		}
148
149		compoundEditCount--;
150	} //}}}
151
152	//{{{ insideCompoundEdit() method
153	public boolean insideCompoundEdit()
154	{
155		return compoundEditCount != 0;
156	} //}}}
157
158	//{{{ getUndoId() method
159	public Object getUndoId()
160	{
161		return undoId;
162	} //}}}
163
164	//{{{ contentInserted() method
165	public void contentInserted(int offset, int length, String text, boolean clearDirty)
166	{
167		Edit last = getLastEdit();
168		Edit toMerge = getMergeEdit();
169
170		if(!clearDirty && toMerge instanceof Insert
171			&& redosFirst == null)
172		{
173			Insert ins = (Insert)toMerge;
174			if(ins.offset == offset)
175			{
176				ins.str = text.concat(ins.str);
177				ins.length += length;
178				return;
179			}
180			else if(ins.offset + ins.length == offset)
181			{
182				ins.str = ins.str.concat(text);
183				ins.length += length;
184				return;
185			}
186		}
187
188		Insert ins = new Insert(this,offset,length,text);
189
190		if(clearDirty)
191		{
192			redoClearDirty = last;
193			undoClearDirty = ins;
194		}
195
196		if(compoundEdit != null)
197			compoundEdit.add(ins);
198		else
199		{
200			reviseUndoId();
201			addEdit(ins);
202		}
203	} //}}}
204
205	//{{{ contentRemoved() method
206	public void contentRemoved(int offset, int length, String text, boolean clearDirty)
207	{
208		Edit last = getLastEdit();
209		Edit toMerge = getMergeEdit();
210
211		if(!clearDirty && toMerge instanceof Remove
212			&& redosFirst == null)
213		{
214			Remove rem = (Remove)toMerge;
215			if(rem.offset == offset)
216			{
217				rem.content.str = rem.content.str.concat(text);
218				rem.content.hashcode = rem.content.str.hashCode();
219				rem.length += length;
220				KillRing.getInstance().changed(rem.content);
221				return;
222			}
223			else if(offset + length == rem.offset)
224			{
225				rem.content.str = text.concat(rem.content.str);
226				rem.content.hashcode = rem.content.str.hashCode();
227				rem.length += length;
228				rem.offset = offset;
229				KillRing.getInstance().changed(rem.content);
230				return;
231			}
232		}
233
234		Remove rem = new Remove(this,offset,length,text);
235		if(clearDirty)
236		{
237			redoClearDirty = last;
238			undoClearDirty = rem;
239		}
240
241		if(compoundEdit != null)
242			compoundEdit.add(rem);
243		else
244		{
245			reviseUndoId();
246			addEdit(rem);
247		}
248
249		KillRing.getInstance().add(rem.content);
250	} //}}}
251
252	//{{{ resetClearDirty method
253	public void resetClearDirty()
254	{
255		redoClearDirty = getLastEdit();
256		if(redosFirst instanceof CompoundEdit)
257			undoClearDirty = ((CompoundEdit)redosFirst).first;
258		else
259			undoClearDirty = redosFirst;
260	} //}}}
261
262	//{{{ Private members
263
264	//{{{ Instance variables
265	private JEditBuffer buffer;
266
267	// queue of undos. last is most recent, first is oldest
268	private Edit undosFirst;
269	private Edit undosLast;
270
271	// queue of redos. first is most recent, last is oldest
272	private Edit redosFirst;
273
274	private int limit;
275	private int undoCount;
276	private int compoundEditCount;
277	private CompoundEdit compoundEdit;
278	private Edit undoClearDirty, redoClearDirty;
279	private Object undoId;
280	//}}}
281
282	//{{{ addEdit() method
283	private void addEdit(Edit edit)
284	{
285		if(undosFirst == null)
286			undosFirst = undosLast = edit;
287		else
288		{
289			undosLast.next = edit;
290			edit.prev = undosLast;
291			undosLast = edit;
292		}
293
294		redosFirst = null;
295
296		undoCount++;
297
298		while(undoCount > limit)
299		{
300			undoCount--;
301
302			if(undosFirst == undosLast)
303				undosFirst = undosLast = null;
304			else
305			{
306				undosFirst.next.prev = null;
307				undosFirst = undosFirst.next;
308			}
309		}
310	} //}}}
311
312	//{{{ getMergeEdit() method
313	private Edit getMergeEdit()
314	{
315		Edit last = getLastEdit();
316		return (compoundEdit != null ? compoundEdit.last : last);
317	} //}}}
318
319	//{{{ getLastEdit() method
320	private Edit getLastEdit()
321	{
322		if(undosLast instanceof CompoundEdit)
323			return ((CompoundEdit)undosLast).last;
324		else
325			return undosLast;
326	} //}}}
327
328	//{{{ reviseUndoId()
329	/*
330	 * Revises a unique undoId for a the undo operation that is being
331	 * created as a result of a buffer content change, or that is being
332	 * used for undo/redo. Content changes that belong to the same undo
333	 * operation will have the same undoId.
334	 * 
335	 * This method should be called whenever:
336	 * - a buffer content change causes a new undo operation to be created;
337	 *   i.e. whenever a content change is not included in the same undo
338	 *   operation as the previous.
339	 * - an undo/redo is performed.
340	 */
341	private void reviseUndoId()
342	{
343		undoId = new Object();
344	} //}}}
345
346	//}}}
347
348	//{{{ Inner classes
349
350	//{{{ Edit class
351	abstract static class Edit
352	{
353		Edit prev, next;
354
355		//{{{ undo() method
356		abstract int undo();
357		//}}}
358
359		//{{{ redo() method
360		abstract int redo();
361		//}}}
362	} //}}}
363
364	//{{{ Insert class
365	static class Insert extends Edit
366	{
367		//{{{ Insert constructor
368		Insert(UndoManager mgr, int offset, int length, String str)
369		{
370			this.mgr = mgr;
371			this.offset = offset;
372			this.length = length;
373			this.str = str;
374		} //}}}
375
376		//{{{ undo() method
377		int undo()
378		{
379			mgr.buffer.remove(offset,length);
380			if(mgr.undoClearDirty == this)
381				mgr.buffer.setDirty(false);
382			return offset;
383		} //}}}
384
385		//{{{ redo() method
386		int redo()
387		{
388			mgr.buffer.insert(offset,str);
389			if(mgr.redoClearDirty == this)
390				mgr.buffer.setDirty(false);
391			return offset + length;
392		} //}}}
393
394		UndoManager mgr;
395		int offset;
396		int length;
397		String str;
398	} //}}}
399
400	//{{{ RemovedContent clas
401	// This class is held in KillRing.
402	public static class RemovedContent
403	{
404		String str;
405		int hashcode;
406		boolean inKillRing;
407
408		public RemovedContent(String str)
409		{
410			this.str = str;
411			this.hashcode = str.hashCode();
412		}
413
414		public String toString()
415		{
416			return str;
417		}
418	}// }}}
419
420	//{{{ Remove class
421	static class Remove extends Edit
422	{
423		//{{{ Remove constructor
424		Remove(UndoManager mgr, int offset, int length, String str)
425		{
426			this.mgr = mgr;
427			this.offset = offset;
428			this.length = length;
429			this.content = new RemovedContent(str);
430		} //}}}
431
432		//{{{ undo() method
433		int undo()
434		{
435			mgr.buffer.insert(offset,content.str);
436			if(mgr.undoClearDirty == this)
437				mgr.buffer.setDirty(false);
438			return offset + length;
439		} //}}}
440
441		//{{{ redo() method
442		int redo()
443		{
444			mgr.buffer.remove(offset,length);
445			if(mgr.redoClearDirty == this)
446				mgr.buffer.setDirty(false);
447			return offset;
448		} //}}}
449
450		UndoManager mgr;
451		int offset;
452		int length;
453		final RemovedContent content;
454	} //}}}
455
456	//{{{ CompoundEdit class
457	static class CompoundEdit extends Edit
458	{
459		//{{{ undo() method
460		public int undo()
461		{
462			int retVal = -1;
463			Edit edit = last;
464			while(edit != null)
465			{
466				retVal = edit.undo();
467				edit = edit.prev;
468			}
469			return retVal;
470		} //}}}
471
472		//{{{ redo() method
473		public int redo()
474		{
475			int retVal = -1;
476			Edit edit = first;
477			while(edit != null)
478			{
479				retVal = edit.redo();
480				edit = edit.next;
481			}
482			return retVal;
483		} //}}}
484
485		//{{{ add() method
486		public void add(Edit edit)
487		{
488			if(first == null)
489				first = last = edit;
490			else
491			{
492				edit.prev = last;
493				last.next = edit;
494				last = edit;
495			}
496		} //}}}
497
498		Edit first, last;
499	} //}}}
500
501	//}}}
502}