c9-core/plugins/c9.ide.editors/undomanager.js

400 wiersze
15 KiB
JavaScript

define(function(require, module, exports) {
main.consumes = ["Plugin"];
main.provides = ["UndoManager"];
return main;
function main(options, imports, register) {
var Plugin = imports.Plugin;
function UndoManager(options) {
var plugin = new Plugin("Ajax.org", main.consumes);
var emit = plugin.getEmitter();
var position = -1, mark = -1, stack = [];
if (options)
setState(options);
function canUndo() {
return position >= 0;
}
function undo() {
if (!canUndo())
return false;
var item = stack[position];
if (!item.undo)
item = stack[position] = findItem(item);
position--;
item.undo();
emit("change");
}
function canRedo() {
return position !== stack.length - 1;
}
function redo() {
if (!canRedo())
return false;
position++;
var item = stack[position];
if (!item.redo)
item = stack[position] = findItem(item);
item.redo();
emit("change");
}
function clearUndo() {
stack = stack.slice(position - 1);
position = 0;
if (mark < position)
mark = -2;
emit("change");
}
function clearRedo(noEvent) {
stack = stack.slice(0, position + 1);
if (mark > position)
mark = -2;
if (!noEvent)
emit("change");
}
function add(item) {
if (!item.undo || !item.redo)
throw new Error("Missing undo and/or redo method implementation");
clearRedo(true);
stack.push(item);
position = stack.length - 1;
emit("change");
}
function remove(idx) {
var item = stack.splice(idx, 1)[0];
if (!item.undo)
item = findItem(item);
item.undo();
if (idx <= position)
position--;
if (mark == idx)
mark = -2;
else if (mark > idx)
mark--;
emit("change");
}
function bookmark(index) {
mark = index !== undefined ? index : position;
emit("change");
}
function isAtBookmark() {
return mark == position;
}
function item(idx) {
return stack[idx] || null;
}
function getState() {
return {
mark: mark,
position: position,
stack: stack
.filter(function(item) { return item; })
.map(function(item) {
return item.getState ? item.getState() : item;
})
};
}
function setState(state) {
if (!state || !state.stack.length)
return reset();
mark = state.mark;
position = state.position;
if (state.stack.length && state.stack[0] == undefined)
return; // guard against broken stack
stack = state.stack;
emit("change"); // If you remove this again, change the test
}
function findItem(compressedItem) {
return emit("itemFind", { state: compressedItem });
}
function reset() {
if (position == -1)
return;
position = -1;
stack = [];
mark = -1;
emit("change");
}
plugin.freezePublicAPI.baseclass();
/**
* The Undo Manager class of Cloud9. Each {@link Document}
* has a single instance of the undo manager that
* is used by each editor that works on the document. The undo
* manager manages the undo stack which tracks the changes to the
* document.
*
* The undoManager relates to other objects as such:
*
* * {@link Pane} - Represent a single pane, housing multiple tabs
* * {@link Tab} - A single tab (button) in a pane
* * {@link Editor} - The editor responsible for displaying the file in the tab
* * {@link Document} - The representation of a file in the tab
* * {@link Session} - The session information of the editor
* * **UndoManager - The object that manages the undo stack for this document**
*
* Panes can live in certain areas of Cloud9. By default these areas are:
*
* * {@link panes} - The main area where editor panes are displayed
* * {@link console} - The console in the bottom of the screen
*
* Tabs are managed by the {@link tabManager}. The default way to
* open a new file in an editor uses the tabManager:
*
* tabManager.openFile("/file.js", true, function(err, tab) {
* tab.document.value = "new value"; // adds item to the undo manager.
*
* var undoManager = tab.document.undoManager;
* undoManager.undo();
* });
*
* The undo manager doesn't apply any changes itself. The changes
* are managed by objects that are pushed onto the stack and have
* an undo(), redo() and getState() method. This allows each
* editor to implement it's own way to change the document while
* keeping a consistent undo stack for the document.
*
* Example:
*
* The item class in the following example simply manages an
* array, adding and removing items.
*
* var data = [];
*
* function Item(info, idx) {
* this.getState = function(){ return [ info, idx ] }
* this.undo = function(){ data.splice(idx, 1) }
* this.redo = function(){
* data[idx || (idx = data.length)] = info;
* return this;
* }
* }
*
* var undoManager = new UndoManager();
* undoManager.add(new Item("a").redo()); // data = ["a"]
* undoManager.add(new Item("b").redo()); // data = ["a", "b"]
*
* undoManager.undo(); // data = ["a"];
* undoManager.undo(); // data = [];
*
**/
plugin.freezePublicAPI({
/**
* The number of items on the stack. This number will stay the
* same when using {@link UndoManager#undo} and
* {@link UndoManager#redo}. See the {@link UndoManager#position}
* property if you want to find out which items are applied
* to the document and which are not.
*
* @property {Number} length
* @readonly
*/
get length() { return stack.length; },
/**
* Set to the index of the item in the stack up to which the
* items have been applied to the document. This property
* changes when using the {@link UndoManager#undo} and
* {@link UndoManager#redo} methods.
*
* When {@link UndoManager#length} is 10, and 6 items have been
* executed, this property is set to 5. It is then
* possible to execute redo() 4 times. At that point, the
* position property is set to 9. Calling redo() further will
* not result in any changes. Similarly undo() can be called
* until position is -1.
*
* @property {Number} position
* @readonly
*/
get position() { return position; },
events: [
/**
* Fires when the undo manager is loading state from a state
* object (object returned by {@link #getState}() call). This
* event allows you to switch the state object (which was a
* result of the getState() call to your item object) with
* the actual implementation of your item object.
*
* Return an instance of the item to push on to the stack.
*
* Example:
*
* undoManager.on("itemFind", function(e) {
* return new Item(e.state[0], e.state[1]);
* });
*
* @event itemFind
* @param {Object} e
* @param {Object} e.state The state of the object to
* be unserialized.
*/
"itemFind",
/**
* Fires whenever the {@link #position} or {@link #length}
* properties changes.
* @event change
*/
"change"
],
/**
* Checks if there are items to be undone. This is equivalent
* to:
*
* undoManager.position >= 0
*/
canUndo: canUndo,
/**
* Checks if there are items to be redone. This is equivalent
* to:
*
* undoManager.position != undoManager.length - 1
*/
canRedo: canRedo,
/**
* Reverts the last change that was applied to the document.
* This method calls undo() on the item that is on the stack
* at the index specified by the {@link UndoManager#position}
* property.
*/
undo: undo,
/**
* Executes the change that was undone last.
* This method calls redo() on the item that is on the stack
* at the index specified by the {@link UndoManager#position}
* property + 1.
*/
redo: redo,
/**
* Clears the complete stack
*/
reset: reset,
/**
* Retrieves an object from the stack by it's index.
* @param {Number} index The index of the item to retrieve.
* This needs to be a number between 0 and the value of the
* {@link UndoManager#length} property, including 0.
*/
item: item,
/**
* Sets the bookmark pointer to the `index` specfied or the
* current value of the `position` property.
* The bookmark is used by Cloud9 to specify at which point the
* file was saved.
* @param {Number} [index] The index of the item to bookmark
*/
bookmark: bookmark,
/**
* Retrieves whether the current position of the stack is bookmarked.
*/
isAtBookmark: isAtBookmark,
/**
* Removes all items on the stack that have an index less than
* or equal to the value of {@link UndoManager#position}. These
* are all the items that have been applied to the document.
*/
clearUndo: clearUndo,
/**
* Removes all items on the stack that have an index greater than
* the value of {@link UndoManager#position}. These are all the
* items that have not been applied to the document.
*/
clearRedo: clearRedo,
/**
* Retrieves the state of the undo stack. The state of the
* stack is retrieved by calling getState() on each item in the
* stack.
*
* @return {Object}
* @return {Number} return.mark The value of the {@link #bookmark} property.
* @return {Number} return.position The value of the {@link #position} property.
* @return {Array} return.stack An array containing the
* results of calling getState() on each stack item.
*/
getState: getState,
/**
* Sets the state of the undo stack
* @param {Object} state The state object. See {@link #getState}.
* @fires itemFind
*/
setState: setState,
/**
* Adds an item to the undo stack. The redo() method is called
* on the `item` that is added and the item is added to the
* stack.
* @param {Object} item the object to put on the undo stack.
* This item should have getState(), undo() and redo()
* methods.
*/
add: add,
/**
* Removes an item from the undo stack
* @param {Number} index The index of the item to remove
*/
remove: remove
});
plugin.load(null, "undoManager");
return plugin;
}
/***** Register and define API *****/
register(null, {
UndoManager: UndoManager
});
}
});