UndoManagers

UndoManagers manage the change history of the contents of a document. An UndoManager is completely customizable and enables an editor implementation to implement undo in a straight forward way. This article is geared primarily towards working with the UndoManager in custom editors.

🚧

Creating UndoManagers

We recommend using the UndoManager class to create undoManagers when you need an abstract representation of change history. In most cases you won't need to create an undoManager as this is done automatically when a document is created. However there is no reason you couldn't create an undoManager and use it for other purposes.

Object structure

Besides tabs and panes there are several other objects involved in displaying content and making it editable. The following hierarchy describes the relationships between those objects.

  • Pane - Represent a single pane, housing multiple tabs
    • Editor - The editor responsible for displaying the document in the tab
    • Tab - A single tab (button) in a pane
      • Document - The representation of a file in the tab
        • Session - The session information of the editor
        • UndoManager - The object that manages the undo stack for this document

It is important to note that the Editor plugin(s) are related to the pane, not the tab. There's only one instance of a certain editor for each pane. The editor is responsible for displaying the content of a document based on the tab that is active. There can be a maximum of one tab active per pane.

Undo() and Redo()

The most common operations a user triggers on the undo manager of a document are undo and redo. Undo will revert the last done change and redo will undo a recent revert, if any.

The following example opens a file in the ace editor and adds a change and then calls undo() and redo().

tabManager.open({ path: "/file.js", focus: true }, function(err, tab){
    if (err) return console.error(err);
    
    var doc = tab.document;
    var ace = tab.editor.ace;
    
    // Make a change
    ace.insert("Something cool");
    
    // Undo that change
    doc.undoManager.undo();
    
    // Redo that change
    doc.undoManager.redo();
});

Change event

The change event is called each time the undo stack changes. Operations like undo, redo, reset and bookmark all trigger the change event. Note that you should use the changed property of a document to check if a document is changed.

doc.undoManager.on("change", function(){
    console.log("a change occurred");
});

The Undo Stack

The length and position properties give you access to the undoManager's state. All the items on the stack that have an index the same or less than position have caused a change to the current state of the document's contents. All the items on the stack greater than the position have been reverted.

var undo = doc.undoManager;

console.log("Current length", undo.length); // 10 - Index 0 to 9
console.log("Current position", undo.position); // 9 - No reverted items

undo.undo();

console.log("Current length", undo.length); // 10 - Index 0 to 9
console.log("Current position", undo.position); // 8 - 1 reverted items

Note that if there are 0 items on the stack, the position is -1 and length will be 0.

Clear all the reverted items.

doc.undoManager.clearUndo();

Reset the the undo manager to having a length of 0 and position of -1.

doc.undoManager.reset();

Bookmark

A bookmark is a way to set a place holder at a position on the undo stack. The save plugin uses this feature to keep track of which change state was saved to disk. Set the bookmark using the bookmark() method.

doc.undoManager.bookmark();
doc.undoManager.bookmark(somePosition);

Check whether the current position is at the bookmark.

var isChanged = doc.undoManager.isAtBookmark();

Items

Undo items are the unit of the stack and are the objects that implement actual changes to the document contents. The undo manager by itself only manages the process and calls upon these item objects to make changes.

When implementing a custom editor plugin that supports undo/redo implement your own undo items. An undo item must implement the following methods.

undo()Revert a change
redo()Redo a change
getState()Serialize a change

It's a good idea to implement the constructor in a way that the object can be created with the serialized data that getState() returns. Any JSON serializable object is accepted.

The next example implements an undo item that manipulates an array.

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 undo = doc.undoManager;
undo.add(new Item("a").redo()); // data = ["a"]
undo.add(new Item("b").redo()); // data = ["a", "b"]

undo.undo(); // data = ["a"];
undo.undo(); // data = [];
undo.redo(); // data = ["a"];

When the IDE reloads, the serialized undo state is loaded from a file's metadata. When undo() is called and an undo item is not present, the undo manager calls the itemFind event which allows your editor to create an item based on the serialized state.

doc.undoManager.on("itemFind", function(e){
    return new Item(e.state.[0], e.state[1]);
});