/**@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(/&/g, '&'); str = str.replace(/</g, '<').replace(/>/g, '>'); str = str.replace(/	/g, '\t'); str = str.replace(/<br\/?>/g, '\n').replace(/ /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"> </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(' '); 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 === ' ' ? ' ' : $.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(' '); 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, '&') .replace(/</g, '<').replace(/>/g, '>') // I don't think that it find \n .replace(/\n/g, '<br/>') .replace(/ /g, ' ') .replace(/\t/g, ' '); }, 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> </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, '[').replace(/\]/g, ']'); } // 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('[' + label + ']: ' + message); if (typeof e.fileName === 'string') { //display filename and line which throw exeption self.pause(); $.get(e.fileName, function(file) { self.resume(); var num = e.lineNumber - 1; var line = file.split('\n')[num]; if (line) { self.error('[' + e.lineNumber + ']: ' + line); } }); } if (e.stack) { self.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> </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('[RPC] ' + json.error.message); } terminal.resume(); }, function(xhr, status, error) { terminal.error('[AJAX] ' + 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('[AUTH] Access denied (no token)'); } } }; } //display prompt and last command function echo_command(command) { command = command.replace(/\[/g, '[').replace(/\]/g, ']'); 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('[AJAX\ 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);