
javarepl.console.ui.bower_components.jquery.terminal.js.jquery.terminal.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javarepl Show documentation
Show all versions of javarepl Show documentation
Java REPL is a simple Read-Eval-Print-Loop for Java language.
/**@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);
© 2015 - 2025 Weber Informatics LLC | Privacy Policy