diff options
Diffstat (limited to 'devtools/client/shared/undo.js')
-rw-r--r-- | devtools/client/shared/undo.js | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/devtools/client/shared/undo.js b/devtools/client/shared/undo.js new file mode 100644 index 0000000000..759d3a4aed --- /dev/null +++ b/devtools/client/shared/undo.js @@ -0,0 +1,190 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * A simple undo stack manager. + * + * Actions are added along with the necessary code to + * reverse the action. + * + * @param integer maxUndo Maximum number of undo steps. + * defaults to 50. + */ +function UndoStack(maxUndo) { + this.maxUndo = maxUndo || 50; + this._stack = []; +} + +exports.UndoStack = UndoStack; + +UndoStack.prototype = { + // Current index into the undo stack. Is positioned after the last + // currently-applied change. + _index: 0, + + // The current batch depth (see startBatch() for details) + _batchDepth: 0, + + destroy() { + this.uninstallController(); + delete this._stack; + }, + + /** + * Start a collection of related changes. Changes will be batched + * together into one undo/redo item until endBatch() is called. + * + * Batches can be nested, in which case the outer batch will contain + * all items from the inner batches. This allows larger user + * actions made up of a collection of smaller actions to be + * undone as a single action. + */ + startBatch() { + if (this._batchDepth++ === 0) { + this._batch = []; + } + }, + + /** + * End a batch of related changes, performing its action and adding + * it to the undo stack. + */ + endBatch() { + if (--this._batchDepth > 0) { + return; + } + + // Cut off the end of the undo stack at the current index, + // and the beginning to prevent a stack larger than maxUndo. + const start = Math.max(this._index + 1 - this.maxUndo, 0); + this._stack = this._stack.slice(start, this._index); + + const batch = this._batch; + delete this._batch; + const entry = { + do() { + for (const item of batch) { + item.do(); + } + }, + undo() { + for (let i = batch.length - 1; i >= 0; i--) { + batch[i].undo(); + } + }, + }; + this._stack.push(entry); + this._index = this._stack.length; + entry.do(); + }, + + /** + * Perform an action, adding it to the undo stack. + * + * @param function toDo Called to perform the action. + * @param function undo Called to reverse the action. + */ + do(toDo, undo) { + this.startBatch(); + this._batch.push({ do: toDo, undo }); + this.endBatch(); + }, + + /* + * Returns true if undo() will do anything. + */ + canUndo() { + return this._index > 0; + }, + + /** + * Undo the top of the undo stack. + * + * @return true if an action was undone. + */ + undo() { + if (!this.canUndo()) { + return false; + } + this._stack[--this._index].undo(); + return true; + }, + + /** + * Returns true if redo() will do anything. + */ + canRedo() { + return this._stack.length > this._index; + }, + + /** + * Redo the most recently undone action. + * + * @return true if an action was redone. + */ + redo() { + if (!this.canRedo()) { + return false; + } + this._stack[this._index++].do(); + return true; + }, + + /** + * ViewController implementation for undo/redo. + */ + + /** + * Install this object as a command controller. + */ + installController(controllerWindow) { + const controllers = controllerWindow.controllers; + // Only available when running in a Firefox panel. + if (!controllers || !controllers.appendController) { + return; + } + + this._controllerWindow = controllerWindow; + controllers.appendController(this); + }, + + /** + * Uninstall this object from the command controller. + */ + uninstallController() { + if (!this._controllerWindow) { + return; + } + this._controllerWindow.controllers.removeController(this); + }, + + supportsCommand(command) { + return command == "cmd_undo" || command == "cmd_redo"; + }, + + isCommandEnabled(command) { + switch (command) { + case "cmd_undo": + return this.canUndo(); + case "cmd_redo": + return this.canRedo(); + } + return false; + }, + + doCommand(command) { + switch (command) { + case "cmd_undo": + return this.undo(); + case "cmd_redo": + return this.redo(); + default: + return null; + } + }, + + onEvent(event) {}, +}; |