/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. package org.gjt.sp.jedit.buffer;
  23. //{{{ Imports
  24. import org.gjt.sp.util.Log;
  25. //}}}
  26. /**
  27. * A class internal to jEdit's document model. You should not use it
  28. * directly. To improve performance, none of the methods in this class
  29. * check for out of bounds access, nor are they thread-safe. The
  30. * <code>Buffer</code> class, through which these methods must be
  31. * called through, implements such protection.
  32. *
  33. * @author Slava Pestov
  34. * @version $Id: UndoManager.java 16728 2009-12-25 14:12:38Z shlomy $
  35. * @since jEdit 4.0pre1
  36. */
  37. public class UndoManager
  38. {
  39. //{{{ UndoManager constructor
  40. public UndoManager(JEditBuffer buffer)
  41. {
  42. this.buffer = buffer;
  43. } //}}}
  44. //{{{ setLimit() method
  45. public void setLimit(int limit)
  46. {
  47. this.limit = limit;
  48. } //}}}
  49. //{{{ clear() method
  50. public void clear()
  51. {
  52. undosFirst = undosLast = redosFirst = null;
  53. undoCount = 0;
  54. } //}}}
  55. //{{{ canUndo() method
  56. public boolean canUndo()
  57. {
  58. return (undosLast != null);
  59. } //}}}
  60. //{{{ undo() method
  61. public int undo()
  62. {
  63. if(insideCompoundEdit())
  64. throw new InternalError("Unbalanced begin/endCompoundEdit()");
  65. if(undosLast == null)
  66. return -1;
  67. else
  68. {
  69. reviseUndoId();
  70. undoCount--;
  71. int caret = undosLast.undo();
  72. redosFirst = undosLast;
  73. undosLast = undosLast.prev;
  74. if(undosLast == null)
  75. undosFirst = null;
  76. return caret;
  77. }
  78. } //}}}
  79. //{{{ canRedo() method
  80. public boolean canRedo()
  81. {
  82. return (redosFirst != null);
  83. } //}}}
  84. //{{{ redo() method
  85. public int redo()
  86. {
  87. if(insideCompoundEdit())
  88. throw new InternalError("Unbalanced begin/endCompoundEdit()");
  89. if(redosFirst == null)
  90. return -1;
  91. else
  92. {
  93. reviseUndoId();
  94. undoCount++;
  95. int caret = redosFirst.redo();
  96. undosLast = redosFirst;
  97. if(undosFirst == null)
  98. undosFirst = undosLast;
  99. redosFirst = redosFirst.next;
  100. return caret;
  101. }
  102. } //}}}
  103. //{{{ beginCompoundEdit() method
  104. public void beginCompoundEdit()
  105. {
  106. if(compoundEditCount == 0)
  107. {
  108. compoundEdit = new CompoundEdit();
  109. reviseUndoId();
  110. }
  111. compoundEditCount++;
  112. } //}}}
  113. //{{{ endCompoundEdit() method
  114. public void endCompoundEdit()
  115. {
  116. if(compoundEditCount == 0)
  117. {
  118. Log.log(Log.WARNING,this,new Exception("Unbalanced begin/endCompoundEdit()"));
  119. return;
  120. }
  121. else if(compoundEditCount == 1)
  122. {
  123. if(compoundEdit.first == null)
  124. /* nothing done between begin/end calls */;
  125. else if(compoundEdit.first == compoundEdit.last)
  126. addEdit(compoundEdit.first);
  127. else
  128. addEdit(compoundEdit);
  129. compoundEdit = null;
  130. }
  131. compoundEditCount--;
  132. } //}}}
  133. //{{{ insideCompoundEdit() method
  134. public boolean insideCompoundEdit()
  135. {
  136. return compoundEditCount != 0;
  137. } //}}}
  138. //{{{ getUndoId() method
  139. public Object getUndoId()
  140. {
  141. return undoId;
  142. } //}}}
  143. //{{{ contentInserted() method
  144. public void contentInserted(int offset, int length, String text, boolean clearDirty)
  145. {
  146. Edit last = getLastEdit();
  147. Edit toMerge = getMergeEdit();
  148. if(!clearDirty && toMerge instanceof Insert
  149. && redosFirst == null)
  150. {
  151. Insert ins = (Insert)toMerge;
  152. if(ins.offset == offset)
  153. {
  154. ins.str = text.concat(ins.str);
  155. ins.length += length;
  156. return;
  157. }
  158. else if(ins.offset + ins.length == offset)
  159. {
  160. ins.str = ins.str.concat(text);
  161. ins.length += length;
  162. return;
  163. }
  164. }
  165. Insert ins = new Insert(this,offset,length,text);
  166. if(clearDirty)
  167. {
  168. redoClearDirty = last;
  169. undoClearDirty = ins;
  170. }
  171. if(compoundEdit != null)
  172. compoundEdit.add(ins);
  173. else
  174. {
  175. reviseUndoId();
  176. addEdit(ins);
  177. }
  178. } //}}}
  179. //{{{ contentRemoved() method
  180. public void contentRemoved(int offset, int length, String text, boolean clearDirty)
  181. {
  182. Edit last = getLastEdit();
  183. Edit toMerge = getMergeEdit();
  184. if(!clearDirty && toMerge instanceof Remove
  185. && redosFirst == null)
  186. {
  187. Remove rem = (Remove)toMerge;
  188. if(rem.offset == offset)
  189. {
  190. rem.content.str = rem.content.str.concat(text);
  191. rem.content.hashcode = rem.content.str.hashCode();
  192. rem.length += length;
  193. KillRing.getInstance().changed(rem.content);
  194. return;
  195. }
  196. else if(offset + length == rem.offset)
  197. {
  198. rem.content.str = text.concat(rem.content.str);
  199. rem.content.hashcode = rem.content.str.hashCode();
  200. rem.length += length;
  201. rem.offset = offset;
  202. KillRing.getInstance().changed(rem.content);
  203. return;
  204. }
  205. }
  206. Remove rem = new Remove(this,offset,length,text);
  207. if(clearDirty)
  208. {
  209. redoClearDirty = last;
  210. undoClearDirty = rem;
  211. }
  212. if(compoundEdit != null)
  213. compoundEdit.add(rem);
  214. else
  215. {
  216. reviseUndoId();
  217. addEdit(rem);
  218. }
  219. KillRing.getInstance().add(rem.content);
  220. } //}}}
  221. //{{{ resetClearDirty method
  222. public void resetClearDirty()
  223. {
  224. redoClearDirty = getLastEdit();
  225. if(redosFirst instanceof CompoundEdit)
  226. undoClearDirty = ((CompoundEdit)redosFirst).first;
  227. else
  228. undoClearDirty = redosFirst;
  229. } //}}}
  230. //{{{ Private members
  231. //{{{ Instance variables
  232. private JEditBuffer buffer;
  233. // queue of undos. last is most recent, first is oldest
  234. private Edit undosFirst;
  235. private Edit undosLast;
  236. // queue of redos. first is most recent, last is oldest
  237. private Edit redosFirst;
  238. private int limit;
  239. private int undoCount;
  240. private int compoundEditCount;
  241. private CompoundEdit compoundEdit;
  242. private Edit undoClearDirty, redoClearDirty;
  243. private Object undoId;
  244. //}}}
  245. //{{{ addEdit() method
  246. private void addEdit(Edit edit)
  247. {
  248. if(undosFirst == null)
  249. undosFirst = undosLast = edit;
  250. else
  251. {
  252. undosLast.next = edit;
  253. edit.prev = undosLast;
  254. undosLast = edit;
  255. }
  256. redosFirst = null;
  257. undoCount++;
  258. while(undoCount > limit)
  259. {
  260. undoCount--;
  261. if(undosFirst == undosLast)
  262. undosFirst = undosLast = null;
  263. else
  264. {
  265. undosFirst.next.prev = null;
  266. undosFirst = undosFirst.next;
  267. }
  268. }
  269. } //}}}
  270. //{{{ getMergeEdit() method
  271. private Edit getMergeEdit()
  272. {
  273. Edit last = getLastEdit();
  274. return (compoundEdit != null ? compoundEdit.last : last);
  275. } //}}}
  276. //{{{ getLastEdit() method
  277. private Edit getLastEdit()
  278. {
  279. if(undosLast instanceof CompoundEdit)
  280. return ((CompoundEdit)undosLast).last;
  281. else
  282. return undosLast;
  283. } //}}}
  284. //{{{ reviseUndoId()
  285. /*
  286. * Revises a unique undoId for a the undo operation that is being
  287. * created as a result of a buffer content change, or that is being
  288. * used for undo/redo. Content changes that belong to the same undo
  289. * operation will have the same undoId.
  290. *
  291. * This method should be called whenever:
  292. * - a buffer content change causes a new undo operation to be created;
  293. * i.e. whenever a content change is not included in the same undo
  294. * operation as the previous.
  295. * - an undo/redo is performed.
  296. */
  297. private void reviseUndoId()
  298. {
  299. undoId = new Object();
  300. } //}}}
  301. //}}}
  302. //{{{ Inner classes
  303. //{{{ Edit class
  304. abstract static class Edit
  305. {
  306. Edit prev, next;
  307. //{{{ undo() method
  308. abstract int undo();
  309. //}}}
  310. //{{{ redo() method
  311. abstract int redo();
  312. //}}}
  313. } //}}}
  314. //{{{ Insert class
  315. static class Insert extends Edit
  316. {
  317. //{{{ Insert constructor
  318. Insert(UndoManager mgr, int offset, int length, String str)
  319. {
  320. this.mgr = mgr;
  321. this.offset = offset;
  322. this.length = length;
  323. this.str = str;
  324. } //}}}
  325. //{{{ undo() method
  326. int undo()
  327. {
  328. mgr.buffer.remove(offset,length);
  329. if(mgr.undoClearDirty == this)
  330. mgr.buffer.setDirty(false);
  331. return offset;
  332. } //}}}
  333. //{{{ redo() method
  334. int redo()
  335. {
  336. mgr.buffer.insert(offset,str);
  337. if(mgr.redoClearDirty == this)
  338. mgr.buffer.setDirty(false);
  339. return offset + length;
  340. } //}}}
  341. UndoManager mgr;
  342. int offset;
  343. int length;
  344. String str;
  345. } //}}}
  346. //{{{ RemovedContent clas
  347. // This class is held in KillRing.
  348. public static class RemovedContent
  349. {
  350. String str;
  351. int hashcode;
  352. boolean inKillRing;
  353. public RemovedContent(String str)
  354. {
  355. this.str = str;
  356. this.hashcode = str.hashCode();
  357. }
  358. public String toString()
  359. {
  360. return str;
  361. }
  362. }// }}}
  363. //{{{ Remove class
  364. static class Remove extends Edit
  365. {
  366. //{{{ Remove constructor
  367. Remove(UndoManager mgr, int offset, int length, String str)
  368. {
  369. this.mgr = mgr;
  370. this.offset = offset;
  371. this.length = length;
  372. this.content = new RemovedContent(str);
  373. } //}}}
  374. //{{{ undo() method
  375. int undo()
  376. {
  377. mgr.buffer.insert(offset,content.str);
  378. if(mgr.undoClearDirty == this)
  379. mgr.buffer.setDirty(false);
  380. return offset + length;
  381. } //}}}
  382. //{{{ redo() method
  383. int redo()
  384. {
  385. mgr.buffer.remove(offset,length);
  386. if(mgr.redoClearDirty == this)
  387. mgr.buffer.setDirty(false);
  388. return offset;
  389. } //}}}
  390. UndoManager mgr;
  391. int offset;
  392. int length;
  393. final RemovedContent content;
  394. } //}}}
  395. //{{{ CompoundEdit class
  396. static class CompoundEdit extends Edit
  397. {
  398. //{{{ undo() method
  399. public int undo()
  400. {
  401. int retVal = -1;
  402. Edit edit = last;
  403. while(edit != null)
  404. {
  405. retVal = edit.undo();
  406. edit = edit.prev;
  407. }
  408. return retVal;
  409. } //}}}
  410. //{{{ redo() method
  411. public int redo()
  412. {
  413. int retVal = -1;
  414. Edit edit = first;
  415. while(edit != null)
  416. {
  417. retVal = edit.redo();
  418. edit = edit.next;
  419. }
  420. return retVal;
  421. } //}}}
  422. //{{{ add() method
  423. public void add(Edit edit)
  424. {
  425. if(first == null)
  426. first = last = edit;
  427. else
  428. {
  429. edit.prev = last;
  430. last.next = edit;
  431. last = edit;
  432. }
  433. } //}}}
  434. Edit first, last;
  435. } //}}}
  436. //}}}
  437. }