cloudcmd/lib/client/terminal/jquery-terminal/jquery.terminal.js
2012-12-25 08:20:22 -05:00

2712 lines
No EOL
107 KiB
JavaScript

/**@license
*| __ _____ ________ __
*| / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / /
*| __ / // // // // // _ // _// // / / // _ // _// // // \/ // _ \/ /
*| / / // // // // // ___// / / // / / // ___// / / / / // // /\ // // / /__
*| \___//____ \\___//____//_/ _\_ / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/
*| \/ /____/ version 0.4.22
* http://terminal.jcubic.pl
*
* Licensed under GNU LGPL Version 3 license
* Copyright (c) 2011-2012 Jakub Jankiewicz <http://jcubic.pl>
*
* Includes:
*
* Storage plugin Distributed under the MIT License
* Copyright (c) 2010 Dave Schindler
*
* jQuery Timers licenced with the WTFPL
* <http://jquery.offput.ca/every/>
*
* Cross-Browser Split 1.1.1
* Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
* Available under the MIT License
*
* Date: Thu, 15 Nov 2012 07:12:21 +0000
*/
/*
TODO:
add destroy method to terminal (cmd alrady have it)
add support for - $(...).each(function() { ... });
$.fn.pluginname = function(options) {
var settings = $.extend({}, $.fn.pluginname.defaultOptions, options);
return this.each(function() {
var $this = $(this);
});
$.fn.pluginname.defaultOptions = {
};
};
distinguish between paused and disabled
paused should block keydown in terminal it should disable command line
disable
*/
(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;
};
// debug function
function get_stack(caller) {
"use strict";
if (caller) {
return [caller.toString().match(/.*\n.*\n/)].
concat(get_stack(caller.caller));
} else {
return [];
}
}
// ----------------------------------------
// START Storage plugin
// ----------------------------------------
// Private data
var isLS = typeof window.localStorage !== 'undefined';
// 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
}
});
// ----------------------------------------
// END Storage plugin
// ----------------------------------------
// START jQuery Timers
// ----------------------------------------
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 (jQuery.browser.msie) {
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);
}
}
}
});
}
// ----------------------------------------
// START 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;
})();
// -----------------------------------------------------------------------
/*
function decodeHTML(str) {
if (typeof str === 'string') {
str = str.replace(/&amp;/g, '&');
str = str.replace(/&lt;/g, '<').replace(/&gt;/g, '>');
str = str.replace(/&#09;/g, '\t');
str = str.replace(/<br\/?>/g, '\n').replace(/&nbsp;/g, ' ');
return str;
} else {
return '';
}
}
*/
//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];
}
for (var i = 0; i < len; i += length) {
result.push(str.substring(i, i + length));
}
return result;
}
// -----------------------------------------------------------------------
function skipFormattingCount(string) {
return $('<div>' + $.terminal.strip(string) + '</div>').text().length;
}
// -----------------------------------------------------------------------
function formattingCount(string) {
return string.length - skipFormattingCount(string);
}
// -----------------------------------------------------------------------
// CYCLE DATA STRUCTURE
// -----------------------------------------------------------------------
function Cycle(init) {
var data = init ? [init] : [];
var pos = 0;
$.extend(this, {
rotate: function() {
if (data.length === 1) {
return data[0];
} else {
if (pos === data.length - 1) {
pos = 0;
} else {
++pos;
}
return data[pos];
}
},
length: function() {
return data.length;
},
set: function(item) {
for (var i = data.length; i--;) {
if (data[i] === item) {
pos = i;
return;
}
}
this.append(item);
},
front: function() {
return data[pos];
},
append: function(item) {
data.push(item);
}
});
}
// -----------------------------------------------------------------------
// :: BCYCLE DATA STRUCTURE // Two way cycle
// -----------------------------------------------------------------------
function BCycle(init) {
var data = init instanceof Array ? init : init ? [init] : [];
var pos = 0;
$.extend(this, {
left: function() {
if (pos === 0) {
pos = data.length - 1;
} else {
--pos;
}
return data[pos];
},
right: function() {
if (pos === data.length - 1) {
pos = 0;
} else {
++pos;
}
return data[pos];
},
current: function() {
return data[pos];
},
data: function() {
return data;
},
length: function() {
return data.length;
},
reset: function() {
pos = 0;
},
append: function(item) {
data.push(item);
this.reset();
}
});
}
// -----------------------------------------------------------------------
// :: STACK DATA STRUCTURE
// -----------------------------------------------------------------------
function Stack(init) {
var data = init ? [init] : [];
$.extend(this, {
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;
}
});
}
// serialize object myself (biwascheme or prototype library do something
// wiked with JSON serialization for Arrays)
$.json_stringify = function(object, level) {
var result = '', i;
level = level === undefined ? 1 : level;
var type = typeof object;
switch (type) {
case 'function':
result += object;
break;
case 'boolean':
result += object ? 'true' : 'false';
break;
case 'object':
if (object === null) {
result += 'null';
} else if (object instanceof Array) {
result += '[';
var len = object.length;
for (i = 0; i < len - 1; ++i) {
result += $.json_stringify(object[i], level + 1);
}
result += $.json_stringify(object[len - 1], level + 1) + ']';
} else {
result += '{';
for (var property in object) {
if (object.hasOwnProperty(property)) {
result += '"' + property + '":' +
$.json_stringify(object[property], level + 1);
}
}
result += '}';
}
break;
case 'string':
var str = object;
var repl = {
'\\\\': '\\\\',
'"': '\\"',
'/': '\\/',
'\\n': '\\n',
'\\r': '\\r',
'\\t': '\\t'};
for (i in repl) {
if (repl.hasOwnProperty(i)) {
str = str.replace(new RegExp(i, 'g'), repl[i]);
}
}
result += '"' + str + '"';
break;
case 'number':
result += String(object);
break;
}
result += (level > 1 ? ',' : '');
// quick hacks below
if (level === 1) {
// fix last comma
result = result.replace(/,([\]}])/g, '$1');
}
// fix comma before array or object
return result.replace(/([\[{]),/g, '$1');
};
// -----------------------------------------------------------------------
// :: HISTORY CLASS
// -----------------------------------------------------------------------
function History(name) {
var enabled = true;
if (typeof name === 'string' && name !== '') {
name += '_';
}
var data = $.Storage.get(name + 'commands');
var bc = new BCycle(data ? eval('(' + data + ')') : ['']);
$.extend(this, {
append: function(item) {
if (enabled) {
bc.append(item);
$.Storage.set(name + 'commands', $.json_stringify(bc.data()));
}
},
data: function() {
return bc.data();
},
next: function() {
return bc.right();
},
last: function() {
bc.reset();
},
previous: function() {
return bc.left();
},
clear: function() {
bc = new BCycle();
$.Storage.remove(name + 'commands');
},
enable: function() {
enabled = true;
},
disable: function() {
enabled = false;
}
});
}
// -----------------------------------------------------------------------
// :: COMMAND LINE PLUGIN
// -----------------------------------------------------------------------
$.fn.cmd = function(options) {
var self = this;
self.addClass('cmd');
self.append('<span class="prompt"></span><span></span>' +
'<span class="cursor">&nbsp;</span><span></span>');
var clip = $('<textarea/>').addClass('clipboard').appendTo(self);
if (options.width) {
self.width(options.width);
}
var num_chars; // calculates by draw_prompt
var prompt_len;
var reverse_search = false;
var reverse_search_string = '';
var reverse_search_position = null;
var backup_prompt;
var mask = options.mask || false;
var command = '';
var position = 0;
var prompt;
var enabled = options.enabled;
var name, history;
var cursor = self.find('.cursor');
function blink(i) {
cursor.toggleClass('inverted');
}
function draw_reverse_prompt() {
prompt = "(reverse-i-search)`" + reverse_search_string + "': ";
draw_prompt();
}
function clear_reverse_state() {
prompt = backup_prompt;
reverse_search = false;
reverse_search_position = null;
reverse_search_string = '';
}
// if next is not defined or false it search for first item from the end
// if true it search for next item
function reverse_history_search(next) {
var history_data = history.data();
var regex = new RegExp('^' + reverse_search_string);
var len = history_data.length;
if (next && reverse_search_position > 0) {
len -= reverse_search_position;
}
for (var i=len; i--;) {
if (regex.test(history_data[i])) {
reverse_search_position = history_data.length - i;
position = 0;
self.set(history_data[i], true);
redraw();
break;
}
}
}
function change_num_chars() {
var W = self.width();
var w = cursor.innerWidth();
num_chars = Math.floor(W / w);
}
function str_repeat(str, n) {
var result = '';
for (var i = n; i--;) {
result += str;
}
return result;
}
function get_splited_command_line(string) {
/*
string = str_repeat('x', prompt_len) + string;
var result = $.terminal.split_equal(string);
result[0] = result[0].substring(prompt_len);
return result;
*/
var first = string.substring(0, num_chars - prompt_len);
var rest = string.substring(num_chars - prompt_len);
return [first].concat(str_parts(rest, num_chars));
}
var redraw = (function(self) {
var before = cursor.prev();
var after = cursor.next();
function draw_cursor_line(string, position) {
var len = string.length;
if (position === len) {
before.html($.terminal.encode(string));
cursor.html('&nbsp;');
after.html('');
} else if (position === 0) {
before.html('');
//fix for tilda in IE
cursor.html($.terminal.encode(string.slice(0, 1)));
//cursor.html($.terminal.encode(string[0]));
after.html($.terminal.encode(string.slice(1)));
} else {
var before_str = $.terminal.encode(string.slice(0, position));
before.html(before_str);
//fix for tilda in IE
var c = string.slice(position, position + 1);
//cursor.html(string[position]));
cursor.html(c === ' ' ? '&nbsp;' : $.terminal.encode(c));
if (position === string.length - 1) {
after.html('');
} else {
after.html($.terminal.encode(string.slice(position + 1)));
}
}
}
function div(string) {
return '<div>' + $.terminal.encode(string) + '</div>';
}
function lines_after(lines) {
var last_ins = after;
$.each(lines, function(i, line) {
last_ins = $(div(line)).insertAfter(last_ins).
addClass('clear');
});
}
function lines_before(lines) {
$.each(lines, function(i, line) {
before.before(div(line));
});
}
var count = 0;
return function() {
var string = mask ? command.replace(/./g, '*') : command;
var i, first_len;
self.find('div').remove();
before.html('');
// long line
if (string.length > num_chars - prompt_len - 1 ||
string.match(/\n/)) {
var array;
var tabs = string.match(/\t/g);
var tabs_rm = tabs ? tabs.length * 3 : 0;
//quick tabulation hack
if (tabs) {
string = string.replace(/\t/g, '\x00\x00\x00\x00');
}
// command contain new line characters
if (string.match(/\n/)) {
var tmp = string.split("\n");
first_len = num_chars - prompt_len - 1;
// empty character after each line
for (i=0; i<tmp.length-1; ++i) {
tmp[i] += ' ';
}
// split first line
if (tmp[0].length > first_len) {
array = [tmp[0].substring(0, first_len)];
array = array.concat(str_parts(tmp[0].substring(first_len), num_chars));
} else {
array = [tmp[0]];
}
// process rest of the lines
for (i=1; i<tmp.length; ++i) {
if (tmp[i].length > num_chars) {
array = array.concat(str_parts(tmp[i], num_chars));
} else {
array.push(tmp[i]);
}
}
} else {
array = get_splited_command_line(string);
}
if (tabs) {
array = $.map(array, function(line) {
return line.replace(/\x00\x00\x00\x00/g, '\t');
});
}
first_len = array[0].length;
//cursor in first line
if (position < first_len) {
draw_cursor_line(array[0], position);
lines_after(array.slice(1));
} else if (position === first_len) {
before.before(div(array[0]));
draw_cursor_line(array[1], 0);
lines_after(array.slice(2));
} else {
var num_lines = array.length;
var offset = 0;
if (position < first_len) {
draw_cursor_line(array[0], position);
lines_after(array.slice(1));
} else if (position === first_len) {
before.before(div(array[0]));
draw_cursor_line(array[1], 0);
lines_after(array.slice(2));
} else {
var last = array.slice(-1)[0];
var from_last = string.length - position;
var last_len = last.length;
var pos = 0;
if (from_last <= last_len) {
lines_before(array.slice(0, -1));
pos = last_len === from_last ? 0 : last_len-from_last;
draw_cursor_line(last, pos+tabs_rm);
} else {
// in the middle
if (num_lines === 3) {
before.before('<div>' + $.terminal.encode(array[0]) +
'</div>');
draw_cursor_line(array[1], position-first_len-1);
after.after('<div class="clear">' +
$.terminal.encode(array[2]) +
'</div>');
} else {
// more lines, cursor in the middle
var line_index;
var current;
pos = position;
for (i=0; i<array.length; ++i) {
var current_len = array[i].length;
if (pos > current_len) {
pos -= current_len;
} else {
break;
}
}
current = array[i];
line_index = i;
// cursor on first character in line
if (pos === current.length) {
pos = 0;
current = array[++line_index];
}
draw_cursor_line(current, pos);
lines_before(array.slice(0, line_index));
lines_after(array.slice(line_index+1));
}
}
}
}
} else {
if (string === '') {
before.html('');
cursor.html('&nbsp;');
after.html('');
} else {
draw_cursor_line(string, position);
}
}
};
})(self);
var draw_prompt = (function() {
var prompt_node = self.find('.prompt');
return function() {
if (typeof prompt === 'string') {
prompt_len = skipFormattingCount(prompt);
prompt_node.html($.terminal.format(prompt));
} else {
prompt(function(string) {
prompt_len = skipFormattingCount(string);
prompt_node.html($.terminal.format(string));
});
}
//change_num_chars();
};
})();
// paste content to terminal using hidden textarea
function paste() {
clip.focus();
//wait until Browser insert text to textarea
self.oneTime(1, function() {
self.insert(clip.val());
clip.blur().val('');
});
}
function keydown_event(e) {
if (options.keydown && options.keydown(e) === false) {
return false;
}
if (enabled) {
var pos, len, result;
// arrows / Home / End / ENTER
if (reverse_search && (e.which === 35 || e.which === 36 ||
e.which === 37 || e.which === 38 ||
e.which === 39 || e.which === 40 ||
e.which === 66 || e.which === 13 ||
e.which === 27)) {
clear_reverse_state();
draw_prompt();
if (e.which === 27) { // ESC
command = '';
}
redraw();
// finish reverse search and execute normal event handler
keydown_event.call(this, e);
} else if (e.altKey) {
// Chrome on Windows set ctrlKey and altKey for alt
// need to check for alt first
//if (e.which === 18) { // press ALT
if (e.which === 68) { //ALT+D
var regex = /[^ ]+ |[^ ]+$/;
self.set(command.slice(0, position) +
command.slice(position).replace(regex, ''),
true);
// chrome jump to address bar
return false;
}
return true;
} else if (e.keyCode === 13) { //enter
if ((history && command) &&
((options.historyFilter &&
options.historyFilter(command)) ||
!options.historyFilter)) {
if (history.data().slice(-1)[0] !== command) {
history.append(command);
}
}
history.last();
var tmp = command;
self.set('');
if (options.commands) {
options.commands(tmp);
}
if (typeof prompt === 'function') {
draw_prompt();
}
} else if (e.which === 32) { //space
if (reverse_search) {
reverse_search_string += ' ';
draw_reverse_prompt();
} else {
self.insert(' ');
}
} else if (e.which === 8) { //backspace
if (reverse_search) {
reverse_search_string = reverse_search_string.slice(0, -1);
draw_reverse_prompt();
} else {
if (command !== '' && position > 0) {
command = command.slice(0, position - 1) +
command.slice(position, command.length);
--position;
redraw();
}
}
} else if (e.which === 9 && !(e.ctrlKey || e.altKey)) { // TAB
self.insert('\t');
} else if (e.which === 46) {
//DELETE
if (command !== '' && position < command.length) {
command = command.slice(0, position) +
command.slice(position + 1, command.length);
redraw();
}
return true;
} else if (history && e.which === 38 ||
(e.which === 80 && e.ctrlKey)) {
//UP ARROW or CTRL+P
self.set(history.previous());
} else if (history && e.which === 40 ||
(e.which === 78 && e.ctrlKey)) {
//DOWN ARROW or CTRL+N
self.set(history.next());
} else if (e.which === 37 ||
(e.which === 66 && e.ctrlKey)) {
//CTRL+LEFT ARROW or CTRL+B
if (e.ctrlKey && e.which !== 66) {
len = position - 1;
pos = 0;
if (command[len] === ' ') {
--len;
}
for (var i = len; i > 0; --i) {
if (command[i] === ' ' && command[i+1] !== ' ') {
pos = i + 1;
break;
} else if (command[i] === '\n' && command[i+1] !== '\n') {
pos = i;
break;
}
}
self.position(pos);
} else {
//LEFT ARROW or CTRL+B
if (position > 0) {
--position;
redraw();
}
}
} else if (e.which === 82 && e.ctrlKey) { // CTRL+R
if (reverse_search) {
reverse_history_search(true);
} else {
backup_prompt = prompt;
draw_reverse_prompt();
command = '';
redraw();
reverse_search = true;
}
} else if (e.which === 39 ||
(e.which === 70 && e.ctrlKey)) {
//RIGHT ARROW OR CTRL+F
if (e.ctrlKey && e.which !== 70) {
// jump to beginig or end of the word
if (command[position] === ' ') {
++position;
}
var match = command.slice(position).match(/\S[\n\s]{2,}|[\n\s]+\S?/);
if (!match || match[0].match(/^\s+$/)) {
position = command.length;
} else {
if (match[0][0] !== ' ') {
position += match.index + 1;
} else {
position += match.index + match[0].length - 1;
if (match[0][match[0].length-1] !== ' ') {
--position;
}
}
}
redraw();
} else {
if (position < command.length) {
++position;
redraw();
}
}
} else if (e.which === 123) { //F12 - Allow Firebug
return true;
} else if (e.which === 36) { //HOME
self.position(0);
} else if (e.which === 35) {
//END
self.position(command.length);
} else if (e.ctrlKey || e.metaKey) {
if (e.shiftKey) { // CTRL+SHIFT+??
if (e.which === 84) {
//CTRL+SHIFT+T open closed tab
return true;
}
//} else if (e.altKey) { //ALT+CTRL+??
} else {
//NOTE: in opera charCode is undefined
if (e.which === 65) {
//CTRL+A
self.position(0);
} else if (e.which === 69) {
//CTRL+E
self.position(command.length);
} else if (e.which === 88 || e.which === 67 ||
e.which === 87 || e.which === 84) {
//CTRL+X CTRL+C CTRL+W CTRL+T
return true;
} else if (e.which === 86) {
//CTRL+V
paste();
return true;
} else if (e.which === 75) {
//CTRL+K
if (position === 0) {
self.set('');
} else if (position !== command.length) {
self.set(command.slice(0, position));
}
} else if (e.which === 85) { // CTRL+U
self.set(command.slice(position, command.length));
self.position(0);
} else if (e.which === 17) { //CTRL+TAB switch tab
return true;
}
}
} else {
return true;
}
return false;
} /*else {
if ((e.altKey && e.which === 68) ||
(e.ctrlKey &&
$.inArray(e.which, [65, 66, 68, 69, 80, 78, 70]) > -1) ||
// 68 === D
[35, 36, 37, 38, 39, 40].has(e.which)) {
return false;
}
} */
}
$.extend(self, {
name: function(string) {
if (string !== undefined) {
name = string;
history = new History(string);
} else {
return name;
}
},
history: function() {
return history;
},
set: function(string, stay) {
if (string !== undefined) {
command = string;
if (!stay) {
position = command.length;
}
redraw();
if (typeof options.onCommandChange === 'function') {
options.onCommandChange(command);
}
}
},
insert: function(string, stay) {
if (position === command.length) {
command += string;
} else if (position === 0) {
command = string + command;
} else {
command = command.slice(0, position) +
string + command.slice(position);
}
if (!stay) {
position += string.length;
}
redraw();
if (typeof options.onCommandChange === 'function') {
options.onCommandChange(command);
}
},
get: function() {
return command;
},
commands: function(commands) {
if (commands) {
options.commands = commands;
} else {
return commands;
}
},
destroy: function() {
$(document.documentElement).unbind('.commandline');
self.find('.prompt').remove();
},
prompt: function(user_prompt) {
if (user_prompt === undefined) {
return prompt;
} else {
if (typeof user_prompt === 'string' ||
typeof user_prompt === 'function') {
prompt = user_prompt;
} else {
throw 'prompt must be a function or string';
}
draw_prompt();
// we could check if command is longer then numchars-new prompt
redraw();
}
},
position: function(n) {
if (typeof n === 'number') {
position = n < 0 ? 0 : n > command.length ? command.length : n;
redraw();
} else {
return position;
}
},
visible: (function() {
var visible = self.visible;
return function() {
visible.apply(self, []);
redraw();
draw_prompt();
};
})(),
show: (function() {
var show = self.show;
return function() {
show.apply(self, []);
redraw();
draw_prompt();
};
})(),
resize: function(num) {
if (num) {
num_chars = num;
} else {
change_num_chars();
}
redraw();
},
enable: function() {
if (!enabled) {
cursor.addClass('inverted');
self.everyTime(500, 'blink', blink);
enabled = true;
}
},
isenabled: function() {
return enabled;
},
disable: function() {
if (enabled) {
self.stopTime('blink', blink);
cursor.removeClass('inverted');
enabled = false;
}
},
mask: function(display) {
if (typeof display === 'boolean') {
mask = display;
redraw();
} else {
return mask;
}
}
});
// INIT
self.name(options.name || '');
prompt = options.prompt || '> ';
draw_prompt();
if (options.enabled === undefined || options.enabled === true) {
self.enable();
}
// Keystrokes
//document.documentElement
var object;
if ($.browser.msie) {
object = document.documentElement;
} else {
object = window;
}
$(object).keypress(function(e) {
var result;
if (e.ctrlKey && e.which === 99) {
return true;
}
if (!reverse_search && options.keypress) {
result = options.keypress(e);
}
if (result === undefined || result) {
if (enabled) {
if ($.inArray(e.which, [38, 32, 13, 0, 8]) > -1 &&
e.keyCode !== 123 && // for F12 which === 0
//!(e.which === 40 && e.shiftKey ||
!(e.which === 38 && e.shiftKey)) {
return false;
} else if (!e.ctrlKey && !(e.altKey && e.which === 100)) {
// TODO: this should be in one statement
if (reverse_search) {
reverse_search_string += String.fromCharCode(e.which);
draw_reverse_prompt();
reverse_history_search();
} else {
self.insert(String.fromCharCode(e.which));
}
return false;
} else if (e.altKey) {
if (reverse_search) {
reverse_search_string += String.fromCharCode(e.which);
draw_reverse_prompt();
reverse_history_search();
} else {
self.insert(String.fromCharCode(e.which));
}
}
}
} else {
return result;
}
}).keydown(keydown_event);
// characters
return self;
};
// -------------------------------------------------------------------------
// :: TOOLS
// -------------------------------------------------------------------------
var format_split_re = /(\[\[[gbius]*;[^;]*;[^\]]*\](?:[^\]\[]*|\[*(?!\[)[^\]]*\][^\]]*)\])/g;
var format_re = /\[\[([gbius]*);([^;]*);([^;\]]*;|[^\]]*);?([^\]]*)\]([^\]\[]*|[^\[]*\[(?!\[)*[^\]]*\][^\]]*)\]/g;
var color_hex_re = /#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})/;
var url_re = /(https?:((?!&[^;]+;)[^\s:"'<)])+)/g;
var email_regex = /((([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,})))/g;
$.terminal = {
// split text into lines with equal width and make each line be renderd
// separatly (text formating can be longer then a line).
split_equal: function(str, length) {
var array = str.split(/\n/g);
var re_format = /(\[\[[gbius]*;[^;]*;[^\]]*\][^\]\[]*\]?)/g;
var re_begin = /(\[\[[gbius]*;[^;]*;[^\]]*\])/;
var re_last = /\[\[[gbius]*;?[^;]*;?[^\]]*\]?$/;
var formatting = false;
var in_text = false;
var braket = 0;
var prev_format = '';
var result = [];
for (var i = 0, len = array.length; i < len; ++i) {
if (prev_format !== '') {
if (array[i] === '') {
result.push(prev_format + ']');
continue;
} else {
array[i] = prev_format + array[i];
prev_format = '';
}
} else {
if (array[i] === '') {
result.push('');
continue;
}
}
var line = array[i];
var first_index = 0;
var count = 0;
for (var j=0, jlen=line.length; j<jlen; ++j) {
if (line[j] === '[' && line[j+1] === '[') {
formatting = true;
} else if (formatting && line[j] === ']') {
if (in_text) {
formatting = false;
in_text = false;
} else {
in_text = true;
}
} else if ((formatting && in_text) || !formatting) {
++count;
}
if (count === length || j === jlen-1) {
var output_line = line.substring(first_index, j+1);
if (prev_format) {
output_line = prev_format + output_line;
if (output_line.match(']')) {
prev_format = '';
}
}
first_index = j+1;
count = 0;
var matched = output_line.match(re_format);
if (matched) {
var last = matched[matched.length-1];
if (last[last.length-1] !== ']') {
prev_format = last.match(re_begin)[1];
output_line += ']';
} else if (output_line.match(re_last)) {
var line_len = output_line.length;
var f_len = line_len - last[last.length-1].length;
output_line = output_line.replace(re_last, '');
prev_format = last.match(re_begin)[1];
}
}
result.push(output_line);
}
}
}
return result;
},
// encode formating as html for inserto into DOM
encode: function(str) {
// don't escape entities
return str.replace(/&(?!#[0-9]+;|[a-zA-Z]+;)/g, '&amp;')
.replace(/</g, '&lt;').replace(/>/g, '&gt;')
// I don't think that it find \n
.replace(/\n/g, '<br/>')
.replace(/ /g, '&nbsp;')
.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;');
},
format: function(str) {
if (typeof str === 'string') {
str = $.terminal.encode($.terminal.from_ansi(str));
//support for formating foo[[u;;]bar]baz[[b;#fff;]quux]zzz
var splited = str.split(format_split_re);
if (splited && splited.length > 1) {
str = $.map(splited, function(text) {
if (text === '') {
return text;
} else if (text.substring(0,1) === '[') {
// use substring for IE quirks mode - [0] don't work
return text.replace(format_re, function(s,
style,
color,
background,
_class,
text) {
if (text === '') {
return '<span>&nbsp;</span>';
}
var style_str = '';
if (style.indexOf('b') !== -1) {
style_str += 'font-weight:bold;';
}
var text_decoration = 'text-decoration:';
if (style.indexOf('u') !== -1) {
text_decoration += 'underline ';
}
if (style.indexOf('s') !== -1) {
text_decoration += 'line-through';
}
if (style.indexOf('s') !== -1 ||
style.indexOf('u') !== -1) {
style_str += text_decoration + ';';
}
if (style.indexOf('i') !== -1) {
style_str += 'font-style:italic;';
}
if (color.match(color_hex_re)) {
style_str += 'color:' + color + ';';
if (style.indexOf('g') !== -1) {
style_str += 'text-shadow: 0 0 5px ' + color + ';';
}
}
if (background.match(color_hex_re)) {
style_str += 'background-color:' + background;
}
var result = '<span style="' + style_str + '"' +
(_class != '' ? ' class="' + _class + '"' : '') +
'>' + text + '</span>';
return result;
});
} else {
return '<span>' + text + '</span>';
}
}).join('');
}
return str.replace(url_re, function(link) {
var comma = link.match(/\.$/);
link = link.replace(/\.$/, '');
return '<a target="_blank" href="' + link + '">' + link + '</a>' +
(comma ? '.' : '');
}).replace(email_regex, '<a href="mailto:$1">$1</a>').
replace(/<span><br\/?><\/span>/g, '<br/>');
} else {
return '';
}
},
// remove formatting from text
strip: function(str) {
return str.replace(format_re, '$5');
},
// return active terminal
active: function() {
return terminals.front();
},
ansi_colors: {
normal: {
black: '#000',
red: '#AA0000',
green: '#008400',
yellow: '#AA5500',
blue: '#0000AA',
magenta: '#AA00AA',
cyan: '#00AAAA',
white: '#fff'
},
bold: {
white: '#fff',
red: '#FF5555',
green: '#44D544',
yellow: '#FFFF55',
blue: '#5555FF',
magenta: '#FF55FF',
cyan: '#55FFFF',
black: '#000'
}
},
from_ansi: (function() {
var color = {
30: 'black',
31: 'red',
32: 'green',
33: 'yellow',
34: 'blue',
35: 'magenta',
36: 'cyan',
37: 'white'
};
var background = {
40: 'black',
41: 'red',
42: 'green',
43: 'yellow',
44: 'blue',
45: 'magenta',
46: 'cyan',
47: 'white'
};
function format_ansi(code) {
var controls = code.split(';');
var num;
var styles = [];
var output_color = '';
var output_background = '';
for(var i in controls) {
num = parseInt(controls[i], 10);
if (num === 1) {
styles.push('b');
}
if (num === 4) {
styles.push('u');
}
if (background[num]) {
output_background = background[num];
}
if (color[num]) {
output_color = color[num];
}
}
var normal = $.terminal.ansi_colors.normal;
var colors = normal;
for (var i=styles.length;i--;) {
if (styles[i] == 'b') {
if (output_color == '') {
output_color = 'white';
}
colors = $.terminal.ansi_colors.bold;
break;
}
}
return '[[' + [styles.join(''),
colors[output_color],
normal[output_background]
].join(';') + ']';
}
return function(input) {
var splitted = input.split(/(\[[0-9;]*m)/g);
if (splitted.length == 1) {
return input;
}
var output = [];
//skip closing at the begining
if (splitted.length > 3 && splitted.slice(0,3).join('') == '[0m') {
splitted = splitted.slice(3);
}
var inside = false;
for (var i=0; i<splitted.length; ++i) {
var match = splitted[i].match(/^\[([0-9;]*)m$/);
if (match) {
if (match[1] == '') {
continue;
}
if (inside) {
output.push(']');
if (match[1] == '0') {
//just closing
inside = false;
} else {
// someone forget to close - process
output.push(format_ansi(match[1]));
}
} else {
inside = true;
output.push(format_ansi(match[1]));
}
} else {
output.push(splitted[i]);
}
}
if (inside) {
output.push(']');
}
return output.join('');
};
})()
};
// -----------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------
$.fn.visible = function() {
return this.css('visibility', 'visible');
};
$.fn.hidden = function() {
return this.css('visibility', 'hidden');
};
// -----------------------------------------------------------------------
// JSON-RPC CALL
// -----------------------------------------------------------------------
$.jrpc = function(url, id, method, params, success, error) {
var request = $.json_stringify({
'jsonrpc': '2.0', 'method': method,
'params': params, 'id': id});
return $.ajax({
url: url,
data: request,
success: success,
error: error,
contentType: 'application/json',
dataType: 'json',
async: true,
cache: false,
//timeout: 1,
type: 'POST'});
};
// -----------------------------------------------------------------------
// :: TERMINAL PLUGIN CODE
// -----------------------------------------------------------------------
var version = '0.4.22';
var copyright = 'Copyright (c) 2011-2012 Jakub Jankiewicz <http://jcubic.pl>';
var version_string = 'version ' + version;
//regex is for placing version string aligned to the right
var reg = new RegExp(" {" + version_string.length + "}$");
var signatures = [
['jQuery Terminal', '(c) 2011-2012 jcubic'],
['jQuery Terminal Emulator v. ' + version,
copyright.replace(/ *<.*>/, '')],
['jQuery Terminal Emulator version ' + version_string,
copyright],
[' _______ ________ __',
' / / _ /_ ____________ _/__ ___/______________ _____ / /',
' __ / / // / // / _ / _/ // / / / _ / _/ / / \\/ / _ \\/ /',
'/ / / // / // / ___/ // // / / / ___/ // / / / / /\\ / // / /__',
'\\___/____ \\\\__/____/_/ \\__ / /_/____/_//_/ /_/ /_/ \\/\\__\\_\\___/',
' \\/ /____/ '.replace(reg, '') +
version_string,
copyright],
[' __ _____ ________ __',
' / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / /',
' __ / // // // // // _ // _// // / / // _ // _// // // \\/ // _ \\/ /',
'/ / // // // // // ___// / / // / / // ___// / / / / // // /\\ // // / /__',
'\\___//____ \\\\___//____//_/ _\\_ / /_//____//_/ /_/ /_//_//_/ /_/ \\__\\_\\___/',
' \\/ /____/ '.replace(reg, '') +
version_string,
copyright]
];
// for canceling on CTRL+D
var requests = [];
var terminals = new Cycle(); //list of terminals global in this scope
$.fn.terminal = function(init_eval, options) {
var self = this;
var lines = [];
var output;
var terminal_id = terminals.length();
var num_chars; // numer of chars in line
var command_list = []; // for tab completion
var settings = $.extend({
name: '',
prompt: '> ',
history: true,
exit: true,
clear: true,
enabled: true,
displayExceptions: true,
cancelableAjax: true,
login: null,
tabcompletion: null,
historyFilter: null,
onInit: $.noop,
onClear: $.noop,
onBlur: $.noop,
onFocus: $.noop,
onTerminalChange: $.noop,
onExit: $.noop,
keypress: $.noop,
keydown: $.noop
}, options || {});
if (settings.width) {
self.width(settings.width);
}
if (settings.height) {
self.height(settings.height);
}
var pause = !settings.enabled;
if (self.length === 0) {
throw 'Sorry, but terminal said that "' + self.selector +
'" is not valid selector!';
}
// register ajaxSend for cancel requests on CTRL+D
self.ajaxSend(function(e, xhr, opt) {
requests.push(xhr);
});
// terminal already exist
if (self.data('terminal')) {
return self.data('terminal');
}
output = $('<div>').addClass('terminal-output').appendTo(self);
self.addClass('terminal').append('<div/>');
self.click(function() {
self.find('textarea').focus();
});
/*
self.bind('touchstart.touchScroll', function() {
});
self.bind('touchmove.touchScroll', function() {
});
*/
//$('<input type="text"/>').hide().focus().appendTo(self);
//calculate numbers of characters
function haveScrollbars() {
return self.get(0).scrollHeight > self.innerHeight();
}
function get_num_chars() {
var cursor = self.find('.cursor');
var cur_width = cursor.width();
var result = Math.floor(self.width() / cur_width);
if (haveScrollbars()) {
// assume that scrollbars are 20px - in my Laptop with
// Linux/Chrome they are 16px
var margins = self.innerWidth() - self.width();
result -= Math.ceil((20 - margins / 2) / (cur_width-1));
}
return result;
}
function escape_brackets(string) {
return string.replace(/\[/g, '&#91;').replace(/\]/g, '&#93;');
}
// display Exception on terminal
function display_exception(e, label) {
if (settings.displayExceptions) {
var message;
if (typeof e === 'string') {
message = e;
} else {
if (typeof e.fileName === 'string') {
message = e.fileName + ': ' + e.message;
} else {
message = e.message;
}
}
self.error('&#91;' + label + '&#93;: ' + 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('&#91;' + e.lineNumber + '&#93;: ' + line);
}
});
}
if (e.stack) {
self.error(e.stack);
}
}
}
//validating if object is string or function, call that function and
//display exeption if any
function validate(label, object) {
try {
if (typeof object === 'function') {
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;
}
function scroll_to_bottom() {
var scrollHeight = self.prop ? self.prop('scrollHeight') :
self.attr('scrollHeight');
self.scrollTop(scrollHeight);
}
function draw_line(string) {
string = typeof string === 'string' ? string : String(string);
var div, i, len;
if (string.length > num_chars) {
// string can have line break
//var array = string.split('\n');
// TODO: the way it should work
var array = $.terminal.split_equal(string, num_chars);
div = $('<div></div>');
for (i = 0, len = array.length; i < len; ++i) {
if (array[i] === '' || array[i] === '\r') {
div.append('<div>&nbsp;</div>');
} else {
$('<div/>').html($.terminal.format(array[i])).appendTo(div);
}
}
} else {
div = $('<div/>').html($.terminal.format(string));
}
output.append(div);
div.width('100%');
scroll_to_bottom();
return div;
}
function show_greetings() {
if (options.greetings === undefined) {
self.echo(self.signature);
} else if (options.greetings) {
self.echo(options.greetings);
}
}
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));
}
// ----------------------------------------------------------
// TERMINAL METHODS
// ----------------------------------------------------------
var dalyed_commands = [];
$.extend(self, $.omap({
clear: function() {
output.html('');
command_line.set('');
lines = [];
try {
settings.onClear(self);
} catch (e) {
display_exception(e, 'onClear');
throw e;
}
self.attr({ scrollTop: 0});
return self;
},
exec: function(command, silent) {
if (pause) {
dalyed_commands.push([command, silent]);
} else {
commands(command, silent);
}
return self;
},
commands: function() {
return interpreters.top().eval;
},
greetings: function() {
show_greetings();
return self;
},
paused: function() {
return pause;
},
pause: function() {
if (command_line) {
pause = true;
self.disable();
command_line.hidden();
}
return self;
},
resume: function() {
if (command_line) {
self.enable();
var original = dalyed_commands;
dalyed_commands = [];
while (original.length) {
var command = original.shift();
self.exec.apply(self, command);
}
command_line.visible();
scroll_to_bottom();
}
return self;
},
cols: function() {
return num_chars;
},
rows: function() {
return lines.length;
},
history: function() {
return command_line.history();
},
next: function() {
if (terminals.length() === 1) {
return self;
} else {
var offsetTop = self.offset().top;
var height = self.height();
var scrollTop = self.scrollTop();
if (!is_scrolled_into_view(self)) {
self.enable();
$('html,body').animate({scrollTop: offsetTop-50}, 500);
return self;
} else {
terminals.front().disable();
var next = terminals.rotate().enable();
// 100 provides buffer in viewport
var x = next.offset().top - 50;
$('html,body').animate({scrollTop: x}, 500);
try {
settings.onTerminalChange(next);
} catch (e) {
display_exception(e, 'onTerminalChange');
throw e;
}
return next;
}
}
},
// silent used so events are not fired on init
focus: function(toggle, silent) {
self.oneTime(1, function() {
if (terminals.length() === 1) {
if (toggle === false) {
try {
if (!silent && settings.onBlur(self) !== false) {
self.disable();
}
} catch (e) {
display_exception(e, 'onBlur');
throw e;
}
} else {
try {
if (!silent && settings.onFocus(self) !== false) {
self.enable();
}
} catch (e) {
display_exception(e, 'onFocus');
throw e;
}
}
} else {
if (toggle === false) {
self.next();
} else {
var front = terminals.front();
if (front != self) {
front.disable();
if (!silent) {
try {
settings.onTerminalChange(self);
} catch (e) {
display_exception(e, 'onTerminalChange');
throw e;
}
}
}
terminals.set(self);
self.enable();
}
}
});
return self;
},
enable: function() {
if (num_chars === undefined) {
//enabling first time
self.resize();
}
if (pause) {
if (command_line) {
command_line.enable();
pause = false;
}
}
return self;
},
disable: function() {
if (command_line) {
pause = true;
command_line.disable();
}
return self;
},
enabled: function() {
return pause;
},
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 '';
}
},
version: function() {
return version;
},
/* COMMAND LINE FUNCTIONS */
get_command: function() {
return command_line.get();
},
insert: function(string) {
if (typeof string === 'string') {
command_line.insert(string);
return self;
} else {
throw "insert function argument is not a string";
}
},
set_prompt: function(prompt) {
if (validate('prompt', prompt)) {
if (typeof prompt == 'function') {
command_line.prompt(function(command) {
prompt(command, self);
});
} else {
command_line.prompt(prompt);
}
interpreters.top().prompt = prompt;
}
return self;
},
get_prompt: function() {
return interpreters.top().prompt;
// command_line.prompt(); - can be a wrapper
//return command_line.prompt();
},
set_command: function(command) {
command_line.set(command);
return self;
},
set_mask: function(display) {
command_line.mask(display);
return self;
},
get_output: function(raw) {
if (raw) {
return lines;
} else {
return $.map(lines, function(i, item) {
return typeof item == 'function' ? item() : item;
}).join('\n');
}
},
resize: function(width, height) {
if (width && height) {
self.width(width);
self.height(height);
}
num_chars = get_num_chars();
command_line.resize(num_chars);
var o = output.detach();
output.html('');
$.each(lines, function(i, line) {
draw_line(line && typeof line == 'function' ? line() : line);
});
self.prepend(o);
scroll_to_bottom();
return self;
},
echo: function(line) {
lines.push(line);
draw_line(typeof line === 'function' ? line() : line);
on_scrollbar_show_resize();
return self;
},
error: function(message) {
//echo red message
return self.echo('[[;#f00;]' + escape_brackets(message) + ']');
},
scroll: function(amount) {
var pos;
amount = Math.round(amount);
if (self.prop) {
if (amount > self.prop('scrollTop') && amount > 0) {
self.prop('scrollTop', 0);
}
pos = self.prop('scrollTop');
self.prop('scrollTop', pos + amount);
return self;
} else {
if (amount > self.attr('scrollTop') && amount > 0) {
self.attr('scrollTop', 0);
}
pos = self.attr('scrollTop');
self.attr('scrollTop', pos + amount);
return self;
}
},
logout: settings.login ? function() {
while (interpreters.size() > 1) {
interpreters.pop();
}
logout();
return self;
} : function() {
throw "You don't have login function";
},
token: settings.login ? function() {
var name = settings.name;
return $.Storage.get('token' + (name ? '_' + name : ''));
} : $.noop,
login_name: settings.login ? function() {
var name = settings.name;
return $.Storage.get('login' + (name ? '_' + name : ''));
} : $.noop,
name: function() {
return settings.name;
},
push: function(_eval, options) {
if (options && (!options.prompt || validate('prompt', options.prompt)) || !options) {
if (typeof _eval === 'string') {
_eval = make_json_rpc_eval_fun(options['eval'], self);
}
interpreters.push($.extend({'eval': _eval}, options));
prepare_top_interpreter();
}
return self;
},
reset: function() {
self.clear();
while(interpreters.size() > 1) {
interpreters.pop();
}
initialize();
},
pop: function(string) {
if (string !== undefined) {
echo_command(string);
}
if (interpreters.top().name === settings.name) {
if (settings.login) {
logout();
if (typeof settings.onExit === 'function') {
try {
settings.onExit(self);
} catch (e) {
display_exception(e, 'onExit');
throw e;
}
}
}
} else {
var current = interpreters.pop();
prepare_top_interpreter();
if (typeof current.onExit === 'function') {
try {
current.onExit(self);
} catch (e) {
display_exception(e, 'onExit');
throw e;
}
}
}
return self;
}
}, function(_, fun) {
// wrap all functions and display execptions
return function() {
try {
return fun.apply(this, Array.prototype.slice.apply(arguments));
} catch(e) {
display_exception(e, 'TERMINAL');
}
};
}));
//function constructor for eval
function make_json_rpc_eval_fun(url, terminal) {
var id = 1;
var service = function(method, params) {
terminal.pause();
$.jrpc(url, id++, method, params, function(json) {
if (!json.error) {
if (typeof json.result === 'string') {
terminal.echo(json.result);
} else if (json.result instanceof Array) {
terminal.echo(json.result.join(' '));
} else if (typeof json.result === 'object') {
var string = '';
for (var f in json.result) {
if (json.result.hasOwnProperty(f)) {
string += f + ': ' + json.result[f] + '\n';
}
}
terminal.echo(string);
}
} else {
terminal.error('&#91;RPC&#93; ' + json.error.message);
}
terminal.resume();
}, function(xhr, status, error) {
terminal.error('&#91;AJAX&#93; ' + status +
' - Server reponse is: \n' +
xhr.responseText);
terminal.resume();
});
};
//this is eval function
return function(command, terminal) {
if (command === '') {
return;
}
var method, params;
if (!command.match(/[^ ]* /)) {
method = command;
params = [];
} else {
command = command.split(/ +/);
method = command[0];
params = command.slice(1);
}
if (!settings.login || method === 'help') {
service(method, params);
} else {
var token = terminal.token();
if (token) {
service(method, [token].concat(params));
} else {
//should never happen
terminal.error('&#91;AUTH&#93; Access denied (no token)');
}
}
};
}
//display prompt and last command
function echo_command(command) {
command = command.replace(/\[/g, '&#91;').replace(/\]/g, '&#93;');
var prompt = command_line.prompt();
if (command_line.mask()) {
command = command.replace(/./g, '*');
}
if (typeof prompt === 'function') {
prompt(function(string) {
self.echo(string + command);
});
} else {
self.echo(prompt + command);
}
}
// wrapper over eval it implements exit and catch all exeptions
// from user code and display them on terminal
function commands(command, silent) {
try {
var interpreter = interpreters.top();
if (command === 'exit' && settings.exit) {
if (interpreters.size() === 1) {
if (settings.login) {
logout();
} else {
var msg = 'You can exit from main interpeter';
if (!silent) {
echo_command(command);
}
self.echo(msg);
}
} else {
self.pop('exit');
}
} else {
if (!silent) {
echo_command(command);
}
if (command === 'clear' && settings.clear) {
self.clear();
} else {
interpreter['eval'](command, self);
}
}
} catch (e) {
display_exception(e, 'USER');
self.resume();
throw e;
}
}
// functions change prompt of command line to login to password
// and call user login function with callback that set token
// if user call it with value that is true
function login() {
var user = null;
command_line.prompt('login: ');
// don't stor logins in history
if (settings.history) {
command_line.history().disable();
}
command_line.commands(function(command) {
try {
echo_command(command);
if (!user) {
user = command;
command_line.prompt('password: ');
command_line.mask(true);
} else {
command_line.mask(false);
self.pause();
if (typeof settings.login !== 'function') {
throw "Value of login property must be a function";
}
var passwd = command;
settings.login(user, passwd, function(token) {
if (token) {
var name = settings.name;
name = (name ? '_' + name : '');
$.Storage.set('token' + name, token);
$.Storage.set('login' + name, user);
//restore commands and run interpreter
command_line.commands(commands);
// move this to one function init.
initialize();
} else {
self.error('Wrong password try again');
command_line.prompt('login: ');
user = null;
}
self.resume();
if (settings.history) {
command_line.history().enable();
}
});
}
} catch (e) {
display_exception(e, 'LOGIN', self);
throw e;
}
});
}
//logout function remove Storage, disable history and run login function
//this function is call only when options.login function is defined
//check for this is in self.pop method
function logout() {
if (typeof settings.onBeforelogout === 'function') {
try {
if (settings.onBeforelogout(self) == false) {
return;
}
} catch (e) {
display_exception(e, 'onBeforelogout');
throw e;
}
}
var name = settings.name;
name = (name ? '_' + name : '');
$.Storage.remove('token' + name, null);
$.Storage.remove('login' + name, null);
if (settings.history) {
command_line.history().disable();
}
login();
if (typeof settings.onAfterlogout === 'function') {
try {
settings.onAfterlogout(self);
} catch (e) {
display_exception(e, 'onAfterlogout');
throw e;
}
}
}
//function enable history, set prompt, run eval function
function prepare_top_interpreter() {
var interpreter = interpreters.top();
var name = '';
if (interpreter.name !== undefined &&
interpreter.name !== '') {
name += interpreter.name + '_';
}
name += terminal_id;
command_line.name(name);
if (typeof interpreter.prompt == 'function') {
command_line.prompt(function(command) {
interpreter.prompt(command, self);
});
} else {
command_line.prompt(interpreter.prompt);
}
if (settings.history) {
command_line.history().enable();
}
command_line.set('');
if (typeof interpreter.onStart === 'function') {
interpreter.onStart(self);
}
}
function initialize() {
prepare_top_interpreter();
show_greetings();
if (typeof settings.onInit === 'function') {
try {
settings.onInit(self);
} catch (e) {
display_exception(e, 'OnInit');
throw e;
}
}
}
// ---------------------------------------------------------------------
var on_scrollbar_show_resize = (function() {
var scrollBars = haveScrollbars();
return function() {
if (scrollBars !== haveScrollbars()) {
// if scollbars appearance change we will have different
// number of chars
self.resize();
scrollBars = haveScrollbars();
}
};
})();
// ---------------------------------------------------------------------
// KEYDOWN EVENT HANDLER
// ---------------------------------------------------------------------
var tab_count = 0;
function key_down(e) {
var i;
// after text pasted into textarea in cmd plugin
self.oneTime(5, function() {
on_scrollbar_show_resize();
});
if (settings.keydown && settings.keydown(e, self) === false) {
return false;
}
if (!self.paused()) {
if (e.which !== 9) { // not a TAB
tab_count = 0;
}
if (e.which === 68 && e.ctrlKey) { // CTRL+D
if (command_line.get() === '') {
if (interpreters.size() > 1 ||
settings.login !== undefined) {
self.pop('');
} else {
self.resume();
self.echo('');
}
} else {
self.set_command('');
}
return false;
} else if (settings.tabcompletion && e.which === 9) { // TAB
// TODO: move this to cmd plugin
// add tabcompletion = array | function
++tab_count;
var command = command_line.get();
if (!command.match(' ')) { // complete only first word
var reg = new RegExp('^' + command);
var commands = interpreters.top().command_list;
var matched = [];
for (i=commands.length; i--;) {
if (reg.test(commands[i])) {
matched.push(commands[i]);
}
}
if (matched.length === 1) {
self.set_command(matched[0]);
} else if (matched.length > 1) {
if (tab_count >= 2) {
echo_command(command);
self.echo(matched.join('\t'));
tab_count = 0;
}
}
}
return false;
} else if (e.which === 86 && e.ctrlKey) { // CTRL+V
self.oneTime(1, function() {
scroll_to_bottom();
});
return true;
} 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
for (i=requests.length; i--;) {
var r = requests[i];
if (4 !== r.readyState) {
try {
r.abort();
} catch(e) {
self.error('error in aborting ajax');
}
}
}
self.resume();
return false;
}
}
}
// ---------------------------------------------------------------------
// INIT CODE
// ---------------------------------------------------------------------
var url;
if (settings.login && typeof settings.onBeforeLogin === 'function') {
try {
settings.onBeforeLogin(self);
} catch (e) {
display_exception(e, 'onBeforeLogin');
throw e;
}
}
if (typeof init_eval == 'string') {
url = init_eval; //url variable is use when making login function
init_eval = make_json_rpc_eval_fun(init_eval, self);
} else if (typeof init_eval == 'object' && init_eval.constructor === Array) {
throw "You can't use array as eval";
} else if (typeof init_eval === 'object') {
// top commands
for (var i in init_eval) {
if (init_eval.hasOwnProperty(i)) {
command_list.push(i);
}
}
init_eval = (function make_eval(object) {
// function that maps commands to object methods
// it keeps terminal context
return function(command, terminal) {
if (command === '') {
return;
}
command = command.split(/ +/);
var method = command[0];
var params = command.slice(1);
var val = object[method];
var type = typeof val;
if (type === 'function') {
val.apply(self, params);
} else if (type === 'object' || type === 'string') {
var commands = [];
if (type === 'object') {
for (var m in val) {
if (val.hasOwnProperty(m)) {
commands.push(m);
}
}
val = make_eval(val);
}
self.push(val, {
prompt: method + '> ',
name: method,
command_list: commands
});
} else {
self.error("Command '" + method + "' Not Found");
}
};
})(init_eval);
} else if (typeof init_eval !== 'function') {
throw 'Unknow object "' + String(init_eval) + '" passed as eval';
}
// create json-rpc authentication function
if (url && (typeof settings.login === 'string' || settings.login)) {
settings.login = (function(method) {
var id = 1;
return function(user, passwd, callback) {
self.pause();
$.jrpc(url,
id++,
method,
[user, passwd],
function(response) {
self.resume();
if (!response.error && response.result) {
callback(response.result);
} else {
callback(null);
}
}, function(xhr, status, error) {
self.resume();
self.error('&#91;AJAX&#92; Response: ' +
status + '\n' +
xhr.responseText);
});
};
//default name is login so you can pass true
})(typeof settings.login === 'boolean' ? 'login' : settings.login);
}
if (validate('prompt', settings.prompt)) {
var interpreters = new Stack({
name: settings.name,
'eval': init_eval,
prompt: settings.prompt,
command_list: command_list,
greetings: settings.greetings
});
var command_line = self.find('.terminal-output').next().cmd({
prompt: settings.prompt,
history: settings.history,
historyFilter: settings.historyFilter,
width: '100%',
keydown: key_down,
keypress: settings.keypress ? function(e) {
return settings.keypress(e, self);
} : null,
onCommandChange: function(command) {
if (typeof settings.onCommandChange === 'function') {
try {
settings.onCommandChange(command, self);
} catch (e) {
display_exception(e, 'onCommandChange');
throw e;
}
}
scroll_to_bottom();
},
commands: commands
});
//num_chars = get_num_chars();
terminals.append(self);
if (settings.enabled === true) {
self.focus(undefined, true);
} else {
self.disable();
}
$(window).resize(self.resize);
self.click(function() {
if (!(pause && terminals.length() > 1 && self === $.terminal.active())) {
self.focus();
}
});
if (options.login && self.token && !self.token() && self.login_name &&
!self.login_name()) {
login();
} else {
initialize();
}
if (typeof $.fn.init.prototype.mousewheel === 'function') {
self.mousewheel(function(event, delta) {
//self.echo(dir(event));
if (delta > 0) {
self.scroll(-40);
} else {
self.scroll(40);
}
return false;
}, true);
}
}
self.data('terminal', self);
return self;
}; //terminal plugin
})(jQuery);