undomanager.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /* ***** BEGIN LICENSE BLOCK *****
  2. * Distributed under the BSD license:
  3. *
  4. * Copyright (c) 2010, Ajax.org B.V.
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * * Neither the name of Ajax.org B.V. nor the
  15. * names of its contributors may be used to endorse or promote products
  16. * derived from this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *
  29. * ***** END LICENSE BLOCK ***** */
  30. define(function(require, exports, module) {
  31. "use strict";
  32. /**
  33. *
  34. *
  35. * This object maintains the undo stack for an [[EditSession `EditSession`]].
  36. * @class UndoManager
  37. **/
  38. /**
  39. *
  40. *
  41. * Resets the current undo state and creates a new `UndoManager`.
  42. *
  43. * @constructor
  44. **/
  45. var UndoManager = function() {
  46. this.reset();
  47. };
  48. (function() {
  49. /**
  50. * Provides a means for implementing your own undo manager. `options` has one property, `args`, an [[Array `Array`]], with two elements:
  51. *
  52. * - `args[0]` is an array of deltas
  53. * - `args[1]` is the document to associate with
  54. *
  55. * @param {Object} options Contains additional properties
  56. *
  57. **/
  58. this.execute = function(options) {
  59. // Normalize deltas for storage.
  60. // var deltaSets = this.$serializeDeltas(options.args[0]);
  61. var deltaSets = options.args[0];
  62. // Add deltas to undo stack.
  63. this.$doc = options.args[1];
  64. if (options.merge && this.hasUndo()){
  65. this.dirtyCounter--;
  66. deltaSets = this.$undoStack.pop().concat(deltaSets);
  67. }
  68. this.$undoStack.push(deltaSets);
  69. // Reset redo stack.
  70. this.$redoStack = [];
  71. if (this.dirtyCounter < 0) {
  72. // The user has made a change after undoing past the last clean state.
  73. // We can never get back to a clean state now until markClean() is called.
  74. this.dirtyCounter = NaN;
  75. }
  76. this.dirtyCounter++;
  77. };
  78. /**
  79. * [Perform an undo operation on the document, reverting the last change.]{: #UndoManager.undo}
  80. * @param {Boolean} dontSelect {:dontSelect}
  81. *
  82. *
  83. * @returns {Range} The range of the undo.
  84. **/
  85. this.undo = function(dontSelect) {
  86. var deltaSets = this.$undoStack.pop();
  87. var undoSelectionRange = null;
  88. if (deltaSets) {
  89. undoSelectionRange = this.$doc.undoChanges(deltaSets, dontSelect);
  90. this.$redoStack.push(deltaSets);
  91. this.dirtyCounter--;
  92. }
  93. return undoSelectionRange;
  94. };
  95. /**
  96. * [Perform a redo operation on the document, reimplementing the last change.]{: #UndoManager.redo}
  97. * @param {Boolean} dontSelect {:dontSelect}
  98. *
  99. *
  100. **/
  101. this.redo = function(dontSelect) {
  102. var deltaSets = this.$redoStack.pop();
  103. var redoSelectionRange = null;
  104. if (deltaSets) {
  105. redoSelectionRange =
  106. this.$doc.redoChanges(this.$deserializeDeltas(deltaSets), dontSelect);
  107. this.$undoStack.push(deltaSets);
  108. this.dirtyCounter++;
  109. }
  110. return redoSelectionRange;
  111. };
  112. /**
  113. *
  114. * Destroys the stack of undo and redo redo operations.
  115. **/
  116. this.reset = function() {
  117. this.$undoStack = [];
  118. this.$redoStack = [];
  119. this.dirtyCounter = 0;
  120. };
  121. /**
  122. *
  123. * Returns `true` if there are undo operations left to perform.
  124. * @returns {Boolean}
  125. **/
  126. this.hasUndo = function() {
  127. return this.$undoStack.length > 0;
  128. };
  129. /**
  130. *
  131. * Returns `true` if there are redo operations left to perform.
  132. * @returns {Boolean}
  133. **/
  134. this.hasRedo = function() {
  135. return this.$redoStack.length > 0;
  136. };
  137. /**
  138. *
  139. * Marks the current status clean
  140. **/
  141. this.markClean = function() {
  142. this.dirtyCounter = 0;
  143. };
  144. /**
  145. *
  146. * Returns if the current status is clean
  147. * @returns {Boolean}
  148. **/
  149. this.isClean = function() {
  150. return this.dirtyCounter === 0;
  151. };
  152. // Serializes deltaSets to reduce memory usage.
  153. this.$serializeDeltas = function(deltaSets) {
  154. return cloneDeltaSetsObj(deltaSets, $serializeDelta);
  155. };
  156. // Deserializes deltaSets to allow application to the document.
  157. this.$deserializeDeltas = function(deltaSets) {
  158. return cloneDeltaSetsObj(deltaSets, $deserializeDelta);
  159. };
  160. function $serializeDelta(delta){
  161. return {
  162. action: delta.action,
  163. start: delta.start,
  164. end: delta.end,
  165. lines: delta.lines.length == 1 ? null : delta.lines,
  166. text: delta.lines.length == 1 ? delta.lines[0] : null
  167. };
  168. }
  169. function $deserializeDelta(delta) {
  170. return {
  171. action: delta.action,
  172. start: delta.start,
  173. end: delta.end,
  174. lines: delta.lines || [delta.text]
  175. };
  176. }
  177. function cloneDeltaSetsObj(deltaSets_old, fnGetModifiedDelta) {
  178. var deltaSets_new = new Array(deltaSets_old.length);
  179. for (var i = 0; i < deltaSets_old.length; i++) {
  180. var deltaSet_old = deltaSets_old[i];
  181. var deltaSet_new = { group: deltaSet_old.group, deltas: new Array(deltaSet_old.length)};
  182. for (var j = 0; j < deltaSet_old.deltas.length; j++) {
  183. var delta_old = deltaSet_old.deltas[j];
  184. deltaSet_new.deltas[j] = fnGetModifiedDelta(delta_old);
  185. }
  186. deltaSets_new[i] = deltaSet_new;
  187. }
  188. return deltaSets_new;
  189. }
  190. }).call(UndoManager.prototype);
  191. exports.UndoManager = UndoManager;
  192. });