Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**@license
* __ _____ ________ __
* / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / /
* __ / // // // // // _ // _// // / / // _ // _// // // \/ // _ \/ /
* / / // // // // // ___// / / // / / // ___// / / / / // // /\ // // / /__
* \___//____ \\___//____//_/ _\_ / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/
* \/ /____/ version 0.11.19
*
* This file is part of jQuery Terminal. http://terminal.jcubic.pl
*
* Copyright (c) 2010-2016 Jakub Jankiewicz
* Released under the MIT license
*
* Contains:
*
* Storage plugin Distributed under the MIT License
* Copyright (c) 2010 Dave Schindler
*
* jQuery Timers licenced with the WTFPL
*
*
* Cross-Browser Split 1.1.1
* Copyright 2007-2012 Steven Levithan
* Available under the MIT License
*
* jQuery Caret
* Copyright (c) 2009, Gideon Sireling
* 3 clause BSD License
*
* sprintf.js
* Copyright (c) 2007-2013 Alexandru Marasteanu
* licensed under 3 clause BSD license
*
* Date: Tue, 22 Nov 2016 11:04:26 +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.filter(Boolean).length) {
return;
}
if (data.length === 1) {
return data[0];
} else {
if (pos === data.length - 1) {
pos = 0;
} else {
++pos;
}
if (data[pos]) {
return data[pos];
} else {
return 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;
var restart = false;
while (!data[index]) {
index++;
if (index > data.length) {
if (restart) {
break;
}
index = 0;
restart = true;
}
}
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, {
data: function() {
return data;
},
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));
}
});
}
// -------------------------------------------------------------------------
// :: HISTORY CLASS
// -------------------------------------------------------------------------
function History(name, size, memory) {
var enabled = true;
var storage_key = '';
if (typeof name === 'string' && name !== '') {
storage_key = name + '_';
}
storage_key += 'commands';
var data;
if (memory) {
data = [];
} else {
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;
if (!memory) {
$.Storage.set(storage_key, JSON.stringify(data));
}
}
}
},
data: function() {
return data;
},
reset: function() {
pos = data.length-1;
},
last: function() {
return data[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() {
if (!memory) {
$.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('' +
'');
// on mobile the only way to hide textarea on desktop it's needed because
// textarea show up after focus
//self.append('');
var clip = $('').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(/( |\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, '&');
return str.replace(//g, '>')
.replace(/ /g, ' ')
.replace(/\t/g, ' ');
},
// ---------------------------------------------------------------------
// :: 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)) {
// fix inside formatting because encode is called
// before format
text = text.replace(/\[\[[^\]]+\]/, function(text) {
return text.replace(/ /g, ' ');
});
return text.replace(format_parts_re, function(s,
style,
color,
background,
_class,
data_text,
text) {
if (text === '') {
return ''; //'';
}
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(/]/g, ']');
}
var result;
if (style.indexOf('!') !== -1) {
if (data.match(email_re)) {
result = '' + text + '';
} else {
result += ' data-text="' +
data.replace('"', '"e;') + '">' +
text + '';
}
return result;
});
} else {
return '' + text.replace(/\\\]/g, ']') + '';
}
}).join('');
return str.replace(/ <\/span>/gi, ' ');
} else {
return '';
}
},
// ---------------------------------------------------------------------
// :: Replace brackets with html entities
// ---------------------------------------------------------------------
escape_brackets: function(string) {
return string.replace(/\[/g, '[').replace(/\]/g, ']');
},
// ---------------------------------------------------------------------
// :: Remove formatting from text
// ---------------------------------------------------------------------
strip: function(str) {
return str.replace(format_parts_re, '$6');
},
// ---------------------------------------------------------------------
// :: Return active terminal
// ---------------------------------------------------------------------
active: function() {
return terminals.front();
},
// implmentation detail id is always length of terminals Cycle
last_id: function() {
var len = terminals.length();
if (len) {
return len - 1;
}
},
// 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|text)\/json/)) {
var msg = 'Response Content-Type is neither application/json nor text/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:
// :: $('').terminal().echo('foo bar').appendTo('body');
// -----------------------------------------------------------------------
function char_size() {
var temp = $('
').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 = $('
' +
'
').appendTo('body').css('padding', 0);
var span = temp.find('span');
var width = span[0].getBoundingClientRect().width;
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 ';
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: '*',
wrap: true,
checkArity: true,
raw: false,
exceptionHandler: null,
memory: false,
cancelableAjax: true,
processArguments: true,
linksNoReferrer: false,
processRPCResponse: null,
Token: true, // where this came from?
convertLinks: true,
historyState: false,
echoCommand: true,
scrollOnEcho: true,
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 = 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
var last_id;
$.fn.terminal = function(init_interpreter, options) {
function StorageHelper(memory) {
if (memory) {
this.storage = {};
}
this.set = function(key, value) {
if (memory) {
this.storage[key] = value;
} else {
$.Storage.set(key, value);
}
};
this.get = function(key) {
if (memory) {
return this.storage[key];
} else {
return $.Storage.get(key);
}
};
this.remove = function(key) {
if (memory) {
delete this.storage[key];
} else {
$.Storage.remove(key);
}
};
}
// ---------------------------------------------------------------------
// :: 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('[RPC] ' + error.message);
if (error.error && error.error.message) {
error = error.error;
// more detailed error message
var msg = '\t' + error.message;
if (error.file) {
msg += ' in file "' + error.file.replace(/.*\//, '') + '"';
}
if (error.at) {
msg += ' at line ' + error.at;
}
self.error(msg);
}
}
}
// ---------------------------------------------------------------------
// :: 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('[AUTH] ' +
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('[AUTH] ' + strings.noTokenError);
return;
}
}*/
var val = object[command.name];
var type = $.type(val);
if (type === 'function') {
if (arity && val.length !== command.args.length) {
self.error("[Arity] " +
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('[AJAX] ' + 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("[Arity] " +
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('[AUTH] ' +
strings.noTokenError);
}
}
$.jrpc(url, proc.name, args, 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);
}
};
});
interpreter_object.help = interpreter_object.help || function(fn) {
if (typeof fn == 'undefined') {
self.echo('Available commands: ' + ret.procs.map(function(proc) {
return proc.name;
}).join(', ') + ', help');
} else {
var found = false;
$.each(ret.procs, function(_, proc) {
if (proc.name == fn) {
found = true;
var msg = '';
msg += '[[bu;#fff;]' + proc.name + ']';
if (proc.params) {
msg += ' ' + proc.params.join(' ');
}
if (proc.help) {
msg += '\n' + proc.help;
}
self.echo(msg);
return false;
}
});
if (!found) {
if (fn == 'help') {
self.echo('[[bu;#fff;]help] [method]\ndisplay help ' +
'for the method or list of methods if not'+
' specified');
} else {
var msg = 'Method `' + fn.toString() + '\' not found ';//'
self.error(msg);
}
}
}
};
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 object;
var result = {};
var rpc_count = 0; // only one rpc can be use for array
var fn_interpreter;
if (type === 'array') {
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) {
object = {
interpreter: make_basic_json_rpc(user_intrp, login)
};
if ($.isArray(settings.completion)) {
object.completion = settings.completion;
}
finalize(object);
} 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);
}
finalize(result);
self.resume();
});
}
} else if (type === 'object') {
finalize({
interpreter: make_object_interpreter(user_intrp,
settings.checkArity),
completion: Object.keys(user_intrp)
});
} else {
// allow $('').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 num_chars ||
string.match(/\n/)) &&
((settings.wrap === true && options.wrap === undefined) ||
settings.wrap === false && options.wrap === true)) {
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('');
} 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
});
string.split(/\n/).forEach(function(string) {
output_buffer.push(string);
});
} else {
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 && settings.echoCommand) {
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(true);
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');
}
}
clear_loging_storage();
if ($.isFunction(settings.onAfterLogout)) {
try {
settings.onAfterLogout(self);
} catch (e) {
display_exception(e, 'onAfterlogout');
}
}
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 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 command = command_line.get().substring(0, pos);
var cmd_strings = command.split(' ');
var string; // string before cursor that will be completed
if (strings.length == 1) {
string = cmd_strings[0];
} else {
string = cmd_strings[cmd_strings.length-1];
for (i=cmd_strings.length-1; i>0; i--) {
// treat escape space as part of the string
if (cmd_strings[i-1][cmd_strings[i-1].length-1] == '\\') {
string = cmd_strings[i-1] + ' ' + string;
} else {
break;
}
}
}
switch ($.type(completion)) {
case 'function':
completion(self, string, 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 === 118 || e.which === 86) &&
(e.ctrlKey || e.metaKey)) { // 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 interpreter 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 init_deferr = $.Deferred();
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 storage = new StorageHelper(settings.memory);
var strings = $.terminal.defaults.strings;
var enabled = settings.enabled, frozen = false;
var paused = false;
var autologin = true; // set to false of onBeforeLogin return 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');
}
self.attr({ scrollTop: 0});
return self;
},
// -------------------------------------------------------------
// :: Return an object that can be used with import_view to
// :: restore the state
// -------------------------------------------------------------
export_view: function() {
var user_export = {};
if ($.isFunction(settings.onExport)) {
try {
user_export = settings.onExport();
} catch(e) {
display_exception(e, 'onExport');
}
}
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()
}, user_export);
},
// -------------------------------------------------------------
// :: 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)) {
try {
settings.onImport(view);
} catch(e) {
display_exception(e, 'onImport');
}
}
init_deferr.then(function() {
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) {
var d = deferred || new $.Deferred();
function run() {
if ($.isArray(command)) {
(function recur() {
var cmd = command.shift();
if (cmd) {
self.exec(cmd, silent).then(recur);
} else {
d.resolve();
}
})();
} else if (paused) {
// both commands executed here (resume will call Term::exec)
// 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);
});
}
}
// while testing it didn't executed last exec when using this
// for resolved deferred
if (init_deferr.state() != 'resolved') {
init_deferr.then(run);
} else {
run();
}
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);
}
in_login = true;
if (self.token() && self.level() == 1 && !autologin) {
in_login = false; // logout will call login
self.logout(true);
} else {
if (self.token(true) && self.login_name(true)) {
in_login = false;
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();
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!');
}
return 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();
}
return self;
},
// -------------------------------------------------------------
// :: 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(visible) {
onPause();
if (!paused && command_line) {
init_deferr.then(function() {
paused = true;
command_line.disable();
if (!visible) {
command_line.hidden();
}
if ($.isFunction(settings.onPause)) {
settings.onPause();
}
});
}
return self;
},
// -------------------------------------------------------------
// :: Resume the previously paused terminal
// -------------------------------------------------------------
resume: function() {
function run() {
paused = false;
if (terminals.front() == self) {
command_line.enable();
}
command_line.visible();
var original = delayed_commands;
delayed_commands = [];
for (var i = 0; i < original.length; ++i) {
self.exec.apply(self, original[i]);
}
self.trigger('resume');
var fn = resume_callbacks.shift();
if (fn) {
fn();
}
scroll_to_bottom();
if ($.isFunction(settings.onResume)) {
settings.onResume();
}
}
if (paused && command_line) {
if (init_deferr.state() != 'resolved') {
init_deferr.then(run);
} else {
run();
}
}
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;
},
// -------------------------------------------------------------
// :: clear the history state
// -------------------------------------------------------------
clear_history_state: function() {
hash_commands = [];
save_state = [];
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');
}
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) {
init_deferr.then(function() {
if (terminals.length() === 1) {
if (toggle === false) {
try {
if (!silent && settings.onBlur(self) !== false ||
silent) {
self.disable();
}
} catch (e) {
display_exception(e, 'onBlur');
}
} else {
try {
if (!silent && settings.onFocus(self) !== false ||
silent) {
self.enable();
}
} catch (e) {
display_exception(e, 'onFocus');
}
}
} 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');
}
}
}
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) {
init_deferr.then(function() {
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();
}
init_deferr.then(function() {
command_line.enable();
enabled = true;
});
}
return self;
},
// -------------------------------------------------------------
// :: Disable the terminal
// -------------------------------------------------------------
disable: function() {
if (enabled && !frozen) {
init_deferr.then(function() {
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) {
init_deferr.then(function() {
command_line.set(command);
});
return self;
},
// -------------------------------------------------------------
// :: Insert text into the command line after the cursor
// -------------------------------------------------------------
insert: function(string) {
if (typeof string === 'string') {
init_deferr.then(function() {
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) {
init_deferr.then(function() {
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) {
init_deferr.then(function() {
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 = $('');
} 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 {
$('').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();
}
});
}
}
num_rows = get_num_rows(self);
on_scrollbar_show_resize();
if (settings.scrollOnEcho) {
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) {
init_deferr.then(function() {
if (line < 0) {
line = lines.length + line; // yes +
}
if (!lines[line]) {
self.error('Invalid line number ' + line);
} else {
if (string === null) {
lines.splice(line, 1);
} 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;
},
// -------------------------------------------------------------
// :: return index of last line in case when you need to update
// :: after something is echo on the terminal
// -------------------------------------------------------------
last_index: function() {
return lines.length-1;
},
// -------------------------------------------------------------
// :: 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 {
var locals = $.extend({
flush: true,
raw: settings.raw,
finalize: $.noop,
keepWords: false
}, options || {});
if (locals.flush) {
output_buffer = [];
}
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();
}
} 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(/\\$/, '\').
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 = '[' + label + ']: ' + 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) {
var stack = $.terminal.escape_brackets(e.stack);
self.echo(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, 'logout'));
}
init_deferr.then(function() {
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) {
init_deferr.then(function() {
options = options || {};
var defaults = {
infiniteLogin: false
};
var settings = $.extend({}, defaults, options);
if (!settings.name && prev_command) {
// push is called in login
settings.name = prev_command.name;
}
if (settings.prompt === undefined) {
settings.prompt = (settings.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, settings));
if ($.isArray(ret.completion) && settings.completion === true) {
interpreters.top().completion = ret.completion;
} else if (!ret.completion && settings.completion === true) {
interpreters.top().completion = false;
}
if (settings.login) {
var type = $.type(settings.login);
if (type == 'function') {
// self.pop on error
self.login(settings.login,
settings.infiniteLogin,
prepare_top_interpreter,
settings.infiniteLogin ? $.noop : self.pop);
} else if ($.type(interpreter) == 'string' &&
type == 'string' || type == 'boolean') {
self.login(make_json_rpc_login(interpreter,
settings.login),
settings.infiniteLogin,
prepare_top_interpreter,
settings.infiniteLogin ? $.noop : 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');
}
}
return true;
} else {
self.error(strings.canExitError);
}
} else {
if (self.token(true)) {
clear_loging_storage();
}
var current = interpreters.pop();
prepare_top_interpreter();
// we check in case if you don't pop from password interpreter
if (in_login && self.get_prompt() != strings.login + ': ') {
in_login = false;
}
if ($.isFunction(current.onExit)) {
try {
current.onExit(self);
} catch (e) {
display_exception(e, 'onExit');
}
}
// 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() {
init_deferr.then(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() {
init_deferr.then(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() {
init_deferr.then(function() {
command_line.destroy().remove();
output.remove();
$(document).unbind('.terminal');
$(window).unbind('.terminal');
self.unbind('click mousewheel mousedown mouseup');
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 = $('
').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() {
});
*/
//$('').hide().focus().appendTo(self);
// before login event
if (settings.login && $.isFunction(settings.onBeforeLogin)) {
try {
if (settings.onBeforeLogin(self) === false) {
autologin = false;
}
} 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) {
// first JSON-RPC
for (var i=0, len=init_interpreter.length; i').appendTo(self).cmd({
prompt: settings.prompt,
history: settings.memory ? 'memory' : 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);
});
var $win = $(window);
if (!is_touch) {
// work weird on mobile
$win.on('focus', focus_terminal).
on('blur', blur_terminal);
} else {
/*
self.find('textarea').on('blur.terminal', function() {
if (enabled) {
self.focus(false);
}
});*/
}
if (is_touch) {
self.click(function() {
if (!self.enabled() && !frozen) {
self.focus();
command_line.enable();
} else {
self.focus(false);
}
});
} else {
// detect mouse drag
(function() {
var count = 0;
var isDragging = false;
self.mousedown(function() {
self.oneTime(1, function() {
$(window).mousemove(function() {
isDragging = true;
count = 0;
$(window).unbind('mousemove');
});
});
}).mouseup(function() {
var wasDragging = isDragging;
isDragging = false;
$(window).unbind('mousemove');
if (!wasDragging && ++count == 1) {
count = 0;
if (!self.enabled() && !frozen) {
self.focus();
command_line.enable();
}
}
});
})();
}
self.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) {
// wait until login is initialized
setTimeout(function() {
try {
var hash = location.hash.replace(/^#/, '');
// yes no var - local 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();
}
});
}
init_deferr.resolve();
}); // make_interpreter
self.data('terminal', self);
return self;
}; //terminal plugin
})(jQuery);