function UndoManager(callback, panels) {

        var undoObj = this;
        var undoStack = []; // A stack of undo states
        var stackPtr = 0; // The index of the current state
        var mode = "none";
        var lastState; // The last state
        var timer; // The setTimeout handle for cancelling the timer
        var inputStateObj;

        // Set the mode for later logic steps.
        var setMode = function (newMode, noSave) {
            if (mode != newMode) {
                mode = newMode;
                if (!noSave) {
                    saveState();
                }
            }

            if (!uaSniffed.isIE || mode != "moving") {
                timer = setTimeout(refreshState, 1);
            }
            else {
                inputStateObj = null;
            }
        };

        var refreshState = function (isInitialState) {
            inputStateObj = new TextareaState(panels, isInitialState);
            timer = undefined;
        };

        this.setCommandMode = function () {
            mode = "command";
            saveState();
            timer = setTimeout(refreshState, 0);
        };

        this.canUndo = function () {
            return stackPtr > 1;
        };

        this.canRedo = function () {
            return !!undoStack[stackPtr + 1];
        };

        // Removes the last state and restores it.
        this.undo = function () {

            if (undoObj.canUndo()) {
                if (lastState) {
                    // What about setting state -1 to null or checking for undefined?
                    lastState.restore();
                    lastState = null;
                }
                else {
                    undoStack[stackPtr] = new TextareaState(panels);
                    undoStack[--stackPtr].restore();

                    if (callback) {
                        callback();
                    }
                }
            }

            mode = "none";
            panels.input.focus();
            refreshState();
        };

        // Redo an action.
        this.redo = function () {

            if (undoObj.canRedo()) {

                undoStack[++stackPtr].restore();

                if (callback) {
                    callback();
                }
            }

            mode = "none";
            panels.input.focus();
            refreshState();
        };

        // Push the input area state to the stack.
        var saveState = function () {
            var currState = inputStateObj || new TextareaState(panels);

            if (!currState) {
                return false;
            }
            if (mode == "moving") {
                if (!lastState) {
                    lastState = currState;
                }
                return;
            }
            if (lastState) {
                if (undoStack[stackPtr - 1].text != lastState.text) {
                    undoStack[stackPtr++] = lastState;
                }
                lastState = null;
            }
            undoStack[stackPtr++] = currState;
            undoStack[stackPtr + 1] = null;
            if (callback) {
                callback();
            }
        };

        var handleCtrlYZ = function (event) {

            var handled = false;

            if ((event.ctrlKey || event.metaKey) && !event.altKey) {

                // IE and Opera do not support charCode.
                var keyCode = event.charCode || event.keyCode;
                var keyCodeChar = String.fromCharCode(keyCode);

                switch (keyCodeChar.toLowerCase()) {

                    case "y":
                        undoObj.redo();
                        handled = true;
                        break;

                    case "z":
                        if (!event.shiftKey) {
                            undoObj.undo();
                        }
                        else {
                            undoObj.redo();
                        }
                        handled = true;
                        break;
                }
            }

            if (handled) {
                if (event.preventDefault) {
                    event.preventDefault();
                }
                if (window.event) {
                    window.event.returnValue = false;
                }

                return null;
            }
        };

        // Set the mode depending on what is going on in the input area.
        var handleModeChange = function (event) {

            if (!event.ctrlKey && !event.metaKey) {

                var keyCode = event.keyCode;

                if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) {
                    // 33 - 40: page up/dn and arrow keys
                    // 63232 - 63235: page up/dn and arrow keys on safari
                    setMode("moving");
                }
                else if (keyCode == 8 || keyCode == 46 || keyCode == 127) {
                    // 8: backspace
                    // 46: delete
                    // 127: delete
                    setMode("deleting");
                }
                else if (keyCode == 13) {
                    // 13: Enter
                    setMode("newlines");
                }
                else if (keyCode == 27) {
                    // 27: escape
                    setMode("escape");
                }
                else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) {
                    // 16-20 are shift, etc.
                    // 91: left window key
                    // I think this might be a little messed up since there are
                    // a lot of nonprinting keys above 20.
                    setMode("typing");
                }
            }
        };

        var setEventHandlers = function () {
            util.addEvent(panels.input, "keypress", function (event) {
                // keyCode 89: y
                // keyCode 90: z
                if ((event.ctrlKey || event.metaKey) && !event.altKey && (event.keyCode == 89 || event.keyCode == 90)) {
                    event.preventDefault();
                }
            });

            var handlePaste = function () {
                if (uaSniffed.isIE || (inputStateObj && inputStateObj.text != panels.input.value)) {
                    if (timer == undefined) {
                        mode = "paste";
                        saveState();
                        refreshState();
                    }
                }
            };

            util.addEvent(panels.input, "keydown", handleCtrlYZ);
            util.addEvent(panels.input, "keydown", handleModeChange);
            util.addEvent(panels.input, "mousedown", function () {
                setMode("moving");
            });

            panels.input.onpaste = handlePaste;
            panels.input.ondrop = handlePaste;
        };

        var init = function () {
            setEventHandlers();
            refreshState(true);
            saveState();
        };

        init();
    }
// I should rename the stack variables to list.

I should rename the stack variables to list.
I should rename the stack variables to list .
function UndoManager(callback, panels) {
        var undoObj = this;
        var undoStack = []; 
        var stackPtr = 0; 
        var mode = "none";
        var lastState; 
        var timer; 
        var inputStateObj;
        var setMode = function (newMode, noSave) {
            if (mode != newMode) {
                mode = newMode;
                if (!noSave) {
                    saveState();
                }
            }
            if (!uaSniffed.isIE || mode != "moving") {
                timer = setTimeout(refreshState, 1);
            }
            else {
                inputStateObj = null;
            }
        };
        var refreshState = function (isInitialState) {
            inputStateObj = new TextareaState(panels, isInitialState);
            timer = undefined;
        };
        this.setCommandMode = function () {
            mode = "command";
            saveState();
            timer = setTimeout(refreshState, 0);
        };
        this.canUndo = function () {
            return stackPtr > 1;
        };
        this.canRedo = function () {
            return !!undoStack[stackPtr + 1];
        };
        this.undo = function () {
            if (undoObj.canUndo()) {
                if (lastState) {
                    lastState.restore();
                    lastState = null;
                }
                else {
                    undoStack[stackPtr] = new TextareaState(panels);
                    undoStack[--stackPtr].restore();
                    if (callback) {
                        callback();
                    }
                }
            }
            mode = "none";
            panels.input.focus();
            refreshState();
        };
        this.redo = function () {
            if (undoObj.canRedo()) {
                undoStack[++stackPtr].restore();
                if (callback) {
                    callback();
                }
            }
            mode = "none";
            panels.input.focus();
            refreshState();
        };
        var saveState = function () {
            var currState = inputStateObj || new TextareaState(panels);
            if (!currState) {
                return false;
            }
            if (mode == "moving") {
                if (!lastState) {
                    lastState = currState;
                }
                return;
            }
            if (lastState) {
                if (undoStack[stackPtr - 1].text != lastState.text) {
                    undoStack[stackPtr++] = lastState;
                }
                lastState = null;
            }
            undoStack[stackPtr++] = currState;
            undoStack[stackPtr + 1] = null;
            if (callback) {
                callback();
            }
        };
        var handleCtrlYZ = function (event) {
            var handled = false;
            if ((event.ctrlKey || event.metaKey) && !event.altKey) {
                var keyCode = event.charCode || event.keyCode;
                var keyCodeChar = String.fromCharCode(keyCode);
                switch (keyCodeChar.toLowerCase()) {
                    case "y":
                        undoObj.redo();
                        handled = true;
                        break;
                    case "z":
                        if (!event.shiftKey) {
                            undoObj.undo();
                        }
                        else {
                            undoObj.redo();
                        }
                        handled = true;
                        break;
                }
            }
            if (handled) {
                if (event.preventDefault) {
                    event.preventDefault();
                }
                if (window.event) {
                    window.event.returnValue = false;
                }
                return null;
            }
        };
        var handleModeChange = function (event) {
            if (!event.ctrlKey && !event.metaKey) {
                var keyCode = event.keyCode;
                if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) {
                    setMode("moving");
                }
                else if (keyCode == 8 || keyCode == 46 || keyCode == 127) {
                    setMode("deleting");
                }
                else if (keyCode == 13) {
                    setMode("newlines");
                }
                else if (keyCode == 27) {
                    setMode("escape");
                }
                else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) {
                    setMode("typing");
                }
            }
        };
        var setEventHandlers = function () {
            util.addEvent(panels.input, "keypress", function (event) {
                if ((event.ctrlKey || event.metaKey) && !event.altKey && (event.keyCode == 89 || event.keyCode == 90)) {
                    event.preventDefault();
                }
            });
            var handlePaste = function () {
                if (uaSniffed.isIE || (inputStateObj && inputStateObj.text != panels.input.value)) {
                    if (timer == undefined) {
                        mode = "paste";
                        saveState();
                        refreshState();
                    }
                }
            };
            util.addEvent(panels.input, "keydown", handleCtrlYZ);
            util.addEvent(panels.input, "keydown", handleModeChange);
            util.addEvent(panels.input, "mousedown", function () {
                setMode("moving");
            });
            panels.input.onpaste = handlePaste;
            panels.input.ondrop = handlePaste;
        };
        var init = function () {
            setEventHandlers();
            refreshState(true);
            saveState();
        };
        init();
    }
function UndoManager ( callback , panels ) { var undoObj = this ; var undoStack = [ ] ; var stackPtr = 0 ; var mode = "none" ; var lastState ; var timer ; var inputStateObj ; var setMode = function ( newMode , noSave ) { if ( mode != newMode ) { mode = newMode ; if ( ! noSave ) { saveState ( ) ; } } if ( ! uaSniffed . isIE || mode != "moving" ) { timer = setTimeout ( refreshState , 1 ) ; } else { inputStateObj = null ; } } ; var refreshState = function ( isInitialState ) { inputStateObj = new TextareaState ( panels , isInitialState ) ; timer = undefined ; } ; this . setCommandMode = function ( ) { mode = "command" ; saveState ( ) ; timer = setTimeout ( refreshState , 0 ) ; } ; this . canUndo = function ( ) { return stackPtr > 1 ; } ; this . canRedo = function ( ) { return ! ! undoStack [ stackPtr + 1 ] ; } ; this . undo = function ( ) { if ( undoObj . canUndo ( ) ) { if ( lastState ) { lastState . restore ( ) ; lastState = null ; } else { undoStack [ stackPtr ] = new TextareaState ( panels ) ; undoStack [ -- stackPtr ] . restore ( ) ; if ( callback ) { callback ( ) ; } } } mode = "none" ; panels . input . focus ( ) ; refreshState ( ) ; } ; this . redo = function ( ) { if ( undoObj . canRedo ( ) ) { undoStack [ ++ stackPtr ] . restore ( ) ; if ( callback ) { callback ( ) ; } } mode = "none" ; panels . input . focus ( ) ; refreshState ( ) ; } ; var saveState = function ( ) { var currState = inputStateObj || new TextareaState ( panels ) ; if ( ! currState ) { return false ; } if ( mode == "moving" ) { if ( ! lastState ) { lastState = currState ; } return ; } if ( lastState ) { if ( undoStack [ stackPtr - 1 ] . text != lastState . text ) { undoStack [ stackPtr ++ ] = lastState ; } lastState = null ; } undoStack [ stackPtr ++ ] = currState ; undoStack [ stackPtr + 1 ] = null ; if ( callback ) { callback ( ) ; } } ; var handleCtrlYZ = function ( event ) { var handled = false ; if ( ( event . ctrlKey || event . metaKey ) && ! event . altKey ) { var keyCode = event . charCode || event . keyCode ; var keyCodeChar = String . fromCharCode ( keyCode ) ; switch ( keyCodeChar . toLowerCase ( ) ) { case "y" : undoObj . redo ( ) ; handled = true ; break ; case "z" : if ( ! event . shiftKey ) { undoObj . undo ( ) ; } else { undoObj . redo ( ) ; } handled = true ; break ; } } if ( handled ) { if ( event . preventDefault ) { event . preventDefault ( ) ; } if ( window . event ) { window . event . returnValue = false ; } return null ; } } ; var handleModeChange = function ( event ) { if ( ! event . ctrlKey && ! event . metaKey ) { var keyCode = event . keyCode ; if ( ( keyCode >= 33 && keyCode <= 40 ) || ( keyCode >= 63232 && keyCode <= 63235 ) ) { setMode ( "moving" ) ; } else if ( keyCode == 8 || keyCode == 46 || keyCode == 127 ) { setMode ( "deleting" ) ; } else if ( keyCode == 13 ) { setMode ( "newlines" ) ; } else if ( keyCode == 27 ) { setMode ( "escape" ) ; } else if ( ( keyCode < 16 || keyCode > 20 ) && keyCode != 91 ) { setMode ( "typing" ) ; } } } ; var setEventHandlers = function ( ) { util . addEvent ( panels . input , "keypress" , function ( event ) { if ( ( event . ctrlKey || event . metaKey ) && ! event . altKey && ( event . keyCode == 89 || event . keyCode == 90 ) ) { event . preventDefault ( ) ; } } ) ; var handlePaste = function ( ) { if ( uaSniffed . isIE || ( inputStateObj && inputStateObj . text != panels . input . value ) ) { if ( timer == undefined ) { mode = "paste" ; saveState ( ) ; refreshState ( ) ; } } } ; util . addEvent ( panels . input , "keydown" , handleCtrlYZ ) ; util . addEvent ( panels . input , "keydown" , handleModeChange ) ; util . addEvent ( panels . input , "mousedown" , function ( ) { setMode ( "moving" ) ; } ) ; panels . input . onpaste = handlePaste ; panels . input . ondrop = handlePaste ; } ; var init = function ( ) { setEventHandlers ( ) ; refreshState ( true ) ; saveState ( ) ; } ; init ( ) ; }
I should rename the stack variables to list.
I should rename the stack variables to list .
// A stack of undo states
 // The index of the current state
 // The last state
 // The setTimeout handle for cancelling the timer
 // Set the mode for later logic steps.
 // Removes the last state and restores it.
 // What about setting state -1 to null or checking for undefined?
 // Redo an action.
 // Push the input area state to the stack.
 // IE and Opera do not support charCode.
 // Set the mode depending on what is going on in the input area.
 // 33 - 40: page up/dn and arrow keys
 // 63232 - 63235: page up/dn and arrow keys on safari
 // 8: backspace
 // 46: delete
 // 127: delete
 // 13: Enter
 // 27: escape
 // 16-20 are shift, etc.
 // 91: left window key
 // I think this might be a little messed up since there are
 // a lot of nonprinting keys above 20.
 // keyCode 89: y
 // keyCode 90: z
