Added JS files.
[nit.git] / contrib / online_ide / www / js / jquery.terminal-0.8.7.js
1 /**@license
2 * __ _____ ________ __
3 * / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / /
4 * __ / // // // // // _ // _// // / / // _ // _// // // \/ // _ \/ /
5 * / / // // // // // ___// / / // / / // ___// / / / / // // /\ // // / /__
6 * \___//____ \\___//____//_/ _\_ / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/
7 * \/ /____/ version 0.8.7
8 * http://terminal.jcubic.pl
9 *
10 * Licensed under GNU LGPL Version 3 license
11 * Copyright (c) 2011-2013 Jakub Jankiewicz <http://jcubic.pl>
12 *
13 * Includes:
14 *
15 * Storage plugin Distributed under the MIT License
16 * Copyright (c) 2010 Dave Schindler
17 *
18 * jQuery Timers licenced with the WTFPL
19 * <http://jquery.offput.ca/every/>
20 *
21 * Cross-Browser Split 1.1.1
22 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
23 * Available under the MIT License
24 *
25 * sprintf.js
26 * Copyright (c) 2007-2013 Alexandru Marasteanu <hello at alexei dot ro>
27 * licensed under 3 clause BSD license
28 *
29 * Date: Sat, 22 Mar 2014 13:32:26 +0000
30 *
31 */
32
33 (function(ctx) {
34 var sprintf = function() {
35 if (!sprintf.cache.hasOwnProperty(arguments[0])) {
36 sprintf.cache[arguments[0]] = sprintf.parse(arguments[0]);
37 }
38 return sprintf.format.call(null, sprintf.cache[arguments[0]], arguments);
39 };
40
41 sprintf.format = function(parse_tree, argv) {
42 var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
43 for (i = 0; i < tree_length; i++) {
44 node_type = get_type(parse_tree[i]);
45 if (node_type === 'string') {
46 output.push(parse_tree[i]);
47 }
48 else if (node_type === 'array') {
49 match = parse_tree[i]; // convenience purposes only
50 if (match[2]) { // keyword argument
51 arg = argv[cursor];
52 for (k = 0; k < match[2].length; k++) {
53 if (!arg.hasOwnProperty(match[2][k])) {
54 throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
55 }
56 arg = arg[match[2][k]];
57 }
58 }
59 else if (match[1]) { // positional argument (explicit)
60 arg = argv[match[1]];
61 }
62 else { // positional argument (implicit)
63 arg = argv[cursor++];
64 }
65
66 if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
67 throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
68 }
69 switch (match[8]) {
70 case 'b': arg = arg.toString(2); break;
71 case 'c': arg = String.fromCharCode(arg); break;
72 case 'd': arg = parseInt(arg, 10); break;
73 case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
74 case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
75 case 'o': arg = arg.toString(8); break;
76 case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
77 case 'u': arg = arg >>> 0; break;
78 case 'x': arg = arg.toString(16); break;
79 case 'X': arg = arg.toString(16).toUpperCase(); break;
80 }
81 arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
82 pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
83 pad_length = match[6] - String(arg).length;
84 pad = match[6] ? str_repeat(pad_character, pad_length) : '';
85 output.push(match[5] ? arg + pad : pad + arg);
86 }
87 }
88 return output.join('');
89 };
90
91 sprintf.cache = {};
92
93 sprintf.parse = function(fmt) {
94 var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
95 while (_fmt) {
96 if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
97 parse_tree.push(match[0]);
98 }
99 else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
100 parse_tree.push('%');
101 }
102 else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
103 if (match[2]) {
104 arg_names |= 1;
105 var field_list = [], replacement_field = match[2], field_match = [];
106 if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
107 field_list.push(field_match[1]);
108 while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
109 if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
110 field_list.push(field_match[1]);
111 }
112 else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
113 field_list.push(field_match[1]);
114 }
115 else {
116 throw('[sprintf] huh?');
117 }
118 }
119 }
120 else {
121 throw('[sprintf] huh?');
122 }
123 match[2] = field_list;
124 }
125 else {
126 arg_names |= 2;
127 }
128 if (arg_names === 3) {
129 throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
130 }
131 parse_tree.push(match);
132 }
133 else {
134 throw('[sprintf] huh?');
135 }
136 _fmt = _fmt.substring(match[0].length);
137 }
138 return parse_tree;
139 };
140
141 var vsprintf = function(fmt, argv, _argv) {
142 _argv = argv.slice(0);
143 _argv.splice(0, 0, fmt);
144 return sprintf.apply(null, _argv);
145 };
146
147 /**
148 * helpers
149 */
150 function get_type(variable) {
151 return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
152 }
153
154 function str_repeat(input, multiplier) {
155 for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
156 return output.join('');
157 }
158
159 /**
160 * export to either browser or node.js
161 */
162 ctx.sprintf = sprintf;
163 ctx.vsprintf = vsprintf;
164 })(typeof exports != "undefined" ? exports : window);
165
166 (function($, undefined) {
167 "use strict";
168 // -----------------------------------------------------------------------
169 // :: map object to object
170 // -----------------------------------------------------------------------
171 $.omap = function(o, fn) {
172 var result = {};
173 $.each(o, function(k, v) {
174 result[k] = fn.call(o, k, v);
175 });
176 return result;
177 };
178 // -----------------------------------------------------------------------
179 // :: Storage plugin
180 // -----------------------------------------------------------------------
181 // Private data
182 var isLS = typeof window.localStorage !== 'undefined';
183 // Private functions
184 function wls(n, v) {
185 var c;
186 if (typeof n === 'string' && typeof v === 'string') {
187 localStorage[n] = v;
188 return true;
189 } else if (typeof n === 'object' && typeof v === 'undefined') {
190 for (c in n) {
191 if (n.hasOwnProperty(c)) {
192 localStorage[c] = n[c];
193 }
194 }
195 return true;
196 }
197 return false;
198 }
199 function wc(n, v) {
200 var dt, e, c;
201 dt = new Date();
202 dt.setTime(dt.getTime() + 31536000000);
203 e = '; expires=' + dt.toGMTString();
204 if (typeof n === 'string' && typeof v === 'string') {
205 document.cookie = n + '=' + v + e + '; path=/';
206 return true;
207 } else if (typeof n === 'object' && typeof v === 'undefined') {
208 for (c in n) {
209 if (n.hasOwnProperty(c)) {
210 document.cookie = c + '=' + n[c] + e + '; path=/';
211 }
212 }
213 return true;
214 }
215 return false;
216 }
217 function rls(n) {
218 return localStorage[n];
219 }
220 function rc(n) {
221 var nn, ca, i, c;
222 nn = n + '=';
223 ca = document.cookie.split(';');
224 for (i = 0; i < ca.length; i++) {
225 c = ca[i];
226 while (c.charAt(0) === ' ') {
227 c = c.substring(1, c.length);
228 }
229 if (c.indexOf(nn) === 0) {
230 return c.substring(nn.length, c.length);
231 }
232 }
233 return null;
234 }
235 function dls(n) {
236 return delete localStorage[n];
237 }
238 function dc(n) {
239 return wc(n, '', -1);
240 }
241 /**
242 * Public API
243 * $.Storage.set("name", "value")
244 * $.Storage.set({"name1":"value1", "name2":"value2", etc})
245 * $.Storage.get("name")
246 * $.Storage.remove("name")
247 */
248 $.extend({
249 Storage: {
250 set: isLS ? wls : wc,
251 get: isLS ? rls : rc,
252 remove: isLS ? dls : dc
253 }
254 });
255 // -----------------------------------------------------------------------
256 // :: jQuery Timers
257 // -----------------------------------------------------------------------
258 jQuery.fn.extend({
259 everyTime: function(interval, label, fn, times, belay) {
260 return this.each(function() {
261 jQuery.timer.add(this, interval, label, fn, times, belay);
262 });
263 },
264 oneTime: function(interval, label, fn) {
265 return this.each(function() {
266 jQuery.timer.add(this, interval, label, fn, 1);
267 });
268 },
269 stopTime: function(label, fn) {
270 return this.each(function() {
271 jQuery.timer.remove(this, label, fn);
272 });
273 }
274 });
275
276 jQuery.extend({
277 timer: {
278 guid: 1,
279 global: {},
280 regex: /^([0-9]+)\s*(.*s)?$/,
281 powers: {
282 // Yeah this is major overkill...
283 'ms': 1,
284 'cs': 10,
285 'ds': 100,
286 's': 1000,
287 'das': 10000,
288 'hs': 100000,
289 'ks': 1000000
290 },
291 timeParse: function(value) {
292 if (value === undefined || value === null) {
293 return null;
294 }
295 var result = this.regex.exec(jQuery.trim(value.toString()));
296 if (result[2]) {
297 var num = parseInt(result[1], 10);
298 var mult = this.powers[result[2]] || 1;
299 return num * mult;
300 } else {
301 return value;
302 }
303 },
304 add: function(element, interval, label, fn, times, belay) {
305 var counter = 0;
306
307 if (jQuery.isFunction(label)) {
308 if (!times) {
309 times = fn;
310 }
311 fn = label;
312 label = interval;
313 }
314
315 interval = jQuery.timer.timeParse(interval);
316
317 if (typeof interval !== 'number' ||
318 isNaN(interval) ||
319 interval <= 0) {
320 return;
321 }
322 if (times && times.constructor !== Number) {
323 belay = !!times;
324 times = 0;
325 }
326
327 times = times || 0;
328 belay = belay || false;
329
330 if (!element.$timers) {
331 element.$timers = {};
332 }
333 if (!element.$timers[label]) {
334 element.$timers[label] = {};
335 }
336 fn.$timerID = fn.$timerID || this.guid++;
337
338 var handler = function() {
339 if (belay && handler.inProgress) {
340 return;
341 }
342 handler.inProgress = true;
343 if ((++counter > times && times !== 0) ||
344 fn.call(element, counter) === false) {
345 jQuery.timer.remove(element, label, fn);
346 }
347 handler.inProgress = false;
348 };
349
350 handler.$timerID = fn.$timerID;
351
352 if (!element.$timers[label][fn.$timerID]) {
353 element.$timers[label][fn.$timerID] = window.setInterval(handler, interval);
354 }
355
356 if (!this.global[label]) {
357 this.global[label] = [];
358 }
359 this.global[label].push(element);
360
361 },
362 remove: function(element, label, fn) {
363 var timers = element.$timers, ret;
364
365 if (timers) {
366
367 if (!label) {
368 for (var lab in timers) {
369 if (timers.hasOwnProperty(lab)) {
370 this.remove(element, lab, fn);
371 }
372 }
373 } else if (timers[label]) {
374 if (fn) {
375 if (fn.$timerID) {
376 window.clearInterval(timers[label][fn.$timerID]);
377 delete timers[label][fn.$timerID];
378 }
379 } else {
380 for (var _fn in timers[label]) {
381 if (timers[label].hasOwnProperty(_fn)) {
382 window.clearInterval(timers[label][_fn]);
383 delete timers[label][_fn];
384 }
385 }
386 }
387
388 for (ret in timers[label]) {
389 if (timers[label].hasOwnProperty(ret)) {
390 break;
391 }
392 }
393 if (!ret) {
394 ret = null;
395 delete timers[label];
396 }
397 }
398
399 for (ret in timers) {
400 if (timers.hasOwnProperty(ret)) {
401 break;
402 }
403 }
404 if (!ret) {
405 element.$timers = null;
406 }
407 }
408 }
409 }
410 });
411
412 if (/(msie) ([\w.]+)/.exec(navigator.userAgent.toLowerCase())) {
413 jQuery(window).one('unload', function() {
414 var global = jQuery.timer.global;
415 for (var label in global) {
416 if (global.hasOwnProperty(label)) {
417 var els = global[label], i = els.length;
418 while (--i) {
419 jQuery.timer.remove(els[i], label);
420 }
421 }
422 }
423 });
424 }
425 // -----------------------------------------------------------------------
426 // :: CROSS BROWSER SPLIT
427 // -----------------------------------------------------------------------
428
429 (function(undef) {
430
431 // prevent double include
432
433 if (!String.prototype.split.toString().match(/\[native/)) {
434 return;
435 }
436
437 var nativeSplit = String.prototype.split,
438 compliantExecNpcg = /()??/.exec("")[1] === undef, // NPCG: nonparticipating capturing group
439 self;
440
441 self = function (str, separator, limit) {
442 // If `separator` is not a regex, use `nativeSplit`
443 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
444 return nativeSplit.call(str, separator, limit);
445 }
446 var output = [],
447 flags = (separator.ignoreCase ? "i" : "") +
448 (separator.multiline ? "m" : "") +
449 (separator.extended ? "x" : "") + // Proposed for ES6
450 (separator.sticky ? "y" : ""), // Firefox 3+
451 lastLastIndex = 0,
452 // Make `global` and avoid `lastIndex` issues by working with a copy
453 separator2, match, lastIndex, lastLength;
454 separator = new RegExp(separator.source, flags + "g");
455 str += ""; // Type-convert
456 if (!compliantExecNpcg) {
457 // Doesn't need flags gy, but they don't hurt
458 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
459 }
460 /* Values for `limit`, per the spec:
461 * If undefined: 4294967295 // Math.pow(2, 32) - 1
462 * If 0, Infinity, or NaN: 0
463 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
464 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
465 * If other: Type-convert, then use the above rules
466 */
467 // ? Math.pow(2, 32) - 1 : ToUint32(limit)
468 limit = limit === undef ? -1 >>> 0 : limit >>> 0;
469 while (match = separator.exec(str)) {
470 // `separator.lastIndex` is not reliable cross-browser
471 lastIndex = match.index + match[0].length;
472 if (lastIndex > lastLastIndex) {
473 output.push(str.slice(lastLastIndex, match.index));
474 // Fix browsers whose `exec` methods don't consistently return `undefined` for
475 // nonparticipating capturing groups
476 if (!compliantExecNpcg && match.length > 1) {
477 match[0].replace(separator2, function () {
478 for (var i = 1; i < arguments.length - 2; i++) {
479 if (arguments[i] === undef) {
480 match[i] = undef;
481 }
482 }
483 });
484 }
485 if (match.length > 1 && match.index < str.length) {
486 Array.prototype.push.apply(output, match.slice(1));
487 }
488 lastLength = match[0].length;
489 lastLastIndex = lastIndex;
490 if (output.length >= limit) {
491 break;
492 }
493 }
494 if (separator.lastIndex === match.index) {
495 separator.lastIndex++; // Avoid an infinite loop
496 }
497 }
498 if (lastLastIndex === str.length) {
499 if (lastLength || !separator.test("")) {
500 output.push("");
501 }
502 } else {
503 output.push(str.slice(lastLastIndex));
504 }
505 return output.length > limit ? output.slice(0, limit) : output;
506 };
507
508 // For convenience
509 String.prototype.split = function (separator, limit) {
510 return self(this, separator, limit);
511 };
512
513 return self;
514
515 })();
516 // -----------------------------------------------------------------------
517 // :: Split string to array of strings with the same length
518 // -----------------------------------------------------------------------
519 function str_parts(str, length) {
520 var result = [];
521 var len = str.length;
522 if (len < length) {
523 return [str];
524 }
525 for (var i = 0; i < len; i += length) {
526 result.push(str.substring(i, i + length));
527 }
528 return result;
529 }
530 // -----------------------------------------------------------------------
531 // :: CYCLE DATA STRUCTURE
532 // -----------------------------------------------------------------------
533 function Cycle(init) {
534 var data = init ? [init] : [];
535 var pos = 0;
536 $.extend(this, {
537 get: function() {
538 return data;
539 },
540 rotate: function() {
541 if (data.length === 1) {
542 return data[0];
543 } else {
544 if (pos === data.length - 1) {
545 pos = 0;
546 } else {
547 ++pos;
548 }
549 return data[pos];
550 }
551 },
552 length: function() {
553 return data.length;
554 },
555 set: function(item) {
556 for (var i = data.length; i--;) {
557 if (data[i] === item) {
558 pos = i;
559 return;
560 }
561 }
562 this.append(item);
563 },
564 front: function() {
565 return data[pos];
566 },
567 append: function(item) {
568 data.push(item);
569 }
570 });
571 }
572 // -----------------------------------------------------------------------
573 // :: STACK DATA STRUCTURE
574 // -----------------------------------------------------------------------
575 function Stack(init) {
576 var data = init ? [init] : [];
577 $.extend(this, {
578 map: function(fn) {
579 return $.map(data, fn);
580 },
581 size: function() {
582 return data.length;
583 },
584 pop: function() {
585 if (data.length === 0) {
586 return null;
587 } else {
588 var value = data[data.length - 1];
589 data = data.slice(0, data.length - 1);
590 return value;
591 }
592 },
593 push: function(value) {
594 data = data.concat([value]);
595 return value;
596 },
597 top: function() {
598 return data.length > 0 ? data[data.length - 1] : null;
599 }
600 });
601 }
602 // -----------------------------------------------------------------------
603 // :: Serialize object myself (biwascheme or prototype library do something
604 // :: wicked with JSON serialization for Arrays)
605 // -----------------------------------------------------------------------
606 $.json_stringify = function(object, level) {
607 var result = '', i;
608 level = level === undefined ? 1 : level;
609 var type = typeof object;
610 switch (type) {
611 case 'function':
612 result += object;
613 break;
614 case 'boolean':
615 result += object ? 'true' : 'false';
616 break;
617 case 'object':
618 if (object === null) {
619 result += 'null';
620 } else if (object instanceof Array) {
621 result += '[';
622 var len = object.length;
623 for (i = 0; i < len - 1; ++i) {
624 result += $.json_stringify(object[i], level + 1);
625 }
626 result += $.json_stringify(object[len - 1], level + 1) + ']';
627 } else {
628 result += '{';
629 for (var property in object) {
630 if (object.hasOwnProperty(property)) {
631 result += '"' + property + '":' +
632 $.json_stringify(object[property], level + 1);
633 }
634 }
635 result += '}';
636 }
637 break;
638 case 'string':
639 var str = object;
640 var repl = {
641 '\\\\': '\\\\',
642 '"': '\\"',
643 '/': '\\/',
644 '\\n': '\\n',
645 '\\r': '\\r',
646 '\\t': '\\t'};
647 for (i in repl) {
648 if (repl.hasOwnProperty(i)) {
649 str = str.replace(new RegExp(i, 'g'), repl[i]);
650 }
651 }
652 result += '"' + str + '"';
653 break;
654 case 'number':
655 result += String(object);
656 break;
657 }
658 result += (level > 1 ? ',' : '');
659 // quick hacks below
660 if (level === 1) {
661 // fix last comma
662 result = result.replace(/,([\]}])/g, '$1');
663 }
664 // fix comma before array or object
665 return result.replace(/([\[{]),/g, '$1');
666 };
667 // -----------------------------------------------------------------------
668 // :: HISTORY CLASS
669 // -----------------------------------------------------------------------
670 function History(name, size) {
671 var enabled = true;
672 var storage_key = '';
673 if (typeof name === 'string' && name !== '') {
674 storage_key = name + '_';
675 }
676 storage_key += 'commands';
677 var data = $.Storage.get(storage_key);
678 data = data ? $.parseJSON(data) : [];
679 var pos = data.length-1;
680 $.extend(this, {
681 append: function(item) {
682 if (enabled) {
683 if (data[data.length-1] !== item) {
684 data.push(item);
685 if (size && data.length > size) {
686 data = data.slice(-size);
687 }
688 pos = data.length-1;
689 $.Storage.set(storage_key, $.json_stringify(data));
690 }
691 }
692 },
693 data: function() {
694 return data;
695 },
696 reset: function() {
697 pos = data.length-1;
698 },
699 last: function() {
700 return data[length-1];
701 },
702 end: function() {
703 return pos === data.length-1;
704 },
705 position: function() {
706 return pos;
707 },
708 current: function() {
709 return data[pos];
710 },
711 next: function() {
712 if (pos < data.length-1) {
713 ++pos;
714 }
715 if (pos !== -1) {
716 return data[pos];
717 }
718 },
719 previous: function() {
720 var old = pos;
721 if (pos > 0) {
722 --pos;
723 }
724 if (old !== -1) {
725 return data[pos];
726 }
727 },
728 clear: function() {
729 data = [];
730 this.purge();
731 },
732 enabled: function() {
733 return enabled;
734 },
735 enable: function() {
736 enabled = true;
737 },
738 purge: function() {
739 $.Storage.remove(storage_key);
740 },
741 disable: function() {
742 enabled = false;
743 }
744 });
745 }
746 // -----------------------------------------------------------------------
747 // :: COMMAND LINE PLUGIN
748 // -----------------------------------------------------------------------
749 $.fn.cmd = function(options) {
750 var self = this;
751 var maybe_data = self.data('cmd');
752 if (maybe_data) {
753 return maybe_data;
754 }
755 self.addClass('cmd');
756 self.append('<span class="prompt"></span><span></span>' +
757 '<span class="cursor">&nbsp;</span><span></span>');
758 var clip = $('<textarea/>').addClass('clipboard').appendTo(self);
759 if (options.width) {
760 self.width(options.width);
761 }
762 var num_chars; // calculated by draw_prompt
763 var prompt_len;
764 var reverse_search = false;
765 var reverse_search_string = '';
766 var reverse_search_position = null;
767 var backup_prompt;
768 var mask = options.mask || false;
769 var command = '';
770 var selected_text = ''; // text from selection using CTRL+SHIFT+C (as in Xterm)
771 var kill_text = ''; // text from command that kill part of the command
772 var position = 0;
773 var prompt;
774 var enabled = options.enabled;
775 var historySize = options.historySize || 60;
776 var name, history;
777 var cursor = self.find('.cursor');
778 var animation;
779 if (supportAnimations()) {
780 animation = function(toggle) {
781 if (toggle) {
782 cursor.addClass('blink');
783 } else {
784 cursor.removeClass('blink');
785 }
786 };
787 } else {
788 animation = function(toggle) {
789 if (toggle && !enabled) {
790 cursor.addClass('inverted');
791 self.everyTime(500, 'blink', blink);
792 } else if (enabled) {
793 self.stopTime('blink', blink);
794 cursor.removeClass('inverted');
795 }
796 };
797 }
798 // -----------------------------------------------------------------------
799 // :: Blinking cursor function
800 // -----------------------------------------------------------------------
801 function blink(i) {
802 cursor.toggleClass('inverted');
803 }
804 // -----------------------------------------------------------------------
805 // :: Set prompt for reverse search
806 // -----------------------------------------------------------------------
807 function draw_reverse_prompt() {
808 prompt = "(reverse-i-search)`" + reverse_search_string + "': ";
809 draw_prompt();
810 }
811 // -----------------------------------------------------------------------
812 // :: Disable reverse search
813 // -----------------------------------------------------------------------
814 function clear_reverse_state() {
815 prompt = backup_prompt;
816 reverse_search = false;
817 reverse_search_position = null;
818 reverse_search_string = '';
819 }
820 // -----------------------------------------------------------------------
821 // :: Search through command line history. If next is not defined or false
822 // :: it searches for the first item from the end. If true it search for
823 // :: the next item
824 // -----------------------------------------------------------------------
825 function reverse_history_search(next) {
826 var history_data = history.data();
827 var regex, save_string;
828 var len = history_data.length;
829 if (next && reverse_search_position > 0) {
830 len -= reverse_search_position;
831 }
832 if (reverse_search_string.length > 0) {
833 for (var j=reverse_search_string.length; j>0; j--) {
834 save_string = reverse_search_string.substring(0, j).
835 replace(/([.*+{}\[\]?])/g, '\\$1');
836 regex = new RegExp(save_string);
837 for (var i=len; i--;) {
838 if (regex.test(history_data[i])) {
839 reverse_search_position = history_data.length - i;
840 position = 0;
841 self.set(history_data[i], true);
842 redraw();
843 if (reverse_search_string.length !== j) {
844 reverse_search_string = reverse_search_string.substring(0, j);
845 draw_reverse_prompt();
846 }
847 return;
848 }
849 }
850 }
851 }
852 reverse_search_string = ''; // clear if not found any
853 }
854 // -----------------------------------------------------------------------
855 // :: Recalculate number of characters in command line
856 // -----------------------------------------------------------------------
857 function change_num_chars() {
858 var W = self.width();
859 var w = cursor.innerWidth();
860 num_chars = Math.floor(W / w);
861 }
862 // -----------------------------------------------------------------------
863 // :: Return string repeated n times
864 // -----------------------------------------------------------------------
865 function str_repeat(str, n) {
866 var result = '';
867 for (var i = n; i--;) {
868 result += str;
869 }
870 return result;
871 }
872 // -----------------------------------------------------------------------
873 // :: Split String that fit into command line where first line need to
874 // :: fit next to prompt (need to have less characters)
875 // -----------------------------------------------------------------------
876 function get_splited_command_line(string) {
877 var first = string.substring(0, num_chars - prompt_len);
878 var rest = string.substring(num_chars - prompt_len);
879 return [first].concat(str_parts(rest, num_chars));
880 }
881 // -----------------------------------------------------------------------
882 // :: Function that displays the command line. Split long lines and place
883 // :: cursor in the right place
884 // -----------------------------------------------------------------------
885 var redraw = (function(self) {
886 var before = cursor.prev();
887 var after = cursor.next();
888 // -----------------------------------------------------------------------
889 // :: Draw line with the cursor
890 // -----------------------------------------------------------------------
891 function draw_cursor_line(string, position) {
892 var len = string.length;
893 if (position === len) {
894 before.html($.terminal.encode(string, true));
895 cursor.html('&nbsp;');
896 after.html('');
897 } else if (position === 0) {
898 before.html('');
899 //fix for tilda in IE
900 cursor.html($.terminal.encode(string.slice(0, 1), true));
901 //cursor.html($.terminal.encode(string[0]));
902 after.html($.terminal.encode(string.slice(1), true));
903 } else {
904 var before_str = $.terminal.encode(string.slice(0, position), true);
905 before.html(before_str);
906 //fix for tilda in IE
907 var c = string.slice(position, position + 1);
908 //cursor.html(string[position]));
909 cursor.html(c === ' ' ? '&nbsp;' : $.terminal.encode(c, true));
910 if (position === string.length - 1) {
911 after.html('');
912 } else {
913 after.html($.terminal.encode(string.slice(position + 1), true));
914 }
915 }
916 }
917 function div(string) {
918 return '<div>' + $.terminal.encode(string, true) + '</div>';
919 }
920 // -----------------------------------------------------------------------
921 // :: Display lines after the cursor
922 // -----------------------------------------------------------------------
923 function lines_after(lines) {
924 var last_ins = after;
925 $.each(lines, function(i, line) {
926 last_ins = $(div(line)).insertAfter(last_ins).
927 addClass('clear');
928 });
929 }
930 // -----------------------------------------------------------------------
931 // :: Display lines before the cursor
932 // -----------------------------------------------------------------------
933 function lines_before(lines) {
934 $.each(lines, function(i, line) {
935 before.before(div(line));
936 });
937 }
938 var count = 0;
939 // -----------------------------------------------------------------------
940 // :: Redraw function
941 // -----------------------------------------------------------------------
942 return function() {
943 var string = mask ? command.replace(/./g, '*') : command;
944 var i, first_len;
945 self.find('div').remove();
946 before.html('');
947 // long line
948 if (string.length > num_chars - prompt_len - 1 ||
949 string.match(/\n/)) {
950 var array;
951 var tabs = string.match(/\t/g);
952 var tabs_rm = tabs ? tabs.length * 3 : 0;
953 //quick tabulation hack
954 if (tabs) {
955 string = string.replace(/\t/g, '\x00\x00\x00\x00');
956 }
957 // command contains new line characters
958 if (string.match(/\n/)) {
959 var tmp = string.split("\n");
960 first_len = num_chars - prompt_len - 1;
961 // empty character after each line
962 for (i=0; i<tmp.length-1; ++i) {
963 tmp[i] += ' ';
964 }
965 // split first line
966 if (tmp[0].length > first_len) {
967 array = [tmp[0].substring(0, first_len)];
968 array = array.concat(str_parts(tmp[0].substring(first_len), num_chars));
969 } else {
970 array = [tmp[0]];
971 }
972 // process rest of the lines
973 for (i=1; i<tmp.length; ++i) {
974 if (tmp[i].length > num_chars) {
975 array = array.concat(str_parts(tmp[i], num_chars));
976 } else {
977 array.push(tmp[i]);
978 }
979 }
980 } else {
981 array = get_splited_command_line(string);
982 }
983 if (tabs) {
984 array = $.map(array, function(line) {
985 return line.replace(/\x00\x00\x00\x00/g, '\t');
986 });
987 }
988 first_len = array[0].length;
989 //cursor in first line
990 if (first_len === 0 && array.length === 1) {
991 // skip empty line
992 } else if (position < first_len) {
993 draw_cursor_line(array[0], position);
994 lines_after(array.slice(1));
995 } else if (position === first_len) {
996 before.before(div(array[0]));
997 draw_cursor_line(array[1], 0);
998 lines_after(array.slice(2));
999 } else {
1000 var num_lines = array.length;
1001 var offset = 0;
1002 if (position < first_len) {
1003 draw_cursor_line(array[0], position);
1004 lines_after(array.slice(1));
1005 } else if (position === first_len) {
1006 before.before(div(array[0]));
1007 draw_cursor_line(array[1], 0);
1008 lines_after(array.slice(2));
1009 } else {
1010 var last = array.slice(-1)[0];
1011 var from_last = string.length - position;
1012 var last_len = last.length;
1013 var pos = 0;
1014 if (from_last <= last_len) {
1015 lines_before(array.slice(0, -1));
1016 pos = last_len === from_last ? 0 : last_len-from_last;
1017 draw_cursor_line(last, pos+tabs_rm);
1018 } else {
1019 // in the middle
1020 if (num_lines === 3) {
1021 before.before('<div>' + $.terminal.encode(array[0], true) +
1022 '</div>');
1023 draw_cursor_line(array[1], position-first_len-1);
1024 after.after('<div class="clear">' +
1025 $.terminal.encode(array[2], true) +
1026 '</div>');
1027 } else {
1028 // more lines, cursor in the middle
1029 var line_index;
1030 var current;
1031 pos = position;
1032 for (i=0; i<array.length; ++i) {
1033 var current_len = array[i].length;
1034 if (pos > current_len) {
1035 pos -= current_len;
1036 } else {
1037 break;
1038 }
1039 }
1040 current = array[i];
1041 line_index = i;
1042 // cursor on first character in line
1043 if (pos === current.length) {
1044 pos = 0;
1045 current = array[++line_index];
1046 }
1047 draw_cursor_line(current, pos);
1048 lines_before(array.slice(0, line_index));
1049 lines_after(array.slice(line_index+1));
1050 }
1051 }
1052 }
1053 }
1054 } else {
1055 if (string === '') {
1056 before.html('');
1057 cursor.html('&nbsp;');
1058 after.html('');
1059 } else {
1060 draw_cursor_line(string, position);
1061 }
1062 }
1063 };
1064 })(self);
1065 var last_command;
1066 // -----------------------------------------------------------------------
1067 // :: Draw prompt that can be a function or a string
1068 // -----------------------------------------------------------------------
1069 var draw_prompt = (function() {
1070 var prompt_node = self.find('.prompt');
1071 function set(prompt) {
1072 prompt_len = skipFormattingCount(prompt);
1073 prompt_node.html($.terminal.format($.terminal.encode(prompt)));
1074 }
1075 return function() {
1076 switch (typeof prompt) {
1077 case 'string':
1078 set(prompt);
1079 break;
1080 case 'function':
1081 prompt(set);
1082 break;
1083 }
1084 };
1085 })();
1086 // -----------------------------------------------------------------------
1087 // :: Paste content to terminal using hidden textarea
1088 // -----------------------------------------------------------------------
1089 function paste() {
1090 clip.focus();
1091 //wait until Browser insert text to textarea
1092 self.oneTime(1, function() {
1093 self.insert(clip.val());
1094 clip.blur().val('');
1095 });
1096 }
1097 var first_up_history = true;
1098 //var prevent_keypress = false;
1099 // -----------------------------------------------------------------------
1100 // :: Keydown Event Handler
1101 // -----------------------------------------------------------------------
1102 function keydown_event(e) {
1103 var result, pos, len;
1104 if (typeof options.keydown == 'function') {
1105 result = options.keydown(e);
1106 if (result !== undefined) {
1107 //prevent_keypress = true;
1108 return result;
1109 }
1110 }
1111 if (enabled) {
1112 if (e.which !== 38 &&
1113 !(e.which === 80 && e.ctrlKey)) {
1114 first_up_history = true;
1115 }
1116 // arrows / Home / End / ENTER
1117 if (reverse_search && (e.which === 35 || e.which === 36 ||
1118 e.which === 37 || e.which === 38 ||
1119 e.which === 39 || e.which === 40 ||
1120 e.which === 13 || e.which === 27)) {
1121 clear_reverse_state();
1122 draw_prompt();
1123 if (e.which === 27) { // ESC
1124 command = '';
1125 }
1126 redraw();
1127 // finish reverse search and execute normal event handler
1128 keydown_event.call(this, e);
1129 } else if (e.altKey) {
1130 // Chrome on Windows sets ctrlKey and altKey for alt
1131 // need to check for alt first
1132 //if (e.which === 18) { // press ALT
1133 if (e.which === 68) { //ALT+D
1134 self.set(command.slice(0, position) +
1135 command.slice(position).replace(/[^ ]+ |[^ ]+$/, ''),
1136 true);
1137 // chrome jump to address bar
1138 return false;
1139 }
1140 return true;
1141 } else if (e.keyCode === 13) { //enter
1142 if (e.shiftKey) {
1143 self.insert('\n');
1144 } else {
1145 if (history && command && !mask &&
1146 ((typeof options.historyFilter == 'function' &&
1147 options.historyFilter(command)) ||
1148 !options.historyFilter)) {
1149 history.append(command);
1150 }
1151 var tmp = command;
1152 history.reset();
1153 self.set('');
1154 if (options.commands) {
1155 options.commands(tmp);
1156 }
1157 if (typeof prompt === 'function') {
1158 draw_prompt();
1159 }
1160 }
1161 } else if (e.which === 8) { //backspace
1162 if (reverse_search) {
1163 reverse_search_string = reverse_search_string.slice(0, -1);
1164 draw_reverse_prompt();
1165 } else {
1166 if (command !== '' && position > 0) {
1167 command = command.slice(0, position - 1) +
1168 command.slice(position, command.length);
1169 --position;
1170 redraw();
1171 }
1172 }
1173 } else if (e.which === 67 && e.ctrlKey && e.shiftKey) { // CTRL+SHIFT+C
1174 selected_text = getSelectedText();
1175 } else if (e.which === 86 && e.ctrlKey && e.shiftKey) {
1176 if (selected_text !== '') {
1177 self.insert(selected_text);
1178 }
1179 } else if (e.which === 9 && !(e.ctrlKey || e.altKey)) { // TAB
1180 self.insert('\t');
1181 } else if (e.which === 46) {
1182 //DELETE
1183 if (command !== '' && position < command.length) {
1184 command = command.slice(0, position) +
1185 command.slice(position + 1, command.length);
1186 redraw();
1187 }
1188 return true;
1189 } else if (history && e.which === 38 ||
1190 (e.which === 80 && e.ctrlKey)) {
1191 //UP ARROW or CTRL+P
1192 if (first_up_history) {
1193 last_command = command;
1194 self.set(history.current());
1195 } else {
1196 self.set(history.previous());
1197 }
1198 first_up_history = false;
1199 } else if (history && e.which === 40 ||
1200 (e.which === 78 && e.ctrlKey)) {
1201 //DOWN ARROW or CTRL+N
1202 self.set(history.end() ? last_command : history.next());
1203 } else if (e.which === 37 ||
1204 (e.which === 66 && e.ctrlKey)) {
1205 //CTRL+LEFT ARROW or CTRL+B
1206 if (e.ctrlKey && e.which !== 66) {
1207 len = position - 1;
1208 pos = 0;
1209 if (command[len] === ' ') {
1210 --len;
1211 }
1212 for (var i = len; i > 0; --i) {
1213 if (command[i] === ' ' && command[i+1] !== ' ') {
1214 pos = i + 1;
1215 break;
1216 } else if (command[i] === '\n' && command[i+1] !== '\n') {
1217 pos = i;
1218 break;
1219 }
1220 }
1221 self.position(pos);
1222 } else {
1223 //LEFT ARROW or CTRL+B
1224 if (position > 0) {
1225 --position;
1226 redraw();
1227 }
1228 }
1229 } else if (e.which === 82 && e.ctrlKey) { // CTRL+R
1230 if (reverse_search) {
1231 reverse_history_search(true);
1232 } else {
1233 backup_prompt = prompt;
1234 draw_reverse_prompt();
1235 last_command = command;
1236 command = '';
1237 redraw();
1238 reverse_search = true;
1239 }
1240 } else if (e.which == 71 && e.ctrlKey) { // CTRL+G
1241 if (reverse_search) {
1242 prompt = backup_prompt;
1243 draw_prompt();
1244 command = last_command;
1245 redraw();
1246 reverse_search = false;
1247 reverse_search_string = '';
1248 }
1249 } else if (e.which === 39 ||
1250 (e.which === 70 && e.ctrlKey)) {
1251 //RIGHT ARROW OR CTRL+F
1252 if (e.ctrlKey && e.which !== 70) {
1253 // jump to beginning or end of the word
1254 if (command[position] === ' ') {
1255 ++position;
1256 }
1257 var re = /\S[\n\s]{2,}|[\n\s]+\S?/;
1258 var match = command.slice(position).match(re);
1259 if (!match || match[0].match(/^\s+$/)) {
1260 position = command.length;
1261 } else {
1262 if (match[0][0] !== ' ') {
1263 position += match.index + 1;
1264 } else {
1265 position += match.index + match[0].length - 1;
1266 if (match[0][match[0].length-1] !== ' ') {
1267 --position;
1268 }
1269 }
1270 }
1271 redraw();
1272 } else {
1273 if (position < command.length) {
1274 ++position;
1275 redraw();
1276 }
1277 }
1278 } else if (e.which === 123) { //F12 - Allow Firebug
1279 return true;
1280 } else if (e.which === 36) { //HOME
1281 self.position(0);
1282 } else if (e.which === 35) {
1283 //END
1284 self.position(command.length);
1285 } else if (e.shiftKey && e.which == 45) { // Shift+Insert
1286 paste();
1287 return true;
1288 } else if (e.ctrlKey || e.metaKey) {
1289 if (e.which === 192) { // CMD+` switch browser window on Mac
1290 return true;
1291 }
1292 if (e.metaKey) {
1293 if(e.which === 82) { // CMD+r page reload in Chrome Mac
1294 return true;
1295 } else if(e.which === 76) {
1296 return true; // CMD+l jump into Omnibox on Chrome Mac
1297 }
1298 }
1299 if (e.shiftKey) { // CTRL+SHIFT+??
1300 if (e.which === 84) {
1301 //CTRL+SHIFT+T open closed tab
1302 return true;
1303 }
1304 //} else if (e.altKey) { //ALT+CTRL+??
1305 } else {
1306 if (e.which === 81) { // CTRL+W
1307 // don't work in Chromium (can't prevent close tab)
1308 if (command !== '' && position !== 0) {
1309 var first = command.slice(0, position);
1310 var last = command.slice(position+1);
1311 var m = first.match(/([^ ]+ *$)/);
1312 position = first.length-m[0].length;
1313 kill_text = first.slice(position);
1314 command = first.slice(0, position) + last;
1315 redraw();
1316 }
1317 return false;
1318 } else if (e.which === 72) { // CTRL+H
1319 if (command !== '' && position > 0) {
1320 command = command.slice(0, --position);
1321 if (position < command.length-1) {
1322 command += command.slice(position);
1323 }
1324 redraw();
1325 }
1326 return false;
1327 //NOTE: in opera charCode is undefined
1328 } else if (e.which === 65) {
1329 //CTRL+A
1330 self.position(0);
1331 } else if (e.which === 69) {
1332 //CTRL+E
1333 self.position(command.length);
1334 } else if (e.which === 88 || e.which === 67 || e.which === 84) {
1335 //CTRL+X CTRL+C CTRL+W CTRL+T
1336 return true;
1337 } else if (e.which === 89) { // CTRL+Y
1338 if (kill_text !== '') {
1339 self.insert(kill_text);
1340 }
1341 } else if (e.which === 86) {
1342 //CTRL+V
1343 paste();
1344 return true;
1345 } else if (e.which === 75) {
1346 //CTRL+K
1347 if (position === 0) {
1348 kill_text = command;
1349 self.set('');
1350 } else if (position !== command.length) {
1351 kill_text = command.slice(position);
1352 self.set(command.slice(0, position));
1353 }
1354 } else if (e.which === 85) { // CTRL+U
1355 if (command !== '' && position !== 0) {
1356 kill_text = command.slice(0, position);
1357 self.set(command.slice(position, command.length));
1358 self.position(0);
1359 }
1360 } else if (e.which === 17) { //CTRL+TAB switch tab
1361 return false;
1362 }
1363 }
1364 } else {
1365 return true;
1366 }
1367 return false;
1368 } /*else {
1369 if ((e.altKey && e.which === 68) ||
1370 (e.ctrlKey &&
1371 $.inArray(e.which, [65, 66, 68, 69, 80, 78, 70]) > -1) ||
1372 // 68 === D
1373 [35, 36, 37, 38, 39, 40].has(e.which)) {
1374 return false;
1375 }
1376 } */
1377 }
1378 var history_list = [];
1379 // -----------------------------------------------------------------------
1380 // :: Command Line Methods
1381 // -----------------------------------------------------------------------
1382 $.extend(self, {
1383 name: function(string) {
1384 if (string !== undefined) {
1385 name = string;
1386 var enabled = history && history.enabled() || !history;
1387 history = new History(string, historySize);
1388 // disable new history if old was disabled
1389 if (!enabled) {
1390 history.disable();
1391 }
1392 return self;
1393 } else {
1394 return name;
1395 }
1396 },
1397 purge: function() {
1398 history.clear();
1399 return self;
1400 },
1401 history: function() {
1402 return history;
1403 },
1404 set: function(string, stay) {
1405 if (string !== undefined) {
1406 command = string;
1407 if (!stay) {
1408 position = command.length;
1409 }
1410 redraw();
1411 if (typeof options.onCommandChange === 'function') {
1412 options.onCommandChange(command);
1413 }
1414 }
1415 return self;
1416 },
1417 insert: function(string, stay) {
1418 if (position === command.length) {
1419 command += string;
1420 } else if (position === 0) {
1421 command = string + command;
1422 } else {
1423 command = command.slice(0, position) +
1424 string + command.slice(position);
1425 }
1426 if (!stay) {
1427 position += string.length;
1428 }
1429 redraw();
1430 if (typeof options.onCommandChange === 'function') {
1431 options.onCommandChange(command);
1432 }
1433 return self;
1434 },
1435 get: function() {
1436 return command;
1437 },
1438 commands: function(commands) {
1439 if (commands) {
1440 options.commands = commands;
1441 return self;
1442 } else {
1443 return commands;
1444 }
1445 },
1446 destroy: function() {
1447 $(document.documentElement || window).unbind('.cmd');
1448 self.stopTime('blink', blink);
1449 self.find('.cursor').next().remove().end().prev().remove().end().remove();
1450 self.find('.prompt, .clipboard').remove();
1451 self.removeClass('cmd').removeData('cmd');
1452 return self;
1453 },
1454 prompt: function(user_prompt) {
1455 if (user_prompt === undefined) {
1456 return prompt;
1457 } else {
1458 if (typeof user_prompt === 'string' ||
1459 typeof user_prompt === 'function') {
1460 prompt = user_prompt;
1461 } else {
1462 throw 'prompt must be a function or string';
1463 }
1464 draw_prompt();
1465 // we could check if command is longer then numchars-new prompt
1466 redraw();
1467 return self;
1468 }
1469 },
1470 kill_text: function() {
1471 return kill_text;
1472 },
1473 position: function(n) {
1474 if (typeof n === 'number') {
1475 position = n < 0 ? 0 : n > command.length ? command.length : n;
1476 redraw();
1477 return self;
1478 } else {
1479 return position;
1480 }
1481 },
1482 visible: (function() {
1483 var visible = self.visible;
1484 return function() {
1485 visible.apply(self, []);
1486 redraw();
1487 draw_prompt();
1488 };
1489 })(),
1490 show: (function() {
1491 var show = self.show;
1492 return function() {
1493 show.apply(self, []);
1494 redraw();
1495 draw_prompt();
1496 };
1497 })(),
1498 resize: function(num) {
1499 if (num) {
1500 num_chars = num;
1501 } else {
1502 change_num_chars();
1503 }
1504 redraw();
1505 return self;
1506 },
1507 enable: function() {
1508 enabled = true;
1509 animation(true);
1510 return self;
1511 },
1512 isenabled: function() {
1513 return enabled;
1514 },
1515 disable: function() {
1516 enabled = false;
1517 animation(false);
1518 return self;
1519 },
1520 mask: function(display) {
1521 if (typeof display === 'boolean') {
1522 mask = display;
1523 redraw();
1524 return self;
1525 } else {
1526 return mask;
1527 }
1528 }
1529 });
1530 // -----------------------------------------------------------------------
1531 // :: INIT
1532 // -----------------------------------------------------------------------
1533 self.name(options.name || options.prompt || '');
1534 prompt = options.prompt || '> ';
1535 draw_prompt();
1536 if (options.enabled === undefined || options.enabled === true) {
1537 self.enable();
1538 }
1539 // Keystrokes
1540 var object;
1541 $(document.documentElement || window).bind('keypress.cmd', function(e) {
1542 var result;
1543 if (e.ctrlKey && e.which === 99) { // CTRL+C
1544 return true;
1545 }
1546 if (!reverse_search && typeof options.keypress === 'function') {
1547 result = options.keypress(e);
1548 }
1549 if (result === undefined || result) {
1550 if (enabled) {
1551 if ($.inArray(e.which, [38, 13, 0, 8]) > -1 &&
1552 e.keyCode !== 123 && // for F12 which === 0
1553 //!(e.which === 40 && e.shiftKey ||
1554 !(e.which === 38 && e.shiftKey)) {
1555 return false;
1556 } else if (!e.ctrlKey && !(e.altKey && e.which === 100) || e.altKey) { // ALT+D
1557 // TODO: this should be in one statement
1558 if (reverse_search) {
1559 reverse_search_string += String.fromCharCode(e.which);
1560 reverse_history_search();
1561 draw_reverse_prompt();
1562 } else {
1563 self.insert(String.fromCharCode(e.which));
1564 }
1565 return false;
1566 }
1567 }
1568 } else {
1569 return result;
1570 }
1571 }).bind('keydown.cmd', keydown_event);
1572 // characters
1573 self.data('cmd', self);
1574 return self;
1575 }; // cmd plugin
1576
1577 // -------------------------------------------------------------------------
1578 // :: TOOLS
1579 // -------------------------------------------------------------------------
1580 function skipFormattingCount(string) {
1581 // this will covert html entities to single characters
1582 return $('<div>' + $.terminal.strip(string) + '</div>').text().length;
1583 }
1584 // -------------------------------------------------------------------------
1585 function formattingCount(string) {
1586 return string.length - skipFormattingCount(string);
1587 }
1588 // -------------------------------------------------------------------------
1589 // taken from https://hacks.mozilla.org/2011/09/detecting-and-generating-css-animations-in-javascript/
1590 function supportAnimations() {
1591 var animation = false,
1592 animationstring = 'animation',
1593 keyframeprefix = '',
1594 domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
1595 pfx = '',
1596 elm = document.createElement('div');
1597 if (elm.style.animationName) { animation = true; }
1598 if (animation === false) {
1599 for (var i = 0; i < domPrefixes.length; i++) {
1600 if (elm.style[ domPrefixes[i] + 'AnimationName' ] !== undefined) {
1601 pfx = domPrefixes[i];
1602 animationstring = pfx + 'Animation';
1603 keyframeprefix = '-' + pfx.toLowerCase() + '-';
1604 animation = true;
1605 break;
1606 }
1607 }
1608 }
1609 return animation;
1610 }
1611 // -------------------------------------------------------------------------
1612 function processCommand(string, fn) {
1613 var args = string.replace(/^\s+|\s+$/g, '').split(/(\s+)/);
1614 var rest = string.replace(/^[^\s]+\s*/, '');
1615 return {
1616 name: args[0],
1617 args: fn(rest),
1618 rest: rest
1619 };
1620 }
1621 // colors from http://www.w3.org/wiki/CSS/Properties/color/keywords
1622 var color_names = [
1623 'black', 'silver', 'gray', 'white', 'maroon', 'red', 'purple',
1624 'fuchsia', 'green', 'lime', 'olive', 'yellow', 'navy', 'blue', 'teal',
1625 'aqua', 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure',
1626 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet',
1627 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral',
1628 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan',
1629 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki',
1630 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred',
1631 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray',
1632 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink',
1633 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick',
1634 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite',
1635 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'grey', 'honeydew',
1636 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender',
1637 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral',
1638 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen',
1639 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen',
1640 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue',
1641 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon',
1642 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple',
1643 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',
1644 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream',
1645 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive',
1646 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod',
1647 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip',
1648 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red',
1649 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown',
1650 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue',
1651 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan',
1652 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white',
1653 'whitesmoke', 'yellow', 'yellowgreen'];
1654 // -------------------------------------------------------------------------
1655 var format_split_re = /(\[\[[gbiuso]*;[^;]*;[^\]]*\](?:[^\]]*\\\][^\]]*|[^\]]*|[^\[]*\[[^\]]*)\]?)/i;
1656 var format_parts_re = /\[\[([gbiuso]*);([^;]*);([^;\]]*);?([^;\]]*);?([^\]]*)\]([^\]]*\\\][^\]]*|[^\]]*|[^\[]*\[[^\]]*)\]?/gi;
1657 var format_re = /\[\[([gbiuso]*;[^;\]]*;[^;\]]*(?:;|[^\]()]*);?[^\]]*)\]([^\]]*\\\][^\]]*|[^\]]*|[^\[]*\[[^\]]*)\]?/gi;
1658 var format_full_re = /^\[\[([gbiuso]*;[^;\]]*;[^;\]]*(?:;|[^\]()]*);?[^\]]*)\]([^\]]*\\\][^\]]*|[^\]]*|[^\[]*\[[^\]]*)\]$/gi;
1659 var color_hex_re = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
1660 //var url_re = /https?:\/\/(?:(?!&[^;]+;)[^\s:"'<>)])+/g;
1661 var url_re = /\bhttps?:\/\/(?:(?!&[^;]+;)[^\s"'<>)])+\b/g;
1662 var email_re = /((([^<>('")[\]\\.,;:\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;
1663 var command_re = /('[^']*'|"(\\"|[^"])*"|\/(\\\/|[^\/])+\/[gimy]*|(\\ |[^ ])+|[\w-]+)/g;
1664 var format_begin_re = /(\[\[[gbiuso]*;[^;]*;[^\]]*\])/i;
1665 var format_last_re = /\[\[[gbiuso]*;[^;]*;[^\]]*\]?$/i;
1666 $.terminal = {
1667 // -----------------------------------------------------------------------
1668 // :: Validate html color (it can be name or hex)
1669 // -----------------------------------------------------------------------
1670 valid_color: function(color) {
1671 return color.match(color_hex_re) || $.inArray(color.toLowerCase(), color_names) !== -1;
1672 },
1673 // -----------------------------------------------------------------------
1674 // :: Escape all special regex characters, so it can be use as regex to
1675 // :: match exact string that contain those characters
1676 // -----------------------------------------------------------------------
1677 escape_regex: function(string) {
1678 var special = /([\^\$\[\]\(\)\+\*\.\|])/g;
1679 return string.replace(special, '\\$1');
1680 },
1681 // -----------------------------------------------------------------------
1682 // :: test if string contain formatting
1683 // -----------------------------------------------------------------------
1684 have_formatting: function(str) {
1685 return str.match(format_re);
1686 },
1687 is_formatting: function(str) {
1688 return str.match(format_full_re);
1689 },
1690 // -----------------------------------------------------------------------
1691 // :: return array of formatting and text between them
1692 // -----------------------------------------------------------------------
1693 format_split: function(str) {
1694 return str.split(format_split_re);
1695 },
1696 // -----------------------------------------------------------------------
1697 // :: split text into lines with equal length so each line can be rendered
1698 // :: separately (text formatting can be longer then a line).
1699 // -----------------------------------------------------------------------
1700 split_equal: function(str, length) {
1701 var formatting = false;
1702 var in_text = false;
1703 var braket = 0;
1704 var prev_format = '';
1705 var result = [];
1706 // add format text as 5th paramter to formatting it's used for
1707 // data attribute in format function
1708 var array = str.replace(format_re, function(_, format, text) {
1709 var semicolons = format.match(/;/g).length;
1710 // missing semicolons
1711 if (semicolons == 2) {
1712 semicolons = ';;';
1713 } else if (semicolons == 3) {
1714 semicolons = ';';
1715 } else {
1716 semicolons = '';
1717 }
1718 // return '[[' + format + ']' + text + ']';
1719 // closing braket will break formatting so we need to escape those using
1720 // html entity equvalent
1721 return '[[' + format + semicolons +
1722 text.replace(/\\\]/g, '&#93;').replace(/\n/g, '\\n') + ']' +
1723 text + ']';
1724 }).split(/\n/g);
1725 for (var i = 0, len = array.length; i < len; ++i) {
1726 if (array[i] === '') {
1727 result.push('');
1728 continue;
1729 }
1730 var line = array[i];
1731 var first_index = 0;
1732 var count = 0;
1733 for (var j=0, jlen=line.length; j<jlen; ++j) {
1734 if (line[j] === '[' && line[j+1] === '[') {
1735 formatting = true;
1736 } else if (formatting && line[j] === ']') {
1737 if (in_text) {
1738 formatting = false;
1739 in_text = false;
1740 } else {
1741 in_text = true;
1742 }
1743 } else if ((formatting && in_text) || !formatting) {
1744 if (line[j] === '&') { // treat entity as one character
1745 var m = line.substring(j).match(/^(&[^;]+;)/);
1746 if (!m) {
1747 // should never happen if used by terminal, because
1748 // it always calls $.terminal.encode before this function
1749 throw "Unclosed html entity in line " + (i+1) + ' at char ' + (j+1);
1750 }
1751 j+=m[1].length-2; // because continue adds 1 to j
1752 // if entity is at the end there is no next loop - issue #77
1753 if (j === jlen-1) {
1754 result.push(output_line + m[1]);
1755 }
1756 continue;
1757 } else if (line[j] === ']' && line[j-1] === '\\') {
1758 // escape \] counts as one character
1759 --count;
1760 } else {
1761 ++count;
1762 }
1763 }
1764 if (count === length || j === jlen-1) {
1765 var output_line = line.substring(first_index, j+1);
1766 if (prev_format) {
1767 output_line = prev_format + output_line;
1768 if (output_line.match(']')) {
1769 prev_format = '';
1770 }
1771 }
1772 first_index = j+1;
1773 count = 0;
1774 // Fix output_line if formatting not closed
1775 var matched = output_line.match(format_re);
1776 if (matched) {
1777 var last = matched[matched.length-1];
1778 if (last[last.length-1] !== ']') {
1779 prev_format = last.match(format_begin_re)[1];
1780 output_line += ']';
1781 } else if (output_line.match(format_last_re)) {
1782 var line_len = output_line.length;
1783 var f_len = line_len - last[last.length-1].length;
1784 output_line = output_line.replace(format_last_re, '');
1785 prev_format = last.match(format_begin_re)[1];
1786 }
1787 }
1788 result.push(output_line);
1789 }
1790 }
1791 }
1792 return result;
1793 },
1794 // -----------------------------------------------------------------------
1795 // :: Encode formating as html for insertion into DOM
1796 // -----------------------------------------------------------------------
1797 encode: function(str, full) {
1798 // don't escape entities
1799 if (full) {
1800 str = str.replace(/&(?![^=]+=)/g, '&amp;');
1801 } else {
1802 str = str.replace(/&(?!#[0-9]+;|[a-zA-Z]+;|[^= "]+=[^=])/g, '&amp;');
1803 }
1804 return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
1805 .replace(/ /g, '&nbsp;')
1806 .replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;');
1807 },
1808 // -----------------------------------------------------------------------
1809 // :: Replace terminal formatting with html
1810 // -----------------------------------------------------------------------
1811 format: function(str, options) {
1812 var settings = $.extend({}, {
1813 linksNoReferrer: false
1814 }, options || {});
1815 if (typeof str === 'string') {
1816 //support for formating foo[[u;;]bar]baz[[b;#fff;]quux]zzz
1817 var splited = $.terminal.format_split(str);
1818 if (splited && splited.length > 1) {
1819 str = $.map(splited, function(text) {
1820 if (text === '') {
1821 return text;
1822 } else if (text.substring(0,1) === '[') {
1823 // use substring for IE quirks mode - [0] don't work
1824 return text.replace(format_parts_re, function(s,
1825 style,
1826 color,
1827 background,
1828 _class,
1829 data_text,
1830 text) {
1831 if (text === '') {
1832 return ''; //'<span>&nbsp;</span>';
1833 }
1834 text = text.replace(/\\]/g, ']');
1835 var style_str = '';
1836 if (style.indexOf('b') !== -1) {
1837 style_str += 'font-weight:bold;';
1838 }
1839 var text_decoration = [];
1840 if (style.indexOf('u') !== -1) {
1841 text_decoration.push('underline');
1842 }
1843 if (style.indexOf('s') !== -1) {
1844 text_decoration.push('line-through');
1845 }
1846 if (style.indexOf('o') !== -1) {
1847 text_decoration.push('overline');
1848 }
1849 if (text_decoration.length) {
1850 style_str += 'text-decoration:' +
1851 text_decoration.join(' ') + ';';
1852 }
1853 if (style.indexOf('i') !== -1) {
1854 style_str += 'font-style:italic;';
1855 }
1856 if ($.terminal.valid_color(color)) {
1857 style_str += 'color:' + color + ';';
1858 if (style.indexOf('g') !== -1) {
1859 style_str += 'text-shadow:0 0 5px ' + color + ';';
1860 }
1861 }
1862 if ($.terminal.valid_color(background)) {
1863 style_str += 'background-color:' + background;
1864 }
1865 var data;
1866 if (data_text === '') {
1867 data = text;
1868 } else {
1869 data = data_text.replace(/&#93;/g, ']');
1870 }
1871 var result = '<span style="' + style_str + '"' +
1872 (_class !== '' ? ' class="' + _class + '"' : '') +
1873 ' data-text="'+ data.replace('"', '&quote;') +
1874 '">' + text + '</span>';
1875 return result;
1876 });
1877 } else {
1878 return '<span>' + text + '</span>';
1879 }
1880 }).join('');
1881 }
1882 return $.map(str.split(/(<\/?span[^>]*>)/g), function(string) {
1883 if (!string.match(/span/)) {
1884 return string.replace(url_re, function(link) {
1885 var comma = link.match(/\.$/);
1886 link = link.replace(/\.$/, '');
1887 return '<a target="_blank" ' +
1888 (settings.linksNoReferer ? ' rel="noreferrer" ' : '') +
1889 'href="' + link + '">' + link + '</a>' +
1890 (comma ? '.' : '');
1891 }).replace(email_re, '<a href="mailto:$1">$1</a>');
1892 } else {
1893 return string;
1894 }
1895 }).join('').replace(/<span><br\/?><\/span>/g, '<br/>');
1896 } else {
1897 return '';
1898 }
1899 },
1900 // -----------------------------------------------------------------------
1901 // :: Replace brackets with html entities
1902 // -----------------------------------------------------------------------
1903 escape_brackets: function(string) {
1904 return string.replace(/\[/g, '&#91;').replace(/\]/g, '&#93;');
1905 },
1906 // -----------------------------------------------------------------------
1907 // :: Remove formatting from text
1908 // -----------------------------------------------------------------------
1909 strip: function(str) {
1910 return str.replace(format_parts_re, '$6');
1911 },
1912 // -----------------------------------------------------------------------
1913 // :: Return active terminal
1914 // -----------------------------------------------------------------------
1915 active: function() {
1916 return terminals.front();
1917 },
1918 // -----------------------------------------------------------------------
1919 // :: Replace overtyping (from man) formatting with terminal formatting
1920 // -----------------------------------------------------------------------
1921 overtyping: function(string) {
1922 return string.replace(/((?:_\x08.|.\x08_)+)/g, function(full, g) {
1923 return '[[u;;]' + full.replace(/_x08|\x08_|_\u0008|\u0008_/g, '') + ']';
1924 }).replace(/((?:.\x08.)+)/g, function(full, g) {
1925 return '[[b;#fff;]' + full.replace(/(.)(?:\x08|\u0008)(.)/g,
1926 function(full, g1, g2) {
1927 return g2;
1928 }) + ']';
1929 });
1930 },
1931 // -----------------------------------------------------------------------
1932 // :: Html colors taken from ANSI formatting in Linux Terminal
1933 // -----------------------------------------------------------------------
1934 ansi_colors: {
1935 normal: {
1936 black: '#000',
1937 red: '#A00',
1938 green: '#008400',
1939 yellow: '#A50',
1940 blue: '#00A',
1941 magenta: '#A0A',
1942 cyan: '#0AA',
1943 white: '#AAA'
1944 },
1945 faited: {
1946 black: '#000',
1947 red: '#640000',
1948 green: '#006100',
1949 yellow: '#737300',
1950 blue: '#000087',
1951 magenta: '#650065',
1952 cyan: '#008787',
1953 white: '#818181'
1954 },
1955 bold: {
1956 black: '#000',
1957 red: '#F55',
1958 green: '#44D544',
1959 yellow: '#FF5',
1960 blue: '#55F',
1961 magenta: '#F5F',
1962 cyan: '#5FF',
1963 white: '#FFF'
1964 },
1965 // XTerm 8-bit pallete
1966 palette: [
1967 '#000000', '#AA0000', '#00AA00', '#AA5500', '#0000AA',
1968 '#AA00AA', '#00AAAA', '#AAAAAA', '#555555', '#FF5555',
1969 '#55FF55', '#FFFF55', '#5555FF', '#FF55FF', '#55FFFF',
1970 '#FFFFFF', '#000000', '#00005F', '#000087', '#0000AF',
1971 '#0000D7', '#0000FF', '#005F00', '#005F5F', '#005F87',
1972 '#005FAF', '#005FD7', '#005FFF', '#008700', '#00875F',
1973 '#008787', '#0087AF', '#0087D7', '#00AF00', '#00AF5F',
1974 '#00AF87', '#00AFAF', '#00AFD7', '#00AFFF', '#00D700',
1975 '#00D75F', '#00D787', '#00D7AF', '#00D7D7', '#00D7FF',
1976 '#00FF00', '#00FF5F', '#00FF87', '#00FFAF', '#00FFD7',
1977 '#00FFFF', '#5F0000', '#5F005F', '#5F0087', '#5F00AF',
1978 '#5F00D7', '#5F00FF', '#5F5F00', '#5F5F5F', '#5F5F87',
1979 '#5F5FAF', '#5F5FD7', '#5F5FFF', '#5F8700', '#5F875F',
1980 '#5F8787', '#5F87AF', '#5F87D7', '#5F87FF', '#5FAF00',
1981 '#5FAF5F', '#5FAF87', '#5FAFAF', '#5FAFD7', '#5FAFFF',
1982 '#5FD700', '#5FD75F', '#5FD787', '#5FD7AF', '#5FD7D7',
1983 '#5FD7FF', '#5FFF00', '#5FFF5F', '#5FFF87', '#5FFFAF',
1984 '#5FFFD7', '#5FFFFF', '#870000', '#87005F', '#870087',
1985 '#8700AF', '#8700D7', '#8700FF', '#875F00', '#875F5F',
1986 '#875F87', '#875FAF', '#875FD7', '#875FFF', '#878700',
1987 '#87875F', '#878787', '#8787AF', '#8787D7', '#8787FF',
1988 '#87AF00', '#87AF5F', '#87AF87', '#87AFAF', '#87AFD7',
1989 '#87AFFF', '#87D700', '#87D75F', '#87D787', '#87D7AF',
1990 '#87D7D7', '#87D7FF', '#87FF00', '#87FF5F', '#87FF87',
1991 '#87FFAF', '#87FFD7', '#87FFFF', '#AF0000', '#AF005F',
1992 '#AF0087', '#AF00AF', '#AF00D7', '#AF00FF', '#AF5F00',
1993 '#AF5F5F', '#AF5F87', '#AF5FAF', '#AF5FD7', '#AF5FFF',
1994 '#AF8700', '#AF875F', '#AF8787', '#AF87AF', '#AF87D7',
1995 '#AF87FF', '#AFAF00', '#AFAF5F', '#AFAF87', '#AFAFAF',
1996 '#AFAFD7', '#AFAFFF', '#AFD700', '#AFD75F', '#AFD787',
1997 '#AFD7AF', '#AFD7D7', '#AFD7FF', '#AFFF00', '#AFFF5F',
1998 '#AFFF87', '#AFFFAF', '#AFFFD7', '#AFFFFF', '#D70000',
1999 '#D7005F', '#D70087', '#D700AF', '#D700D7', '#D700FF',
2000 '#D75F00', '#D75F5F', '#D75F87', '#D75FAF', '#D75FD7',
2001 '#D75FFF', '#D78700', '#D7875F', '#D78787', '#D787AF',
2002 '#D787D7', '#D787FF', '#D7AF00', '#D7AF5F', '#D7AF87',
2003 '#D7AFAF', '#D7AFD7', '#D7AFFF', '#D7D700', '#D7D75F',
2004 '#D7D787', '#D7D7AF', '#D7D7D7', '#D7D7FF', '#D7FF00',
2005 '#D7FF5F', '#D7FF87', '#D7FFAF', '#D7FFD7', '#D7FFFF',
2006 '#FF0000', '#FF005F', '#FF0087', '#FF00AF', '#FF00D7',
2007 '#FF00FF', '#FF5F00', '#FF5F5F', '#FF5F87', '#FF5FAF',
2008 '#FF5FD7', '#FF5FFF', '#FF8700', '#FF875F', '#FF8787',
2009 '#FF87AF', '#FF87D7', '#FF87FF', '#FFAF00', '#FFAF5F',
2010 '#FFAF87', '#FFAFAF', '#FFAFD7', '#FFAFFF', '#FFD700',
2011 '#FFD75F', '#FFD787', '#FFD7AF', '#FFD7D7', '#FFD7FF',
2012 '#FFFF00', '#FFFF5F', '#FFFF87', '#FFFFAF', '#FFFFD7',
2013 '#FFFFFF', '#080808', '#121212', '#1C1C1C', '#262626',
2014 '#303030', '#3A3A3A', '#444444', '#4E4E4E', '#585858',
2015 '#626262', '#6C6C6C', '#767676', '#808080', '#8A8A8A',
2016 '#949494', '#9E9E9E', '#A8A8A8', '#B2B2B2', '#BCBCBC',
2017 '#C6C6C6', '#D0D0D0', '#DADADA', '#E4E4E4', '#EEEEEE'
2018 ]
2019 },
2020 // -----------------------------------------------------------------------
2021 // :: Replace ANSI formatting with terminal formatting
2022 // -----------------------------------------------------------------------
2023 from_ansi: (function() {
2024 var color = {
2025 30: 'black',
2026 31: 'red',
2027 32: 'green',
2028 33: 'yellow',
2029 34: 'blue',
2030 35: 'magenta',
2031 36: 'cyan',
2032 37: 'white',
2033
2034 39: 'white' // default color
2035 };
2036 var background = {
2037 40: 'black',
2038 41: 'red',
2039 42: 'green',
2040 43: 'yellow',
2041 44: 'blue',
2042 45: 'magenta',
2043 46: 'cyan',
2044 47: 'white',
2045
2046 49: 'black' // default background
2047 };
2048 function format_ansi(code) {
2049 var controls = code.split(';');
2050 var num;
2051 var faited = false;
2052 var reverse = false;
2053 var bold = false;
2054 var styles = [];
2055 var output_color = '';
2056 var output_background = '';
2057 var _8bit_color = false;
2058 var _8bit_background = false;
2059 var process_8bit = false;
2060 var palette = $.terminal.ansi_colors.palette;
2061 for(var i in controls) {
2062 num = parseInt(controls[i], 10);
2063 switch(num) {
2064 case 1:
2065 styles.push('b');
2066 bold = true;
2067 faited = false;
2068 break;
2069 case 4:
2070 styles.push('u');
2071 break;
2072 case 3:
2073 styles.push('i');
2074 break;
2075 case 5:
2076 process_8bit = true;
2077 break;
2078 case 38:
2079 _8bit_color = true;
2080 break;
2081 case 48:
2082 _8bit_background = true;
2083 break;
2084 case 2:
2085 faited = true;
2086 bold = false;
2087 break;
2088 case 7:
2089 reverse = true;
2090 break;
2091 default:
2092 if (_8bit_color && process_8bit && palette[num-1]) {
2093 output_color = palette[num-1];
2094 } else if (color[num]) {
2095 output_color = color[num];
2096 }
2097 if (_8bit_background && process_8bit && palette[num-1]) {
2098 output_background = palette[num-1];
2099 } else if (background[num]) {
2100 output_background = background[num];
2101 }
2102 }
2103 if (num !== 5) {
2104 process_8bit = false;
2105 }
2106 }
2107 if (reverse) {
2108 if (output_color && output_background) {
2109 var tmp = output_background;
2110 output_background = output_color;
2111 output_color = tmp;
2112 } else {
2113 output_color = 'black';
2114 output_background = 'white';
2115 }
2116 }
2117 var colors, backgrounds;
2118 if (bold) {
2119 colors = backgrounds = $.terminal.ansi_colors.bold;
2120 } else if (faited) {
2121 colors = backgrounds = $.terminal.ansi_colors.faited;
2122 } else {
2123 colors = backgrounds = $.terminal.ansi_colors.normal;
2124 }
2125 return [styles.join(''),
2126 _8bit_color ? output_color : colors[output_color],
2127 _8bit_background ? output_background : backgrounds[output_background]
2128 ];
2129 }
2130 return function(input) {
2131 var splitted = input.split(/(\x1B\[[0-9;]*[A-Za-z])/g);
2132 if (splitted.length == 1) {
2133 return input;
2134 }
2135 var output = [];
2136 //skip closing at the begining
2137 if (splitted.length > 3 && splitted.slice(0,3).join('') == '[0m') {
2138 splitted = splitted.slice(3);
2139 }
2140 var inside = false, next, prev_color, prev_background, code, match;
2141 for (var i=0; i<splitted.length; ++i) {
2142 match = splitted[i].match(/^\x1B\[([0-9;]*)([A-Za-z])$/);
2143 if (match) {
2144 switch (match[2]) {
2145 case 'm':
2146 if (match[1] === '') {
2147 continue;
2148 }
2149 if (match[1] !== '0') {
2150 code = format_ansi(match[1]);
2151 }
2152 if (inside) {
2153 output.push(']');
2154 if (match[1] == '0') {
2155 //just closing
2156 inside = false;
2157 prev_color = prev_background = '';
2158 } else {
2159 // someone forget to close - move to next
2160 code[1] = code[1] || prev_color;
2161 code[2] = code[2] || prev_background;
2162 output.push('[[' + code.join(';') + ']');
2163 // store colors to next use
2164 if (code[1]) {
2165 prev_color = code[1];
2166 }
2167 if (code[2]) {
2168 prev_background = code[2];
2169 }
2170 }
2171 } else {
2172 if (match[1] != '0') {
2173 inside = true;
2174 output.push('[[' + code.join(';') + ']');
2175 // store colors to next use
2176 if (code[1]) {
2177 prev_color = code[1];
2178 }
2179 if (code[2]) {
2180 prev_background = code[2];
2181 }
2182 }
2183 }
2184 break;
2185 }
2186 } else {
2187 output.push(splitted[i]);
2188 }
2189 }
2190 if (inside) {
2191 output.push(']');
2192 }
2193 return output.join(''); //.replace(/\[\[[^\]]+\]\]/g, '');
2194 };
2195 })(),
2196 // -----------------------------------------------------------------------
2197 // :: Function splits arguments and works with strings like
2198 // :: 'asd' 'asd\' asd' "asd asd" asd\ 123 -n -b / [^ ]+ / /\s+/ asd\ asd
2199 // :: it creates a regex and numbers and replaces escape characters in double
2200 // :: quotes
2201 // -----------------------------------------------------------------------
2202 parseArguments: function(string) {
2203 return $.map(string.match(command_re) || [], function(arg) {
2204 if (arg[0] === "'" && arg[arg.length-1] === "'") {
2205 return arg.replace(/^'|'$/g, '');
2206 } else if (arg[0] === '"' && arg[arg.length-1] === '"') {
2207 arg = arg.replace(/^"|"$/g, '').replace(/\\([" ])/g, '$1');
2208 return arg.replace(/\\\\|\\t|\\n/g, function(string) {
2209 if (string[1] === 't') {
2210 return '\t';
2211 } else if (string[1] === 'n') {
2212 return '\n';
2213 } else {
2214 return '\\';
2215 }
2216 }).replace(/\\x([0-9a-f]+)/gi, function(_, hex) {
2217 return String.fromCharCode(parseInt(hex, 16));
2218 }).replace(/\\0([0-7]+)/g, function(_, oct) {
2219 return String.fromCharCode(parseInt(oct, 8));
2220 });
2221 } else if (arg.match(/^\/(\\\/|[^\/])+\/[gimy]*$/)) {
2222 var m = arg.match(/^\/([^\/]+)\/([^\/]*)$/);
2223 return new RegExp(m[1], m[2]);
2224 } else if (arg.match(/^-?[0-9]+$/)) {
2225 return parseInt(arg, 10);
2226 } else if (arg.match(/^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/)) {
2227 return parseFloat(arg);
2228 } else {
2229 return arg.replace(/\\ /g, ' ');
2230 }
2231 });
2232 },
2233 // -----------------------------------------------------------------------
2234 // :: Split arguments: it only strips single and double quotes and escapes
2235 // :: spaces
2236 // -----------------------------------------------------------------------
2237 splitArguments: function(string) {
2238 return $.map(string.match(command_re) || [], function(arg) {
2239 if (arg[0] === "'" && arg[arg.length-1] === "'") {
2240 return arg.replace(/^'|'$/g, '');
2241 } else if (arg[0] === '"' && arg[arg.length-1] === '"') {
2242 return arg.replace(/^"|"$/g, '').replace(/\\([" ])/g, '$1');
2243 } else if (arg[0] === '/' && arg[arg.length-1] == '/') {
2244 return arg;
2245 } else {
2246 return arg.replace(/\\ /g, ' ');
2247 }
2248 });
2249 },
2250 // -----------------------------------------------------------------------
2251 // :: Function that returns an object {name,args}. Arguments are parsed
2252 // :: using the function parseArguments
2253 // -----------------------------------------------------------------------
2254 parseCommand: function(string) {
2255 return processCommand(string, $.terminal.parseArguments);
2256 },
2257 // -----------------------------------------------------------------------
2258 // :: Same as parseCommand but arguments are parsed using splitArguments
2259 // -----------------------------------------------------------------------
2260 splitCommand: function(string) {
2261 return processCommand(string, $.terminal.splitArguments);
2262 },
2263 // -----------------------------------------------------------------------
2264 // :: Test $.terminal functions using terminal
2265 // -----------------------------------------------------------------------
2266 test: function() {
2267 var term = $.terminal.active();
2268 if (!term) {
2269 term = $('body').terminal($.noop).css('margin', 0);
2270 var margin = term.outerHeight() - term.height();
2271 var $win = $(window);
2272 $win.resize(function() {
2273 term.css('height', $(window).height()-20);
2274 }).resize();
2275 }
2276 term.echo('Testing...');
2277 function assert(cond, msg) {
2278 term.echo(msg + ' &#91;' + (cond ? '[[b;#44D544;]PASS]' : '[[b;#FF5555;]FAIL]') + '&#93;');
2279 }
2280 var string = 'name "foo bar" baz /^asd [x]/ str\\ str 10 1e10';
2281 var cmd = $.terminal.splitCommand(string);
2282 assert(cmd.name === 'name' && cmd.args[0] === 'foo bar' &&
2283 cmd.args[1] === 'baz' && cmd.args[2] === '/^asd [x]/' &&
2284 cmd.args[3] === 'str str' && cmd.args[4] === '10' &&
2285 cmd.args[5] === '1e10', '$.terminal.splitCommand');
2286 cmd = $.terminal.parseCommand(string);
2287 assert(cmd.name === 'name' && cmd.args[0] === 'foo bar' &&
2288 cmd.args[1] === 'baz' && $.type(cmd.args[2]) === 'regexp' &&
2289 cmd.args[2].source === '^asd [x]' &&
2290 cmd.args[3] === 'str str' && cmd.args[4] === 10 &&
2291 cmd.args[5] === 1e10, '$.terminal.parseCommand');
2292 string = '\x1b[2;31;46mFoo\x1b[1;3;4;32;45mBar\x1b[0m\x1b[7mBaz';
2293 assert($.terminal.from_ansi(string) ===
2294 '[[;#640000;#008787]Foo][[biu;#44D544;#F5F]Bar][[;#000;#AAA]Baz]',
2295 '$.terminal.from_ansi');
2296 string = '[[biugs;#fff;#000]Foo][[i;;;foo]Bar][[ous;;]Baz]';
2297 term.echo('$.terminal.format');
2298 assert($.terminal.format(string) === '<span style="font-weight:bold;text-decoration:underline line-through;font-style:italic;color:#fff;text-shadow:0 0 5px #fff;background-color:#000" data-text="Foo">Foo</span><span style="font-style:italic;" class="foo" data-text="Bar">Bar</span><span style="text-decoration:underline line-through overline;" data-text="Baz">Baz</span>', '\tformatting');
2299 string = 'http://terminal.jcubic.pl/examples.php https://www.google.com/?q=jquery%20terminal';
2300 assert($.terminal.format(string) === '<a target="_blank" href="http://terminal.jcubic.pl/examples.php">http://terminal.jcubic.pl/examples.php</a> <a target="_blank" href="https://www.google.com/?q=jquery%20terminal">https://www.google.com/?q=jquery%20terminal</a>', '\turls');
2301 string = 'foo@bar.com baz.quux@example.com';
2302 assert($.terminal.format(string) === '<a href="mailto:foo@bar.com">foo@bar.com</a> <a href="mailto:baz.quux@example.com">baz.quux@example.com</a>', '\temails');
2303 string = '-_-[[biugs;#fff;#000]Foo]-_-[[i;;;foo]Bar]-_-[[ous;;]Baz]-_-';
2304 assert($.terminal.strip(string) === '-_-Foo-_-Bar-_-Baz-_-', '$.terminal.strip');
2305 string = '[[bui;#fff;]Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sed dolor nisl, in suscipit justo. Donec a enim et est porttitor semper at vitae augue. Proin at nulla at dui mattis mattis. Nam a volutpat ante. Aliquam consequat dui eu sem convallis ullamcorper. Nulla suscipit, massa vitae suscipit ornare, tellus] est [[b;;#f00]consequat nunc, quis blandit elit odio eu arcu. Nam a urna nec nisl varius sodales. Mauris iaculis tincidunt orci id commodo. Aliquam] non magna quis [[i;;]tortor malesuada aliquam] eget ut lacus. Nam ut vestibulum est. Praesent volutpat tellus in eros dapibus elementum. Nam laoreet risus non nulla mollis ac luctus [[ub;#fff;]felis dapibus. Pellentesque mattis elementum augue non sollicitudin. Nullam lobortis fermentum elit ac mollis. Nam ac varius risus. Cras faucibus euismod nulla, ac auctor diam rutrum sit amet. Nulla vel odio erat], ac mattis enim.';
2306 term.echo('$.terminal.split_equal');
2307 var cols = [10, 40, 60, 400];
2308 for (var i=cols.length; i--;) {
2309 var lines = $.terminal.split_equal(string, cols[i]);
2310 var success = true;
2311 for (var j=0; j<lines.length; ++j) {
2312 if ($.terminal.strip(lines[j]).length > cols[i]) {
2313 success = false;
2314 break;
2315 }
2316 }
2317 assert(success, '\tsplit ' + cols[i]);
2318 }
2319 }
2320 };
2321
2322 // -----------------------------------------------------------------------
2323 // Helper plugins
2324 // -----------------------------------------------------------------------
2325 $.fn.visible = function() {
2326 return this.css('visibility', 'visible');
2327 };
2328 $.fn.hidden = function() {
2329 return this.css('visibility', 'hidden');
2330 };
2331 // -----------------------------------------------------------------------
2332 // JSON-RPC CALL
2333 // -----------------------------------------------------------------------
2334 var ids = {};
2335 $.jrpc = function(url, method, params, success, error) {
2336 ids[url] = ids[url] || 0;
2337 var request = $.json_stringify({
2338 'jsonrpc': '2.0', 'method': method,
2339 'params': params, 'id': ++ids[url]});
2340 return $.ajax({
2341 url: url,
2342 data: request,
2343 success: function(result, status, jqXHR) {
2344 var content_type = jqXHR.getResponseHeader('Content-Type');
2345 if (!content_type.match(/application\/json/)) {
2346 if (console && console.warn) {
2347 console.warn('Response Content-Type is not application/json');
2348 } else {
2349 throw new Error('WARN: Response Content-Type is not application/json');
2350 }
2351 }
2352 var json;
2353 try {
2354 json = $.parseJSON(result);
2355 } catch (e) {
2356 if (error) {
2357 error(jqXHR, 'Invalid JSON', e);
2358 } else {
2359 throw new Error('Invalid JSON');
2360 }
2361 return;
2362 }
2363 // don't catch errors in success callback
2364 success(json, status, jqXHR);
2365 },
2366 error: error,
2367 contentType: 'application/json',
2368 dataType: 'text',
2369 async: true,
2370 cache: false,
2371 //timeout: 1,
2372 type: 'POST'});
2373 };
2374
2375 // -----------------------------------------------------------------------
2376 function is_scrolled_into_view(elem) {
2377 var docViewTop = $(window).scrollTop();
2378 var docViewBottom = docViewTop + $(window).height();
2379
2380 var elemTop = $(elem).offset().top;
2381 var elemBottom = elemTop + $(elem).height();
2382
2383 return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom));
2384 }
2385 // -----------------------------------------------------------------------
2386 // :: Create fake terminal to calcualte the dimention of one character
2387 // :: this will make terminal work if terminal div is not added to the
2388 // :: DOM at init like with:
2389 // :: $('<div/>').terminal().echo('foo bar').appendTo('body');
2390 // -----------------------------------------------------------------------
2391 function char_size() {
2392 var temp = $('<div class="terminal"><div class="cmd"><span>&nbsp;' +
2393 '</span></div></div>').appendTo('body');
2394 var span = temp.find('span');
2395 var result = {
2396 width: span.width(),
2397 height: span.outerHeight()
2398 };
2399 temp.remove();
2400 return result;
2401 }
2402 // -----------------------------------------------------------------------
2403 // :: calculate numbers of characters
2404 // -----------------------------------------------------------------------
2405 function get_num_chars(terminal) {
2406 var width = char_size().width;
2407 var result = Math.floor(terminal.width() / width);
2408 if (have_scrollbars(terminal)) {
2409 var SCROLLBAR_WIDTH = 20;
2410 // assume that scrollbars are 20px - in my Laptop with
2411 // Linux/Chrome they are 16px
2412 var margins = terminal.innerWidth() - terminal.width();
2413 result -= Math.ceil((SCROLLBAR_WIDTH - margins / 2) / (width-1));
2414 }
2415 return result;
2416 }
2417 // -----------------------------------------------------------------------
2418 // :: Calculate number of lines that fit without scroll
2419 // -----------------------------------------------------------------------
2420 function get_num_rows(terminal) {
2421 return Math.floor(terminal.height() / char_size().height);
2422 }
2423 // -----------------------------------------------------------------------
2424 // :: Get Selected Text (this is internal because it return text even if
2425 // :: it's outside of terminal, is used to paste text to the terminal)
2426 // -----------------------------------------------------------------------
2427 function getSelectedText() {
2428 if (window.getSelection || document.getSelection) {
2429 var selection = (window.getSelection || document.getSelection)();
2430 if (selection.text) {
2431 return selection.text;
2432 } else {
2433 return selection.toString();
2434 }
2435 } else if (document.selection) {
2436 return document.selection.createRange().text;
2437 }
2438 }
2439 // -----------------------------------------------------------------------
2440 // :: check if div have scrollbars (need to have overflow auto or always)
2441 // -----------------------------------------------------------------------
2442 function have_scrollbars(div) {
2443 return div.get(0).scrollHeight > div.innerHeight();
2444 }
2445 // -----------------------------------------------------------------------
2446 // :: TERMINAL PLUGIN CODE
2447 // -----------------------------------------------------------------------
2448 var version = '0.8.7';
2449 var version_set = !version.match(/^\{\{/);
2450 var copyright = 'Copyright (c) 2011-2013 Jakub Jankiewicz <http://jcubic.pl>';
2451 var version_string = version_set ? ' version ' + version : ' ';
2452 //regex is for placing version string aligned to the right
2453 var reg = new RegExp(" {" + version_string.length + "}$");
2454 // -----------------------------------------------------------------------
2455 // :: Terminal Signatures
2456 // -----------------------------------------------------------------------
2457 var signatures = [
2458 ['jQuery Terminal', '(c) 2011-2013 jcubic'],
2459 ['jQuery Terminal Emulator' + (version_set ? ' v. ' + version : ''),
2460 copyright.replace(/ *<.*>/, '')],
2461 ['jQuery Terminal Emulator' + (version_set ? version_string : ''),
2462 copyright.replace(/^Copyright /, '')],
2463 [' _______ ________ __',
2464 ' / / _ /_ ____________ _/__ ___/______________ _____ / /',
2465 ' __ / / // / // / _ / _/ // / / / _ / _/ / / \\/ / _ \\/ /',
2466 '/ / / // / // / ___/ // // / / / ___/ // / / / / /\\ / // / /__',
2467 '\\___/____ \\\\__/____/_/ \\__ / /_/____/_//_/ /_/ /_/ \\/\\__\\_\\___/',
2468 ' \\/ /____/ '.replace(reg, ' ') +
2469 version_string,
2470 copyright],
2471 [' __ _____ ________ __',
2472 ' / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / /',
2473 ' __ / // // // // // _ // _// // / / // _ // _// // // \\/ // _ \\/ /',
2474 '/ / // // // // // ___// / / // / / // ___// / / / / // // /\\ // // / /__',
2475 '\\___//____ \\\\___//____//_/ _\\_ / /_//____//_/ /_/ /_//_//_/ /_/ \\__\\_\\___/',
2476 ' \\/ /____/ '.replace(reg, '') +
2477 version_string,
2478 copyright]
2479 ];
2480 // -----------------------------------------------------------------------
2481 // :: Default options
2482 // -----------------------------------------------------------------------
2483 $.terminal.defaults = {
2484 prompt: '> ',
2485 history: true,
2486 exit: true,
2487 clear: true,
2488 enabled: true,
2489 historySize: 60,
2490 checkArity: true,
2491 exceptionHandler: null,
2492 cancelableAjax: true,
2493 processArguments: true,
2494 linksNoReferrer: false,
2495 login: null,
2496 outputLimit: -1,
2497 onAjaxError: null,
2498 onRPCError: null,
2499 completion: false,
2500 historyFilter: null,
2501 onInit: $.noop,
2502 onClear: $.noop,
2503 onBlur: $.noop,
2504 onFocus: $.noop,
2505 onTerminalChange: $.noop,
2506 onExit: $.noop,
2507 keypress: $.noop,
2508 keydown: $.noop,
2509 strings: {
2510 wrongPasswordTryAgain: "Wrong password try again!",
2511 wrongPassword: "Wrong password!",
2512 ajaxAbortError: "Error while aborting ajax call!",
2513 wrongArity: "Wrong number of arguments. Function '%s' expect %s got %s!",
2514 commandNotFound: "Command '%s' Not Found!",
2515 oneRPCWithIgnore: "You can use only one rpc with ignoreSystemDescribe",
2516 oneInterpreterFunction: "You can't use more then one function (rpc with " +
2517 "ignoreSystemDescribe is count as one)",
2518 loginFunctionMissing: "You don't have login function",
2519 noTokenError: "Access denied (no token)",
2520 serverResponse: "Server reponse is",
2521 wrongGreetings: "Wrong value of greetings parameter",
2522 notWhileLogin: "You can't call that function while in login",
2523 loginIsNotAFunction: "Authenticate must be a function",
2524 canExitError: "You can't exit from main interpeter",
2525 invalidCompletion: "Invalid completion",
2526 login: "login",
2527 password: "password"
2528 }
2529 };
2530 // -----------------------------------------------------------------------
2531 // :: All terminal globals
2532 // -----------------------------------------------------------------------
2533 var requests = []; // for canceling on CTRL+D
2534 var terminals = new Cycle(); // list of terminals global in this scope
2535 $.fn.terminal = function(init_interpreter, options) {
2536 // -----------------------------------------------------------------------
2537 // :: helper function
2538 // -----------------------------------------------------------------------
2539 function get_processed_command(command) {
2540 if (typeof settings.processArguments === 'function') {
2541 return processCommand(command, settings.processArguments);
2542 } else if (settings.processArguments) {
2543 return $.terminal.parseCommand(command);
2544 } else {
2545 return $.terminal.splitCommand(command);
2546 }
2547 }
2548 // -----------------------------------------------------------------------
2549 // :: Display object on terminal
2550 // -----------------------------------------------------------------------
2551 function display_object(object) {
2552 if (typeof object === 'string') {
2553 self.echo(object);
2554 } else if (object instanceof Array) {
2555 self.echo($.map(object, function(object) {
2556 return $.json_stringify(object);
2557 }).join(' '));
2558 } else if (typeof object === 'object') {
2559 self.echo($.json_stringify(object));
2560 } else {
2561 self.echo(object);
2562 }
2563 }
2564 // -----------------------------------------------------------------------
2565 // :: Helper function
2566 // -----------------------------------------------------------------------
2567 function display_json_rpc_error(error) {
2568 if (typeof settings.onRPCError === 'function') {
2569 settings.onRPCError.call(self, error);
2570 } else {
2571 self.error('&#91;RPC&#93; ' + error.message);
2572 }
2573 }
2574 // -----------------------------------------------------------------------
2575 // :: Create interpreter function from url string
2576 // -----------------------------------------------------------------------
2577 function make_basic_json_rpc_interpreter(url) {
2578 var service = function(method, params) {
2579 self.pause();
2580 $.jrpc(url, method, params, function(json) {
2581 if (!json.error) {
2582 if (typeof settings.processRPCResponse === 'function') {
2583 settings.processRPCResponse.call(self, json.result);
2584 } else {
2585 display_object(json.result);
2586 }
2587 } else {
2588 display_json_rpc_error(json.error);
2589 }
2590 self.resume();
2591 }, ajax_error);
2592 };
2593 //this is the interpreter function
2594 return function(command, terminal) {
2595 if (command === '') {
2596 return;
2597 }
2598 command = get_processed_command(command);
2599 if (!settings.login || command.name === 'help') {
2600 // allows to call help without a token
2601 service(command.name, command.args);
2602 } else {
2603 var token = terminal.token();
2604 if (token) {
2605 service(command.name, [token].concat(command.args));
2606 } else {
2607 //should never happen
2608 terminal.error('&#91;AUTH&#93; ' +
2609 strings.noTokenError);
2610 }
2611 }
2612 };
2613 }
2614 // -----------------------------------------------------------------------
2615 // :: Create interpreter function from Object. If the value is object
2616 // :: it will create nested interpreters
2617 // -----------------------------------------------------------------------
2618 function make_object_interpreter(object, arity, fallback) {
2619 // function that maps commands to object methods
2620 // it keeps terminal context
2621 return function(user_command, terminal) {
2622 if (user_command === '') {
2623 return;
2624 }
2625 //command = split_command_line(command);
2626 var command = get_processed_command(user_command);
2627 var val = object[command.name];
2628 var type = $.type(val);
2629 if (type === 'function') {
2630 if (arity && val.length !== command.args.length) {
2631 self.error("&#91;Arity&#93; " +
2632 sprintf(strings.wrongArity,
2633 command.name,
2634 val.length,
2635 command.args.length));
2636 } else {
2637 return val.apply(self, command.args);
2638 }
2639 } else if (type === 'object' || type === 'string') {
2640 var commands = [];
2641 if (type === 'object') {
2642 commands = Object.keys(val);
2643 val = make_object_interpreter(val, arity);
2644 }
2645 terminal.push(val, {
2646 prompt: command.name + '> ',
2647 name: command.name,
2648 completion: type === 'object' ? function(term, string, callback) {
2649 callback(commands);
2650 } : undefined
2651 });
2652 } else {
2653 if ($.type(fallback) === 'function') {
2654 fallback(user_command, self);
2655 } else if ($.type(settings.onCommandNotFound) === 'function') {
2656 settings.onCommandNotFound(user_command, self);
2657 } else {
2658 terminal.error(sprintf(strings.commandNotFound, command.name));
2659 }
2660 }
2661 };
2662 }
2663 // -----------------------------------------------------------------------
2664 function ajax_error(xhr, status, error) {
2665 self.resume(); // onAjaxError can use pause/resume call it first
2666 if (typeof settings.onAjaxError == 'function') {
2667 settings.onAjaxError.call(self, xhr, status, error);
2668 } else if (status !== 'abort') {
2669 self.error('&#91;AJAX&#93; ' + status + ' - ' +
2670 strings.serverResponse +
2671 ': \n' + xhr.responseText);
2672 }
2673 }
2674 // -----------------------------------------------------------------------
2675 function make_json_rpc_object(url, success) {
2676 $.jrpc(url, 'system.describe', [], function(ret) {
2677 var commands = [];
2678 if (ret.procs) {
2679 var interpreter_object = {};
2680 $.each(ret.procs, function(_, proc) {
2681 interpreter_object[proc.name] = function() {
2682 var args = Array.prototype.slice.call(arguments);
2683 if (settings.checkArity && proc.params &&
2684 proc.params.length !== args.length) {
2685 self.error("&#91;Arity&#93; " +
2686 sprintf(strings.wrongArity,
2687 proc.name,
2688 proc.params.length,
2689 args.length));
2690 } else {
2691 self.pause();
2692 $.jrpc(url, proc.name, args, function(json) {
2693 if (json.error) {
2694 display_json_rpc_error(json.error);
2695 } else {
2696 display_object(json.result);
2697 }
2698 self.resume();
2699 }, ajax_error);
2700 }
2701 };
2702 });
2703 success(interpreter_object);
2704 } else {
2705 success(null);
2706 }
2707 }, function() {
2708 success(null);
2709 });
2710 }
2711 // -----------------------------------------------------------------------
2712 function make_interpreter(user_interpreter, finalize) {
2713 finalize = finalize || $.noop;
2714 var type = $.type(user_interpreter);
2715 var result = {};
2716 var commands;
2717 var rpc_count = 0; // only one rpc can be use for array
2718 var function_interpreter;
2719 if (type === 'array') {
2720 var object = {};
2721 // recur will be called when previous acync call is finished
2722 (function recur(interpreters, success) {
2723 if (interpreters.length) {
2724 var first = interpreters[0];
2725 var rest = interpreters.slice(1);
2726 var type = $.type(first);
2727 if (type === 'string') {
2728 rpc_count++;
2729 self.pause();
2730 if (settings.ignoreSystemDescribe) {
2731 if (rpc_count === 1) {
2732 function_interpreter = make_basic_json_rpc_interpreter(first);
2733 } else {
2734 self.error(strings.oneRPCWithIgnore);
2735 }
2736 recur(rest, success);
2737 } else {
2738 make_json_rpc_object(first, function(new_object) {
2739 // will ignore rpc in array that don't have system.describe
2740 if (new_object) {
2741 $.extend(object, new_object);
2742 }
2743 self.resume();
2744 recur(rest, success);
2745 });
2746 }
2747 } else if (type === 'function') {
2748 if (function_interpreter) {
2749 self.error(strings.oneInterpreterFunction);
2750 } else {
2751 function_interpreter = first;
2752 }
2753 } else if (type === 'object') {
2754 $.extend(object, first);
2755 recur(rest, success);
2756 }
2757 } else {
2758 success();
2759 }
2760 })(user_interpreter, function() {
2761 commands = Object.keys(object);
2762 result.interpreter = make_object_interpreter(object, false, function_interpreter);
2763 result.completion = function(term, string, callback) {
2764 callback(commands);
2765 };
2766 finalize(result);
2767 });
2768 } else if (type === 'string') {
2769 if (settings.ignoreSystemDescribe) {
2770 finalize({
2771 interpreter: make_basic_json_rpc_interpreter(user_interpreter),
2772 completion: settings.completion
2773 });
2774 } else {
2775 self.pause();
2776 make_json_rpc_object(user_interpreter, function(object) {
2777 if (object) {
2778 result.interpreter = make_object_interpreter(object, false);
2779 result.completion = function(term, string, callback) {
2780 callback(commands);
2781 };
2782 } else {
2783 // no procs in system.describe
2784 result.interpreter = make_basic_json_rpc_interpreter(user_interpreter);
2785 result.completion = settings.completion;
2786 }
2787 self.resume();
2788 finalize(result);
2789 });
2790 }
2791 } else if (type === 'object') {
2792 commands = Object.keys(user_interpreter);
2793 result.interpreter = make_object_interpreter(user_interpreter, settings.checkArity);
2794 result.completion = function(term, string, callback) {
2795 callback(commands);
2796 };
2797 finalize(result);
2798 } else {
2799 // allow $('<div/>).terminal();
2800 if (type === 'undefined') {
2801 user_interpreter = $.noop;
2802 } else if (type !== 'function') {
2803 throw type + " is invalid interpreter value";
2804 }
2805 finalize({
2806 interpreter: user_interpreter,
2807 completion: settings.completion
2808 });
2809 }
2810 }
2811 // -----------------------------------------------------------------------
2812 // :: Create JSON-RPC authentication function
2813 // -----------------------------------------------------------------------
2814 function make_json_rpc_login(url, login) {
2815 var method = $.type(login) === 'boolean' ? 'login' : login;
2816 return function(user, passwd, callback, term) {
2817 self.pause();
2818 $.jrpc(url,
2819 method,
2820 [user, passwd],
2821 function(response) {
2822 self.resume();
2823 if (!response.error && response.result) {
2824 callback(response.result);
2825 } else {
2826 // null will trigger message that login fail
2827 callback(null);
2828 }
2829 }, ajax_error);
2830 };
2831 //default name is login so you can pass true
2832 }
2833 // -----------------------------------------------------------------------
2834 // :: Return exception message as string
2835 // -----------------------------------------------------------------------
2836 function exception_message(e) {
2837 if (typeof e === 'string') {
2838 return e;
2839 } else if (typeof e.fileName === 'string') {
2840 return e.fileName + ': ' + e.message;
2841 } else {
2842 return e.message;
2843 }
2844 }
2845 // -----------------------------------------------------------------------
2846 // :: display Exception on terminal
2847 // -----------------------------------------------------------------------
2848 function display_exception(e, label) {
2849 if (typeof settings.exceptionHandler == 'function') {
2850 settings.exceptionHandler.call(self, e);
2851 } else {
2852 self.exception(e, label);
2853 }
2854 }
2855 // -----------------------------------------------------------------------
2856 function scroll_to_bottom() {
2857 var scrollHeight = scroll_object.prop ? scroll_object.prop('scrollHeight') :
2858 scroll_object.attr('scrollHeight');
2859 scroll_object.scrollTop(scrollHeight);
2860 }
2861 // -----------------------------------------------------------------------
2862 // :: validating if object is a string or a function, call that function
2863 // :: and display the exeption if any
2864 // -----------------------------------------------------------------------
2865 function validate(label, object) {
2866 try {
2867 if (typeof object === 'function') {
2868 object(function() {
2869 // don't care
2870 });
2871 } else if (typeof object !== 'string') {
2872 var msg = label + ' must be string or function';
2873 throw msg;
2874 }
2875 } catch (e) {
2876 display_exception(e, label.toUpperCase());
2877 return false;
2878 }
2879 return true;
2880 }
2881 // -----------------------------------------------------------------------
2882 // :: Draw line - can have line breaks and be longer than the width of
2883 // :: the terminal, there are 2 options raw and finalize
2884 // :: raw - will not encode the string and finalize if a function that
2885 // :: will have div container of the line as first argument
2886 // :: NOTE: it formats and appends lines to output_buffer. The actual
2887 // :: append to terminal output happens in the flush function
2888 // -----------------------------------------------------------------------
2889 var output_buffer = [];
2890 var NEW_LINE = 1;
2891 function draw_line(string, options) {
2892 // prevent exception in display exception
2893 try {
2894 var line_settings = $.extend({
2895 raw: false,
2896 finalize: $.noop
2897 }, options || {});
2898 string = $.type(string) === "function" ? string() : string;
2899 string = $.type(string) === "string" ? string : String(string);
2900 var i, len;
2901 if (!line_settings.raw) {
2902 string = $.terminal.encode(string);
2903 }
2904 string = $.terminal.overtyping(string);
2905 string = $.terminal.from_ansi(string);
2906 output_buffer.push(NEW_LINE);
2907 if (!line_settings.raw && (string.length > num_chars || string.match(/\n/))) {
2908 var array = $.terminal.split_equal(string, num_chars);
2909 for (i = 0, len = array.length; i < len; ++i) {
2910 if (array[i] === '' || array[i] === '\r') {
2911 output_buffer.push('&nbsp;');
2912 } else {
2913 if (line_settings.raw) {
2914 output_buffer.push(array[i]);
2915 } else {
2916 output_buffer.push($.terminal.format(array[i], {
2917 linksNoReferer: settings.linksNoReferer
2918 }));
2919 }
2920 }
2921 }
2922 } else {
2923 if (!line_settings.raw) {
2924 string = $.terminal.format(string, {
2925 linksNoReferer: settings.linksNoReferer
2926 });
2927 }
2928 output_buffer.push(string);
2929 }
2930 output_buffer.push(line_settings.finalize);
2931 } catch (e) {
2932 output_buffer = [];
2933 // don't display exception if exception throw in terminal
2934 alert('[Internal Exception(draw_line)]:' + exception_message(e) + '\n' +
2935 e.stack);
2936 }
2937 }
2938 // -----------------------------------------------------------------------
2939 // Redraw all lines
2940 // -----------------------------------------------------------------------
2941 function redraw() {
2942 command_line.resize(num_chars);
2943 var o = output.empty().detach();
2944 var lines_to_show;
2945 if (settings.outputLimit >= 0) {
2946 // flush will limit lines but if there is lot of
2947 // lines we don't need to show them and then remove
2948 // them from terminal
2949 var limit = settings.outputLimit === 0 ?
2950 self.rows() :
2951 settings.outputLimit;
2952 lines_to_show = lines.slice(lines.length-limit-1);
2953 } else {
2954 lines_to_show = lines;
2955 }
2956 $.each(lines_to_show, function(i, line) {
2957 draw_line.apply(null, line); // line is an array
2958 });
2959 command_line.before(o);
2960 self.flush();
2961 }
2962 // -----------------------------------------------------------------------
2963 // :: Display user greetings or terminal signature
2964 // -----------------------------------------------------------------------
2965 function show_greetings() {
2966 if (settings.greetings === undefined) {
2967 self.echo(self.signature);
2968 } else if (settings.greetings) {
2969 var type = typeof settings.greetings;
2970 if (type === 'string') {
2971 self.echo(settings.greetings);
2972 } else if (type === 'function') {
2973 settings.greetings.call(self, self.echo);
2974 } else {
2975 self.error(strings.wrongGreetings);
2976 }
2977 }
2978 }
2979 // -----------------------------------------------------------------------
2980 // :: Display prompt and last command
2981 // -----------------------------------------------------------------------
2982 function echo_command(command) {
2983 command = $.terminal.escape_brackets($.terminal.encode(command, true));
2984 var prompt = command_line.prompt();
2985 if (command_line.mask()) {
2986 command = command.replace(/./g, '*');
2987 }
2988 if (typeof prompt === 'function') {
2989 prompt(function(string) {
2990 self.echo(string + command);
2991 });
2992 } else {
2993 self.echo(prompt + command);
2994 }
2995 }
2996 // -----------------------------------------------------------------------
2997 // :: Wrapper over interpreter, it implements exit and catches all exeptions
2998 // :: from user code and displays them on the terminal
2999 // -----------------------------------------------------------------------
3000 function commands(command, silent, exec) {
3001 try {
3002 if (!ghost()) {
3003 prev_command = $.terminal.splitCommand(command).name;
3004 if (exec && typeof settings.historyFilter == 'function' &&
3005 settings.historyFilter(command) || !settings.historyFilter) {
3006 command_line.history().append(command);
3007 }
3008 }
3009 var interpreter = interpreters.top();
3010 if (command === 'exit' && settings.exit) {
3011 var count = interpreters.size();
3012 self.token();
3013 if (count == 1 && self.token() || count > 1) {
3014 if (!silent) {
3015 echo_command(command);
3016 }
3017 self.pop();
3018 }
3019 } else {
3020 if (!silent) {
3021 echo_command(command);
3022 }
3023 var position = lines.length-1;
3024 if (command === 'clear' && settings.clear) {
3025 self.clear();
3026 } else {
3027 // Execute command from the interpreter
3028 var result = interpreter.interpreter(command, self);
3029 if (result !== undefined) {
3030 // was lines after echo_command (by interpreter)
3031 if (position === lines.length-1) {
3032 lines.pop();
3033 if (result !== false) {
3034 self.echo(result);
3035 }
3036 } else {
3037 if (result === false) {
3038 lines = lines.slice(0, position).
3039 concat(lines.slice(position+1));
3040 } else {
3041 lines = lines.slice(0, position).
3042 concat([result]).
3043 concat(lines.slice(position+1));
3044 }
3045 }
3046 self.resize();
3047 }
3048 }
3049 }
3050 } catch (e) {
3051 display_exception(e, 'USER');
3052 self.resume();
3053 throw e;
3054 }
3055 }
3056 // -----------------------------------------------------------------------
3057 // :: The logout function removes Storage, disables history and runs
3058 // :: the login function. This function is called only when options.login
3059 // :: function is defined. The check for this is in the self.pop method
3060 // -----------------------------------------------------------------------
3061 function global_logout() {
3062 if (typeof settings.onBeforeLogout === 'function') {
3063 try {
3064 if (settings.onBeforeLogout(self) === false) {
3065 return;
3066 }
3067 } catch (e) {
3068 display_exception(e, 'onBeforeLogout');
3069 throw e;
3070 }
3071 }
3072 logout();
3073 if (typeof settings.onAfterLogout === 'function') {
3074 try {
3075 settings.onAfterLogout(self);
3076 } catch (e) {
3077 display_exception(e, 'onAfterlogout');
3078 throw e;
3079 }
3080 }
3081 self.login(settings.login, true, initialize);
3082 }
3083 // -----------------------------------------------------------------------
3084 function logout() {
3085 var name = self.prefix_name(true) + '_';
3086 $.Storage.remove(name + 'token');
3087 $.Storage.remove(name + 'login');
3088 }
3089 // -----------------------------------------------------------------------
3090 // :: Save the interpreter name for use with purge
3091 // -----------------------------------------------------------------------
3092 function maybe_append_name(interpreter_name) {
3093 var storage_key = self.prefix_name() + '_interpreters';
3094 var names = $.Storage.get(storage_key);
3095 if (names) {
3096 names = $.parseJSON(names);
3097 } else {
3098 names = [];
3099 }
3100 if ($.inArray(interpreter_name, names) == -1) {
3101 names.push(interpreter_name);
3102 $.Storage.set(storage_key, $.json_stringify(names));
3103 }
3104 }
3105 // -----------------------------------------------------------------------
3106 // :: Function enables history, sets prompt, runs interpreter function
3107 // -----------------------------------------------------------------------
3108 function prepare_top_interpreter(silent) {
3109 var interpreter = interpreters.top();
3110 var name = self.prefix_name(true);
3111 if (!ghost()) {
3112 maybe_append_name(name);
3113 }
3114 command_line.name(name);
3115 if (typeof interpreter.prompt == 'function') {
3116 command_line.prompt(function(command) {
3117 interpreter.prompt(command, self);
3118 });
3119 } else {
3120 command_line.prompt(interpreter.prompt);
3121 }
3122 command_line.set('');
3123 if (!silent && typeof interpreter.onStart === 'function') {
3124 interpreter.onStart(self);
3125 }
3126 }
3127 // ---------------------------------------------------------------------
3128 function initialize() {
3129 prepare_top_interpreter();
3130 show_greetings();
3131 // was_paused flag is workaround for case when user call exec before
3132 // login and pause in onInit, 3rd exec will have proper timing (will
3133 // execute after onInit resume)
3134 var was_paused = false;
3135 if (typeof settings.onInit === 'function') {
3136 onPause = function() { // local in terminal
3137 was_paused = true;
3138 };
3139 try {
3140 settings.onInit(self);
3141 } catch (e) {
3142 display_exception(e, 'OnInit');
3143 throw e;
3144 } finally {
3145 onPause = $.noop;
3146 if (!was_paused) {
3147 // resume login if user didn't call pause in onInit
3148 // if user pause in onInit wait with exec until it resume
3149 self.resume();
3150 }
3151 }
3152 }
3153 }
3154 // ---------------------------------------------------------------------
3155 // :: function complete the command
3156 // ---------------------------------------------------------------------
3157 function complete_helper(command, string, commands) {
3158 var test = command_line.get().substring(0, command_line.position());
3159 if (test !== command) {
3160 // command line changed between TABS - ignore
3161 return;
3162 }
3163 var regex = new RegExp('^' + $.terminal.escape_regex(string));
3164 var matched = [];
3165 for (var i=commands.length; i--;) {
3166 if (regex.test(commands[i])) {
3167 matched.push(commands[i]);
3168 }
3169 }
3170 if (matched.length === 1) {
3171 self.insert(matched[0].replace(regex, '') + ' ');
3172 } else if (matched.length > 1) {
3173 if (tab_count >= 2) {
3174 echo_command(command);
3175 self.echo(matched.join('\t'));
3176 tab_count = 0;
3177 } else {
3178 var found = false;
3179 var found_index;
3180 var j;
3181 loop:
3182 for (j=string.length; j<matched[0].length; ++j) {
3183 for (i=1; i<matched.length; ++i) {
3184 if (matched[0].charAt(j) !== matched[i].charAt(j)) {
3185 break loop;
3186 }
3187 }
3188 found = true;
3189 }
3190 if (found) {
3191 self.insert(matched[0].slice(0, j).replace(regex, ''));
3192 }
3193 }
3194 }
3195 }
3196 // ---------------------------------------------------------------------
3197 // :: IF Ghost don't store anything in localstorage
3198 // ---------------------------------------------------------------------
3199 function ghost() {
3200 return in_login || command_line.mask();
3201 }
3202 // ---------------------------------------------------------------------
3203 // :: Keydown event handler
3204 // ---------------------------------------------------------------------
3205 function key_down(e) {
3206 // Prevent to be executed by cmd: CTRL+D, TAB, CTRL+TAB (if more then
3207 // one terminal)
3208 var result, i, top = interpreters.top();
3209 if ($.type(top.keydown) === 'function') {
3210 result = top.keydown(e, self);
3211 if (result !== undefined) {
3212 return result;
3213 }
3214 }
3215 var completion;
3216 if ((settings.completion && $.type(settings.completion) != 'boolean') &&
3217 !top.completion) {
3218 completion = settings.completion;
3219 } else {
3220 completion = top.completion;
3221 }
3222 // after text pasted into textarea in cmd plugin
3223 self.oneTime(10, function() {
3224 on_scrollbar_show_resize();
3225 });
3226 if ($.type(settings.keydown) === 'function') {
3227 result = settings.keydown(e, self);
3228 if (result !== undefined) {
3229 return result;
3230 }
3231 }
3232 if (!self.paused()) {
3233 if (e.which !== 9) { // not a TAB
3234 tab_count = 0;
3235 }
3236 if (e.which === 68 && e.ctrlKey) { // CTRL+D
3237 if (!in_login) {
3238 if (command_line.get() === '') {
3239 if (interpreters.size() > 1 ||
3240 settings.login !== undefined) {
3241 self.pop('');
3242 } else {
3243 self.resume();
3244 self.echo('');
3245 }
3246 } else {
3247 self.set_command('');
3248 }
3249 }
3250 return false;
3251 } else if (e.which === 76 && e.ctrlKey) { // CTRL+L
3252 self.clear();
3253 } else if (completion && e.which === 9) { // TAB
3254 // TODO: move this to cmd plugin
3255 // add completion = array | function
3256 ++tab_count;
3257 // cursor can be in the middle of the command
3258 // so we need to get the text before the cursor
3259 var command = command_line.get().substring(0, command_line.position());
3260 var strings = command.split(' ');
3261 var string; // string before cursor that will be completed
3262 if (strings.length == 1) {
3263 string = strings[0];
3264 } else {
3265 string = strings[strings.length-1];
3266 for (i=strings.length-1; i>0; i--) {
3267 // treat escape space as part of the string
3268 if (strings[i-1][strings[i-1].length-1] == '\\') {
3269 string = strings[i-1] + ' ' + string;
3270 } else {
3271 break;
3272 }
3273 }
3274 }
3275 switch ($.type(completion)) {
3276 case 'function':
3277 completion(self, string, function(commands) {
3278 complete_helper(command, string, commands);
3279 });
3280 break;
3281 case 'array':
3282 complete_helper(command, string, completion);
3283 break;
3284 default:
3285 // terminal will not catch this because it's an event
3286 throw new Error($.terminal.defaults.strings.invalidCompletion);
3287 }
3288 return false;
3289 } else if (e.which === 86 && e.ctrlKey) { // CTRL+V
3290 self.oneTime(1, function() {
3291 scroll_to_bottom();
3292 });
3293 return;
3294 } else if (e.which === 9 && e.ctrlKey) { // CTRL+TAB
3295 if (terminals.length() > 1) {
3296 self.focus(false);
3297 return false;
3298 }
3299 } else if (e.which === 34) { // PAGE DOWN
3300 self.scroll(self.height());
3301 } else if (e.which === 33) { // PAGE UP
3302 self.scroll(-self.height());
3303 } else {
3304 self.attr({scrollTop: self.attr('scrollHeight')});
3305 }
3306 } else if (e.which === 68 && e.ctrlKey) { // CTRL+D (if paused)
3307 if (requests.length) {
3308 for (i=requests.length; i--;) {
3309 var r = requests[i];
3310 if (4 !== r.readyState) {
3311 try {
3312 r.abort();
3313 } catch (error) {
3314 self.error(strings.ajaxAbortError);
3315 }
3316 }
3317 }
3318 requests = [];
3319 // only resume if there are ajax calls
3320 self.resume();
3321 }
3322 return false;
3323 }
3324 }
3325 // -----------------------------------------------------------------------
3326 var self = this;
3327 if (this.length > 1) {
3328 return this.each(function() {
3329 $.fn.terminal.call($(this),
3330 init_interpreter,
3331 $.extend({name: self.selector}, options));
3332 });
3333 } else {
3334 // terminal already exists
3335 if (self.data('terminal')) {
3336 return self.data('terminal');
3337 }
3338 if (self.length === 0) {
3339 throw 'Sorry, but terminal said that "' + self.selector +
3340 '" is not valid selector!';
3341 }
3342 //var names = []; // stack if interpeter names
3343 var scroll_object;
3344 var prev_command; // used for name on the terminal if not defined
3345 var loged_in = false;
3346 var tab_count = 0; // for tab completion
3347 // array of line objects:
3348 // - function (called whenever necessary, result is printed)
3349 // - array (expected form: [line, settings])
3350 // - anything else (cast to string when painted)
3351 var lines = [];
3352 var output; // .terminal-output jquery object
3353 var terminal_id = terminals.length();
3354 var num_chars; // numer of chars in line
3355 var num_rows; // number of lines that fit without scrollbar
3356 var command_list = []; // for tab completion
3357 var url;
3358 var in_login = false; // some Methods should not be called when login
3359 // TODO: Try to use mutex like counter for pause/resume
3360 var onPause = $.noop; // used to indicate that user call pause onInit
3361 var old_width, old_height;
3362 var dalyed_commands = []; // used when exec commands with pause
3363 var settings = $.extend({},
3364 $.terminal.defaults,
3365 {name: self.selector},
3366 options || {});
3367 var strings = $.terminal.defaults.strings;
3368 var enabled = settings.enabled;
3369 var paused = false;
3370 // -----------------------------------------------------------------------
3371 // TERMINAL METHODS
3372 // -----------------------------------------------------------------------
3373 $.extend(self, $.omap({
3374 // -----------------------------------------------------------------------
3375 // :: Clear the output
3376 // -----------------------------------------------------------------------
3377 clear: function() {
3378 output.html('');
3379 command_line.set('');
3380 lines = [];
3381 try {
3382 settings.onClear(self);
3383 } catch (e) {
3384 display_exception(e, 'onClear');
3385 throw e;
3386 }
3387 self.attr({ scrollTop: 0});
3388 return self;
3389 },
3390 // -----------------------------------------------------------------------
3391 // :: Return an object that can be used with import_view to restore the state
3392 // -----------------------------------------------------------------------
3393 export_view: function() {
3394 if (in_login) {
3395 throw new Exception(strings.notWhileLogin);
3396 }
3397 return {
3398 prompt: self.get_prompt(),
3399 command: self.get_command(),
3400 position: command_line.position(),
3401 lines: lines.slice(0)
3402 };
3403 },
3404 // -----------------------------------------------------------------------
3405 // :: Restore the state of the previous exported view
3406 // -----------------------------------------------------------------------
3407 import_view: function(view) {
3408 if (in_login) {
3409 throw new Exception(strings.notWhileLogin);
3410 }
3411 self.set_prompt(view.prompt);
3412 self.set_command(view.command);
3413 command_line.position(view.position);
3414 lines = view.lines;
3415 redraw();
3416 return self;
3417 },
3418 // -----------------------------------------------------------------------
3419 // :: Execute a command, it will handle commands that do AJAX calls
3420 // :: and have delays, if the second argument is set to true it will not
3421 // :: echo executed command
3422 // -----------------------------------------------------------------------
3423 exec: function(command, silent) {
3424 // both commands executed here (resume will call Terminal::exec)
3425 if (paused) {
3426 dalyed_commands.push([command, silent]);
3427 } else {
3428 commands(command, silent, true);
3429 }
3430 return self;
3431 },
3432 // -----------------------------------------------------------------------
3433 // :: Function changes the prompt of the command line to login
3434 // :: with a password and calls the user login function with
3435 // :: the callback that expects a token. The login is successful
3436 // :: if the user calls it with value that is truthy
3437 // -----------------------------------------------------------------------
3438 login: function(auth, infinite, success, error) {
3439 if (in_login) {
3440 throw new Error(strings.notWhileLogin);
3441 }
3442 if (typeof auth !== 'function') {
3443 throw new Error(strings.loginIsNotAFunction);
3444 }
3445 if (self.token(true) && self.login_name(true)) {
3446 if (typeof success == 'function') {
3447 success();
3448 }
3449 return self;
3450 }
3451 var user = null;
3452 // don't store login data in history
3453 if (settings.history) {
3454 command_line.history().disable();
3455 }
3456 in_login = true;
3457 return self.push(function(user) {
3458 self.set_mask(true).push(function(pass) {
3459 try {
3460 auth.call(self, user, pass, function(token, silent) {
3461 if (token) {
3462 self.pop().pop();
3463 if (settings.history) {
3464 command_line.history().enable();
3465 }
3466 var name = self.prefix_name(true) + '_';
3467 $.Storage.set(name + 'token', token);
3468 $.Storage.set(name + 'login', user);
3469 in_login = false;
3470 if (typeof success == 'function') {
3471 // will be used internaly since users know
3472 // when login success (they decide when
3473 // it happen by calling the callback -
3474 // this funtion)
3475 success();
3476 }
3477 } else {
3478 if (infinite) {
3479 if (!silent) {
3480 self.error(strings.wrongPasswordTryAgain);
3481 }
3482 self.pop().set_mask(false);
3483 } else {
3484 in_login = false;
3485 if (!silent) {
3486 self.error(strings.wrongPassword);
3487 }
3488 self.pop().pop();
3489 }
3490 // used only to call pop in push
3491 if (typeof error == 'function') {
3492 error();
3493 }
3494 }
3495 });
3496 } catch(e) {
3497 display_exception(e, 'USER(authentication)');
3498 }
3499 }, {
3500 prompt: strings.password + ': '
3501 });
3502 }, {
3503 prompt: strings.login + ': '
3504 });
3505 },
3506 // -----------------------------------------------------------------------
3507 // :: User defined settings and defaults as well
3508 // -----------------------------------------------------------------------
3509 settings: settings,
3510 // -----------------------------------------------------------------------
3511 // :: Return commands function from top interpreter
3512 // -----------------------------------------------------------------------
3513 commands: function() {
3514 return interpreters.top().interpreter;
3515 },
3516 // -----------------------------------------------------------------------
3517 // :: Low Level method that overwrites interpreter
3518 // -----------------------------------------------------------------------
3519 setInterpreter: function(user_interpreter, login) {
3520 function overwrite_interpreter() {
3521 self.pause();
3522 make_interpreter(user_interpreter, function(result) {
3523 self.resume();
3524 var top = interpreters.top();
3525 $.extend(top, result);
3526 prepare_top_interpreter(true);
3527 });
3528 }
3529 if ($.type(user_interpreter) == 'string' && login) {
3530 self.login(make_json_rpc_login(user_interpreter, login),
3531 true,
3532 overwrite_interpreter);
3533 } else {
3534 overwrite_interpreter();
3535 }
3536 },
3537 // -----------------------------------------------------------------------
3538 // :: Show user greetings or terminal signature
3539 // -----------------------------------------------------------------------
3540 greetings: function() {
3541 show_greetings();
3542 return self;
3543 },
3544 // -----------------------------------------------------------------------
3545 // :: Return true if terminal is paused false otherwise
3546 // -----------------------------------------------------------------------
3547 paused: function() {
3548 return paused;
3549 },
3550 // -----------------------------------------------------------------------
3551 // :: Pause the terminal, it should be used for ajax calls
3552 // -----------------------------------------------------------------------
3553 pause: function() {
3554 onPause();
3555 if (!paused && command_line) {
3556 paused = true;
3557 self.disable();
3558 command_line.hidden();
3559 }
3560 return self;
3561 },
3562 // -----------------------------------------------------------------------
3563 // :: Resume the previously paused terminal
3564 // -----------------------------------------------------------------------
3565 resume: function() {
3566 if (paused && command_line) {
3567 paused = false;
3568 self.enable();
3569 command_line.visible();
3570 var original = dalyed_commands;
3571 dalyed_commands = [];
3572 while (original.length) {
3573 self.exec.apply(self, original.shift());
3574 }
3575 scroll_to_bottom();
3576 }
3577 return self;
3578 },
3579 // -----------------------------------------------------------------------
3580 // :: Return the number of characters that fit into the width of the terminal
3581 // -----------------------------------------------------------------------
3582 cols: function() {
3583 return num_chars;
3584 },
3585 // -----------------------------------------------------------------------
3586 // :: Return the number of lines that fit into the height of the terminal
3587 // -----------------------------------------------------------------------
3588 rows: function() {
3589 return num_rows;
3590 },
3591 // -----------------------------------------------------------------------
3592 // :: Return the History object
3593 // -----------------------------------------------------------------------
3594 history: function() {
3595 return command_line.history();
3596 },
3597 // -----------------------------------------------------------------------
3598 // :: Switch to the next terminal
3599 // -----------------------------------------------------------------------
3600 next: function() {
3601 if (terminals.length() === 1) {
3602 return self;
3603 } else {
3604 var offsetTop = self.offset().top;
3605 var height = self.height();
3606 var scrollTop = self.scrollTop();
3607 if (!is_scrolled_into_view(self)) {
3608 self.enable();
3609 $('html,body').animate({scrollTop: offsetTop-50}, 500);
3610 return self;
3611 } else {
3612 terminals.front().disable();
3613 var next = terminals.rotate().enable();
3614 // 100 provides buffer in viewport
3615 var x = next.offset().top - 50;
3616 $('html,body').animate({scrollTop: x}, 500);
3617 try {
3618 settings.onTerminalChange(next);
3619 } catch (e) {
3620 display_exception(e, 'onTerminalChange');
3621 throw e;
3622 }
3623 return next;
3624 }
3625 }
3626 },
3627 // -----------------------------------------------------------------------
3628 // :: Make the terminal in focus or blur depending on the first argument.
3629 // :: If there is more then one terminal it will switch to next one,
3630 // :: if the second argument is set to true the events will be not fired.
3631 // :: Used on init
3632 // -----------------------------------------------------------------------
3633 focus: function(toggle, silent) {
3634 self.oneTime(1, function() {
3635 if (terminals.length() === 1) {
3636 if (toggle === false) {
3637 try {
3638 if (!silent && settings.onBlur(self) !== false) {
3639 self.disable();
3640 }
3641 } catch (e) {
3642 display_exception(e, 'onBlur');
3643 throw e;
3644 }
3645 } else {
3646 try {
3647 if (!silent && settings.onFocus(self) !== false) {
3648 self.enable();
3649 }
3650 } catch (e) {
3651 display_exception(e, 'onFocus');
3652 throw e;
3653 }
3654 }
3655 } else {
3656 if (toggle === false) {
3657 self.next();
3658 } else {
3659 var front = terminals.front();
3660 if (front != self) {
3661 front.disable();
3662 if (!silent) {
3663 try {
3664 settings.onTerminalChange(self);
3665 } catch (e) {
3666 display_exception(e, 'onTerminalChange');
3667 throw e;
3668 }
3669 }
3670 }
3671 terminals.set(self);
3672 self.enable();
3673 }
3674 }
3675 });
3676 return self;
3677 },
3678 // -----------------------------------------------------------------------
3679 // :: Enable the terminal
3680 // -----------------------------------------------------------------------
3681 enable: function() {
3682 if (num_chars === undefined) {
3683 //enabling first time
3684 self.resize();
3685 }
3686 if (command_line) {
3687 command_line.enable();
3688 enabled = true;
3689 }
3690 return self;
3691 },
3692 // -----------------------------------------------------------------------
3693 // :: Disable the terminal
3694 // -----------------------------------------------------------------------
3695 disable: function() {
3696 if (command_line) {
3697 enabled = false;
3698 command_line.disable();
3699 }
3700 return self;
3701 },
3702 // -----------------------------------------------------------------------
3703 // :: return true if the terminal is enabled
3704 // -----------------------------------------------------------------------
3705 enabled: function() {
3706 return enabled;
3707 },
3708 // -----------------------------------------------------------------------
3709 // :: Return the terminal signature depending on the size of the terminal
3710 // -----------------------------------------------------------------------
3711 signature: function() {
3712 var cols = self.cols();
3713 var i = cols < 15 ? null : cols < 35 ? 0 : cols < 55 ? 1 : cols < 64 ? 2 : cols < 75 ? 3 : 4;
3714 if (i !== null) {
3715 return signatures[i].join('\n') + '\n';
3716 } else {
3717 return '';
3718 }
3719 },
3720 // -----------------------------------------------------------------------
3721 // :: Return the version number
3722 // -----------------------------------------------------------------------
3723 version: function() {
3724 return version;
3725 },
3726 // -----------------------------------------------------------------------
3727 // :: Return actual command line object (jquery object with cmd methods)
3728 // -----------------------------------------------------------------------
3729 cmd: function() {
3730 return command_line;
3731 },
3732 // -----------------------------------------------------------------------
3733 // :: Return the current command entered by terminal
3734 // -----------------------------------------------------------------------
3735 get_command: function() {
3736 return command_line.get();
3737 },
3738 // -----------------------------------------------------------------------
3739 // :: Change the command line to the new one
3740 // -----------------------------------------------------------------------
3741 set_command: function(command) {
3742 command_line.set(command);
3743 return self;
3744 },
3745 // -----------------------------------------------------------------------
3746 // :: Insert text into the command line after the cursor
3747 // -----------------------------------------------------------------------
3748 insert: function(string) {
3749 if (typeof string === 'string') {
3750 command_line.insert(string);
3751 return self;
3752 } else {
3753 throw "insert function argument is not a string";
3754 }
3755 },
3756 // -----------------------------------------------------------------------
3757 // :: Set the prompt of the command line
3758 // -----------------------------------------------------------------------
3759 set_prompt: function(prompt) {
3760 if (validate('prompt', prompt)) {
3761 if (typeof prompt == 'function') {
3762 command_line.prompt(function(command) {
3763 prompt(command, self);
3764 });
3765 } else {
3766 command_line.prompt(prompt);
3767 }
3768 interpreters.top().prompt = prompt;
3769 }
3770 return self;
3771 },
3772 // -----------------------------------------------------------------------
3773 // :: Return the prompt used by the terminal
3774 // -----------------------------------------------------------------------
3775 get_prompt: function() {
3776 return interpreters.top().prompt;
3777 // command_line.prompt(); - can be a wrapper
3778 //return command_line.prompt();
3779 },
3780 // -----------------------------------------------------------------------
3781 // :: Enable or Disable mask depedning on the passed argument
3782 // -----------------------------------------------------------------------
3783 set_mask: function(display) {
3784 command_line.mask(display);
3785 return self;
3786 },
3787 // -----------------------------------------------------------------------
3788 // :: Return the ouput of the terminal as text
3789 // -----------------------------------------------------------------------
3790 get_output: function(raw) {
3791 if (raw) {
3792 return lines;
3793 } else {
3794 return $.map(lines, function(item) {
3795 return typeof item[0] == 'function' ? item[0]() : item[0];
3796 }).join('\n');
3797 }
3798 },
3799 // -----------------------------------------------------------------------
3800 // :: Recalculate and redraw everything
3801 // -----------------------------------------------------------------------
3802 resize: function(width, height) {
3803 if (width && height) {
3804 self.width(width);
3805 self.height(height);
3806 }
3807 width = self.width();
3808 height = self.height();
3809 var new_num_chars = get_num_chars(self);
3810 var new_num_rows = get_num_rows(self);
3811 // only if number of chars changed
3812 if (new_num_chars !== num_chars || new_num_rows !== num_rows) {
3813 num_chars = new_num_chars;
3814 num_rows = new_num_rows;
3815 redraw();
3816 if (typeof settings.onResize === 'function' &&
3817 (old_height !== height || old_width !== width)) {
3818 settings.onResize(self);
3819 }
3820 if (old_height !== height || old_width !== width) {
3821 old_height = height;
3822 old_width = width;
3823 }
3824 }
3825 return self;
3826 },
3827 // -----------------------------------------------------------------------
3828 // :: Flush the output to the terminal
3829 // -----------------------------------------------------------------------
3830 flush: function() {
3831 try {
3832 var wrapper;
3833 // print all lines
3834 $.each(output_buffer, function(i, line) {
3835 if (line === NEW_LINE) {
3836 wrapper = $('<div></div>');
3837 } else if (typeof line === 'function') {
3838 wrapper.appendTo(output);
3839 try {
3840 line(wrapper);
3841 } catch (e) {
3842 display_exception(e, 'USER:echo(finalize)');
3843 }
3844 } else {
3845 $('<div/>').html(line).appendTo(wrapper).width('100%');
3846 }
3847 });
3848 if (settings.outputLimit >= 0) {
3849 var limit = settings.outputLimit === 0 ?
3850 self.rows() :
3851 settings.outputLimit;
3852 var $lines = output.find('div div');
3853 if ($lines.length > limit) {
3854 var for_remove = $lines.slice(0, lines.length-limit+1);
3855 // you can't get parent if you remove the element so
3856 // we first get the parent
3857 var parents = for_remove.parent();
3858 for_remove.remove();
3859 parents.each(function() {
3860 var self = $(this);
3861 if (self.is(':empty')) {
3862 self.remove();
3863 }
3864 });
3865 }
3866 }
3867 scroll_to_bottom();
3868 output_buffer = [];
3869 } catch (e) {
3870 alert('[Flush] ' + exception_message(e) + '\n' +
3871 e.stack);
3872 }
3873 return self;
3874 },
3875 // -----------------------------------------------------------------------
3876 // :: Print data to the terminal output. It can have two options:
3877 // :: a function that is called with the container div that holds the
3878 // :: output (as a jquery object) every time the output is printed
3879 // :: (including resize and scrolling)
3880 // :: If the line is a function it will be called for every redraw.
3881 // -----------------------------------------------------------------------
3882 echo: function(string, options) {
3883 try {
3884 string = string || '';
3885 var settings = $.extend({
3886 flush: true,
3887 raw: false,
3888 finalize: $.noop
3889 }, options || {});
3890 output_buffer = [];
3891 draw_line(string, settings);
3892 lines.push([string, settings]);
3893 if (settings.flush) {
3894 self.flush();
3895 }
3896 on_scrollbar_show_resize();
3897 } catch (e) {
3898 // if echo throw exception we can't use error to display that
3899 // exception
3900 alert('[Terminal.echo] ' + exception_message(e) + '\n' +
3901 e.stack);
3902 }
3903 return self;
3904 },
3905 // -----------------------------------------------------------------------
3906 // :: echo red text
3907 // -----------------------------------------------------------------------
3908 error: function(message, finalize) {
3909 //quick hack to fix trailing back slash
3910 return self.echo('[[;#f00;]' + $.terminal.escape_brackets(message).
3911 replace(/\\$/, '&#92;') + ']', finalize);
3912 },
3913 // -----------------------------------------------------------------------
3914 // :: Display Exception on terminal
3915 // -----------------------------------------------------------------------
3916 exception: function(e, label) {
3917 var message = exception_message(e);
3918 if (label) {
3919 message = '&#91;' + label + '&#93;: ' + message;
3920 }
3921 if (message) {
3922 self.error(message, {
3923 finalize: function(div) {
3924 div.addClass('exception message');
3925 }
3926 });
3927 }
3928 if (typeof e.fileName === 'string') {
3929 //display filename and line which throw exeption
3930 self.pause();
3931 $.get(e.fileName, function(file) {
3932 self.resume();
3933 var num = e.lineNumber - 1;
3934 var line = file.split('\n')[num];
3935 if (line) {
3936 self.error('&#91;' + e.lineNumber + '&#93;: ' + line);
3937 }
3938 });
3939 }
3940 if (e.stack) {
3941 self.error(e.stack, {
3942 finalize: function(div) {
3943 div.addClass('exception stack-trace');
3944 }
3945 });
3946 }
3947 },
3948 // -----------------------------------------------------------------------
3949 // :: Scroll Div that holds the terminal
3950 // -----------------------------------------------------------------------
3951 scroll: function(amount) {
3952 var pos;
3953 amount = Math.round(amount);
3954 if (scroll_object.prop) { // work with jQuery > 1.6
3955 if (amount > scroll_object.prop('scrollTop') && amount > 0) {
3956 scroll_object.prop('scrollTop', 0);
3957 }
3958 pos = scroll_object.prop('scrollTop');
3959 scroll_object.scrollTop(pos + amount);
3960 } else {
3961 if (amount > scroll_object.attr('scrollTop') && amount > 0) {
3962 scroll_object.attr('scrollTop', 0);
3963 }
3964 pos = scroll_object.attr('scrollTop');
3965 scroll_object.scrollTop(pos + amount);
3966 }
3967 return self;
3968 },
3969 // -----------------------------------------------------------------------
3970 // :: Exit all interpreters and logout. The function will throw exception
3971 // :: if there is no login provided
3972 // -----------------------------------------------------------------------
3973 logout: settings.login ? function() {
3974 while (interpreters.size() > 1) {
3975 self.pop();
3976 }
3977 return self.pop();
3978 } : function() {
3979 self.error(strings.loginFunctionMissing);
3980 },
3981 // -----------------------------------------------------------------------
3982 // :: Function returns the token returned by callback function in login
3983 // :: function. It does nothing (return undefined) if there is no login
3984 // -----------------------------------------------------------------------
3985 token: settings.login ? function(local) {
3986 return $.Storage.get(self.prefix_name(local) + '_token');
3987 } : $.noop,
3988 // -----------------------------------------------------------------------
3989 // :: Function return Login name entered by the user
3990 // -----------------------------------------------------------------------
3991 login_name: settings.login ? function(local) {
3992 return $.Storage.get(self.prefix_name(local) + '_login');
3993 } : $.noop,
3994 // -----------------------------------------------------------------------
3995 // :: Function returns the name of current interpreter
3996 // -----------------------------------------------------------------------
3997 name: function() {
3998 return interpreters.top().name;
3999 },
4000 // -----------------------------------------------------------------------
4001 // :: Function return prefix name for login/token
4002 // -----------------------------------------------------------------------
4003 prefix_name: function(local) {
4004 var name = (settings.name ? settings.name + '_' : '') + terminal_id;
4005 if (local && interpreters.size() > 1) {
4006 name += '_' + interpreters.map(function(intrp) {
4007 return intrp.name;
4008 }).slice(1).join('_');
4009 }
4010 return name;
4011 },
4012 // -----------------------------------------------------------------------
4013 // :: Push a new interenter on the Stack
4014 // -----------------------------------------------------------------------
4015 push: function(interpreter, options) {
4016 options = options || {};
4017 options.name = options.name || prev_command;
4018 options.prompt = options.prompt || options.name + ' ';
4019 //names.push(options.name);
4020 var top = interpreters.top();
4021 if (top) {
4022 top.mask = command_line.mask();
4023 }
4024 make_interpreter(interpreter, function(result) {
4025 // result is object with interpreter and completion properties
4026 interpreters.push($.extend({}, result, options));
4027 if (options.login) {
4028 var type = $.type(options.login);
4029 if (type == 'function') {
4030 // self.pop on error
4031 self.login(options.login,
4032 false,
4033 prepare_top_interpreter,
4034 self.pop);
4035 } else if ($.type(interpreter) == 'string' &&
4036 type == 'string' || type == 'boolean') {
4037 self.login(make_json_rpc_login(interpreter, options.login),
4038 false,
4039 prepare_top_interpreter,
4040 self.pop);
4041 }
4042 } else {
4043 prepare_top_interpreter();
4044 }
4045 });
4046 return self;
4047 },
4048 // -----------------------------------------------------------------------
4049 // :: Remove the last interpreter from the Stack
4050 // -----------------------------------------------------------------------
4051 pop: function(string) {
4052 if (string !== undefined) {
4053 echo_command(string);
4054 }
4055 var token = self.token(true);
4056 if (interpreters.size() == 1) {
4057 if (settings.login) {
4058 global_logout();
4059 if ($.type(settings.onExit) === 'function') {
4060 try {
4061 settings.onExit(self);
4062 } catch (e) {
4063 display_exception(e, 'onExit');
4064 throw e;
4065 }
4066 }
4067 } else {
4068 self.error(strings.canExitError);
4069 }
4070 } else {
4071 if (token) {
4072 logout();
4073 }
4074 var current = interpreters.pop();
4075 prepare_top_interpreter();
4076 if ($.type(current.onExit) === 'function') {
4077 try {
4078 current.onExit(self);
4079 } catch (e) {
4080 display_exception(e, 'onExit');
4081 throw e;
4082 }
4083 }
4084 // restore mask
4085 self.set_mask(interpreters.top().mask);
4086 }
4087 return self;
4088 },
4089 // -----------------------------------------------------------------------
4090 // :: Return how deep you are in nested interpreters
4091 // -----------------------------------------------------------------------
4092 level: function() {
4093 return interpreters.size();
4094 },
4095 // -----------------------------------------------------------------------
4096 // :: Reinitialize the terminal
4097 // -----------------------------------------------------------------------
4098 reset: function() {
4099 self.clear();
4100 while(interpreters.size() > 1) {
4101 interpreters.pop();
4102 }
4103 initialize();
4104 return self;
4105 },
4106 // -----------------------------------------------------------------------
4107 // :: Remove all local storage left by terminal, it will not logout you
4108 // :: until you refresh the browser
4109 // -----------------------------------------------------------------------
4110 purge: function() {
4111 var prefix = self.prefix_name() + '_';
4112 var names = $.Storage.get(prefix + 'interpreters');
4113 $.each($.parseJSON(names), function(_, name) {
4114 $.Storage.remove(name + '_commands');
4115 $.Storage.remove(name + '_token');
4116 $.Storage.remove(name + '_login');
4117 });
4118 command_line.purge();
4119 $.Storage.remove(prefix + 'interpreters');
4120 return self;
4121 },
4122 // -----------------------------------------------------------------------
4123 // :: Remove all events and DOM nodes left by terminal, it will not purge
4124 // :: the terminal so you will have the same state when you refresh the
4125 // :: browser
4126 // -----------------------------------------------------------------------
4127 destroy: function() {
4128 command_line.destroy().remove();
4129 output.remove();
4130 $(document).unbind('.terminal');
4131 $(window).unbind('.terminal');
4132 self.unbind('click, mousewheel');
4133 self.removeData('terminal').removeClass('terminal');
4134 if (settings.width) {
4135 self.css('width', '');
4136 }
4137 if (settings.height) {
4138 self.css('height', '');
4139 }
4140 return self;
4141 }
4142 }, function(_, fun) {
4143 // wrap all functions and display execptions
4144 return function() {
4145 try {
4146 return fun.apply(this, Array.prototype.slice.apply(arguments));
4147 } catch (e) {
4148 // exec catch by command (resume call exec)
4149 if (_ !== 'exec' && _ !== 'resume') {
4150 display_exception(e, 'TERMINAL');
4151 }
4152 throw e;
4153 }
4154 };
4155 }));
4156
4157 // ---------------------------------------------------------------------
4158 var on_scrollbar_show_resize = (function() {
4159 var scroll_bars = have_scrollbars(self);
4160 return function() {
4161 if (scroll_bars !== have_scrollbars(self)) {
4162 // if the scollbar appearance changes we will have a different
4163 // number of chars
4164 self.resize();
4165 scroll_bars = have_scrollbars(self);
4166 }
4167 };
4168 })();
4169
4170 // ---------------------------------------------------------------------
4171 // INIT CODE
4172 // ---------------------------------------------------------------------
4173 if (settings.width) {
4174 self.width(settings.width);
4175 }
4176 if (settings.height) {
4177 self.height(settings.height);
4178 }
4179 if (!navigator.userAgent.toLowerCase().match(/(webkit)[ \/]([\w.]+)/) &&
4180 self[0].tagName.toLowerCase() == 'body') {
4181 scroll_object = $('html');
4182 } else {
4183 scroll_object = self;
4184 }
4185 // register ajaxSend for cancel requests on CTRL+D
4186 $(document).bind('ajaxSend.terminal', function(e, xhr, opt) {
4187 requests.push(xhr);
4188 });
4189 output = $('<div>').addClass('terminal-output').appendTo(self);
4190 self.addClass('terminal');
4191 // keep focus in clipboard textarea in mobile
4192 if (('ontouchstart' in window) || window.DocumentTouch &&
4193 document instanceof DocumentTouch) {
4194 self.click(function() {
4195 self.find('textarea').focus();
4196 });
4197 self.find('textarea').focus();
4198 }
4199 /*
4200 self.bind('touchstart.touchScroll', function() {
4201
4202 });
4203 self.bind('touchmove.touchScroll', function() {
4204
4205 });
4206 */
4207 //$('<input type="text"/>').hide().focus().appendTo(self);
4208
4209 // before login event
4210 if (settings.login && typeof settings.onBeforeLogin === 'function') {
4211 try {
4212 settings.onBeforeLogin(self);
4213 } catch (e) {
4214 display_exception(e, 'onBeforeLogin');
4215 throw e;
4216 }
4217 }
4218 // create json-rpc authentication function
4219 if (typeof init_interpreter === 'string' &&
4220 (typeof settings.login === 'string' || settings.login)) {
4221 settings.login = make_json_rpc_login(init_interpreter, settings.login);
4222 }
4223 terminals.append(self);
4224 var interpreters;
4225 var command_line;
4226 make_interpreter(init_interpreter, function(interpreter) {
4227 interpreters = new Stack($.extend({
4228 name: settings.name,
4229 prompt: settings.prompt,
4230 greetings: settings.greetings
4231 }, interpreter));
4232 // CREATE COMMAND LINE
4233 command_line = $('<div/>').appendTo(self).cmd({
4234 prompt: settings.prompt,
4235 history: settings.history,
4236 historyFilter: settings.historyFilter,
4237 historySize: settings.historySize,
4238 width: '100%',
4239 keydown: key_down,
4240 keypress: settings.keypress ? function(e) {
4241 return settings.keypress(e, self);
4242 } : null,
4243 onCommandChange: function(command) {
4244 if ($.type(settings.onCommandChange) === 'function') {
4245 try {
4246 settings.onCommandChange(command, self);
4247 } catch (e) {
4248 display_exception(e, 'onCommandChange');
4249 throw e;
4250 }
4251 }
4252 scroll_to_bottom();
4253 },
4254 commands: commands
4255 });
4256 if (enabled) {
4257 self.focus(undefined, true);
4258 } else {
4259 self.disable();
4260 }
4261 $(document).bind('click.terminal', function(e) {
4262 if (!$(e.target).closest('.terminal').hasClass('terminal') &&
4263 settings.onBlur(self) !== false) {
4264 self.disable();
4265 }
4266 });
4267 self.click(function(e) {
4268 if (!self.enabled()) {
4269 self.focus();
4270 }
4271 }).mousedown(function(e) {
4272 if (e.which == 2) {
4273 self.insert(getSelectedText());
4274 }
4275 });
4276 // ------------------------------------------------
4277 // Run Login
4278 if (settings.login) {
4279 self.login(settings.login, true, initialize);
4280 } else {
4281 initialize();
4282 }
4283 if (self.is(':visible')) {
4284 num_chars = get_num_chars(self);
4285 command_line.resize(num_chars);
4286 num_rows = get_num_rows(self);
4287 }
4288 self.oneTime(100, function() {
4289 $(window).bind('resize.terminal', function() {
4290 if (self.is(':visible')) {
4291 var width = self.width();
4292 var height = self.height();
4293 // prevent too many calculations in IE
4294 if (old_height !== height || old_width !== width) {
4295 self.resize();
4296 }
4297 }
4298 });
4299 });
4300 if ($.event.special.mousewheel) {
4301 var shift = false;
4302 $(document).bind('keydown.terminal', function(e) {
4303 if (e.shiftKey) {
4304 shift = true;
4305 }
4306 }).bind('keyup.terminal', function(e) {
4307 // in Google Chromium/Linux shiftKey is false
4308 if (e.shiftKey || e.which == 16) {
4309 shift = false;
4310 }
4311 });
4312 self.mousewheel(function(event, delta) {
4313 if (!shift) {
4314 if (delta > 0) {
4315 self.scroll(-40);
4316 } else {
4317 self.scroll(40);
4318 }
4319 //event.preventDefault();
4320 }
4321 });
4322 }
4323 });
4324 self.data('terminal', self);
4325 return self;
4326 }
4327 }; //terminal plugin
4328 })(jQuery);