UNPKG

node-wings

Version:

node-wings ==========

1,276 lines (1,241 loc) 129 kB
/**@license *| __ _____ ________ __ *| / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / / *| __ / // // // // // _ // _// // / / // _ // _// // // \/ // _ \/ / *| / / // // // // // ___// / / // / / // ___// / / / / // // /\ // // / /__ *| \___//____ \\___//____//_/ _\_ / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/ *| \/ /____/ version 0.7.0 * http://terminal.jcubic.pl * * Licensed under GNU LGPL Version 3 license * Copyright (c) 2011-2013 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: Sat, 13 Jul 2013 19:55:28 +0000 */ /* TODO: 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; }; // ---------------------------------------- // 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 && jQuery.browser.msie || /(msie) ([\w.]+)/.exec(navigator.userAgent.toLowerCase())) { jQuery(window).one('unload', function() { var global = jQuery.timer.global; for (var label in global) { if (global.hasOwnProperty(label)) { var els = global[label], i = els.length; while (--i) { jQuery.timer.remove(els[i], label); } } } }); } // ---------------------------------------- // 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; } // ----------------------------------------------------------------------- // CYCLE DATA STRUCTURE // ----------------------------------------------------------------------- function Cycle(init) { var data = init ? [init] : []; var pos = 0; $.extend(this, { get: function() { return data; }, rotate: function() { if (data.length === 1) { return data[0]; } else { if (pos === data.length - 1) { pos = 0; } else { ++pos; } 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); } }); } // ----------------------------------------------------------------------- // :: 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, size) { var enabled = true; if (typeof name === 'string' && name !== '') { name += '_'; } var data = $.Storage.get(name + 'commands'); data = data ? new Function('return ' + data + ';')() : []; var pos = data.length-1; $.extend(this, { append: function(item) { if (enabled) { if (data[data.length-1] !== item) { data.push(item); pos = data.length-1; if (size && data.length > size) { data = data.slice(-size); } $.Storage.set(name + 'commands', $.json_stringify(data)); } } }, data: function() { return data; }, next: function() { if (pos < data.length-1) { ++pos; } if (pos !== -1) { return data[pos]; } }, reset: function() { pos = data.length-1; }, last: function() { return data[length-1]; }, end: function() { return pos === data.length-1; }, position: function() { return pos; }, previous: function() { var old = pos; if (pos > 0) { --pos; } if (old !== -1) { return data[old]; } }, clear: function() { data = []; $.Storage.remove(name + 'commands'); }, enable: function() { enabled = true; }, purge: function() { $.Storage.remove(name + 'commands'); }, disable: function() { enabled = false; } }); } // ----------------------------------------------------------------------- // :: COMMAND LINE PLUGIN // ----------------------------------------------------------------------- $.fn.cmd = function(options) { var self = this; var maybe_data = self.data('cmd'); if (maybe_data) { return maybe_data; } self.addClass('cmd'); self.append('<span class="prompt"></span><span></span>' + '<span class="cursor">&nbsp;</span><span></span>'); 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 historySize = options.historySize || 60; 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; var len = history_data.length; if (next && reverse_search_position > 0) { len -= reverse_search_position; } if (reverse_search_string.length > 0) { for (var j=reverse_search_string.length; j>0; j--) { regex = new RegExp('^' + reverse_search_string.substring(0, j)); 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(); if (reverse_search_string.length !== j) { reverse_search_string = reverse_search_string.substring(0, j); draw_reverse_prompt(); } return; } } } } } 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) { 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 (first_len === 0 && array.length === 1) { // skip empty line } else if (position < first_len) { draw_cursor_line(array[0], position); lines_after(array.slice(1)); } else if (position === first_len) { before.before(div(array[0])); draw_cursor_line(array[1], 0); lines_after(array.slice(2)); } else { var num_lines = array.length; var offset = 0; if (position < first_len) { draw_cursor_line(array[0], position); lines_after(array.slice(1)); } else if (position === first_len) { before.before(div(array[0])); draw_cursor_line(array[1], 0); lines_after(array.slice(2)); } else { var last = array.slice(-1)[0]; var from_last = string.length - position; 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 last_command; 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(''); }); } //var prevent_keypress = false; function keydown_event(e) { if (typeof options.keydown == 'function') { var result = options.keydown(e); if (result !== undefined) { //prevent_keypress = true; return result; } } 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 === 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 self.set(command.slice(0, position) + command.slice(position).replace(/[^ ]+ |[^ ]+$/, ''), true); // chrome jump to address bar return false; } return true; } else if (e.keyCode === 13) { //enter if ((history && command) && ((options.historyFilter && options.historyFilter(command)) || !options.historyFilter)) { history.append(command); } var tmp = command; history.reset(); self.set(''); if (options.commands) { options.commands(tmp); } if (typeof prompt === 'function') { draw_prompt(); } } 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 if (history.end()) { last_command = command; } self.set(history.previous()); } else if (history && e.which === 40 || (e.which === 78 && e.ctrlKey)) { //DOWN ARROW or CTRL+N self.set(history.end() ? last_command : history.next()); } else if (e.which === 37 || (e.which === 66 && e.ctrlKey)) { //CTRL+LEFT ARROW or CTRL+B if (e.ctrlKey && e.which !== 66) { len = position - 1; pos = 0; if (command[len] === ' ') { --len; } for (var i = len; i > 0; --i) { if (command[i] === ' ' && command[i+1] !== ' ') { pos = i + 1; break; } else if (command[i] === '\n' && command[i+1] !== '\n') { pos = i; break; } } self.position(pos); } else { //LEFT ARROW or CTRL+B if (position > 0) { --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(); last_command = command; command = ''; redraw(); reverse_search = true; } } else if (e.which == 71 && e.ctrlKey) { // CTRL+G if (reverse_search) { prompt = backup_prompt; draw_prompt(); command = last_command; redraw(); reverse_search = false; } } 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.shiftKey && e.which == 45) { // Shift+Insert paste(); return true; } else if (e.ctrlKey || e.metaKey) { if (e.which === 192) { // CMD+` switch browser window on Mac return true; } if (e.metaKey) { if(e.which === 82) // CMD+r page reload in Chrome Mac return true; if(e.which === 76) return true; // CMD+l jump into Ominbox on Chrome Mac } if (e.shiftKey) { // CTRL+SHIFT+?? if (e.which === 84) { //CTRL+SHIFT+T open closed tab return true; } //} else if (e.altKey) { //ALT+CTRL+?? } else { if (e.which === 87) { // CTRL+W if (command !== '') { var first = command.slice(0, position); var last = command.slice(position+1); var m = first.match(/([^ ]+ *$)/); position = first.length-m[0].length; command = first.slice(0, position) + last; redraw(); } return false; } else if (e.which === 72) { // CTRL+H if (command !== '' && position > 0) { command = command.slice(0, --position); if (position < command.length-1) { command += command.slice(position); } redraw(); } return false; //NOTE: in opera charCode is undefined } else if (e.which === 65) { //CTRL+A self.position(0); } else if (e.which === 69) { //CTRL+E self.position(command.length); } else if (e.which === 88 || e.which === 67 || e.which === 84) { //CTRL+X CTRL+C CTRL+W CTRL+T return 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 false; } } } 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; } } */ } var history_list = []; $.extend(self, { name: function(string) { if (string !== undefined) { name = string; history = new History(string, historySize); history_list.push(history); return self; } else { return name; } }, purge: function() { for (var i=history_list.length; i--;) { history_list[i].purge(); } history_list = []; return self; }, 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); } } return self; }, insert: function(string, stay) { if (position === command.length) { command += string; } else if (position === 0) { command = string + command; } else { command = command.slice(0, position) + string + command.slice(position); } if (!stay) { position += string.length; } redraw(); if (typeof options.onCommandChange === 'function') { options.onCommandChange(command); } return self; }, get: function() { return command; }, commands: function(commands) { if (commands) { options.commands = commands; return self; } else { return commands; } }, destroy: function() { $(document.documentElement || window).unbind('.cmd'); self.stopTime('blink', blink); self.find('.cursor').next().remove().end().prev().remove().end().remove(); self.find('.prompt, .clipboard').remove(); self.removeClass('cmd').removeData('cmd'); return self; }, prompt: function(user_prompt) { if (user_prompt === undefined) { return prompt; } else { if (typeof user_prompt === 'string' || typeof user_prompt === 'function') { prompt = user_prompt; } else { throw 'prompt must be a function or string'; } draw_prompt(); // we could check if command is longer then numchars-new prompt redraw(); return self; } }, position: function(n) { if (typeof n === 'number') { position = n < 0 ? 0 : n > command.length ? command.length : n; redraw(); return self; } else { return position; } }, visible: (function() { var visible = self.visible; return function() { visible.apply(self, []); redraw(); draw_prompt(); }; })(), show: (function() { var show = self.show; return function() { show.apply(self, []); redraw(); draw_prompt(); }; })(), resize: function(num) { if (num) { num_chars = num; } else { change_num_chars(); } redraw(); return self; }, enable: function() { if (!enabled) { cursor.addClass('inverted'); self.everyTime(500, 'blink'