3 * / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / /
4 * __ / // // // // // _ // _// // / / // _ // _// // // \/ // _ \/ /
5 * / / // // // // // ___// / / // / / // ___// / / / / // // /\ // // / /__
6 * \___//____ \\___//____//_/ _\_ / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/
7 * \/ /____/ version 0.8.7
8 * http://terminal.jcubic.pl
10 * Licensed under GNU LGPL Version 3 license
11 * Copyright (c) 2011-2013 Jakub Jankiewicz <http://jcubic.pl>
15 * Storage plugin Distributed under the MIT License
16 * Copyright (c) 2010 Dave Schindler
18 * jQuery Timers licenced with the WTFPL
19 * <http://jquery.offput.ca/every/>
21 * Cross-Browser Split 1.1.1
22 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
23 * Available under the MIT License
26 * Copyright (c) 2007-2013 Alexandru Marasteanu <hello at alexei dot ro>
27 * licensed under 3 clause BSD license
29 * Date: Sat, 22 Mar 2014 13:32:26 +0000
34 var sprintf
= function() {
35 if (!sprintf
.cache
.hasOwnProperty(arguments
[0])) {
36 sprintf
.cache
[arguments
[0]] = sprintf
.parse(arguments
[0]);
38 return sprintf
.format
.call(null, sprintf
.cache
[arguments
[0]], arguments
);
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]);
48 else if (node_type
=== 'array') {
49 match
= parse_tree
[i]; // convenience purposes only
50 if (match
[2]) { // keyword argument
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]));
56 arg
= arg
[match
[2][k]];
59 else if (match
[1]) { // positional argument (explicit)
62 else { // positional argument (implicit)
66 if (/[^s
]/.test(match
[8]) && (get_type(arg
) != 'number')) {
67 throw(sprintf('[sprintf] expecting number but found %s', get_type(arg
)));
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;
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
);
88 return output
.join('');
93 sprintf
.parse
= function(fmt
) {
94 var _fmt
= fmt
, match
= [], parse_tree
= [], arg_names
= 0;
96 if ((match
= /^
[^
\x25]+/.exec(_fmt
)) !== null) {
97 parse_tree
.push(match
[0]);
99 else if ((match
= /^
\x25{2}/.exec(_fmt
)) !== null) {
100 parse_tree
.push('%');
102 else if ((match
= /^
\x25(?
:([1-9]\d
*)\$|\
(([^\
)]+)\
))?
(\
+)?
(0|
'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
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]);
112 else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
113 field_list.push(field_match[1]);
116 throw('[sprintf] huh?
');
121 throw('[sprintf] huh?
');
123 match[2] = field_list;
128 if (arg_names === 3) {
129 throw('[sprintf] mixing positional and named placeholders is
not (yet
) supported
');
131 parse_tree.push(match);
134 throw('[sprintf] huh?
');
136 _fmt = _fmt.substring(match[0].length);
141 var vsprintf = function(fmt, argv, _argv) {
142 _argv = argv.slice(0);
143 _argv.splice(0, 0, fmt);
144 return sprintf.apply(null, _argv);
150 function get_type(variable) {
151 return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
154 function str_repeat(input, multiplier) {
155 for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
156 return output.join('');
160 * export to either browser or node.js
162 ctx.sprintf = sprintf;
163 ctx.vsprintf = vsprintf;
164 })(typeof exports != "undefined" ? exports : window);
166 (function($, undefined) {
168 // -----------------------------------------------------------------------
169 // :: map object to object
170 // -----------------------------------------------------------------------
171 $.omap = function(o, fn) {
173 $.each(o, function(k, v) {
174 result[k] = fn.call(o, k, v);
178 // -----------------------------------------------------------------------
180 // -----------------------------------------------------------------------
182 var isLS = typeof window.localStorage !== 'undefined
';
186 if (typeof n === 'string
' && typeof v === 'string
') {
189 } else if (typeof n === 'object
' && typeof v === 'undefined
') {
191 if (n.hasOwnProperty(c)) {
192 localStorage[c] = n[c];
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
=/';
207 } else if (typeof n === 'object
' && typeof v === 'undefined
') {
209 if (n.hasOwnProperty(c)) {
210 document.cookie = c + '=' + n[c] + e + '; path
=/';
218 return localStorage[n];
223 ca = document.cookie.split(';');
224 for (i = 0; i < ca.length; i++) {
226 while (c.charAt(0) === ' ') {
227 c = c.substring(1, c.length);
229 if (c.indexOf(nn) === 0) {
230 return c.substring(nn.length, c.length);
236 return delete localStorage[n];
239 return wc(n, '', -1);
243 * $.Storage.set("name", "value")
244 * $.Storage.set({"name1":"value1", "name2":"value2", etc})
245 * $.Storage.get("name")
246 * $.Storage.remove("name")
250 set: isLS ? wls : wc,
251 get: isLS ? rls : rc,
252 remove: isLS ? dls : dc
255 // -----------------------------------------------------------------------
257 // -----------------------------------------------------------------------
259 everyTime: function(interval, label, fn, times, belay) {
260 return this.each(function() {
261 jQuery.timer.add(this, interval, label, fn, times, belay);
264 oneTime: function(interval, label, fn) {
265 return this.each(function() {
266 jQuery.timer.add(this, interval, label, fn, 1);
269 stopTime: function(label, fn) {
270 return this.each(function() {
271 jQuery.timer.remove(this, label, fn);
280 regex: /^([0-9]+)\s*(.*s)?$/,
282 // Yeah this is major overkill...
291 timeParse: function(value) {
292 if (value === undefined || value === null) {
295 var result = this.regex.exec(jQuery.trim(value.toString()));
297 var num = parseInt(result[1], 10);
298 var mult = this.powers[result[2]] || 1;
304 add: function(element, interval, label, fn, times, belay) {
307 if (jQuery.isFunction(label)) {
315 interval = jQuery.timer.timeParse(interval);
317 if (typeof interval !== 'number
' ||
322 if (times && times.constructor !== Number) {
328 belay = belay || false;
330 if (!element.$timers) {
331 element.$timers = {};
333 if (!element.$timers[label]) {
334 element.$timers[label] = {};
336 fn.$timerID = fn.$timerID || this.guid++;
338 var handler = function() {
339 if (belay && handler.inProgress) {
342 handler.inProgress = true;
343 if ((++counter > times && times !== 0) ||
344 fn.call(element, counter) === false) {
345 jQuery.timer.remove(element, label, fn);
347 handler.inProgress = false;
350 handler.$timerID = fn.$timerID;
352 if (!element.$timers[label][fn.$timerID]) {
353 element.$timers[label][fn.$timerID] = window.setInterval(handler, interval);
356 if (!this.global[label]) {
357 this.global[label] = [];
359 this.global[label].push(element);
362 remove: function(element, label, fn) {
363 var timers = element.$timers, ret;
368 for (var lab in timers) {
369 if (timers.hasOwnProperty(lab)) {
370 this.remove(element, lab, fn);
373 } else if (timers[label]) {
376 window.clearInterval(timers[label][fn.$timerID]);
377 delete timers[label][fn.$timerID];
380 for (var _fn in timers[label]) {
381 if (timers[label].hasOwnProperty(_fn)) {
382 window.clearInterval(timers[label][_fn]);
383 delete timers[label][_fn];
388 for (ret in timers[label]) {
389 if (timers[label].hasOwnProperty(ret)) {
395 delete timers[label];
399 for (ret in timers) {
400 if (timers.hasOwnProperty(ret)) {
405 element.$timers = null;
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;
419 jQuery.timer.remove(els[i], label);
425 // -----------------------------------------------------------------------
426 // :: CROSS BROWSER SPLIT
427 // -----------------------------------------------------------------------
431 // prevent double include
433 if (!String.prototype.split.toString().match(/\[native/)) {
437 var nativeSplit = String.prototype.split,
438 compliantExecNpcg = /()??/.exec("")[1] === undef, // NPCG: nonparticipating capturing group
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);
447 flags = (separator.ignoreCase ? "i" : "") +
448 (separator.multiline ? "m" : "") +
449 (separator.extended ? "x" : "") + // Proposed for ES6
450 (separator.sticky ? "y" : ""), // Firefox 3+
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);
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
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
) {
485 if (match
.length
> 1 && match
.index
< str
.length
) {
486 Array
.prototype.push
.apply(output
, match
.slice(1));
488 lastLength
= match
[0].length
;
489 lastLastIndex
= lastIndex
;
490 if (output
.length
>= limit
) {
494 if (separator
.lastIndex
=== match
.index
) {
495 separator
.lastIndex
++; // Avoid an infinite loop
498 if (lastLastIndex
=== str
.length
) {
499 if (lastLength ||
!separator
.test("")) {
503 output
.push(str
.slice(lastLastIndex
));
505 return output
.length
> limit ? output
.slice(0, limit
) : output
;
509 String
.prototype.split
= function (separator
, limit
) {
510 return self(this, separator
, limit
);
516 // -----------------------------------------------------------------------
517 // :: Split string to array of strings with the same length
518 // -----------------------------------------------------------------------
519 function str_parts(str
, length
) {
521 var len
= str
.length
;
525 for (var i
= 0; i
< len
; i
+= length
) {
526 result
.push(str
.substring(i
, i
+ length
));
530 // -----------------------------------------------------------------------
531 // :: CYCLE DATA STRUCTURE
532 // -----------------------------------------------------------------------
533 function Cycle(init
) {
534 var data
= init ?
[init] : [];
541 if (data
.length
=== 1) {
544 if (pos
=== data
.length
- 1) {
555 set
: function(item
) {
556 for (var i
= data
.length
; i
--;) {
557 if (data
[i] === item
) {
567 append
: function(item
) {
572 // -----------------------------------------------------------------------
573 // :: STACK DATA STRUCTURE
574 // -----------------------------------------------------------------------
575 function Stack(init
) {
576 var data
= init ?
[init] : [];
579 return $
.map(data
, fn
);
585 if (data
.length
=== 0) {
588 var value
= data
[data
.length
- 1];
589 data
= data
.slice(0, data
.length
- 1);
593 push
: function(value
) {
594 data
= data
.concat([value]);
598 return data
.length
> 0 ? data
[data
.length
- 1] : null;
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
) {
608 level
= level
=== undefined ?
1 : level
;
609 var type
= typeof object
;
615 result
+= object ?
'true' : 'false';
618 if (object
=== null) {
620 } else if (object
instanceof Array
) {
622 var len
= object
.length
;
623 for (i
= 0; i
< len
- 1; ++i
) {
624 result
+= $
.json_stringify(object
[i], level
+ 1);
626 result
+= $
.json_stringify(object
[len
- 1], level
+ 1) + ']';
629 for (var property
in object
) {
630 if (object
.hasOwnProperty(property
)) {
631 result
+= '"' + property
+ '":' +
632 $
.json_stringify(object
[property], level
+ 1);
648 if (repl
.hasOwnProperty(i
)) {
649 str
= str
.replace(new RegExp(i
, 'g'), repl
[i]);
652 result
+= '"' + str
+ '"';
655 result
+= String(object
);
658 result
+= (level
> 1 ?
',' : '');
662 result
= result
.replace(/,([\
]}])/g
, '$1');
664 // fix comma before array or object
665 return result
.replace(/([\
[{]),/g
, '$1');
667 // -----------------------------------------------------------------------
669 // -----------------------------------------------------------------------
670 function History(name
, size
) {
672 var storage_key
= '';
673 if (typeof name
=== 'string' && name
!== '') {
674 storage_key
= name
+ '_';
676 storage_key
+= 'commands';
677 var data
= $
.Storage
.get(storage_key
);
678 data
= data ? $
.parseJSON(data
) : [];
679 var pos
= data
.length
-1;
681 append
: function(item
) {
683 if (data
[data
.length
-1] !== item
) {
685 if (size
&& data
.length
> size
) {
686 data
= data
.slice(-size
);
689 $
.Storage
.set(storage_key
, $
.json_stringify(data
));
700 return data
[length
-1];
703 return pos
=== data
.length
-1;
705 position
: function() {
708 current
: function() {
712 if (pos
< data
.length
-1) {
719 previous
: function() {
732 enabled
: function() {
739 $
.Storage
.remove(storage_key
);
741 disable
: function() {
746 // -----------------------------------------------------------------------
747 // :: COMMAND LINE PLUGIN
748 // -----------------------------------------------------------------------
749 $
.fn
.cmd
= function(options
) {
751 var maybe_data
= self
.data('cmd');
755 self
.addClass('cmd');
756 self
.append('<span class="prompt"></span><span></span>' +
757 '<span class="cursor"> </span><span></span>');
758 var clip
= $
('<textarea/>').addClass('clipboard').appendTo(self
);
760 self
.width(options
.width
);
762 var num_chars
; // calculated by draw_prompt
764 var reverse_search
= false;
765 var reverse_search_string
= '';
766 var reverse_search_position
= null;
768 var mask
= options
.mask ||
false;
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
774 var enabled
= options
.enabled
;
775 var historySize
= options
.historySize ||
60;
777 var cursor
= self
.find('.cursor');
779 if (supportAnimations()) {
780 animation
= function(toggle
) {
782 cursor
.addClass('blink');
784 cursor
.removeClass('blink');
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');
798 // -----------------------------------------------------------------------
799 // :: Blinking cursor function
800 // -----------------------------------------------------------------------
802 cursor
.toggleClass('inverted');
804 // -----------------------------------------------------------------------
805 // :: Set prompt for reverse search
806 // -----------------------------------------------------------------------
807 function draw_reverse_prompt() {
808 prompt
= "(reverse-i-search)`" + reverse_search_string
+ "': ";
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
= '';
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
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
;
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
;
841 self
.set(history_data
[i], true);
843 if (reverse_search_string
.length
!== j
) {
844 reverse_search_string
= reverse_search_string
.substring(0, j
);
845 draw_reverse_prompt();
852 reverse_search_string
= ''; // clear if not found any
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
);
862 // -----------------------------------------------------------------------
863 // :: Return string repeated n times
864 // -----------------------------------------------------------------------
865 function str_repeat(str
, n
) {
867 for (var i
= n
; i
--;) {
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
));
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(' ');
897 } else if (position
=== 0) {
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));
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
=== ' ' ?
' ' : $
.terminal
.encode(c
, true));
910 if (position
=== string
.length
- 1) {
913 after
.html($
.terminal
.encode(string
.slice(position
+ 1), true));
917 function div(string
) {
918 return '<div>' + $
.terminal
.encode(string
, true) + '</div>';
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
).
930 // -----------------------------------------------------------------------
931 // :: Display lines before the cursor
932 // -----------------------------------------------------------------------
933 function lines_before(lines
) {
934 $
.each(lines
, function(i
, line
) {
935 before
.before(div(line
));
939 // -----------------------------------------------------------------------
940 // :: Redraw function
941 // -----------------------------------------------------------------------
943 var string
= mask ? command
.replace(/./g
, '*') : command
;
945 self
.find('div').remove();
948 if (string
.length
> num_chars
- prompt_len
- 1 ||
949 string
.match(/\n/)) {
951 var tabs
= string
.match(/\t/g
);
952 var tabs_rm
= tabs ? tabs
.length
* 3 : 0;
953 //quick tabulation hack
955 string
= string
.replace(/\t/g
, '\x00\x00\x00\x00');
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
) {
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
));
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
));
981 array
= get_splited_command_line(string
);
984 array
= $
.map(array
, function(line
) {
985 return line
.replace(/\x00\x00\x00\x00/g
, '\t');
988 first_len
= array
[0].length
;
989 //cursor in first line
990 if (first_len
=== 0 && array
.length
=== 1) {
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));
1000 var num_lines
= array
.length
;
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));
1010 var last
= array
.slice(-1)[0];
1011 var from_last
= string
.length
- position
;
1012 var last_len
= last
.length
;
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
);
1020 if (num_lines
=== 3) {
1021 before
.before('<div>' + $
.terminal
.encode(array
[0], true) +
1023 draw_cursor_line(array
[1], position
-first_len
-1);
1024 after
.after('<div class="clear">' +
1025 $
.terminal
.encode(array
[2], true) +
1028 // more lines, cursor in the middle
1032 for (i
=0; i
<array
.length
; ++i
) {
1033 var current_len
= array
[i].length
;
1034 if (pos
> current_len
) {
1042 // cursor on first character in line
1043 if (pos
=== current
.length
) {
1045 current
= array
[++line_index
];
1047 draw_cursor_line(current
, pos
);
1048 lines_before(array
.slice(0, line_index
));
1049 lines_after(array
.slice(line_index
+1));
1055 if (string
=== '') {
1057 cursor
.html(' ');
1060 draw_cursor_line(string
, position
);
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
)));
1076 switch (typeof prompt
) {
1086 // -----------------------------------------------------------------------
1087 // :: Paste content to terminal using hidden textarea
1088 // -----------------------------------------------------------------------
1091 //wait until Browser insert text to textarea
1092 self
.oneTime(1, function() {
1093 self
.insert(clip
.val());
1094 clip
.blur().val('');
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;
1112 if (e
.which
!== 38 &&
1113 !(e
.which
=== 80 && e
.ctrlKey
)) {
1114 first_up_history
= true;
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();
1123 if (e
.which
=== 27) { // ESC
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(/[^
]+ |
[^
]+$
/, ''),
1137 // chrome jump to address bar
1141 } else if (e
.keyCode
=== 13) { //enter
1145 if (history
&& command
&& !mask
&&
1146 ((typeof options
.historyFilter
== 'function' &&
1147 options
.historyFilter(command
)) ||
1148 !options
.historyFilter
)) {
1149 history
.append(command
);
1154 if (options
.commands
) {
1155 options
.commands(tmp
);
1157 if (typeof prompt
=== 'function') {
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();
1166 if (command
!== '' && position
> 0) {
1167 command
= command
.slice(0, position
- 1) +
1168 command
.slice(position
, command
.length
);
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
);
1179 } else if (e
.which
=== 9 && !(e
.ctrlKey || e
.altKey
)) { // TAB
1181 } else if (e
.which
=== 46) {
1183 if (command
!== '' && position
< command
.length
) {
1184 command
= command
.slice(0, position
) +
1185 command
.slice(position
+ 1, command
.length
);
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());
1196 self
.set(history
.previous());
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) {
1209 if (command
[len] === ' ') {
1212 for (var i
= len
; i
> 0; --i
) {
1213 if (command
[i] === ' ' && command
[i
+1] !== ' ') {
1216 } else if (command
[i] === '\n' && command
[i
+1] !== '\n') {
1223 //LEFT ARROW or CTRL+B
1229 } else if (e
.which
=== 82 && e
.ctrlKey
) { // CTRL+R
1230 if (reverse_search
) {
1231 reverse_history_search(true);
1233 backup_prompt
= prompt
;
1234 draw_reverse_prompt();
1235 last_command
= command
;
1238 reverse_search
= true;
1240 } else if (e
.which
== 71 && e
.ctrlKey
) { // CTRL+G
1241 if (reverse_search
) {
1242 prompt
= backup_prompt
;
1244 command
= last_command
;
1246 reverse_search
= false;
1247 reverse_search_string
= '';
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] === ' ') {
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
;
1262 if (match
[0][0] !== ' ') {
1263 position
+= match
.index
+ 1;
1265 position
+= match
.index
+ match
[0].length
- 1;
1266 if (match
[0][match
[0].length
-1] !== ' ') {
1273 if (position
< command
.length
) {
1278 } else if (e
.which
=== 123) { //F12 - Allow Firebug
1280 } else if (e
.which
=== 36) { //HOME
1282 } else if (e
.which
=== 35) {
1284 self
.position(command
.length
);
1285 } else if (e
.shiftKey
&& e
.which
== 45) { // Shift+Insert
1288 } else if (e
.ctrlKey || e
.metaKey
) {
1289 if (e
.which
=== 192) { // CMD+` switch browser window on Mac
1293 if(e
.which
=== 82) { // CMD+r page reload in Chrome Mac
1295 } else if(e
.which
=== 76) {
1296 return true; // CMD+l jump into Omnibox on Chrome Mac
1299 if (e
.shiftKey
) { // CTRL+SHIFT+??
1300 if (e
.which
=== 84) {
1301 //CTRL+SHIFT+T open closed tab
1304 //} else if (e.altKey) { //ALT+CTRL+??
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
;
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
);
1327 //NOTE: in opera charCode is undefined
1328 } else if (e
.which
=== 65) {
1331 } else if (e
.which
=== 69) {
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
1337 } else if (e
.which
=== 89) { // CTRL+Y
1338 if (kill_text
!== '') {
1339 self
.insert(kill_text
);
1341 } else if (e
.which
=== 86) {
1345 } else if (e
.which
=== 75) {
1347 if (position
=== 0) {
1348 kill_text
= command
;
1350 } else if (position
!== command
.length
) {
1351 kill_text
= command
.slice(position
);
1352 self
.set(command
.slice(0, position
));
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
));
1360 } else if (e
.which
=== 17) { //CTRL+TAB switch tab
1369 if ((e.altKey && e.which === 68) ||
1371 $.inArray(e.which, [65, 66, 68, 69, 80, 78, 70]) > -1) ||
1373 [35, 36, 37, 38, 39, 40].has(e.which)) {
1378 var history_list
= [];
1379 // -----------------------------------------------------------------------
1380 // :: Command Line Methods
1381 // -----------------------------------------------------------------------
1383 name
: function(string
) {
1384 if (string
!== undefined
) {
1386 var enabled
= history
&& history
.enabled() ||
!history
;
1387 history
= new History(string
, historySize
);
1388 // disable new history if old was disabled
1401 history
: function() {
1404 set
: function(string
, stay
) {
1405 if (string
!== undefined
) {
1408 position
= command
.length
;
1411 if (typeof options
.onCommandChange
=== 'function') {
1412 options
.onCommandChange(command
);
1417 insert
: function(string
, stay
) {
1418 if (position
=== command
.length
) {
1420 } else if (position
=== 0) {
1421 command
= string
+ command
;
1423 command
= command
.slice(0, position
) +
1424 string
+ command
.slice(position
);
1427 position
+= string
.length
;
1430 if (typeof options
.onCommandChange
=== 'function') {
1431 options
.onCommandChange(command
);
1438 commands
: function(commands
) {
1440 options
.commands
= commands
;
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');
1454 prompt
: function(user_prompt
) {
1455 if (user_prompt
=== undefined
) {
1458 if (typeof user_prompt
=== 'string' ||
1459 typeof user_prompt
=== 'function') {
1460 prompt
= user_prompt
;
1462 throw 'prompt must be a function or string';
1465 // we could check if command is longer then numchars-new prompt
1470 kill_text
: function() {
1473 position
: function(n
) {
1474 if (typeof n
=== 'number') {
1475 position
= n
< 0 ?
0 : n
> command
.length ? command
.length
: n
;
1482 visible
: (function() {
1483 var visible
= self
.visible
;
1485 visible
.apply(self
, []);
1491 var show
= self
.show
;
1493 show
.apply(self
, []);
1498 resize
: function(num
) {
1507 enable
: function() {
1512 isenabled
: function() {
1515 disable
: function() {
1520 mask
: function(display
) {
1521 if (typeof display
=== 'boolean') {
1530 // -----------------------------------------------------------------------
1532 // -----------------------------------------------------------------------
1533 self
.name(options
.name || options
.prompt ||
'');
1534 prompt
= options
.prompt ||
'> ';
1536 if (options
.enabled
=== undefined || options
.enabled
=== true) {
1541 $
(document
.documentElement || window
).bind('keypress.cmd', function(e
) {
1543 if (e
.ctrlKey
&& e
.which
=== 99) { // CTRL+C
1546 if (!reverse_search
&& typeof options
.keypress
=== 'function') {
1547 result
= options
.keypress(e
);
1549 if (result
=== undefined || result
) {
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
)) {
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();
1563 self
.insert(String
.fromCharCode(e
.which
));
1571 }).bind('keydown.cmd', keydown_event
);
1573 self
.data('cmd', self
);
1577 // -------------------------------------------------------------------------
1579 // -------------------------------------------------------------------------
1580 function skipFormattingCount(string
) {
1581 // this will covert html entities to single characters
1582 return $
('<div>' + $
.terminal
.strip(string
) + '</div>').text().length
;
1584 // -------------------------------------------------------------------------
1585 function formattingCount(string
) {
1586 return string
.length
- skipFormattingCount(string
);
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(' '),
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() + '-';
1611 // -------------------------------------------------------------------------
1612 function processCommand(string
, fn
) {
1613 var args
= string
.replace(/^\s
+|\s
+$
/g
, '').split(/(\s
+)/);
1614 var rest
= string
.replace(/^
[^\s
]+\s*/
, '');
1621 // colors from http://www.w3.org/wiki/CSS/Properties/color/keywords
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;
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;
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');
1681 // -----------------------------------------------------------------------
1682 // :: test if string contain formatting
1683 // -----------------------------------------------------------------------
1684 have_formatting: function(str) {
1685 return str.match(format_re);
1687 is_formatting: function(str) {
1688 return str.match(format_full_re);
1690 // -----------------------------------------------------------------------
1691 // :: return array of formatting and text between them
1692 // -----------------------------------------------------------------------
1693 format_split: function(str) {
1694 return str.split(format_split_re);
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;
1704 var prev_format = '';
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) {
1713 } else if (semicolons
== 3) {
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
, ']').replace(/\n/g
, '\\n') + ']' +
1725 for (var i
= 0, len
= array
.length
; i
< len
; ++i
) {
1726 if (array
[i] === '') {
1730 var line
= array
[i];
1731 var first_index
= 0;
1733 for (var j
=0, jlen
=line
.length
; j
<jlen
; ++j
) {
1734 if (line
[j] === '[' && line
[j
+1] === '[') {
1736 } else if (formatting
&& line
[j] === ']') {
1743 } else if ((formatting
&& in_text
) ||
!formatting
) {
1744 if (line
[j] === '&') { // treat entity as one character
1745 var m
= line
.substring(j
).match(/^
(&[^
;]+;)/);
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);
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
1754 result
.push(output_line
+ m
[1]);
1757 } else if (line
[j] === ']' && line
[j
-1] === '\\') {
1758 // escape \] counts as one character
1764 if (count
=== length || j
=== jlen
-1) {
1765 var output_line
= line
.substring(first_index
, j
+1);
1767 output_line
= prev_format
+ output_line
;
1768 if (output_line
.match(']')) {
1774 // Fix output_line if formatting not closed
1775 var matched
= output_line
.match(format_re
);
1777 var last
= matched
[matched
.length
-1];
1778 if (last
[last
.length
-1] !== ']') {
1779 prev_format
= last
.match(format_begin_re
)[1];
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];
1788 result
.push(output_line
);
1794 // -----------------------------------------------------------------------
1795 // :: Encode formating as html for insertion into DOM
1796 // -----------------------------------------------------------------------
1797 encode
: function(str
, full
) {
1798 // don't escape entities
1800 str
= str
.replace(/&(?
![^
=]+=)/g
, '&');
1802 str
= str
.replace(/&(?
!#
[0-9]+;|
[a
-zA
-Z
]+;|
[^
= "]+=[^=])/g, '&');
1804 return str.replace(/</g, '<').replace(/>/g, '>')
1805 .replace(/ /g, ' ')
1806 .replace(/\t/g, ' ');
1808 // -----------------------------------------------------------------------
1809 // :: Replace terminal formatting with html
1810 // -----------------------------------------------------------------------
1811 format: function(str, options) {
1812 var settings = $.extend({}, {
1813 linksNoReferrer: false
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) {
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,
1832 return ''; //'<span> </span>';
1834 text = text.replace(/\\]/g, ']');
1836 if (style.indexOf('b') !== -1) {
1837 style_str += 'font-weight:bold;';
1839 var text_decoration = [];
1840 if (style.indexOf('u') !== -1) {
1841 text_decoration.push('underline');
1843 if (style.indexOf('s') !== -1) {
1844 text_decoration.push('line-through');
1846 if (style.indexOf('o') !== -1) {
1847 text_decoration.push('overline');
1849 if (text_decoration.length) {
1850 style_str += 'text-decoration:' +
1851 text_decoration.join(' ') + ';';
1853 if (style.indexOf('i') !== -1) {
1854 style_str += 'font-style:italic;';
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 + ';';
1862 if ($.terminal.valid_color(background)) {
1863 style_str += 'background-color:' + background;
1866 if (data_text === '') {
1869 data = data_text.replace(/]/g, ']');
1871 var result = '<span style="' + style_str + '"' +
1872 (_class !== '' ? ' class="' + _class + '"' : '') +
1873 ' data-text="'+ data.replace('"', '"e;') +
1874 '">' + text + '</span
>';
1878 return '<span
>' + text + '</span
>';
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
>' +
1891 }).replace(email_re, '<a href
="mailto:$1">$
1</a
>');
1895 }).join('').replace(/<span><br\/?><\/span>/g, '<br
/>');
1900 // -----------------------------------------------------------------------
1901 // :: Replace brackets with html entities
1902 // -----------------------------------------------------------------------
1903 escape_brackets: function(string) {
1904 return string.replace(/\[/g, '&#
91;').replace(/\]/g, '&#
93;');
1906 // -----------------------------------------------------------------------
1907 // :: Remove formatting from text
1908 // -----------------------------------------------------------------------
1909 strip: function(str) {
1910 return str.replace(format_parts_re, '$
6');
1912 // -----------------------------------------------------------------------
1913 // :: Return active terminal
1914 // -----------------------------------------------------------------------
1915 active: function() {
1916 return terminals.front();
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) {
1931 // -----------------------------------------------------------------------
1932 // :: Html colors taken from ANSI formatting in Linux Terminal
1933 // -----------------------------------------------------------------------
1965 // XTerm 8-bit pallete
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
'
2020 // -----------------------------------------------------------------------
2021 // :: Replace ANSI formatting with terminal formatting
2022 // -----------------------------------------------------------------------
2023 from_ansi: (function() {
2034 39: 'white
' // default color
2046 49: 'black
' // default background
2048 function format_ansi(code) {
2049 var controls = code.split(';');
2052 var reverse = false;
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);
2076 process_8bit = true;
2082 _8bit_background = true;
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];
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];
2104 process_8bit = false;
2108 if (output_color && output_background) {
2109 var tmp = output_background;
2110 output_background = output_color;
2113 output_color = 'black
';
2114 output_background = 'white
';
2117 var colors, backgrounds;
2119 colors = backgrounds = $.terminal.ansi_colors.bold;
2120 } else if (faited) {
2121 colors = backgrounds = $.terminal.ansi_colors.faited;
2123 colors = backgrounds = $.terminal.ansi_colors.normal;
2125 return [styles.join(''),
2126 _8bit_color ? output_color : colors[output_color],
2127 _8bit_background ? output_background : backgrounds[output_background]
2130 return function(input) {
2131 var splitted = input.split(/(\x1B\[[0-9;]*[A-Za-z])/g);
2132 if (splitted.length == 1) {
2136 //skip closing at the begining
2137 if (splitted.length > 3 && splitted.slice(0,3).join('') == '[0m
') {
2138 splitted = splitted.slice(3);
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])$/);
2146 if (match[1] === '') {
2149 if (match[1] !== '0') {
2150 code = format_ansi(match[1]);
2154 if (match[1] == '0') {
2157 prev_color = prev_background = '';
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
2165 prev_color = code[1];
2168 prev_background = code[2];
2172 if (match[1] != '0') {
2174 output.push('[[' + code.join(';') + ']');
2175 // store colors to next use
2177 prev_color = code[1];
2180 prev_background = code[2];
2187 output.push(splitted[i]);
2193 return output.join(''); //.replace(/\[\[[^\]]+\]\]/g, '');
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
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
') {
2211 } else if (string[1] === 'n
') {
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));
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);
2229 return arg.replace(/\\ /g, ' ');
2233 // -----------------------------------------------------------------------
2234 // :: Split arguments: it only strips single and double quotes and escapes
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] == '/') {
2246 return arg.replace(/\\ /g, ' ');
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);
2257 // -----------------------------------------------------------------------
2258 // :: Same as parseCommand but arguments are parsed using splitArguments
2259 // -----------------------------------------------------------------------
2260 splitCommand: function(string) {
2261 return processCommand(string, $.terminal.splitArguments);
2263 // -----------------------------------------------------------------------
2264 // :: Test $.terminal functions using terminal
2265 // -----------------------------------------------------------------------
2267 var term = $.terminal.active();
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);
2276 term.echo('Testing
...');
2277 function assert(cond, msg) {
2278 term.echo(msg + ' &#
91;' + (cond ? '[[b
;#
44D544
;]PASS
]' : '[[b
;#FF5555
;]FAIL
]') + '&#
93;');
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]);
2311 for (var j
=0; j
<lines
.length
; ++j
) {
2312 if ($
.terminal
.strip(lines
[j]).length
> cols
[i]) {
2317 assert(success
, '\tsplit ' + cols
[i]);
2322 // -----------------------------------------------------------------------
2324 // -----------------------------------------------------------------------
2325 $
.fn
.visible
= function() {
2326 return this.css('visibility', 'visible');
2328 $
.fn
.hidden
= function() {
2329 return this.css('visibility', 'hidden');
2331 // -----------------------------------------------------------------------
2333 // -----------------------------------------------------------------------
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]});
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');
2349 throw new Error('WARN: Response Content-Type is not application/json');
2354 json
= $
.parseJSON(result
);
2357 error(jqXHR
, 'Invalid JSON', e
);
2359 throw new Error('Invalid JSON');
2363 // don't catch errors in success callback
2364 success(json
, status
, jqXHR
);
2367 contentType
: 'application/json',
2375 // -----------------------------------------------------------------------
2376 function is_scrolled_into_view(elem
) {
2377 var docViewTop
= $
(window
).scrollTop();
2378 var docViewBottom
= docViewTop
+ $
(window
).height();
2380 var elemTop
= $
(elem
).offset().top
;
2381 var elemBottom
= elemTop
+ $
(elem
).height();
2383 return ((elemBottom
>= docViewTop
) && (elemTop
<= docViewBottom
));
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> ' +
2393 '</span></div></div>').appendTo('body');
2394 var span
= temp
.find('span');
2396 width
: span
.width(),
2397 height
: span
.outerHeight()
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));
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
);
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
;
2433 return selection
.toString();
2435 } else if (document
.selection
) {
2436 return document
.selection
.createRange().text
;
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();
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 // -----------------------------------------------------------------------
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
, ' ') +
2471 [' __ _____ ________ __',
2472 ' / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / /',
2473 ' __ / // // // // // _ // _// // / / // _ // _// // // \\/ // _ \\/ /',
2474 '/ / // // // // // ___// / / // / / // ___// / / / / // // /\\ // // / /__',
2475 '\\___//____ \\\\___//____//_/ _\\_ / /_//____//_/ /_/ /_//_//_/ /_/ \\__\\_\\___/',
2476 ' \\/ /____/ '.replace(reg
, '') +
2480 // -----------------------------------------------------------------------
2481 // :: Default options
2482 // -----------------------------------------------------------------------
2483 $
.terminal
.defaults
= {
2491 exceptionHandler
: null,
2492 cancelableAjax
: true,
2493 processArguments
: true,
2494 linksNoReferrer
: false,
2500 historyFilter
: null,
2505 onTerminalChange
: $
.noop
,
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",
2527 password
: "password"
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
);
2545 return $
.terminal
.splitCommand(command
);
2548 // -----------------------------------------------------------------------
2549 // :: Display object on terminal
2550 // -----------------------------------------------------------------------
2551 function display_object(object
) {
2552 if (typeof object
=== 'string') {
2554 } else if (object
instanceof Array
) {
2555 self
.echo($
.map(object
, function(object
) {
2556 return $
.json_stringify(object
);
2558 } else if (typeof object
=== 'object') {
2559 self
.echo($
.json_stringify(object
));
2564 // -----------------------------------------------------------------------
2565 // :: Helper function
2566 // -----------------------------------------------------------------------
2567 function display_json_rpc_error(error
) {
2568 if (typeof settings
.onRPCError
=== 'function') {
2569 settings
.onRPCError
.call(self
, error
);
2571 self
.error('[RPC] ' + error
.message
);
2574 // -----------------------------------------------------------------------
2575 // :: Create interpreter function from url string
2576 // -----------------------------------------------------------------------
2577 function make_basic_json_rpc_interpreter(url
) {
2578 var service
= function(method
, params
) {
2580 $
.jrpc(url
, method
, params
, function(json
) {
2582 if (typeof settings
.processRPCResponse
=== 'function') {
2583 settings
.processRPCResponse
.call(self
, json
.result
);
2585 display_object(json
.result
);
2588 display_json_rpc_error(json
.error
);
2593 //this is the interpreter function
2594 return function(command
, terminal
) {
2595 if (command
=== '') {
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
);
2603 var token
= terminal
.token();
2605 service(command
.name
, [token].concat(command
.args
));
2607 //should never happen
2608 terminal
.error('[AUTH] ' +
2609 strings
.noTokenError
);
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
=== '') {
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("[Arity] " +
2632 sprintf(strings
.wrongArity
,
2635 command
.args
.length
));
2637 return val
.apply(self
, command
.args
);
2639 } else if (type
=== 'object' || type
=== 'string') {
2641 if (type
=== 'object') {
2642 commands
= Object
.keys(val
);
2643 val
= make_object_interpreter(val
, arity
);
2645 terminal
.push(val
, {
2646 prompt
: command
.name
+ '> ',
2648 completion
: type
=== 'object' ?
function(term
, string
, callback
) {
2653 if ($
.type(fallback
) === 'function') {
2654 fallback(user_command
, self
);
2655 } else if ($
.type(settings
.onCommandNotFound
) === 'function') {
2656 settings
.onCommandNotFound(user_command
, self
);
2658 terminal
.error(sprintf(strings
.commandNotFound
, command
.name
));
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('[AJAX] ' + status
+ ' - ' +
2670 strings
.serverResponse
+
2671 ': \n' + xhr
.responseText
);
2674 // -----------------------------------------------------------------------
2675 function make_json_rpc_object(url
, success
) {
2676 $
.jrpc(url
, 'system.describe', [], function(ret
) {
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("[Arity] " +
2686 sprintf(strings
.wrongArity
,
2692 $
.jrpc(url
, proc
.name
, args
, function(json
) {
2694 display_json_rpc_error(json
.error
);
2696 display_object(json
.result
);
2703 success(interpreter_object
);
2711 // -----------------------------------------------------------------------
2712 function make_interpreter(user_interpreter
, finalize
) {
2713 finalize
= finalize || $
.noop
;
2714 var type
= $
.type(user_interpreter
);
2717 var rpc_count
= 0; // only one rpc can be use for array
2718 var function_interpreter
;
2719 if (type
=== 'array') {
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') {
2730 if (settings
.ignoreSystemDescribe
) {
2731 if (rpc_count
=== 1) {
2732 function_interpreter
= make_basic_json_rpc_interpreter(first
);
2734 self
.error(strings
.oneRPCWithIgnore
);
2736 recur(rest
, success
);
2738 make_json_rpc_object(first
, function(new_object
) {
2739 // will ignore rpc in array that don't have system.describe
2741 $
.extend(object
, new_object
);
2744 recur(rest
, success
);
2747 } else if (type
=== 'function') {
2748 if (function_interpreter
) {
2749 self
.error(strings
.oneInterpreterFunction
);
2751 function_interpreter
= first
;
2753 } else if (type
=== 'object') {
2754 $
.extend(object
, first
);
2755 recur(rest
, success
);
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
) {
2768 } else if (type
=== 'string') {
2769 if (settings
.ignoreSystemDescribe
) {
2771 interpreter
: make_basic_json_rpc_interpreter(user_interpreter
),
2772 completion
: settings
.completion
2776 make_json_rpc_object(user_interpreter
, function(object
) {
2778 result
.interpreter
= make_object_interpreter(object
, false);
2779 result
.completion
= function(term
, string
, callback
) {
2783 // no procs in system.describe
2784 result
.interpreter
= make_basic_json_rpc_interpreter(user_interpreter
);
2785 result
.completion
= settings
.completion
;
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
) {
2799 // allow $('<div/>).terminal();
2800 if (type
=== 'undefined') {
2801 user_interpreter
= $
.noop
;
2802 } else if (type
!== 'function') {
2803 throw type
+ " is invalid interpreter value";
2806 interpreter
: user_interpreter
,
2807 completion
: settings
.completion
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
) {
2821 function(response
) {
2823 if (!response
.error
&& response
.result
) {
2824 callback(response
.result
);
2826 // null will trigger message that login fail
2831 //default name is login so you can pass true
2833 // -----------------------------------------------------------------------
2834 // :: Return exception message as string
2835 // -----------------------------------------------------------------------
2836 function exception_message(e
) {
2837 if (typeof e
=== 'string') {
2839 } else if (typeof e
.fileName
=== 'string') {
2840 return e
.fileName
+ ': ' + e
.message
;
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
);
2852 self
.exception(e
, label
);
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
);
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
) {
2867 if (typeof object
=== 'function') {
2871 } else if (typeof object
!== 'string') {
2872 var msg
= label
+ ' must be string or function';
2876 display_exception(e
, label
.toUpperCase());
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
= [];
2891 function draw_line(string
, options
) {
2892 // prevent exception in display exception
2894 var line_settings
= $
.extend({
2898 string
= $
.type(string
) === "function" ?
string() : string
;
2899 string
= $
.type(string
) === "string" ? string
: String(string
);
2901 if (!line_settings
.raw
) {
2902 string
= $
.terminal
.encode(string
);
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(' ');
2913 if (line_settings
.raw
) {
2914 output_buffer
.push(array
[i]);
2916 output_buffer
.push($
.terminal
.format(array
[i], {
2917 linksNoReferer
: settings
.linksNoReferer
2923 if (!line_settings
.raw
) {
2924 string
= $
.terminal
.format(string
, {
2925 linksNoReferer
: settings
.linksNoReferer
2928 output_buffer
.push(string
);
2930 output_buffer
.push(line_settings
.finalize
);
2933 // don't display exception if exception throw in terminal
2934 alert('[Internal Exception(draw_line)]:' + exception_message(e
) + '\n' +
2938 // -----------------------------------------------------------------------
2940 // -----------------------------------------------------------------------
2942 command_line
.resize(num_chars
);
2943 var o
= output
.empty().detach();
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 ?
2951 settings
.outputLimit
;
2952 lines_to_show
= lines
.slice(lines
.length
-limit
-1);
2954 lines_to_show
= lines
;
2956 $
.each(lines_to_show
, function(i
, line
) {
2957 draw_line
.apply(null, line
); // line is an array
2959 command_line
.before(o
);
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
);
2975 self
.error(strings
.wrongGreetings
);
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
, '*');
2988 if (typeof prompt
=== 'function') {
2989 prompt(function(string
) {
2990 self
.echo(string
+ command
);
2993 self
.echo(prompt
+ command
);
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
) {
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
);
3009 var interpreter
= interpreters
.top();
3010 if (command
=== 'exit' && settings
.exit
) {
3011 var count
= interpreters
.size();
3013 if (count
== 1 && self
.token() || count
> 1) {
3015 echo_command(command
);
3021 echo_command(command
);
3023 var position
= lines
.length
-1;
3024 if (command
=== 'clear' && settings
.clear
) {
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) {
3033 if (result
!== false) {
3037 if (result
=== false) {
3038 lines
= lines
.slice(0, position
).
3039 concat(lines
.slice(position
+1));
3041 lines
= lines
.slice(0, position
).
3043 concat(lines
.slice(position
+1));
3051 display_exception(e
, 'USER');
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') {
3064 if (settings
.onBeforeLogout(self
) === false) {
3068 display_exception(e
, 'onBeforeLogout');
3073 if (typeof settings
.onAfterLogout
=== 'function') {
3075 settings
.onAfterLogout(self
);
3077 display_exception(e
, 'onAfterlogout');
3081 self
.login(settings
.login
, true, initialize
);
3083 // -----------------------------------------------------------------------
3085 var name
= self
.prefix_name(true) + '_';
3086 $
.Storage
.remove(name
+ 'token');
3087 $
.Storage
.remove(name
+ 'login');
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
);
3096 names
= $
.parseJSON(names
);
3100 if ($
.inArray(interpreter_name
, names
) == -1) {
3101 names
.push(interpreter_name
);
3102 $
.Storage
.set(storage_key
, $
.json_stringify(names
));
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);
3112 maybe_append_name(name
);
3114 command_line
.name(name
);
3115 if (typeof interpreter
.prompt
== 'function') {
3116 command_line
.prompt(function(command
) {
3117 interpreter
.prompt(command
, self
);
3120 command_line
.prompt(interpreter
.prompt
);
3122 command_line
.set('');
3123 if (!silent
&& typeof interpreter
.onStart
=== 'function') {
3124 interpreter
.onStart(self
);
3127 // ---------------------------------------------------------------------
3128 function initialize() {
3129 prepare_top_interpreter();
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
3140 settings
.onInit(self
);
3142 display_exception(e
, 'OnInit');
3147 // resume login if user didn't call pause in onInit
3148 // if user pause in onInit wait with exec until it resume
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
3163 var regex
= new RegExp('^' + $
.terminal
.escape_regex(string
));
3165 for (var i
=commands
.length
; i
--;) {
3166 if (regex
.test(commands
[i])) {
3167 matched
.push(commands
[i]);
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'));
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
)) {
3191 self
.insert(matched
[0].slice(0, j
).replace(regex
, ''));
3196 // ---------------------------------------------------------------------
3197 // :: IF Ghost don't store anything in localstorage
3198 // ---------------------------------------------------------------------
3200 return in_login || command_line
.mask();
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
3208 var result
, i
, top
= interpreters
.top();
3209 if ($
.type(top
.keydown
) === 'function') {
3210 result
= top
.keydown(e
, self
);
3211 if (result
!== undefined
) {
3216 if ((settings
.completion
&& $
.type(settings
.completion
) != 'boolean') &&
3218 completion
= settings
.completion
;
3220 completion
= top
.completion
;
3222 // after text pasted into textarea in cmd plugin
3223 self
.oneTime(10, function() {
3224 on_scrollbar_show_resize();
3226 if ($
.type(settings
.keydown
) === 'function') {
3227 result
= settings
.keydown(e
, self
);
3228 if (result
!== undefined
) {
3232 if (!self
.paused()) {
3233 if (e
.which
!== 9) { // not a TAB
3236 if (e
.which
=== 68 && e
.ctrlKey
) { // CTRL+D
3238 if (command_line
.get() === '') {
3239 if (interpreters
.size() > 1 ||
3240 settings
.login
!== undefined
) {
3247 self
.set_command('');
3251 } else if (e
.which
=== 76 && e
.ctrlKey
) { // CTRL+L
3253 } else if (completion
&& e
.which
=== 9) { // TAB
3254 // TODO: move this to cmd plugin
3255 // add completion = array | function
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];
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
;
3275 switch ($
.type(completion
)) {
3277 completion(self
, string
, function(commands
) {
3278 complete_helper(command
, string
, commands
);
3282 complete_helper(command
, string
, completion
);
3285 // terminal will not catch this because it's an event
3286 throw new Error($
.terminal
.defaults
.strings
.invalidCompletion
);
3289 } else if (e
.which
=== 86 && e
.ctrlKey
) { // CTRL+V
3290 self
.oneTime(1, function() {
3294 } else if (e
.which
=== 9 && e
.ctrlKey
) { // CTRL+TAB
3295 if (terminals
.length() > 1) {
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());
3304 self
.attr({scrollTop
: self
.attr('scrollHeight')});
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
) {
3314 self
.error(strings
.ajaxAbortError
);
3319 // only resume if there are ajax calls
3325 // -----------------------------------------------------------------------
3327 if (this.length
> 1) {
3328 return this.each(function() {
3329 $
.fn
.terminal
.call($
(this),
3331 $
.extend({name
: self
.selector
}, options
));
3334 // terminal already exists
3335 if (self
.data('terminal')) {
3336 return self
.data('terminal');
3338 if (self
.length
=== 0) {
3339 throw 'Sorry, but terminal said that "' + self
.selector
+
3340 '" is not valid selector!';
3342 //var names = []; // stack if interpeter names
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)
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
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
},
3367 var strings
= $
.terminal
.defaults
.strings
;
3368 var enabled
= settings
.enabled
;
3370 // -----------------------------------------------------------------------
3372 // -----------------------------------------------------------------------
3373 $
.extend(self
, $
.omap({
3374 // -----------------------------------------------------------------------
3375 // :: Clear the output
3376 // -----------------------------------------------------------------------
3379 command_line
.set('');
3382 settings
.onClear(self
);
3384 display_exception(e
, 'onClear');
3387 self
.attr({ scrollTop
: 0});
3390 // -----------------------------------------------------------------------
3391 // :: Return an object that can be used with import_view to restore the state
3392 // -----------------------------------------------------------------------
3393 export_view
: function() {
3395 throw new Exception(strings
.notWhileLogin
);
3398 prompt
: self
.get_prompt(),
3399 command
: self
.get_command(),
3400 position
: command_line
.position(),
3401 lines
: lines
.slice(0)
3404 // -----------------------------------------------------------------------
3405 // :: Restore the state of the previous exported view
3406 // -----------------------------------------------------------------------
3407 import_view
: function(view
) {
3409 throw new Exception(strings
.notWhileLogin
);
3411 self
.set_prompt(view
.prompt
);
3412 self
.set_command(view
.command
);
3413 command_line
.position(view
.position
);
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)
3426 dalyed_commands
.push([command
, silent
]);
3428 commands(command
, silent
, true);
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
) {
3440 throw new Error(strings
.notWhileLogin
);
3442 if (typeof auth
!== 'function') {
3443 throw new Error(strings
.loginIsNotAFunction
);
3445 if (self
.token(true) && self
.login_name(true)) {
3446 if (typeof success
== 'function') {
3452 // don't store login data in history
3453 if (settings
.history
) {
3454 command_line
.history().disable();
3457 return self
.push(function(user
) {
3458 self
.set_mask(true).push(function(pass
) {
3460 auth
.call(self
, user
, pass
, function(token
, silent
) {
3463 if (settings
.history
) {
3464 command_line
.history().enable();
3466 var name
= self
.prefix_name(true) + '_';
3467 $
.Storage
.set(name
+ 'token', token
);
3468 $
.Storage
.set(name
+ 'login', user
);
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 -
3480 self
.error(strings
.wrongPasswordTryAgain
);
3482 self
.pop().set_mask(false);
3486 self
.error(strings
.wrongPassword
);
3490 // used only to call pop in push
3491 if (typeof error
== 'function') {
3497 display_exception(e
, 'USER(authentication)');
3500 prompt
: strings
.password
+ ': '
3503 prompt
: strings
.login
+ ': '
3506 // -----------------------------------------------------------------------
3507 // :: User defined settings and defaults as well
3508 // -----------------------------------------------------------------------
3510 // -----------------------------------------------------------------------
3511 // :: Return commands function from top interpreter
3512 // -----------------------------------------------------------------------
3513 commands
: function() {
3514 return interpreters
.top().interpreter
;
3516 // -----------------------------------------------------------------------
3517 // :: Low Level method that overwrites interpreter
3518 // -----------------------------------------------------------------------
3519 setInterpreter
: function(user_interpreter
, login
) {
3520 function overwrite_interpreter() {
3522 make_interpreter(user_interpreter
, function(result
) {
3524 var top
= interpreters
.top();
3525 $
.extend(top
, result
);
3526 prepare_top_interpreter(true);
3529 if ($
.type(user_interpreter
) == 'string' && login
) {
3530 self
.login(make_json_rpc_login(user_interpreter
, login
),
3532 overwrite_interpreter
);
3534 overwrite_interpreter();
3537 // -----------------------------------------------------------------------
3538 // :: Show user greetings or terminal signature
3539 // -----------------------------------------------------------------------
3540 greetings
: function() {
3544 // -----------------------------------------------------------------------
3545 // :: Return true if terminal is paused false otherwise
3546 // -----------------------------------------------------------------------
3547 paused
: function() {
3550 // -----------------------------------------------------------------------
3551 // :: Pause the terminal, it should be used for ajax calls
3552 // -----------------------------------------------------------------------
3555 if (!paused
&& command_line
) {
3558 command_line
.hidden();
3562 // -----------------------------------------------------------------------
3563 // :: Resume the previously paused terminal
3564 // -----------------------------------------------------------------------
3565 resume
: function() {
3566 if (paused
&& command_line
) {
3569 command_line
.visible();
3570 var original
= dalyed_commands
;
3571 dalyed_commands
= [];
3572 while (original
.length
) {
3573 self
.exec
.apply(self
, original
.shift());
3579 // -----------------------------------------------------------------------
3580 // :: Return the number of characters that fit into the width of the terminal
3581 // -----------------------------------------------------------------------
3585 // -----------------------------------------------------------------------
3586 // :: Return the number of lines that fit into the height of the terminal
3587 // -----------------------------------------------------------------------
3591 // -----------------------------------------------------------------------
3592 // :: Return the History object
3593 // -----------------------------------------------------------------------
3594 history
: function() {
3595 return command_line
.history();
3597 // -----------------------------------------------------------------------
3598 // :: Switch to the next terminal
3599 // -----------------------------------------------------------------------
3601 if (terminals
.length() === 1) {
3604 var offsetTop
= self
.offset().top
;
3605 var height
= self
.height();
3606 var scrollTop
= self
.scrollTop();
3607 if (!is_scrolled_into_view(self
)) {
3609 $
('html,body').animate({scrollTop
: offsetTop
-50}, 500);
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);
3618 settings
.onTerminalChange(next
);
3620 display_exception(e
, 'onTerminalChange');
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.
3632 // -----------------------------------------------------------------------
3633 focus
: function(toggle
, silent
) {
3634 self
.oneTime(1, function() {
3635 if (terminals
.length() === 1) {
3636 if (toggle
=== false) {
3638 if (!silent
&& settings
.onBlur(self
) !== false) {
3642 display_exception(e
, 'onBlur');
3647 if (!silent
&& settings
.onFocus(self
) !== false) {
3651 display_exception(e
, 'onFocus');
3656 if (toggle
=== false) {
3659 var front
= terminals
.front();
3660 if (front
!= self
) {
3664 settings
.onTerminalChange(self
);
3666 display_exception(e
, 'onTerminalChange');
3671 terminals
.set(self
);
3678 // -----------------------------------------------------------------------
3679 // :: Enable the terminal
3680 // -----------------------------------------------------------------------
3681 enable
: function() {
3682 if (num_chars
=== undefined
) {
3683 //enabling first time
3687 command_line
.enable();
3692 // -----------------------------------------------------------------------
3693 // :: Disable the terminal
3694 // -----------------------------------------------------------------------
3695 disable
: function() {
3698 command_line
.disable();
3702 // -----------------------------------------------------------------------
3703 // :: return true if the terminal is enabled
3704 // -----------------------------------------------------------------------
3705 enabled
: function() {
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;
3715 return signatures
[i].join('\n') + '\n';
3720 // -----------------------------------------------------------------------
3721 // :: Return the version number
3722 // -----------------------------------------------------------------------
3723 version
: function() {
3726 // -----------------------------------------------------------------------
3727 // :: Return actual command line object (jquery object with cmd methods)
3728 // -----------------------------------------------------------------------
3730 return command_line
;
3732 // -----------------------------------------------------------------------
3733 // :: Return the current command entered by terminal
3734 // -----------------------------------------------------------------------
3735 get_command
: function() {
3736 return command_line
.get();
3738 // -----------------------------------------------------------------------
3739 // :: Change the command line to the new one
3740 // -----------------------------------------------------------------------
3741 set_command
: function(command
) {
3742 command_line
.set(command
);
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
);
3753 throw "insert function argument is not a string";
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
);
3766 command_line
.prompt(prompt
);
3768 interpreters
.top().prompt
= prompt
;
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();
3780 // -----------------------------------------------------------------------
3781 // :: Enable or Disable mask depedning on the passed argument
3782 // -----------------------------------------------------------------------
3783 set_mask
: function(display
) {
3784 command_line
.mask(display
);
3787 // -----------------------------------------------------------------------
3788 // :: Return the ouput of the terminal as text
3789 // -----------------------------------------------------------------------
3790 get_output
: function(raw
) {
3794 return $
.map(lines
, function(item
) {
3795 return typeof item
[0] == 'function' ? item
[0]() : item
[0];
3799 // -----------------------------------------------------------------------
3800 // :: Recalculate and redraw everything
3801 // -----------------------------------------------------------------------
3802 resize
: function(width
, height
) {
3803 if (width
&& height
) {
3805 self
.height(height
);
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
;
3816 if (typeof settings
.onResize
=== 'function' &&
3817 (old_height
!== height || old_width
!== width
)) {
3818 settings
.onResize(self
);
3820 if (old_height
!== height || old_width
!== width
) {
3821 old_height
= height
;
3827 // -----------------------------------------------------------------------
3828 // :: Flush the output to the terminal
3829 // -----------------------------------------------------------------------
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
);
3842 display_exception(e
, 'USER:echo(finalize)');
3845 $
('<div/>').html(line
).appendTo(wrapper
).width('100%');
3848 if (settings
.outputLimit
>= 0) {
3849 var limit
= settings
.outputLimit
=== 0 ?
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() {
3861 if (self
.is(':empty')) {
3870 alert('[Flush] ' + exception_message(e
) + '\n' +
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
) {
3884 string
= string ||
'';
3885 var settings
= $
.extend({
3891 draw_line(string
, settings
);
3892 lines
.push([string
, settings
]);
3893 if (settings
.flush
) {
3896 on_scrollbar_show_resize();
3898 // if echo throw exception we can't use error to display that
3900 alert('[Terminal.echo] ' + exception_message(e
) + '\n' +
3905 // -----------------------------------------------------------------------
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(/\\$
/, '\') + ']', finalize
);
3913 // -----------------------------------------------------------------------
3914 // :: Display Exception on terminal
3915 // -----------------------------------------------------------------------
3916 exception
: function(e
, label
) {
3917 var message
= exception_message(e
);
3919 message
= '[' + label
+ ']: ' + message
;
3922 self
.error(message
, {
3923 finalize
: function(div
) {
3924 div
.addClass('exception message');
3928 if (typeof e
.fileName
=== 'string') {
3929 //display filename and line which throw exeption
3931 $
.get(e
.fileName
, function(file
) {
3933 var num
= e
.lineNumber
- 1;
3934 var line
= file
.split('\n')[num];
3936 self
.error('[' + e
.lineNumber
+ ']: ' + line
);
3941 self
.error(e
.stack
, {
3942 finalize
: function(div
) {
3943 div
.addClass('exception stack-trace');
3948 // -----------------------------------------------------------------------
3949 // :: Scroll Div that holds the terminal
3950 // -----------------------------------------------------------------------
3951 scroll
: function(amount
) {
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);
3958 pos
= scroll_object
.prop('scrollTop');
3959 scroll_object
.scrollTop(pos
+ amount
);
3961 if (amount
> scroll_object
.attr('scrollTop') && amount
> 0) {
3962 scroll_object
.attr('scrollTop', 0);
3964 pos
= scroll_object
.attr('scrollTop');
3965 scroll_object
.scrollTop(pos
+ amount
);
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) {
3979 self
.error(strings
.loginFunctionMissing
);
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');
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');
3994 // -----------------------------------------------------------------------
3995 // :: Function returns the name of current interpreter
3996 // -----------------------------------------------------------------------
3998 return interpreters
.top().name
;
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
) {
4008 }).slice(1).join('_');
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();
4022 top
.mask
= command_line
.mask();
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
,
4033 prepare_top_interpreter
,
4035 } else if ($
.type(interpreter
) == 'string' &&
4036 type
== 'string' || type
== 'boolean') {
4037 self
.login(make_json_rpc_login(interpreter
, options
.login
),
4039 prepare_top_interpreter
,
4043 prepare_top_interpreter();
4048 // -----------------------------------------------------------------------
4049 // :: Remove the last interpreter from the Stack
4050 // -----------------------------------------------------------------------
4051 pop
: function(string
) {
4052 if (string
!== undefined
) {
4053 echo_command(string
);
4055 var token
= self
.token(true);
4056 if (interpreters
.size() == 1) {
4057 if (settings
.login
) {
4059 if ($
.type(settings
.onExit
) === 'function') {
4061 settings
.onExit(self
);
4063 display_exception(e
, 'onExit');
4068 self
.error(strings
.canExitError
);
4074 var current
= interpreters
.pop();
4075 prepare_top_interpreter();
4076 if ($
.type(current
.onExit
) === 'function') {
4078 current
.onExit(self
);
4080 display_exception(e
, 'onExit');
4085 self
.set_mask(interpreters
.top().mask
);
4089 // -----------------------------------------------------------------------
4090 // :: Return how deep you are in nested interpreters
4091 // -----------------------------------------------------------------------
4093 return interpreters
.size();
4095 // -----------------------------------------------------------------------
4096 // :: Reinitialize the terminal
4097 // -----------------------------------------------------------------------
4100 while(interpreters
.size() > 1) {
4106 // -----------------------------------------------------------------------
4107 // :: Remove all local storage left by terminal, it will not logout you
4108 // :: until you refresh the browser
4109 // -----------------------------------------------------------------------
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');
4118 command_line
.purge();
4119 $
.Storage
.remove(prefix
+ 'interpreters');
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
4126 // -----------------------------------------------------------------------
4127 destroy
: function() {
4128 command_line
.destroy().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', '');
4137 if (settings
.height
) {
4138 self
.css('height', '');
4142 }, function(_
, fun
) {
4143 // wrap all functions and display execptions
4146 return fun
.apply(this, Array
.prototype.slice
.apply(arguments
));
4148 // exec catch by command (resume call exec)
4149 if (_
!== 'exec' && _
!== 'resume') {
4150 display_exception(e
, 'TERMINAL');
4157 // ---------------------------------------------------------------------
4158 var on_scrollbar_show_resize
= (function() {
4159 var scroll_bars
= have_scrollbars(self
);
4161 if (scroll_bars
!== have_scrollbars(self
)) {
4162 // if the scollbar appearance changes we will have a different
4165 scroll_bars
= have_scrollbars(self
);
4170 // ---------------------------------------------------------------------
4172 // ---------------------------------------------------------------------
4173 if (settings
.width
) {
4174 self
.width(settings
.width
);
4176 if (settings
.height
) {
4177 self
.height(settings
.height
);
4179 if (!navigator
.userAgent
.toLowerCase().match(/(webkit
)[ \
/]([\w
.]+)/) &&
4180 self
[0].tagName
.toLowerCase() == 'body') {
4181 scroll_object
= $
('html');
4183 scroll_object
= self
;
4185 // register ajaxSend for cancel requests on CTRL+D
4186 $
(document
).bind('ajaxSend.terminal', function(e
, xhr
, opt
) {
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();
4197 self
.find('textarea').focus();
4200 self.bind('touchstart.touchScroll', function() {
4203 self.bind('touchmove.touchScroll', function() {
4207 //$('<input type="text"/>').hide().focus().appendTo(self);
4209 // before login event
4210 if (settings
.login
&& typeof settings
.onBeforeLogin
=== 'function') {
4212 settings
.onBeforeLogin(self
);
4214 display_exception(e
, 'onBeforeLogin');
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
);
4223 terminals
.append(self
);
4226 make_interpreter(init_interpreter
, function(interpreter
) {
4227 interpreters
= new Stack($
.extend({
4228 name
: settings
.name
,
4229 prompt
: settings
.prompt
,
4230 greetings
: settings
.greetings
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
,
4240 keypress
: settings
.keypress ?
function(e
) {
4241 return settings
.keypress(e
, self
);
4243 onCommandChange
: function(command
) {
4244 if ($
.type(settings
.onCommandChange
) === 'function') {
4246 settings
.onCommandChange(command
, self
);
4248 display_exception(e
, 'onCommandChange');
4257 self
.focus(undefined
, true);
4261 $
(document
).bind('click.terminal', function(e
) {
4262 if (!$
(e
.target
).closest('.terminal').hasClass('terminal') &&
4263 settings
.onBlur(self
) !== false) {
4267 self
.click(function(e
) {
4268 if (!self
.enabled()) {
4271 }).mousedown(function(e
) {
4273 self
.insert(getSelectedText());
4276 // ------------------------------------------------
4278 if (settings
.login
) {
4279 self
.login(settings
.login
, true, initialize
);
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
);
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
) {
4300 if ($
.event
.special
.mousewheel
) {
4302 $
(document
).bind('keydown.terminal', function(e
) {
4306 }).bind('keyup.terminal', function(e
) {
4307 // in Google Chromium/Linux shiftKey is false
4308 if (e
.shiftKey || e
.which
== 16) {
4312 self
.mousewheel(function(event
, delta
) {
4319 //event.preventDefault();
4324 self
.data('terminal', self
);
4327 }; //terminal plugin