/**@license
 *       __ _____                     ________                              __
 *      / // _  /__ __ _____ ___ __ _/__  ___/__ ___ ______ __ __  __ ___  / /
 *  __ / // // // // // _  // _// // / / // _  // _//     // //  \/ // _ \/ /
 * /  / // // // // // ___// / / // / / // ___// / / / / // // /\  // // / /__
 * \___//____ \\___//____//_/ _\_  / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/
 *           \/              /____/                              version 0.10.12
 *
 * This file is part of jQuery Terminal. http://terminal.jcubic.pl
 *
 * Copyright (c) 2010-2016 Jakub Jankiewicz <http://jcubic.pl>
 * Released under the MIT license
 *
 * Contains:
 *
 * Storage plugin Distributed under the MIT License
 * Copyright (c) 2010 Dave Schindler
 *
 * jQuery Timers licenced with the WTFPL
 * <http://jquery.offput.ca/timers/>
 *
 * Cross-Browser Split 1.1.1
 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
 * Available under the MIT License
 *
 * jQuery Caret
 * Copyright (c) 2009, Gideon Sireling
 * 3 clause BSD License
 *
 * sprintf.js
 * Copyright (c) 2007-2013 Alexandru Marasteanu <hello at alexei dot ro>
 * licensed under 3 clause BSD license
 *
 * Date: Thu, 16 Jun 2016 09:23:57 +0000
 */

/* TODO:
 *
 * Debug interpreters names in LocalStorage
 * onPositionChange event add to terminal ???
 * different command line history for each login users (add login if present to
 * localStorage key)
 *
 * TEST: login + promises/exec
 *       json-rpc/object + promises
 *
 * NOTE: json-rpc don't need promises and delegate resume/pause because only
 *       exec can call it and exec call interpreter that work with resume/pause
 */

/* jshint ignore:start */
(function(ctx) {
    var sprintf = function() {
        if (!sprintf.cache.hasOwnProperty(arguments[0])) {
            sprintf.cache[arguments[0]] = sprintf.parse(arguments[0]);
        }
        return sprintf.format.call(null, sprintf.cache[arguments[0]], arguments);
    };

    sprintf.format = function(parse_tree, argv) {
        var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
        for (i = 0; i < tree_length; i++) {
            node_type = get_type(parse_tree[i]);
            if (node_type === 'string') {
                output.push(parse_tree[i]);
            }
            else if (node_type === 'array') {
                match = parse_tree[i]; // convenience purposes only
                if (match[2]) { // keyword argument
                    arg = argv[cursor];
                    for (k = 0; k < match[2].length; k++) {
                        if (!arg.hasOwnProperty(match[2][k])) {
                            throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
                        }
                        arg = arg[match[2][k]];
                    }
                }
                else if (match[1]) { // positional argument (explicit)
                    arg = argv[match[1]];
                }
                else { // positional argument (implicit)
                    arg = argv[cursor++];
                }

                if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
                    throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
                }
                switch (match[8]) {
                    case 'b': arg = arg.toString(2); break;
                    case 'c': arg = String.fromCharCode(arg); break;
                    case 'd': arg = parseInt(arg, 10); break;
                    case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
                    case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
                    case 'o': arg = arg.toString(8); break;
                    case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
                    case 'u': arg = arg >>> 0; break;
                    case 'x': arg = arg.toString(16); break;
                    case 'X': arg = arg.toString(16).toUpperCase(); break;
                }
                arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
                pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
                pad_length = match[6] - String(arg).length;
                pad = match[6] ? str_repeat(pad_character, pad_length) : '';
                output.push(match[5] ? arg + pad : pad + arg);
            }
        }
        return output.join('');
    };

    sprintf.cache = {};

    sprintf.parse = function(fmt) {
        var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
        while (_fmt) {
            if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
                parse_tree.push(match[0]);
            }
            else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
                parse_tree.push('%');
            }
            else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
                if (match[2]) {
                    arg_names |= 1;
                    var field_list = [], replacement_field = match[2], field_match = [];
                    if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
                        field_list.push(field_match[1]);
                        while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
                            if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
                                field_list.push(field_match[1]);
                            }
                            else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
                                field_list.push(field_match[1]);
                            }
                            else {
                                throw('[sprintf] huh?');
                            }
                        }
                    }
                    else {
                        throw('[sprintf] huh?');
                    }
                    match[2] = field_list;
                }
                else {
                    arg_names |= 2;
                }
                if (arg_names === 3) {
                    throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
                }
                parse_tree.push(match);
            }
            else {
                throw('[sprintf] huh?');
            }
            _fmt = _fmt.substring(match[0].length);
        }
        return parse_tree;
    };

    var vsprintf = function(fmt, argv, _argv) {
        _argv = argv.slice(0);
        _argv.splice(0, 0, fmt);
        return sprintf.apply(null, _argv);
    };

    /**
     * helpers
     */
    function get_type(variable) {
        return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
    }

    function str_repeat(input, multiplier) {
        for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
        return output.join('');
    }

    /**
     * export to either browser or node.js
     */
    ctx.sprintf = sprintf;
    ctx.vsprintf = vsprintf;
})(typeof global != "undefined" ? global : window);
/* jshint ignore:end */
(function($, undefined) {
    "use strict";
    // -----------------------------------------------------------------------
    // :: map object to object
    // -----------------------------------------------------------------------
    $.omap = function(o, fn) {
        var result = {};
        $.each(o, function(k, v) {
            result[k] = fn.call(o, k, v);
        });
        return result;
    };
    var Clone = {
        clone_object: function(object) {
            var tmp = {};
            if (typeof object == 'object') {
                if ($.isArray(object)) {
                    return this.clone_array(object);
                } else if (object === null) {
                    return object;
                } else {
                    for (var key in object) {
                        if ($.isArray(object[key])) {
                            tmp[key] = this.clone_array(object[key]);
                        } else if (typeof object[key] == 'object') {
                            tmp[key] = this.clone_object(object[key]);
                        } else {
                            tmp[key] = object[key];
                        }
                    }
                }
            }
            return tmp;
        },
        clone_array: function(array) {
            if (!$.isFunction(Array.prototype.map)) {
                throw new Error("You'r browser don't support ES5 array map " +
                                "use es5-shim");
            }
            return array.slice(0).map(function(item) {
                if (typeof item == 'object') {
                    return this.clone_object(item);
                } else {
                    return item;
                }
            }.bind(this));
        }
    };
    var clone = function(object) {
        return Clone.clone_object(object);
    };

    var hasLS = function () {
      var testKey = 'test', storage = window.localStorage;
      try {
        storage.setItem(testKey, '1');
        storage.removeItem(testKey);
        return true;
      } catch (error) {
        return false;
      }
    };

    /* jshint ignore:start */
    // -----------------------------------------------------------------------
    // :: Storage plugin
    // -----------------------------------------------------------------------
    // Private data
    var isLS = hasLS();
    // Private functions
    function wls(n, v) {
        var c;
        if (typeof n === 'string' && typeof v === 'string') {
            localStorage[n] = v;
            return true;
        } else if (typeof n === 'object' && typeof v === 'undefined') {
            for (c in n) {
                if (n.hasOwnProperty(c)) {
                    localStorage[c] = n[c];
                }
            }
            return true;
        }
        return false;
    }
    function wc(n, v) {
        var dt, e, c;
        dt = new Date();
        dt.setTime(dt.getTime() + 31536000000);
        e = '; expires=' + dt.toGMTString();
        if (typeof n === 'string' && typeof v === 'string') {
            document.cookie = n + '=' + v + e + '; path=/';
            return true;
        } else if (typeof n === 'object' && typeof v === 'undefined') {
            for (c in n) {
                if (n.hasOwnProperty(c)) {
                    document.cookie = c + '=' + n[c] + e + '; path=/';
                }
            }
            return true;
        }
        return false;
    }
    function rls(n) {
        return localStorage[n];
    }
    function rc(n) {
        var nn, ca, i, c;
        nn = n + '=';
        ca = document.cookie.split(';');
        for (i = 0; i < ca.length; i++) {
            c = ca[i];
            while (c.charAt(0) === ' ') {
                c = c.substring(1, c.length);
            }
            if (c.indexOf(nn) === 0) {
                return c.substring(nn.length, c.length);
            }
        }
        return null;
    }
    function dls(n) {
        return delete localStorage[n];
    }
    function dc(n) {
        return wc(n, '', -1);
    }
    /**
    * Public API
    * $.Storage.set("name", "value")
    * $.Storage.set({"name1":"value1", "name2":"value2", etc})
    * $.Storage.get("name")
    * $.Storage.remove("name")
    */
    $.extend({
        Storage: {
            set: isLS ? wls : wc,
            get: isLS ? rls : rc,
            remove: isLS ? dls : dc
        }
    });
    // -----------------------------------------------------------------------
    // :: jQuery Timers
    // -----------------------------------------------------------------------
    var jQuery = $;
    jQuery.fn.extend({
        everyTime: function(interval, label, fn, times, belay) {
            return this.each(function() {
                jQuery.timer.add(this, interval, label, fn, times, belay);
            });
        },
        oneTime: function(interval, label, fn) {
            return this.each(function() {
                jQuery.timer.add(this, interval, label, fn, 1);
            });
        },
        stopTime: function(label, fn) {
            return this.each(function() {
                jQuery.timer.remove(this, label, fn);
            });
        }
    });

    jQuery.extend({
        timer: {
            guid: 1,
            global: {},
            regex: /^([0-9]+)\s*(.*s)?$/,
            powers: {
                // Yeah this is major overkill...
                'ms': 1,
                'cs': 10,
                'ds': 100,
                's': 1000,
                'das': 10000,
                'hs': 100000,
                'ks': 1000000
            },
            timeParse: function(value) {
                if (value === undefined || value === null) {
                    return null;
                }
                var result = this.regex.exec(jQuery.trim(value.toString()));
                if (result[2]) {
                    var num = parseInt(result[1], 10);
                    var mult = this.powers[result[2]] || 1;
                    return num * mult;
                } else {
                    return value;
                }
            },
            add: function(element, interval, label, fn, times, belay) {
                var counter = 0;

                if (jQuery.isFunction(label)) {
                    if (!times) {
                        times = fn;
                    }
                    fn = label;
                    label = interval;
                }

                interval = jQuery.timer.timeParse(interval);

                if (typeof interval !== 'number' ||
                    isNaN(interval) ||
                    interval <= 0) {
                    return;
                }
                if (times && times.constructor !== Number) {
                    belay = !!times;
                    times = 0;
                }

                times = times || 0;
                belay = belay || false;

                if (!element.$timers) {
                    element.$timers = {};
                }
                if (!element.$timers[label]) {
                    element.$timers[label] = {};
                }
                fn.$timerID = fn.$timerID || this.guid++;

                var handler = function() {
                    if (belay && handler.inProgress) {
                        return;
                    }
                    handler.inProgress = true;
                    if ((++counter > times && times !== 0) ||
                        fn.call(element, counter) === false) {
                        jQuery.timer.remove(element, label, fn);
                    }
                    handler.inProgress = false;
                };

                handler.$timerID = fn.$timerID;

                if (!element.$timers[label][fn.$timerID]) {
                    element.$timers[label][fn.$timerID] = window.setInterval(handler, interval);
                }

                if (!this.global[label]) {
                    this.global[label] = [];
                }
                this.global[label].push(element);

            },
            remove: function(element, label, fn) {
                var timers = element.$timers, ret;

                if (timers) {

                    if (!label) {
                        for (var lab in timers) {
                            if (timers.hasOwnProperty(lab)) {
                                this.remove(element, lab, fn);
                            }
                        }
                    } else if (timers[label]) {
                        if (fn) {
                            if (fn.$timerID) {
                                window.clearInterval(timers[label][fn.$timerID]);
                                delete timers[label][fn.$timerID];
                            }
                        } else {
                            for (var _fn in timers[label]) {
                                if (timers[label].hasOwnProperty(_fn)) {
                                    window.clearInterval(timers[label][_fn]);
                                    delete timers[label][_fn];
                                }
                            }
                        }

                        for (ret in timers[label]) {
                            if (timers[label].hasOwnProperty(ret)) {
                                break;
                            }
                        }
                        if (!ret) {
                            ret = null;
                            delete timers[label];
                        }
                    }

                    for (ret in timers) {
                        if (timers.hasOwnProperty(ret)) {
                            break;
                        }
                    }
                    if (!ret) {
                        element.$timers = null;
                    }
                }
            }
        }
    });

    if (/(msie) ([\w.]+)/.exec(navigator.userAgent.toLowerCase())) {
        jQuery(window).one('unload', function() {
            var global = jQuery.timer.global;
            for (var label in global) {
                if (global.hasOwnProperty(label)) {
                    var els = global[label], i = els.length;
                    while (--i) {
                        jQuery.timer.remove(els[i], label);
                    }
                }
            }
        });
    }
    // -----------------------------------------------------------------------
    // :: CROSS BROWSER SPLIT
    // -----------------------------------------------------------------------
    (function(undef) {

        // prevent double include

        if (!String.prototype.split.toString().match(/\[native/)) {
            return;
        }

        var nativeSplit = String.prototype.split,
        compliantExecNpcg = /()??/.exec("")[1] === undef, // NPCG: nonparticipating capturing group
        self;

        self = function (str, separator, limit) {
            // If `separator` is not a regex, use `nativeSplit`
            if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
                return nativeSplit.call(str, separator, limit);
            }
            var output = [],
            flags = (separator.ignoreCase ? "i" : "") +
                (separator.multiline  ? "m" : "") +
                (separator.extended   ? "x" : "") + // Proposed for ES6
                (separator.sticky     ? "y" : ""), // Firefox 3+
                lastLastIndex = 0,
            // Make `global` and avoid `lastIndex` issues by working with a copy
            separator2, match, lastIndex, lastLength;
            separator = new RegExp(separator.source, flags + "g");
            str += ""; // Type-convert
            if (!compliantExecNpcg) {
                // Doesn't need flags gy, but they don't hurt
                separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
            }
            /* Values for `limit`, per the spec:
             * If undefined: 4294967295 // Math.pow(2, 32) - 1
             * If 0, Infinity, or NaN: 0
             * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
             * If negative number: 4294967296 - Math.floor(Math.abs(limit))
             * If other: Type-convert, then use the above rules
             */
            // ? Math.pow(2, 32) - 1 : ToUint32(limit)
            limit = limit === undef ? -1 >>> 0 : limit >>> 0;
            while (match = separator.exec(str)) {
                    // `separator.lastIndex` is not reliable cross-browser
                    lastIndex = match.index + match[0].length;
                    if (lastIndex > lastLastIndex) {
                        output.push(str.slice(lastLastIndex, match.index));
                        // Fix browsers whose `exec` methods don't consistently return `undefined` for
                        // nonparticipating capturing groups
                        if (!compliantExecNpcg && match.length > 1) {
                            match[0].replace(separator2, function () {
                                for (var i = 1; i < arguments.length - 2; i++) {
                                    if (arguments[i] === undef) {
                                        match[i] = undef;
                                    }
                                }
                            });
                        }
                        if (match.length > 1 && match.index < str.length) {
                            Array.prototype.push.apply(output, match.slice(1));
                        }
                        lastLength = match[0].length;
                        lastLastIndex = lastIndex;
                        if (output.length >= limit) {
                            break;
                        }
                    }
                    if (separator.lastIndex === match.index) {
                        separator.lastIndex++; // Avoid an infinite loop
                    }
                }
            if (lastLastIndex === str.length) {
                if (lastLength || !separator.test("")) {
                    output.push("");
                }
            } else {
                output.push(str.slice(lastLastIndex));
            }
            return output.length > limit ? output.slice(0, limit) : output;
        };

        // For convenience
        String.prototype.split = function (separator, limit) {
            return self(this, separator, limit);
        };

        return self;

    })();
    // -----------------------------------------------------------------------
    // :: jQuery Caret
    // -----------------------------------------------------------------------
    $.fn.caret = function(pos) {
        var target = this[0];
        var isContentEditable = target.contentEditable === 'true';
        //get
        if (arguments.length == 0) {
            //HTML5
            if (window.getSelection) {
                //contenteditable
                if (isContentEditable) {
                    target.focus();
                    var range1 = window.getSelection().getRangeAt(0),
                    range2 = range1.cloneRange();
                    range2.selectNodeContents(target);
                    range2.setEnd(range1.endContainer, range1.endOffset);
                    return range2.toString().length;
                }
                //textarea
                return target.selectionStart;
            }
            //IE<9
            if (document.selection) {
                target.focus();
                //contenteditable
                if (isContentEditable) {
                    var range1 = document.selection.createRange(),
                    range2 = document.body.createTextRange();
                    range2.moveToElementText(target);
                    range2.setEndPoint('EndToEnd', range1);
                    return range2.text.length;
                }
                //textarea
                var pos = 0,
                range = target.createTextRange(),
                range2 = document.selection.createRange().duplicate(),
                bookmark = range2.getBookmark();
                range.moveToBookmark(bookmark);
                while (range.moveStart('character', -1) !== 0) pos++;
                return pos;
            }
            //not supported
            return 0;
        }
        //set
        if (pos == -1)
            pos = this[isContentEditable? 'text' : 'val']().length;
        //HTML5
        if (window.getSelection) {
            //contenteditable
            if (isContentEditable) {
                target.focus();
                window.getSelection().collapse(target.firstChild, pos);
            }
            //textarea
            else
                target.setSelectionRange(pos, pos);
        }
        //IE<9
        else if (document.body.createTextRange) {
            var range = document.body.createTextRange();
            range.moveToElementText(target);
            range.moveStart('character', pos);
            range.collapse(true);
            range.select();
        }
        if (!isContentEditable)
            target.focus();
        return pos;
    };
    /* jshint ignore:end */
    // -----------------------------------------------------------------------
    // :: Split string to array of strings with the same length
    // -----------------------------------------------------------------------
    function str_parts(str, length) {
        var result = [];
        var len = str.length;
        if (len < length) {
            return [str];
        } else if (length < 0) {
            throw new Error('str_parts: length can\'t be negative'); // '
        }
        for (var i = 0; i < len; i += length) {
            result.push(str.substring(i, i + length));
        }
        return result;
    }
    // -----------------------------------------------------------------------
    // :: CYCLE DATA STRUCTURE
    // -----------------------------------------------------------------------
    function Cycle(init) {
        var data = init ? [init] : [];
        var pos = 0;
        $.extend(this, {
            get: function() {
                return data;
            },
            rotate: function() {
                if (data.length === 1) {
                    return data[0];
                } else {
                    if (pos === data.length - 1) {
                        pos = 0;
                    } else {
                        ++pos;
                    }
                    if (data[pos]) {
                        return data[pos];
                    } else {
                        this.rotate();
                    }
                }
            },
            length: function() {
                return data.length;
            },
            remove: function(index) {
                delete data[index];
            },
            set: function(item) {
                for (var i = data.length; i--;) {
                    if (data[i] === item) {
                        pos = i;
                        return;
                    }
                }
                this.append(item);
            },
            front: function() {
                if (data.length) {
                    var index = pos;
                    while(!data[index]) {
                        index++;
                    }
                    return data[index];
                }
            },
            append: function(item) {
                data.push(item);
            }
        });
    }
    // -----------------------------------------------------------------------
    // :: STACK DATA STRUCTURE
    // -----------------------------------------------------------------------
    function Stack(init) {
        var data = init instanceof Array ? init : init ? [init] : [];
        $.extend(this, {
            map: function(fn) {
                return $.map(data, fn);
            },
            size: function() {
                return data.length;
            },
            pop: function() {
                if (data.length === 0) {
                    return null;
                } else {
                    var value = data[data.length - 1];
                    data = data.slice(0, data.length - 1);
                    return value;
                }
            },
            push: function(value) {
                data = data.concat([value]);
                return value;
            },
            top: function() {
                return data.length > 0 ? data[data.length - 1] : null;
            },
            clone: function() {
                return new Stack(data.slice(0));
            }
        });
    }
    // -------------------------------------------------------------------------
    // :: Serialize object myself (biwascheme or prototype library do something
    // :: wicked with JSON serialization for Arrays)
    // -------------------------------------------------------------------------
    $.json_stringify = function(object, level) {
        var result = '', i;
        level = level === undefined ? 1 : level;
        var type = typeof object;
        switch (type) {
            case 'function':
                result += object;
                break;
            case 'boolean':
                result += object ? 'true' : 'false';
                break;
            case 'object':
                if (object === null) {
                    result += 'null';
                } else if (object instanceof Array) {
                    result += '[';
                    var len = object.length;
                    for (i = 0; i < len - 1; ++i) {
                        result += $.json_stringify(object[i], level + 1);
                    }
                    result += $.json_stringify(object[len - 1], level + 1) + ']';
                } else {
                    result += '{';
                    for (var property in object) {
                        if (object.hasOwnProperty(property)) {
                            result += '"' + property + '":' +
                                $.json_stringify(object[property], level + 1);
                        }
                    }
                    result += '}';
                }
                break;
            case 'string':
                var str = object;
                var repl = {
                    '\\\\': '\\\\',
                    '"': '\\"',
                    '/': '\\/',
                    '\\n': '\\n',
                    '\\r': '\\r',
                    '\\t': '\\t'};
                for (i in repl) {
                    if (repl.hasOwnProperty(i)) {
                        str = str.replace(new RegExp(i, 'g'), repl[i]);
                    }
                }
                result += '"' + str + '"';
                break;
            case 'number':
                result += String(object);
                break;
        }
        result += (level > 1 ? ',' : '');
        // quick hacks below
        if (level === 1) {
            // fix last comma
            result = result.replace(/,([\]}])/g, '$1');
        }
        // fix comma before array or object
        return result.replace(/([\[{]),/g, '$1');
    };
    // -------------------------------------------------------------------------
    // :: HISTORY CLASS
    // -------------------------------------------------------------------------
    function History(name, size) {
        var enabled = true;
        var storage_key = '';
        if (typeof name === 'string' && name !== '') {
            storage_key = name + '_';
        }
        storage_key += 'commands';
        var data = $.Storage.get(storage_key);
        data = data ? $.parseJSON(data) : [];
        var pos = data.length-1;
        $.extend(this, {
            append: function(item) {
                if (enabled) {
                    if (data[data.length-1] !== item) {
                        data.push(item);
                        if (size && data.length > size) {
                            data = data.slice(-size);
                        }
                        pos = data.length-1;
                        $.Storage.set(storage_key, $.json_stringify(data));
                    }
                }
            },
            data: function() {
                return data;
            },
            reset: function() {
                pos = data.length-1;
            },
            last: function() {
                return data[length-1];
            },
            end: function() {
                return pos === data.length-1;
            },
            position: function() {
                return pos;
            },
            current: function() {
                return data[pos];
            },
            next: function() {
                if (pos < data.length-1) {
                    ++pos;
                }
                if (pos !== -1) {
                    return data[pos];
                }
            },
            previous: function() {
                var old = pos;
                if (pos > 0) {
                    --pos;
                }
                if (old !== -1) {
                    return data[pos];
                }
            },
            clear: function() {
                data = [];
                this.purge();
            },
            enabled: function() {
                return enabled;
            },
            enable: function() {
                enabled = true;
            },
            purge: function() {
                $.Storage.remove(storage_key);
            },
            disable: function() {
                enabled = false;
            }
        });
    }
    // -----------------------------------------------------------------------
    var is_paste_supported = (function() {
        var el = document.createElement('div');
        el.setAttribute('onpaste', 'return;');
        return typeof el.onpaste == "function";
    })();
    var first_cmd = true;
    // -------------------------------------------------------------------------
    // :: COMMAND LINE PLUGIN
    // -------------------------------------------------------------------------
    $.fn.cmd = function(options) {
        var self = this;
        var maybe_data = self.data('cmd');
        if (maybe_data) {
            return maybe_data;
        }
        self.addClass('cmd');
        self.append('<span class="prompt"></span><span></span>' +
                    '<span class="cursor">&nbsp;</span><span></span>');
        // on mobile the only way to hide textarea on desktop it's needed because
        // textarea show up after focus
        //self.append('<span class="mask"></mask>');
        var clip = $('<textarea />').addClass('clipboard').appendTo(self);
        if (options.width) {
            self.width(options.width);
        }
        var num_chars; // calculated by draw_prompt
        var prompt_len;
        var prompt_node = self.find('.prompt');
        var reverse_search = false;
        var rev_search_str = '';
        var reverse_search_position = null;
        var backup_prompt;
        var mask = options.mask || false;
        var command = '';
        var last_command;
        // text from selection using CTRL+SHIFT+C (as in Xterm)
        var selected_text = '';
        var kill_text = ''; // text from command that kill part of the command
        var position = 0;
        var prompt;
        var enabled;
        var historySize = options.historySize || 60;
        var name, history;
        var cursor = self.find('.cursor');
        var animation;
        function mobile_focus() {
            if (is_touch) {
                var focus = clip.is(':focus');
                if (enabled) {
                    if (!focus) {
                        clip.focus();
                        self.oneTime(10, function() {
                            clip.focus();
                        });
                    }
                } else {
                    if (focus) {
                        clip.blur();
                    }
                }
            }
        }
        // on mobile you can't delete character if input is empty (event
        // will not fire) so we fake text entry, we could just put dummy
        // data but we put real command and position
        function fake_mobile_entry() {
            if (is_touch) {
                // delay worked while experimenting
                self.oneTime(10, function() {
                    clip.val(command);
                    self.oneTime(10, function() {
                        clip.caret(position);
                    });
                });
            }
        }
        // terminal animation don't work on andorid because they animate
        // 2 properties
        if ((support_animations && !is_android)) {
            animation = function(toggle) {
                if (toggle) {
                    cursor.addClass('blink');
                } else {
                    cursor.removeClass('blink');
                }
            };
        } else {
            var animating = false;
            animation = function(toggle) {
                if (toggle && !animating) {
                    animating = true;
                    cursor.addClass('inverted');
                    self.everyTime(500, 'blink', blink);
                } else if (animating && !toggle) {
                    animating = false;
                    self.stopTime('blink', blink);
                    cursor.removeClass('inverted');
                }
            };
        }
        // ---------------------------------------------------------------------
        // :: Blinking cursor function
        // ---------------------------------------------------------------------
        function blink(i) {
            cursor.toggleClass('inverted');
        }
        // ---------------------------------------------------------------------
        // :: Set prompt for reverse search
        // ---------------------------------------------------------------------
        function draw_reverse_prompt() {
            prompt = "(reverse-i-search)`" + rev_search_str + "': ";
            draw_prompt();
        }
        // ---------------------------------------------------------------------
        // :: Disable reverse search
        // ---------------------------------------------------------------------
        function clear_reverse_state() {
            prompt = backup_prompt;
            reverse_search = false;
            reverse_search_position = null;
            rev_search_str = '';
        }
        // ---------------------------------------------------------------------
        // :: Search through command line history. If next is not defined or
        // :: false it searches for the first item from the end. If true it
        // :: search for the next item
        // ---------------------------------------------------------------------
        function reverse_history_search(next) {
            var history_data = history.data();
            var regex, save_string;
            var len = history_data.length;
            if (next && reverse_search_position > 0) {
                len -= reverse_search_position;
            }
            if (rev_search_str.length > 0) {
                for (var j=rev_search_str.length; j>0; j--) {
                    save_string = $.terminal.escape_regex(rev_search_str.substring(0, j));
                    regex = new RegExp(save_string);
                    for (var i=len; i--;) {
                        if (regex.test(history_data[i])) {
                            reverse_search_position = history_data.length - i;
                            self.position(history_data[i].indexOf(save_string));
                            self.set(history_data[i], true);
                            redraw();
                            if (rev_search_str.length !== j) {
                                rev_search_str = rev_search_str.substring(0, j);
                                draw_reverse_prompt();
                            }
                            return;
                        }
                    }
                }
            }
            rev_search_str = ''; // clear if not found any
        }
        // ---------------------------------------------------------------------
        // :: Recalculate number of characters in command line
        // ---------------------------------------------------------------------
        function change_num_chars() {
            var W = self.width();
            var w = cursor.width();
            num_chars = Math.floor(W / w);
        }
        // ---------------------------------------------------------------------
        // :: Split String that fit into command line where first line need to
        // :: fit next to prompt (need to have less characters)
        // ---------------------------------------------------------------------
        function get_splited_command_line(string) {
            var first = string.substring(0, num_chars - prompt_len);
            var rest = string.substring(num_chars - prompt_len);
            return [first].concat(str_parts(rest, num_chars));
        }
        // ---------------------------------------------------------------------
        // :: Function that displays the command line. Split long lines and
        // :: place cursor in the right place
        // ---------------------------------------------------------------------
        var redraw = (function(self) {
            var before = cursor.prev();
            var after = cursor.next();
            // -----------------------------------------------------------------
            // :: Draw line with the cursor
            // -----------------------------------------------------------------
            function draw_cursor_line(string, position) {
                var len = string.length;
                if (position === len) {
                    before.html($.terminal.encode(string));
                    cursor.html('&nbsp;');
                    after.html('');
                } else if (position === 0) {
                    before.html('');
                    //fix for tilda in IE
                    cursor.html($.terminal.encode(string.slice(0, 1)));
                    //cursor.html($.terminal.encode(string[0]));
                    after.html($.terminal.encode(string.slice(1)));
                } else {
                    var before_str = string.slice(0, position);
                    before.html($.terminal.encode(before_str));
                    //fix for tilda in IE
                    var c = string.slice(position, position + 1);
                    //cursor.html(string[position]);
                    cursor.html($.terminal.encode(c));
                    if (position === string.length - 1) {
                        after.html('');
                    } else {
                        after.html($.terminal.encode(string.slice(position + 1)));
                    }
                }
            }
            function div(string) {
                return '<div>' + $.terminal.encode(string) + '</div>';
            }
            // -----------------------------------------------------------------
            // :: Display lines after the cursor
            // -----------------------------------------------------------------
            function lines_after(lines) {
                var last_ins = after;
                $.each(lines, function(i, line) {
                    last_ins = $(div(line)).insertAfter(last_ins).
                        addClass('clear');
                });
            }
            // -----------------------------------------------------------------
            // :: Display lines before the cursor
            // -----------------------------------------------------------------
            function lines_before(lines) {
                $.each(lines, function(i, line) {
                    before.before(div(line));
                });
            }
            var count = 0;
            // -----------------------------------------------------------------
            // :: Redraw function
            // -----------------------------------------------------------------
            return function() {
                var string;
                var str; // max 80 line helper
                switch(typeof mask) {
                    case 'boolean':
                        string = mask ? command.replace(/./g, '*') : command;
                        break;
                    case 'string':
                        string = command.replace(/./g, mask);
                        break;
                }
                var i, first_len;
                self.find('div').remove();
                before.html('');
                // long line
                if (string.length > num_chars - prompt_len - 1 ||
                    string.match(/\n/)) {
                    var array;
                    var tabs = string.match(/\t/g);
                    var tabs_rm = tabs ? tabs.length * 3 : 0;
                    //quick tabulation hack
                    if (tabs) {
                        string = string.replace(/\t/g, '\x00\x00\x00\x00');
                    }
                    // command contains new line characters
                    if (string.match(/\n/)) {
                        var tmp = string.split("\n");
                        first_len = num_chars - prompt_len - 1;
                        // empty character after each line
                        for (i=0; i<tmp.length-1; ++i) {
                            tmp[i] += ' ';
                        }
                        // split first line
                        if (tmp[0].length > first_len) {
                            array = [tmp[0].substring(0, first_len)];
                            str = tmp[0].substring(first_len);
                            array = array.concat(str_parts(str, num_chars));
                        } else {
                            array = [tmp[0]];
                        }
                        // process rest of the lines
                        for (i=1; i<tmp.length; ++i) {
                            if (tmp[i].length > num_chars) {
                                array = array.concat(str_parts(tmp[i],
                                                               num_chars));
                            } else {
                                array.push(tmp[i]);
                            }
                        }
                    } else {
                        array = get_splited_command_line(string);
                    }
                    if (tabs) {
                        array = $.map(array, function(line) {
                            return line.replace(/\x00\x00\x00\x00/g, '\t');
                        });
                    }
                    first_len = array[0].length;
                    //cursor in first line
                    if (first_len === 0 && array.length === 1) {
                        // skip empty line
                    } else if (position < first_len) {
                        draw_cursor_line(array[0], position);
                        lines_after(array.slice(1));
                    } else if (position === first_len) {
                        before.before(div(array[0]));
                        draw_cursor_line(array[1], 0);
                        lines_after(array.slice(2));
                    } else {
                        var num_lines = array.length;
                        var offset = 0;
                        if (position < first_len) {
                            draw_cursor_line(array[0], position);
                            lines_after(array.slice(1));
                        } else if (position === first_len) {
                            before.before(div(array[0]));
                            draw_cursor_line(array[1], 0);
                            lines_after(array.slice(2));
                        } else {
                            var last = array.slice(-1)[0];
                            var from_last = string.length - position - tabs_rm;
                            var last_len = last.length;
                            var pos = 0;
                            if (from_last <= last_len) {
                                lines_before(array.slice(0, -1));
                                if (last_len === from_last) {
                                    pos = 0;
                                } else {
                                    pos = last_len-from_last;
                                }
                                draw_cursor_line(last, pos);
                            } else {
                                // in the middle
                                if (num_lines === 3) {
                                    str = $.terminal.encode(array[0]);
                                    before.before('<div>' + str + '</div>');
                                    draw_cursor_line(array[1],
                                                     position-first_len-1);
                                    str = $.terminal.encode(array[2]);
                                    after.after('<div class="clear">' + str +
                                                '</div>');
                                } else {
                                    // more lines, cursor in the middle
                                    var line_index;
                                    var current;
                                    pos = position;
                                    for (i=0; i<array.length; ++i) {
                                        var current_len = array[i].length;
                                        if (pos > current_len) {
                                            pos -= current_len;
                                        } else {
                                            break;
                                        }
                                    }
                                    current = array[i];
                                    line_index = i;
                                    // cursor on first character in line
                                    if (pos === current.length) {
                                        pos = 0;
                                        current = array[++line_index];
                                    }
                                    draw_cursor_line(current, pos);
                                    lines_before(array.slice(0, line_index));
                                    lines_after(array.slice(line_index+1));
                                }
                            }
                        }
                    }
                } else {
                     if (string === '') {
                         before.html('');
                         cursor.html('&nbsp;');
                         after.html('');
                     } else {
                         draw_cursor_line(string, position);
                     }
                }
            };
        })(self);
        // ---------------------------------------------------------------------
        // :: Draw prompt that can be a function or a string
        // ---------------------------------------------------------------------
        var draw_prompt = (function() {
            function set(prompt) {
                prompt_node.html($.terminal.format($.terminal.encode(prompt)));
                prompt_len = prompt_node.text().length;
            }
            return function() {
                switch (typeof prompt) {
                    case 'string':
                        set(prompt);
                        break;
                    case 'function':
                        prompt(set);
                        break;
                }
            };
        })();
        // ---------------------------------------------------------------------
        // :: Paste content to terminal using hidden textarea
        // ---------------------------------------------------------------------
        function paste(e) {
            e = e.originalEvent;
            if (self.isenabled()) {
                var clip = self.find('textarea');
                if (!clip.is(':focus')) {
                    clip.focus();
                }
                var text;
                if (window.clipboardData && window.clipboardData.getData) { // IE
                    text = window.clipboardData.getData('Text');
                } else if (e.clipboardData && e.clipboardData.getData) {
                    text = e.clipboardData.getData('text/plain');
                } else {
                    //wait until Browser insert text to textarea
                    cmd.oneTime(100, function() {
                        self.insert(clip.val());
                        clip.val('');
                        fake_mobile_entry();
                    });
                }
                if (text) {
                    self.insert(text);
                    clip.val('');
                    fake_mobile_entry();
                }
            }
        }
        var first_up_history = true;
        // prevent_keypress - hack for Android that was inserting characters on
        // backspace
        var prevent_keypress = false;
        var no_keypress;
        // ---------------------------------------------------------------------
        // :: Keydown Event Handler
        // ---------------------------------------------------------------------
        function keydown_event(e) {
            var result, pos, len;
            if ($.isFunction(options.keydown)) {
              result = options.keydown(e);
              if (result !== undefined) {
                  //prevent_keypress = true;
                  return result;
              }
            }
            if (enabled) {
                if (e.which !== 38 &&
                    !(e.which === 80 && e.ctrlKey)) {
                    first_up_history = true;
                }
                // arrows / Home / End / ENTER
                if (reverse_search && (e.which === 35 || e.which === 36 ||
                                       e.which === 37 || e.which === 38 ||
                                       e.which === 39 || e.which === 40 ||
                                       e.which === 13 || e.which === 27)) {
                    clear_reverse_state();
                    draw_prompt();
                    if (e.which === 27) { // ESC
                        self.set('');
                    }
                    redraw();
                    // finish reverse search and execute normal event handler
                    /* jshint validthis:true */
                    keydown_event.call(this, e);
                } else if (e.altKey) {
                    // Chrome on Windows sets ctrlKey and altKey for alt
                    // need to check for alt first
                    //if (e.which === 18) { // press ALT
                    if (e.which === 68) { //ALT+D
                        self.set(command.slice(0, position) +
                                 command.slice(position).
                                 replace(/ *[^ ]+ *(?= )|[^ ]+$/, ''), true);
                        // chrome jump to address bar
                        return false;
                    }
                    return true;
                } else if (e.keyCode === 13) { //enter
                    if (e.shiftKey) {
                        self.insert('\n');
                    } else {
                        if (history && command && !mask &&
                            ($.isFunction(options.historyFilter) &&
                             options.historyFilter(command)) ||
                            (options.historyFilter instanceof RegExp &&
                             command.match(options.historyFilter)) ||
                            !options.historyFilter) {
                            history.append(command);
                        }
                        var tmp = command;
                        history.reset();
                        self.set('');
                        if (options.commands) {
                            options.commands(tmp);
                        }
                        if ($.isFunction(prompt)) {
                            draw_prompt();
                        }
                    }
                } else if (e.which === 8) { //backspace
                    if (reverse_search) {
                        rev_search_str = rev_search_str.slice(0, -1);
                        draw_reverse_prompt();
                    } else {
                        if (command !== '' && position > 0) {
                            self['delete'](-1);
                        }
                    }
                    if (is_touch) {
                        return true; // mobile fix
                    }
                } else if (e.which === 67 && e.ctrlKey && e.shiftKey) {
                    // CTRL+SHIFT+C
                    selected_text = get_selected_text();
                } else if (e.which === 86 && e.ctrlKey && e.shiftKey) {
                    if (selected_text !== '') {
                        self.insert(selected_text);
                    }
                } else if (e.which === 9 && !(e.ctrlKey || e.altKey)) { // TAB
                    self.insert('\t');
                } else if (e.which === 46) {
                    //DELETE
                    self['delete'](1);
                    return;
                } else if (history && (e.which === 38 && !e.ctrlKey) ||
                           (e.which === 80 && e.ctrlKey)) {
                    //UP ARROW or CTRL+P
                    if (first_up_history) {
                        last_command = command;
                        self.set(history.current());
                    } else {
                        self.set(history.previous());
                    }
                    first_up_history = false;
                } else if (history && (e.which === 40 && !e.ctrlKey) ||
                           (e.which === 78 && e.ctrlKey)) {
                    //DOWN ARROW or CTRL+N
                    self.set(history.end() ? last_command : history.next());
                } else if (e.which === 37 || (e.which === 66 && e.ctrlKey)) {
                    //CTRL+LEFT ARROW or CTRL+B
                    if (e.ctrlKey && e.which !== 66) {
                        len = position - 1;
                        pos = 0;
                        if (command[len] === ' ') {
                            --len;
                        }
                        for (var i = len; i > 0; --i) {
                            if (command[i] === ' ' && command[i+1] !== ' ') {
                                pos = i + 1;
                                break;
                            } else if (command[i] === '\n' &&
                                       command[i+1] !== '\n') {
                                pos = i;
                                break;
                            }
                        }
                        self.position(pos);
                    } else {
                        //LEFT ARROW or CTRL+B
                        if (position > 0) {
                            self.position(-1, true);
                            redraw();
                        }
                    }
                } else if (e.which === 82 && e.ctrlKey) { // CTRL+R
                    if (reverse_search) {
                        reverse_history_search(true);
                    } else {
                        backup_prompt = prompt;
                        draw_reverse_prompt();
                        last_command = command;
                        self.set('');
                        redraw();
                        reverse_search = true;
                    }
                } else if (e.which == 71 && e.ctrlKey) { // CTRL+G
                    if (reverse_search) {
                        prompt = backup_prompt;
                        draw_prompt();
                        self.set(last_command);
                        redraw();
                        reverse_search = false;
                        rev_search_str = '';
                    }
                } else if (e.which === 39 ||
                           (e.which === 70 && e.ctrlKey)) {
                    //RIGHT ARROW OR CTRL+F
                    if (e.ctrlKey && e.which !== 70) {
                        // jump to beginning or end of the word
                        if (command[position] === ' ') {
                            ++position;
                        }
                        var re = /\S[\n\s]{2,}|[\n\s]+\S?/;
                        var match = command.slice(position).match(re);
                        if (!match || match[0].match(/^\s+$/)) {
                            self.position(command.length);
                        } else {
                            if (match[0][0] !== ' ') {
                                position += match.index + 1;
                            } else {
                                position += match.index + match[0].length - 1;
                                if (match[0][match[0].length-1] !== ' ') {
                                    --position;
                                }
                            }
                        }
                        redraw();
                    } else {
                        if (position < command.length) {
                            self.position(1, true);
                        }
                    }
                } else if (e.which === 123) { // F12 - Allow Firebug
                    return;
                } else if (e.which === 36) { // HOME
                    self.position(0);
                } else if (e.which === 35) { // END
                    self.position(command.length);
                } else if (e.shiftKey && e.which == 45) { // Shift+Insert
                    clip.val(''); // so we get it before paste event
                    if (!is_paste_supported) {
                        paste();
                    } else {
                        clip.focus();
                    }
                    return;
                } else if (e.ctrlKey || e.metaKey) {
                    if (e.which === 192) { // CMD+` switch browser window on Mac
                        return;
                    }
                    if (e.metaKey) {
                        if(e.which === 82) { // CMD+r page reload in Chrome Mac
                            return;
                        } else if(e.which === 76) {
                            // CMD+l jump into Omnibox on Chrome Mac
                            return;
                        }
                    }
                    if (e.shiftKey) { // CTRL+SHIFT+??
                        if (e.which === 84) {
                            //CTRL+SHIFT+T open closed tab
                            return;
                        }
                    //} else if (e.altKey) { //ALT+CTRL+??
                    } else {
                        if (e.which === 81) { // CTRL+W
                            // don't work in Chromium (can't prevent close tab)
                            if (command !== '' && position !== 0) {
                                var m = command.slice(0, position).match(/([^ ]+ *$)/);
                                kill_text = self['delete'](-m[0].length);
                            }
                            return false;
                        } else if (e.which === 72) { // CTRL+H
                            if (command !== '' && position > 0) {
                                self['delete'](-1);
                            }
                            return false;
                        //NOTE: in opera charCode is undefined
                        } else if (e.which === 65) {
                            //CTRL+A
                            self.position(0);
                        } else if (e.which === 69) {
                            //CTRL+E
                            self.position(command.length);
                        } else if (e.which === 88 || e.which === 67 ||
                                   e.which === 84) {
                            //CTRL+X CTRL+C CTRL+W CTRL+T
                            return;
                        } else if (e.which === 89) { // CTRL+Y
                            if (kill_text !== '') {
                                self.insert(kill_text);
                            }
                        } else if (e.which === 86) { // CTRL+V
                            clip.val('');
                            if (!is_paste_supported) {
                                paste();
                            } else {
                                clip.focus();
                            }
                            return;
                        } else if (e.which === 75) { // CTRL+K
                            kill_text = self['delete'](command.length-position);
                        } else if (e.which === 85) { // CTRL+U
                            if (command !== '' && position !== 0) {
                                kill_text = self['delete'](-position);
                            }
                        } else if (e.which === 17) { //CTRL+TAB switch tab
                            return false;
                        }
                    }
                } else {
                    prevent_keypress = false;
                    no_keypress = true;
                    return;
                }
                // this will prevent for instance backspace to go back one page
                //prevent_keypress = true;
                e.preventDefault();
            }
        }
        function fire_change_command() {
            if ($.isFunction(options.onCommandChange)) {
                options.onCommandChange(command);
            }
        }
        // ---------------------------------------------------------------------
        // :: Command Line Methods
        // ---------------------------------------------------------------------
        $.extend(self, {
            name: function(string) {
                if (string !== undefined) {
                    name = string;
                    var enabled = history && history.enabled() || !history;
                    history = new History(string, historySize);
                    // disable new history if old was disabled
                    if (!enabled) {
                        history.disable();
                    }
                    return self;
                } else {
                    return name;
                }
            },
            purge: function() {
                history.clear();
                return self;
            },
            history: function() {
                return history;
            },
            'delete': function(n, stay) {
                var removed;
                if (n === 0) {
                    return self;
                } else if (n < 0) {
                    if (position > 0) {
                        // this may look weird but if n is negative we need
                        // to use +
                        removed = command.slice(0, position).slice(n);
                        command = command.slice(0, position + n) +
                            command.slice(position, command.length);
                        if (!stay) {
                            self.position(position+n);
                        } else {
                            fire_change_command();
                        }
                    }
                } else {
                    if (command !== '' && position < command.length) {
                        removed = command.slice(position).slice(0, n);
                        command = command.slice(0, position) +
                            command.slice(position + n, command.length);
                        fire_change_command();
                    }
                }
                redraw();
                fake_mobile_entry();
                return removed;
            },
            set: function(string, stay) {
                if (string !== undefined) {
                    command = string;
                    if (!stay) {
                        self.position(command.length);
                    }
                    redraw();
                    fake_mobile_entry();
                    fire_change_command();
                }
                return self;
            },
            insert: function(string, stay) {
                if (position === command.length) {
                    command += string;
                } else if (position === 0) {
                    command = string + command;
                } else {
                    command = command.slice(0, position) +
                        string + command.slice(position);
                }
                if (!stay) {
                    self.position(string.length, true);
                } else {
                    fake_mobile_entry();
                }
                redraw();
                fire_change_command();
                return self;
            },
            get: function() {
                return command;
            },
            commands: function(commands) {
                if (commands) {
                    options.commands = commands;
                    return self;
                } else {
                    return commands;
                }
            },
            destroy: function() {
                doc.unbind('keypress.cmd', keypress_event);
                doc.unbind('keydown.cmd', keydown_event);
                doc.unbind('paste.cmd', paste);
                doc.unbind('input.cmd', input);
                self.stopTime('blink', blink);
                self.find('.cursor').next().remove().end().prev().remove().
                    end().remove();
                self.find('.prompt, .clipboard').remove();
                self.removeClass('cmd').removeData('cmd');
                return self;
            },
            prompt: function(user_prompt) {
                if (user_prompt === undefined) {
                    return prompt;
                } else {
                    if (typeof user_prompt === 'string' ||
                        typeof user_prompt === 'function') {
                        prompt = user_prompt;
                    } else {
                        throw new Error('prompt must be a function or string');
                    }
                    draw_prompt();
                    // we could check if command is longer then numchars-new
                    // prompt
                    redraw();
                    return self;
                }
            },
            interrupt: function() {
                settings.interrupt();
            },
            kill_text: function() {
                return kill_text;
            },
            position: function(n, relative) {
                if (typeof n === 'number') {
                    // if (position !== n) { this don't work, don't know why
                    if (relative) {
                        position += n;
                    } else {
                        if (n < 0) {
                            position = 0;
                        } else if (n > command.length) {
                            position = command.length;
                        } else {
                            position = n;
                        }
                    }
                    if ($.isFunction(options.onPositionChange)) {
                        options.onPositionChange(position);
                    }
                    redraw();
                    fake_mobile_entry();
                    return self;
                } else {
                    return position;
                }
            },
            visible: (function() {
                var visible = self.visible;
                return function() {
                    visible.apply(self, []);
                    redraw();
                    draw_prompt();
                };
            })(),
            show: (function() {
                var show = self.show;
                return function() {
                    show.apply(self, []);
                    redraw();
                    draw_prompt();
                };
            })(),
            resize: function(num) {
                if (num) {
                    num_chars = num;
                } else {
                    change_num_chars();
                }
                redraw();
                return self;
            },
            enable: function() {
                enabled = true;
                self.addClass('enabled');
                animation(true);
                mobile_focus();
                return self;
            },
            isenabled: function() {
                return enabled;
            },
            disable: function() {
                enabled = false;
                self.removeClass('enabled');
                animation(false);
                mobile_focus();
                return self;
            },
            mask: function(new_mask) {
                if (typeof new_mask === 'undefined') {
                    return mask;
                } else {
                    mask = new_mask;
                    redraw();
                    return self;
                }
            }
        });
        // ---------------------------------------------------------------------
        // :: INIT
        // ---------------------------------------------------------------------
        self.name(options.name || options.prompt || '');
        if (typeof options.prompt == 'string') {
            prompt = options.prompt;
        } else {
            prompt = '> ';
        }
        draw_prompt();
        if (options.enabled === undefined || options.enabled === true) {
            self.enable();
        }
        // Keystrokes
        var object;
        var doc = $(document.documentElement || window);
        function keypress_event(e) {
            var result;
            no_keypress = false;
            if (e.ctrlKey && e.which === 99) { // CTRL+C
                return;
            }
            if (prevent_keypress) {
                return;
            }
            if (!reverse_search && $.isFunction(options.keypress)) {
                result = options.keypress(e);
            }
            //$.terminal.active().echo(JSON.stringify(result));
            if (result === undefined || result) {
                if (enabled) {
                    if ($.inArray(e.which, [38, 13, 0, 8]) > -1 &&
                        //!(e.which === 40 && e.shiftKey ||
                        !(e.which === 38 && e.shiftKey)) {
                        if (e.keyCode == 123) { // for F12 which == 0
                            return;
                        }
                        return false;
                    } else if (!e.ctrlKey && !(e.altKey && e.which === 100) ||
                               e.altKey) { // ALT+D
                        if (reverse_search) {
                            rev_search_str += String.fromCharCode(e.which);
                            reverse_history_search();
                            draw_reverse_prompt();
                        } else {
                            self.insert(String.fromCharCode(e.which));
                        }
                        return false;
                    }
                }
            } else {
                return result;
            }
        }
        function input(e) {
            if (no_keypress) {
                // Some Androids don't fire keypress - #39
                var val = clip.val();
                if (val || e.which == 8) {  // #209 ; 8 - backspace
                    self.set(val);
                }
            }
        }
        doc.bind('keypress.cmd', keypress_event).bind('keydown.cmd', keydown_event).
            bind('input.cmd', input);
        if (is_paste_supported) {
            doc.bind('paste.cmd', paste);
        }
        // characters
        self.data('cmd', self);
        return self;
    }; // cmd plugin

    // -------------------------------------------------------------------------
    // :: TOOLS
    // -------------------------------------------------------------------------
    function skip_formatting_count(string) {
        // this will covert html entities to single characters
        return $('<div>' + $.terminal.strip(string) + '</div>').text().length;
    }
    // -------------------------------------------------------------------------
    function formatting_count(string) {
        return string.length - skip_formatting_count(string);
    }
    // -------------------------------------------------------------------------
    // taken from https://hacks.mozilla.org/2011/09/detecting-and-generating-
    // css-animations-in-javascript/
    var support_animations = (function() {
        var animation = false,
        animationstring = 'animation',
        keyframeprefix = '',
        domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
        pfx  = '',
        elm = document.createElement('div');
        if (elm.style.animationName) { animation = true; }
        if (animation === false) {
            for (var i = 0; i < domPrefixes.length; i++) {
                var name = domPrefixes[i] + 'AnimationName';
                if (elm.style[ name ] !== undefined) {
                    pfx = domPrefixes[i];
                    animationstring = pfx + 'Animation';
                    keyframeprefix = '-' + pfx.toLowerCase() + '-';
                    animation = true;
                    break;
                }
            }
        }
        return animation;
    })();
    // -------------------------------------------------------------------------
    var is_android = navigator.userAgent.toLowerCase().indexOf("android") != -1;
    // -------------------------------------------------------------------------
    var is_touch = (function() {
        return ('ontouchstart' in window) || window.DocumentTouch &&
            document instanceof DocumentTouch;
    })();
    // -------------------------------------------------------------------------
    function process_command(string, fn) {
        var array = fn(string);
        if (array.length) {
            var name = array.shift();
            var regex = new RegExp('^' + $.terminal.escape_regex(name));
            var rest = string.replace(regex, '').trim();
            return {
                command: string,
                name: name,
                args: array,
                rest: rest
            };
        } else {
            return {
                command: string,
                name: '',
                args: [],
                rest: ''
            };
        }
    }
    // -------------------------------------------------------------------------
    var format_split_re = /(\[\[[!gbiuso]*;[^;]*;[^\]]*\](?:[^\]]*\\\][^\]]*|[^\]]*|[^\[]*\[[^\]]*)\]?)/i;
    var format_parts_re = /\[\[([!gbiuso]*);([^;]*);([^;\]]*);?([^;\]]*);?([^\]]*)\]([^\]]*\\\][^\]]*|[^\]]*|[^\[]*\[[^\]]*)\]?/gi;
    var format_re = /\[\[([!gbiuso]*;[^;\]]*;[^;\]]*(?:;|[^\]()]*);?[^\]]*)\]([^\]]*\\\][^\]]*|[^\]]*|[^\[]*\[[^\]]*)\]?/gi;
    var format_exist_re = /\[\[([!gbiuso]*;[^;\]]*;[^;\]]*(?:;|[^\]()]*);?[^\]]*)\]([^\]]*\\\][^\]]*|[^\]]*|[^\[]*\[[^\]]*)\]/gi;
    var format_full_re = /^\[\[([!gbiuso]*;[^;\]]*;[^;\]]*(?:;|[^\]()]*);?[^\]]*)\]([^\]]*\\\][^\]]*|[^\]]*|[^\[]*\[[^\]]*)\]$/gi;
    var color_hex_re = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
    //var url_re = /https?:\/\/(?:(?!&[^;]+;)[^\s:"'<>)])+/g;
    //var url_re = /\bhttps?:\/\/(?:(?!&[^;]+;)[^\s"'<>)])+\b/g;
    var url_re = /(\bhttps?:\/\/(?:(?:(?!&[^;]+;)|(?=&amp;))[^\s"'<>\]\[)])+\b)/gi;
    var url_nf_re = /\b(https?:\/\/(?:(?:(?!&[^;]+;)|(?=&amp;))[^\s"'<>\][)])+)\b(?![^[\]]*])/gi;
    var email_re = /((([^<>('")[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,})))/g;
    var command_re = /('[^']*'|"(\\"|[^"])*"|(?:\/(\\\/|[^\/])+\/[gimy]*)(?=:? |$)|(\\ |[^ ])+|[\w-]+)/gi;
    var format_begin_re = /(\[\[[!gbiuso]*;[^;]*;[^\]]*\])/i;
    var format_start_re = /^(\[\[[!gbiuso]*;[^;]*;[^\]]*\])/i;
    var format_last_re = /\[\[[!gbiuso]*;[^;]*;[^\]]*\]?$/i;
    var format_exec_re = /(\[\[(?:[^\]]|\\\])*\]\])/;
    $.terminal = {
        version: '0.10.12',
        // colors from http://www.w3.org/wiki/CSS/Properties/color/keywords
        color_names: [
            'black', 'silver', 'gray', 'white', 'maroon', 'red', 'purple',
            'fuchsia', 'green', 'lime', 'olive', 'yellow', 'navy', 'blue',
            'teal', 'aqua', 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine',
            'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue',
            'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse',
            'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson',
            'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray',
            'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta',
            'darkolivegreen', 'darkorange', 'darkorchid', 'darkred',
            'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray',
            'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink',
            'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick',
            'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite',
            'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'grey',
            'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki',
            'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon',
            'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow',
            'lightgray', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon',
            'lightseagreen', 'lightskyblue', 'lightslategray', 'lightslategrey',
            'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen',
            'magenta', 'maroon', 'mediumaquamarine', 'mediumblue',
            'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue',
            'mediumspringgreen', 'mediumturquoise', 'mediumvioletred',
            'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite',
            'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered',
            'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise',
            'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum',
            'powderblue', 'purple', 'red', 'rosybrown', 'royalblue',
            'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell',
            'sienna', 'silver', 'skyblue', 'slateblue', 'slategray',
            'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', 'teal',
            'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white',
            'whitesmoke', 'yellow', 'yellowgreen'],
        // ---------------------------------------------------------------------
        // :: Validate html color (it can be name or hex)
        // ---------------------------------------------------------------------
        valid_color: function(color) {
            if (color.match(color_hex_re)) {
                return true;
            } else {
                return $.inArray(color.toLowerCase(),
                                 $.terminal.color_names) !== -1;
            }
        },
        // ---------------------------------------------------------------------
        // :: Escape all special regex characters, so it can be use as regex to
        // :: match exact string that contain those characters
        // ---------------------------------------------------------------------
        escape_regex: function(str) {
            if (typeof str == 'string') {
                var special = /([-\\\^$\[\]()+{}?*.|])/g;
                return str.replace(special, '\\$1');
            }
        },
        // ---------------------------------------------------------------------
        // :: test if string contain formatting
        // ---------------------------------------------------------------------
        have_formatting: function(str) {
            return typeof str == 'string' && !!str.match(format_exist_re);
        },
        is_formatting: function(str) {
            return typeof str == 'string' && !!str.match(format_full_re);
        },
        // ---------------------------------------------------------------------
        // :: return array of formatting and text between them
        // ---------------------------------------------------------------------
        format_split: function(str) {
            return str.split(format_split_re);
        },
        // ---------------------------------------------------------------------
        // :: split text into lines with equal length so each line can be
        // :: rendered separately (text formatting can be longer then a line).
        // ---------------------------------------------------------------------
        split_equal: function(str, length, words) {
            var formatting = false;
            var in_text = false;
            var prev_format = '';
            var result = [];
            // add format text as 5th paramter to formatting it's used for
            // data attribute in format function
            var array = str.replace(format_re, function(_, format, text) {
                var semicolons = format.match(/;/g).length;
                // missing semicolons
                if (semicolons >= 4) {
                    return _;
                } else if (semicolons == 2) {
                    semicolons = ';;';
                } else if (semicolons == 3) {
                    semicolons = ';';
                } else {
                    semicolons = '';
                }
                // return '[[' + format + ']' + text + ']';
                // closing braket will break formatting so we need to escape
                // those using html entity equvalent
                var safe = text.replace(/\\\]/g, '&#93;').replace(/\n/g, '\\n').
                    replace(/&nbsp;/g, ' ');
                return '[[' + format + semicolons + safe + ']' + text + ']';
            }).split(/\n/g);
            function is_space() {
                return line.substring(j-6, j) == '&nbsp;' ||
                    line.substring(j-1, j) == ' ';
            }
            for (var i = 0, len = array.length; i < len; ++i) {
                if (array[i] === '') {
                    result.push('');
                    continue;
                }
                var line = array[i];
                var first_index = 0;
                var count = 0;
                var space = -1;
                for (var j=0, jlen=line.length; j<jlen; ++j) {
                    if (line.substring(j).match(format_start_re)) {
                        formatting = true;
                        in_text = false;
                    } else if (formatting && line[j] === ']') {
                        if (in_text) {
                            formatting = false;
                            in_text = false;
                        } else {
                            in_text = true;
                        }
                    } else if ((formatting && in_text) || !formatting) {
                        if (line[j] === '&') { // treat entity as one character
                            var m = line.substring(j).match(/^(&[^;]+;)/);
                            if (!m) {
                                // should never happen if used by terminal,
                                // because it always calls $.terminal.encode
                                // before this function
                                throw new Error("Unclosed html entity in line " +
                                                (i+1) + ' at char ' + (j+1));
                            }
                            j+=m[1].length-2; // because continue adds 1 to j
                            // if entity is at the end there is no next loop
                            // issue #77
                            if (j === jlen-1) {
                                result.push(output + m[1]);
                            }
                            continue;
                        } else if (line[j] === ']' && line[j-1] === '\\') {
                            // escape \] counts as one character
                            --count;
                        } else {
                            ++count;
                        }
                    }
                    if (is_space() && ((formatting && in_text) || !formatting ||
                                      (line[j] === '[' && line[j+1] === '['))) {
                        space = j;
                    }
                    if ((count === length || j === jlen-1) &&
                        ((formatting && in_text) || !formatting)) {
                        var output;
                        var text = $.terminal.strip(line.substring(space));
                        text = $('<span>' + text + '</span>').text();
                        var text_len = text.length;
                        text = text.substring(0, j+length+1);
                        var can_break = !!text.match(/\s/) || j+length+1 > text_len;
                        if (words && space != -1 && j !== jlen-1 && can_break) {
                            output = line.substring(first_index, space);
                            j = space-1;
                        } else {
                            output = line.substring(first_index, j+1);
                        }
                        if (words) {
                            output = output.replace(/(&nbsp;|\s)+$/g, '');
                        }
                        space = -1;
                        first_index = j+1;
                        count = 0;
                        if (prev_format) {
                            output = prev_format + output;
                            if (output.match(']')) {
                                prev_format = '';
                            }
                        }
                        // Fix output if formatting not closed
                        var matched = output.match(format_re);
                        if (matched) {
                            var last = matched[matched.length-1];
                            if (last[last.length-1] !== ']') {
                                prev_format = last.match(format_begin_re)[1];
                                output += ']';
                            } else if (output.match(format_last_re)) {
                                var line_len = output.length;
                                // why this line ???
                                //var f_len = line_len-last[last.length-1].length;
                                output = output.replace(format_last_re, '');
                                prev_format = last.match(format_begin_re)[1];
                            }
                        }
                        result.push(output);
                    }
                }
            }
            return result;
        },
        // ---------------------------------------------------------------------
        // :: Encode formating as html for insertion into DOM
        // ---------------------------------------------------------------------
        encode: function(str) {
            // don't escape entities
            str = str.replace(/&(?!#[0-9]+;|[a-zA-Z]+;)/g, '&amp;');
            return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
                .replace(/ /g, '&nbsp;')
                .replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;');
        },
        // ---------------------------------------------------------------------
        // :: safe function that will render text as it is
        // ---------------------------------------------------------------------
        escape_formatting: function(string) {
            return $.terminal.escape_brackets($.terminal.encode(string));
        },
        // ---------------------------------------------------------------------
        // :: Replace terminal formatting with html
        // ---------------------------------------------------------------------
        format: function(str, options) {
            var settings = $.extend({}, {
                linksNoReferrer: false
            }, options || {});
            if (typeof str === 'string') {
                //support for formating foo[[u;;]bar]baz[[b;#fff;]quux]zzz
                var splitted = $.terminal.format_split(str);
                str = $.map(splitted, function(text) {
                    if (text === '') {
                        return text;
                    } else if ($.terminal.is_formatting(text)) {
                        return text.replace(format_parts_re, function(s,
                                                                      style,
                                                                      color,
                                                                      background,
                                                                      _class,
                                                                      data_text,
                                                                      text) {
                            if (text === '') {
                                return ''; //'<span>&nbsp;</span>';
                            }
                            text = text.replace(/\\]/g, ']');
                            var style_str = '';
                            if (style.indexOf('b') !== -1) {
                                style_str += 'font-weight:bold;';
                            }
                            var text_decoration = [];
                            if (style.indexOf('u') !== -1) {
                                text_decoration.push('underline');
                            }
                            if (style.indexOf('s') !== -1) {
                                text_decoration.push('line-through');
                            }
                            if (style.indexOf('o') !== -1) {
                                text_decoration.push('overline');
                            }
                            if (text_decoration.length) {
                                style_str += 'text-decoration:' +
                                    text_decoration.join(' ') + ';';
                            }
                            if (style.indexOf('i') !== -1) {
                                style_str += 'font-style:italic;';
                            }
                            if ($.terminal.valid_color(color)) {
                                style_str += 'color:' + color + ';';
                                if (style.indexOf('g') !== -1) {
                                    style_str += 'text-shadow:0 0 5px ' + color + ';';
                                }
                            }
                            if ($.terminal.valid_color(background)) {
                                style_str += 'background-color:' + background;
                            }
                            var data;
                            if (data_text === '') {
                                data = text;
                            } else {
                                data = data_text.replace(/&#93;/g, ']');
                            }
                            var result;
                            if (style.indexOf('!') !== -1) {
                                if (data.match(email_re)) {
                                    result = '<a href="mailto:' + data + '" ';
                                } else {
                                    result = '<a target="_blank" href="' + data + '" ';
                                    if (settings.linksNoReferrer) {
                                        result += 'rel="noreferrer" ';
                                    }
                                }
                            } else {
                                result = '<span ';
                            }
                            if (style_str !== '') {
                                result += 'style="' + style_str + '"';
                            }
                            if (_class !== '') {
                                result += ' class="' + _class + '"';
                            }
                            if (style.indexOf('!') !== -1) {
                                result += '>' + text + '</a>';
                            } else {
                                result += ' data-text="' +
                                    data.replace('"', '&quote;') + '">' +
                                    text + '</span>';
                            }
                            return result;
                        });
                    } else {
                        return '<span>' + text.replace(/\\\]/g, ']') + '</span>';
                    }
                }).join('');
                return str.replace(/<span><br\s*\/?><\/span>/gi, '<br/>');
            } else {
                return '';
            }
        },
        // ---------------------------------------------------------------------
        // :: Replace brackets with html entities
        // ---------------------------------------------------------------------
        escape_brackets: function(string) {
            return string.replace(/\[/g, '&#91;').replace(/\]/g, '&#93;');
        },
        // ---------------------------------------------------------------------
        // :: Remove formatting from text
        // ---------------------------------------------------------------------
        strip: function(str) {
            return str.replace(format_parts_re, '$6');
        },
        // ---------------------------------------------------------------------
        // :: Return active terminal
        // ---------------------------------------------------------------------
        active: function() {
            return terminals.front();
        },
        // keep old as backward compatible
        parseArguments: function(string) {
            return $.terminal.parse_arguments(string);
        },
        splitArguments: function(string) {
            return $.terminal.split_arguments(string);
        },
        parseCommand: function(string) {
            return $.terminal.parse_command(string);
        },
        splitCommand: function(string) {
            return $.terminal.split_command(string);
        },
        // ---------------------------------------------------------------------
        // :: Function splits arguments and works with strings like
        // :: 'asd' 'asd\' asd' "asd asd" asd\ 123 -n -b / [^ ]+ / /\s+/ asd\ a
        // :: it creates a regex and numbers and replaces escape characters in
        // :: double quotes
        // ---------------------------------------------------------------------
        parse_arguments: function(string) {
            var float_re = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/;
            return $.map(string.match(command_re) || [], function(arg) {
                if (arg[0] === "'" && arg[arg.length-1] === "'") {
                    return arg.replace(/^'|'$/g, '');
                } else if (arg[0] === '"' && arg[arg.length-1] === '"') {
                    arg = arg.replace(/^"|"$/g, '').replace(/\\([" ])/g, '$1');
                    return arg.replace(/\\\\|\\t|\\n/g, function(string) {
                        if (string[1] === 't') {
                            return '\t';
                        } else if (string[1] === 'n') {
                            return '\n';
                        } else {
                            return '\\';
                        }
                    }).replace(/\\x([0-9a-f]+)/gi, function(_, hex) {
                        return String.fromCharCode(parseInt(hex, 16));
                    }).replace(/\\0([0-7]+)/g, function(_, oct) {
                        return String.fromCharCode(parseInt(oct, 8));
                    });
                } else if (arg.match(/^\/(\\\/|[^\/])+\/[gimy]*$/)) { // RegEx
                    var m = arg.match(/^\/([^\/]+)\/([^\/]*)$/);
                    return new RegExp(m[1], m[2]);
                } else if (arg.match(/^-?[0-9]+$/)) {
                    return parseInt(arg, 10);
                } else if (arg.match(float_re)) {
                    return parseFloat(arg);
                } else {
                    return arg.replace(/\\ /g, ' ');
                }
            });
        },
        // ---------------------------------------------------------------------
        // :: Split arguments: it only strips single and double quotes and
        // :: escapes spaces
        // ---------------------------------------------------------------------
        split_arguments: function(string) {
            return $.map(string.match(command_re) || [], function(arg) {
                if (arg[0] === "'" && arg[arg.length-1] === "'") {
                    return arg.replace(/^'|'$/g, '');
                } else if (arg[0] === '"' && arg[arg.length-1] === '"') {
                    return arg.replace(/^"|"$/g, '').replace(/\\([" ])/g, '$1');
                } else if (arg.match(/\/.*\/[gimy]*$/)) {
                    return arg;
                } else {
                    return arg.replace(/\\ /g, ' ');
                }
            });
        },
        // ---------------------------------------------------------------------
        // :: Function that returns an object {name,args}. Arguments are parsed
        // :: using the function parse_arguments
        // ---------------------------------------------------------------------
        parse_command: function(string) {
            return process_command(string, $.terminal.parse_arguments);
        },
        // ---------------------------------------------------------------------
        // :: Same as parse_command but arguments are parsed using split_arguments
        // ---------------------------------------------------------------------
        split_command: function(string) {
            return process_command(string, $.terminal.split_arguments);
        },
        // ---------------------------------------------------------------------
        // :: function executed for each text inside [{ .... }]
        // ---------------------------------------------------------------------
        extended_command: function(term, string) {
            try {
                change_hash = false;
                term.exec(string, true).then(function() {
                    change_hash = true;
                });
            } catch(e) {
                // error is process in exec
            }
        }
    };

    // -----------------------------------------------------------------------
    // Helper plugins
    // -----------------------------------------------------------------------
    $.fn.visible = function() {
        return this.css('visibility', 'visible');
    };
    $.fn.hidden = function() {
        return this.css('visibility', 'hidden');
    };
    // -----------------------------------------------------------------------
    // JSON-RPC CALL
    // -----------------------------------------------------------------------
    var ids = {}; // list of url based id of JSON-RPC
    $.jrpc = function(url, method, params, success, error) {
        ids[url] = ids[url] || 0;
        var request = $.json_stringify({
           'jsonrpc': '2.0', 'method': method,
            'params': params, 'id': ++ids[url]});
        return $.ajax({
            url: url,
            data: request,
            success: function(result, status, jqXHR) {
                var content_type = jqXHR.getResponseHeader('Content-Type');
                if (!content_type.match(/application\/json/)) {
                    var msg = 'Response Content-Type is not application/json';
                    if (console && console.warn) {
                        console.warn(msg);
                    } else {
                        throw new Error('WARN: ' + msg);
                    }
                }
                var json;
                try {
                    json = $.parseJSON(result);
                } catch (e) {
                    if (error) {
                        error(jqXHR, 'Invalid JSON', e);
                    } else {
                        throw new Error('Invalid JSON');
                    }
                    return;
                }
                // don't catch errors in success callback
                success(json, status, jqXHR);
            },
            error: error,
            contentType: 'application/json',
            dataType: 'text',
            async: true,
            cache: false,
            //timeout: 1,
            type: 'POST'});
    };

    // -----------------------------------------------------------------------
    function is_scrolled_into_view(elem) {
        var docViewTop = $(window).scrollTop();
        var docViewBottom = docViewTop + $(window).height();

        var elemTop = $(elem).offset().top;
        var elemBottom = elemTop + $(elem).height();

        return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom));
    }
    // -----------------------------------------------------------------------
    // :: Create fake terminal to calcualte the dimention of one character
    // :: this will make terminal work if terminal div is not added to the
    // :: DOM at init like with:
    // :: $('<div/>').terminal().echo('foo bar').appendTo('body');
    // -----------------------------------------------------------------------
    function char_size() {
        var temp = $('<div class="terminal temp"><div class="cmd"><span cla' +
                     'ss="cursor">&nbsp;</span></div></div>').appendTo('body');
        var span = temp.find('span');
        var result = {
            width: span.width(),
            height: span.outerHeight()
        };
        temp.remove();
        return result;
    }
    // -----------------------------------------------------------------------
    // :: calculate numbers of characters
    // -----------------------------------------------------------------------
    function get_num_chars(terminal) {
        var temp = $('<div class="terminal"><span class="cursor">' +
                     '</span></div>').appendTo('body').css('padding', 0);
        var span = temp.find('span');
        // use more characters to get width of single character as a fraction
        var max = 60;
        var spaces = '';
        for (var i=0;i<=max; ++i) {
            spaces += '&nbsp;';
        }
        span.html(spaces);
        var width = span.width()/max;
        var result = Math.floor(terminal.width() / width);
        temp.remove();
        if (have_scrollbars(terminal)) {
            var SCROLLBAR_WIDTH = 20;
            // assume that scrollbars are 20px - in my Laptop with
            // Linux/Chrome they are 16px
            var margins = terminal.innerWidth() - terminal.width();
            result -= Math.ceil((SCROLLBAR_WIDTH - margins / 2) / (width-1));
        }
        return result;
    }
    // -----------------------------------------------------------------------
    // :: Calculate number of lines that fit without scroll
    // -----------------------------------------------------------------------
    function get_num_rows(terminal) {
        return Math.floor(terminal.height() / char_size().height);
    }
    // -----------------------------------------------------------------------
    // :: Get Selected Text (this is internal because it return text even if
    // :: it's outside of terminal, is used to paste text to the terminal)
    // -----------------------------------------------------------------------
    function get_selected_text() {
        if (window.getSelection || document.getSelection) {
            var selection = (window.getSelection || document.getSelection)();
            if (selection.text) {
                return selection.text;
            } else {
                return selection.toString();
            }
        } else if (document.selection) {
            return document.selection.createRange().text;
        }
    }
    // -----------------------------------------------------------------------
    // :: check if div have scrollbars (need to have overflow auto or always)
    // -----------------------------------------------------------------------
    function have_scrollbars(div) {
        if (div.css('overflow') == 'scroll' ||
            div.css('overflow-y') == 'scroll') {
            return true;
        } else if (div.is('body')) {
            return $("body").height() > $(window).height();
        } else {
            return div.get(0).scrollHeight > div.innerHeight();
        }
    }
    // -----------------------------------------------------------------------
    // :: TERMINAL PLUGIN CODE
    // -----------------------------------------------------------------------
    var version_set = !$.terminal.version.match(/^\{\{/);
    var copyright = 'Copyright (c) 2011-2016 Jakub Jankiewicz <http://jcubic'+
        '.pl>';
    var version_string = version_set ? ' v. ' + $.terminal.version : ' ';
    //regex is for placing version string aligned to the right
    var reg = new RegExp(" {" + version_string.length + "}$");
    var name_ver = 'jQuery Terminal Emulator' +
        (version_set ? version_string : '');
    // -----------------------------------------------------------------------
    // :: Terminal Signatures
    // -----------------------------------------------------------------------
    var signatures = [
        ['jQuery Terminal', '(c) 2011-2016 jcubic'],
        [name_ver, copyright.replace(/^Copyright | *<.*>/g, '')],
        [name_ver, copyright.replace(/^Copyright /, '')],
        ['      _______                 ________                        __',
         '     / / _  /_ ____________ _/__  ___/______________  _____  / /',
         ' __ / / // / // / _  / _/ // / / / _  / _/     / /  \\/ / _ \\/ /',
         '/  / / // / // / ___/ // // / / / ___/ // / / / / /\\  / // / /__',
         '\\___/____ \\\\__/____/_/ \\__ / /_/____/_//_/ /_/ /_/  \\/\\__\\_\\___/',
         '         \\/          /____/                                   '.replace(reg, ' ') +
         version_string,
         copyright],
        ['      __ _____                     ________                              __',
         '     / // _  /__ __ _____ ___ __ _/__  ___/__ ___ ______ __ __  __ ___  / /',
         ' __ / // // // // // _  // _// // / / // _  // _//     // //  \\/ // _ \\/ /',
         '/  / // // // // // ___// / / // / / // ___// / / / / // // /\\  // // / /__',
         '\\___//____ \\\\___//____//_/ _\\_  / /_//____//_/ /_/ /_//_//_/ /_/ \\__\\_\\___/',
         '          \\/              /____/                                          '.replace(reg, '') +
         version_string,
         copyright]
    ];
    // -----------------------------------------------------------------------
    // :: Default options
    // -----------------------------------------------------------------------
    $.terminal.defaults = {
        prompt: '> ',
        history: true,
        exit: true,
        clear: true,
        enabled: true,
        historySize: 60,
        maskChar: '*',
        checkArity: true,
        raw: false,
        exceptionHandler: null,
        cancelableAjax: true,
        processArguments: true,
        linksNoReferrer: false,
        processRPCResponse: null,
        Token: true, // where this came from?
        convertLinks: true,
        historyState: false,
        login: null,
        outputLimit: -1,
        formatters: [],
        onAjaxError: null,
        onRPCError: null,
        completion: false,
        historyFilter: null,
        onInit: $.noop,
        onClear: $.noop,
        onBlur: $.noop,
        onFocus: $.noop,
        onTerminalChange: $.noop,
        onExit: $.noop,
        keypress: $.noop,
        keydown: $.noop,
        strings: {
            wrongPasswordTryAgain: "Wrong password try again!",
            wrongPassword: "Wrong password!",
            ajaxAbortError: "Error while aborting ajax call!",
            wrongArity: "Wrong number of arguments. Function '%s' expects %s got"+
                " %s!",
            commandNotFound: "Command '%s' Not Found!",
            oneRPCWithIgnore: "You can use only one rpc with ignoreSystemDescr"+
                "ibe",
            oneInterpreterFunction: "You can't use more than one function (rpc"+
                "with ignoreSystemDescribe counts as one)",
            loginFunctionMissing: "You didn't specify a login function",
            noTokenError: "Access denied (no token)",
            serverResponse: "Server responded",
            wrongGreetings: "Wrong value of greetings parameter",
            notWhileLogin: "You can't call `%s' function while in login",
            loginIsNotAFunction: "Authenticate must be a function",
            canExitError: "You can't exit from main interpreter",
            invalidCompletion: "Invalid completion",
            invalidSelector: 'Sorry, but terminal said that "%s" is not valid '+
                'selector!',
            invalidTerminalId: 'Invalid Terminal ID',
            login: "login",
            password: "password",
            recursiveCall: 'Recursive call detected, skip'
        }
    };
    // -------------------------------------------------------------------------
    // :: All terminal globals
    // -------------------------------------------------------------------------
    var requests = []; // for canceling on CTRL+D
    var terminals = window.terminals = new Cycle(); // list of terminals global in this scope
    // state for all terminals, terminals can't have own array fo state because
    // there is only one popstate event
    var save_state = []; // hold objects returned by export_view by history API
    var hash_commands;
    var change_hash = false; // don't change hash on Init
    var fire_hash_change = true;
    var first_instance = true; // used by history state
    $.fn.terminal = function(init_interpreter, options) {
        // ---------------------------------------------------------------------
        // :: helper function
        // ---------------------------------------------------------------------
        function get_processed_command(command) {
            if ($.isFunction(settings.processArguments)) {
                return process_command(command, settings.processArguments);
            } else if (settings.processArguments) {
                return $.terminal.parse_command(command);
            } else {
                return $.terminal.split_command(command);
            }
        }
        // ---------------------------------------------------------------------
        // :: Display object on terminal
        // ---------------------------------------------------------------------
        function display_object(object) {
            if (typeof object === 'string') {
                self.echo(object);
            } else if (object instanceof Array) {
                self.echo($.map(object, function(object) {
                    return $.json_stringify(object);
                }).join(' '));
            } else if (typeof object === 'object') {
                self.echo($.json_stringify(object));
            } else {
                self.echo(object);
            }
        }
        // Display line code in the file if line numbers are present
        function print_line(url_spec) {
            var re = /(.*):([0-9]+):([0-9]+)$/;
            // google chrome have line and column after filename
            var m = url_spec.match(re);
            if (m) {
                // TODO: do we need to call pause/resume or return promise?
                self.pause();
                $.get(m[1], function(response) {
                    var prefix = location.href.replace(/[^\/]+$/, '');
                    var file = m[1].replace(prefix, '');
                    self.echo('[[b;white;]' + file + ']');
                    var code = response.split('\n');
                    var n = +m[2]-1;
                    self.echo(code.slice(n-2, n+3).map(function(line, i) {
                        if (i == 2) {
                            line = '[[;#f00;]' +
                                $.terminal.escape_brackets(line) + ']';
                        }
                        return '[' + (n+i) + ']: ' + line;
                    }).join('\n')).resume();
                }, 'text');
            }
        }
        // ---------------------------------------------------------------------
        // :: Helper function
        // ---------------------------------------------------------------------
        function display_json_rpc_error(error) {
            if ($.isFunction(settings.onRPCError)) {
                settings.onRPCError.call(self, error);
            } else {
                self.error('&#91;RPC&#93; ' + error.message);
            }
        }
        // ---------------------------------------------------------------------
        // :: Create interpreter function from url string
        // ---------------------------------------------------------------------
        function make_basic_json_rpc(url, auth) {
            var interpreter = function(method, params) {
                self.pause();
                $.jrpc(url, method, params, function(json) {
                    if (json.error) {
                        display_json_rpc_error(json.error);
                    } else {
                        if ($.isFunction(settings.processRPCResponse)) {
                            settings.processRPCResponse.call(self, json.result, self);
                        } else {
                            display_object(json.result);
                        }
                    }
                    self.resume();
                }, ajax_error);
            };
            //this is the interpreter function
            return function(command, terminal) {
                if (command === '') {
                    return;
                }
                try {
                    command = get_processed_command(command);
                } catch(e) {
                    // exception can be thrown on invalid regex
                    terminal.error(e.toString());
                    return;
                    //throw e; // this will show stack in other try..catch
                }
                if (!auth || command.name === 'help') {
                    // allows to call help without a token
                    interpreter(command.name, command.args);
                } else {
                    var token = terminal.token();
                    if (token) {
                        interpreter(command.name, [token].concat(command.args));
                    } else {
                        //should never happen
                        terminal.error('&#91;AUTH&#93; ' +
                                       strings.noTokenError);
                    }
                }
            };
        }
        // ---------------------------------------------------------------------
        // :: Create interpreter function from Object. If the value is object
        // :: it will create nested interpreters
        // ---------------------------------------------------------------------
        function make_object_interpreter(object, arity, login, fallback) {
            // function that maps commands to object methods
            // it keeps terminal context
            return function(user_command, terminal) {
                if (user_command === '') {
                    return;
                }
                //command = split_command_line(command);
                var command;
                try {
                    command = get_processed_command(user_command);
                } catch(e) {
                    // exception can be thrown on invalid regex
                    self.error(e.toString());
                    return;
                    //throw e; // this will show stack in other try..catch
                }
                /*
                if (login) {
                    var token = self.token(true);
                    if (token) {
                        command.args = [token].concat(command.args);
                    } else {
                        terminal.error('&#91;AUTH&#93; ' + strings.noTokenError);
                        return;
                    }
                }*/
                var val = object[command.name];
                var type = $.type(val);
                if (type === 'function') {
                    if (arity && val.length !== command.args.length) {
                        self.error("&#91;Arity&#93; " +
                                   sprintf(strings.wrongArity,
                                           command.name,
                                           val.length,
                                           command.args.length));
                    } else {
                        return val.apply(self, command.args);
                    }
                } else if (type === 'object' || type === 'string') {
                    var commands = [];
                    if (type === 'object') {
                        commands = Object.keys(val);
                        val = make_object_interpreter(val,
                                                      arity,
                                                      login);
                    }
                    terminal.push(val, {
                        prompt: command.name + '> ',
                        name: command.name,
                        completion: type === 'object' ? commands : undefined
                    });
                } else {
                    if ($.isFunction(fallback)) {
                        fallback(user_command, self);
                    } else if ($.isFunction(settings.onCommandNotFound)) {
                        settings.onCommandNotFound(user_command, self);
                    } else {
                        terminal.error(sprintf(strings.commandNotFound,
                                               command.name));
                    }
                }
            };
        }
        // ---------------------------------------------------------------------
        function ajax_error(xhr, status, error) {
            self.resume(); // onAjaxError can use pause/resume call it first
            if ($.isFunction(settings.onAjaxError)) {
                settings.onAjaxError.call(self, xhr, status, error);
            } else if (status !== 'abort') {
                self.error('&#91;AJAX&#93; ' + status + ' - ' +
                           strings.serverResponse + ': \n' +
                           $.terminal.escape_brackets(xhr.responseText));
            }
        }
        // ---------------------------------------------------------------------
        function make_json_rpc_object(url, auth, success) {
            $.jrpc(url, 'system.describe', [], function(ret) {
                var commands = [];
                if (ret.procs) {
                    var interpreter_object = {};
                    $.each(ret.procs, function(_, proc) {
                        interpreter_object[proc.name] = function() {
                            var append = auth && proc.name != 'help';
                            var args = Array.prototype.slice.call(arguments);
                            var args_len = args.length + (append ? 1 : 0);
                            if (settings.checkArity && proc.params &&
                                proc.params.length !== args_len) {
                                self.error("&#91;Arity&#93; " +
                                           sprintf(strings.wrongArity,
                                                   proc.name,
                                                   proc.params.length,
                                                   args_len));
                            } else {
                                self.pause();
                                if (append) {
                                    var token = self.token(true);
                                    if (token) {
                                        args = [token].concat(args);
                                    } else {
                                        self.error('&#91;AUTH&#93; ' +
                                                   strings.noTokenError);
                                    }
                                }
                                $.jrpc(url, proc.name, args, function(json) {
                                    if (json.error) {
                                        display_json_rpc_error(json.error);
                                    } else {
                                        display_object(json.result);
                                    }
                                    self.resume();
                                }, ajax_error);
                            }
                        };
                    });
                    success(interpreter_object);
                } else {
                    success(null);
                }
            }, function() {
                success(null);
            });
        }
        // ---------------------------------------------------------------------
        function make_interpreter(user_intrp, login, finalize) {
            finalize = finalize || $.noop;
            var type = $.type(user_intrp);
            var result = {};
            var rpc_count = 0; // only one rpc can be use for array
            var fn_interpreter;
            if (type === 'array') {
                var object = {};
                // recur will be called when previous acync call is finished
                (function recur(interpreters, success) {
                    if (interpreters.length) {
                        var first = interpreters[0];
                        var rest = interpreters.slice(1);
                        var type = $.type(first);
                        if (type === 'string') {
                            rpc_count++;
                            self.pause();
                            if (settings.ignoreSystemDescribe) {
                                if (rpc_count === 1) {
                                    fn_interpreter = make_basic_json_rpc(first, login);
                                } else {
                                    self.error(strings.oneRPCWithIgnore);
                                }
                                recur(rest, success);
                            } else {
                                make_json_rpc_object(first, login, function(new_obj) {
                                    // will ignore rpc in array that don't have
                                    // system.describe
                                    if (new_obj) {
                                        $.extend(object, new_obj);
                                    }
                                    self.resume();
                                    recur(rest, success);
                                });
                            }
                        } else if (type === 'function') {
                            if (fn_interpreter) {
                                self.error(strings.oneInterpreterFunction);
                            } else {
                                fn_interpreter = first;
                            }
                            recur(rest, success);
                        } else if (type === 'object') {
                            $.extend(object, first);
                            recur(rest, success);
                        }
                    } else {
                        success();
                    }
                })(user_intrp, function() {
                    finalize({
                        interpreter: make_object_interpreter(object,
                                                             false,
                                                             login,
                                                             fn_interpreter),
                        completion: Object.keys(object)
                    });
                });
            } else if (type === 'string') {
                if (settings.ignoreSystemDescribe) {
                    finalize({
                        interpreter: make_basic_json_rpc(user_intrp, login),
                        completion: settings.completion
                    });
                } else {
                    self.pause();
                    make_json_rpc_object(user_intrp, login, function(object) {
                        if (object) {
                            result.interpreter = make_object_interpreter(object,
                                                                         false,
                                                                         login);
                            result.completion = Object.keys(object);
                        } else {
                            // no procs in system.describe
                            result.interpreter = make_basic_json_rpc(user_intrp, login);
                            result.completion = settings.completion;
                        }
                        finalize(result);
                        self.resume();
                    });
                }
            } else if (type === 'object') {
                finalize({
                    interpreter: make_object_interpreter(user_intrp,
                                                         settings.checkArity),
                    completion: Object.keys(user_intrp)
                });
            } else {
                // allow $('<div/>').terminal();
                if (type === 'undefined') {
                    user_intrp = $.noop;
                } else if (type !== 'function') {
                    throw type + " is invalid interpreter value";
                }
                finalize({
                    interpreter: user_intrp,
                    completion: settings.completion
                });
            }
        }
        // ---------------------------------------------------------------------
        // :: Create JSON-RPC authentication function
        // ---------------------------------------------------------------------
        function make_json_rpc_login(url, login) {
            var method = $.type(login) === 'boolean' ? 'login' : login;
            return function(user, passwd, callback, term) {
                self.pause();
                $.jrpc(url,
                       method,
                       [user, passwd],
                       function(response) {
                           if (!response.error && response.result) {
                               callback(response.result);
                           } else {
                               // null will trigger message that login fail
                               callback(null);
                           }
                           self.resume();
                       }, ajax_error);
            };
            //default name is login so you can pass true
        }
        // ---------------------------------------------------------------------
        // :: Return exception message as string
        // ---------------------------------------------------------------------
        function exception_message(e) {
            if (typeof e === 'string') {
                return e;
            } else if (typeof e.fileName === 'string') {
                return e.fileName + ': ' + e.message;
            } else {
                return e.message;
            }
        }
        // ---------------------------------------------------------------------
        // :: display Exception on terminal
        // ---------------------------------------------------------------------
        function display_exception(e, label) {
            if ($.isFunction(settings.exceptionHandler)) {
                settings.exceptionHandler.call(self, e);
            } else {
                self.exception(e, label);
            }
        }
        // ---------------------------------------------------------------------
        function scroll_to_bottom() {
            var scrollHeight;
            if (scroll_object.prop) {
                scrollHeight = scroll_object.prop('scrollHeight');
            } else {
                scrollHeight = scroll_object.attr('scrollHeight');
            }
            scroll_object.scrollTop(scrollHeight);
        }
        // ---------------------------------------------------------------------
        // :: validating if object is a string or a function, call that function
        // :: and display the exeption if any
        // ---------------------------------------------------------------------
        function validate(label, object) {
            try {
                if ($.isFunction(object)) {
                    object(function() {
                        // don't care
                    });
                } else if (typeof object !== 'string') {
                    var msg = label + ' must be string or function';
                    throw msg;
                }
            } catch (e) {
                display_exception(e, label.toUpperCase());
                return false;
            }
            return true;
        }
        // ---------------------------------------------------------------------
        // :: Draw line - can have line breaks and be longer than the width of
        // :: the terminal, there are 2 options raw and finalize
        // :: raw - will not encode the string and finalize if a function that
        // :: will have div container of the line as first argument
        // :: NOTE: it formats and appends lines to output_buffer. The actual
        // :: append to terminal output happens in the flush function
        // ---------------------------------------------------------------------
        var output_buffer = [];
        var NEW_LINE = 1;
        function buffer_line(string, options) {
            // urls should always have formatting to keep url if split
            if (settings.convertLinks) {
                string = string.replace(email_re, '[[!;;]$1]').
                    replace(url_nf_re, '[[!;;]$1]');
            }
            var formatters = $.terminal.defaults.formatters;
            var i, len;
            if (!options.raw) {
                // format using user defined formatters
                for (i=0; i<formatters.length; ++i) {
                    try {
                        if (typeof formatters[i] == 'function') {
                            var ret = formatters[i](string);
                            if (typeof ret == 'string') {
                                string = ret;
                            }
                        }
                    } catch(e) {
                        //display_exception(e, 'FORMATTING');
                        alert('formatting error at formatters[' + i + ']\n' +
                              (e.stack ? e.stack : e));
                    }
                }
                string = $.terminal.encode(string);
            }
            output_buffer.push(NEW_LINE);
            if (!options.raw && (string.length > num_chars ||
                                       string.match(/\n/))) {
                var words = options.keepWords;
                var array = $.terminal.split_equal(string, num_chars, words);
                for (i = 0, len = array.length; i < len; ++i) {
                    if (array[i] === '' || array[i] === '\r') {
                        output_buffer.push('<span></span>');
                    } else {
                        if (options.raw) {
                            output_buffer.push(array[i]);
                        } else {
                            output_buffer.push($.terminal.format(array[i], {
                                linksNoReferrer: settings.linksNoReferrer
                            }));
                        }
                    }
                }
            } else {
                if (!options.raw) {
                    string = $.terminal.format(string, {
                        linksNoReferrer: settings.linksNoReferrer
                    });
                }
                output_buffer.push(string);
            }
            output_buffer.push(options.finalize);
        }
        // ---------------------------------------------------------------------
        function process_line(line, options) {
            // prevent exception in display exception
            try {
                var line_settings = $.extend({
                    exec: true,
                    raw: false,
                    finalize: $.noop
                }, options || {});
                var string = $.type(line) === "function" ? line() : line;
                string = $.type(string) === "string" ? string : String(string);
                if (string !== '') {
                    if (line_settings.exec) {
                        string = $.map(string.split(format_exec_re), function(string) {
                            if (string.match(format_exec_re) &&
                                !$.terminal.is_formatting(string)) {
                                // redraw should not execute commands and it have
                                // and lines variable have all extended commands
                                string = string.replace(/^\[\[|\]\]$/g, '');
                                if (prev_command && prev_command.command == string) {
                                    self.error(strings.recursiveCall);
                                } else {
                                    $.terminal.extended_command(self, string);
                                }
                                return '';
                            } else {
                                return string;
                            }
                        }).join('');
                        if (string !== '') {
                            // string can be empty after removing extended commands
                            buffer_line(string, line_settings);
                        }
                    } else {
                        buffer_line(string, line_settings);
                    }
                }
            } catch (e) {
                output_buffer = [];
                // don't display exception if exception throw in terminal
                alert('[Internal Exception(process_line)]:' +
                      exception_message(e) + '\n' + e.stack);
            }
        }
        // ---------------------------------------------------------------------
        // :: Redraw all lines
        // ---------------------------------------------------------------------
        function redraw() {
            command_line.resize(num_chars);
            // we don't want reflow while processing lines
            var detached_output = output.empty().detach();
            var lines_to_show;
            if (settings.outputLimit >= 0) {
                // flush will limit lines but if there is lot of
                // lines we don't need to show them and then remove
                // them from terminal
                var limit = settings.outputLimit === 0 ?
                    self.rows() :
                    settings.outputLimit;
                lines_to_show = lines.slice(lines.length-limit-1);
            } else {
                lines_to_show = lines;
            }
            try {
                output_buffer = [];
                $.each(lines_to_show, function(i, line) {
                    process_line.apply(null, line); // line is an array
                });
                command_line.before(detached_output); // reinsert output
                self.flush();
            } catch(e) {
                alert('Exception in redraw\n' + e.stack);
            }
        }
        // ---------------------------------------------------------------------
        // :: Display user greetings or terminal signature
        // ---------------------------------------------------------------------
        function show_greetings() {
            if (settings.greetings === undefined) {
                self.echo(self.signature);
            } else if (settings.greetings) {
                var type = typeof settings.greetings;
                if (type === 'string') {
                    self.echo(settings.greetings);
                } else if (type === 'function') {
                    settings.greetings.call(self, self.echo);
                } else {
                    self.error(strings.wrongGreetings);
                }
            }
        }
        // ---------------------------------------------------------------------
        // :: Display prompt and last command
        // ---------------------------------------------------------------------
        function echo_command(command) {
            var prompt = command_line.prompt();
            var mask = command_line.mask();
            switch (typeof mask) {
                case 'string':
                    command = command.replace(/./g, mask);
                    break;
                case 'boolean':
                    if (mask) {
                        command = command.replace(/./g, settings.maskChar);
                    } else {
                        command = $.terminal.escape_formatting(command);
                    }
                    break;
            }
            var options = {
                finalize: function(div) {
                    div.addClass('command');
                }
            };
            if ($.isFunction(prompt)) {
                prompt(function(string) {
                    self.echo(string + command, options);
                });
            } else {
                self.echo(prompt + command, options);
            }
        }
        // ---------------------------------------------------------------------
        // :: Helper function that restore state. Call import_view or exec
        // ---------------------------------------------------------------------
        function restore_state(spec) {
            // spec [terminal_id, state_index, command]
            var terminal = terminals.get()[spec[0]];
            if (!terminal) {
                throw new Error(strings.invalidTerminalId);
            }
            var command_idx = spec[1];
            if (save_state[command_idx]) { // state exists
                terminal.import_view(save_state[command_idx]);
            } else {
                // restore state
                change_hash = false;
                var command = spec[2];
                if (command) {
                    terminal.exec(command).then(function() {
                        change_hash = true;
                        save_state[command_idx] = terminal.export_view();
                    });
                }
            }
            /*if (spec[3].length) {
                restore_state(spec[3]);
            }*/
        }
        // ---------------------------------------------------------------------
        // :: Helper function
        // ---------------------------------------------------------------------
        function maybe_update_hash() {
            if (change_hash) {
                fire_hash_change = false;
                location.hash = $.json_stringify(hash_commands);
                setTimeout(function() {
                    fire_hash_change = true;
                }, 100);
            }
        }
        // ---------------------------------------------------------------------
        // :: Wrapper over interpreter, it implements exit and catches all
        // :: exeptions from user code and displays them on the terminal
        // ---------------------------------------------------------------------
        var first_command = true;
        var last_command;
        var resume_callbacks = [];
        var resume_event_bound = false;
        function commands(command, silent, exec) {
            last_command = command; // for debug
            // first command store state of the terminal before the command get
            // executed
            if (first_command) {
                first_command = false;
                // execHash need first empty command too
                if (settings.historyState || (settings.execHash && exec)) {
                    if (!save_state.length) {
                        // first command in first terminal don't have hash
                        self.save_state();
                    } else {
                        self.save_state(null);
                    }
                }
            }
            function after_exec() {
                // variables defined later in commands
                if (!exec) {
                    change_hash = true;
                    if (settings.historyState) {
                        self.save_state(command, false);
                    }
                    change_hash = saved_change_hash;
                }
                deferred.resolve();
                if ($.isFunction(settings.onAfterCommand)) {
                    settings.onAfterCommand(self, command);
                }
            }
            try {
                // this callback can disable commands
                if ($.isFunction(settings.onBeforeCommand)) {
                    if (settings.onBeforeCommand(self, command) === false) {
                        return;
                    }
                }
                if (!exec) {
                    prev_command = $.terminal.split_command(command);
                }
                if (!ghost()) {
                    // exec execute this function wihout the help of cmd plugin
                    // that add command to history on enter
                    if (exec && $.isFunction(settings.historyFilter) &&
                        settings.historyFilter(command) ||
                        command.match(settings.historyFilter)) {
                        command_line.history().append(command);
                    }
                }
                var interpreter = interpreters.top();
                if (!silent) {
                    echo_command(command);
                }
                // new promise will be returned to exec that will resolve his
                // returned promise
                var deferred = new $.Deferred();
                // we need to save sate before commands is deleyd because
                // execute_extended_command disable it and it can be executed
                // after delay
                var saved_change_hash = change_hash;
                if (command.match(/^\s*login\s*$/) && self.token(true)) {
                    if (self.level() > 1) {
                        self.logout(true);
                    } else {
                        self.logout();
                    }
                    after_exec();
                } else if (settings.exit && command.match(/^\s*exit\s*$/) &&
                           !in_login) {
                    var level = self.level();
                    if (level == 1 && self.get_token() || level > 1) {
                        if (self.get_token(true)) {
                            self.set_token(undefined, true);
                        }
                        self.pop();
                    }
                    after_exec();
                } else if (settings.clear && command.match(/^\s*clear\s*$/) &&
                           !in_login) {
                    self.clear();
                    after_exec();
                } else {
                    var position = lines.length-1;
                    // Call user interpreter function
                    var result = interpreter.interpreter.call(self, command, self);
                    if (result !== undefined) {
                        // auto pause/resume when user return promises
                        self.pause();
                        return $.when(result).then(function(result) {
                            // don't echo result if user echo something
                            if (result && position === lines.length-1) {
                                display_object(result);
                            }
                            after_exec();
                            self.resume();
                        });
                    } else if (paused) {
                        var old_command = command;
                        resume_callbacks.push(function() {
                            // exec with resume/pause in user code
                            after_exec();
                        });
                    } else {
                        after_exec();
                    }
                }
                return deferred.promise();
            } catch (e) {
                display_exception(e, 'USER');
                self.resume();
                throw e;
            }
        }
        // ---------------------------------------------------------------------
        // :: The logout function removes Storage, disables history and runs
        // :: the login function. This function is called only when options.login
        // :: function is defined. The check for this is in the self.pop method
        // ---------------------------------------------------------------------
        function global_logout() {
            if ($.isFunction(settings.onBeforeLogout)) {
                try {
                    if (settings.onBeforeLogout(self) === false) {
                        return;
                    }
                } catch (e) {
                    display_exception(e, 'onBeforeLogout');
                    throw e;
                }
            }
            clear_loging_storage();
            if ($.isFunction(settings.onAfterLogout)) {
                try {
                    settings.onAfterLogout(self);
                } catch (e) {
                    display_exception(e, 'onAfterlogout');
                    throw e;
                }
            }
            self.login(settings.login, true, initialize);
        }
        // ---------------------------------------------------------------------
        function clear_loging_storage() {
            var name = self.prefix_name(true) + '_';
            $.Storage.remove(name + 'token');
            $.Storage.remove(name + 'login');
        }
        // ---------------------------------------------------------------------
        // :: Save the interpreter name for use with purge
        // ---------------------------------------------------------------------
        function maybe_append_name(interpreter_name) {
            var storage_key = self.prefix_name() + '_interpreters';
            var names = $.Storage.get(storage_key);
            if (names) {
                names = $.parseJSON(names);
            } else {
                names = [];
            }
            if ($.inArray(interpreter_name, names) == -1) {
                names.push(interpreter_name);
                $.Storage.set(storage_key, $.json_stringify(names));
            }
        }
        // ---------------------------------------------------------------------
        // :: Function enables history, sets prompt, runs interpreter function
        // ---------------------------------------------------------------------
        function prepare_top_interpreter(silent) {
            var interpreter = interpreters.top();
            var name = self.prefix_name(true);
            if (!ghost()) {
                maybe_append_name(name);
            }
            command_line.name(name);
            if ($.isFunction(interpreter.prompt)) {
                command_line.prompt(function(command) {
                    interpreter.prompt(command, self);
                });
            } else {
                command_line.prompt(interpreter.prompt);
            }
            command_line.set('');
            if (!silent && $.isFunction(interpreter.onStart)) {
                interpreter.onStart(self);
            }
        }
        // ---------------------------------------------------------------------
        var local_first_instance;
        function initialize() {
            prepare_top_interpreter();
            show_greetings();
            // was_paused flag is workaround for case when user call exec before
            // login and pause in onInit, 3rd exec will have proper timing (will
            // execute after onInit resume)
            var was_paused = false;
            if ($.isFunction(settings.onInit)) {
                onPause = function() { // local in terminal
                    was_paused = true;
                };
                try {
                    settings.onInit(self);
                } catch (e) {
                    display_exception(e, 'OnInit');
                    // throw e; // it will be catched by terminal
                } finally {
                    onPause = $.noop;
                    if (!was_paused) {
                        // resume login if user didn't call pause in onInit
                        // if user pause in onInit wait with exec until it
                        // resume
                        self.resume();
                    }
                }
            }
            function hashchange() {
                if (fire_hash_change && settings.execHash) {
                    try {
                        if (location.hash) {
                            var hash = location.hash.replace(/^#/, '');
                            hash_commands = $.parseJSON(decodeURIComponent(hash));
                        } else {
                            hash_commands = [];
                        }
                        if (hash_commands.length) {
                            restore_state(hash_commands[hash_commands.length-1]);
                        } else if (save_state[0]) {
                            self.import_view(save_state[0]);
                        }
                    } catch(e) {
                        display_exception(e, 'TERMINAL');
                    }
                }
            }
            if (first_instance) {
                first_instance = false;
                if ($.fn.hashchange) {
                    $(window).hashchange(hashchange);
                } else {
                    $(window).bind('hashchange', hashchange);
                }
            }
        }
        // ---------------------------------------------------------------------
        // :: function complete the command
        // ---------------------------------------------------------------------
        function complete_helper(command, string, commands) {
            if (settings.clear && $.inArray('clear', commands) == -1) {
                commands.push('clear');
            }
            if (settings.exit && $.inArray('exit', commands) == -1) {
                commands.push('exit');
            }
            var test = command_line.get().substring(0, command_line.position());
            if (test !== command) {
                // command line changed between TABS - ignore
                return;
            }
            var regex = new RegExp('^' + $.terminal.escape_regex(string));
            var matched = [];
            for (var i=commands.length; i--;) {
                if (regex.test(commands[i])) {
                    matched.push(commands[i]);
                }
            }
            if (matched.length === 1) {
                self.insert(matched[0].replace(regex, ''));
            } else if (matched.length > 1) {
                if (tab_count >= 2) {
                    echo_command(command);
                    var text = matched.reverse().join('\t');
                    self.echo($.terminal.escape_brackets(text), {keepWords: true});
                    tab_count = 0;
                } else {
                    var found = false;
                    var found_index;
                    var j;
                    loop:
                    for (j=string.length; j<matched[0].length; ++j) {
                        for (i=1; i<matched.length; ++i) {
                            if (matched[0].charAt(j) !== matched[i].charAt(j)) {
                                break loop;
                            }
                        }
                        found = true;
                    }
                    if (found) {
                        self.insert(matched[0].slice(0, j).replace(regex, ''));
                    }
                }
            }
        }
        // ---------------------------------------------------------------------
        // :: If Ghost don't store anything in localstorage
        // ---------------------------------------------------------------------
        function ghost() {
            return in_login || command_line.mask() !== false;
        }
        // ---------------------------------------------------------------------
        // :: Keydown event handler
        // ---------------------------------------------------------------------
        function key_down(e) {
            // Prevent to be executed by cmd: CTRL+D, TAB, CTRL+TAB (if more
            // then one terminal)
            if (e.which === 67 && e.ctrlKey) { // CTRL+C
                settings.interrupt();
            } else if (e.which === 90 && e.ctrlKey) { // CTRL+Z
                settings.background();
            }
            var result, i, top = interpreters.top();
            if (!self.paused() && self.enabled()) {
                if ($.isFunction(top.keydown)) {
                    result = top.keydown(e, self);
                    if (result !== undefined) {
                        return result;
                    }
                } else if ($.isFunction(settings.keydown)) {
                    result = settings.keydown(e, self);
                    if (result !== undefined) {
                        return result;
                    }
                }
                var completion;
                if ((settings.completion &&
                     $.type(settings.completion) != 'boolean') &&
                    top.completion === undefined) {
                    completion = settings.completion;
                } else {
                    completion = top.completion;
                }
                // after text pasted into textarea in cmd plugin
                self.oneTime(10, function() {
                    on_scrollbar_show_resize();
                });
                if (e.which !== 9) { // not a TAB
                    tab_count = 0;
                }
                if (e.which === 68 && e.ctrlKey) { // CTRL+D
                    if (!in_login) {
                        if (command_line.get() === '') {
                            if (interpreters.size() > 1 ||
                                settings.login !== undefined) {
                                self.pop('');
                            } else {
                                self.resume();
                                self.echo('');
                            }
                        } else {
                            self.set_command('');
                        }
                    }
                    return false;
                } else if (e.which === 76 && e.ctrlKey) { // CTRL+L
                    self.clear();
                } else if (completion && e.which === 9) { // TAB
                    // TODO: move this to cmd plugin
                    //       add completion = array | function
                    //       !!! Problem complete more then one key need terminal
                    ++tab_count;
                    // cursor can be in the middle of the command
                    // so we need to get the text before the cursor
                    var pos = command_line.position();
                    var buffer = command_line.get();
                    var command = command_line.get().substring(0, pos);
                    var strings = command.split(' ');
                    var string; // string before cursor that will be completed
                    if (strings.length == 1) {
                        string = strings[0];
                    } else {
                        string = strings[strings.length-1];
                        for (i=strings.length-1; i>0; i--) {
                            // treat escape space as part of the string
                            if (strings[i-1][strings[i-1].length-1] == '\\') {
                                string = strings[i-1] + ' ' + string;
                            } else {
                                break;
                            }
                        }
                    }
                    switch ($.type(completion)) {
                        case 'function':
                            completion(self, buffer, pos, function(commands) {
                                complete_helper(command, string, commands);
                            });
                            break;
                        case 'array':
                            complete_helper(command, string, completion);
                            break;
                        default:
                            // terminal will not catch this because it's an event
                            throw new Error(strings.invalidCompletion);
                    }
                    return false;
                } else if (e.which === 86 && e.ctrlKey) { // CTRL+V
                    self.oneTime(1, function() {
                        scroll_to_bottom();
                    });
                    return;
                } else if (e.which === 9 && e.ctrlKey) { // CTRL+TAB
                    if (terminals.length() > 1) {
                        self.focus(false);
                        return false;
                    }
                } else if (e.which === 34) { // PAGE DOWN
                    self.scroll(self.height());
                } else if (e.which === 33) { // PAGE UP
                    self.scroll(-self.height());
                } else {
                    self.attr({scrollTop: self.attr('scrollHeight')});
                }
            } else if (e.which === 68 && e.ctrlKey) { // CTRL+D (if paused)
                if (requests.length) {
                    for (i=requests.length; i--;) {
                        var r = requests[i];
                        if (4 !== r.readyState) {
                            try {
                                r.abort();
                            } catch (error) {
                                self.error(strings.ajaxAbortError);
                            }
                        }
                    }
                    requests = [];
                    // only resume if there are ajax calls
                    self.resume();
                }
                return false;
            }
        }
        // ---------------------------------------------------------------------
        var self = this;
        if (this.length > 1) {
            return this.each(function() {
                $.fn.terminal.call($(this),
                                   init_interpreter,
                                   $.extend({name: self.selector}, options));
            });
        }
        // terminal already exists
        if (self.data('terminal')) {
            return self.data('terminal');
        }
        if (self.length === 0) {
            throw sprintf($.terminal.defaults.strings.invalidSelector, self.selector);
        }
        //var names = []; // stack if interpeter names
        var scroll_object;
        var prev_command; // used for name on the terminal if not defined
        var loged_in = false;
        var tab_count = 0; // for tab completion
        // array of line objects:
        // - function (called whenever necessary, result is printed)
        // - array (expected form: [line, settings])
        // - anything else (cast to string when painted)
        var lines = [];
        var output; // .terminal-output jquery object
        var terminal_id = terminals.length();
        var num_chars; // numer of chars in line
        var num_rows; // number of lines that fit without scrollbar
        var command_list = []; // for tab completion
        var url;
        var logins = new Stack(); // stack of logins
        var in_login = false;//some Methods should not be called when login
        // TODO: Try to use mutex like counter for pause/resume
        var onPause = $.noop;//used to indicate that user call pause onInit
        var old_width, old_height;
        var delayed_commands = []; // used when exec commands while paused
        var settings = $.extend({},
                                $.terminal.defaults,
                                {name: self.selector},
                                options || {});
        var strings = $.terminal.defaults.strings;
        var enabled = settings.enabled, frozen;
        var paused = false;
        // -----------------------------------------------------------------
        // TERMINAL METHODS
        // -----------------------------------------------------------------
        $.extend(self, $.omap({
            id: function() {
                return terminal_id;
            },
            // -------------------------------------------------------------
            // :: Clear the output
            // -------------------------------------------------------------
            clear: function() {
                output.html('');
                lines = [];
                try {
                    settings.onClear(self);
                } catch (e) {
                    display_exception(e, 'onClear');
                    throw e;
                }
                self.attr({ scrollTop: 0});
                return self;
            },
            // -------------------------------------------------------------
            // :: Return an object that can be used with import_view to
            // :: restore the state
            // -------------------------------------------------------------
            export_view: function() {
                return $.extend({}, {
                    focus: enabled,
                    mask: command_line.mask(),
                    prompt: self.get_prompt(),
                    command: self.get_command(),
                    position: command_line.position(),
                    lines: clone(lines),
                    interpreters: interpreters.clone()
                }, $.isFunction(settings.onExport) ? settings.onExport() : {});
            },
            // -------------------------------------------------------------
            // :: Restore the state of the previous exported view
            // -------------------------------------------------------------
            import_view: function(view) {
                if (in_login) {
                    throw new Error(sprintf(strings.notWhileLogin, 'import_view'));
                }
                if ($.isFunction(settings.onImport)) {
                    settings.onImport(view);
                }
                self.set_prompt(view.prompt);
                self.set_command(view.command);
                command_line.position(view.position);
                command_line.mask(view.mask);
                if (view.focus) {
                    self.focus();
                }
                lines = clone(view.lines);
                interpreters = view.interpreters;
                redraw();
                return self;
            },
            // -------------------------------------------------------------
            // :: Store current terminal state
            // -------------------------------------------------------------
            save_state: function(command, ignore_hash, index) {
                //save_state.push({view:self.export_view(), join:[]});
                if (typeof index != 'undefined') {
                    save_state[index] = self.export_view();
                } else {
                    save_state.push(self.export_view());
                }
                if (!$.isArray(hash_commands)) {
                    hash_commands = [];
                }
                if (command !== undefined && !ignore_hash) {
                    var state = [
                        terminal_id,
                        save_state.length-1,
                        command
                    ];
                    hash_commands.push(state);
                    maybe_update_hash();
                }
            },
            // -------------------------------------------------------------
            // :: Execute a command, it will handle commands that do AJAX
            // :: calls and have delays, if the second argument is set to
            // :: true it will not echo executed command
            // -------------------------------------------------------------
            exec: function(command, silent, deferred) {
                if ($.isArray(command)) {
                    return $.when.apply($, $.map(command, function(command) {
                        return self.exec(command, silent);
                    }));
                }
                // both commands executed here (resume will call Term::exec)
                var d = deferred || new $.Deferred();
                if (paused) {
                    // delay command multiple time
                    delayed_commands.push([command, silent, d]);
                } else {
                    // commands may return promise from user code
                    // it will resolve exec promise when user promise
                    // is resolved
                    commands(command, silent, true).then(function() {
                        d.resolve(self);
                    });
                }
                return d.promise();
            },
            // -------------------------------------------------------------
            // :: bypass login function that wait untill you type user/pass
            // :: it hide implementation detail
            // -------------------------------------------------------------
            autologin: function(user, token, silent) {
                self.trigger('terminal.autologin', [user, token, silent]);
                return self;
            },
            // -------------------------------------------------------------
            // :: Function changes the prompt of the command line to login
            // :: with a password and calls the user login function with
            // :: the callback that expects a token. The login is successful
            // :: if the user calls it with value that is truthy
            // -------------------------------------------------------------
            login: function(auth, infinite, success, error) {
                logins.push([].slice.call(arguments));
                if (in_login) {
                    throw new Error(sprintf(strings.notWhileLogin, 'login'));
                }
                if (!$.isFunction(auth)) {
                    throw new Error(strings.loginIsNotAFunction);
                }
                if (self.token(true) && self.login_name(true)) {
                    if ($.isFunction(success)) {
                        success();
                    }
                    return self;
                }
                var user = null;
                // don't store login data in history
                if (settings.history) {
                    command_line.history().disable();
                }
                // so we know how many times call pop
                var level = self.level();
                in_login = true;
                function login_callback(user, token, silent, event) {
                    if (token) {
                        while (self.level() > level) {
                            self.pop();
                        }
                        if (settings.history) {
                            command_line.history().enable();
                        }
                        var name = self.prefix_name(true) + '_';
                        $.Storage.set(name + 'token', token);
                        $.Storage.set(name + 'login', user);
                        in_login = false;
                        if ($.isFunction(success)) {
                            // will be used internaly since users know
                            // when login success (they decide when
                            // it happen by calling the callback -
                            // this funtion)
                            success();
                        }
                    } else {
                        if (infinite) {
                            if (!silent) {
                                self.error(strings.wrongPasswordTryAgain);
                            }
                            self.pop().set_mask(false);
                        } else {
                            in_login = false;
                            if (!silent) {
                                self.error(strings.wrongPassword);
                            }
                            self.pop().pop();
                        }
                        // used only to call pop in push
                        if ($.isFunction(error)) {
                            error();
                        }
                    }
                    self.off('terminal.autologin');
                }
                self.on('terminal.autologin', function(event, user, token, silent) {
                    login_callback(user, token, silent);
                });
                self.push(function(user) {
                    self.set_mask(settings.maskChar).push(function(pass) {
                        try {
                            auth.call(self, user, pass, function(token, silent) {
                                login_callback(user, token, silent);
                            });
                        } catch(e) {
                            display_exception(e, 'AUTH');
                        }
                    }, {
                        prompt: strings.password + ': ',
                        name: 'password'
                    });
                }, {
                    prompt: strings.login + ': ',
                    name: 'login'
                });
                return self;
            },
            // -------------------------------------------------------------
            // :: User defined settings and defaults as well
            // -------------------------------------------------------------
            settings: function() {
                return settings;
            },
            // -------------------------------------------------------------
            // :: Return commands function from top interpreter
            // -------------------------------------------------------------
            commands: function() {
                return interpreters.top().interpreter;
            },
            // -------------------------------------------------------------
            // :: Low Level method that overwrites interpreter
            // -------------------------------------------------------------
            setInterpreter: function() {
                if (window.console && console.warn) {
                    console.warn('This function is deprecated, use set_inte'+
                                 'rpreter insead!');
                }
                self.set_interpreter.apply(self, arguments);
            },
            // -------------------------------------------------------------
            set_interpreter: function(user_intrp, login) {
                function overwrite_interpreter() {
                    self.pause();
                    make_interpreter(user_intrp, !!login, function(result) {
                        self.resume();
                        var top = interpreters.top();
                        $.extend(top, result);
                        prepare_top_interpreter(true);
                    });
                }
                if ($.type(user_intrp) == 'string' && login) {
                    self.login(make_json_rpc_login(user_intrp, login),
                               true,
                               overwrite_interpreter);
                } else {
                    overwrite_interpreter();
                }
            },
            // -------------------------------------------------------------
            // :: Show user greetings or terminal signature
            // -------------------------------------------------------------
            greetings: function() {
                show_greetings();
                return self;
            },
            // -------------------------------------------------------------
            // :: Return true if terminal is paused false otherwise
            // -------------------------------------------------------------
            paused: function() {
                return paused;
            },
            // -------------------------------------------------------------
            // :: Pause the terminal, it should be used for ajax calls
            // -------------------------------------------------------------
            pause: function() {
                onPause();
                if (!paused && command_line) {
                    paused = true;
                    command_line.disable().hidden();
                    if ($.isFunction(settings.onPause)) {
                        settings.onPause();
                    }
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Resume the previously paused terminal
            // -------------------------------------------------------------
            resume: function() {
                if (paused && command_line) {
                    paused = false;
                    command_line.enable().visible();
                    var original = delayed_commands;
                    delayed_commands = [];
                    (function recur() {
                        if (original.length) {
                            self.exec.apply(self, original.shift()).then(recur);
                        } else {
                            self.trigger('resume');
                            var fn = resume_callbacks.shift();
                            if (fn) {
                                fn();
                            }
                            scroll_to_bottom();
                            if ($.isFunction(settings.onResume)) {
                                settings.onResume();
                            }
                        }
                    })();
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Return the number of characters that fit into the width of
            // :: the terminal
            // -------------------------------------------------------------
            cols: function() {
                return settings.numChars?settings.numChars:get_num_chars(self);
            },
            // -------------------------------------------------------------
            // :: Return the number of lines that fit into the height of the
            // :: terminal
            // -------------------------------------------------------------
            rows: function() {
                return settings.numRows?settings.numRows:get_num_rows(self);
            },
            // -------------------------------------------------------------
            // :: Return the History object
            // -------------------------------------------------------------
            history: function() {
                return command_line.history();
            },
            // -------------------------------------------------------------
            // :: toggle recording of history state
            // -------------------------------------------------------------
            history_state: function(toggle) {
                if (toggle) {
                    // if set to true and if set from user command we need
                    // not to include the command
                    self.oneTime(1, function() {
                        settings.historyState = true;
                        if (!save_state.length) {
                            self.save_state();
                        } else if (terminals.length() > 1) {
                            self.save_state(null);
                        }
                    });
                } else {
                    settings.historyState = false;
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Switch to the next terminal
            // -------------------------------------------------------------
            next: function() {
                if (terminals.length() === 1) {
                    return self;
                } else {
                    var offsetTop = self.offset().top;
                    var height = self.height();
                    var scrollTop = self.scrollTop();
                    if (!is_scrolled_into_view(self)) {
                        self.enable();
                        $('html,body').animate({
                            scrollTop: offsetTop-50
                        }, 500);
                        return self;
                    } else {
                        terminals.front().disable();
                        var next = terminals.rotate().enable();
                        // 100 provides buffer in viewport
                        var x = next.offset().top - 50;
                        $('html,body').animate({scrollTop: x}, 500);
                        try {
                            settings.onTerminalChange(next);
                        } catch (e) {
                            display_exception(e, 'onTerminalChange');
                            throw e;
                        }
                        return next;
                    }
                }
            },
            // -------------------------------------------------------------
            // :: Make the terminal in focus or blur depending on the first
            // :: argument. If there is more then one terminal it will
            // :: switch to next one, if the second argument is set to true
            // ::  the events will be not fired. Used on init
            // -------------------------------------------------------------
            focus: function(toggle, silent) {
                if (terminals.length() === 1) {
                    if (toggle === false) {
                        try {
                            if (!silent && settings.onBlur(self) !== false ||
                                silent) {
                                self.disable();
                            }
                        } catch (e) {
                            display_exception(e, 'onBlur');
                            throw e;
                        }
                    } else {
                        try {
                            if (!silent && settings.onFocus(self) !== false ||
                                silent) {
                                self.enable();
                            }
                        } catch (e) {
                            display_exception(e, 'onFocus');
                            throw e;
                        }
                    }
                } else {
                    if (toggle === false) {
                        self.next();
                    } else {
                        var front = terminals.front();
                        if (front != self) {
                            front.disable();
                            if (!silent) {
                                try {
                                    settings.onTerminalChange(self);
                                } catch (e) {
                                    display_exception(e, 'onTerminalChange');
                                    throw e;
                                }
                            }
                        }
                        terminals.set(self);
                        self.enable();
                    }
                }
                // why this delay - it can't be use for mobile
                /*
                self.oneTime(1, function() {
                });
                */
                return self;
            },
            // -------------------------------------------------------------
            // :: Disable/Enable terminal that can be enabled by click
            // -------------------------------------------------------------
            freeze: function(freeze) {
                if (freeze) {
                    self.disable();
                    frozen = true;
                } else {
                    frozen = false;
                    self.enable();
                }
            },
            // -------------------------------------------------------------
            // :: check if terminal is frozen
            // -------------------------------------------------------------
            frozen: function() {
                return frozen;
            },
            // -------------------------------------------------------------
            // :: Enable the terminal
            // -------------------------------------------------------------
            enable: function() {
                if (!enabled && !frozen) {
                    if (num_chars === undefined) {
                        //enabling first time
                        self.resize();
                    }
                    if (command_line) {
                        command_line.enable();
                        enabled = true;
                    }
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Disable the terminal
            // -------------------------------------------------------------
            disable: function() {
                if (enabled && command_line && !frozen) {
                    enabled = false;
                    command_line.disable();
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: return true if the terminal is enabled
            // -------------------------------------------------------------
            enabled: function() {
                return enabled;
            },
            // -------------------------------------------------------------
            // :: Return the terminal signature depending on the size of the terminal
            // -------------------------------------------------------------
            signature: function() {
                var cols = self.cols();
                var i = cols < 15 ? null : cols < 35 ? 0 : cols < 55 ? 1 : cols < 64 ? 2 : cols < 75 ? 3 : 4;
                if (i !== null) {
                    return signatures[i].join('\n') + '\n';
                } else {
                    return '';
                }
            },
            // -------------------------------------------------------------
            // :: Return the version number
            // -------------------------------------------------------------
            version: function() {
                return $.terminal.version;
            },
            // -------------------------------------------------------------
            // :: Return actual command line object (jquery object with cmd
            // :: methods)
            // -------------------------------------------------------------
            cmd: function() {
                return command_line;
            },
            // -------------------------------------------------------------
            // :: Return the current command entered by terminal
            // -------------------------------------------------------------
            get_command: function() {
                return command_line.get();
            },
            // -------------------------------------------------------------
            // :: Change the command line to the new one
            // -------------------------------------------------------------
            set_command: function(command) {
                command_line.set(command);
                return self;
            },
            // -------------------------------------------------------------
            // :: Insert text into the command line after the cursor
            // -------------------------------------------------------------
            insert: function(string) {
                if (typeof string === 'string') {
                    command_line.insert(string);
                    return self;
                } else {
                    throw "insert function argument is not a string";
                }
            },
            // -------------------------------------------------------------
            // :: Set the prompt of the command line
            // -------------------------------------------------------------
            set_prompt: function(prompt) {
                if (validate('prompt', prompt)) {
                    if ($.isFunction(prompt)) {
                        command_line.prompt(function(callback) {
                            prompt(callback, self);
                        });
                    } else {
                        command_line.prompt(prompt);
                    }
                    interpreters.top().prompt = prompt;
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Return the prompt used by the terminal
            // -------------------------------------------------------------
            get_prompt: function() {
                return interpreters.top().prompt;
                // command_line.prompt(); - can be a wrapper
                //return command_line.prompt();
            },
            // -------------------------------------------------------------
            // :: Enable or Disable mask depedning on the passed argument
            // :: the mask can also be character (in fact it will work with
            // :: strings longer then one)
            // -------------------------------------------------------------
            set_mask: function(mask) {
                command_line.mask(mask === true ? settings.maskChar : mask);
                return self;
            },
            // -------------------------------------------------------------
            // :: Return the ouput of the terminal as text
            // -------------------------------------------------------------
            get_output: function(raw) {
                if (raw) {
                    return lines;
                } else {
                    return $.map(lines, function(item) {
                        return $.isFunction(item[0]) ? item[0]() : item[0];
                    }).join('\n');
                }
            },
            // -------------------------------------------------------------
            // :: Recalculate and redraw everything
            // -------------------------------------------------------------
            resize: function(width, height) {
                if (!self.is(':visible')) {
                    // delay resize if terminal not visible
                    self.stopTime('resize');
                    self.oneTime(500, 'resize', function() {
                        self.resize(width, height);
                    });
                } else {
                    if (width && height) {
                        self.width(width);
                        self.height(height);
                    }
                    width = self.width();
                    height = self.height();
                    var new_num_chars = self.cols();
                    var new_num_rows = self.rows();
                    // only if number of chars changed
                    if (new_num_chars !== num_chars ||
                        new_num_rows !== num_rows) {
                        num_chars = new_num_chars;
                        num_rows = new_num_rows;
                        redraw();
                        var top = interpreters.top();
                        if ($.isFunction(top.resize)) {
                            top.resize(self);
                        } else if ($.isFunction(settings.onResize)) {
                            settings.onResize(self);
                        }
                        old_height = height;
                        old_width = width;
                        scroll_to_bottom();
                    }
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Flush the output to the terminal
            // -------------------------------------------------------------
            flush: function() {
                try {
                    var wrapper;
                    // print all lines
                    $.each(output_buffer, function(i, line) {
                        if (line === NEW_LINE) {
                            wrapper = $('<div></div>');
                        } else if ($.isFunction(line)) {
                            // this is finalize function from echo
                            wrapper.appendTo(output);
                            try {
                                line(wrapper);
                                /* this don't work with resize
                                   line(wrapper, function(user_finalize) {
                                   // TODO:
                                   //user_finalize need to be save in line object
                                   user_finalize(wrapper);
                                   });*/
                            } catch (e) {
                                display_exception(e, 'USER:echo(finalize)');
                            }
                        } else {
                            $('<div/>').html(line).
                                appendTo(wrapper).width('100%');
                        }
                    });
                    if (settings.outputLimit >= 0) {
                        var limit = settings.outputLimit === 0 ?
                            self.rows() :
                            settings.outputLimit;
                        var $lines = output.find('div div');
                        if ($lines.length > limit) {
                            var max = lines.length-limit+1;
                            var for_remove = $lines.slice(0, max);
                            // you can't get parent if you remove the
                            // element so we first get the parent
                            var parents = for_remove.parent();
                            for_remove.remove();
                            parents.each(function() {
                                var self = $(this);
                                if (self.is(':empty')) {
                                    // there can be divs inside parent that
                                    // was not removed
                                    self.remove();
                                }
                            });
                        }
                    }
                    scroll_to_bottom();
                    output_buffer = [];
                } catch (e) {
                    alert('[Flush] ' + exception_message(e) + '\n' +
                          e.stack);
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Update the output line - line number can be negative
            // -------------------------------------------------------------
            update: function(line, string) {
                if (line < 0) {
                    line = lines.length + line; // yes +
                }
                if (!lines[line]) {
                    self.error('Invalid line number ' + line);
                } else {
                    lines[line][0] = string;
                    // it would be hard to figure out which div need to be
                    // updated so we update everything
                    redraw();
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Print data to the terminal output. It can have two options
            // :: a function that is called with the container div that
            // :: holds the output (as a jquery object) every time the
            // :: output is printed (including resize and scrolling)
            // :: If the line is a function it will be called for every
            // :: redraw.
            // :: it use $.when so you can echo a promise
            // -------------------------------------------------------------
            echo: function(string, options) {
                string = string || '';
                $.when(string).then(function(string) {
                    try {
                        output_buffer = [];
                        var locals = $.extend({
                            flush: true,
                            raw: settings.raw,
                            finalize: $.noop,
                            keepWords: false
                        }, options || {});
                        process_line(string, locals);
                        // extended commands should be processed only once
                        // in echo and not on redraw
                        lines.push([string, $.extend(locals, {
                            exec: false
                        })]);
                        if (locals.flush) {
                            self.flush();
                        }
                        num_rows = get_num_rows(self);
                        on_scrollbar_show_resize();
                    } catch (e) {
                        // if echo throw exception we can't use error to
                        // display that exception
                        alert('[Terminal.echo] ' + exception_message(e) +
                              '\n' + e.stack);
                    }
                });
                return self;
            },
            // -------------------------------------------------------------
            // :: echo red text
            // -------------------------------------------------------------
            error: function(message, finalize) {
                //quick hack to fix trailing backslash
                var str = $.terminal.escape_brackets(message).
                    replace(/\\$/, '&#92;').
                    replace(url_re, ']$1[[;;;error]');
                return self.echo('[[;;;error]' + str + ']', finalize);
            },
            // -------------------------------------------------------------
            // :: Display Exception on terminal
            // -------------------------------------------------------------
            exception: function(e, label) {
                var message = exception_message(e);
                if (label) {
                    message = '&#91;' + label + '&#93;: ' + message;
                }
                if (message) {
                    self.error(message, {
                        finalize: function(div) {
                            div.addClass('exception message');
                        }
                    });
                }
                if (typeof e.fileName === 'string') {
                    //display filename and line which throw exeption
                    self.pause();
                    $.get(e.fileName, function(file) {
                        self.resume();
                        var num = e.lineNumber - 1;
                        var line = file.split('\n')[num];
                        if (line) {
                            self.error('[' + e.lineNumber + ']: ' + line);
                        }
                    });
                }
                if (e.stack) {
                    self.echo(e.stack.split(/\n/g).map(function(trace) {
                        return '[[;;;error]' + trace.replace(url_re, function(url) {
                            return ']' + url + '[[;;;error]';
                        }) + ']';
                    }).join('\n'), {
                        finalize: function(div) {
                            div.addClass('exception stack-trace');
                        }
                    });
                }
            },
            // -------------------------------------------------------------
            // :: Scroll Div that holds the terminal
            // -------------------------------------------------------------
            scroll: function(amount) {
                var pos;
                amount = Math.round(amount);
                if (scroll_object.prop) { // work with jQuery > 1.6
                    if (amount > scroll_object.prop('scrollTop') && amount > 0) {
                        scroll_object.prop('scrollTop', 0);
                    }
                    pos = scroll_object.prop('scrollTop');
                    scroll_object.scrollTop(pos + amount);
                } else {
                    if (amount > scroll_object.attr('scrollTop') && amount > 0) {
                        scroll_object.attr('scrollTop', 0);
                    }
                    pos = scroll_object.attr('scrollTop');
                    scroll_object.scrollTop(pos + amount);
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Exit all interpreters and logout. The function will throw
            // :: exception if there is no login provided
            // -------------------------------------------------------------
            logout: function(local) {
                if (in_login) {
                    throw new Error(sprintf(strings.notWhileLogin, 'import_view'));
                }
                if (local) {
                    var login = logins.pop();
                    self.set_token(undefined, true);
                    self.login.apply(self, login);
                } else {
                    while (interpreters.size() > 0) {
                        // pop will call global_logout that will call login
                        // and size will be > 0; this is workaround the problem
                        if (self.pop()) {
                            break;
                        }
                    }
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Function returns the token returned by callback function
            // :: in login function. It does nothing (return undefined) if
            // :: there is no login
            // -------------------------------------------------------------
            token: function(local) {
                return $.Storage.get(self.prefix_name(local) + '_token');
            },
            // -------------------------------------------------------------
            // :: Function sets the token to the supplied value. This function
            // :: works regardless of wherer settings.login is supplied
            // -------------------------------------------------------------
            set_token: function(token, local) {
                var name = self.prefix_name(local) + '_token';
                if (typeof token == 'undefined') {
                    $.Storage.remove(name, token);
                } else {
                    $.Storage.set(name, token);
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Function get the token either set by the login method or
            // :: by the set_token method.
            // -------------------------------------------------------------
            get_token: function(local) {
                return $.Storage.get(self.prefix_name(local) + '_token');
            },
            // -------------------------------------------------------------
            // :: Function return Login name entered by the user
            // -------------------------------------------------------------
            login_name: function(local) {
                return $.Storage.get(self.prefix_name(local) + '_login');
            },
            // -------------------------------------------------------------
            // :: Function returns the name of current interpreter
            // -------------------------------------------------------------
            name: function() {
                return interpreters.top().name;
            },
            // -------------------------------------------------------------
            // :: Function return prefix name for login/token
            // -------------------------------------------------------------
            prefix_name: function(local) {
                var name = (settings.name ? settings.name + '_': '') +
                    terminal_id;
                if (local && interpreters.size() > 1) {
                    var local_name = interpreters.map(function(intrp) {
                        return intrp.name;
                    }).slice(1).join('_');
                    if (local_name) {
                        name += '_' + local_name;
                    }
                }
                return name;
            },
            // -------------------------------------------------------------
            // :: wrapper for common use case
            // -------------------------------------------------------------
            read: function(message, callback) {
                var d = new $.Deferred();
                self.push(function(text) {
                    self.pop();
                    if ($.isFunction(callback)) {
                        callback(text);
                    }
                    d.resolve(text);
                }, {
                    prompt: message
                });
                return d.promise();
            },
            // -------------------------------------------------------------
            // :: Push a new interenter on the Stack
            // -------------------------------------------------------------
            push: function(interpreter, options) {
                options = options || {};
                if (!options.name && prev_command) {
                    // push is called in login
                    options.name = prev_command.name;
                }
                if (options.prompt === undefined) {
                    options.prompt = options.name + ' ';
                }
                //names.push(options.name);
                var top = interpreters.top();
                if (top) {
                    top.mask = command_line.mask();
                }
                var was_paused = paused;
                self.pause();
                make_interpreter(interpreter, !!options.login, function(ret) {
                    // result is object with interpreter and completion
                    // properties
                    interpreters.push($.extend({}, ret, options));
                    if (options.login) {
                        var type = $.type(options.login);
                        if (type == 'function') {
                            // self.pop on error
                            self.login(options.login,
                                       false,
                                       prepare_top_interpreter,
                                       self.pop);
                        } else if ($.type(interpreter) == 'string' &&
                                   type == 'string' || type == 'boolean') {
                            self.login(make_json_rpc_login(interpreter,
                                                           options.login),
                                       false,
                                       prepare_top_interpreter,
                                       self.pop);
                        }
                    } else {
                        prepare_top_interpreter();
                    }
                    if (!was_paused) {
                        self.resume();
                    }
                });
                return self;
            },
            // -------------------------------------------------------------
            // :: Remove the last interpreter from the Stack
            // -------------------------------------------------------------
            pop: function(string) {
                if (string !== undefined) {
                    echo_command(string);
                }
                var token = self.token(true);
                if (interpreters.size() == 1) {
                    if (settings.login) {
                        global_logout();
                        if ($.isFunction(settings.onExit)) {
                            try {
                                settings.onExit(self);
                            } catch (e) {
                                display_exception(e, 'onExit');
                                throw e;
                            }
                        }
                        return true;
                    } else {
                        self.error(strings.canExitError);
                    }
                } else {
                    if (self.token(true)) {
                        clear_loging_storage();
                    }
                    var current = interpreters.pop();
                    prepare_top_interpreter();
                    if ($.isFunction(current.onExit)) {
                        try {
                            current.onExit(self);
                        } catch (e) {
                            display_exception(e, 'onExit');
                            throw e;
                        }
                    }
                    // restore mask
                    self.set_mask(interpreters.top().mask);
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Change terminal option(s) at runtime
            // -------------------------------------------------------------
            option: function(object_or_name, value) {
                if (typeof value == 'undefined') {
                    if (typeof object_or_name == 'string') {
                        return settings[object_or_name];
                    } else if (typeof object_or_name == 'object') {
                        $.each(object_or_name, function(key, value) {
                            settings[key] = value;
                        });
                    }
                } else {
                    settings[object_or_name] = value;
                }
                return self;
            },
            // -------------------------------------------------------------
            // :: Return how deep you are in nested interpreters
            // -------------------------------------------------------------
            level: function() {
                return interpreters.size();
            },
            // -------------------------------------------------------------
            // :: Reinitialize the terminal
            // -------------------------------------------------------------
            reset: function() {
                self.clear();
                while(interpreters.size() > 1) {
                    interpreters.pop();
                }
                initialize();
                return self;
            },
            // -------------------------------------------------------------
            // :: Remove all local storage left by terminal, it will not
            // :: logout you until you refresh the browser
            // -------------------------------------------------------------
            purge: function() {
                var prefix = self.prefix_name() + '_';
                var names = $.Storage.get(prefix + 'interpreters');
                $.each($.parseJSON(names), function(_, name) {
                    $.Storage.remove(name + '_commands');
                    $.Storage.remove(name + '_token');
                    $.Storage.remove(name + '_login');
                });
                command_line.purge();
                $.Storage.remove(prefix + 'interpreters');
                return self;
            },
            // -------------------------------------------------------------
            // :: Remove all events and DOM nodes left by terminal, it will
            // :: not purge the terminal so you will have the same state
            // :: when you refresh the browser
            // -------------------------------------------------------------
            destroy: function() {
                command_line.destroy().remove();
                output.remove();
                $(document).unbind('.terminal');
                $(window).unbind('.terminal');
                self.unbind('click mousewheel');
                self.removeData('terminal').removeClass('terminal');
                if (settings.width) {
                    self.css('width', '');
                }
                if (settings.height) {
                    self.css('height', '');
                }
                $(window).off('blur', blur_terminal).
                    off('focus', focus_terminal);
                terminals.remove(terminal_id);
                return self;
            }
        }, function(name, fun) {
            // wrap all functions and display execptions
            return function() {
                try {
                    return fun.apply(self, [].slice.apply(arguments));
                } catch (e) {
                    // exec catch by command (resume call exec)
                    if (name !== 'exec' && name !== 'resume') {
                        display_exception(e, 'TERMINAL');
                    }
                    throw e;
                }
            };
        }));

        // -----------------------------------------------------------------
        var on_scrollbar_show_resize = (function() {
            var scroll_bars = have_scrollbars(self);
            return function() {
                if (scroll_bars !== have_scrollbars(self)) {
                    // if the scollbar appearance changes we will have a
                    // different number of chars
                    self.resize();
                    scroll_bars = have_scrollbars(self);
                }
            };
        })();


        // -----------------------------------------------------------------
        // INIT CODE
        // -----------------------------------------------------------------
        if (settings.width) {
            self.width(settings.width);
        }
        if (settings.height) {
            self.height(settings.height);
        }
        var agent = navigator.userAgent.toLowerCase();
        if (!agent.match(/(webkit)[ \/]([\w.]+)/) &&
            self[0].tagName.toLowerCase() == 'body') {
            scroll_object = $('html');
        } else {
            scroll_object = self;
        }
        // register ajaxSend for cancel requests on CTRL+D
        $(document).bind('ajaxSend.terminal', function(e, xhr, opt) {
            requests.push(xhr);
        });
        output = $('<div>').addClass('terminal-output').appendTo(self);
        self.addClass('terminal');
        // keep focus in clipboard textarea in mobile
        /*
          if (('ontouchstart' in window) || window.DocumentTouch &&
          document instanceof DocumentTouch) {
          self.click(function() {
          self.find('textarea').focus();
          });
          self.find('textarea').focus();
          }
        */
        /*
          self.bind('touchstart.touchScroll', function() {

          });
          self.bind('touchmove.touchScroll', function() {

          });
        */
        //$('<input type="text"/>').hide().focus().appendTo(self);

        // before login event
        if (settings.login && $.isFunction(settings.onBeforeLogin)) {
            try {
                settings.onBeforeLogin(self);
            } catch (e) {
                display_exception(e, 'onBeforeLogin');
                throw e;
            }
        }
        var auth = settings.login;
        // create json-rpc authentication function
        var base_interpreter;
        if (typeof init_interpreter == 'string') {
            base_interpreter = init_interpreter;
        } else if (init_interpreter instanceof Array &&
                   typeof init_interpreter[0] == 'string') {
            base_interpreter = init_interpreter[0];
        }
        if (base_interpreter &&
            (typeof settings.login === 'string' || settings.login)) {
            settings.login = make_json_rpc_login(base_interpreter,
                                                 settings.login);
        }
        terminals.append(self);
        var interpreters;
        var command_line;
        var old_enabled;
        function focus_terminal() {
            if (old_enabled) {
                self.focus();
            }
        }
        function blur_terminal() {
            old_enabled = enabled;
            self.disable();
        }
        make_interpreter(init_interpreter, !!settings.login, function(itrp) {
            if (settings.completion && typeof settings.completion != 'boolean') {
                //overwrite interpreter completion by global setting #224
                itrp.completion = settings.completion;
            }
            interpreters = new Stack($.extend({
                name: settings.name,
                prompt: settings.prompt,
                keypress: settings.keypress,
                keydown: settings.keydown,
                resize: settings.onResize,
                greetings: settings.greetings,
                interrupt: settings.interrupt,
                mousewheel: settings.mousewheel
            }, itrp));
            // CREATE COMMAND LINE
            command_line = $('<div/>').appendTo(self).cmd({
                prompt: settings.prompt,
                history: settings.history,
                historyFilter: settings.historyFilter,
                historySize: settings.historySize,
                width: '100%',
                enabled: enabled && !is_touch,
                keydown: key_down,
                keypress: function(e) {
                    var result, i, top = interpreters.top();
                    if ($.isFunction(top.keypress)) {
                        return top.keypress(e, self);
                    } else if ($.isFunction(settings.keypress)) {
                        return settings.keypress(e, self);
                    }
                },
                onCommandChange: function(command) {
                    if ($.isFunction(settings.onCommandChange)) {
                        try {
                            settings.onCommandChange(command, self);
                        } catch (e) {
                            display_exception(e, 'onCommandChange');
                            throw e;
                        }
                    }
                    scroll_to_bottom();
                },
                commands: commands
            });
            // touch devices need touch event to get virtual keyboard
            if (enabled && self.is(':visible') && !is_touch) {
                self.focus(undefined, true);
            } else {
                self.disable();
            }
            self.oneTime(100, function() {
                function disable(e) {
                    var sender = $(e.target);
                    if (!sender.closest('.terminal').length &&
                        self.enabled() &&
                        settings.onBlur(self) !== false) {
                        self.disable();
                    }
                }
                $(document).bind('click.terminal', disable).
                    bind('contextmenu.terminal', disable);
            });
            if (!is_touch) {
                // work weird on mobile
                var $win = $(window).on('focus', focus_terminal).
                    on('blur', blur_terminal);
            } else {
                /*
                self.find('textarea').on('blur.terminal', function() {
                    if (enabled) {
                        self.focus(false);
                    }
                });*/
            }
            self.click(function(e) {
        		if (self.paused()) {
        			return;
        		}
                if (!self.enabled()) {
                    self.focus();
                } else if (is_touch) {
                    // keep focusing silently so textarea get focus
                    self.focus(true, true);
                }
                // this will ensure that textarea has focus
                command_line.enable();
            }).delegate('.exception a', 'click', function(e) {
                //.on('click', '.exception a', function(e) {
                // in new jquery .delegate just call .on
                var href = $(this).attr('href');
                if (href.match(/:[0-9]+$/)) { // display line if specified
                    e.preventDefault();
                    print_line(href);
                }
            });
            if (!navigator.platform.match(/linux/i)) {
                // on linux system paste work with middle mouse button
                self.mousedown(function(e) {
                    if (e.which == 2) {
                        var selected = get_selected_text();
                        self.insert(selected);
                    }
                });
            }
            if (self.is(':visible')) {
                num_chars = self.cols();
                command_line.resize(num_chars);
                num_rows = get_num_rows(self);
            }
            // -------------------------------------------------------------
            // Run Login
            if (settings.login) {
                self.login(settings.login, true, initialize);
            } else {
                initialize();
            }
            self.oneTime(100, function() {
                $win.bind('resize.terminal', function() {
                    if (self.is(':visible')) {
                        var width = self.width();
                        var height = self.height();
                        // prevent too many calculations in IE
                        if (old_height !== height || old_width !== width) {
                            self.resize();
                        }
                    }
                });
            });
            // -------------------------------------------------------------
            // :: helper
            function exec_spec(spec) {
                var terminal = terminals.get()[spec[0]];
                // execute if belong to this terminal
                if (terminal && terminal_id == terminal.id()) {
                    if (spec[2]) {
                        try {
                            if (paused) {
                                var defer = $.Deferred();
                                resume_callbacks.push(function() {
                                    return terminal.exec(spec[2]).then(function(term, i) {
                                        terminal.save_state(spec[2], true, spec[1]);
                                        defer.resolve();
                                    });
                                });
                                return defer.promise();
                            } else {
                                return terminal.exec(spec[2]).then(function(term, i) {
                                    terminal.save_state(spec[2], true, spec[1]);
                                });
                            }
                        } catch (e) {
                            var cmd = $.terminal.escape_brackets(command);
                            var msg = "Error while exec with command " + cmd;
                            terminal.error(msg).exception(e);
                        }
                    }
                }
            }
            // exec from hash called in each terminal instance
            if (settings.execHash) {
                if (location.hash) {
                    try {
                        var hash = location.hash.replace(/^#/, '');
                        // yes no var - global inside terminal
                        hash_commands = $.parseJSON(decodeURIComponent(hash));
                        var i = 0;
                        (function recur() {
                            var spec = hash_commands[i++];
                            if (spec) {
                                exec_spec(spec).then(recur);
                            } else {
                                change_hash = true;
                            }
                        })();//*/
                    } catch (e) {
                        //invalid json - ignore
                    }
                } else {
                    change_hash = true;
                }
            } else {
                change_hash = true; // if enabled later
            }
            //change_hash = true; // exec can now change hash
            // -------------------------------------------------------------
            if ($.event.special.mousewheel) {
                var shift = false;
                $(document).bind('keydown.terminal', function(e) {
                    if (e.shiftKey) {
                        shift = true;
                    }
                }).bind('keyup.terminal', function(e) {
                    // in Google Chromium/Linux shiftKey is false
                    if (e.shiftKey || e.which == 16) {
                        shift = false;
                    }
                });
                self.mousewheel(function(event, delta) {
                    if (!shift) {
                        var interpreter = interpreters.top();
                        if ($.isFunction(interpreter.mousewheel)) {
                            var ret = interpreter.mousewheel(event, delta, self);
                            if (ret === false) {
                                return;
                            }
                        } else if ($.isFunction(settings.mousewheel)) {
                            settings.mousewheel(event, delta, self);
                        }
                        if (delta > 0) {
                            self.scroll(-40);
                        } else {
                            self.scroll(40);
                        }
                        //event.preventDefault();
                    }
                });
            }
        }); // make_interpreter
        self.data('terminal', self);
        return self;
    }; //terminal plugin
})(jQuery);
