/jEdit/branches/4.3.x-fix-view-leak/org/gjt/sp/jedit/buffer/UndoManager.java

# · Java · 503 lines · 347 code · 65 blank · 91 comment · 68 complexity · bedf175224a2d658db7a77e72e0ccb47 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 16098 2009-08-27 21:59:29Z 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 = redosLast = 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 Edit redosLast;
  239. private int limit;
  240. private int undoCount;
  241. private int compoundEditCount;
  242. private CompoundEdit compoundEdit;
  243. private Edit undoClearDirty, redoClearDirty;
  244. private Object undoId;
  245. //}}}
  246. //{{{ addEdit() method
  247. private void addEdit(Edit edit)
  248. {
  249. if(undosFirst == null)
  250. undosFirst = undosLast = edit;
  251. else
  252. {
  253. undosLast.next = edit;
  254. edit.prev = undosLast;
  255. undosLast = edit;
  256. }
  257. redosFirst = redosLast = null;
  258. undoCount++;
  259. while(undoCount > limit)
  260. {
  261. undoCount--;
  262. if(undosFirst == undosLast)
  263. undosFirst = undosLast = null;
  264. else
  265. {
  266. undosFirst.next.prev = null;
  267. undosFirst = undosFirst.next;
  268. }
  269. }
  270. } //}}}
  271. //{{{ getMergeEdit() method
  272. private Edit getMergeEdit()
  273. {
  274. Edit last = getLastEdit();
  275. return (compoundEdit != null ? compoundEdit.last : last);
  276. } //}}}
  277. //{{{ getLastEdit() method
  278. private Edit getLastEdit()
  279. {
  280. if(undosLast instanceof CompoundEdit)
  281. return ((CompoundEdit)undosLast).last;
  282. else
  283. return undosLast;
  284. } //}}}
  285. //{{{ reviseUndoId()
  286. /*
  287. * Revises a unique undoId for a the undo operation that is being
  288. * created as a result of a buffer content change, or that is being
  289. * used for undo/redo. Content changes that belong to the same undo
  290. * operation will have the same undoId.
  291. *
  292. * This method should be called whenever:
  293. * - a buffer content change causes a new undo operation to be created;
  294. * i.e. whenever a content change is not included in the same undo
  295. * operation as the previous.
  296. * - an undo/redo is performed.
  297. */
  298. private void reviseUndoId()
  299. {
  300. undoId = new Object();
  301. } //}}}
  302. //}}}
  303. //{{{ Inner classes
  304. //{{{ Edit class
  305. abstract static class Edit
  306. {
  307. Edit prev, next;
  308. //{{{ undo() method
  309. abstract int undo();
  310. //}}}
  311. //{{{ redo() method
  312. abstract int redo();
  313. //}}}
  314. } //}}}
  315. //{{{ Insert class
  316. static class Insert extends Edit
  317. {
  318. //{{{ Insert constructor
  319. Insert(UndoManager mgr, int offset, int length, String str)
  320. {
  321. this.mgr = mgr;
  322. this.offset = offset;
  323. this.length = length;
  324. this.str = str;
  325. } //}}}
  326. //{{{ undo() method
  327. int undo()
  328. {
  329. mgr.buffer.remove(offset,length);
  330. if(mgr.undoClearDirty == this)
  331. mgr.buffer.setDirty(false);
  332. return offset;
  333. } //}}}
  334. //{{{ redo() method
  335. int redo()
  336. {
  337. mgr.buffer.insert(offset,str);
  338. if(mgr.redoClearDirty == this)
  339. mgr.buffer.setDirty(false);
  340. return offset + length;
  341. } //}}}
  342. UndoManager mgr;
  343. int offset;
  344. int length;
  345. String str;
  346. } //}}}
  347. //{{{ RemovedContent clas
  348. // This class is held in KillRing.
  349. public static class RemovedContent
  350. {
  351. String str;
  352. int hashcode;
  353. boolean inKillRing;
  354. public RemovedContent(String str)
  355. {
  356. this.str = str;
  357. this.hashcode = str.hashCode();
  358. }
  359. public String toString()
  360. {
  361. return str;
  362. }
  363. }// }}}
  364. //{{{ Remove class
  365. static class Remove extends Edit
  366. {
  367. //{{{ Remove constructor
  368. Remove(UndoManager mgr, int offset, int length, String str)
  369. {
  370. this.mgr = mgr;
  371. this.offset = offset;
  372. this.length = length;
  373. this.content = new RemovedContent(str);
  374. } //}}}
  375. //{{{ undo() method
  376. int undo()
  377. {
  378. mgr.buffer.insert(offset,content.str);
  379. if(mgr.undoClearDirty == this)
  380. mgr.buffer.setDirty(false);
  381. return offset + length;
  382. } //}}}
  383. //{{{ redo() method
  384. int redo()
  385. {
  386. mgr.buffer.remove(offset,length);
  387. if(mgr.redoClearDirty == this)
  388. mgr.buffer.setDirty(false);
  389. return offset;
  390. } //}}}
  391. UndoManager mgr;
  392. int offset;
  393. int length;
  394. final RemovedContent content;
  395. } //}}}
  396. //{{{ CompoundEdit class
  397. static class CompoundEdit extends Edit
  398. {
  399. //{{{ undo() method
  400. public int undo()
  401. {
  402. int retVal = -1;
  403. Edit edit = last;
  404. while(edit != null)
  405. {
  406. retVal = edit.undo();
  407. edit = edit.prev;
  408. }
  409. return retVal;
  410. } //}}}
  411. //{{{ redo() method
  412. public int redo()
  413. {
  414. int retVal = -1;
  415. Edit edit = first;
  416. while(edit != null)
  417. {
  418. retVal = edit.redo();
  419. edit = edit.next;
  420. }
  421. return retVal;
  422. } //}}}
  423. //{{{ add() method
  424. public void add(Edit edit)
  425. {
  426. if(first == null)
  427. first = last = edit;
  428. else
  429. {
  430. edit.prev = last;
  431. last.next = edit;
  432. last = edit;
  433. }
  434. } //}}}
  435. Edit first, last;
  436. } //}}}
  437. //}}}
  438. }