memcached-manager 0.4.0 → 1.0.0

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