blob: 4bab739d9c447e91cf065f0724746e3e2c592c50 [file] [log] [blame]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001// This file was generated by libdot/bin/concat.sh.
2// It has been marked read-only for your safety. Rather
3// than edit it directly, please modify one of these source
4// files...
5//
6// libdot/js/lib.js
7// libdot/js/lib_colors.js
8// libdot/js/lib_f.js
9// libdot/js/lib_message_manager.js
10// libdot/js/lib_preference_manager.js
11// libdot/js/lib_resource.js
12// libdot/js/lib_storage.js
13// libdot/js/lib_storage_chrome.js
14// libdot/js/lib_storage_local.js
15// libdot/js/lib_storage_memory.js
16// libdot/js/lib_test_manager.js
17// libdot/js/lib_utf8.js
18// libdot/js/lib_wc.js
19// hterm/js/hterm.js
20// hterm/js/hterm_frame.js
21// hterm/js/hterm_keyboard.js
22// hterm/js/hterm_keyboard_bindings.js
23// hterm/js/hterm_keyboard_keymap.js
24// hterm/js/hterm_keyboard_keypattern.js
25// hterm/js/hterm_options.js
26// hterm/js/hterm_parser.js
27// hterm/js/hterm_parser_identifiers.js
28// hterm/js/hterm_preference_manager.js
29// hterm/js/hterm_pubsub.js
30// hterm/js/hterm_screen.js
31// hterm/js/hterm_scrollport.js
32// hterm/js/hterm_terminal.js
33// hterm/js/hterm_terminal_io.js
34// hterm/js/hterm_text_attributes.js
35// hterm/js/hterm_vt.js
36// hterm/js/hterm_vt_character_map.js
37//
38
39// SOURCE FILE: libdot/js/lib.js
40// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
41// Use of this source code is governed by a BSD-style license that can be
42// found in the LICENSE file.
43
44'use strict';
45
46if (typeof lib != 'undefined')
47 throw new Error('Global "lib" object already exists.');
48
49var lib = {};
50
51/**
52 * Map of "dependency" to ["source", ...].
53 *
54 * Each dependency is a object name, like "lib.fs", "source" is the url that
55 * depends on the object.
56 */
57lib.runtimeDependencies_ = {};
58
59/**
60 * List of functions that need to be invoked during library initialization.
61 *
62 * Each element in the initCallbacks_ array is itself a two-element array.
63 * Element 0 is a short string describing the owner of the init routine, useful
64 * for debugging. Element 1 is the callback function.
65 */
66lib.initCallbacks_ = [];
67
68/**
69 * Records a runtime dependency.
70 *
71 * This can be useful when you want to express a run-time dependency at
72 * compile time. It is not intended to be a full-fledged library system or
73 * dependency tracker. It's just there to make it possible to debug the
74 * deps without running all the code.
75 *
76 * Object names are specified as strings. For example...
77 *
78 * lib.rtdep('lib.colors', 'lib.PreferenceManager');
79 *
80 * Object names need not be rooted by 'lib'. You may use this to declare a
81 * dependency on any object.
82 *
83 * The client program may call lib.ensureRuntimeDependencies() at startup in
84 * order to ensure that all runtime dependencies have been met.
85 *
86 * @param {string} var_args One or more objects specified as strings.
87 */
88lib.rtdep = function(var_args) {
89 var source;
90
91 try {
92 throw new Error();
93 } catch (ex) {
94 var stackArray = ex.stack.split('\n');
95 // In Safari, the resulting stackArray will only have 2 elements and the
96 // individual strings are formatted differently.
97 if (stackArray.length >= 3) {
98 source = stackArray[2].replace(/^\s*at\s+/, '');
99 } else {
100 source = stackArray[1].replace(/^\s*global code@/, '');
101 }
102 }
103
104 for (var i = 0; i < arguments.length; i++) {
105 var path = arguments[i];
106 if (path instanceof Array) {
107 lib.rtdep.apply(lib, path);
108 } else {
109 var ary = this.runtimeDependencies_[path];
110 if (!ary)
111 ary = this.runtimeDependencies_[path] = [];
112 ary.push(source);
113 }
114 }
115};
116
117/**
118 * Ensures that all runtime dependencies are met, or an exception is thrown.
119 *
120 * Every unmet runtime dependency will be logged to the JS console. If at
121 * least one dependency is unmet this will raise an exception.
122 */
123lib.ensureRuntimeDependencies_ = function() {
124 var passed = true;
125
126 for (var path in lib.runtimeDependencies_) {
127 var sourceList = lib.runtimeDependencies_[path];
128 var names = path.split('.');
129
130 // In a document context 'window' is the global object. In a worker it's
131 // called 'self'.
132 var obj = (window || self);
133 for (var i = 0; i < names.length; i++) {
134 if (!(names[i] in obj)) {
135 console.warn('Missing "' + path + '" is needed by', sourceList);
136 passed = false;
137 break;
138 }
139
140 obj = obj[names[i]];
141 }
142 }
143
144 if (!passed)
145 throw new Error('Failed runtime dependency check');
146};
147
148/**
149 * Register an initialization function.
150 *
151 * The initialization functions are invoked in registration order when
152 * lib.init() is invoked. Each function will receive a single parameter, which
153 * is a function to be invoked when it completes its part of the initialization.
154 *
155 * @param {string} name A short descriptive name of the init routine useful for
156 * debugging.
157 * @param {function(function)} callback The initialization function to register.
158 * @return {function} The callback parameter.
159 */
160lib.registerInit = function(name, callback) {
161 lib.initCallbacks_.push([name, callback]);
162 return callback;
163};
164
165/**
166 * Initialize the library.
167 *
168 * This will ensure that all registered runtime dependencies are met, and
169 * invoke any registered initialization functions.
170 *
171 * Initialization is asynchronous. The library is not ready for use until
172 * the onInit function is invoked.
173 *
174 * @param {function()} onInit The function to invoke when initialization is
175 * complete.
176 * @param {function(*)} opt_logFunction An optional function to send
177 * initialization related log messages to.
178 */
179lib.init = function(onInit, opt_logFunction) {
180 var ary = lib.initCallbacks_;
181
182 var initNext = function() {
183 if (ary.length) {
184 var rec = ary.shift();
185 if (opt_logFunction)
186 opt_logFunction('init: ' + rec[0]);
187 rec[1](lib.f.alarm(initNext));
188 } else {
189 onInit();
190 }
191 };
192
193 if (typeof onInit != 'function')
194 throw new Error('Missing or invalid argument: onInit');
195
196 lib.ensureRuntimeDependencies_();
197
198 setTimeout(initNext, 0);
199};
200// SOURCE FILE: libdot/js/lib_colors.js
201// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
202// Use of this source code is governed by a BSD-style license that can be
203// found in the LICENSE file.
204
205'use strict';
206
207/**
208 * Namespace for color utilities.
209 */
210lib.colors = {};
211
212/**
213 * First, some canned regular expressions we're going to use in this file.
214 *
215 *
216 * BRACE YOURSELF
217 *
218 * ,~~~~.
219 * |>_< ~~
220 * 3`---'-/.
221 * 3:::::\v\
222 * =o=:::::\,\
223 * | :::::\,,\
224 *
225 * THE REGULAR EXPRESSIONS
226 * ARE COMING.
227 *
228 * There's no way to break long RE literals in JavaScript. Fix that why don't
229 * you? Oh, and also there's no way to write a string that doesn't interpret
230 * escapes.
231 *
232 * Instead, we stoop to this .replace() trick.
233 */
234lib.colors.re_ = {
235 // CSS hex color, #RGB.
236 hex16: /#([a-f0-9])([a-f0-9])([a-f0-9])/i,
237
238 // CSS hex color, #RRGGBB.
239 hex24: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i,
240
241 // CSS rgb color, rgb(rrr,ggg,bbb).
242 rgb: new RegExp(
243 ('^/s*rgb/s*/(/s*(/d{1,3})/s*,/s*(/d{1,3})/s*,' +
244 '/s*(/d{1,3})/s*/)/s*$'
245 ).replace(/\//g, '\\'), 'i'),
246
247 // CSS rgb color, rgb(rrr,ggg,bbb,aaa).
248 rgba: new RegExp(
249 ('^/s*rgba/s*' +
250 '/(/s*(/d{1,3})/s*,/s*(/d{1,3})/s*,/s*(/d{1,3})/s*' +
251 '(?:,/s*(/d+(?:/./d+)?)/s*)/)/s*$'
252 ).replace(/\//g, '\\'), 'i'),
253
254 // Either RGB or RGBA.
255 rgbx: new RegExp(
256 ('^/s*rgba?/s*' +
257 '/(/s*(/d{1,3})/s*,/s*(/d{1,3})/s*,/s*(/d{1,3})/s*' +
258 '(?:,/s*(/d+(?:/./d+)?)/s*)?/)/s*$'
259 ).replace(/\//g, '\\'), 'i'),
260
261 // An X11 "rgb:dddd/dddd/dddd" value.
262 x11rgb: /^\s*rgb:([a-f0-9]{1,4})\/([a-f0-9]{1,4})\/([a-f0-9]{1,4})\s*$/i,
263
264 // English color name.
265 name: /[a-z][a-z0-9\s]+/,
266};
267
268/**
269 * Convert a CSS rgb(ddd,ddd,ddd) color value into an X11 color value.
270 *
271 * Other CSS color values are ignored to ensure sanitary data handling.
272 *
273 * Each 'ddd' component is a one byte value specified in decimal.
274 *
275 * @param {string} value The CSS color value to convert.
276 * @return {string} The X11 color value or null if the value could not be
277 * converted.
278 */
279lib.colors.rgbToX11 = function(value) {
280 function scale(v) {
281 v = (Math.min(v, 255) * 257).toString(16);
282 return lib.f.zpad(v, 4);
283 }
284
285 var ary = value.match(lib.colors.re_.rgbx);
286 if (!ary)
287 return null;
288
289 return 'rgb:' + scale(ary[1]) + '/' + scale(ary[2]) + '/' + scale(ary[3]);
290};
291
292/**
293 * Convert a legacy X11 colover value into an CSS rgb(...) color value.
294 *
295 * They take the form:
296 * 12 bit: #RGB -> #R000G000B000
297 * 24 bit: #RRGGBB -> #RR00GG00BB00
298 * 36 bit: #RRRGGGBBB -> #RRR0GGG0BBB0
299 * 48 bit: #RRRRGGGGBBBB
300 * These are the most significant bits.
301 *
302 * Truncate values back down to 24 bit since that's all CSS supports.
303 */
304lib.colors.x11HexToCSS = function(v) {
305 if (!v.startsWith('#'))
306 return null;
307 // Strip the leading # off.
308 v = v.substr(1);
309
310 // Reject unknown sizes.
311 if ([3, 6, 9, 12].indexOf(v.length) == -1)
312 return null;
313
314 // Reject non-hex values.
315 if (v.match(/[^a-f0-9]/i))
316 return null;
317
318 // Split the colors out.
319 var size = v.length / 3;
320 var r = v.substr(0, size);
321 var g = v.substr(size, size);
322 var b = v.substr(size + size, size);
323
324 // Normalize to 16 bits.
325 function norm16(v) {
326 v = parseInt(v, 16);
327 return size == 2 ? v : // 16 bit
328 size == 1 ? v << 4 : // 8 bit
329 v >> (4 * (size - 2)); // 24 or 32 bit
330 }
331 return lib.colors.arrayToRGBA([r, g, b].map(norm16));
332};
333
334/**
335 * Convert an X11 color value into an CSS rgb(...) color value.
336 *
337 * The X11 value may be an X11 color name, or an RGB value of the form
338 * rgb:hhhh/hhhh/hhhh. If a component value is less than 4 digits it is
339 * padded out to 4, then scaled down to fit in a single byte.
340 *
341 * @param {string} value The X11 color value to convert.
342 * @return {string} The CSS color value or null if the value could not be
343 * converted.
344 */
345lib.colors.x11ToCSS = function(v) {
346 function scale(v) {
347 // Pad out values with less than four digits. This padding (probably)
348 // matches xterm. It's difficult to say for sure since xterm seems to
349 // arrive at a padded value and then perform some combination of
350 // gamma correction, color space transformation, and quantization.
351
352 if (v.length == 1) {
353 // Single digits pad out to four by repeating the character. "f" becomes
354 // "ffff". Scaling down a hex value of this pattern by 257 is the same
355 // as cutting off one byte. We skip the middle step and just double
356 // the character.
357 return parseInt(v + v, 16);
358 }
359
360 if (v.length == 2) {
361 // Similar deal here. X11 pads two digit values by repeating the
362 // byte (or scale up by 257). Since we're going to scale it back
363 // down anyway, we can just return the original value.
364 return parseInt(v, 16);
365 }
366
367 if (v.length == 3) {
368 // Three digit values seem to be padded by repeating the final digit.
369 // e.g. 10f becomes 10ff.
370 v = v + v.substr(2);
371 }
372
373 // Scale down the 2 byte value.
374 return Math.round(parseInt(v, 16) / 257);
375 }
376
377 var ary = v.match(lib.colors.re_.x11rgb);
378 if (!ary) {
379 // Handle the legacy format.
380 if (v.startsWith('#'))
381 return lib.colors.x11HexToCSS(v);
382 else
383 return lib.colors.nameToRGB(v);
384 }
385
386 ary.splice(0, 1);
387 return lib.colors.arrayToRGBA(ary.map(scale));
388};
389
390/**
391 * Converts one or more CSS '#RRGGBB' color values into their rgb(...)
392 * form.
393 *
394 * Arrays are converted in place. If a value cannot be converted, it is
395 * replaced with null.
396 *
397 * @param {string|Array.<string>} A single RGB value or array of RGB values to
398 * convert.
399 * @return {string|Array.<string>} The converted value or values.
400 */
401lib.colors.hexToRGB = function(arg) {
402 var hex16 = lib.colors.re_.hex16;
403 var hex24 = lib.colors.re_.hex24;
404
405 function convert(hex) {
406 if (hex.length == 4) {
407 hex = hex.replace(hex16, function(h, r, g, b) {
408 return "#" + r + r + g + g + b + b;
409 });
410 }
411 var ary = hex.match(hex24);
412 if (!ary)
413 return null;
414
415 return 'rgb(' + parseInt(ary[1], 16) + ', ' +
416 parseInt(ary[2], 16) + ', ' +
417 parseInt(ary[3], 16) + ')';
418 }
419
420 if (arg instanceof Array) {
421 for (var i = 0; i < arg.length; i++) {
422 arg[i] = convert(arg[i]);
423 }
424 } else {
425 arg = convert(arg);
426 }
427
428 return arg;
429};
430
431/**
432 * Converts one or more CSS rgb(...) forms into their '#RRGGBB' color values.
433 *
434 * If given an rgba(...) form, the alpha field is thrown away.
435 *
436 * Arrays are converted in place. If a value cannot be converted, it is
437 * replaced with null.
438 *
439 * @param {string|Array.<string>} A single rgb(...) value or array of rgb(...)
440 * values to convert.
441 * @return {string|Array.<string>} The converted value or values.
442 */
443lib.colors.rgbToHex = function(arg) {
444 function convert(rgb) {
445 var ary = lib.colors.crackRGB(rgb);
446 if (!ary)
447 return null;
448 return '#' + lib.f.zpad(((parseInt(ary[0]) << 16) |
449 (parseInt(ary[1]) << 8) |
450 (parseInt(ary[2]) << 0)).toString(16), 6);
451 }
452
453 if (arg instanceof Array) {
454 for (var i = 0; i < arg.length; i++) {
455 arg[i] = convert(arg[i]);
456 }
457 } else {
458 arg = convert(arg);
459 }
460
461 return arg;
462};
463
464/**
465 * Take any valid css color definition and turn it into an rgb or rgba value.
466 *
467 * Returns null if the value could not be normalized.
468 */
469lib.colors.normalizeCSS = function(def) {
470 if (def.substr(0, 1) == '#')
471 return lib.colors.hexToRGB(def);
472
473 if (lib.colors.re_.rgbx.test(def))
474 return def;
475
476 return lib.colors.nameToRGB(def);
477};
478
479/**
480 * Convert a 3 or 4 element array into an rgba(...) string.
481 */
482lib.colors.arrayToRGBA = function(ary) {
483 var alpha = (ary.length > 3) ? ary[3] : 1;
484 return 'rgba(' + ary[0] + ', ' + ary[1] + ', ' + ary[2] + ', ' + alpha + ')';
485};
486
487/**
488 * Overwrite the alpha channel of an rgb/rgba color.
489 */
490lib.colors.setAlpha = function(rgb, alpha) {
491 var ary = lib.colors.crackRGB(rgb);
492 ary[3] = alpha;
493 return lib.colors.arrayToRGBA(ary);
494};
495
496/**
497 * Mix a percentage of a tint color into a base color.
498 */
499lib.colors.mix = function(base, tint, percent) {
500 var ary1 = lib.colors.crackRGB(base);
501 var ary2 = lib.colors.crackRGB(tint);
502
503 for (var i = 0; i < 4; ++i) {
504 var diff = ary2[i] - ary1[i];
505 ary1[i] = Math.round(parseInt(ary1[i]) + diff * percent);
506 }
507
508 return lib.colors.arrayToRGBA(ary1);
509};
510
511/**
512 * Split an rgb/rgba color into an array of its components.
513 *
514 * On success, a 4 element array will be returned. For rgb values, the alpha
515 * will be set to 1.
516 */
517lib.colors.crackRGB = function(color) {
518 if (color.substr(0, 4) == 'rgba') {
519 var ary = color.match(lib.colors.re_.rgba);
520 if (ary) {
521 ary.shift();
522 return ary;
523 }
524 } else {
525 var ary = color.match(lib.colors.re_.rgb);
526 if (ary) {
527 ary.shift();
528 ary.push(1);
529 return ary;
530 }
531 }
532
533 console.error('Couldn\'t crack: ' + color);
534 return null;
535};
536
537/**
538 * Convert an X11 color name into a CSS rgb(...) value.
539 *
540 * Names are stripped of spaces and converted to lowercase. If the name is
541 * unknown, null is returned.
542 *
543 * This list of color name to RGB mapping is derived from the stock X11
544 * rgb.txt file.
545 *
546 * @param {string} name The color name to convert.
547 * @return {string} The corresponding CSS rgb(...) value.
548 */
549lib.colors.nameToRGB = function(name) {
550 if (name in lib.colors.colorNames)
551 return lib.colors.colorNames[name];
552
553 name = name.toLowerCase();
554 if (name in lib.colors.colorNames)
555 return lib.colors.colorNames[name];
556
557 name = name.replace(/\s+/g, '');
558 if (name in lib.colors.colorNames)
559 return lib.colors.colorNames[name];
560
561 return null;
562};
563
564/**
565 * The stock color palette.
566 */
567lib.colors.stockColorPalette = lib.colors.hexToRGB
568 ([// The "ANSI 16"...
569 '#000000', '#CC0000', '#4E9A06', '#C4A000',
570 '#3465A4', '#75507B', '#06989A', '#D3D7CF',
571 '#555753', '#EF2929', '#00BA13', '#FCE94F',
572 '#729FCF', '#F200CB', '#00B5BD', '#EEEEEC',
573
574 // The 6x6 color cubes...
575 '#000000', '#00005F', '#000087', '#0000AF', '#0000D7', '#0000FF',
576 '#005F00', '#005F5F', '#005F87', '#005FAF', '#005FD7', '#005FFF',
577 '#008700', '#00875F', '#008787', '#0087AF', '#0087D7', '#0087FF',
578 '#00AF00', '#00AF5F', '#00AF87', '#00AFAF', '#00AFD7', '#00AFFF',
579 '#00D700', '#00D75F', '#00D787', '#00D7AF', '#00D7D7', '#00D7FF',
580 '#00FF00', '#00FF5F', '#00FF87', '#00FFAF', '#00FFD7', '#00FFFF',
581
582 '#5F0000', '#5F005F', '#5F0087', '#5F00AF', '#5F00D7', '#5F00FF',
583 '#5F5F00', '#5F5F5F', '#5F5F87', '#5F5FAF', '#5F5FD7', '#5F5FFF',
584 '#5F8700', '#5F875F', '#5F8787', '#5F87AF', '#5F87D7', '#5F87FF',
585 '#5FAF00', '#5FAF5F', '#5FAF87', '#5FAFAF', '#5FAFD7', '#5FAFFF',
586 '#5FD700', '#5FD75F', '#5FD787', '#5FD7AF', '#5FD7D7', '#5FD7FF',
587 '#5FFF00', '#5FFF5F', '#5FFF87', '#5FFFAF', '#5FFFD7', '#5FFFFF',
588
589 '#870000', '#87005F', '#870087', '#8700AF', '#8700D7', '#8700FF',
590 '#875F00', '#875F5F', '#875F87', '#875FAF', '#875FD7', '#875FFF',
591 '#878700', '#87875F', '#878787', '#8787AF', '#8787D7', '#8787FF',
592 '#87AF00', '#87AF5F', '#87AF87', '#87AFAF', '#87AFD7', '#87AFFF',
593 '#87D700', '#87D75F', '#87D787', '#87D7AF', '#87D7D7', '#87D7FF',
594 '#87FF00', '#87FF5F', '#87FF87', '#87FFAF', '#87FFD7', '#87FFFF',
595
596 '#AF0000', '#AF005F', '#AF0087', '#AF00AF', '#AF00D7', '#AF00FF',
597 '#AF5F00', '#AF5F5F', '#AF5F87', '#AF5FAF', '#AF5FD7', '#AF5FFF',
598 '#AF8700', '#AF875F', '#AF8787', '#AF87AF', '#AF87D7', '#AF87FF',
599 '#AFAF00', '#AFAF5F', '#AFAF87', '#AFAFAF', '#AFAFD7', '#AFAFFF',
600 '#AFD700', '#AFD75F', '#AFD787', '#AFD7AF', '#AFD7D7', '#AFD7FF',
601 '#AFFF00', '#AFFF5F', '#AFFF87', '#AFFFAF', '#AFFFD7', '#AFFFFF',
602
603 '#D70000', '#D7005F', '#D70087', '#D700AF', '#D700D7', '#D700FF',
604 '#D75F00', '#D75F5F', '#D75F87', '#D75FAF', '#D75FD7', '#D75FFF',
605 '#D78700', '#D7875F', '#D78787', '#D787AF', '#D787D7', '#D787FF',
606 '#D7AF00', '#D7AF5F', '#D7AF87', '#D7AFAF', '#D7AFD7', '#D7AFFF',
607 '#D7D700', '#D7D75F', '#D7D787', '#D7D7AF', '#D7D7D7', '#D7D7FF',
608 '#D7FF00', '#D7FF5F', '#D7FF87', '#D7FFAF', '#D7FFD7', '#D7FFFF',
609
610 '#FF0000', '#FF005F', '#FF0087', '#FF00AF', '#FF00D7', '#FF00FF',
611 '#FF5F00', '#FF5F5F', '#FF5F87', '#FF5FAF', '#FF5FD7', '#FF5FFF',
612 '#FF8700', '#FF875F', '#FF8787', '#FF87AF', '#FF87D7', '#FF87FF',
613 '#FFAF00', '#FFAF5F', '#FFAF87', '#FFAFAF', '#FFAFD7', '#FFAFFF',
614 '#FFD700', '#FFD75F', '#FFD787', '#FFD7AF', '#FFD7D7', '#FFD7FF',
615 '#FFFF00', '#FFFF5F', '#FFFF87', '#FFFFAF', '#FFFFD7', '#FFFFFF',
616
617 // The greyscale ramp...
618 '#080808', '#121212', '#1C1C1C', '#262626', '#303030', '#3A3A3A',
619 '#444444', '#4E4E4E', '#585858', '#626262', '#6C6C6C', '#767676',
620 '#808080', '#8A8A8A', '#949494', '#9E9E9E', '#A8A8A8', '#B2B2B2',
621 '#BCBCBC', '#C6C6C6', '#D0D0D0', '#DADADA', '#E4E4E4', '#EEEEEE'
622 ]);
623
624/**
625 * The current color palette, possibly with user changes.
626 */
627lib.colors.colorPalette = lib.colors.stockColorPalette;
628
629/**
630 * Named colors according to the stock X11 rgb.txt file.
631 */
632lib.colors.colorNames = {
633 "aliceblue": "rgb(240, 248, 255)",
634 "antiquewhite": "rgb(250, 235, 215)",
635 "antiquewhite1": "rgb(255, 239, 219)",
636 "antiquewhite2": "rgb(238, 223, 204)",
637 "antiquewhite3": "rgb(205, 192, 176)",
638 "antiquewhite4": "rgb(139, 131, 120)",
639 "aquamarine": "rgb(127, 255, 212)",
640 "aquamarine1": "rgb(127, 255, 212)",
641 "aquamarine2": "rgb(118, 238, 198)",
642 "aquamarine3": "rgb(102, 205, 170)",
643 "aquamarine4": "rgb(69, 139, 116)",
644 "azure": "rgb(240, 255, 255)",
645 "azure1": "rgb(240, 255, 255)",
646 "azure2": "rgb(224, 238, 238)",
647 "azure3": "rgb(193, 205, 205)",
648 "azure4": "rgb(131, 139, 139)",
649 "beige": "rgb(245, 245, 220)",
650 "bisque": "rgb(255, 228, 196)",
651 "bisque1": "rgb(255, 228, 196)",
652 "bisque2": "rgb(238, 213, 183)",
653 "bisque3": "rgb(205, 183, 158)",
654 "bisque4": "rgb(139, 125, 107)",
655 "black": "rgb(0, 0, 0)",
656 "blanchedalmond": "rgb(255, 235, 205)",
657 "blue": "rgb(0, 0, 255)",
658 "blue1": "rgb(0, 0, 255)",
659 "blue2": "rgb(0, 0, 238)",
660 "blue3": "rgb(0, 0, 205)",
661 "blue4": "rgb(0, 0, 139)",
662 "blueviolet": "rgb(138, 43, 226)",
663 "brown": "rgb(165, 42, 42)",
664 "brown1": "rgb(255, 64, 64)",
665 "brown2": "rgb(238, 59, 59)",
666 "brown3": "rgb(205, 51, 51)",
667 "brown4": "rgb(139, 35, 35)",
668 "burlywood": "rgb(222, 184, 135)",
669 "burlywood1": "rgb(255, 211, 155)",
670 "burlywood2": "rgb(238, 197, 145)",
671 "burlywood3": "rgb(205, 170, 125)",
672 "burlywood4": "rgb(139, 115, 85)",
673 "cadetblue": "rgb(95, 158, 160)",
674 "cadetblue1": "rgb(152, 245, 255)",
675 "cadetblue2": "rgb(142, 229, 238)",
676 "cadetblue3": "rgb(122, 197, 205)",
677 "cadetblue4": "rgb(83, 134, 139)",
678 "chartreuse": "rgb(127, 255, 0)",
679 "chartreuse1": "rgb(127, 255, 0)",
680 "chartreuse2": "rgb(118, 238, 0)",
681 "chartreuse3": "rgb(102, 205, 0)",
682 "chartreuse4": "rgb(69, 139, 0)",
683 "chocolate": "rgb(210, 105, 30)",
684 "chocolate1": "rgb(255, 127, 36)",
685 "chocolate2": "rgb(238, 118, 33)",
686 "chocolate3": "rgb(205, 102, 29)",
687 "chocolate4": "rgb(139, 69, 19)",
688 "coral": "rgb(255, 127, 80)",
689 "coral1": "rgb(255, 114, 86)",
690 "coral2": "rgb(238, 106, 80)",
691 "coral3": "rgb(205, 91, 69)",
692 "coral4": "rgb(139, 62, 47)",
693 "cornflowerblue": "rgb(100, 149, 237)",
694 "cornsilk": "rgb(255, 248, 220)",
695 "cornsilk1": "rgb(255, 248, 220)",
696 "cornsilk2": "rgb(238, 232, 205)",
697 "cornsilk3": "rgb(205, 200, 177)",
698 "cornsilk4": "rgb(139, 136, 120)",
699 "cyan": "rgb(0, 255, 255)",
700 "cyan1": "rgb(0, 255, 255)",
701 "cyan2": "rgb(0, 238, 238)",
702 "cyan3": "rgb(0, 205, 205)",
703 "cyan4": "rgb(0, 139, 139)",
704 "darkblue": "rgb(0, 0, 139)",
705 "darkcyan": "rgb(0, 139, 139)",
706 "darkgoldenrod": "rgb(184, 134, 11)",
707 "darkgoldenrod1": "rgb(255, 185, 15)",
708 "darkgoldenrod2": "rgb(238, 173, 14)",
709 "darkgoldenrod3": "rgb(205, 149, 12)",
710 "darkgoldenrod4": "rgb(139, 101, 8)",
711 "darkgray": "rgb(169, 169, 169)",
712 "darkgreen": "rgb(0, 100, 0)",
713 "darkgrey": "rgb(169, 169, 169)",
714 "darkkhaki": "rgb(189, 183, 107)",
715 "darkmagenta": "rgb(139, 0, 139)",
716 "darkolivegreen": "rgb(85, 107, 47)",
717 "darkolivegreen1": "rgb(202, 255, 112)",
718 "darkolivegreen2": "rgb(188, 238, 104)",
719 "darkolivegreen3": "rgb(162, 205, 90)",
720 "darkolivegreen4": "rgb(110, 139, 61)",
721 "darkorange": "rgb(255, 140, 0)",
722 "darkorange1": "rgb(255, 127, 0)",
723 "darkorange2": "rgb(238, 118, 0)",
724 "darkorange3": "rgb(205, 102, 0)",
725 "darkorange4": "rgb(139, 69, 0)",
726 "darkorchid": "rgb(153, 50, 204)",
727 "darkorchid1": "rgb(191, 62, 255)",
728 "darkorchid2": "rgb(178, 58, 238)",
729 "darkorchid3": "rgb(154, 50, 205)",
730 "darkorchid4": "rgb(104, 34, 139)",
731 "darkred": "rgb(139, 0, 0)",
732 "darksalmon": "rgb(233, 150, 122)",
733 "darkseagreen": "rgb(143, 188, 143)",
734 "darkseagreen1": "rgb(193, 255, 193)",
735 "darkseagreen2": "rgb(180, 238, 180)",
736 "darkseagreen3": "rgb(155, 205, 155)",
737 "darkseagreen4": "rgb(105, 139, 105)",
738 "darkslateblue": "rgb(72, 61, 139)",
739 "darkslategray": "rgb(47, 79, 79)",
740 "darkslategray1": "rgb(151, 255, 255)",
741 "darkslategray2": "rgb(141, 238, 238)",
742 "darkslategray3": "rgb(121, 205, 205)",
743 "darkslategray4": "rgb(82, 139, 139)",
744 "darkslategrey": "rgb(47, 79, 79)",
745 "darkturquoise": "rgb(0, 206, 209)",
746 "darkviolet": "rgb(148, 0, 211)",
747 "debianred": "rgb(215, 7, 81)",
748 "deeppink": "rgb(255, 20, 147)",
749 "deeppink1": "rgb(255, 20, 147)",
750 "deeppink2": "rgb(238, 18, 137)",
751 "deeppink3": "rgb(205, 16, 118)",
752 "deeppink4": "rgb(139, 10, 80)",
753 "deepskyblue": "rgb(0, 191, 255)",
754 "deepskyblue1": "rgb(0, 191, 255)",
755 "deepskyblue2": "rgb(0, 178, 238)",
756 "deepskyblue3": "rgb(0, 154, 205)",
757 "deepskyblue4": "rgb(0, 104, 139)",
758 "dimgray": "rgb(105, 105, 105)",
759 "dimgrey": "rgb(105, 105, 105)",
760 "dodgerblue": "rgb(30, 144, 255)",
761 "dodgerblue1": "rgb(30, 144, 255)",
762 "dodgerblue2": "rgb(28, 134, 238)",
763 "dodgerblue3": "rgb(24, 116, 205)",
764 "dodgerblue4": "rgb(16, 78, 139)",
765 "firebrick": "rgb(178, 34, 34)",
766 "firebrick1": "rgb(255, 48, 48)",
767 "firebrick2": "rgb(238, 44, 44)",
768 "firebrick3": "rgb(205, 38, 38)",
769 "firebrick4": "rgb(139, 26, 26)",
770 "floralwhite": "rgb(255, 250, 240)",
771 "forestgreen": "rgb(34, 139, 34)",
772 "gainsboro": "rgb(220, 220, 220)",
773 "ghostwhite": "rgb(248, 248, 255)",
774 "gold": "rgb(255, 215, 0)",
775 "gold1": "rgb(255, 215, 0)",
776 "gold2": "rgb(238, 201, 0)",
777 "gold3": "rgb(205, 173, 0)",
778 "gold4": "rgb(139, 117, 0)",
779 "goldenrod": "rgb(218, 165, 32)",
780 "goldenrod1": "rgb(255, 193, 37)",
781 "goldenrod2": "rgb(238, 180, 34)",
782 "goldenrod3": "rgb(205, 155, 29)",
783 "goldenrod4": "rgb(139, 105, 20)",
784 "gray": "rgb(190, 190, 190)",
785 "gray0": "rgb(0, 0, 0)",
786 "gray1": "rgb(3, 3, 3)",
787 "gray10": "rgb(26, 26, 26)",
788 "gray100": "rgb(255, 255, 255)",
789 "gray11": "rgb(28, 28, 28)",
790 "gray12": "rgb(31, 31, 31)",
791 "gray13": "rgb(33, 33, 33)",
792 "gray14": "rgb(36, 36, 36)",
793 "gray15": "rgb(38, 38, 38)",
794 "gray16": "rgb(41, 41, 41)",
795 "gray17": "rgb(43, 43, 43)",
796 "gray18": "rgb(46, 46, 46)",
797 "gray19": "rgb(48, 48, 48)",
798 "gray2": "rgb(5, 5, 5)",
799 "gray20": "rgb(51, 51, 51)",
800 "gray21": "rgb(54, 54, 54)",
801 "gray22": "rgb(56, 56, 56)",
802 "gray23": "rgb(59, 59, 59)",
803 "gray24": "rgb(61, 61, 61)",
804 "gray25": "rgb(64, 64, 64)",
805 "gray26": "rgb(66, 66, 66)",
806 "gray27": "rgb(69, 69, 69)",
807 "gray28": "rgb(71, 71, 71)",
808 "gray29": "rgb(74, 74, 74)",
809 "gray3": "rgb(8, 8, 8)",
810 "gray30": "rgb(77, 77, 77)",
811 "gray31": "rgb(79, 79, 79)",
812 "gray32": "rgb(82, 82, 82)",
813 "gray33": "rgb(84, 84, 84)",
814 "gray34": "rgb(87, 87, 87)",
815 "gray35": "rgb(89, 89, 89)",
816 "gray36": "rgb(92, 92, 92)",
817 "gray37": "rgb(94, 94, 94)",
818 "gray38": "rgb(97, 97, 97)",
819 "gray39": "rgb(99, 99, 99)",
820 "gray4": "rgb(10, 10, 10)",
821 "gray40": "rgb(102, 102, 102)",
822 "gray41": "rgb(105, 105, 105)",
823 "gray42": "rgb(107, 107, 107)",
824 "gray43": "rgb(110, 110, 110)",
825 "gray44": "rgb(112, 112, 112)",
826 "gray45": "rgb(115, 115, 115)",
827 "gray46": "rgb(117, 117, 117)",
828 "gray47": "rgb(120, 120, 120)",
829 "gray48": "rgb(122, 122, 122)",
830 "gray49": "rgb(125, 125, 125)",
831 "gray5": "rgb(13, 13, 13)",
832 "gray50": "rgb(127, 127, 127)",
833 "gray51": "rgb(130, 130, 130)",
834 "gray52": "rgb(133, 133, 133)",
835 "gray53": "rgb(135, 135, 135)",
836 "gray54": "rgb(138, 138, 138)",
837 "gray55": "rgb(140, 140, 140)",
838 "gray56": "rgb(143, 143, 143)",
839 "gray57": "rgb(145, 145, 145)",
840 "gray58": "rgb(148, 148, 148)",
841 "gray59": "rgb(150, 150, 150)",
842 "gray6": "rgb(15, 15, 15)",
843 "gray60": "rgb(153, 153, 153)",
844 "gray61": "rgb(156, 156, 156)",
845 "gray62": "rgb(158, 158, 158)",
846 "gray63": "rgb(161, 161, 161)",
847 "gray64": "rgb(163, 163, 163)",
848 "gray65": "rgb(166, 166, 166)",
849 "gray66": "rgb(168, 168, 168)",
850 "gray67": "rgb(171, 171, 171)",
851 "gray68": "rgb(173, 173, 173)",
852 "gray69": "rgb(176, 176, 176)",
853 "gray7": "rgb(18, 18, 18)",
854 "gray70": "rgb(179, 179, 179)",
855 "gray71": "rgb(181, 181, 181)",
856 "gray72": "rgb(184, 184, 184)",
857 "gray73": "rgb(186, 186, 186)",
858 "gray74": "rgb(189, 189, 189)",
859 "gray75": "rgb(191, 191, 191)",
860 "gray76": "rgb(194, 194, 194)",
861 "gray77": "rgb(196, 196, 196)",
862 "gray78": "rgb(199, 199, 199)",
863 "gray79": "rgb(201, 201, 201)",
864 "gray8": "rgb(20, 20, 20)",
865 "gray80": "rgb(204, 204, 204)",
866 "gray81": "rgb(207, 207, 207)",
867 "gray82": "rgb(209, 209, 209)",
868 "gray83": "rgb(212, 212, 212)",
869 "gray84": "rgb(214, 214, 214)",
870 "gray85": "rgb(217, 217, 217)",
871 "gray86": "rgb(219, 219, 219)",
872 "gray87": "rgb(222, 222, 222)",
873 "gray88": "rgb(224, 224, 224)",
874 "gray89": "rgb(227, 227, 227)",
875 "gray9": "rgb(23, 23, 23)",
876 "gray90": "rgb(229, 229, 229)",
877 "gray91": "rgb(232, 232, 232)",
878 "gray92": "rgb(235, 235, 235)",
879 "gray93": "rgb(237, 237, 237)",
880 "gray94": "rgb(240, 240, 240)",
881 "gray95": "rgb(242, 242, 242)",
882 "gray96": "rgb(245, 245, 245)",
883 "gray97": "rgb(247, 247, 247)",
884 "gray98": "rgb(250, 250, 250)",
885 "gray99": "rgb(252, 252, 252)",
886 "green": "rgb(0, 255, 0)",
887 "green1": "rgb(0, 255, 0)",
888 "green2": "rgb(0, 238, 0)",
889 "green3": "rgb(0, 205, 0)",
890 "green4": "rgb(0, 139, 0)",
891 "greenyellow": "rgb(173, 255, 47)",
892 "grey": "rgb(190, 190, 190)",
893 "grey0": "rgb(0, 0, 0)",
894 "grey1": "rgb(3, 3, 3)",
895 "grey10": "rgb(26, 26, 26)",
896 "grey100": "rgb(255, 255, 255)",
897 "grey11": "rgb(28, 28, 28)",
898 "grey12": "rgb(31, 31, 31)",
899 "grey13": "rgb(33, 33, 33)",
900 "grey14": "rgb(36, 36, 36)",
901 "grey15": "rgb(38, 38, 38)",
902 "grey16": "rgb(41, 41, 41)",
903 "grey17": "rgb(43, 43, 43)",
904 "grey18": "rgb(46, 46, 46)",
905 "grey19": "rgb(48, 48, 48)",
906 "grey2": "rgb(5, 5, 5)",
907 "grey20": "rgb(51, 51, 51)",
908 "grey21": "rgb(54, 54, 54)",
909 "grey22": "rgb(56, 56, 56)",
910 "grey23": "rgb(59, 59, 59)",
911 "grey24": "rgb(61, 61, 61)",
912 "grey25": "rgb(64, 64, 64)",
913 "grey26": "rgb(66, 66, 66)",
914 "grey27": "rgb(69, 69, 69)",
915 "grey28": "rgb(71, 71, 71)",
916 "grey29": "rgb(74, 74, 74)",
917 "grey3": "rgb(8, 8, 8)",
918 "grey30": "rgb(77, 77, 77)",
919 "grey31": "rgb(79, 79, 79)",
920 "grey32": "rgb(82, 82, 82)",
921 "grey33": "rgb(84, 84, 84)",
922 "grey34": "rgb(87, 87, 87)",
923 "grey35": "rgb(89, 89, 89)",
924 "grey36": "rgb(92, 92, 92)",
925 "grey37": "rgb(94, 94, 94)",
926 "grey38": "rgb(97, 97, 97)",
927 "grey39": "rgb(99, 99, 99)",
928 "grey4": "rgb(10, 10, 10)",
929 "grey40": "rgb(102, 102, 102)",
930 "grey41": "rgb(105, 105, 105)",
931 "grey42": "rgb(107, 107, 107)",
932 "grey43": "rgb(110, 110, 110)",
933 "grey44": "rgb(112, 112, 112)",
934 "grey45": "rgb(115, 115, 115)",
935 "grey46": "rgb(117, 117, 117)",
936 "grey47": "rgb(120, 120, 120)",
937 "grey48": "rgb(122, 122, 122)",
938 "grey49": "rgb(125, 125, 125)",
939 "grey5": "rgb(13, 13, 13)",
940 "grey50": "rgb(127, 127, 127)",
941 "grey51": "rgb(130, 130, 130)",
942 "grey52": "rgb(133, 133, 133)",
943 "grey53": "rgb(135, 135, 135)",
944 "grey54": "rgb(138, 138, 138)",
945 "grey55": "rgb(140, 140, 140)",
946 "grey56": "rgb(143, 143, 143)",
947 "grey57": "rgb(145, 145, 145)",
948 "grey58": "rgb(148, 148, 148)",
949 "grey59": "rgb(150, 150, 150)",
950 "grey6": "rgb(15, 15, 15)",
951 "grey60": "rgb(153, 153, 153)",
952 "grey61": "rgb(156, 156, 156)",
953 "grey62": "rgb(158, 158, 158)",
954 "grey63": "rgb(161, 161, 161)",
955 "grey64": "rgb(163, 163, 163)",
956 "grey65": "rgb(166, 166, 166)",
957 "grey66": "rgb(168, 168, 168)",
958 "grey67": "rgb(171, 171, 171)",
959 "grey68": "rgb(173, 173, 173)",
960 "grey69": "rgb(176, 176, 176)",
961 "grey7": "rgb(18, 18, 18)",
962 "grey70": "rgb(179, 179, 179)",
963 "grey71": "rgb(181, 181, 181)",
964 "grey72": "rgb(184, 184, 184)",
965 "grey73": "rgb(186, 186, 186)",
966 "grey74": "rgb(189, 189, 189)",
967 "grey75": "rgb(191, 191, 191)",
968 "grey76": "rgb(194, 194, 194)",
969 "grey77": "rgb(196, 196, 196)",
970 "grey78": "rgb(199, 199, 199)",
971 "grey79": "rgb(201, 201, 201)",
972 "grey8": "rgb(20, 20, 20)",
973 "grey80": "rgb(204, 204, 204)",
974 "grey81": "rgb(207, 207, 207)",
975 "grey82": "rgb(209, 209, 209)",
976 "grey83": "rgb(212, 212, 212)",
977 "grey84": "rgb(214, 214, 214)",
978 "grey85": "rgb(217, 217, 217)",
979 "grey86": "rgb(219, 219, 219)",
980 "grey87": "rgb(222, 222, 222)",
981 "grey88": "rgb(224, 224, 224)",
982 "grey89": "rgb(227, 227, 227)",
983 "grey9": "rgb(23, 23, 23)",
984 "grey90": "rgb(229, 229, 229)",
985 "grey91": "rgb(232, 232, 232)",
986 "grey92": "rgb(235, 235, 235)",
987 "grey93": "rgb(237, 237, 237)",
988 "grey94": "rgb(240, 240, 240)",
989 "grey95": "rgb(242, 242, 242)",
990 "grey96": "rgb(245, 245, 245)",
991 "grey97": "rgb(247, 247, 247)",
992 "grey98": "rgb(250, 250, 250)",
993 "grey99": "rgb(252, 252, 252)",
994 "honeydew": "rgb(240, 255, 240)",
995 "honeydew1": "rgb(240, 255, 240)",
996 "honeydew2": "rgb(224, 238, 224)",
997 "honeydew3": "rgb(193, 205, 193)",
998 "honeydew4": "rgb(131, 139, 131)",
999 "hotpink": "rgb(255, 105, 180)",
1000 "hotpink1": "rgb(255, 110, 180)",
1001 "hotpink2": "rgb(238, 106, 167)",
1002 "hotpink3": "rgb(205, 96, 144)",
1003 "hotpink4": "rgb(139, 58, 98)",
1004 "indianred": "rgb(205, 92, 92)",
1005 "indianred1": "rgb(255, 106, 106)",
1006 "indianred2": "rgb(238, 99, 99)",
1007 "indianred3": "rgb(205, 85, 85)",
1008 "indianred4": "rgb(139, 58, 58)",
1009 "ivory": "rgb(255, 255, 240)",
1010 "ivory1": "rgb(255, 255, 240)",
1011 "ivory2": "rgb(238, 238, 224)",
1012 "ivory3": "rgb(205, 205, 193)",
1013 "ivory4": "rgb(139, 139, 131)",
1014 "khaki": "rgb(240, 230, 140)",
1015 "khaki1": "rgb(255, 246, 143)",
1016 "khaki2": "rgb(238, 230, 133)",
1017 "khaki3": "rgb(205, 198, 115)",
1018 "khaki4": "rgb(139, 134, 78)",
1019 "lavender": "rgb(230, 230, 250)",
1020 "lavenderblush": "rgb(255, 240, 245)",
1021 "lavenderblush1": "rgb(255, 240, 245)",
1022 "lavenderblush2": "rgb(238, 224, 229)",
1023 "lavenderblush3": "rgb(205, 193, 197)",
1024 "lavenderblush4": "rgb(139, 131, 134)",
1025 "lawngreen": "rgb(124, 252, 0)",
1026 "lemonchiffon": "rgb(255, 250, 205)",
1027 "lemonchiffon1": "rgb(255, 250, 205)",
1028 "lemonchiffon2": "rgb(238, 233, 191)",
1029 "lemonchiffon3": "rgb(205, 201, 165)",
1030 "lemonchiffon4": "rgb(139, 137, 112)",
1031 "lightblue": "rgb(173, 216, 230)",
1032 "lightblue1": "rgb(191, 239, 255)",
1033 "lightblue2": "rgb(178, 223, 238)",
1034 "lightblue3": "rgb(154, 192, 205)",
1035 "lightblue4": "rgb(104, 131, 139)",
1036 "lightcoral": "rgb(240, 128, 128)",
1037 "lightcyan": "rgb(224, 255, 255)",
1038 "lightcyan1": "rgb(224, 255, 255)",
1039 "lightcyan2": "rgb(209, 238, 238)",
1040 "lightcyan3": "rgb(180, 205, 205)",
1041 "lightcyan4": "rgb(122, 139, 139)",
1042 "lightgoldenrod": "rgb(238, 221, 130)",
1043 "lightgoldenrod1": "rgb(255, 236, 139)",
1044 "lightgoldenrod2": "rgb(238, 220, 130)",
1045 "lightgoldenrod3": "rgb(205, 190, 112)",
1046 "lightgoldenrod4": "rgb(139, 129, 76)",
1047 "lightgoldenrodyellow": "rgb(250, 250, 210)",
1048 "lightgray": "rgb(211, 211, 211)",
1049 "lightgreen": "rgb(144, 238, 144)",
1050 "lightgrey": "rgb(211, 211, 211)",
1051 "lightpink": "rgb(255, 182, 193)",
1052 "lightpink1": "rgb(255, 174, 185)",
1053 "lightpink2": "rgb(238, 162, 173)",
1054 "lightpink3": "rgb(205, 140, 149)",
1055 "lightpink4": "rgb(139, 95, 101)",
1056 "lightsalmon": "rgb(255, 160, 122)",
1057 "lightsalmon1": "rgb(255, 160, 122)",
1058 "lightsalmon2": "rgb(238, 149, 114)",
1059 "lightsalmon3": "rgb(205, 129, 98)",
1060 "lightsalmon4": "rgb(139, 87, 66)",
1061 "lightseagreen": "rgb(32, 178, 170)",
1062 "lightskyblue": "rgb(135, 206, 250)",
1063 "lightskyblue1": "rgb(176, 226, 255)",
1064 "lightskyblue2": "rgb(164, 211, 238)",
1065 "lightskyblue3": "rgb(141, 182, 205)",
1066 "lightskyblue4": "rgb(96, 123, 139)",
1067 "lightslateblue": "rgb(132, 112, 255)",
1068 "lightslategray": "rgb(119, 136, 153)",
1069 "lightslategrey": "rgb(119, 136, 153)",
1070 "lightsteelblue": "rgb(176, 196, 222)",
1071 "lightsteelblue1": "rgb(202, 225, 255)",
1072 "lightsteelblue2": "rgb(188, 210, 238)",
1073 "lightsteelblue3": "rgb(162, 181, 205)",
1074 "lightsteelblue4": "rgb(110, 123, 139)",
1075 "lightyellow": "rgb(255, 255, 224)",
1076 "lightyellow1": "rgb(255, 255, 224)",
1077 "lightyellow2": "rgb(238, 238, 209)",
1078 "lightyellow3": "rgb(205, 205, 180)",
1079 "lightyellow4": "rgb(139, 139, 122)",
1080 "limegreen": "rgb(50, 205, 50)",
1081 "linen": "rgb(250, 240, 230)",
1082 "magenta": "rgb(255, 0, 255)",
1083 "magenta1": "rgb(255, 0, 255)",
1084 "magenta2": "rgb(238, 0, 238)",
1085 "magenta3": "rgb(205, 0, 205)",
1086 "magenta4": "rgb(139, 0, 139)",
1087 "maroon": "rgb(176, 48, 96)",
1088 "maroon1": "rgb(255, 52, 179)",
1089 "maroon2": "rgb(238, 48, 167)",
1090 "maroon3": "rgb(205, 41, 144)",
1091 "maroon4": "rgb(139, 28, 98)",
1092 "mediumaquamarine": "rgb(102, 205, 170)",
1093 "mediumblue": "rgb(0, 0, 205)",
1094 "mediumorchid": "rgb(186, 85, 211)",
1095 "mediumorchid1": "rgb(224, 102, 255)",
1096 "mediumorchid2": "rgb(209, 95, 238)",
1097 "mediumorchid3": "rgb(180, 82, 205)",
1098 "mediumorchid4": "rgb(122, 55, 139)",
1099 "mediumpurple": "rgb(147, 112, 219)",
1100 "mediumpurple1": "rgb(171, 130, 255)",
1101 "mediumpurple2": "rgb(159, 121, 238)",
1102 "mediumpurple3": "rgb(137, 104, 205)",
1103 "mediumpurple4": "rgb(93, 71, 139)",
1104 "mediumseagreen": "rgb(60, 179, 113)",
1105 "mediumslateblue": "rgb(123, 104, 238)",
1106 "mediumspringgreen": "rgb(0, 250, 154)",
1107 "mediumturquoise": "rgb(72, 209, 204)",
1108 "mediumvioletred": "rgb(199, 21, 133)",
1109 "midnightblue": "rgb(25, 25, 112)",
1110 "mintcream": "rgb(245, 255, 250)",
1111 "mistyrose": "rgb(255, 228, 225)",
1112 "mistyrose1": "rgb(255, 228, 225)",
1113 "mistyrose2": "rgb(238, 213, 210)",
1114 "mistyrose3": "rgb(205, 183, 181)",
1115 "mistyrose4": "rgb(139, 125, 123)",
1116 "moccasin": "rgb(255, 228, 181)",
1117 "navajowhite": "rgb(255, 222, 173)",
1118 "navajowhite1": "rgb(255, 222, 173)",
1119 "navajowhite2": "rgb(238, 207, 161)",
1120 "navajowhite3": "rgb(205, 179, 139)",
1121 "navajowhite4": "rgb(139, 121, 94)",
1122 "navy": "rgb(0, 0, 128)",
1123 "navyblue": "rgb(0, 0, 128)",
1124 "oldlace": "rgb(253, 245, 230)",
1125 "olivedrab": "rgb(107, 142, 35)",
1126 "olivedrab1": "rgb(192, 255, 62)",
1127 "olivedrab2": "rgb(179, 238, 58)",
1128 "olivedrab3": "rgb(154, 205, 50)",
1129 "olivedrab4": "rgb(105, 139, 34)",
1130 "orange": "rgb(255, 165, 0)",
1131 "orange1": "rgb(255, 165, 0)",
1132 "orange2": "rgb(238, 154, 0)",
1133 "orange3": "rgb(205, 133, 0)",
1134 "orange4": "rgb(139, 90, 0)",
1135 "orangered": "rgb(255, 69, 0)",
1136 "orangered1": "rgb(255, 69, 0)",
1137 "orangered2": "rgb(238, 64, 0)",
1138 "orangered3": "rgb(205, 55, 0)",
1139 "orangered4": "rgb(139, 37, 0)",
1140 "orchid": "rgb(218, 112, 214)",
1141 "orchid1": "rgb(255, 131, 250)",
1142 "orchid2": "rgb(238, 122, 233)",
1143 "orchid3": "rgb(205, 105, 201)",
1144 "orchid4": "rgb(139, 71, 137)",
1145 "palegoldenrod": "rgb(238, 232, 170)",
1146 "palegreen": "rgb(152, 251, 152)",
1147 "palegreen1": "rgb(154, 255, 154)",
1148 "palegreen2": "rgb(144, 238, 144)",
1149 "palegreen3": "rgb(124, 205, 124)",
1150 "palegreen4": "rgb(84, 139, 84)",
1151 "paleturquoise": "rgb(175, 238, 238)",
1152 "paleturquoise1": "rgb(187, 255, 255)",
1153 "paleturquoise2": "rgb(174, 238, 238)",
1154 "paleturquoise3": "rgb(150, 205, 205)",
1155 "paleturquoise4": "rgb(102, 139, 139)",
1156 "palevioletred": "rgb(219, 112, 147)",
1157 "palevioletred1": "rgb(255, 130, 171)",
1158 "palevioletred2": "rgb(238, 121, 159)",
1159 "palevioletred3": "rgb(205, 104, 137)",
1160 "palevioletred4": "rgb(139, 71, 93)",
1161 "papayawhip": "rgb(255, 239, 213)",
1162 "peachpuff": "rgb(255, 218, 185)",
1163 "peachpuff1": "rgb(255, 218, 185)",
1164 "peachpuff2": "rgb(238, 203, 173)",
1165 "peachpuff3": "rgb(205, 175, 149)",
1166 "peachpuff4": "rgb(139, 119, 101)",
1167 "peru": "rgb(205, 133, 63)",
1168 "pink": "rgb(255, 192, 203)",
1169 "pink1": "rgb(255, 181, 197)",
1170 "pink2": "rgb(238, 169, 184)",
1171 "pink3": "rgb(205, 145, 158)",
1172 "pink4": "rgb(139, 99, 108)",
1173 "plum": "rgb(221, 160, 221)",
1174 "plum1": "rgb(255, 187, 255)",
1175 "plum2": "rgb(238, 174, 238)",
1176 "plum3": "rgb(205, 150, 205)",
1177 "plum4": "rgb(139, 102, 139)",
1178 "powderblue": "rgb(176, 224, 230)",
1179 "purple": "rgb(160, 32, 240)",
1180 "purple1": "rgb(155, 48, 255)",
1181 "purple2": "rgb(145, 44, 238)",
1182 "purple3": "rgb(125, 38, 205)",
1183 "purple4": "rgb(85, 26, 139)",
1184 "red": "rgb(255, 0, 0)",
1185 "red1": "rgb(255, 0, 0)",
1186 "red2": "rgb(238, 0, 0)",
1187 "red3": "rgb(205, 0, 0)",
1188 "red4": "rgb(139, 0, 0)",
1189 "rosybrown": "rgb(188, 143, 143)",
1190 "rosybrown1": "rgb(255, 193, 193)",
1191 "rosybrown2": "rgb(238, 180, 180)",
1192 "rosybrown3": "rgb(205, 155, 155)",
1193 "rosybrown4": "rgb(139, 105, 105)",
1194 "royalblue": "rgb(65, 105, 225)",
1195 "royalblue1": "rgb(72, 118, 255)",
1196 "royalblue2": "rgb(67, 110, 238)",
1197 "royalblue3": "rgb(58, 95, 205)",
1198 "royalblue4": "rgb(39, 64, 139)",
1199 "saddlebrown": "rgb(139, 69, 19)",
1200 "salmon": "rgb(250, 128, 114)",
1201 "salmon1": "rgb(255, 140, 105)",
1202 "salmon2": "rgb(238, 130, 98)",
1203 "salmon3": "rgb(205, 112, 84)",
1204 "salmon4": "rgb(139, 76, 57)",
1205 "sandybrown": "rgb(244, 164, 96)",
1206 "seagreen": "rgb(46, 139, 87)",
1207 "seagreen1": "rgb(84, 255, 159)",
1208 "seagreen2": "rgb(78, 238, 148)",
1209 "seagreen3": "rgb(67, 205, 128)",
1210 "seagreen4": "rgb(46, 139, 87)",
1211 "seashell": "rgb(255, 245, 238)",
1212 "seashell1": "rgb(255, 245, 238)",
1213 "seashell2": "rgb(238, 229, 222)",
1214 "seashell3": "rgb(205, 197, 191)",
1215 "seashell4": "rgb(139, 134, 130)",
1216 "sienna": "rgb(160, 82, 45)",
1217 "sienna1": "rgb(255, 130, 71)",
1218 "sienna2": "rgb(238, 121, 66)",
1219 "sienna3": "rgb(205, 104, 57)",
1220 "sienna4": "rgb(139, 71, 38)",
1221 "skyblue": "rgb(135, 206, 235)",
1222 "skyblue1": "rgb(135, 206, 255)",
1223 "skyblue2": "rgb(126, 192, 238)",
1224 "skyblue3": "rgb(108, 166, 205)",
1225 "skyblue4": "rgb(74, 112, 139)",
1226 "slateblue": "rgb(106, 90, 205)",
1227 "slateblue1": "rgb(131, 111, 255)",
1228 "slateblue2": "rgb(122, 103, 238)",
1229 "slateblue3": "rgb(105, 89, 205)",
1230 "slateblue4": "rgb(71, 60, 139)",
1231 "slategray": "rgb(112, 128, 144)",
1232 "slategray1": "rgb(198, 226, 255)",
1233 "slategray2": "rgb(185, 211, 238)",
1234 "slategray3": "rgb(159, 182, 205)",
1235 "slategray4": "rgb(108, 123, 139)",
1236 "slategrey": "rgb(112, 128, 144)",
1237 "snow": "rgb(255, 250, 250)",
1238 "snow1": "rgb(255, 250, 250)",
1239 "snow2": "rgb(238, 233, 233)",
1240 "snow3": "rgb(205, 201, 201)",
1241 "snow4": "rgb(139, 137, 137)",
1242 "springgreen": "rgb(0, 255, 127)",
1243 "springgreen1": "rgb(0, 255, 127)",
1244 "springgreen2": "rgb(0, 238, 118)",
1245 "springgreen3": "rgb(0, 205, 102)",
1246 "springgreen4": "rgb(0, 139, 69)",
1247 "steelblue": "rgb(70, 130, 180)",
1248 "steelblue1": "rgb(99, 184, 255)",
1249 "steelblue2": "rgb(92, 172, 238)",
1250 "steelblue3": "rgb(79, 148, 205)",
1251 "steelblue4": "rgb(54, 100, 139)",
1252 "tan": "rgb(210, 180, 140)",
1253 "tan1": "rgb(255, 165, 79)",
1254 "tan2": "rgb(238, 154, 73)",
1255 "tan3": "rgb(205, 133, 63)",
1256 "tan4": "rgb(139, 90, 43)",
1257 "thistle": "rgb(216, 191, 216)",
1258 "thistle1": "rgb(255, 225, 255)",
1259 "thistle2": "rgb(238, 210, 238)",
1260 "thistle3": "rgb(205, 181, 205)",
1261 "thistle4": "rgb(139, 123, 139)",
1262 "tomato": "rgb(255, 99, 71)",
1263 "tomato1": "rgb(255, 99, 71)",
1264 "tomato2": "rgb(238, 92, 66)",
1265 "tomato3": "rgb(205, 79, 57)",
1266 "tomato4": "rgb(139, 54, 38)",
1267 "turquoise": "rgb(64, 224, 208)",
1268 "turquoise1": "rgb(0, 245, 255)",
1269 "turquoise2": "rgb(0, 229, 238)",
1270 "turquoise3": "rgb(0, 197, 205)",
1271 "turquoise4": "rgb(0, 134, 139)",
1272 "violet": "rgb(238, 130, 238)",
1273 "violetred": "rgb(208, 32, 144)",
1274 "violetred1": "rgb(255, 62, 150)",
1275 "violetred2": "rgb(238, 58, 140)",
1276 "violetred3": "rgb(205, 50, 120)",
1277 "violetred4": "rgb(139, 34, 82)",
1278 "wheat": "rgb(245, 222, 179)",
1279 "wheat1": "rgb(255, 231, 186)",
1280 "wheat2": "rgb(238, 216, 174)",
1281 "wheat3": "rgb(205, 186, 150)",
1282 "wheat4": "rgb(139, 126, 102)",
1283 "white": "rgb(255, 255, 255)",
1284 "whitesmoke": "rgb(245, 245, 245)",
1285 "yellow": "rgb(255, 255, 0)",
1286 "yellow1": "rgb(255, 255, 0)",
1287 "yellow2": "rgb(238, 238, 0)",
1288 "yellow3": "rgb(205, 205, 0)",
1289 "yellow4": "rgb(139, 139, 0)",
1290 "yellowgreen": "rgb(154, 205, 50)"
1291};
1292// SOURCE FILE: libdot/js/lib_f.js
1293// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
1294// Use of this source code is governed by a BSD-style license that can be
1295// found in the LICENSE file.
1296
1297'use strict';
1298
1299/**
1300 * Grab bag of utility functions.
1301 */
1302lib.f = {};
1303
1304/**
1305 * Replace variable references in a string.
1306 *
1307 * Variables are of the form %FUNCTION(VARNAME). FUNCTION is an optional
1308 * escape function to apply to the value.
1309 *
1310 * For example
1311 * lib.f.replaceVars("%(greeting), %encodeURIComponent(name)",
1312 * { greeting: "Hello",
1313 * name: "Google+" });
1314 *
1315 * Will result in "Hello, Google%2B".
1316 */
1317lib.f.replaceVars = function(str, vars) {
1318 return str.replace(/%([a-z]*)\(([^\)]+)\)/gi, function(match, fn, varname) {
1319 if (typeof vars[varname] == 'undefined')
1320 throw 'Unknown variable: ' + varname;
1321
1322 var rv = vars[varname];
1323
1324 if (fn in lib.f.replaceVars.functions) {
1325 rv = lib.f.replaceVars.functions[fn](rv);
1326 } else if (fn) {
1327 throw 'Unknown escape function: ' + fn;
1328 }
1329
1330 return rv;
1331 });
1332};
1333
1334/**
1335 * Functions that can be used with replaceVars.
1336 *
1337 * Clients can add to this list to extend lib.f.replaceVars().
1338 */
1339lib.f.replaceVars.functions = {
1340 encodeURI: encodeURI,
1341 encodeURIComponent: encodeURIComponent,
1342 escapeHTML: function(str) {
1343 var map = {
1344 '<': '&lt;',
1345 '>': '&gt;',
1346 '&': '&amp;',
1347 '"': '&quot;',
1348 "'": '&#39;'
1349 };
1350
1351 return str.replace(/[<>&\"\']/g, function(m) { return map[m] });
1352 }
1353};
1354
1355/**
1356 * Get the list of accepted UI languages.
1357 *
1358 * @param {function(Array)} callback Function to invoke with the results. The
1359 * parameter is a list of locale names.
1360 */
1361lib.f.getAcceptLanguages = function(callback) {
1362 if (lib.f.getAcceptLanguages.chromeSupported()) {
1363 chrome.i18n.getAcceptLanguages(callback);
1364 } else {
1365 setTimeout(function() {
1366 callback([navigator.language.replace(/-/g, '_')]);
1367 }, 0);
1368 }
1369};
1370
1371lib.f.getAcceptLanguages.chromeSupported = function() {
1372 return window.chrome && chrome.i18n;
1373};
1374
1375/**
1376 * Parse a query string into a hash.
1377 *
1378 * This takes a url query string in the form 'name1=value&name2=value' and
1379 * converts it into an object of the form { name1: 'value', name2: 'value' }.
1380 * If a given name appears multiple times in the query string, only the
1381 * last value will appear in the result.
1382 *
1383 * Names and values are passed through decodeURIComponent before being added
1384 * to the result object.
1385 *
1386 * @param {string} queryString The string to parse. If it starts with a
1387 * leading '?', the '?' will be ignored.
1388 */
1389lib.f.parseQuery = function(queryString) {
1390 if (queryString.substr(0, 1) == '?')
1391 queryString = queryString.substr(1);
1392
1393 var rv = {};
1394
1395 var pairs = queryString.split('&');
1396 for (var i = 0; i < pairs.length; i++) {
1397 var pair = pairs[i].split('=');
1398 rv[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
1399 }
1400
1401 return rv;
1402};
1403
1404lib.f.getURL = function(path) {
1405 if (lib.f.getURL.chromeSupported())
1406 return chrome.runtime.getURL(path);
1407
1408 return path;
1409};
1410
1411lib.f.getURL.chromeSupported = function() {
1412 return window.chrome && chrome.runtime && chrome.runtime.getURL;
1413};
1414
1415/**
1416 * Clamp a given integer to a specified range.
1417 *
1418 * @param {integer} v The value to be clamped.
1419 * @param {integer} min The minimum acceptable value.
1420 * @param {integer} max The maximum acceptable value.
1421 */
1422lib.f.clamp = function(v, min, max) {
1423 if (v < min)
1424 return min;
1425 if (v > max)
1426 return max;
1427 return v;
1428};
1429
1430/**
1431 * Left pad a string to a given length using a given character.
1432 *
1433 * @param {string} str The string to pad.
1434 * @param {integer} length The desired length.
1435 * @param {string} opt_ch The optional padding character, defaults to ' '.
1436 * @return {string} The padded string.
1437 */
1438lib.f.lpad = function(str, length, opt_ch) {
1439 str = String(str);
1440 opt_ch = opt_ch || ' ';
1441
1442 while (str.length < length)
1443 str = opt_ch + str;
1444
1445 return str;
1446};
1447
1448/**
1449 * Left pad a number to a given length with leading zeros.
1450 *
1451 * @param {string|integer} number The number to pad.
1452 * @param {integer} length The desired length.
1453 * @return {string} The padded number as a string.
1454 */
1455lib.f.zpad = function(number, length) {
1456 return lib.f.lpad(number, length, '0');
1457};
1458
1459/**
1460 * Return a string containing a given number of space characters.
1461 *
1462 * This method maintains a static cache of the largest amount of whitespace
1463 * ever requested. It shouldn't be used to generate an insanely huge amount of
1464 * whitespace.
1465 *
1466 * @param {integer} length The desired amount of whitespace.
1467 * @param {string} A string of spaces of the requested length.
1468 */
1469lib.f.getWhitespace = function(length) {
1470 if (length <= 0)
1471 return '';
1472
1473 var f = this.getWhitespace;
1474 if (!f.whitespace)
1475 f.whitespace = ' ';
1476
1477 while (length > f.whitespace.length) {
1478 f.whitespace += f.whitespace;
1479 }
1480
1481 return f.whitespace.substr(0, length);
1482};
1483
1484 /**
1485 * Ensure that a function is called within a certain time limit.
1486 *
1487 * Simple usage looks like this...
1488 *
1489 * lib.registerInit(lib.f.alarm(onInit));
1490 *
1491 * This will log a warning to the console if onInit() is not invoked within
1492 * 5 seconds.
1493 *
1494 * If you're performing some operation that may take longer than 5 seconds you
1495 * can pass a duration in milliseconds as the optional second parameter.
1496 *
1497 * If you pass a string identifier instead of a callback function, you'll get a
1498 * wrapper generator rather than a single wrapper. Each call to the
1499 * generator will return a wrapped version of the callback wired to
1500 * a shared timeout. This is for cases where you want to ensure that at least
1501 * one of a set of callbacks is invoked before a timeout expires.
1502 *
1503 * var alarm = lib.f.alarm('fetch object');
1504 * lib.foo.fetchObject(alarm(onSuccess), alarm(onFailure));
1505 *
1506 * @param {function(*)} callback The function to wrap in an alarm.
1507 * @param {int} opt_ms Optional number of milliseconds to wait before raising
1508 * an alarm. Default is 5000 (5 seconds).
1509 * @return {function} If callback is a function then the return value will be
1510 * the wrapped callback. If callback is a string then the return value will
1511 * be a function that generates new wrapped callbacks.
1512 */
1513lib.f.alarm = function(callback, opt_ms) {
1514 var ms = opt_ms || 5 * 1000;
1515 var stack = lib.f.getStack(1);
1516
1517 return (function() {
1518 // This outer function is called immediately. It's here to capture a new
1519 // scope for the timeout variable.
1520
1521 // The 'timeout' variable is shared by this timeout function, and the
1522 // callback wrapper.
1523 var timeout = setTimeout(function() {
1524 var name = (typeof callback == 'string') ? name : callback.name;
1525 name = name ? (': ' + name) : '';
1526 console.warn('lib.f.alarm: timeout expired: ' + (ms / 1000) + 's' + name);
1527 console.log(stack);
1528 timeout = null;
1529 }, ms);
1530
1531 var wrapperGenerator = function(callback) {
1532 return function() {
1533 if (timeout) {
1534 clearTimeout(timeout);
1535 timeout = null;
1536 }
1537
1538 return callback.apply(null, arguments);
1539 }
1540 };
1541
1542 if (typeof callback == 'string')
1543 return wrapperGenerator;
1544
1545 return wrapperGenerator(callback);
1546 })();
1547};
1548
1549/**
1550 * Return the current call stack after skipping a given number of frames.
1551 *
1552 * This method is intended to be used for debugging only. It returns an
1553 * Object instead of an Array, because the console stringifies arrays by
1554 * default and that's not what we want.
1555 *
1556 * A typical call might look like...
1557 *
1558 * console.log('Something wicked this way came', lib.f.getStack());
1559 * // Notice the comma ^
1560 *
1561 * This would print the message to the js console, followed by an object
1562 * which can be clicked to reveal the stack.
1563 *
1564 * @param {number} opt_ignoreFrames The optional number of stack frames to
1565 * ignore. The actual 'getStack' call is always ignored.
1566 */
1567lib.f.getStack = function(opt_ignoreFrames) {
1568 var ignoreFrames = opt_ignoreFrames ? opt_ignoreFrames + 2 : 2;
1569
1570 var stackArray;
1571
1572 try {
1573 throw new Error();
1574 } catch (ex) {
1575 stackArray = ex.stack.split('\n');
1576 }
1577
1578 var stackObject = {};
1579 for (var i = ignoreFrames; i < stackArray.length; i++) {
1580 stackObject[i - ignoreFrames] = stackArray[i].replace(/^\s*at\s+/, '');
1581 }
1582
1583 return stackObject;
1584};
1585
1586/**
1587 * Divides the two numbers and floors the results, unless the remainder is less
1588 * than an incredibly small value, in which case it returns the ceiling.
1589 * This is useful when the number are truncated approximations of longer
1590 * values, and so doing division with these numbers yields a result incredibly
1591 * close to a whole number.
1592 *
1593 * @param {number} numerator
1594 * @param {number} denominator
1595 * @return {number}
1596 */
1597lib.f.smartFloorDivide = function(numerator, denominator) {
1598 var val = numerator / denominator;
1599 var ceiling = Math.ceil(val);
1600 if (ceiling - val < .0001) {
1601 return ceiling;
1602 } else {
1603 return Math.floor(val);
1604 }
1605};
1606// SOURCE FILE: libdot/js/lib_message_manager.js
1607// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
1608// Use of this source code is governed by a BSD-style license that can be
1609// found in the LICENSE file.
1610
1611'use strict';
1612
1613/**
1614 * MessageManager class handles internationalized strings.
1615 *
1616 * Note: chrome.i18n isn't sufficient because...
1617 * 1. There's a bug in chrome that makes it unavailable in iframes:
1618 * https://crbug.com/130200
1619 * 2. The client code may not be packaged in a Chrome extension.
1620 * 3. The client code may be part of a library packaged in a third-party
1621 * Chrome extension.
1622 *
1623 * @param {Array} languages List of languages to load, in the order they
1624 * should be loaded. Newer messages replace older ones. 'en' is
1625 * automatically added as the first language if it is not already present.
1626 */
1627lib.MessageManager = function(languages) {
1628 this.languages_ = languages.map(
1629 function(el) { return el.replace(/-/g, '_') });
1630
1631 if (this.languages_.indexOf('en') == -1)
1632 this.languages_.unshift('en');
1633
1634 this.messages = {};
1635};
1636
1637/**
1638 * Add message definitions to the message manager.
1639 *
1640 * This takes an object of the same format of a Chrome messages.json file. See
1641 * <https://developer.chrome.com/extensions/i18n-messages>.
1642 */
1643lib.MessageManager.prototype.addMessages = function(defs) {
1644 for (var key in defs) {
1645 var def = defs[key];
1646
1647 if (!def.placeholders) {
1648 this.messages[key] = def.message;
1649 } else {
1650 // Replace "$NAME$" placeholders with "$1", etc.
1651 this.messages[key] = def.message.replace(
1652 /\$([a-z][^\s\$]+)\$/ig,
1653 function(m, name) {
1654 return defs[key].placeholders[name.toLowerCase()].content;
1655 });
1656 }
1657 }
1658};
1659
1660/**
1661 * Load the first available language message bundle.
1662 *
1663 * @param {string} pattern A url pattern containing a "$1" where the locale
1664 * name should go.
1665 * @param {function(Array,Array)} onComplete Function to be called when loading
1666 * is complete. The two arrays are the list of successful and failed
1667 * locale names. If the first parameter is length 0, no locales were
1668 * loaded.
1669 */
1670lib.MessageManager.prototype.findAndLoadMessages = function(
1671 pattern, onComplete) {
1672 var languages = this.languages_.concat();
1673 var loaded = [];
1674 var failed = [];
1675
1676 function onLanguageComplete(state) {
1677 if (state) {
1678 loaded = languages.shift();
1679 } else {
1680 failed = languages.shift();
1681 }
1682
1683 if (languages.length) {
1684 tryNextLanguage();
1685 } else {
1686 onComplete(loaded, failed);
1687 }
1688 }
1689
1690 var tryNextLanguage = function() {
1691 this.loadMessages(this.replaceReferences(pattern, languages),
1692 onLanguageComplete.bind(this, true),
1693 onLanguageComplete.bind(this, false));
1694 }.bind(this);
1695
1696 tryNextLanguage();
1697};
1698
1699/**
1700 * Load messages from a messages.json file.
1701 */
1702lib.MessageManager.prototype.loadMessages = function(
1703 url, onSuccess, opt_onError) {
1704 var xhr = new XMLHttpRequest();
1705
1706 xhr.onloadend = function() {
1707 if (xhr.status != 200) {
1708 if (opt_onError)
1709 opt_onError(xhr.status);
1710
1711 return;
1712 }
1713
1714 this.addMessages(JSON.parse(xhr.responseText));
1715 onSuccess();
1716 }.bind(this);
1717
1718 xhr.open('GET', url);
1719 xhr.send();
1720};
1721
1722/**
1723 * Replace $1...$n references with the elements of the args array.
1724 *
1725 * @param {string} msg String containing the message and argument references.
1726 * @param {Array} args Array containing the argument values.
1727 */
1728lib.MessageManager.replaceReferences = function(msg, args) {
1729 return msg.replace(/\$(\d+)/g, function (m, index) {
1730 return args[index - 1];
1731 });
1732};
1733
1734/**
1735 * Per-instance copy of replaceReferences.
1736 */
1737lib.MessageManager.prototype.replaceReferences =
1738 lib.MessageManager.replaceReferences;
1739
1740/**
1741 * Get a message by name, optionally replacing arguments too.
1742 *
1743 * @param {string} msgname String containing the name of the message to get.
1744 * @param {Array} opt_args Optional array containing the argument values.
1745 * @param {string} opt_default Optional value to return if the msgname is not
1746 * found. Returns the message name by default.
1747 */
1748lib.MessageManager.prototype.get = function(msgname, opt_args, opt_default) {
1749 var message;
1750
1751 if (msgname in this.messages) {
1752 message = this.messages[msgname];
1753
1754 } else {
1755 if (window.chrome.i18n)
1756 message = chrome.i18n.getMessage(msgname);
1757
1758 if (!message) {
1759 console.warn('Unknown message: ' + msgname);
1760 return (typeof opt_default == 'undefined') ? msgname : opt_default;
1761 }
1762 }
1763
1764 if (!opt_args)
1765 return message;
1766
1767 if (!(opt_args instanceof Array))
1768 opt_args = [opt_args];
1769
1770 return this.replaceReferences(message, opt_args);
1771};
1772
1773/**
1774 * Process all of the "i18n" html attributes found in a given dom fragment.
1775 *
1776 * Each i18n attribute should contain a JSON object. The keys are taken to
1777 * be attribute names, and the values are message names.
1778 *
1779 * If the JSON object has a "_" (underscore) key, it's value is used as the
1780 * textContent of the element.
1781 *
1782 * Message names can refer to other attributes on the same element with by
1783 * prefixing with a dollar sign. For example...
1784 *
1785 * <button id='send-button'
1786 * i18n='{"aria-label": "$id", "_": "SEND_BUTTON_LABEL"}'
1787 * ></button>
1788 *
1789 * The aria-label message name will be computed as "SEND_BUTTON_ARIA_LABEL".
1790 * Notice that the "id" attribute was appended to the target attribute, and
1791 * the result converted to UPPER_AND_UNDER style.
1792 */
1793lib.MessageManager.prototype.processI18nAttributes = function(dom) {
1794 // Convert the "lower-and-dashes" attribute names into
1795 // "UPPER_AND_UNDER" style.
1796 function thunk(str) { return str.replace(/-/g, '_').toUpperCase() }
1797
1798 var nodes = dom.querySelectorAll('[i18n]');
1799
1800 for (var i = 0; i < nodes.length; i++) {
1801 var node = nodes[i];
1802 var i18n = node.getAttribute('i18n');
1803
1804 if (!i18n)
1805 continue;
1806
1807 try {
1808 i18n = JSON.parse(i18n);
1809 } catch (ex) {
1810 console.error('Can\'t parse ' + node.tagName + '#' + node.id + ': ' +
1811 i18n);
1812 throw ex;
1813 }
1814
1815 for (var key in i18n) {
1816 var msgname = i18n[key];
1817 if (msgname.substr(0, 1) == '$')
1818 msgname = thunk(node.getAttribute(msgname.substr(1)) + '_' + key);
1819
1820 var msg = this.get(msgname);
1821 if (key == '_') {
1822 node.textContent = msg;
1823 } else {
1824 node.setAttribute(key, msg);
1825 }
1826 }
1827 }
1828};
1829// SOURCE FILE: libdot/js/lib_preference_manager.js
1830// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
1831// Use of this source code is governed by a BSD-style license that can be
1832// found in the LICENSE file.
1833
1834'use strict';
1835
1836/**
1837 * Constructor for lib.PreferenceManager objects.
1838 *
1839 * These objects deal with persisting changes to stable storage and notifying
1840 * consumers when preferences change.
1841 *
1842 * It is intended that the backing store could be something other than HTML5
1843 * storage, but there aren't any use cases at the moment. In the future there
1844 * may be a chrome api to store sync-able name/value pairs, and we'd want
1845 * that.
1846 *
1847 * @param {lib.Storage.*} storage The storage object to use as a backing
1848 * store.
1849 * @param {string} opt_prefix The optional prefix to be used for all preference
1850 * names. The '/' character should be used to separate levels of hierarchy,
1851 * if you're going to have that kind of thing. If provided, the prefix
1852 * should start with a '/'. If not provided, it defaults to '/'.
1853 */
1854lib.PreferenceManager = function(storage, opt_prefix) {
1855 this.storage = storage;
1856 this.storageObserver_ = this.onStorageChange_.bind(this);
1857
1858 this.isActive_ = false;
1859 this.activate();
1860
1861 this.trace = false;
1862
1863 var prefix = opt_prefix || '/';
1864 if (prefix.substr(prefix.length - 1) != '/')
1865 prefix += '/';
1866
1867 this.prefix = prefix;
1868
1869 this.prefRecords_ = {};
1870 this.globalObservers_ = [];
1871
1872 this.childFactories_ = {};
1873
1874 // Map of list-name to {map of child pref managers}
1875 // As in...
1876 //
1877 // this.childLists_ = {
1878 // 'profile-ids': {
1879 // 'one': PreferenceManager,
1880 // 'two': PreferenceManager,
1881 // ...
1882 // },
1883 //
1884 // 'frob-ids': {
1885 // ...
1886 // }
1887 // }
1888 this.childLists_ = {};
1889};
1890
1891/**
1892 * Used internally to indicate that the current value of the preference should
1893 * be taken from the default value defined with the preference.
1894 *
1895 * Equality tests against this value MUST use '===' or '!==' to be accurate.
1896 */
1897lib.PreferenceManager.prototype.DEFAULT_VALUE = new String('DEFAULT');
1898
1899/**
1900 * An individual preference.
1901 *
1902 * These objects are managed by the PreferenceManager, you shouldn't need to
1903 * handle them directly.
1904 */
1905lib.PreferenceManager.Record = function(name, defaultValue) {
1906 this.name = name;
1907 this.defaultValue = defaultValue;
1908 this.currentValue = this.DEFAULT_VALUE;
1909 this.observers = [];
1910};
1911
1912/**
1913 * A local copy of the DEFAULT_VALUE constant to make it less verbose.
1914 */
1915lib.PreferenceManager.Record.prototype.DEFAULT_VALUE =
1916 lib.PreferenceManager.prototype.DEFAULT_VALUE;
1917
1918/**
1919 * Register a callback to be invoked when this preference changes.
1920 *
1921 * @param {function(value, string, lib.PreferenceManager} observer The function
1922 * to invoke. It will receive the new value, the name of the preference,
1923 * and a reference to the PreferenceManager as parameters.
1924 */
1925lib.PreferenceManager.Record.prototype.addObserver = function(observer) {
1926 this.observers.push(observer);
1927};
1928
1929/**
1930 * Unregister an observer callback.
1931 *
1932 * @param {function} observer A previously registered callback.
1933 */
1934lib.PreferenceManager.Record.prototype.removeObserver = function(observer) {
1935 var i = this.observers.indexOf(observer);
1936 if (i >= 0)
1937 this.observers.splice(i, 1);
1938};
1939
1940/**
1941 * Fetch the value of this preference.
1942 */
1943lib.PreferenceManager.Record.prototype.get = function() {
1944 if (this.currentValue === this.DEFAULT_VALUE) {
1945 if (/^(string|number)$/.test(typeof this.defaultValue))
1946 return this.defaultValue;
1947
1948 if (typeof this.defaultValue == 'object') {
1949 // We want to return a COPY of the default value so that users can
1950 // modify the array or object without changing the default value.
1951 return JSON.parse(JSON.stringify(this.defaultValue));
1952 }
1953
1954 return this.defaultValue;
1955 }
1956
1957 return this.currentValue;
1958};
1959
1960/**
1961 * Stop this preference manager from tracking storage changes.
1962 *
1963 * Call this if you're going to swap out one preference manager for another so
1964 * that you don't get notified about irrelevant changes.
1965 */
1966lib.PreferenceManager.prototype.deactivate = function() {
1967 if (!this.isActive_)
1968 throw new Error('Not activated');
1969
1970 this.isActive_ = false;
1971 this.storage.removeObserver(this.storageObserver_);
1972};
1973
1974/**
1975 * Start tracking storage changes.
1976 *
1977 * If you previously deactivated this preference manager, you can reactivate it
1978 * with this method. You don't need to call this at initialization time, as
1979 * it's automatically called as part of the constructor.
1980 */
1981lib.PreferenceManager.prototype.activate = function() {
1982 if (this.isActive_)
1983 throw new Error('Already activated');
1984
1985 this.isActive_ = true;
1986 this.storage.addObserver(this.storageObserver_);
1987};
1988
1989/**
1990 * Read the backing storage for these preferences.
1991 *
1992 * You should do this once at initialization time to prime the local cache
1993 * of preference values. The preference manager will monitor the backing
1994 * storage for changes, so you should not need to call this more than once.
1995 *
1996 * This function recursively reads storage for all child preference managers as
1997 * well.
1998 *
1999 * This function is asynchronous, if you need to read preference values, you
2000 * *must* wait for the callback.
2001 *
2002 * @param {function()} opt_callback Optional function to invoke when the read
2003 * has completed.
2004 */
2005lib.PreferenceManager.prototype.readStorage = function(opt_callback) {
2006 var pendingChildren = 0;
2007
2008 function onChildComplete() {
2009 if (--pendingChildren == 0 && opt_callback)
2010 opt_callback();
2011 }
2012
2013 var keys = Object.keys(this.prefRecords_).map(
2014 function(el) { return this.prefix + el }.bind(this));
2015
2016 if (this.trace)
2017 console.log('Preferences read: ' + this.prefix);
2018
2019 this.storage.getItems(keys, function(items) {
2020 var prefixLength = this.prefix.length;
2021
2022 for (var key in items) {
2023 var value = items[key];
2024 var name = key.substr(prefixLength);
2025 var needSync = (name in this.childLists_ &&
2026 (JSON.stringify(value) !=
2027 JSON.stringify(this.prefRecords_[name].currentValue)));
2028
2029 this.prefRecords_[name].currentValue = value;
2030
2031 if (needSync) {
2032 pendingChildren++;
2033 this.syncChildList(name, onChildComplete);
2034 }
2035 }
2036
2037 if (pendingChildren == 0 && opt_callback)
2038 setTimeout(opt_callback);
2039 }.bind(this));
2040};
2041
2042/**
2043 * Define a preference.
2044 *
2045 * This registers a name, default value, and onChange handler for a preference.
2046 *
2047 * @param {string} name The name of the preference. This will be prefixed by
2048 * the prefix of this PreferenceManager before written to local storage.
2049 * @param {string|number|boolean|Object|Array|null} value The default value of
2050 * this preference. Anything that can be represented in JSON is a valid
2051 * default value.
2052 * @param {function(value, string, lib.PreferenceManager} opt_observer A
2053 * function to invoke when the preference changes. It will receive the new
2054 * value, the name of the preference, and a reference to the
2055 * PreferenceManager as parameters.
2056 */
2057lib.PreferenceManager.prototype.definePreference = function(
2058 name, value, opt_onChange) {
2059
2060 var record = this.prefRecords_[name];
2061 if (record) {
2062 this.changeDefault(name, value);
2063 } else {
2064 record = this.prefRecords_[name] =
2065 new lib.PreferenceManager.Record(name, value);
2066 }
2067
2068 if (opt_onChange)
2069 record.addObserver(opt_onChange);
2070};
2071
2072/**
2073 * Define multiple preferences with a single function call.
2074 *
2075 * @param {Array} defaults An array of 3-element arrays. Each three element
2076 * array should contain the [key, value, onChange] parameters for a
2077 * preference.
2078 */
2079lib.PreferenceManager.prototype.definePreferences = function(defaults) {
2080 for (var i = 0; i < defaults.length; i++) {
2081 this.definePreference(defaults[i][0], defaults[i][1], defaults[i][2]);
2082 }
2083};
2084
2085/**
2086 * Define an ordered list of child preferences.
2087 *
2088 * Child preferences are different from just storing an array of JSON objects
2089 * in that each child is an instance of a preference manager. This means you
2090 * can observe changes to individual child preferences, and get some validation
2091 * that you're not reading or writing to an undefined child preference value.
2092 *
2093 * @param {string} listName A name for the list of children. This must be
2094 * unique in this preference manager. The listName will become a
2095 * preference on this PreferenceManager used to store the ordered list of
2096 * child ids. It is also used in get/add/remove operations to identify the
2097 * list of children to operate on.
2098 * @param {function} childFactory A function that will be used to generate
2099 * instances of these children. The factory function will receive the
2100 * parent lib.PreferenceManager object and a unique id for the new child
2101 * preferences.
2102 */
2103lib.PreferenceManager.prototype.defineChildren = function(
2104 listName, childFactory) {
2105
2106 // Define a preference to hold the ordered list of child ids.
2107 this.definePreference(listName, [],
2108 this.onChildListChange_.bind(this, listName));
2109 this.childFactories_[listName] = childFactory;
2110 this.childLists_[listName] = {};
2111};
2112
2113/**
2114 * Register to observe preference changes.
2115 *
2116 * @param {Function} global A callback that will happen for every preference.
2117 * Pass null if you don't need one.
2118 * @param {Object} map A map of preference specific callbacks. Pass null if
2119 * you don't need any.
2120 */
2121lib.PreferenceManager.prototype.addObservers = function(global, map) {
2122 if (global && typeof global != 'function')
2123 throw new Error('Invalid param: globals');
2124
2125 if (global)
2126 this.globalObservers_.push(global);
2127
2128 if (!map)
2129 return;
2130
2131 for (var name in map) {
2132 if (!(name in this.prefRecords_))
2133 throw new Error('Unknown preference: ' + name);
2134
2135 this.prefRecords_[name].addObserver(map[name]);
2136 }
2137};
2138
2139/**
2140 * Dispatch the change observers for all known preferences.
2141 *
2142 * It may be useful to call this after readStorage completes, in order to
2143 * get application state in sync with user preferences.
2144 *
2145 * This can be used if you've changed a preference manager out from under
2146 * a live object, for example when switching to a different prefix.
2147 */
2148lib.PreferenceManager.prototype.notifyAll = function() {
2149 for (var name in this.prefRecords_) {
2150 this.notifyChange_(name);
2151 }
2152};
2153
2154/**
2155 * Notify the change observers for a given preference.
2156 *
2157 * @param {string} name The name of the preference that changed.
2158 */
2159lib.PreferenceManager.prototype.notifyChange_ = function(name) {
2160 var record = this.prefRecords_[name];
2161 if (!record)
2162 throw new Error('Unknown preference: ' + name);
2163
2164 var currentValue = record.get();
2165
2166 for (var i = 0; i < this.globalObservers_.length; i++)
2167 this.globalObservers_[i](name, currentValue);
2168
2169 for (var i = 0; i < record.observers.length; i++) {
2170 record.observers[i](currentValue, name, this);
2171 }
2172};
2173
2174/**
2175 * Create a new child PreferenceManager for the given child list.
2176 *
2177 * The optional hint parameter is an opaque prefix added to the auto-generated
2178 * unique id for this child. Your child factory can parse out the prefix
2179 * and use it.
2180 *
2181 * @param {string} listName The child list to create the new instance from.
2182 * @param {string} opt_hint Optional hint to include in the child id.
2183 * @param {string} opt_id Optional id to override the generated id.
2184 */
2185lib.PreferenceManager.prototype.createChild = function(listName, opt_hint,
2186 opt_id) {
2187 var ids = this.get(listName);
2188 var id;
2189
2190 if (opt_id) {
2191 id = opt_id;
2192 if (ids.indexOf(id) != -1)
2193 throw new Error('Duplicate child: ' + listName + ': ' + id);
2194
2195 } else {
2196 // Pick a random, unique 4-digit hex identifier for the new profile.
2197 while (!id || ids.indexOf(id) != -1) {
2198 id = Math.floor(Math.random() * 0xffff + 1).toString(16);
2199 id = lib.f.zpad(id, 4);
2200 if (opt_hint)
2201 id = opt_hint + ':' + id;
2202 }
2203 }
2204
2205 var childManager = this.childFactories_[listName](this, id);
2206 childManager.trace = this.trace;
2207 childManager.resetAll();
2208
2209 this.childLists_[listName][id] = childManager;
2210
2211 ids.push(id);
2212 this.set(listName, ids);
2213
2214 return childManager;
2215};
2216
2217/**
2218 * Remove a child preferences instance.
2219 *
2220 * Removes a child preference manager and clears any preferences stored in it.
2221 *
2222 * @param {string} listName The name of the child list containing the child to
2223 * remove.
2224 * @param {string} id The child ID.
2225 */
2226lib.PreferenceManager.prototype.removeChild = function(listName, id) {
2227 var prefs = this.getChild(listName, id);
2228 prefs.resetAll();
2229
2230 var ids = this.get(listName);
2231 var i = ids.indexOf(id);
2232 if (i != -1) {
2233 ids.splice(i, 1);
2234 this.set(listName, ids);
2235 }
2236
2237 delete this.childLists_[listName][id];
2238};
2239
2240/**
2241 * Return a child PreferenceManager instance for a given id.
2242 *
2243 * If the child list or child id is not known this will return the specified
2244 * default value or throw an exception if no default value is provided.
2245 *
2246 * @param {string} listName The child list to look in.
2247 * @param {string} id The child ID.
2248 * @param {*} opt_default The optional default value to return if the child
2249 * is not found.
2250 */
2251lib.PreferenceManager.prototype.getChild = function(listName, id, opt_default) {
2252 if (!(listName in this.childLists_))
2253 throw new Error('Unknown child list: ' + listName);
2254
2255 var childList = this.childLists_[listName];
2256 if (!(id in childList)) {
2257 if (typeof opt_default == 'undefined')
2258 throw new Error('Unknown "' + listName + '" child: ' + id);
2259
2260 return opt_default;
2261 }
2262
2263 return childList[id];
2264};
2265
2266/**
2267 * Calculate the difference between two lists of child ids.
2268 *
2269 * Given two arrays of child ids, this function will return an object
2270 * with "added", "removed", and "common" properties. Each property is
2271 * a map of child-id to `true`. For example, given...
2272 *
2273 * a = ['child-x', 'child-y']
2274 * b = ['child-y']
2275 *
2276 * diffChildLists(a, b) =>
2277 * { added: { 'child-x': true }, removed: {}, common: { 'child-y': true } }
2278 *
2279 * The added/removed properties assume that `a` is the current list.
2280 *
2281 * @param {Array[string]} a The most recent list of child ids.
2282 * @param {Array[string]} b An older list of child ids.
2283 * @return {Object} An object with added/removed/common properties.
2284 */
2285lib.PreferenceManager.diffChildLists = function(a, b) {
2286 var rv = {
2287 added: {},
2288 removed: {},
2289 common: {},
2290 };
2291
2292 for (var i = 0; i < a.length; i++) {
2293 if (b.indexOf(a[i]) != -1) {
2294 rv.common[a[i]] = true;
2295 } else {
2296 rv.added[a[i]] = true;
2297 }
2298 }
2299
2300 for (var i = 0; i < b.length; i++) {
2301 if ((b[i] in rv.added) || (b[i] in rv.common))
2302 continue;
2303
2304 rv.removed[b[i]] = true;
2305 }
2306
2307 return rv;
2308};
2309
2310/**
2311 * Synchronize a list of child PreferenceManagers instances with the current
2312 * list stored in prefs.
2313 *
2314 * This will instantiate any missing managers and read current preference values
2315 * from storage. Any active managers that no longer appear in preferences will
2316 * be deleted.
2317 *
2318 * @param {string} listName The child list to synchronize.
2319 * @param {function()} opt_callback Optional function to invoke when the sync
2320 * is complete.
2321 */
2322lib.PreferenceManager.prototype.syncChildList = function(
2323 listName, opt_callback) {
2324
2325 var pendingChildren = 0;
2326 function onChildStorage() {
2327 if (--pendingChildren == 0 && opt_callback)
2328 opt_callback();
2329 }
2330
2331 // The list of child ids that we *should* have a manager for.
2332 var currentIds = this.get(listName);
2333
2334 // The known managers at the start of the sync. Any manager still in this
2335 // list at the end should be discarded.
2336 var oldIds = Object.keys(this.childLists_[listName]);
2337
2338 var rv = lib.PreferenceManager.diffChildLists(currentIds, oldIds);
2339
2340 for (var i = 0; i < currentIds.length; i++) {
2341 var id = currentIds[i];
2342
2343 var managerIndex = oldIds.indexOf(id);
2344 if (managerIndex >= 0)
2345 oldIds.splice(managerIndex, 1);
2346
2347 if (!this.childLists_[listName][id]) {
2348 var childManager = this.childFactories_[listName](this, id);
2349 if (!childManager) {
2350 console.warn('Unable to restore child: ' + listName + ': ' + id);
2351 continue;
2352 }
2353
2354 childManager.trace = this.trace;
2355 this.childLists_[listName][id] = childManager;
2356 pendingChildren++;
2357 childManager.readStorage(onChildStorage);
2358 }
2359 }
2360
2361 for (var i = 0; i < oldIds.length; i++) {
2362 delete this.childLists_[listName][oldIds[i]];
2363 }
2364
2365 if (!pendingChildren && opt_callback)
2366 setTimeout(opt_callback);
2367};
2368
2369/**
2370 * Reset a preference to its default state.
2371 *
2372 * This will dispatch the onChange handler if the preference value actually
2373 * changes.
2374 *
2375 * @param {string} name The preference to reset.
2376 */
2377lib.PreferenceManager.prototype.reset = function(name) {
2378 var record = this.prefRecords_[name];
2379 if (!record)
2380 throw new Error('Unknown preference: ' + name);
2381
2382 this.storage.removeItem(this.prefix + name);
2383
2384 if (record.currentValue !== this.DEFAULT_VALUE) {
2385 record.currentValue = this.DEFAULT_VALUE;
2386 this.notifyChange_(name);
2387 }
2388};
2389
2390/**
2391 * Reset all preferences back to their default state.
2392 */
2393lib.PreferenceManager.prototype.resetAll = function() {
2394 var changed = [];
2395
2396 for (var listName in this.childLists_) {
2397 var childList = this.childLists_[listName];
2398 for (var id in childList) {
2399 childList[id].resetAll();
2400 }
2401 }
2402
2403 for (var name in this.prefRecords_) {
2404 if (this.prefRecords_[name].currentValue !== this.DEFAULT_VALUE) {
2405 this.prefRecords_[name].currentValue = this.DEFAULT_VALUE;
2406 changed.push(name);
2407 }
2408 }
2409
2410 var keys = Object.keys(this.prefRecords_).map(function(el) {
2411 return this.prefix + el;
2412 }.bind(this));
2413
2414 this.storage.removeItems(keys);
2415
2416 changed.forEach(this.notifyChange_.bind(this));
2417};
2418
2419/**
2420 * Return true if two values should be considered not-equal.
2421 *
2422 * If both values are the same scalar type and compare equal this function
2423 * returns false (no difference), otherwise return true.
2424 *
2425 * This is used in places where we want to check if a preference has changed.
2426 * Rather than take the time to compare complex values we just consider them
2427 * to always be different.
2428 *
2429 * @param {*} a A value to compare.
2430 * @param {*} b A value to compare.
2431 */
2432lib.PreferenceManager.prototype.diff = function(a, b) {
2433 // If the types are different, or the type is not a simple primitive one.
2434 if ((typeof a) !== (typeof b) ||
2435 !(/^(undefined|boolean|number|string)$/.test(typeof a))) {
2436 return true;
2437 }
2438
2439 return a !== b;
2440};
2441
2442/**
2443 * Change the default value of a preference.
2444 *
2445 * This is useful when subclassing preference managers.
2446 *
2447 * The function does not alter the current value of the preference, unless
2448 * it has the old default value. When that happens, the change observers
2449 * will be notified.
2450 *
2451 * @param {string} name The name of the parameter to change.
2452 * @param {*} newValue The new default value for the preference.
2453 */
2454lib.PreferenceManager.prototype.changeDefault = function(name, newValue) {
2455 var record = this.prefRecords_[name];
2456 if (!record)
2457 throw new Error('Unknown preference: ' + name);
2458
2459 if (!this.diff(record.defaultValue, newValue)) {
2460 // Default value hasn't changed.
2461 return;
2462 }
2463
2464 if (record.currentValue !== this.DEFAULT_VALUE) {
2465 // This pref has a specific value, just change the default and we're done.
2466 record.defaultValue = newValue;
2467 return;
2468 }
2469
2470 record.defaultValue = newValue;
2471
2472 this.notifyChange_(name);
2473};
2474
2475/**
2476 * Change the default value of multiple preferences.
2477 *
2478 * @param {Object} map A map of name -> value pairs specifying the new default
2479 * values.
2480 */
2481lib.PreferenceManager.prototype.changeDefaults = function(map) {
2482 for (var key in map) {
2483 this.changeDefault(key, map[key]);
2484 }
2485};
2486
2487/**
2488 * Set a preference to a specific value.
2489 *
2490 * This will dispatch the onChange handler if the preference value actually
2491 * changes.
2492 *
2493 * @param {string} key The preference to set.
2494 * @param {*} value The value to set. Anything that can be represented in
2495 * JSON is a valid value.
2496 */
2497lib.PreferenceManager.prototype.set = function(name, newValue) {
2498 var record = this.prefRecords_[name];
2499 if (!record)
2500 throw new Error('Unknown preference: ' + name);
2501
2502 var oldValue = record.get();
2503
2504 if (!this.diff(oldValue, newValue))
2505 return;
2506
2507 if (this.diff(record.defaultValue, newValue)) {
2508 record.currentValue = newValue;
2509 this.storage.setItem(this.prefix + name, newValue);
2510 } else {
2511 record.currentValue = this.DEFAULT_VALUE;
2512 this.storage.removeItem(this.prefix + name);
2513 }
2514
2515 // We need to manually send out the notification on this instance. If we
2516 // The storage event won't fire a notification because we've already changed
2517 // the currentValue, so it won't see a difference. If we delayed changing
2518 // currentValue until the storage event, a pref read immediately after a write
2519 // would return the previous value.
2520 //
2521 // The notification is in a timeout so clients don't accidentally depend on
2522 // a synchronous notification.
2523 setTimeout(this.notifyChange_.bind(this, name), 0);
2524};
2525
2526/**
2527 * Get the value of a preference.
2528 *
2529 * @param {string} key The preference to get.
2530 */
2531lib.PreferenceManager.prototype.get = function(name) {
2532 var record = this.prefRecords_[name];
2533 if (!record)
2534 throw new Error('Unknown preference: ' + name);
2535
2536 return record.get();
2537};
2538
2539/**
2540 * Return all non-default preferences as a JSON object.
2541 *
2542 * This includes any nested preference managers as well.
2543 */
2544lib.PreferenceManager.prototype.exportAsJson = function() {
2545 var rv = {};
2546
2547 for (var name in this.prefRecords_) {
2548 if (name in this.childLists_) {
2549 rv[name] = [];
2550 var childIds = this.get(name);
2551 for (var i = 0; i < childIds.length; i++) {
2552 var id = childIds[i];
2553 rv[name].push({id: id, json: this.getChild(name, id).exportAsJson()});
2554 }
2555
2556 } else {
2557 var record = this.prefRecords_[name];
2558 if (record.currentValue != this.DEFAULT_VALUE)
2559 rv[name] = record.currentValue;
2560 }
2561 }
2562
2563 return rv;
2564};
2565
2566/**
2567 * Import a JSON blob of preferences previously generated with exportAsJson.
2568 *
2569 * This will create nested preference managers as well.
2570 */
2571lib.PreferenceManager.prototype.importFromJson = function(json) {
2572 for (var name in json) {
2573 if (name in this.childLists_) {
2574 var childList = json[name];
2575 for (var i = 0; i < childList.length; i++) {
2576 var id = childList[i].id;
2577
2578 var childPrefManager = this.childLists_[name][id];
2579 if (!childPrefManager)
2580 childPrefManager = this.createChild(name, null, id);
2581
2582 childPrefManager.importFromJson(childList[i].json);
2583 }
2584
2585 } else {
2586 this.set(name, json[name]);
2587 }
2588 }
2589};
2590
2591/**
2592 * Called when one of the child list preferences changes.
2593 */
2594lib.PreferenceManager.prototype.onChildListChange_ = function(listName) {
2595 this.syncChildList(listName);
2596};
2597
2598/**
2599 * Called when a key in the storage changes.
2600 */
2601lib.PreferenceManager.prototype.onStorageChange_ = function(map) {
2602 for (var key in map) {
2603 if (this.prefix) {
2604 if (key.lastIndexOf(this.prefix, 0) != 0)
2605 continue;
2606 }
2607
2608 var name = key.substr(this.prefix.length);
2609
2610 if (!(name in this.prefRecords_)) {
2611 // Sometimes we'll get notified about prefs that are no longer defined.
2612 continue;
2613 }
2614
2615 var record = this.prefRecords_[name];
2616
2617 var newValue = map[key].newValue;
2618 var currentValue = record.currentValue;
2619 if (currentValue === record.DEFAULT_VALUE)
2620 currentValue = (void 0);
2621
2622 if (this.diff(currentValue, newValue)) {
2623 if (typeof newValue == 'undefined') {
2624 record.currentValue = record.DEFAULT_VALUE;
2625 } else {
2626 record.currentValue = newValue;
2627 }
2628
2629 this.notifyChange_(name);
2630 }
2631 }
2632};
2633// SOURCE FILE: libdot/js/lib_resource.js
2634// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2635// Use of this source code is governed by a BSD-style license that can be
2636// found in the LICENSE file.
2637
2638'use strict';
2639
2640/**
2641 * Storage for canned resources.
2642 *
2643 * These are usually non-JavaScript things that are collected during a build
2644 * step and converted into a series of 'lib.resource.add(...)' calls. See
2645 * the "@resource" directive from libdot/bin/concat.sh for the canonical use
2646 * case.
2647 *
2648 * This is global storage, so you should prefix your resource names to avoid
2649 * collisions.
2650 */
2651lib.resource = {
2652 resources_: {}
2653};
2654
2655/**
2656 * Add a resource.
2657 *
2658 * @param {string} name A name for the resource. You should prefix this to
2659 * avoid collisions with resources from a shared library.
2660 * @param {string} type A mime type for the resource, or "raw" if not
2661 * applicable.
2662 * @param {*} data The value of the resource.
2663 */
2664lib.resource.add = function(name, type, data) {
2665 lib.resource.resources_[name] = {
2666 type: type,
2667 name: name,
2668 data: data
2669 };
2670};
2671
2672/**
2673 * Retrieve a resource record.
2674 *
2675 * The resource data is stored on the "data" property of the returned object.
2676 *
2677 * @param {string} name The name of the resource to get.
2678 * @param {*} opt_defaultValue The optional value to return if the resource is
2679 * not defined.
2680 * @return {object} An object with "type", "name", and "data" properties.
2681 */
2682lib.resource.get = function(name, opt_defaultValue) {
2683 if (!(name in lib.resource.resources_)) {
2684 if (typeof opt_defaultValue == 'undefined')
2685 throw 'Unknown resource: ' + name;
2686
2687 return opt_defaultValue;
2688 }
2689
2690 return lib.resource.resources_[name];
2691};
2692
2693/**
2694 * Retrieve resource data.
2695 *
2696 * @param {string} name The name of the resource to get.
2697 * @param {*} opt_defaultValue The optional value to return if the resource is
2698 * not defined.
2699 * @return {*} The resource data.
2700 */
2701lib.resource.getData = function(name, opt_defaultValue) {
2702 if (!(name in lib.resource.resources_)) {
2703 if (typeof opt_defaultValue == 'undefined')
2704 throw 'Unknown resource: ' + name;
2705
2706 return opt_defaultValue;
2707 }
2708
2709 return lib.resource.resources_[name].data;
2710};
2711
2712/**
2713 * Retrieve resource as a data: url.
2714 *
2715 * @param {string} name The name of the resource to get.
2716 * @param {*} opt_defaultValue The optional value to return if the resource is
2717 * not defined.
2718 * @return {*} A data: url encoded version of the resource.
2719 */
2720lib.resource.getDataUrl = function(name, opt_defaultValue) {
2721 var resource = lib.resource.get(name, opt_defaultValue);
2722 return 'data:' + resource.type + ',' + resource.data;
2723};
2724// SOURCE FILE: libdot/js/lib_storage.js
2725// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2726// Use of this source code is governed by a BSD-style license that can be
2727// found in the LICENSE file.
2728
2729'use strict';
2730
2731/**
2732 * Namespace for implementations of persistent, possibly cloud-backed
2733 * storage.
2734 */
2735lib.Storage = new Object();
2736// SOURCE FILE: libdot/js/lib_storage_chrome.js
2737// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2738// Use of this source code is governed by a BSD-style license that can be
2739// found in the LICENSE file.
2740
2741'use strict';
2742
2743/**
2744 * chrome.storage based class with an async interface that is interchangeable
2745 * with other lib.Storage.* implementations.
2746 */
2747lib.Storage.Chrome = function(storage) {
2748 this.storage_ = storage;
2749 this.observers_ = [];
2750
2751 chrome.storage.onChanged.addListener(this.onChanged_.bind(this));
2752};
2753
2754/**
2755 * Called by the storage implementation when the storage is modified.
2756 */
2757lib.Storage.Chrome.prototype.onChanged_ = function(changes, areaname) {
2758 if (chrome.storage[areaname] != this.storage_)
2759 return;
2760
2761 for (var i = 0; i < this.observers_.length; i++) {
2762 this.observers_[i](changes);
2763 }
2764};
2765
2766/**
2767 * Register a function to observe storage changes.
2768 *
2769 * @param {function(map)} callback The function to invoke when the storage
2770 * changes.
2771 */
2772lib.Storage.Chrome.prototype.addObserver = function(callback) {
2773 this.observers_.push(callback);
2774};
2775
2776/**
2777 * Unregister a change observer.
2778 *
2779 * @param {function} observer A previously registered callback.
2780 */
2781lib.Storage.Chrome.prototype.removeObserver = function(callback) {
2782 var i = this.observers_.indexOf(callback);
2783 if (i != -1)
2784 this.observers_.splice(i, 1);
2785};
2786
2787/**
2788 * Delete everything in this storage.
2789 *
2790 * @param {function(map)} callback The function to invoke when the delete
2791 * has completed.
2792 */
2793lib.Storage.Chrome.prototype.clear = function(opt_callback) {
2794 this.storage_.clear();
2795
2796 if (opt_callback)
2797 setTimeout(opt_callback, 0);
2798};
2799
2800/**
2801 * Return the current value of a storage item.
2802 *
2803 * @param {string} key The key to look up.
2804 * @param {function(value) callback The function to invoke when the value has
2805 * been retrieved.
2806 */
2807lib.Storage.Chrome.prototype.getItem = function(key, callback) {
2808 this.storage_.get(key, callback);
2809};
2810/**
2811 * Fetch the values of multiple storage items.
2812 *
2813 * @param {Array} keys The keys to look up.
2814 * @param {function(map) callback The function to invoke when the values have
2815 * been retrieved.
2816 */
2817
2818lib.Storage.Chrome.prototype.getItems = function(keys, callback) {
2819 this.storage_.get(keys, callback);
2820};
2821
2822/**
2823 * Set a value in storage.
2824 *
2825 * @param {string} key The key for the value to be stored.
2826 * @param {*} value The value to be stored. Anything that can be serialized
2827 * with JSON is acceptable.
2828 * @param {function()} opt_callback Optional function to invoke when the
2829 * set is complete. You don't have to wait for the set to complete in order
2830 * to read the value, since the local cache is updated synchronously.
2831 */
2832lib.Storage.Chrome.prototype.setItem = function(key, value, opt_callback) {
2833 var obj = {};
2834 obj[key] = value;
2835 this.storage_.set(obj, opt_callback);
2836};
2837
2838/**
2839 * Set multiple values in storage.
2840 *
2841 * @param {Object} map A map of key/values to set in storage.
2842 * @param {function()} opt_callback Optional function to invoke when the
2843 * set is complete. You don't have to wait for the set to complete in order
2844 * to read the value, since the local cache is updated synchronously.
2845 */
2846lib.Storage.Chrome.prototype.setItems = function(obj, opt_callback) {
2847 this.storage_.set(obj, opt_callback);
2848};
2849
2850/**
2851 * Remove an item from storage.
2852 *
2853 * @param {string} key The key to be removed.
2854 * @param {function()} opt_callback Optional function to invoke when the
2855 * remove is complete. You don't have to wait for the set to complete in
2856 * order to read the value, since the local cache is updated synchronously.
2857 */
2858lib.Storage.Chrome.prototype.removeItem = function(key, opt_callback) {
2859 this.storage_.remove(key, opt_callback);
2860};
2861
2862/**
2863 * Remove multiple items from storage.
2864 *
2865 * @param {Array} keys The keys to be removed.
2866 * @param {function()} opt_callback Optional function to invoke when the
2867 * remove is complete. You don't have to wait for the set to complete in
2868 * order to read the value, since the local cache is updated synchronously.
2869 */
2870lib.Storage.Chrome.prototype.removeItems = function(keys, opt_callback) {
2871 this.storage_.remove(keys, opt_callback);
2872};
2873// SOURCE FILE: libdot/js/lib_storage_local.js
2874// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2875// Use of this source code is governed by a BSD-style license that can be
2876// found in the LICENSE file.
2877
2878'use strict';
2879
2880/**
2881 * window.localStorage based class with an async interface that is
2882 * interchangeable with other lib.Storage.* implementations.
2883 */
2884lib.Storage.Local = function() {
2885 this.observers_ = [];
2886 this.storage_ = window.localStorage;
2887 window.addEventListener('storage', this.onStorage_.bind(this));
2888};
2889
2890/**
2891 * Called by the storage implementation when the storage is modified.
2892 */
2893lib.Storage.Local.prototype.onStorage_ = function(e) {
2894 if (e.storageArea != this.storage_)
2895 return;
2896
2897 // IE throws an exception if JSON.parse is given an empty string.
2898 var prevValue = e.oldValue ? JSON.parse(e.oldValue) : "";
2899 var curValue = e.newValue ? JSON.parse(e.newValue) : "";
2900 var o = {};
2901 o[e.key] = {
2902 oldValue: prevValue,
2903 newValue: curValue
2904 };
2905
2906 for (var i = 0; i < this.observers_.length; i++) {
2907 this.observers_[i](o);
2908 }
2909};
2910
2911/**
2912 * Register a function to observe storage changes.
2913 *
2914 * @param {function(map)} callback The function to invoke when the storage
2915 * changes.
2916 */
2917lib.Storage.Local.prototype.addObserver = function(callback) {
2918 this.observers_.push(callback);
2919};
2920
2921/**
2922 * Unregister a change observer.
2923 *
2924 * @param {function} observer A previously registered callback.
2925 */
2926lib.Storage.Local.prototype.removeObserver = function(callback) {
2927 var i = this.observers_.indexOf(callback);
2928 if (i != -1)
2929 this.observers_.splice(i, 1);
2930};
2931
2932/**
2933 * Delete everything in this storage.
2934 *
2935 * @param {function(map)} callback The function to invoke when the delete
2936 * has completed.
2937 */
2938lib.Storage.Local.prototype.clear = function(opt_callback) {
2939 this.storage_.clear();
2940
2941 if (opt_callback)
2942 setTimeout(opt_callback, 0);
2943};
2944
2945/**
2946 * Return the current value of a storage item.
2947 *
2948 * @param {string} key The key to look up.
2949 * @param {function(value) callback The function to invoke when the value has
2950 * been retrieved.
2951 */
2952lib.Storage.Local.prototype.getItem = function(key, callback) {
2953 var value = this.storage_.getItem(key);
2954
2955 if (typeof value == 'string') {
2956 try {
2957 value = JSON.parse(value);
2958 } catch (e) {
2959 // If we can't parse the value, just return it unparsed.
2960 }
2961 }
2962
2963 setTimeout(callback.bind(null, value), 0);
2964};
2965
2966/**
2967 * Fetch the values of multiple storage items.
2968 *
2969 * @param {Array} keys The keys to look up.
2970 * @param {function(map) callback The function to invoke when the values have
2971 * been retrieved.
2972 */
2973lib.Storage.Local.prototype.getItems = function(keys, callback) {
2974 var rv = {};
2975
2976 for (var i = keys.length - 1; i >= 0; i--) {
2977 var key = keys[i];
2978 var value = this.storage_.getItem(key);
2979 if (typeof value == 'string') {
2980 try {
2981 rv[key] = JSON.parse(value);
2982 } catch (e) {
2983 // If we can't parse the value, just return it unparsed.
2984 rv[key] = value;
2985 }
2986 } else {
2987 keys.splice(i, 1);
2988 }
2989 }
2990
2991 setTimeout(callback.bind(null, rv), 0);
2992};
2993
2994/**
2995 * Set a value in storage.
2996 *
2997 * @param {string} key The key for the value to be stored.
2998 * @param {*} value The value to be stored. Anything that can be serialized
2999 * with JSON is acceptable.
3000 * @param {function()} opt_callback Optional function to invoke when the
3001 * set is complete. You don't have to wait for the set to complete in order
3002 * to read the value, since the local cache is updated synchronously.
3003 */
3004lib.Storage.Local.prototype.setItem = function(key, value, opt_callback) {
3005 this.storage_.setItem(key, JSON.stringify(value));
3006
3007 if (opt_callback)
3008 setTimeout(opt_callback, 0);
3009};
3010
3011/**
3012 * Set multiple values in storage.
3013 *
3014 * @param {Object} map A map of key/values to set in storage.
3015 * @param {function()} opt_callback Optional function to invoke when the
3016 * set is complete. You don't have to wait for the set to complete in order
3017 * to read the value, since the local cache is updated synchronously.
3018 */
3019lib.Storage.Local.prototype.setItems = function(obj, opt_callback) {
3020 for (var key in obj) {
3021 this.storage_.setItem(key, JSON.stringify(obj[key]));
3022 }
3023
3024 if (opt_callback)
3025 setTimeout(opt_callback, 0);
3026};
3027
3028/**
3029 * Remove an item from storage.
3030 *
3031 * @param {string} key The key to be removed.
3032 * @param {function()} opt_callback Optional function to invoke when the
3033 * remove is complete. You don't have to wait for the set to complete in
3034 * order to read the value, since the local cache is updated synchronously.
3035 */
3036lib.Storage.Local.prototype.removeItem = function(key, opt_callback) {
3037 this.storage_.removeItem(key);
3038
3039 if (opt_callback)
3040 setTimeout(opt_callback, 0);
3041};
3042
3043/**
3044 * Remove multiple items from storage.
3045 *
3046 * @param {Array} keys The keys to be removed.
3047 * @param {function()} opt_callback Optional function to invoke when the
3048 * remove is complete. You don't have to wait for the set to complete in
3049 * order to read the value, since the local cache is updated synchronously.
3050 */
3051lib.Storage.Local.prototype.removeItems = function(ary, opt_callback) {
3052 for (var i = 0; i < ary.length; i++) {
3053 this.storage_.removeItem(ary[i]);
3054 }
3055
3056 if (opt_callback)
3057 setTimeout(opt_callback, 0);
3058};
3059// SOURCE FILE: libdot/js/lib_storage_memory.js
3060// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3061// Use of this source code is governed by a BSD-style license that can be
3062// found in the LICENSE file.
3063
3064'use strict';
3065
3066/**
3067 * In-memory storage class with an async interface that is interchangeable with
3068 * other lib.Storage.* implementations.
3069 */
3070lib.Storage.Memory = function() {
3071 this.observers_ = [];
3072 this.storage_ = {};
3073};
3074
3075/**
3076 * Register a function to observe storage changes.
3077 *
3078 * @param {function(map)} callback The function to invoke when the storage
3079 * changes.
3080 */
3081lib.Storage.Memory.prototype.addObserver = function(callback) {
3082 this.observers_.push(callback);
3083};
3084
3085/**
3086 * Unregister a change observer.
3087 *
3088 * @param {function} observer A previously registered callback.
3089 */
3090lib.Storage.Memory.prototype.removeObserver = function(callback) {
3091 var i = this.observers_.indexOf(callback);
3092 if (i != -1)
3093 this.observers_.splice(i, 1);
3094};
3095
3096/**
3097 * Delete everything in this storage.
3098 *
3099 * @param {function(map)} callback The function to invoke when the delete
3100 * has completed.
3101 */
3102lib.Storage.Memory.prototype.clear = function(opt_callback) {
3103 var e = {};
3104 for (var key in this.storage_) {
3105 e[key] = {oldValue: this.storage_[key], newValue: (void 0)};
3106 }
3107
3108 this.storage_ = {};
3109
3110 setTimeout(function() {
3111 for (var i = 0; i < this.observers_.length; i++) {
3112 this.observers_[i](e);
3113 }
3114 }.bind(this), 0);
3115
3116 if (opt_callback)
3117 setTimeout(opt_callback, 0);
3118};
3119
3120/**
3121 * Return the current value of a storage item.
3122 *
3123 * @param {string} key The key to look up.
3124 * @param {function(value) callback The function to invoke when the value has
3125 * been retrieved.
3126 */
3127lib.Storage.Memory.prototype.getItem = function(key, callback) {
3128 var value = this.storage_[key];
3129
3130 if (typeof value == 'string') {
3131 try {
3132 value = JSON.parse(value);
3133 } catch (e) {
3134 // If we can't parse the value, just return it unparsed.
3135 }
3136 }
3137
3138 setTimeout(callback.bind(null, value), 0);
3139};
3140
3141/**
3142 * Fetch the values of multiple storage items.
3143 *
3144 * @param {Array} keys The keys to look up.
3145 * @param {function(map) callback The function to invoke when the values have
3146 * been retrieved.
3147 */
3148lib.Storage.Memory.prototype.getItems = function(keys, callback) {
3149 var rv = {};
3150
3151 for (var i = keys.length - 1; i >= 0; i--) {
3152 var key = keys[i];
3153 var value = this.storage_[key];
3154 if (typeof value == 'string') {
3155 try {
3156 rv[key] = JSON.parse(value);
3157 } catch (e) {
3158 // If we can't parse the value, just return it unparsed.
3159 rv[key] = value;
3160 }
3161 } else {
3162 keys.splice(i, 1);
3163 }
3164 }
3165
3166 setTimeout(callback.bind(null, rv), 0);
3167};
3168
3169/**
3170 * Set a value in storage.
3171 *
3172 * @param {string} key The key for the value to be stored.
3173 * @param {*} value The value to be stored. Anything that can be serialized
3174 * with JSON is acceptable.
3175 * @param {function()} opt_callback Optional function to invoke when the
3176 * set is complete. You don't have to wait for the set to complete in order
3177 * to read the value, since the local cache is updated synchronously.
3178 */
3179lib.Storage.Memory.prototype.setItem = function(key, value, opt_callback) {
3180 var oldValue = this.storage_[key];
3181 this.storage_[key] = JSON.stringify(value);
3182
3183 var e = {};
3184 e[key] = {oldValue: oldValue, newValue: value};
3185
3186 setTimeout(function() {
3187 for (var i = 0; i < this.observers_.length; i++) {
3188 this.observers_[i](e);
3189 }
3190 }.bind(this), 0);
3191
3192 if (opt_callback)
3193 setTimeout(opt_callback, 0);
3194};
3195
3196/**
3197 * Set multiple values in storage.
3198 *
3199 * @param {Object} map A map of key/values to set in storage.
3200 * @param {function()} opt_callback Optional function to invoke when the
3201 * set is complete. You don't have to wait for the set to complete in order
3202 * to read the value, since the local cache is updated synchronously.
3203 */
3204lib.Storage.Memory.prototype.setItems = function(obj, opt_callback) {
3205 var e = {};
3206
3207 for (var key in obj) {
3208 e[key] = {oldValue: this.storage_[key], newValue: obj[key]};
3209 this.storage_[key] = JSON.stringify(obj[key]);
3210 }
3211
3212 setTimeout(function() {
3213 for (var i = 0; i < this.observers_.length; i++) {
3214 this.observers_[i](e);
3215 }
3216 }.bind(this));
3217
3218 if (opt_callback)
3219 setTimeout(opt_callback, 0);
3220};
3221
3222/**
3223 * Remove an item from storage.
3224 *
3225 * @param {string} key The key to be removed.
3226 * @param {function()} opt_callback Optional function to invoke when the
3227 * remove is complete. You don't have to wait for the set to complete in
3228 * order to read the value, since the local cache is updated synchronously.
3229 */
3230lib.Storage.Memory.prototype.removeItem = function(key, opt_callback) {
3231 delete this.storage_[key];
3232
3233 if (opt_callback)
3234 setTimeout(opt_callback, 0);
3235};
3236
3237/**
3238 * Remove multiple items from storage.
3239 *
3240 * @param {Array} keys The keys to be removed.
3241 * @param {function()} opt_callback Optional function to invoke when the
3242 * remove is complete. You don't have to wait for the set to complete in
3243 * order to read the value, since the local cache is updated synchronously.
3244 */
3245lib.Storage.Memory.prototype.removeItems = function(ary, opt_callback) {
3246 for (var i = 0; i < ary.length; i++) {
3247 delete this.storage_[ary[i]];
3248 }
3249
3250 if (opt_callback)
3251 setTimeout(opt_callback, 0);
3252};
3253// SOURCE FILE: libdot/js/lib_test_manager.js
3254// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3255// Use of this source code is governed by a BSD-style license that can be
3256// found in the LICENSE file.
3257
3258'use strict';
3259
3260/**
3261 * @fileoverview JavaScript unit testing framework for synchronous and
3262 * asynchronous tests.
3263 *
3264 * This file contains the lib.TestManager and related classes. At the moment
3265 * it's all collected in a single file since it's reasonably small
3266 * (=~1k lines), and it's a lot easier to include one file into your test
3267 * harness than it is to include seven.
3268 *
3269 * The following classes are defined...
3270 *
3271 * lib.TestManager - The root class and entrypoint for creating test runs.
3272 * lib.TestManager.Log - Logging service.
3273 * lib.TestManager.Suite - A collection of tests.
3274 * lib.TestManager.Test - A single test.
3275 * lib.TestManager.TestRun - Manages the execution of a set of tests.
3276 * lib.TestManager.Result - A single test result.
3277 */
3278
3279/**
3280 * Root object in the unit test hierarchy, and keeper of the log object.
3281 *
3282 * @param {lib.TestManager.Log} opt_log Optional lib.TestManager.Log object.
3283 * Logs to the JavaScript console if omitted.
3284 */
3285lib.TestManager = function(opt_log) {
3286 this.log = opt_log || new lib.TestManager.Log();
3287}
3288
3289/**
3290 * Create a new test run object for this test manager.
3291 *
3292 * @param {Object} opt_cx An object to be passed to test suite setup(),
3293 * preamble(), and test cases during this test run. This object is opaque
3294 * to lib.TestManager.* code. It's entirely up to the test suite what it's
3295 * used for.
3296 */
3297lib.TestManager.prototype.createTestRun = function(opt_cx) {
3298 return new lib.TestManager.TestRun(this, opt_cx);
3299};
3300
3301/**
3302 * Called when a test run associated with this test manager completes.
3303 *
3304 * Clients may override this to call an appropriate function.
3305 */
3306lib.TestManager.prototype.onTestRunComplete = function(testRun) {};
3307
3308/**
3309 * Called before a test associated with this test manager is run.
3310 *
3311 * @param {lib.TestManager.Result} result The result object for the upcoming
3312 * test.
3313 * @param {Object} cx The context object for a test run.
3314 */
3315lib.TestManager.prototype.testPreamble = function(result, cx) {};
3316
3317/**
3318 * Called after a test associated with this test manager finishes.
3319 *
3320 * @param {lib.TestManager.Result} result The result object for the finished
3321 * test.
3322 * @param {Object} cx The context object for a test run.
3323 */
3324lib.TestManager.prototype.testPostamble = function(result, cx) {};
3325
3326/**
3327 * Destination for test case output.
3328 *
3329 * @param {function(string)} opt_logFunction Optional function to call to
3330 * write a string to the log. If omitted, console.log is used.
3331 */
3332lib.TestManager.Log = function(opt_logFunction) {
3333 this.save = false;
3334 this.data = '';
3335 this.logFunction_ = opt_logFunction || function(s) {
3336 if (this.save)
3337 this.data += s + '\n';
3338 console.log(s);
3339 };
3340 this.pending_ = '';
3341 this.prefix_ = '';
3342 this.prefixStack_ = [];
3343};
3344
3345/**
3346 * Add a prefix to log messages.
3347 *
3348 * This only affects log messages that are added after the prefix is pushed.
3349 *
3350 * @param {string} str The prefix to prepend to future log messages.
3351 */
3352lib.TestManager.Log.prototype.pushPrefix = function(str) {
3353 this.prefixStack_.push(str);
3354 this.prefix_ = this.prefixStack_.join('');
3355};
3356
3357/**
3358 * Remove the most recently added message prefix.
3359 */
3360lib.TestManager.Log.prototype.popPrefix = function() {
3361 this.prefixStack_.pop();
3362 this.prefix_ = this.prefixStack_.join('');
3363};
3364
3365/**
3366 * Queue up a string to print to the log.
3367 *
3368 * If a line is already pending, this string is added to it.
3369 *
3370 * The string is not actually printed to the log until flush() or println()
3371 * is called. The following call sequence will result in TWO lines in the
3372 * log...
3373 *
3374 * log.print('hello');
3375 * log.print(' ');
3376 * log.println('world');
3377 *
3378 * While a typical stream-like thing would result in 'hello world\n', this one
3379 * results in 'hello \nworld\n'.
3380 *
3381 * @param {string} str The string to add to the log.
3382 */
3383lib.TestManager.Log.prototype.print = function(str) {
3384 if (this.pending_) {
3385 this.pending_ += str;
3386 } else {
3387 this.pending_ = this.prefix_ + str;
3388 }
3389};
3390
3391/**
3392 * Print a line to the log and flush it immediately.
3393 *
3394 * @param {string} str The string to add to the log.
3395 */
3396lib.TestManager.Log.prototype.println = function(str) {
3397 if (this.pending_)
3398 this.flush();
3399
3400 this.logFunction_(this.prefix_ + str);
3401};
3402
3403/**
3404 * Flush any pending log message.
3405 */
3406lib.TestManager.Log.prototype.flush = function() {
3407 if (!this.pending_)
3408 return;
3409
3410 this.logFunction_(this.pending_);
3411 this.pending_ = '';
3412};
3413
3414/**
3415 * Returns a new constructor function that will inherit from
3416 * lib.TestManager.Suite.
3417 *
3418 * Use this function to create a new test suite subclass. It will return a
3419 * properly initialized constructor function for the subclass. You can then
3420 * override the setup() and preamble() methods if necessary and add test cases
3421 * to the subclass.
3422 *
3423 * var MyTests = new lib.TestManager.Suite('MyTests');
3424 *
3425 * MyTests.prototype.setup = function(cx) {
3426 * // Sets this.size to cx.size if it exists, or the default value of 10
3427 * // if not.
3428 * this.setDefault(cx, {size: 10});
3429 * };
3430 *
3431 * MyTests.prototype.preamble = function(result, cx) {
3432 * // Some tests (even successful ones) may side-effect this list, so
3433 * // recreate it before every test.
3434 * this.list = [];
3435 * for (var i = 0; i < this.size; i++) {
3436 * this.list[i] = i;
3437 * }
3438 * };
3439 *
3440 * // Basic synchronous test case.
3441 * MyTests.addTest('pop-length', function(result, cx) {
3442 * this.list.pop();
3443 *
3444 * // If this assertion fails, the testcase will stop here.
3445 * result.assertEQ(this.list.length, this.size - 1);
3446 *
3447 * // A test must indicate it has passed by calling this method.
3448 * result.pass();
3449 * });
3450 *
3451 * // Sample asynchronous test case.
3452 * MyTests.addTest('async-pop-length', function(result, cx) {
3453 * var self = this;
3454 *
3455 * var callback = function() {
3456 * result.assertEQ(self.list.length, self.size - 1);
3457 * result.pass();
3458 * };
3459 *
3460 * // Wait 100ms to check the array length for the sake of this example.
3461 * setTimeout(callback, 100);
3462 *
3463 * this.list.pop();
3464 *
3465 * // Indicate that this test needs another 200ms to complete.
3466 * // If the test does not report pass/fail by then, it is considered to
3467 * // have timed out.
3468 * result.requestTime(200);
3469 * });
3470 *
3471 * ...
3472 *
3473 * @param {string} suiteName The name of the test suite.
3474 */
3475lib.TestManager.Suite = function(suiteName) {
3476 function ctor(testManager, cx) {
3477 this.testManager_ = testManager;
3478 this.suiteName = suiteName;
3479
3480 this.setup(cx);
3481 }
3482
3483 ctor.suiteName = suiteName;
3484 ctor.addTest = lib.TestManager.Suite.addTest;
3485 ctor.disableTest = lib.TestManager.Suite.disableTest;
3486 ctor.getTest = lib.TestManager.Suite.getTest;
3487 ctor.getTestList = lib.TestManager.Suite.getTestList;
3488 ctor.testList_ = [];
3489 ctor.testMap_ = {};
3490 ctor.prototype = { __proto__: lib.TestManager.Suite.prototype };
3491
3492 lib.TestManager.Suite.subclasses.push(ctor);
3493
3494 return ctor;
3495};
3496
3497/**
3498 * List of lib.TestManager.Suite subclasses, in the order they were defined.
3499 */
3500lib.TestManager.Suite.subclasses = [];
3501
3502/**
3503 * Add a test to a lib.TestManager.Suite.
3504 *
3505 * This method is copied to new subclasses when they are created.
3506 */
3507lib.TestManager.Suite.addTest = function(testName, testFunction) {
3508 if (testName in this.testMap_)
3509 throw 'Duplicate test name: ' + testName;
3510
3511 var test = new lib.TestManager.Test(this, testName, testFunction);
3512 this.testMap_[testName] = test;
3513 this.testList_.push(test);
3514};
3515
3516/**
3517 * Defines a disabled test.
3518 */
3519lib.TestManager.Suite.disableTest = function(testName, testFunction) {
3520 if (testName in this.testMap_)
3521 throw 'Duplicate test name: ' + testName;
3522
3523 var test = new lib.TestManager.Test(this, testName, testFunction);
3524 console.log('Disabled test: ' + test.fullName);
3525};
3526
3527/**
3528 * Get a lib.TestManager.Test instance by name.
3529 *
3530 * This method is copied to new subclasses when they are created.
3531 *
3532 * @param {string} testName The name of the desired test.
3533 * @return {lib.TestManager.Test} The requested test, or undefined if it was not
3534 * found.
3535 */
3536lib.TestManager.Suite.getTest = function(testName) {
3537 return this.testMap_[testName];
3538};
3539
3540/**
3541 * Get an array of lib.TestManager.Tests associated with this Suite.
3542 *
3543 * This method is copied to new subclasses when they are created.
3544 */
3545lib.TestManager.Suite.getTestList = function() {
3546 return this.testList_;
3547};
3548
3549/**
3550 * Set properties on a test suite instance, pulling the property value from
3551 * the context if it exists and from the defaults dictionary if not.
3552 *
3553 * This is intended to be used in your test suite's setup() method to
3554 * define parameters for the test suite which may be overridden through the
3555 * context object. For example...
3556 *
3557 * MySuite.prototype.setup = function(cx) {
3558 * this.setDefaults(cx, {size: 10});
3559 * };
3560 *
3561 * If the context object has a 'size' property then this.size will be set to
3562 * the value of cx.size, otherwise this.size will get a default value of 10.
3563 *
3564 * @param {Object} cx The context object for a test run.
3565 * @param {Object} defaults An object containing name/value pairs to set on
3566 * this test suite instance. The value listed here will be used if the
3567 * name is not defined on the context object.
3568 */
3569lib.TestManager.Suite.prototype.setDefaults = function(cx, defaults) {
3570 for (var k in defaults) {
3571 this[k] = (k in cx) ? cx[k] : defaults[k];
3572 }
3573};
3574
3575/**
3576 * Subclassable method called to set up the test suite.
3577 *
3578 * The default implementation of this method is a no-op. If your test suite
3579 * requires some kind of suite-wide setup, this is the place to do it.
3580 *
3581 * It's fine to store state on the test suite instance, that state will be
3582 * accessible to all tests in the suite. If any test case fails, the entire
3583 * test suite object will be discarded and a new one will be created for
3584 * the remaining tests.
3585 *
3586 * Any side effects outside of this test suite instance must be idempotent.
3587 * For example, if you're adding DOM nodes to a document, make sure to first
3588 * test that they're not already there. If they are, remove them rather than
3589 * reuse them. You should not count on their state, since they were probably
3590 * left behind by a failed testcase.
3591 *
3592 * Any exception here will abort the remainder of the test run.
3593 *
3594 * @param {Object} cx The context object for a test run.
3595 */
3596lib.TestManager.Suite.prototype.setup = function(cx) {};
3597
3598/**
3599 * Subclassable method called to do pre-test set up.
3600 *
3601 * The default implementation of this method is a no-op. If your test suite
3602 * requires some kind of pre-test setup, this is the place to do it.
3603 *
3604 * This can be used to avoid a bunch of boilerplate setup/teardown code in
3605 * this suite's testcases.
3606 *
3607 * Any exception here will abort the remainder of the test run.
3608 *
3609 * @param {lib.TestManager.Result} result The result object for the upcoming
3610 * test.
3611 * @param {Object} cx The context object for a test run.
3612 */
3613lib.TestManager.Suite.prototype.preamble = function(result, cx) {};
3614
3615/**
3616 * Subclassable method called to do post-test tear-down.
3617 *
3618 * The default implementation of this method is a no-op. If your test suite
3619 * requires some kind of pre-test setup, this is the place to do it.
3620 *
3621 * This can be used to avoid a bunch of boilerplate setup/teardown code in
3622 * this suite's testcases.
3623 *
3624 * Any exception here will abort the remainder of the test run.
3625 *
3626 * @param {lib.TestManager.Result} result The result object for the finished
3627 * test.
3628 * @param {Object} cx The context object for a test run.
3629 */
3630lib.TestManager.Suite.prototype.postamble = function(result, cx) {};
3631
3632/**
3633 * Object representing a single test in a test suite.
3634 *
3635 * These are created as part of the lib.TestManager.Suite.addTest() method.
3636 * You should never have to construct one by hand.
3637 *
3638 * @param {lib.TestManager.Suite} suiteClass The test suite class containing
3639 * this test.
3640 * @param {string} testName The local name of this test case, not including the
3641 * test suite name.
3642 * @param {function(lib.TestManager.Result, Object)} testFunction The function
3643 * to invoke for this test case. This is passed a Result instance and the
3644 * context object associated with the test run.
3645 *
3646 */
3647lib.TestManager.Test = function(suiteClass, testName, testFunction) {
3648 /**
3649 * The test suite class containing this function.
3650 */
3651 this.suiteClass = suiteClass;
3652
3653 /**
3654 * The local name of this test, not including the test suite name.
3655 */
3656 this.testName = testName;
3657
3658 /**
3659 * The global name of this test, including the test suite name.
3660 */
3661 this.fullName = suiteClass.suiteName + '[' + testName + ']';
3662
3663 // The function to call for this test.
3664 this.testFunction_ = testFunction;
3665};
3666
3667/**
3668 * Execute this test.
3669 *
3670 * This is called by a lib.TestManager.Result instance, as part of a
3671 * lib.TestManager.TestRun. You should not call it by hand.
3672 *
3673 * @param {lib.TestManager.Result} result The result object for the test.
3674 */
3675lib.TestManager.Test.prototype.run = function(result) {
3676 try {
3677 // Tests are applied to the parent lib.TestManager.Suite subclass.
3678 this.testFunction_.apply(result.suite,
3679 [result, result.testRun.cx]);
3680 } catch (ex) {
3681 if (ex instanceof lib.TestManager.Result.TestComplete)
3682 return;
3683
3684 result.println('Test raised an exception: ' + ex);
3685
3686 if (ex.stack) {
3687 if (ex.stack instanceof Array) {
3688 result.println(ex.stack.join('\n'));
3689 } else {
3690 result.println(ex.stack);
3691 }
3692 }
3693
3694 result.completeTest_(result.FAILED, false);
3695 }
3696};
3697
3698/**
3699 * Used to choose a set of tests and run them.
3700 *
3701 * It's slightly more convenient to construct one of these from
3702 * lib.TestManager.prototype.createTestRun().
3703 *
3704 * @param {lib.TestManager} testManager The testManager associated with this
3705 * TestRun.
3706 * @param {Object} cx A context to be passed into the tests. This can be used
3707 * to set parameters for the test suite or individual test cases.
3708 */
3709lib.TestManager.TestRun = function(testManager, cx) {
3710 /**
3711 * The associated lib.TestManager instance.
3712 */
3713 this.testManager = testManager;
3714
3715 /**
3716 * Shortcut to the lib.TestManager's log.
3717 */
3718 this.log = testManager.log;
3719
3720 /**
3721 * The test run context. It's entirely up to the test suite and test cases
3722 * how this is used. It is opaque to lib.TestManager.* classes.
3723 */
3724 this.cx = cx || {};
3725
3726 /**
3727 * The list of test cases that encountered failures.
3728 */
3729 this.failures = [];
3730
3731 /**
3732 * The list of test cases that passed.
3733 */
3734 this.passes = [];
3735
3736 /**
3737 * The time the test run started, or null if it hasn't been started yet.
3738 */
3739 this.startDate = null;
3740
3741 /**
3742 * The time in milliseconds that the test run took to complete, or null if
3743 * it hasn't completed yet.
3744 */
3745 this.duration = null;
3746
3747 /**
3748 * The most recent result object, or null if the test run hasn't started
3749 * yet. In order to detect late failures, this is not cleared when the test
3750 * completes.
3751 */
3752 this.currentResult = null;
3753
3754 /**
3755 * Number of maximum failures. The test run will stop when this number is
3756 * reached. If 0 or omitted, the entire set of selected tests is run, even
3757 * if some fail.
3758 */
3759 this.maxFailures = 0;
3760
3761 /**
3762 * True if this test run ended early because of an unexpected condition.
3763 */
3764 this.panic = false;
3765
3766 // List of pending test cases.
3767 this.testQueue_ = [];
3768
3769};
3770
3771/**
3772 * This value can be passed to select() to indicate that all tests should
3773 * be selected.
3774 */
3775lib.TestManager.TestRun.prototype.ALL_TESTS = new String('<all-tests>');
3776
3777/**
3778 * Add a single test to the test run.
3779 */
3780lib.TestManager.TestRun.prototype.selectTest = function(test) {
3781 this.testQueue_.push(test);
3782};
3783
3784lib.TestManager.TestRun.prototype.selectSuite = function(
3785 suiteClass, opt_pattern) {
3786 var pattern = opt_pattern || this.ALL_TESTS;
3787 var selectCount = 0;
3788 var testList = suiteClass.getTestList();
3789
3790 for (var j = 0; j < testList.length; j++) {
3791 var test = testList[j];
3792 // Note that we're using "!==" rather than "!=" so that we're matching
3793 // the ALL_TESTS String object, rather than the contents of the string.
3794 if (pattern !== this.ALL_TESTS) {
3795 if (pattern instanceof RegExp) {
3796 if (!pattern.test(test.testName))
3797 continue;
3798 } else if (test.testName != pattern) {
3799 continue;
3800 }
3801 }
3802
3803 this.selectTest(test);
3804 selectCount++;
3805 }
3806
3807 return selectCount;
3808};
3809
3810/**
3811 * Selects one or more tests to gather results for.
3812 *
3813 * Selecting the same test more than once is allowed.
3814 *
3815 * @param {string|RegExp} pattern Pattern used to select tests.
3816 * If TestRun.prototype.ALL_TESTS, all tests are selected.
3817 * If a string, only the test that exactly matches is selected.
3818 * If a RegExp, only tests matching the RegExp are added.
3819 *
3820 * @return {int} The number of additional tests that have been selected into
3821 * this TestRun.
3822 */
3823lib.TestManager.TestRun.prototype.selectPattern = function(pattern) {
3824 var selectCount = 0;
3825
3826 for (var i = 0; i < lib.TestManager.Suite.subclasses.length; i++) {
3827 selectCount += this.selectSuite(lib.TestManager.Suite.subclasses[i],
3828 pattern);
3829 }
3830
3831 if (!selectCount) {
3832 this.log.println('No tests matched selection criteria: ' + pattern);
3833 }
3834
3835 return selectCount;
3836};
3837
3838/**
3839 * Hooked up to window.onerror during a test run in order to catch exceptions
3840 * that would otherwise go uncaught.
3841 */
3842lib.TestManager.TestRun.prototype.onUncaughtException_ = function(
3843 message, file, line) {
3844
3845 if (message.indexOf('Uncaught lib.TestManager.Result.TestComplete') == 0 ||
3846 message.indexOf('status: passed') != -1) {
3847 // This is a result.pass() or result.fail() call from a callback. We're
3848 // already going to deal with it as part of the completeTest_() call
3849 // that raised it. We can safely squelch this error message.
3850 return true;
3851 }
3852
3853 if (!this.currentResult)
3854 return;
3855
3856 if (message == 'Uncaught ' + this.currentResult.expectedErrorMessage_) {
3857 // Test cases may need to raise an unhandled exception as part of the test.
3858 return;
3859 }
3860
3861 var when = 'during';
3862
3863 if (this.currentResult.status != this.currentResult.PENDING)
3864 when = 'after';
3865
3866 this.log.println('Uncaught exception ' + when + ' test case: ' +
3867 this.currentResult.test.fullName);
3868 this.log.println(message + ', ' + file + ':' + line);
3869
3870 this.currentResult.completeTest_(this.currentResult.FAILED, false);
3871
3872 return false;
3873};
3874
3875/**
3876 * Called to when this test run has completed.
3877 *
3878 * This method typically re-runs itself asynchronously, in order to let the
3879 * DOM stabilize and short-term timeouts to complete before declaring the
3880 * test run complete.
3881 *
3882 * @param {boolean} opt_skipTimeout If true, the timeout is skipped and the
3883 * test run is completed immediately. This should only be used from within
3884 * this function.
3885 */
3886lib.TestManager.TestRun.prototype.onTestRunComplete_ = function(
3887 opt_skipTimeout) {
3888 if (!opt_skipTimeout) {
3889 // The final test may have left a lingering setTimeout(..., 0), or maybe
3890 // poked at the DOM in a way that will trigger a event to fire at the end
3891 // of this stack, so we give things a chance to settle down before our
3892 // final cleanup...
3893 setTimeout(this.onTestRunComplete_.bind(this), 0, true);
3894 return;
3895 }
3896
3897 this.duration = (new Date()) - this.startDate;
3898
3899 this.log.popPrefix();
3900 this.log.println('} ' + this.passes.length + ' passed, ' +
3901 this.failures.length + ' failed, ' +
3902 this.msToSeconds_(this.duration));
3903 this.log.println('');
3904
3905 this.summarize();
3906
3907 window.onerror = null;
3908
3909 this.testManager.onTestRunComplete(this);
3910};
3911
3912/**
3913 * Called by the lib.TestManager.Result object when a test completes.
3914 *
3915 * @param {lib.TestManager.Result} result The result object which has just
3916 * completed.
3917 */
3918lib.TestManager.TestRun.prototype.onResultComplete = function(result) {
3919 try {
3920 this.testManager.testPostamble(result, this.cx);
3921 result.suite.postamble(result, this.ctx);
3922 } catch (ex) {
3923 this.log.println('Unexpected exception in postamble: ' +
3924 (ex.stack ? ex.stack : ex));
3925 this.panic = true;
3926 }
3927
3928 this.log.popPrefix();
3929 this.log.print('} ' + result.status + ', ' +
3930 this.msToSeconds_(result.duration));
3931 this.log.flush();
3932
3933 if (result.status == result.FAILED) {
3934 this.failures.push(result);
3935 this.currentSuite = null;
3936 } else if (result.status == result.PASSED) {
3937 this.passes.push(result);
3938 } else {
3939 this.log.println('Unknown result status: ' + result.test.fullName + ': ' +
3940 result.status);
3941 return this.panic = true;
3942 }
3943
3944 this.runNextTest_();
3945};
3946
3947/**
3948 * Called by the lib.TestManager.Result object when a test which has already
3949 * completed reports another completion.
3950 *
3951 * This is usually indicative of a buggy testcase. It is probably reporting a
3952 * result on exit and then again from an asynchronous callback.
3953 *
3954 * It may also be the case that the last act of the testcase causes a DOM change
3955 * which triggers some event to run after the test returns. If the event
3956 * handler reports a failure or raises an uncaught exception, the test will
3957 * fail even though it has already completed.
3958 *
3959 * In any case, re-completing a test ALWAYS moves it into the failure pile.
3960 *
3961 * @param {lib.TestManager.Result} result The result object which has just
3962 * completed.
3963 * @param {string} lateStatus The status that the test attempted to record this
3964 * time around.
3965 */
3966lib.TestManager.TestRun.prototype.onResultReComplete = function(
3967 result, lateStatus) {
3968 this.log.println('Late complete for test: ' + result.test.fullName + ': ' +
3969 lateStatus);
3970
3971 // Consider any late completion a failure, even if it's a double-pass, since
3972 // it's a misuse of the testing API.
3973 var index = this.passes.indexOf(result);
3974 if (index >= 0) {
3975 this.passes.splice(index, 1);
3976 this.failures.push(result);
3977 }
3978};
3979
3980/**
3981 * Run the next test in the queue.
3982 */
3983lib.TestManager.TestRun.prototype.runNextTest_ = function() {
3984 if (this.panic || !this.testQueue_.length)
3985 return this.onTestRunComplete_();
3986
3987 if (this.maxFailures && this.failures.length >= this.maxFailures) {
3988 this.log.println('Maximum failure count reached, aborting test run.');
3989 return this.onTestRunComplete_();
3990 }
3991
3992 // Peek at the top test first. We remove it later just before it's about
3993 // to run, so that we don't disturb the incomplete test count in the
3994 // event that we fail before running it.
3995 var test = this.testQueue_[0];
3996 var suite = this.currentResult ? this.currentResult.suite : null;
3997
3998 try {
3999 if (!suite || !(suite instanceof test.suiteClass)) {
4000 this.log.println('Initializing suite: ' + test.suiteClass.suiteName);
4001 suite = new test.suiteClass(this.testManager, this.cx);
4002 }
4003 } catch (ex) {
4004 // If test suite setup fails we're not even going to try to run the tests.
4005 this.log.println('Exception during setup: ' + (ex.stack ? ex.stack : ex));
4006 this.panic = true;
4007 this.onTestRunComplete_();
4008 return;
4009 }
4010
4011 try {
4012 this.log.print('Test: ' + test.fullName + ' {');
4013 this.log.pushPrefix(' ');
4014
4015 this.currentResult = new lib.TestManager.Result(this, suite, test);
4016 this.testManager.testPreamble(this.currentResult, this.cx);
4017 suite.preamble(this.currentResult, this.cx);
4018
4019 this.testQueue_.shift();
4020 } catch (ex) {
4021 this.log.println('Unexpected exception during test preamble: ' +
4022 (ex.stack ? ex.stack : ex));
4023 this.log.popPrefix();
4024 this.log.println('}');
4025
4026 this.panic = true;
4027 this.onTestRunComplete_();
4028 return;
4029 }
4030
4031 try {
4032 this.currentResult.run();
4033 } catch (ex) {
4034 // Result.run() should catch test exceptions and turn them into failures.
4035 // If we got here, it means there is trouble in the testing framework.
4036 this.log.println('Unexpected exception during test run: ' +
4037 (ex.stack ? ex.stack : ex));
4038 this.panic = true;
4039 }
4040};
4041
4042/**
4043 * Run the selected list of tests.
4044 *
4045 * Some tests may need to run asynchronously, so you cannot assume the run is
4046 * complete when this function returns. Instead, pass in a function to be
4047 * called back when the run has completed.
4048 *
4049 * This function will log the results of the test run as they happen into the
4050 * log defined by the associated lib.TestManager. By default this is
4051 * console.log, which can be viewed in the JavaScript console of most browsers.
4052 *
4053 * The browser state is determined by the last test to run. We intentionally
4054 * don't do any cleanup so that you can inspect the state of a failed test, or
4055 * leave the browser ready for manual testing.
4056 *
4057 * Any failures in lib.TestManager.* code or test suite setup or test case
4058 * preamble will cause the test run to abort.
4059 */
4060lib.TestManager.TestRun.prototype.run = function() {
4061 this.log.println('Running ' + this.testQueue_.length + ' test(s) {');
4062 this.log.pushPrefix(' ');
4063
4064 window.onerror = this.onUncaughtException_.bind(this);
4065 this.startDate = new Date();
4066 this.runNextTest_();
4067};
4068
4069/**
4070 * Format milliseconds as fractional seconds.
4071 */
4072lib.TestManager.TestRun.prototype.msToSeconds_ = function(ms) {
4073 var secs = (ms / 1000).toFixed(2);
4074 return secs + 's';
4075};
4076
4077/**
4078 * Log the current result summary.
4079 */
4080lib.TestManager.TestRun.prototype.summarize = function() {
4081 if (this.failures.length) {
4082 for (var i = 0; i < this.failures.length; i++) {
4083 this.log.println('FAILED: ' + this.failures[i].test.fullName);
4084 }
4085 }
4086
4087 if (this.testQueue_.length) {
4088 this.log.println('Test run incomplete: ' + this.testQueue_.length +
4089 ' test(s) were not run.');
4090 }
4091};
4092
4093/**
4094 * Record of the result of a single test.
4095 *
4096 * These are constructed during a test run, you shouldn't have to make one
4097 * on your own.
4098 *
4099 * An instance of this class is passed in to each test function. It can be
4100 * used to add messages to the test log, to record a test pass/fail state, to
4101 * test assertions, or to create exception-proof wrappers for callback
4102 * functions.
4103 *
4104 * @param {lib.TestManager.TestRun} testRun The TestRun instance associated with
4105 * this result.
4106 * @param {lib.TestManager.Suit} suite The Suite containing the test we're
4107 * collecting this result for.
4108 * @param {lib.TestManager.Test} test The test we're collecting this result for.
4109 */
4110lib.TestManager.Result = function(testRun, suite, test) {
4111 /**
4112 * The TestRun instance associated with this result.
4113 */
4114 this.testRun = testRun;
4115
4116 /**
4117 * The Suite containing the test we're collecting this result for.
4118 */
4119 this.suite = suite;
4120
4121 /**
4122 * The test we're collecting this result for.
4123 */
4124 this.test = test;
4125
4126 /**
4127 * The time we started to collect this result, or null if we haven't started.
4128 */
4129 this.startDate = null;
4130
4131 /**
4132 * The time in milliseconds that the test took to complete, or null if
4133 * it hasn't completed yet.
4134 */
4135 this.duration = null;
4136
4137 /**
4138 * The current status of this test result.
4139 */
4140 this.status = this.PENDING;
4141
4142 // An error message that the test case is expected to generate.
4143 this.expectedErrorMessage_ = null;
4144};
4145
4146/**
4147 * Possible values for this.status.
4148 */
4149lib.TestManager.Result.prototype.PENDING = 'pending';
4150lib.TestManager.Result.prototype.FAILED = 'FAILED';
4151lib.TestManager.Result.prototype.PASSED = 'passed';
4152
4153/**
4154 * Exception thrown when a test completes (pass or fail), to ensure no more of
4155 * the test is run.
4156 */
4157lib.TestManager.Result.TestComplete = function(result) {
4158 this.result = result;
4159};
4160
4161lib.TestManager.Result.TestComplete.prototype.toString = function() {
4162 return 'lib.TestManager.Result.TestComplete: ' + this.result.test.fullName +
4163 ', status: ' + this.result.status;
4164}
4165
4166/**
4167 * Start the test associated with this result.
4168 */
4169lib.TestManager.Result.prototype.run = function() {
4170 var self = this;
4171
4172 this.startDate = new Date();
4173 this.test.run(this);
4174
4175 if (this.status == this.PENDING && !this.timeout_) {
4176 this.println('Test did not return a value and did not request more time.');
4177 this.completeTest_(this.FAILED, false);
4178 }
4179};
4180
4181/**
4182 * Unhandled error message this test expects to generate.
4183 *
4184 * This must be the exact string that would appear in the JavaScript console,
4185 * minus the 'Uncaught ' prefix.
4186 *
4187 * The test case does *not* automatically fail if the error message is not
4188 * encountered.
4189 */
4190lib.TestManager.Result.prototype.expectErrorMessage = function(str) {
4191 this.expectedErrorMessage_ = str;
4192};
4193
4194/**
4195 * Function called when a test times out.
4196 */
4197lib.TestManager.Result.prototype.onTimeout_ = function() {
4198 this.timeout_ = null;
4199
4200 if (this.status != this.PENDING)
4201 return;
4202
4203 this.println('Test timed out.');
4204 this.completeTest_(this.FAILED, false);
4205};
4206
4207/**
4208 * Indicate that a test case needs more time to complete.
4209 *
4210 * Before a test case returns it must report a pass/fail result, or request more
4211 * time to do so.
4212 *
4213 * If a test does not report pass/fail before the time expires it will
4214 * be reported as a timeout failure. Any late pass/fails will be noted in the
4215 * test log, but will not affect the final result of the test.
4216 *
4217 * Test cases may call requestTime more than once. If you have a few layers
4218 * of asynchronous API to go through, you should call this once per layer with
4219 * an estimate of how long each callback will take to complete.
4220 *
4221 * @param {int} ms Number of milliseconds requested.
4222 */
4223lib.TestManager.Result.prototype.requestTime = function(ms) {
4224 if (this.timeout_)
4225 clearTimeout(this.timeout_);
4226
4227 this.timeout_ = setTimeout(this.onTimeout_.bind(this), ms);
4228};
4229
4230/**
4231 * Report the completion of a test.
4232 *
4233 * @param {string} status The status of the test case.
4234 * @param {boolean} opt_throw Optional boolean indicating whether or not
4235 * to throw the TestComplete exception.
4236 */
4237lib.TestManager.Result.prototype.completeTest_ = function(status, opt_throw) {
4238 if (this.status == this.PENDING) {
4239 this.duration = (new Date()) - this.startDate;
4240 this.status = status;
4241
4242 this.testRun.onResultComplete(this);
4243 } else {
4244 this.testRun.onResultReComplete(this, status);
4245 }
4246
4247 if (arguments.length < 2 || opt_throw)
4248 throw new lib.TestManager.Result.TestComplete(this);
4249};
4250
4251/**
4252 * Check that two arrays are equal.
4253 */
4254lib.TestManager.Result.prototype.arrayEQ_ = function(actual, expected) {
4255 if (!actual || !expected)
4256 return (!actual && !expected);
4257
4258 if (actual.length != expected.length)
4259 return false;
4260
4261 for (var i = 0; i < actual.length; ++i)
4262 if (actual[i] != expected[i])
4263 return false;
4264
4265 return true;
4266};
4267
4268/**
4269 * Assert that an actual value is exactly equal to the expected value.
4270 *
4271 * This uses the JavaScript '===' operator in order to avoid type coercion.
4272 *
4273 * If the assertion fails, the test is marked as a failure and a TestCompleted
4274 * exception is thrown.
4275 *
4276 * @param {*} actual The actual measured value.
4277 * @param {*} expected The value expected.
4278 * @param {string} opt_name An optional name used to identify this
4279 * assertion in the test log. If omitted it will be the file:line
4280 * of the caller.
4281 */
4282lib.TestManager.Result.prototype.assertEQ = function(
4283 actual, expected, opt_name) {
4284 // Utility function to pretty up the log.
4285 function format(value) {
4286 if (typeof value == 'number')
4287 return value;
4288
4289 var str = String(value);
4290 var ary = str.split('\n').map(function (e) { return JSON.stringify(e) });
4291 if (ary.length > 1) {
4292 // If the string has newlines, start it off on its own line so that
4293 // it's easier to compare against another string with newlines.
4294 return '\n' + ary.join('\n');
4295 } else {
4296 return ary.join('\n');
4297 }
4298 }
4299
4300 if (actual === expected)
4301 return;
4302
4303 // Deal with common object types since JavaScript can't.
4304 if (expected instanceof Array)
4305 if (this.arrayEQ_(actual, expected))
4306 return;
4307
4308 var name = opt_name ? '[' + opt_name + ']' : '';
4309
4310 this.fail('assertEQ' + name + ': ' + this.getCallerLocation_(1) + ': ' +
4311 format(actual) + ' !== ' + format(expected));
4312};
4313
4314/**
4315 * Assert that a value is true.
4316 *
4317 * This uses the JavaScript '===' operator in order to avoid type coercion.
4318 * The must be the boolean value `true`, not just some "truish" value.
4319 *
4320 * If the assertion fails, the test is marked as a failure and a TestCompleted
4321 * exception is thrown.
4322 *
4323 * @param {boolean} actual The actual measured value.
4324 * @param {string} opt_name An optional name used to identify this
4325 * assertion in the test log. If omitted it will be the file:line
4326 * of the caller.
4327 */
4328lib.TestManager.Result.prototype.assert = function(actual, opt_name) {
4329 if (actual === true)
4330 return;
4331
4332 var name = opt_name ? '[' + opt_name + ']' : '';
4333
4334 this.fail('assert' + name + ': ' + this.getCallerLocation_(1) + ': ' +
4335 String(actual));
4336};
4337
4338/**
4339 * Return the filename:line of a calling stack frame.
4340 *
4341 * This uses a dirty hack. It throws an exception, catches it, and examines
4342 * the stack property of the caught exception.
4343 *
4344 * @param {int} frameIndex The stack frame to return. 0 is the frame that
4345 * called this method, 1 is its caller, and so on.
4346 * @return {string} A string of the format "filename:linenumber".
4347 */
4348lib.TestManager.Result.prototype.getCallerLocation_ = function(frameIndex) {
4349 try {
4350 throw new Error();
4351 } catch (ex) {
4352 var frame = ex.stack.split('\n')[frameIndex + 2];
4353 var ary = frame.match(/([^/]+:\d+):\d+\)?$/);
4354 return ary ? ary[1] : '???';
4355 }
4356};
4357
4358/**
4359 * Write a message to the result log.
4360 */
4361lib.TestManager.Result.prototype.println = function(message) {
4362 this.testRun.log.println(message);
4363};
4364
4365/**
4366 * Mark a failed test and exit out of the rest of the test.
4367 *
4368 * This will throw a TestCompleted exception, causing the current test to stop.
4369 *
4370 * @param {string} opt_message Optional message to add to the log.
4371 */
4372lib.TestManager.Result.prototype.fail = function(opt_message) {
4373 if (arguments.length)
4374 this.println(opt_message);
4375
4376 this.completeTest_(this.FAILED, true);
4377};
4378
4379/**
4380 * Mark a passed test and exit out of the rest of the test.
4381 *
4382 * This will throw a TestCompleted exception, causing the current test to stop.
4383 */
4384lib.TestManager.Result.prototype.pass = function() {
4385 this.completeTest_(this.PASSED, true);
4386};
4387// SOURCE FILE: libdot/js/lib_utf8.js
4388// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4389// Use of this source code is governed by a BSD-style license that can be
4390// found in the LICENSE file.
4391
4392'use strict';
4393
4394// TODO(davidben): When the string encoding API is implemented,
4395// replace this with the native in-browser implementation.
4396//
4397// https://wiki.whatwg.org/wiki/StringEncoding
4398// https://encoding.spec.whatwg.org/
4399
4400/**
4401 * A stateful UTF-8 decoder.
4402 */
4403lib.UTF8Decoder = function() {
4404 // The number of bytes left in the current sequence.
4405 this.bytesLeft = 0;
4406 // The in-progress code point being decoded, if bytesLeft > 0.
4407 this.codePoint = 0;
4408 // The lower bound on the final code point, if bytesLeft > 0.
4409 this.lowerBound = 0;
4410};
4411
4412/**
4413 * Decodes a some UTF-8 data, taking into account state from previous
4414 * data streamed through the encoder.
4415 *
4416 * @param {String} str data to decode, represented as a JavaScript
4417 * String with each code unit representing a byte between 0x00 to
4418 * 0xFF.
4419 * @return {String} The data decoded into a JavaScript UTF-16 string.
4420 */
4421lib.UTF8Decoder.prototype.decode = function(str) {
4422 var ret = '';
4423 for (var i = 0; i < str.length; i++) {
4424 var c = str.charCodeAt(i);
4425 if (this.bytesLeft == 0) {
4426 if (c <= 0x7F) {
4427 ret += str.charAt(i);
4428 } else if (0xC0 <= c && c <= 0xDF) {
4429 this.codePoint = c - 0xC0;
4430 this.bytesLeft = 1;
4431 this.lowerBound = 0x80;
4432 } else if (0xE0 <= c && c <= 0xEF) {
4433 this.codePoint = c - 0xE0;
4434 this.bytesLeft = 2;
4435 this.lowerBound = 0x800;
4436 } else if (0xF0 <= c && c <= 0xF7) {
4437 this.codePoint = c - 0xF0;
4438 this.bytesLeft = 3;
4439 this.lowerBound = 0x10000;
4440 } else if (0xF8 <= c && c <= 0xFB) {
4441 this.codePoint = c - 0xF8;
4442 this.bytesLeft = 4;
4443 this.lowerBound = 0x200000;
4444 } else if (0xFC <= c && c <= 0xFD) {
4445 this.codePoint = c - 0xFC;
4446 this.bytesLeft = 5;
4447 this.lowerBound = 0x4000000;
4448 } else {
4449 ret += '\ufffd';
4450 }
4451 } else {
4452 if (0x80 <= c && c <= 0xBF) {
4453 this.bytesLeft--;
4454 this.codePoint = (this.codePoint << 6) + (c - 0x80);
4455 if (this.bytesLeft == 0) {
4456 // Got a full sequence. Check if it's within bounds and
4457 // filter out surrogate pairs.
4458 var codePoint = this.codePoint;
4459 if (codePoint < this.lowerBound
4460 || (0xD800 <= codePoint && codePoint <= 0xDFFF)
4461 || codePoint > 0x10FFFF) {
4462 ret += '\ufffd';
4463 } else {
4464 // Encode as UTF-16 in the output.
4465 if (codePoint < 0x10000) {
4466 ret += String.fromCharCode(codePoint);
4467 } else {
4468 // Surrogate pair.
4469 codePoint -= 0x10000;
4470 ret += String.fromCharCode(
4471 0xD800 + ((codePoint >>> 10) & 0x3FF),
4472 0xDC00 + (codePoint & 0x3FF));
4473 }
4474 }
4475 }
4476 } else {
4477 // Too few bytes in multi-byte sequence. Rewind stream so we
4478 // don't lose the next byte.
4479 ret += '\ufffd';
4480 this.bytesLeft = 0;
4481 i--;
4482 }
4483 }
4484 }
4485 return ret;
4486};
4487
4488/**
4489 * Decodes UTF-8 data. This is a convenience function for when all the
4490 * data is already known.
4491 *
4492 * @param {String} str data to decode, represented as a JavaScript
4493 * String with each code unit representing a byte between 0x00 to
4494 * 0xFF.
4495 * @return {String} The data decoded into a JavaScript UTF-16 string.
4496 */
4497lib.decodeUTF8 = function(utf8) {
4498 return (new lib.UTF8Decoder()).decode(utf8);
4499};
4500
4501/**
4502 * Encodes a UTF-16 string into UTF-8.
4503 *
4504 * TODO(davidben): Do we need a stateful version of this that can
4505 * handle a surrogate pair split in two calls? What happens if a
4506 * keypress event would have contained a character outside the BMP?
4507 *
4508 * @param {String} str The string to encode.
4509 * @return {String} The string encoded as UTF-8, as a JavaScript
4510 * string with bytes represented as code units from 0x00 to 0xFF.
4511 */
4512lib.encodeUTF8 = function(str) {
4513 var ret = '';
4514 for (var i = 0; i < str.length; i++) {
4515 // Get a unicode code point out of str.
4516 var c = str.charCodeAt(i);
4517 if (0xDC00 <= c && c <= 0xDFFF) {
4518 c = 0xFFFD;
4519 } else if (0xD800 <= c && c <= 0xDBFF) {
4520 if (i+1 < str.length) {
4521 var d = str.charCodeAt(i+1);
4522 if (0xDC00 <= d && d <= 0xDFFF) {
4523 // Swallow a surrogate pair.
4524 c = 0x10000 + ((c & 0x3FF) << 10) + (d & 0x3FF);
4525 i++;
4526 } else {
4527 c = 0xFFFD;
4528 }
4529 } else {
4530 c = 0xFFFD;
4531 }
4532 }
4533
4534 // Encode c in UTF-8.
4535 var bytesLeft;
4536 if (c <= 0x7F) {
4537 ret += str.charAt(i);
4538 continue;
4539 } else if (c <= 0x7FF) {
4540 ret += String.fromCharCode(0xC0 | (c >>> 6));
4541 bytesLeft = 1;
4542 } else if (c <= 0xFFFF) {
4543 ret += String.fromCharCode(0xE0 | (c >>> 12));
4544 bytesLeft = 2;
4545 } else /* if (c <= 0x10FFFF) */ {
4546 ret += String.fromCharCode(0xF0 | (c >>> 18));
4547 bytesLeft = 3;
4548 }
4549
4550 while (bytesLeft > 0) {
4551 bytesLeft--;
4552 ret += String.fromCharCode(0x80 | ((c >>> (6 * bytesLeft)) & 0x3F));
4553 }
4554 }
4555 return ret;
4556};
4557// SOURCE FILE: libdot/js/lib_wc.js
4558// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
4559// Use of lib.wc source code is governed by a BSD-style license that can be
4560// found in the LICENSE file.
4561
4562'use strict';
4563
4564/**
4565 * This JavaScript library is ported from the wcwidth.js module of node.js.
4566 * The original implementation can be found at:
4567 * https://npmjs.org/package/wcwidth.js
4568 */
4569
4570/**
4571 * JavaScript porting of Markus Kuhn's wcwidth() implementation
4572 *
4573 * The following explanation comes from the original C implementation:
4574 *
4575 * This is an implementation of wcwidth() and wcswidth() (defined in
4576 * IEEE Std 1002.1-2001) for Unicode.
4577 *
4578 * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
4579 * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
4580 *
4581 * In fixed-width output devices, Latin characters all occupy a single
4582 * "cell" position of equal width, whereas ideographic CJK characters
4583 * occupy two such cells. Interoperability between terminal-line
4584 * applications and (teletype-style) character terminals using the
4585 * UTF-8 encoding requires agreement on which character should advance
4586 * the cursor by how many cell positions. No established formal
4587 * standards exist at present on which Unicode character shall occupy
4588 * how many cell positions on character terminals. These routines are
4589 * a first attempt of defining such behavior based on simple rules
4590 * applied to data provided by the Unicode Consortium.
4591 *
4592 * For some graphical characters, the Unicode standard explicitly
4593 * defines a character-cell width via the definition of the East Asian
4594 * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
4595 * In all these cases, there is no ambiguity about which width a
4596 * terminal shall use. For characters in the East Asian Ambiguous (A)
4597 * class, the width choice depends purely on a preference of backward
4598 * compatibility with either historic CJK or Western practice.
4599 * Choosing single-width for these characters is easy to justify as
4600 * the appropriate long-term solution, as the CJK practice of
4601 * displaying these characters as double-width comes from historic
4602 * implementation simplicity (8-bit encoded characters were displayed
4603 * single-width and 16-bit ones double-width, even for Greek,
4604 * Cyrillic, etc.) and not any typographic considerations.
4605 *
4606 * Much less clear is the choice of width for the Not East Asian
4607 * (Neutral) class. Existing practice does not dictate a width for any
4608 * of these characters. It would nevertheless make sense
4609 * typographically to allocate two character cells to characters such
4610 * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
4611 * represented adequately with a single-width glyph. The following
4612 * routines at present merely assign a single-cell width to all
4613 * neutral characters, in the interest of simplicity. This is not
4614 * entirely satisfactory and should be reconsidered before
4615 * establishing a formal standard in lib.wc area. At the moment, the
4616 * decision which Not East Asian (Neutral) characters should be
4617 * represented by double-width glyphs cannot yet be answered by
4618 * applying a simple rule from the Unicode database content. Setting
4619 * up a proper standard for the behavior of UTF-8 character terminals
4620 * will require a careful analysis not only of each Unicode character,
4621 * but also of each presentation form, something the author of these
4622 * routines has avoided to do so far.
4623 *
4624 * http://www.unicode.org/unicode/reports/tr11/
4625 *
4626 * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
4627 *
4628 * Permission to use, copy, modify, and distribute lib.wc software
4629 * for any purpose and without fee is hereby granted. The author
4630 * disclaims all warranties with regard to lib.wc software.
4631 *
4632 * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
4633 */
4634
4635/**
4636 * The following function defines the column width of an ISO 10646 character
4637 * as follows:
4638 *
4639 * - The null character (U+0000) has a column width of 0.
4640 * - Other C0/C1 control characters and DEL will lead to a return value of -1.
4641 * - Non-spacing and enclosing combining characters (general category code Mn
4642 * or Me in the Unicode database) have a column width of 0.
4643 * - SOFT HYPHEN (U+00AD) has a column width of 1.
4644 * - Other format characters (general category code Cf in the Unicode database)
4645 * and ZERO WIDTH SPACE (U+200B) have a column width of 0.
4646 * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) have a
4647 * column width of 0.
4648 * - Spacing characters in the East Asian Wide (W) or East Asian Full-width (F)
4649 * category as defined in Unicode Technical Report #11 have a column width of
4650 * 2.
4651 * - East Asian Ambiguous characters are taken into account if
4652 * regardCjkAmbiguous flag is enabled. They have a column width of 2.
4653 * - All remaining characters (including all printable ISO 8859-1 and WGL4
4654 * characters, Unicode control characters, etc.) have a column width of 1.
4655 *
4656 * This implementation assumes that characters are encoded in ISO 10646.
4657 */
4658
4659/**
4660 * This library relies on the use of codePointAt, which is not supported in
4661 * all browsers. Polyfill if not. See
4662 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt#Polyfill
4663 */
4664if (!String.prototype.codePointAt) {
4665 (function() {
4666 'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
4667 var codePointAt = function(position) {
4668 if (this == null) {
4669 throw TypeError();
4670 }
4671 var string = String(this);
4672 var size = string.length;
4673 // `ToInteger`
4674 var index = position ? Number(position) : 0;
4675 if (index != index) { // better `isNaN`
4676 index = 0;
4677 }
4678 // Account for out-of-bounds indices:
4679 if (index < 0 || index >= size) {
4680 return undefined;
4681 }
4682 // Get the first code unit
4683 var first = string.charCodeAt(index);
4684 var second;
4685 if ( // check if it’s the start of a surrogate pair
4686 first >= 0xD800 && first <= 0xDBFF && // high surrogate
4687 size > index + 1 // there is a next code unit
4688 ) {
4689 second = string.charCodeAt(index + 1);
4690 if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
4691 // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
4692 return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
4693 }
4694 }
4695 return first;
4696 };
4697 if (Object.defineProperty) {
4698 Object.defineProperty(String.prototype, 'codePointAt', {
4699 'value': codePointAt,
4700 'configurable': true,
4701 'writable': true
4702 });
4703 } else {
4704 String.prototype.codePointAt = codePointAt;
4705 }
4706 }());
4707}
4708
4709lib.wc = {};
4710
4711// Width of a nul character.
4712lib.wc.nulWidth = 0;
4713
4714// Width of a control character.
4715lib.wc.controlWidth = 0;
4716
4717// Flag whether to consider East Asian Ambiguous characters.
4718lib.wc.regardCjkAmbiguous = false;
4719
4720// Width of an East Asian Ambiguous character.
4721lib.wc.cjkAmbiguousWidth = 2;
4722
4723// Sorted list of non-overlapping intervals of non-spacing characters
4724// generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c"
4725lib.wc.combining = [
4726 [ 0x0300, 0x036F ], [ 0x0483, 0x0486 ], [ 0x0488, 0x0489 ],
4727 [ 0x0591, 0x05BD ], [ 0x05BF, 0x05BF ], [ 0x05C1, 0x05C2 ],
4728 [ 0x05C4, 0x05C5 ], [ 0x05C7, 0x05C7 ], [ 0x0600, 0x0603 ],
4729 [ 0x0610, 0x0615 ], [ 0x064B, 0x065E ], [ 0x0670, 0x0670 ],
4730 [ 0x06D6, 0x06E4 ], [ 0x06E7, 0x06E8 ], [ 0x06EA, 0x06ED ],
4731 [ 0x070F, 0x070F ], [ 0x0711, 0x0711 ], [ 0x0730, 0x074A ],
4732 [ 0x07A6, 0x07B0 ], [ 0x07EB, 0x07F3 ], [ 0x0901, 0x0902 ],
4733 [ 0x093C, 0x093C ], [ 0x0941, 0x0948 ], [ 0x094D, 0x094D ],
4734 [ 0x0951, 0x0954 ], [ 0x0962, 0x0963 ], [ 0x0981, 0x0981 ],
4735 [ 0x09BC, 0x09BC ], [ 0x09C1, 0x09C4 ], [ 0x09CD, 0x09CD ],
4736 [ 0x09E2, 0x09E3 ], [ 0x0A01, 0x0A02 ], [ 0x0A3C, 0x0A3C ],
4737 [ 0x0A41, 0x0A42 ], [ 0x0A47, 0x0A48 ], [ 0x0A4B, 0x0A4D ],
4738 [ 0x0A70, 0x0A71 ], [ 0x0A81, 0x0A82 ], [ 0x0ABC, 0x0ABC ],
4739 [ 0x0AC1, 0x0AC5 ], [ 0x0AC7, 0x0AC8 ], [ 0x0ACD, 0x0ACD ],
4740 [ 0x0AE2, 0x0AE3 ], [ 0x0B01, 0x0B01 ], [ 0x0B3C, 0x0B3C ],
4741 [ 0x0B3F, 0x0B3F ], [ 0x0B41, 0x0B43 ], [ 0x0B4D, 0x0B4D ],
4742 [ 0x0B56, 0x0B56 ], [ 0x0B82, 0x0B82 ], [ 0x0BC0, 0x0BC0 ],
4743 [ 0x0BCD, 0x0BCD ], [ 0x0C3E, 0x0C40 ], [ 0x0C46, 0x0C48 ],
4744 [ 0x0C4A, 0x0C4D ], [ 0x0C55, 0x0C56 ], [ 0x0CBC, 0x0CBC ],
4745 [ 0x0CBF, 0x0CBF ], [ 0x0CC6, 0x0CC6 ], [ 0x0CCC, 0x0CCD ],
4746 [ 0x0CE2, 0x0CE3 ], [ 0x0D41, 0x0D43 ], [ 0x0D4D, 0x0D4D ],
4747 [ 0x0DCA, 0x0DCA ], [ 0x0DD2, 0x0DD4 ], [ 0x0DD6, 0x0DD6 ],
4748 [ 0x0E31, 0x0E31 ], [ 0x0E34, 0x0E3A ], [ 0x0E47, 0x0E4E ],
4749 [ 0x0EB1, 0x0EB1 ], [ 0x0EB4, 0x0EB9 ], [ 0x0EBB, 0x0EBC ],
4750 [ 0x0EC8, 0x0ECD ], [ 0x0F18, 0x0F19 ], [ 0x0F35, 0x0F35 ],
4751 [ 0x0F37, 0x0F37 ], [ 0x0F39, 0x0F39 ], [ 0x0F71, 0x0F7E ],
4752 [ 0x0F80, 0x0F84 ], [ 0x0F86, 0x0F87 ], [ 0x0F90, 0x0F97 ],
4753 [ 0x0F99, 0x0FBC ], [ 0x0FC6, 0x0FC6 ], [ 0x102D, 0x1030 ],
4754 [ 0x1032, 0x1032 ], [ 0x1036, 0x1037 ], [ 0x1039, 0x1039 ],
4755 [ 0x1058, 0x1059 ], [ 0x1160, 0x11FF ], [ 0x135F, 0x135F ],
4756 [ 0x1712, 0x1714 ], [ 0x1732, 0x1734 ], [ 0x1752, 0x1753 ],
4757 [ 0x1772, 0x1773 ], [ 0x17B4, 0x17B5 ], [ 0x17B7, 0x17BD ],
4758 [ 0x17C6, 0x17C6 ], [ 0x17C9, 0x17D3 ], [ 0x17DD, 0x17DD ],
4759 [ 0x180B, 0x180D ], [ 0x18A9, 0x18A9 ], [ 0x1920, 0x1922 ],
4760 [ 0x1927, 0x1928 ], [ 0x1932, 0x1932 ], [ 0x1939, 0x193B ],
4761 [ 0x1A17, 0x1A18 ], [ 0x1B00, 0x1B03 ], [ 0x1B34, 0x1B34 ],
4762 [ 0x1B36, 0x1B3A ], [ 0x1B3C, 0x1B3C ], [ 0x1B42, 0x1B42 ],
4763 [ 0x1B6B, 0x1B73 ], [ 0x1DC0, 0x1DCA ], [ 0x1DFE, 0x1DFF ],
4764 [ 0x200B, 0x200F ], [ 0x202A, 0x202E ], [ 0x2060, 0x2063 ],
4765 [ 0x206A, 0x206F ], [ 0x20D0, 0x20EF ], [ 0x302A, 0x302F ],
4766 [ 0x3099, 0x309A ], [ 0xA806, 0xA806 ], [ 0xA80B, 0xA80B ],
4767 [ 0xA825, 0xA826 ], [ 0xFB1E, 0xFB1E ], [ 0xFE00, 0xFE0F ],
4768 [ 0xFE20, 0xFE23 ], [ 0xFEFF, 0xFEFF ], [ 0xFFF9, 0xFFFB ],
4769 [ 0x10A01, 0x10A03 ], [ 0x10A05, 0x10A06 ], [ 0x10A0C, 0x10A0F ],
4770 [ 0x10A38, 0x10A3A ], [ 0x10A3F, 0x10A3F ], [ 0x1D167, 0x1D169 ],
4771 [ 0x1D173, 0x1D182 ], [ 0x1D185, 0x1D18B ], [ 0x1D1AA, 0x1D1AD ],
4772 [ 0x1D242, 0x1D244 ], [ 0xE0001, 0xE0001 ], [ 0xE0020, 0xE007F ],
4773 [ 0xE0100, 0xE01EF ]
4774];
4775
4776// Sorted list of non-overlapping intervals of East Asian Ambiguous characters
4777// generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c"
4778lib.wc.ambiguous = [
4779 [ 0x00A1, 0x00A1 ], [ 0x00A4, 0x00A4 ], [ 0x00A7, 0x00A8 ],
4780 [ 0x00AA, 0x00AA ], [ 0x00AE, 0x00AE ], [ 0x00B0, 0x00B4 ],
4781 [ 0x00B6, 0x00BA ], [ 0x00BC, 0x00BF ], [ 0x00C6, 0x00C6 ],
4782 [ 0x00D0, 0x00D0 ], [ 0x00D7, 0x00D8 ], [ 0x00DE, 0x00E1 ],
4783 [ 0x00E6, 0x00E6 ], [ 0x00E8, 0x00EA ], [ 0x00EC, 0x00ED ],
4784 [ 0x00F0, 0x00F0 ], [ 0x00F2, 0x00F3 ], [ 0x00F7, 0x00FA ],
4785 [ 0x00FC, 0x00FC ], [ 0x00FE, 0x00FE ], [ 0x0101, 0x0101 ],
4786 [ 0x0111, 0x0111 ], [ 0x0113, 0x0113 ], [ 0x011B, 0x011B ],
4787 [ 0x0126, 0x0127 ], [ 0x012B, 0x012B ], [ 0x0131, 0x0133 ],
4788 [ 0x0138, 0x0138 ], [ 0x013F, 0x0142 ], [ 0x0144, 0x0144 ],
4789 [ 0x0148, 0x014B ], [ 0x014D, 0x014D ], [ 0x0152, 0x0153 ],
4790 [ 0x0166, 0x0167 ], [ 0x016B, 0x016B ], [ 0x01CE, 0x01CE ],
4791 [ 0x01D0, 0x01D0 ], [ 0x01D2, 0x01D2 ], [ 0x01D4, 0x01D4 ],
4792 [ 0x01D6, 0x01D6 ], [ 0x01D8, 0x01D8 ], [ 0x01DA, 0x01DA ],
4793 [ 0x01DC, 0x01DC ], [ 0x0251, 0x0251 ], [ 0x0261, 0x0261 ],
4794 [ 0x02C4, 0x02C4 ], [ 0x02C7, 0x02C7 ], [ 0x02C9, 0x02CB ],
4795 [ 0x02CD, 0x02CD ], [ 0x02D0, 0x02D0 ], [ 0x02D8, 0x02DB ],
4796 [ 0x02DD, 0x02DD ], [ 0x02DF, 0x02DF ], [ 0x0391, 0x03A1 ],
4797 [ 0x03A3, 0x03A9 ], [ 0x03B1, 0x03C1 ], [ 0x03C3, 0x03C9 ],
4798 [ 0x0401, 0x0401 ], [ 0x0410, 0x044F ], [ 0x0451, 0x0451 ],
4799 [ 0x2010, 0x2010 ], [ 0x2013, 0x2016 ], [ 0x2018, 0x2019 ],
4800 [ 0x201C, 0x201D ], [ 0x2020, 0x2022 ], [ 0x2024, 0x2027 ],
4801 [ 0x2030, 0x2030 ], [ 0x2032, 0x2033 ], [ 0x2035, 0x2035 ],
4802 [ 0x203B, 0x203B ], [ 0x203E, 0x203E ], [ 0x2074, 0x2074 ],
4803 [ 0x207F, 0x207F ], [ 0x2081, 0x2084 ], [ 0x20AC, 0x20AC ],
4804 [ 0x2103, 0x2103 ], [ 0x2105, 0x2105 ], [ 0x2109, 0x2109 ],
4805 [ 0x2113, 0x2113 ], [ 0x2116, 0x2116 ], [ 0x2121, 0x2122 ],
4806 [ 0x2126, 0x2126 ], [ 0x212B, 0x212B ], [ 0x2153, 0x2154 ],
4807 [ 0x215B, 0x215E ], [ 0x2160, 0x216B ], [ 0x2170, 0x2179 ],
4808 [ 0x2190, 0x2199 ], [ 0x21B8, 0x21B9 ], [ 0x21D2, 0x21D2 ],
4809 [ 0x21D4, 0x21D4 ], [ 0x21E7, 0x21E7 ], [ 0x2200, 0x2200 ],
4810 [ 0x2202, 0x2203 ], [ 0x2207, 0x2208 ], [ 0x220B, 0x220B ],
4811 [ 0x220F, 0x220F ], [ 0x2211, 0x2211 ], [ 0x2215, 0x2215 ],
4812 [ 0x221A, 0x221A ], [ 0x221D, 0x2220 ], [ 0x2223, 0x2223 ],
4813 [ 0x2225, 0x2225 ], [ 0x2227, 0x222C ], [ 0x222E, 0x222E ],
4814 [ 0x2234, 0x2237 ], [ 0x223C, 0x223D ], [ 0x2248, 0x2248 ],
4815 [ 0x224C, 0x224C ], [ 0x2252, 0x2252 ], [ 0x2260, 0x2261 ],
4816 [ 0x2264, 0x2267 ], [ 0x226A, 0x226B ], [ 0x226E, 0x226F ],
4817 [ 0x2282, 0x2283 ], [ 0x2286, 0x2287 ], [ 0x2295, 0x2295 ],
4818 [ 0x2299, 0x2299 ], [ 0x22A5, 0x22A5 ], [ 0x22BF, 0x22BF ],
4819 [ 0x2312, 0x2312 ], [ 0x2460, 0x24E9 ], [ 0x24EB, 0x254B ],
4820 [ 0x2550, 0x2573 ], [ 0x2580, 0x258F ], [ 0x2592, 0x2595 ],
4821 [ 0x25A0, 0x25A1 ], [ 0x25A3, 0x25A9 ], [ 0x25B2, 0x25B3 ],
4822 [ 0x25B6, 0x25B7 ], [ 0x25BC, 0x25BD ], [ 0x25C0, 0x25C1 ],
4823 [ 0x25C6, 0x25C8 ], [ 0x25CB, 0x25CB ], [ 0x25CE, 0x25D1 ],
4824 [ 0x25E2, 0x25E5 ], [ 0x25EF, 0x25EF ], [ 0x2605, 0x2606 ],
4825 [ 0x2609, 0x2609 ], [ 0x260E, 0x260F ], [ 0x2614, 0x2615 ],
4826 [ 0x261C, 0x261C ], [ 0x261E, 0x261E ], [ 0x2640, 0x2640 ],
4827 [ 0x2642, 0x2642 ], [ 0x2660, 0x2661 ], [ 0x2663, 0x2665 ],
4828 [ 0x2667, 0x266A ], [ 0x266C, 0x266D ], [ 0x266F, 0x266F ],
4829 [ 0x273D, 0x273D ], [ 0x2776, 0x277F ], [ 0xE000, 0xF8FF ],
4830 [ 0xFFFD, 0xFFFD ], [ 0xF0000, 0xFFFFD ], [ 0x100000, 0x10FFFD ]
4831];
4832
4833/**
4834 * Binary search to check if the given unicode character is a space character.
4835 *
4836 * @param {integer} ucs A unicode character code.
4837 *
4838 * @return {boolean} True if the given character is a space character; false
4839 * otherwise.
4840 */
4841lib.wc.isSpace = function(ucs) {
4842 // Auxiliary function for binary search in interval table.
4843 var min = 0, max = lib.wc.combining.length - 1;
4844 var mid;
4845
4846 if (ucs < lib.wc.combining[0][0] || ucs > lib.wc.combining[max][1])
4847 return false;
4848 while (max >= min) {
4849 mid = Math.floor((min + max) / 2);
4850 if (ucs > lib.wc.combining[mid][1]) {
4851 min = mid + 1;
4852 } else if (ucs < lib.wc.combining[mid][0]) {
4853 max = mid - 1;
4854 } else {
4855 return true;
4856 }
4857 }
4858
4859 return false;
4860};
4861
4862/**
4863 * Auxiliary function for checking if the given unicode character is a East
4864 * Asian Ambiguous character.
4865 *
4866 * @param {integer} ucs A unicode character code.
4867 *
4868 * @return {boolean} True if the given character is a East Asian Ambiguous
4869 * character.
4870 */
4871lib.wc.isCjkAmbiguous = function(ucs) {
4872 var min = 0, max = lib.wc.ambiguous.length - 1;
4873 var mid;
4874
4875 if (ucs < lib.wc.ambiguous[0][0] || ucs > lib.wc.ambiguous[max][1])
4876 return false;
4877 while (max >= min) {
4878 mid = Math.floor((min + max) / 2);
4879 if (ucs > lib.wc.ambiguous[mid][1]) {
4880 min = mid + 1;
4881 } else if (ucs < lib.wc.ambiguous[mid][0]) {
4882 max = mid - 1;
4883 } else {
4884 return true;
4885 }
4886 }
4887
4888 return false;
4889};
4890
4891/**
4892 * Determine the column width of the given character.
4893 *
4894 * @param {integer} ucs A unicode character code.
4895 *
4896 * @return {integer} The column width of the given character.
4897 */
4898lib.wc.charWidth = function(ucs) {
4899 if (lib.wc.regardCjkAmbiguous) {
4900 return lib.wc.charWidthRegardAmbiguous(ucs);
4901 } else {
4902 return lib.wc.charWidthDisregardAmbiguous(ucs);
4903 }
4904};
4905
4906/**
4907 * Determine the column width of the given character without considering East
4908 * Asian Ambiguous characters.
4909 *
4910 * @param {integer} ucs A unicode character code.
4911 *
4912 * @return {integer} The column width of the given character.
4913 */
4914lib.wc.charWidthDisregardAmbiguous = function(ucs) {
4915 // Test for 8-bit control characters.
4916 if (ucs === 0)
4917 return lib.wc.nulWidth;
4918 if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
4919 return lib.wc.controlWidth;
4920
4921 // Optimize for ASCII characters.
4922 if (ucs < 0x7f)
4923 return 1;
4924
4925 // Binary search in table of non-spacing characters.
4926 if (lib.wc.isSpace(ucs))
4927 return 0;
4928
4929 // If we arrive here, ucs is not a combining or C0/C1 control character.
4930 return 1 +
4931 (ucs >= 0x1100 &&
4932 (ucs <= 0x115f || // Hangul Jamo init. consonants
4933 ucs == 0x2329 || ucs == 0x232a ||
4934 (ucs >= 0x2e80 && ucs <= 0xa4cf &&
4935 ucs != 0x303f) || // CJK ... Yi
4936 (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
4937 (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compatibility Ideographs
4938 (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
4939 (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compatibility Forms
4940 (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
4941 (ucs >= 0xffe0 && ucs <= 0xffe6) ||
4942 (ucs >= 0x20000 && ucs <= 0x2fffd) ||
4943 (ucs >= 0x30000 && ucs <= 0x3fffd)));
4944 // TODO: emoji characters usually require space for wide characters although
4945 // East Asian width spec says nothing. Should we add special cases for them?
4946};
4947
4948/**
4949 * Determine the column width of the given character considering East Asian
4950 * Ambiguous characters.
4951 *
4952 * @param {integer} ucs A unicode character code.
4953 *
4954 * @return {integer} The column width of the given character.
4955 */
4956lib.wc.charWidthRegardAmbiguous = function(ucs) {
4957 if (lib.wc.isCjkAmbiguous(ucs))
4958 return lib.wc.cjkAmbiguousWidth;
4959
4960 return lib.wc.charWidthDisregardAmbiguous(ucs);
4961};
4962
4963/**
4964 * Determine the column width of the given string.
4965 *
4966 * @param {string} str A string.
4967 *
4968 * @return {integer} The column width of the given string.
4969 */
4970lib.wc.strWidth = function(str) {
4971 var width, rv = 0;
4972
4973 for (var i = 0; i < str.length;) {
4974 var codePoint = str.codePointAt(i);
4975 width = lib.wc.charWidth(codePoint);
4976 if (width < 0)
4977 return -1;
4978 rv += width;
4979 i += (codePoint <= 0xffff) ? 1 : 2;
4980 }
4981
4982 return rv;
4983};
4984
4985/**
4986 * Get the substring at the given column offset of the given column width.
4987 *
4988 * @param {string} str The string to get substring from.
4989 * @param {integer} start The starting column offset to get substring.
4990 * @param {integer} opt_width The column width of the substring.
4991 *
4992 * @return {string} The substring.
4993 */
4994lib.wc.substr = function(str, start, opt_width) {
4995 var startIndex, endIndex, width;
4996
4997 for (startIndex = 0, width = 0; startIndex < str.length; startIndex++) {
4998 width += lib.wc.charWidth(str.charCodeAt(startIndex));
4999 if (width > start)
5000 break;
5001 }
5002
5003 if (opt_width != undefined) {
5004 for (endIndex = startIndex, width = 0;
5005 endIndex < str.length && width < opt_width;
5006 width += lib.wc.charWidth(str.charCodeAt(endIndex)), endIndex++);
5007 if (width > opt_width)
5008 endIndex--;
5009 return str.substring(startIndex, endIndex);
5010 }
5011
5012 return str.substr(startIndex);
5013};
5014
5015/**
5016 * Get substring at the given start and end column offset.
5017 *
5018 * @param {string} str The string to get substring from.
5019 * @param {integer} start The starting column offset.
5020 * @param {integer} end The ending column offset.
5021 *
5022 * @return {string} The substring.
5023 */
5024lib.wc.substring = function(str, start, end) {
5025 return lib.wc.substr(str, start, end - start);
5026};
5027lib.resource.add('libdot/changelog/version', 'text/plain',
5028'1.11' +
5029''
5030);
5031
5032lib.resource.add('libdot/changelog/date', 'text/plain',
5033'2017-04-17' +
5034''
5035);
5036
5037// SOURCE FILE: hterm/js/hterm.js
5038// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
5039// Use of this source code is governed by a BSD-style license that can be
5040// found in the LICENSE file.
5041
5042'use strict';
5043
5044lib.rtdep('lib.Storage');
5045
5046/**
5047 * @fileoverview Declares the hterm.* namespace and some basic shared utilities
5048 * that are too small to deserve dedicated files.
5049 */
5050var hterm = {};
5051
5052/**
5053 * The type of window hosting hterm.
5054 *
5055 * This is set as part of hterm.init(). The value is invalid until
5056 * initialization completes.
5057 */
5058hterm.windowType = null;
5059
5060/**
5061 * Warning message to display in the terminal when browser zoom is enabled.
5062 *
5063 * You can replace it with your own localized message.
5064 */
5065hterm.zoomWarningMessage = 'ZOOM != 100%';
5066
5067/**
5068 * Brief overlay message displayed when text is copied to the clipboard.
5069 *
5070 * By default it is the unicode BLACK SCISSORS character, but you can
5071 * replace it with your own localized message.
5072 *
5073 * This is only displayed when the 'enable-clipboard-notice' preference
5074 * is enabled.
5075 */
5076hterm.notifyCopyMessage = '\u2702';
5077
5078
5079/**
5080 * Text shown in a desktop notification for the terminal
5081 * bell. \u226a is a unicode EIGHTH NOTE, %(title) will
5082 * be replaced by the terminal title.
5083 */
5084hterm.desktopNotificationTitle = '\u266A %(title) \u266A';
5085
5086/**
5087 * List of known hterm test suites.
5088 *
5089 * A test harness should ensure that they all exist before running.
5090 */
5091hterm.testDeps = ['hterm.ScrollPort.Tests', 'hterm.Screen.Tests',
5092 'hterm.Terminal.Tests', 'hterm.VT.Tests',
5093 'hterm.VT.CannedTests'];
5094
5095/**
5096 * The hterm init function, registered with lib.registerInit().
5097 *
5098 * This is called during lib.init().
5099 *
5100 * @param {function} onInit The function lib.init() wants us to invoke when
5101 * initialization is complete.
5102 */
5103lib.registerInit('hterm', function(onInit) {
5104 function onWindow(window) {
5105 hterm.windowType = window.type;
5106 setTimeout(onInit, 0);
5107 }
5108
5109 function onTab(tab) {
5110 if (tab && window.chrome) {
5111 chrome.windows.get(tab.windowId, null, onWindow);
5112 } else {
5113 // TODO(rginda): This is where we end up for a v1 app's background page.
5114 // Maybe windowType = 'none' would be more appropriate, or something.
5115 hterm.windowType = 'normal';
5116 setTimeout(onInit, 0);
5117 }
5118 }
5119
5120 if (!hterm.defaultStorage) {
5121 var ary = navigator.userAgent.match(/\sChrome\/(\d\d)/);
5122 var version = ary ? parseInt(ary[1]) : -1;
5123 if (window.chrome && chrome.storage && chrome.storage.sync &&
5124 version > 21) {
5125 hterm.defaultStorage = new lib.Storage.Chrome(chrome.storage.sync);
5126 } else {
5127 hterm.defaultStorage = new lib.Storage.Local();
5128 }
5129 }
5130
5131 // The chrome.tabs API is not supported in packaged apps, and detecting if
5132 // you're a packaged app is a little awkward.
5133 var isPackagedApp = false;
5134 if (window.chrome && chrome.runtime && chrome.runtime.getManifest) {
5135 var manifest = chrome.runtime.getManifest();
5136 isPackagedApp = manifest.app && manifest.app.background;
5137 }
5138
5139 if (isPackagedApp) {
5140 // Packaged apps are never displayed in browser tabs.
5141 setTimeout(onWindow.bind(null, {type: 'popup'}), 0);
5142 } else {
5143 if (window.chrome && chrome.tabs) {
5144 // The getCurrent method gets the tab that is "currently running", not the
5145 // topmost or focused tab.
5146 chrome.tabs.getCurrent(onTab);
5147 } else {
5148 setTimeout(onWindow.bind(null, {type: 'normal'}), 0);
5149 }
5150 }
5151});
5152
5153/**
5154 * Return decimal { width, height } for a given dom node.
5155 */
5156hterm.getClientSize = function(dom) {
5157 return dom.getBoundingClientRect();
5158};
5159
5160/**
5161 * Return decimal width for a given dom node.
5162 */
5163hterm.getClientWidth = function(dom) {
5164 return dom.getBoundingClientRect().width;
5165};
5166
5167/**
5168 * Return decimal height for a given dom node.
5169 */
5170hterm.getClientHeight = function(dom) {
5171 return dom.getBoundingClientRect().height;
5172};
5173
5174/**
5175 * Copy the current selection to the system clipboard.
5176 *
5177 * @param {HTMLDocument} The document with the selection to copy.
5178 */
5179hterm.copySelectionToClipboard = function(document) {
5180 try {
5181 document.execCommand('copy');
5182 } catch (firefoxException) {
5183 // Ignore this. FF throws an exception if there was an error, even though
5184 // the spec says just return false.
5185 }
5186};
5187
5188/**
5189 * Paste the system clipboard into the element with focus.
5190 *
5191 * @param {HTMLDocument} The document to paste into.
5192 */
5193hterm.pasteFromClipboard = function(document) {
5194 try {
5195 document.execCommand('paste');
5196 } catch (firefoxException) {
5197 // Ignore this. FF throws an exception if there was an error, even though
5198 // the spec says just return false.
5199 }
5200};
5201
5202/**
5203 * Constructor for a hterm.Size record.
5204 *
5205 * Instances of this class have public read/write members for width and height.
5206 *
5207 * @param {integer} width The width of this record.
5208 * @param {integer} height The height of this record.
5209 */
5210hterm.Size = function(width, height) {
5211 this.width = width;
5212 this.height = height;
5213};
5214
5215/**
5216 * Adjust the width and height of this record.
5217 *
5218 * @param {integer} width The new width of this record.
5219 * @param {integer} height The new height of this record.
5220 */
5221hterm.Size.prototype.resize = function(width, height) {
5222 this.width = width;
5223 this.height = height;
5224};
5225
5226/**
5227 * Return a copy of this record.
5228 *
5229 * @return {hterm.Size} A new hterm.Size instance with the same width and
5230 * height.
5231 */
5232hterm.Size.prototype.clone = function() {
5233 return new hterm.Size(this.width, this.height);
5234};
5235
5236/**
5237 * Set the height and width of this instance based on another hterm.Size.
5238 *
5239 * @param {hterm.Size} that The object to copy from.
5240 */
5241hterm.Size.prototype.setTo = function(that) {
5242 this.width = that.width;
5243 this.height = that.height;
5244};
5245
5246/**
5247 * Test if another hterm.Size instance is equal to this one.
5248 *
5249 * @param {hterm.Size} that The other hterm.Size instance.
5250 * @return {boolean} True if both instances have the same width/height, false
5251 * otherwise.
5252 */
5253hterm.Size.prototype.equals = function(that) {
5254 return this.width == that.width && this.height == that.height;
5255};
5256
5257/**
5258 * Return a string representation of this instance.
5259 *
5260 * @return {string} A string that identifies the width and height of this
5261 * instance.
5262 */
5263hterm.Size.prototype.toString = function() {
5264 return '[hterm.Size: ' + this.width + ', ' + this.height + ']';
5265};
5266
5267/**
5268 * Constructor for a hterm.RowCol record.
5269 *
5270 * Instances of this class have public read/write members for row and column.
5271 *
5272 * This class includes an 'overflow' bit which is use to indicate that an
5273 * attempt has been made to move the cursor column passed the end of the
5274 * screen. When this happens we leave the cursor column set to the last column
5275 * of the screen but set the overflow bit. In this state cursor movement
5276 * happens normally, but any attempt to print new characters causes a cr/lf
5277 * first.
5278 *
5279 * @param {integer} row The row of this record.
5280 * @param {integer} column The column of this record.
5281 * @param {boolean} opt_overflow Optional boolean indicating that the RowCol
5282 * has overflowed.
5283 */
5284hterm.RowCol = function(row, column, opt_overflow) {
5285 this.row = row;
5286 this.column = column;
5287 this.overflow = !!opt_overflow;
5288};
5289
5290/**
5291 * Adjust the row and column of this record.
5292 *
5293 * @param {integer} row The new row of this record.
5294 * @param {integer} column The new column of this record.
5295 * @param {boolean} opt_overflow Optional boolean indicating that the RowCol
5296 * has overflowed.
5297 */
5298hterm.RowCol.prototype.move = function(row, column, opt_overflow) {
5299 this.row = row;
5300 this.column = column;
5301 this.overflow = !!opt_overflow;
5302};
5303
5304/**
5305 * Return a copy of this record.
5306 *
5307 * @return {hterm.RowCol} A new hterm.RowCol instance with the same row and
5308 * column.
5309 */
5310hterm.RowCol.prototype.clone = function() {
5311 return new hterm.RowCol(this.row, this.column, this.overflow);
5312};
5313
5314/**
5315 * Set the row and column of this instance based on another hterm.RowCol.
5316 *
5317 * @param {hterm.RowCol} that The object to copy from.
5318 */
5319hterm.RowCol.prototype.setTo = function(that) {
5320 this.row = that.row;
5321 this.column = that.column;
5322 this.overflow = that.overflow;
5323};
5324
5325/**
5326 * Test if another hterm.RowCol instance is equal to this one.
5327 *
5328 * @param {hterm.RowCol} that The other hterm.RowCol instance.
5329 * @return {boolean} True if both instances have the same row/column, false
5330 * otherwise.
5331 */
5332hterm.RowCol.prototype.equals = function(that) {
5333 return (this.row == that.row && this.column == that.column &&
5334 this.overflow == that.overflow);
5335};
5336
5337/**
5338 * Return a string representation of this instance.
5339 *
5340 * @return {string} A string that identifies the row and column of this
5341 * instance.
5342 */
5343hterm.RowCol.prototype.toString = function() {
5344 return ('[hterm.RowCol: ' + this.row + ', ' + this.column + ', ' +
5345 this.overflow + ']');
5346};
5347// SOURCE FILE: hterm/js/hterm_frame.js
5348// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
5349// Use of this source code is governed by a BSD-style license that can be
5350// found in the LICENSE file.
5351
5352'use strict';
5353
5354lib.rtdep('lib.f');
5355
5356/**
5357 * First draft of the interface between the terminal and a third party dialog.
5358 *
5359 * This is rough. It's just the terminal->dialog layer. To complete things
5360 * we'll also need a command->terminal layer. That will have to facilitate
5361 * command->terminal->dialog or direct command->dialog communication.
5362 *
5363 * I imagine this class will change significantly when that happens.
5364 */
5365
5366/**
5367 * Construct a new frame for the given terminal.
5368 *
5369 * @param terminal {hterm.Terminal} The parent terminal object.
5370 * @param url {String} The url to load in the frame.
5371 * @param opt_options {Object} Optional options for the frame. Not implemented.
5372 */
5373hterm.Frame = function(terminal, url, opt_options) {
5374 this.terminal_ = terminal;
5375 this.div_ = terminal.div_;
5376 this.url = url;
5377 this.options = opt_options || {};
5378 this.iframe_ = null;
5379 this.container_ = null;
5380 this.messageChannel_ = null;
5381};
5382
5383/**
5384 * Handle messages from the iframe.
5385 */
5386hterm.Frame.prototype.onMessage_ = function(e) {
5387 if (e.data.name != 'ipc-init-ok') {
5388 console.log('Unknown message from frame:', e.data);
5389 return;
5390 }
5391
5392 this.sendTerminalInfo_();
5393 this.messageChannel_.port1.onmessage = this.onMessage.bind(this);
5394 this.onLoad();
5395};
5396
5397/**
5398 * Clients could override this, I guess.
5399 *
5400 * It doesn't support multiple listeners, but I'm not sure that would make sense
5401 * here. It's probably better to speak directly to our parents.
5402 */
5403hterm.Frame.prototype.onMessage = function() {};
5404
5405/**
5406 * Handle iframe onLoad event.
5407 */
5408hterm.Frame.prototype.onLoad_ = function() {
5409 this.messageChannel_ = new MessageChannel();
5410 this.messageChannel_.port1.onmessage = this.onMessage_.bind(this);
5411 this.messageChannel_.port1.start();
5412 this.iframe_.contentWindow.postMessage(
5413 {name: 'ipc-init', argv: [{messagePort: this.messageChannel_.port2}]},
5414 this.url, [this.messageChannel_.port2]);
5415};
5416
5417/**
5418 * Clients may override this.
5419 */
5420hterm.Frame.prototype.onLoad = function() {};
5421
5422/**
5423 * Sends the terminal-info message to the iframe.
5424 */
5425hterm.Frame.prototype.sendTerminalInfo_ = function() {
5426 lib.f.getAcceptLanguages(function(languages) {
5427 this.postMessage('terminal-info', [{
5428 acceptLanguages: languages,
5429 foregroundColor: this.terminal_.getForegroundColor(),
5430 backgroundColor: this.terminal_.getBackgroundColor(),
5431 cursorColor: this.terminal_.getCursorColor(),
5432 fontSize: this.terminal_.getFontSize(),
5433 fontFamily: this.terminal_.getFontFamily(),
5434 baseURL: lib.f.getURL('/')
5435 }]
5436 );
5437 }.bind(this));
5438};
5439
5440/**
5441 * User clicked the close button on the frame decoration.
5442 */
5443hterm.Frame.prototype.onCloseClicked_ = function() {
5444 this.close();
5445};
5446
5447/**
5448 * Close this frame.
5449 */
5450hterm.Frame.prototype.close = function() {
5451 if (!this.container_ || !this.container_.parentNode)
5452 return;
5453
5454 this.container_.parentNode.removeChild(this.container_);
5455 this.onClose();
5456};
5457
5458
5459/**
5460 * Clients may override this.
5461 */
5462hterm.Frame.prototype.onClose = function() {};
5463
5464/**
5465 * Send a message to the iframe.
5466 */
5467hterm.Frame.prototype.postMessage = function(name, argv) {
5468 if (!this.messageChannel_)
5469 throw new Error('Message channel is not set up.');
5470
5471 this.messageChannel_.port1.postMessage({name: name, argv: argv});
5472};
5473
5474/**
5475 * Show the UI for this frame.
5476 *
5477 * The iframe src is not loaded until this method is called.
5478 */
5479hterm.Frame.prototype.show = function() {
5480 var self = this;
5481
5482 function opt(name, defaultValue) {
5483 if (name in self.options)
5484 return self.options[name];
5485
5486 return defaultValue;
5487 }
5488
5489 var self = this;
5490
5491 if (this.container_ && this.container_.parentNode) {
5492 console.error('Frame already visible');
5493 return;
5494 }
5495
5496 var headerHeight = '16px';
5497
5498 var divSize = hterm.getClientSize(this.div_);
5499
5500 var width = opt('width', 640);
5501 var height = opt('height', 480);
5502 var left = (divSize.width - width) / 2;
5503 var top = (divSize.height - height) / 2;
5504
5505 var document = this.terminal_.document_;
5506
5507 var container = this.container_ = document.createElement('div');
5508 container.style.cssText = (
5509 'position: absolute;' +
5510 'display: -webkit-flex;' +
5511 '-webkit-flex-direction: column;' +
5512 'top: 10%;' +
5513 'left: 4%;' +
5514 'width: 90%;' +
5515 'height: 80%;' +
5516 'box-shadow: 0 0 2px ' + this.terminal_.getForegroundColor() + ';' +
5517 'border: 2px ' + this.terminal_.getForegroundColor() + ' solid;');
5518
5519 var header = document.createElement('div');
5520 header.style.cssText = (
5521 'display: -webkit-flex;' +
5522 '-webkit-justify-content: flex-end;' +
5523 'height: ' + headerHeight + ';' +
5524 'background-color: ' + this.terminal_.getForegroundColor() + ';' +
5525 'color: ' + this.terminal_.getBackgroundColor() + ';' +
5526 'font-size: 16px;' +
5527 'font-family: ' + this.terminal_.getFontFamily());
5528 container.appendChild(header);
5529
5530 if (false) {
5531 // No use for the close button.
5532 var button = document.createElement('div');
5533 button.setAttribute('role', 'button');
5534 button.style.cssText = (
5535 'margin-top: -3px;' +
5536 'margin-right: 3px;' +
5537 'cursor: pointer;');
5538 button.textContent = '\u2a2f';
5539 button.addEventListener('click', this.onCloseClicked_.bind(this));
5540 header.appendChild(button);
5541 }
5542
5543 var iframe = this.iframe_ = document.createElement('iframe');
5544 iframe.onload = this.onLoad_.bind(this);
5545 iframe.style.cssText = (
5546 'display: -webkit-flex;' +
5547 '-webkit-flex: 1;' +
5548 'width: 100%');
5549 iframe.setAttribute('src', this.url);
5550 iframe.setAttribute('seamless', true);
5551 container.appendChild(iframe);
5552
5553 this.div_.appendChild(container);
5554};
5555// SOURCE FILE: hterm/js/hterm_keyboard.js
5556// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
5557// Use of this source code is governed by a BSD-style license that can be
5558// found in the LICENSE file.
5559
5560'use strict';
5561
5562lib.rtdep('hterm.Keyboard.KeyMap');
5563
5564/**
5565 * Keyboard handler.
5566 *
5567 * Consumes onKey* events and invokes onVTKeystroke on the associated
5568 * hterm.Terminal object.
5569 *
5570 * See also: [XTERM] as referenced in vt.js.
5571 *
5572 * @param {hterm.Terminal} The Terminal object associated with this keyboard.
5573 */
5574hterm.Keyboard = function(terminal) {
5575 // The parent vt interpreter.
5576 this.terminal = terminal;
5577
5578 // The element we're currently capturing keyboard events for.
5579 this.keyboardElement_ = null;
5580
5581 // The event handlers we are interested in, and their bound callbacks, saved
5582 // so they can be uninstalled with removeEventListener, when required.
5583 this.handlers_ = [
5584 ['focusout', this.onFocusOut_.bind(this)],
5585 ['keydown', this.onKeyDown_.bind(this)],
5586 ['keypress', this.onKeyPress_.bind(this)],
5587 ['keyup', this.onKeyUp_.bind(this)],
5588 ['textInput', this.onTextInput_.bind(this)]
5589 ];
5590
5591 /**
5592 * The current key map.
5593 */
5594 this.keyMap = new hterm.Keyboard.KeyMap(this);
5595
5596 this.bindings = new hterm.Keyboard.Bindings(this);
5597
5598 /**
5599 * none: Disable any AltGr related munging.
5600 * ctrl-alt: Assume Ctrl+Alt means AltGr.
5601 * left-alt: Assume left Alt means AltGr.
5602 * right-alt: Assume right Alt means AltGr.
5603 */
5604 this.altGrMode = 'none';
5605
5606 /**
5607 * If true, Shift-Insert will fall through to the browser as a paste.
5608 * If false, the keystroke will be sent to the host.
5609 */
5610 this.shiftInsertPaste = true;
5611
5612 /**
5613 * If true, home/end will control the terminal scrollbar and shift home/end
5614 * will send the VT keycodes. If false then home/end sends VT codes and
5615 * shift home/end scrolls.
5616 */
5617 this.homeKeysScroll = false;
5618
5619 /**
5620 * Same as above, except for page up/page down.
5621 */
5622 this.pageKeysScroll = false;
5623
5624 /**
5625 * If true, Ctrl-Plus/Minus/Zero controls zoom.
5626 * If false, Ctrl-Shift-Plus/Minus/Zero controls zoom, Ctrl-Minus sends ^_,
5627 * Ctrl-Plus/Zero do nothing.
5628 */
5629 this.ctrlPlusMinusZeroZoom = true;
5630
5631 /**
5632 * Ctrl+C copies if true, sends ^C to host if false.
5633 * Ctrl+Shift+C sends ^C to host if true, copies if false.
5634 */
5635 this.ctrlCCopy = false;
5636
5637 /**
5638 * Ctrl+V pastes if true, sends ^V to host if false.
5639 * Ctrl+Shift+V sends ^V to host if true, pastes if false.
5640 */
5641 this.ctrlVPaste = false;
5642
5643 /**
5644 * Enable/disable application keypad.
5645 *
5646 * This changes the way numeric keys are sent from the keyboard.
5647 */
5648 this.applicationKeypad = false;
5649
5650 /**
5651 * Enable/disable the application cursor mode.
5652 *
5653 * This changes the way cursor keys are sent from the keyboard.
5654 */
5655 this.applicationCursor = false;
5656
5657 /**
5658 * If true, the backspace should send BS ('\x08', aka ^H). Otherwise
5659 * the backspace key should send '\x7f'.
5660 */
5661 this.backspaceSendsBackspace = false;
5662
5663 /**
5664 * The encoding method for data sent to the host.
5665 */
5666 this.characterEncoding = 'utf-8';
5667
5668 /**
5669 * Set whether the meta key sends a leading escape or not.
5670 */
5671 this.metaSendsEscape = true;
5672
5673 /**
5674 * Set whether meta-V gets passed to host.
5675 */
5676 this.passMetaV = true;
5677
5678 /**
5679 * Controls how the alt key is handled.
5680 *
5681 * escape....... Send an ESC prefix.
5682 * 8-bit........ Add 128 to the unshifted character as in xterm.
5683 * browser-key.. Wait for the keypress event and see what the browser says.
5684 * (This won't work well on platforms where the browser
5685 * performs a default action for some alt sequences.)
5686 *
5687 * This setting only matters when alt is distinct from meta (altIsMeta is
5688 * false.)
5689 */
5690 this.altSendsWhat = 'escape';
5691
5692 /**
5693 * Set whether the alt key acts as a meta key, instead of producing 8-bit
5694 * characters.
5695 *
5696 * True to enable, false to disable, null to autodetect based on platform.
5697 */
5698 this.altIsMeta = false;
5699
5700 /**
5701 * If true, tries to detect DEL key events that are from alt-backspace on
5702 * Chrome OS vs from a true DEL key press.
5703 *
5704 * Background: At the time of writing, on Chrome OS, alt-backspace is mapped
5705 * to DEL. Some users may be happy with this, but others may be frustrated
5706 * that it's impossible to do meta-backspace. If the user enables this pref,
5707 * we use a trick to tell a true DEL keypress from alt-backspace: on
5708 * alt-backspace, we will see the alt key go down, then get a DEL keystroke
5709 * that indicates that alt is not pressed. See https://crbug.com/174410 .
5710 */
5711 this.altBackspaceIsMetaBackspace = false;
5712
5713 /**
5714 * Used to keep track of the current alt-key state, which is necessary for
5715 * the altBackspaceIsMetaBackspace preference above and for the altGrMode
5716 * preference. This is a bitmap with where bit positions correspond to the
5717 * "location" property of the key event.
5718 */
5719 this.altKeyPressed = 0;
5720
5721 /**
5722 * If true, Chrome OS media keys will be mapped to their F-key equivalent.
5723 * E.g. "Back" will be mapped to F1. If false, Chrome will handle the keys.
5724 */
5725 this.mediaKeysAreFKeys = false;
5726
5727 /**
5728 * Holds the previous setting of altSendsWhat when DECSET 1039 is used. When
5729 * DECRST 1039 is used, altSendsWhat is changed back to this and this is
5730 * nulled out.
5731 */
5732 this.previousAltSendsWhat_ = null;
5733};
5734
5735/**
5736 * Special handling for keyCodes in a keyboard layout.
5737 */
5738hterm.Keyboard.KeyActions = {
5739 /**
5740 * Call preventDefault and stopPropagation for this key event and nothing
5741 * else.
5742 */
5743 CANCEL: new String('CANCEL'),
5744
5745 /**
5746 * This performs the default terminal action for the key. If used in the
5747 * 'normal' action and the the keystroke represents a printable key, the
5748 * character will be sent to the host. If used in one of the modifier
5749 * actions, the terminal will perform the normal action after (possibly)
5750 * altering it.
5751 *
5752 * - If the normal sequence starts with CSI, the sequence will be adjusted
5753 * to include the modifier parameter as described in [XTERM] in the final
5754 * table of the "PC-Style Function Keys" section.
5755 *
5756 * - If the control key is down and the key represents a printable character,
5757 * and the uppercase version of the unshifted keycap is between
5758 * 64 (ASCII '@') and 95 (ASCII '_'), then the uppercase version of the
5759 * unshifted keycap minus 64 is sent. This makes '^@' send '\x00' and
5760 * '^_' send '\x1f'. (Note that one higher that 0x1f is 0x20, which is
5761 * the first printable ASCII value.)
5762 *
5763 * - If the alt key is down and the key represents a printable character then
5764 * the value of the character is shifted up by 128.
5765 *
5766 * - If meta is down and configured to send an escape, '\x1b' will be sent
5767 * before the normal action is performed.
5768 */
5769 DEFAULT: new String('DEFAULT'),
5770
5771 /**
5772 * Causes the terminal to opt out of handling the key event, instead letting
5773 * the browser deal with it.
5774 */
5775 PASS: new String('PASS'),
5776
5777 /**
5778 * Insert the first or second character of the keyCap, based on e.shiftKey.
5779 * The key will be handled in onKeyDown, and e.preventDefault() will be
5780 * called.
5781 *
5782 * It is useful for a modified key action, where it essentially strips the
5783 * modifier while preventing the browser from reacting to the key.
5784 */
5785 STRIP: new String('STRIP')
5786};
5787
5788/**
5789 * Encode a string according to the 'send-encoding' preference.
5790 */
5791hterm.Keyboard.prototype.encode = function(str) {
5792 if (this.characterEncoding == 'utf-8')
5793 return this.terminal.vt.encodeUTF8(str);
5794
5795 return str;
5796};
5797
5798/**
5799 * Capture keyboard events sent to the associated element.
5800 *
5801 * This enables the keyboard. Captured events are consumed by this class
5802 * and will not perform their default action or bubble to other elements.
5803 *
5804 * Passing a null element will uninstall the keyboard handlers.
5805 *
5806 * @param {HTMLElement} element The element whose events should be captured, or
5807 * null to disable the keyboard.
5808 */
5809hterm.Keyboard.prototype.installKeyboard = function(element) {
5810 if (element == this.keyboardElement_)
5811 return;
5812
5813 if (element && this.keyboardElement_)
5814 this.installKeyboard(null);
5815
5816 for (var i = 0; i < this.handlers_.length; i++) {
5817 var handler = this.handlers_[i];
5818 if (element) {
5819 element.addEventListener(handler[0], handler[1]);
5820 } else {
5821 this.keyboardElement_.removeEventListener(handler[0], handler[1]);
5822 }
5823 }
5824
5825 this.keyboardElement_ = element;
5826};
5827
5828/**
5829 * Disable keyboard event capture.
5830 *
5831 * This will allow the browser to process key events normally.
5832 */
5833hterm.Keyboard.prototype.uninstallKeyboard = function() {
5834 this.installKeyboard(null);
5835};
5836
5837/**
5838 * Handle onTextInput events.
5839 *
5840 * We're not actually supposed to get these, but we do on the Mac in the case
5841 * where a third party app sends synthetic keystrokes to Chrome.
5842 */
5843hterm.Keyboard.prototype.onTextInput_ = function(e) {
5844 if (!e.data)
5845 return;
5846
5847 e.data.split('').forEach(this.terminal.onVTKeystroke.bind(this.terminal));
5848};
5849
5850/**
5851 * Handle onKeyPress events.
5852 */
5853hterm.Keyboard.prototype.onKeyPress_ = function(e) {
5854 var code;
5855
5856 var key = String.fromCharCode(e.which);
5857 var lowerKey = key.toLowerCase();
5858 if ((e.ctrlKey || e.metaKey) && (lowerKey == 'c' || lowerKey == 'v')) {
5859 // On FF the key press (not key down) event gets fired for copy/paste.
5860 // Let it fall through for the default browser behavior.
5861 return;
5862 }
5863
5864 if (e.altKey && this.altSendsWhat == 'browser-key' && e.charCode == 0) {
5865 // If we got here because we were expecting the browser to handle an
5866 // alt sequence but it didn't do it, then we might be on an OS without
5867 // an enabled IME system. In that case we fall back to xterm-like
5868 // behavior.
5869 //
5870 // This happens here only as a fallback. Typically these platforms should
5871 // set altSendsWhat to either 'escape' or '8-bit'.
5872 var ch = String.fromCharCode(e.keyCode);
5873 if (!e.shiftKey)
5874 ch = ch.toLowerCase();
5875 code = ch.charCodeAt(0) + 128;
5876
5877 } else if (e.charCode >= 32) {
5878 ch = e.charCode;
5879 }
5880
5881 if (ch)
5882 this.terminal.onVTKeystroke(String.fromCharCode(ch));
5883
5884 e.preventDefault();
5885 e.stopPropagation();
5886};
5887
5888/**
5889 * Prevent default handling for non-ctrl-shifted event.
5890 *
5891 * When combined with Chrome permission 'app.window.fullscreen.overrideEsc',
5892 * and called for both key down and key up events,
5893 * the ESC key remains usable within fullscreen Chrome app windows.
5894 */
5895hterm.Keyboard.prototype.preventChromeAppNonCtrlShiftDefault_ = function(e) {
5896 if (!window.chrome || !window.chrome.app || !window.chrome.app.window)
5897 return;
5898 if (!e.ctrlKey || !e.shiftKey)
5899 e.preventDefault();
5900};
5901
5902hterm.Keyboard.prototype.onFocusOut_ = function(e) {
5903 this.altKeyPressed = 0;
5904};
5905
5906hterm.Keyboard.prototype.onKeyUp_ = function(e) {
5907 if (e.keyCode == 18)
5908 this.altKeyPressed = this.altKeyPressed & ~(1 << (e.location - 1));
5909
5910 if (e.keyCode == 27)
5911 this.preventChromeAppNonCtrlShiftDefault_(e);
5912};
5913
5914/**
5915 * Handle onKeyDown events.
5916 */
5917hterm.Keyboard.prototype.onKeyDown_ = function(e) {
5918 if (e.keyCode == 18)
5919 this.altKeyPressed = this.altKeyPressed | (1 << (e.location - 1));
5920
5921 if (e.keyCode == 27)
5922 this.preventChromeAppNonCtrlShiftDefault_(e);
5923
5924 var keyDef = this.keyMap.keyDefs[e.keyCode];
5925 if (!keyDef) {
5926 console.warn('No definition for keyCode: ' + e.keyCode);
5927 return;
5928 }
5929
5930 // The type of action we're going to use.
5931 var resolvedActionType = null;
5932
5933 var self = this;
5934 function getAction(name) {
5935 // Get the key action for the given action name. If the action is a
5936 // function, dispatch it. If the action defers to the normal action,
5937 // resolve that instead.
5938
5939 resolvedActionType = name;
5940
5941 var action = keyDef[name];
5942 if (typeof action == 'function')
5943 action = action.apply(self.keyMap, [e, keyDef]);
5944
5945 if (action === DEFAULT && name != 'normal')
5946 action = getAction('normal');
5947
5948 return action;
5949 }
5950
5951 // Note that we use the triple-equals ('===') operator to test equality for
5952 // these constants, in order to distinguish usage of the constant from usage
5953 // of a literal string that happens to contain the same bytes.
5954 var CANCEL = hterm.Keyboard.KeyActions.CANCEL;
5955 var DEFAULT = hterm.Keyboard.KeyActions.DEFAULT;
5956 var PASS = hterm.Keyboard.KeyActions.PASS;
5957 var STRIP = hterm.Keyboard.KeyActions.STRIP;
5958
5959 var control = e.ctrlKey;
5960 var alt = this.altIsMeta ? false : e.altKey;
5961 var meta = this.altIsMeta ? (e.altKey || e.metaKey) : e.metaKey;
5962
5963 // In the key-map, we surround the keyCap for non-printables in "[...]"
5964 var isPrintable = !(/^\[\w+\]$/.test(keyDef.keyCap));
5965
5966 switch (this.altGrMode) {
5967 case 'ctrl-alt':
5968 if (isPrintable && control && alt) {
5969 // ctrl-alt-printable means altGr. We clear out the control and
5970 // alt modifiers and wait to see the charCode in the keydown event.
5971 control = false;
5972 alt = false;
5973 }
5974 break;
5975
5976 case 'right-alt':
5977 if (isPrintable && (this.terminal.keyboard.altKeyPressed & 2)) {
5978 control = false;
5979 alt = false;
5980 }
5981 break;
5982
5983 case 'left-alt':
5984 if (isPrintable && (this.terminal.keyboard.altKeyPressed & 1)) {
5985 control = false;
5986 alt = false;
5987 }
5988 break;
5989 }
5990
5991 var action;
5992
5993 if (control) {
5994 action = getAction('control');
5995 } else if (alt) {
5996 action = getAction('alt');
5997 } else if (meta) {
5998 action = getAction('meta');
5999 } else {
6000 action = getAction('normal');
6001 }
6002
6003 // If e.maskShiftKey was set (during getAction) it means the shift key is
6004 // already accounted for in the action, and we should not act on it any
6005 // further. This is currently only used for Ctrl-Shift-Tab, which should send
6006 // "CSI Z", not "CSI 1 ; 2 Z".
6007 var shift = !e.maskShiftKey && e.shiftKey;
6008
6009 var keyDown = {
6010 keyCode: e.keyCode,
6011 shift: e.shiftKey, // not `var shift` from above.
6012 ctrl: control,
6013 alt: alt,
6014 meta: meta
6015 };
6016
6017 var binding = this.bindings.getBinding(keyDown);
6018
6019 if (binding) {
6020 // Clear out the modifier bits so we don't try to munge the sequence
6021 // further.
6022 shift = control = alt = meta = false;
6023 resolvedActionType = 'normal';
6024 action = binding.action;
6025
6026 if (typeof action == 'function')
6027 action = action.call(this, this.terminal, keyDown);
6028 }
6029
6030 if (alt && this.altSendsWhat == 'browser-key' && action == DEFAULT) {
6031 // When altSendsWhat is 'browser-key', we wait for the keypress event.
6032 // In keypress, the browser should have set the event.charCode to the
6033 // appropriate character.
6034 // TODO(rginda): Character compositions will need some black magic.
6035 action = PASS;
6036 }
6037
6038 if (action === PASS || (action === DEFAULT && !(control || alt || meta))) {
6039 // If this key is supposed to be handled by the browser, or it is an
6040 // unmodified key with the default action, then exit this event handler.
6041 // If it's an unmodified key, it'll be handled in onKeyPress where we
6042 // can tell for sure which ASCII code to insert.
6043 //
6044 // This block needs to come before the STRIP test, otherwise we'll strip
6045 // the modifier and think it's ok to let the browser handle the keypress.
6046 // The browser won't know we're trying to ignore the modifiers and might
6047 // perform some default action.
6048 return;
6049 }
6050
6051 if (action === STRIP) {
6052 alt = control = false;
6053 action = keyDef.normal;
6054 if (typeof action == 'function')
6055 action = action.apply(this.keyMap, [e, keyDef]);
6056
6057 if (action == DEFAULT && keyDef.keyCap.length == 2)
6058 action = keyDef.keyCap.substr((shift ? 1 : 0), 1);
6059 }
6060
6061 e.preventDefault();
6062 e.stopPropagation();
6063
6064 if (action === CANCEL)
6065 return;
6066
6067 if (action !== DEFAULT && typeof action != 'string') {
6068 console.warn('Invalid action: ' + JSON.stringify(action));
6069 return;
6070 }
6071
6072 // Strip the modifier that is associated with the action, since we assume that
6073 // modifier has already been accounted for in the action.
6074 if (resolvedActionType == 'control') {
6075 control = false;
6076 } else if (resolvedActionType == 'alt') {
6077 alt = false;
6078 } else if (resolvedActionType == 'meta') {
6079 meta = false;
6080 }
6081
6082 if (action.substr(0, 2) == '\x1b[' && (alt || control || shift)) {
6083 // The action is an escape sequence that and it was triggered in the
6084 // presence of a keyboard modifier, we may need to alter the action to
6085 // include the modifier before sending it.
6086
6087 var mod;
6088
6089 if (shift && !(alt || control)) {
6090 mod = ';2';
6091 } else if (alt && !(shift || control)) {
6092 mod = ';3';
6093 } else if (shift && alt && !control) {
6094 mod = ';4';
6095 } else if (control && !(shift || alt)) {
6096 mod = ';5';
6097 } else if (shift && control && !alt) {
6098 mod = ';6';
6099 } else if (alt && control && !shift) {
6100 mod = ';7';
6101 } else if (shift && alt && control) {
6102 mod = ';8';
6103 }
6104
6105 if (action.length == 3) {
6106 // Some of the CSI sequences have zero parameters unless modified.
6107 action = '\x1b[1' + mod + action.substr(2, 1);
6108 } else {
6109 // Others always have at least one parameter.
6110 action = action.substr(0, action.length - 1) + mod +
6111 action.substr(action.length - 1);
6112 }
6113
6114 } else {
6115 if (action === DEFAULT) {
6116 action = keyDef.keyCap.substr((shift ? 1 : 0), 1);
6117
6118 if (control) {
6119 var unshifted = keyDef.keyCap.substr(0, 1);
6120 var code = unshifted.charCodeAt(0);
6121 if (code >= 64 && code <= 95) {
6122 action = String.fromCharCode(code - 64);
6123 }
6124 }
6125 }
6126
6127 if (alt && this.altSendsWhat == '8-bit' && action.length == 1) {
6128 var code = action.charCodeAt(0) + 128;
6129 action = String.fromCharCode(code);
6130 }
6131
6132 // We respect alt/metaSendsEscape even if the keymap action was a literal
6133 // string. Otherwise, every overridden alt/meta action would have to
6134 // check alt/metaSendsEscape.
6135 if ((alt && this.altSendsWhat == 'escape') ||
6136 (meta && this.metaSendsEscape)) {
6137 action = '\x1b' + action;
6138 }
6139 }
6140
6141 this.terminal.onVTKeystroke(action);
6142};
6143// SOURCE FILE: hterm/js/hterm_keyboard_bindings.js
6144// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
6145// Use of this source code is governed by a BSD-style license that can be
6146// found in the LICENSE file.
6147
6148'use strict';
6149
6150/**
6151 * A mapping from hterm.Keyboard.KeyPattern to an action.
6152 *
6153 * TODO(rginda): For now this bindings code is only used for user overrides.
6154 * hterm.Keyboard.KeyMap still handles all of the built-in key mappings.
6155 * It'd be nice if we migrated that over to be hterm.Keyboard.Bindings based.
6156 */
6157hterm.Keyboard.Bindings = function() {
6158 this.bindings_ = {};
6159};
6160
6161/**
6162 * Remove all bindings.
6163 */
6164hterm.Keyboard.Bindings.prototype.clear = function () {
6165 this.bindings_ = {};
6166};
6167
6168/**
6169 * Add a new binding.
6170 *
6171 * Internal API that assumes parsed objects as inputs.
6172 * See the public addBinding for more details.
6173 *
6174 * @param {hterm.Keyboard.KeyPattern} keyPattern
6175 * @param {string|function|hterm.Keyboard.KeyAction} action
6176 */
6177hterm.Keyboard.Bindings.prototype.addBinding_ = function(keyPattern, action) {
6178 var binding = null;
6179 var list = this.bindings_[keyPattern.keyCode];
6180 if (list) {
6181 for (var i = 0; i < list.length; i++) {
6182 if (list[i].keyPattern.matchKeyPattern(keyPattern)) {
6183 binding = list[i];
6184 break;
6185 }
6186 }
6187 }
6188
6189 if (binding) {
6190 binding.action = action;
6191 } else {
6192 binding = {keyPattern: keyPattern, action: action};
6193
6194 if (!list) {
6195 this.bindings_[keyPattern.keyCode] = [binding];
6196 } else {
6197 this.bindings_[keyPattern.keyCode].push(binding);
6198
6199 list.sort(function(a, b) {
6200 return hterm.Keyboard.KeyPattern.sortCompare(
6201 a.keyPattern, b.keyPattern);
6202 });
6203 }
6204 }
6205};
6206
6207/**
6208 * Add a new binding.
6209 *
6210 * If a binding for the keyPattern already exists it will be overridden.
6211 *
6212 * More specific keyPatterns take precedence over those with wildcards. Given
6213 * bindings for "Ctrl-A" and "Ctrl-*-A", and a "Ctrl-A" keydown, the "Ctrl-A"
6214 * binding will match even if "Ctrl-*-A" was created last.
6215 *
6216 * If action is a string, it will be passed through hterm.Parser.parseKeyAction.
6217 *
6218 * For example:
6219 * // Will replace Ctrl-P keystrokes with the string "hiya!".
6220 * addBinding('Ctrl-P', "'hiya!'");
6221 * // Will cancel the keystroke entirely (make it do nothing).
6222 * addBinding('Alt-D', hterm.Keyboard.KeyActions.CANCEL);
6223 * // Will execute the code and return the action.
6224 * addBinding('Ctrl-T', function() {
6225 * console.log('Got a T!');
6226 * return hterm.Keyboard.KeyActions.PASS;
6227 * });
6228 *
6229 * @param {string|hterm.Keyboard.KeyPattern} keyPattern
6230 * @param {string|function|hterm.Keyboard.KeyAction} action
6231 */
6232hterm.Keyboard.Bindings.prototype.addBinding = function(key, action) {
6233 // If we're given a hterm.Keyboard.KeyPattern object, pass it down.
6234 if (typeof key != 'string') {
6235 this.addBinding_(key, action);
6236 return;
6237 }
6238
6239 // Here we treat key as a string.
6240 var p = new hterm.Parser();
6241
6242 p.reset(key);
6243 var sequence;
6244
6245 try {
6246 sequence = p.parseKeySequence();
6247 } catch (ex) {
6248 console.error(ex);
6249 return;
6250 }
6251
6252 if (!p.isComplete()) {
6253 console.error(p.error('Expected end of sequence: ' + sequence));
6254 return;
6255 }
6256
6257 // If action is a string, parse it. Otherwise assume it's callable.
6258 if (typeof action == 'string') {
6259 p.reset(action);
6260 try {
6261 action = p.parseKeyAction();
6262 } catch (ex) {
6263 console.error(ex);
6264 return;
6265 }
6266 }
6267
6268 if (!p.isComplete()) {
6269 console.error(p.error('Expected end of sequence: ' + sequence));
6270 return;
6271 }
6272
6273 this.addBinding_(new hterm.Keyboard.KeyPattern(sequence), action);
6274};
6275
6276/**
6277 * Add multiple bindings at a time using a map of {string: string, ...}
6278 *
6279 * This uses hterm.Parser to parse the maps key into KeyPatterns, and the
6280 * map values into {string|function|KeyAction}.
6281 *
6282 * For example:
6283 * {
6284 * // Will replace Ctrl-P keystrokes with the string "hiya!".
6285 * 'Ctrl-P': "'hiya!'",
6286 * // Will cancel the keystroke entirely (make it do nothing).
6287 * 'Alt-D': hterm.Keyboard.KeyActions.CANCEL,
6288 * }
6289 *
6290 * @param {Object} map
6291 */
6292hterm.Keyboard.Bindings.prototype.addBindings = function(map) {
6293 for (var key in map) {
6294 this.addBinding(key, map[key]);
6295 }
6296};
6297
6298/**
6299 * Return the binding that is the best match for the given keyDown record,
6300 * or null if there is no match.
6301 *
6302 * @param {Object} keyDown An object with a keyCode property and zero or
6303 * more boolean properties representing key modifiers. These property names
6304 * must match those defined in hterm.Keyboard.KeyPattern.modifiers.
6305 */
6306hterm.Keyboard.Bindings.prototype.getBinding = function(keyDown) {
6307 var list = this.bindings_[keyDown.keyCode];
6308 if (!list)
6309 return null;
6310
6311 for (var i = 0; i < list.length; i++) {
6312 var binding = list[i];
6313 if (binding.keyPattern.matchKeyDown(keyDown))
6314 return binding;
6315 }
6316
6317 return null;
6318};
6319// SOURCE FILE: hterm/js/hterm_keyboard_keymap.js
6320// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
6321// Use of this source code is governed by a BSD-style license that can be
6322// found in the LICENSE file.
6323
6324'use strict';
6325
6326lib.rtdep('hterm.Keyboard.KeyActions');
6327
6328/**
6329 * The default key map for hterm.
6330 *
6331 * Contains a mapping of keyCodes to keyDefs (aka key definitions). The key
6332 * definition tells the hterm.Keyboard class how to handle keycodes.
6333 *
6334 * This should work for most cases, as the printable characters get handled
6335 * in the keypress event. In that case, even if the keycap is wrong in the
6336 * key map, the correct character should be sent.
6337 *
6338 * Different layouts, such as Dvorak should work with this keymap, as those
6339 * layouts typically move keycodes around on the keyboard without disturbing
6340 * the actual keycaps.
6341 *
6342 * There may be issues with control keys on non-US keyboards or with keyboards
6343 * that very significantly from the expectations here, in which case we may
6344 * have to invent new key maps.
6345 *
6346 * The sequences defined in this key map come from [XTERM] as referenced in
6347 * vt.js, starting with the section titled "Alt and Meta Keys".
6348 */
6349hterm.Keyboard.KeyMap = function(keyboard) {
6350 this.keyboard = keyboard;
6351 this.keyDefs = {};
6352 this.reset();
6353};
6354
6355/**
6356 * Add a single key definition.
6357 *
6358 * The definition is a hash containing the following keys: 'keyCap', 'normal',
6359 * 'control', and 'alt'.
6360 *
6361 * - keyCap is a string identifying the key. For printable
6362 * keys, the key cap should be exactly two characters, starting with the
6363 * unshifted version. For example, 'aA', 'bB', '1!' and '=+'. For
6364 * non-printable the key cap should be surrounded in square braces, as in
6365 * '[INS]', '[LEFT]'. By convention, non-printable keycaps are in uppercase
6366 * but this is not a strict requirement.
6367 *
6368 * - Normal is the action that should be performed when they key is pressed
6369 * in the absence of any modifier. See below for the supported actions.
6370 *
6371 * - Control is the action that should be performed when they key is pressed
6372 * along with the control modifier. See below for the supported actions.
6373 *
6374 * - Alt is the action that should be performed when they key is pressed
6375 * along with the alt modifier. See below for the supported actions.
6376 *
6377 * - Meta is the action that should be performed when they key is pressed
6378 * along with the meta modifier. See below for the supported actions.
6379 *
6380 * Actions can be one of the hterm.Keyboard.KeyActions as documented below,
6381 * a literal string, or an array. If the action is a literal string then
6382 * the string is sent directly to the host. If the action is an array it
6383 * is taken to be an escape sequence that may be altered by modifier keys.
6384 * The second-to-last element of the array will be overwritten with the
6385 * state of the modifier keys, as specified in the final table of "PC-Style
6386 * Function Keys" from [XTERM].
6387 */
6388hterm.Keyboard.KeyMap.prototype.addKeyDef = function(keyCode, def) {
6389 if (keyCode in this.keyDefs)
6390 console.warn('Duplicate keyCode: ' + keyCode);
6391
6392 this.keyDefs[keyCode] = def;
6393};
6394
6395/**
6396 * Add multiple key definitions in a single call.
6397 *
6398 * This function takes the key definitions as variable argument list. Each
6399 * argument is the key definition specified as an array.
6400 *
6401 * (If the function took everything as one big hash we couldn't detect
6402 * duplicates, and there would be a lot more typing involved.)
6403 *
6404 * Each key definition should have 6 elements: (keyCode, keyCap, normal action,
6405 * control action, alt action and meta action). See KeyMap.addKeyDef for the
6406 * meaning of these elements.
6407 */
6408hterm.Keyboard.KeyMap.prototype.addKeyDefs = function(var_args) {
6409 for (var i = 0; i < arguments.length; i++) {
6410 this.addKeyDef(arguments[i][0],
6411 { keyCap: arguments[i][1],
6412 normal: arguments[i][2],
6413 control: arguments[i][3],
6414 alt: arguments[i][4],
6415 meta: arguments[i][5]
6416 });
6417 }
6418};
6419
6420/**
6421 * Set up the default state for this keymap.
6422 */
6423hterm.Keyboard.KeyMap.prototype.reset = function() {
6424 this.keyDefs = {};
6425
6426 var self = this;
6427
6428 // This function is used by the "macro" functions below. It makes it
6429 // possible to use the call() macro as an argument to any other macro.
6430 function resolve(action, e, k) {
6431 if (typeof action == 'function')
6432 return action.apply(self, [e, k]);
6433
6434 return action;
6435 }
6436
6437 // If not application keypad a, else b. The keys that care about
6438 // application keypad ignore it when the key is modified.
6439 function ak(a, b) {
6440 return function(e, k) {
6441 var action = (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey ||
6442 !self.keyboard.applicationKeypad) ? a : b;
6443 return resolve(action, e, k);
6444 };
6445 }
6446
6447 // If mod or not application cursor a, else b. The keys that care about
6448 // application cursor ignore it when the key is modified.
6449 function ac(a, b) {
6450 return function(e, k) {
6451 var action = (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey ||
6452 !self.keyboard.applicationCursor) ? a : b;
6453 return resolve(action, e, k);
6454 };
6455 }
6456
6457 // If not backspace-sends-backspace keypad a, else b.
6458 function bs(a, b) {
6459 return function(e, k) {
6460 var action = !self.keyboard.backspaceSendsBackspace ? a : b;
6461 return resolve(action, e, k);
6462 };
6463 }
6464
6465 // If not e.shiftKey a, else b.
6466 function sh(a, b) {
6467 return function(e, k) {
6468 var action = !e.shiftKey ? a : b;
6469 e.maskShiftKey = true;
6470 return resolve(action, e, k);
6471 };
6472 }
6473
6474 // If not e.altKey a, else b.
6475 function alt(a, b) {
6476 return function(e, k) {
6477 var action = !e.altKey ? a : b;
6478 return resolve(action, e, k);
6479 };
6480 }
6481
6482 // If no modifiers a, else b.
6483 function mod(a, b) {
6484 return function(e, k) {
6485 var action = !(e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) ? a : b;
6486 return resolve(action, e, k);
6487 };
6488 }
6489
6490 // Compute a control character for a given character.
6491 function ctl(ch) { return String.fromCharCode(ch.charCodeAt(0) - 64) }
6492
6493 // Call a method on the keymap instance.
6494 function c(m) { return function (e, k) { return this[m](e, k) } }
6495
6496 // Ignore if not trapping media keys.
6497 function med(fn) {
6498 return function(e, k) {
6499 if (!self.keyboard.mediaKeysAreFKeys) {
6500 // Block Back, Forward, and Reload keys to avoid navigating away from
6501 // the current page.
6502 return (e.keyCode == 166 || e.keyCode == 167 || e.keyCode == 168) ?
6503 hterm.Keyboard.KeyActions.CANCEL :
6504 hterm.Keyboard.KeyActions.PASS;
6505 }
6506 return resolve(fn, e, k);
6507 };
6508 }
6509
6510 var ESC = '\x1b';
6511 var CSI = '\x1b[';
6512 var SS3 = '\x1bO';
6513
6514 var CANCEL = hterm.Keyboard.KeyActions.CANCEL;
6515 var DEFAULT = hterm.Keyboard.KeyActions.DEFAULT;
6516 var PASS = hterm.Keyboard.KeyActions.PASS;
6517 var STRIP = hterm.Keyboard.KeyActions.STRIP;
6518
6519 this.addKeyDefs(
6520 // These fields are: [keycode, keycap, normal, control, alt, meta]
6521
6522 // The browser sends the keycode 0 for some keys. We'll just assume it's
6523 // going to do the right thing by default for those keys.
6524 [0, '[UNKNOWN]', PASS, PASS, PASS, PASS],
6525
6526 // First row.
6527 [27, '[ESC]', ESC, DEFAULT, DEFAULT, DEFAULT],
6528 [112, '[F1]', mod(SS3 + 'P', CSI + 'P'), DEFAULT, CSI + "23~", DEFAULT],
6529 [113, '[F2]', mod(SS3 + 'Q', CSI + 'Q'), DEFAULT, CSI + "24~", DEFAULT],
6530 [114, '[F3]', mod(SS3 + 'R', CSI + 'R'), DEFAULT, CSI + "25~", DEFAULT],
6531 [115, '[F4]', mod(SS3 + 'S', CSI + 'S'), DEFAULT, CSI + "26~", DEFAULT],
6532 [116, '[F5]', CSI + '15~', DEFAULT, CSI + "28~", DEFAULT],
6533 [117, '[F6]', CSI + '17~', DEFAULT, CSI + "29~", DEFAULT],
6534 [118, '[F7]', CSI + '18~', DEFAULT, CSI + "31~", DEFAULT],
6535 [119, '[F8]', CSI + '19~', DEFAULT, CSI + "32~", DEFAULT],
6536 [120, '[F9]', CSI + '20~', DEFAULT, CSI + "33~", DEFAULT],
6537 [121, '[F10]', CSI + '21~', DEFAULT, CSI + "34~", DEFAULT],
6538 [122, '[F11]', CSI + '23~', DEFAULT, CSI + "42~", DEFAULT],
6539 [123, '[F12]', CSI + '24~', DEFAULT, CSI + "43~", DEFAULT],
6540
6541 // Second row.
6542 [192, '`~', DEFAULT, sh(ctl('@'), ctl('^')), DEFAULT, PASS],
6543 [49, '1!', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6544 [50, '2@', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6545 [51, '3#', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6546 [52, '4$', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6547 [53, '5%', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6548 [54, '6^', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6549 [55, '7&', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6550 [56, '8*', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6551 [57, '9(', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6552 [48, '0)', DEFAULT, c('onPlusMinusZero_'),c('onAltNum_'),c('onPlusMinusZero_')],
6553 [189, '-_', DEFAULT, c('onPlusMinusZero_'), DEFAULT, c('onPlusMinusZero_')],
6554 [187, '=+', DEFAULT, c('onPlusMinusZero_'), DEFAULT, c('onPlusMinusZero_')],
6555 // Firefox -_ and =+
6556 [173, '-_', DEFAULT, c('onPlusMinusZero_'), DEFAULT, c('onPlusMinusZero_')],
6557 [61, '=+', DEFAULT, c('onPlusMinusZero_'), DEFAULT, c('onPlusMinusZero_')],
6558 // Firefox Italian +*
6559 [171, '+*', DEFAULT, c('onPlusMinusZero_'), DEFAULT, c('onPlusMinusZero_')],
6560
6561 [8, '[BKSP]', bs('\x7f', '\b'), bs('\b', '\x7f'), DEFAULT, DEFAULT],
6562
6563 // Third row.
6564 [9, '[TAB]', sh('\t', CSI + 'Z'), STRIP, PASS, DEFAULT],
6565 [81, 'qQ', DEFAULT, ctl('Q'), DEFAULT, DEFAULT],
6566 [87, 'wW', DEFAULT, ctl('W'), DEFAULT, DEFAULT],
6567 [69, 'eE', DEFAULT, ctl('E'), DEFAULT, DEFAULT],
6568 [82, 'rR', DEFAULT, ctl('R'), DEFAULT, DEFAULT],
6569 [84, 'tT', DEFAULT, ctl('T'), DEFAULT, DEFAULT],
6570 [89, 'yY', DEFAULT, ctl('Y'), DEFAULT, DEFAULT],
6571 [85, 'uU', DEFAULT, ctl('U'), DEFAULT, DEFAULT],
6572 [73, 'iI', DEFAULT, ctl('I'), DEFAULT, DEFAULT],
6573 [79, 'oO', DEFAULT, ctl('O'), DEFAULT, DEFAULT],
6574 [80, 'pP', DEFAULT, ctl('P'), DEFAULT, DEFAULT],
6575 [219, '[{', DEFAULT, ctl('['), DEFAULT, DEFAULT],
6576 [221, ']}', DEFAULT, ctl(']'), DEFAULT, DEFAULT],
6577 [220, '\\|', DEFAULT, ctl('\\'), DEFAULT, DEFAULT],
6578
6579 // Fourth row. (We let Ctrl-Shift-J pass for Chrome DevTools.)
6580 [20, '[CAPS]', PASS, PASS, PASS, DEFAULT],
6581 [65, 'aA', DEFAULT, ctl('A'), DEFAULT, DEFAULT],
6582 [83, 'sS', DEFAULT, ctl('S'), DEFAULT, DEFAULT],
6583 [68, 'dD', DEFAULT, ctl('D'), DEFAULT, DEFAULT],
6584 [70, 'fF', DEFAULT, ctl('F'), DEFAULT, DEFAULT],
6585 [71, 'gG', DEFAULT, ctl('G'), DEFAULT, DEFAULT],
6586 [72, 'hH', DEFAULT, ctl('H'), DEFAULT, DEFAULT],
6587 [74, 'jJ', DEFAULT, sh(ctl('J'), PASS), DEFAULT, DEFAULT],
6588 [75, 'kK', DEFAULT, sh(ctl('K'), c('onClear_')), DEFAULT, DEFAULT],
6589 [76, 'lL', DEFAULT, sh(ctl('L'), PASS), DEFAULT, DEFAULT],
6590 [186, ';:', DEFAULT, STRIP, DEFAULT, DEFAULT],
6591 [222, '\'"', DEFAULT, STRIP, DEFAULT, DEFAULT],
6592 [13, '[ENTER]', '\r', CANCEL, CANCEL, DEFAULT],
6593
6594 // Fifth row. This includes the copy/paste shortcuts. On some
6595 // platforms it's Ctrl-C/V, on others it's Meta-C/V. We assume either
6596 // Ctrl-C/Meta-C should pass to the browser when there is a selection,
6597 // and Ctrl-Shift-V/Meta-*-V should always pass to the browser (since
6598 // these seem to be recognized as paste too).
6599 [16, '[SHIFT]', PASS, PASS, PASS, DEFAULT],
6600 [90, 'zZ', DEFAULT, ctl('Z'), DEFAULT, DEFAULT],
6601 [88, 'xX', DEFAULT, ctl('X'), DEFAULT, DEFAULT],
6602 [67, 'cC', DEFAULT, c('onCtrlC_'), DEFAULT, c('onMetaC_')],
6603 [86, 'vV', DEFAULT, c('onCtrlV_'), DEFAULT, c('onMetaV_')],
6604 [66, 'bB', DEFAULT, sh(ctl('B'), PASS), DEFAULT, sh(DEFAULT, PASS)],
6605 [78, 'nN', DEFAULT, c('onCtrlN_'), DEFAULT, c('onMetaN_')],
6606 [77, 'mM', DEFAULT, ctl('M'), DEFAULT, DEFAULT],
6607 [188, ',<', DEFAULT, alt(STRIP, PASS), DEFAULT, DEFAULT],
6608 [190, '.>', DEFAULT, alt(STRIP, PASS), DEFAULT, DEFAULT],
6609 [191, '/?', DEFAULT, sh(ctl('_'), ctl('?')), DEFAULT, DEFAULT],
6610
6611 // Sixth and final row.
6612 [17, '[CTRL]', PASS, PASS, PASS, PASS],
6613 [18, '[ALT]', PASS, PASS, PASS, PASS],
6614 [91, '[LAPL]', PASS, PASS, PASS, PASS],
6615 [32, ' ', DEFAULT, ctl('@'), DEFAULT, DEFAULT],
6616 [92, '[RAPL]', PASS, PASS, PASS, PASS],
6617 [93, '[RMENU]', PASS, PASS, PASS, PASS],
6618
6619 // These things.
6620 [42, '[PRTSCR]', PASS, PASS, PASS, PASS],
6621 [145, '[SCRLK]', PASS, PASS, PASS, PASS],
6622 [19, '[BREAK]', PASS, PASS, PASS, PASS],
6623
6624 // The block of six keys above the arrows.
6625 [45, '[INSERT]', c('onKeyInsert_'), DEFAULT, DEFAULT, DEFAULT],
6626 [36, '[HOME]', c('onKeyHome_'), DEFAULT, DEFAULT, DEFAULT],
6627 [33, '[PGUP]', c('onKeyPageUp_'), DEFAULT, DEFAULT, DEFAULT],
6628 [46, '[DEL]', c('onKeyDel_'), DEFAULT, DEFAULT, DEFAULT],
6629 [35, '[END]', c('onKeyEnd_'), DEFAULT, DEFAULT, DEFAULT],
6630 [34, '[PGDOWN]', c('onKeyPageDown_'), DEFAULT, DEFAULT, DEFAULT],
6631
6632 // Arrow keys. When unmodified they respect the application cursor state,
6633 // otherwise they always send the CSI codes.
6634 [38, '[UP]', ac(CSI + 'A', SS3 + 'A'), DEFAULT, DEFAULT, DEFAULT],
6635 [40, '[DOWN]', ac(CSI + 'B', SS3 + 'B'), DEFAULT, DEFAULT, DEFAULT],
6636 [39, '[RIGHT]', ac(CSI + 'C', SS3 + 'C'), DEFAULT, DEFAULT, DEFAULT],
6637 [37, '[LEFT]', ac(CSI + 'D', SS3 + 'D'), DEFAULT, DEFAULT, DEFAULT],
6638
6639 [144, '[NUMLOCK]', PASS, PASS, PASS, PASS],
6640
6641 // With numlock off, the keypad generates the same key codes as the arrows
6642 // and 'block of six' for some keys, and null key codes for the rest.
6643
6644 // Keypad with numlock on generates unique key codes...
6645 [96, '[KP0]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6646 [97, '[KP1]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6647 [98, '[KP2]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6648 [99, '[KP3]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6649 [100, '[KP4]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6650 [101, '[KP5]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6651 [102, '[KP6]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6652 [103, '[KP7]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6653 [104, '[KP8]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6654 [105, '[KP9]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6655 [107, '[KP+]', DEFAULT, c('onPlusMinusZero_'), DEFAULT, c('onPlusMinusZero_')],
6656 [109, '[KP-]', DEFAULT, c('onPlusMinusZero_'), DEFAULT, c('onPlusMinusZero_')],
6657 [106, '[KP*]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6658 [111, '[KP/]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6659 [110, '[KP.]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6660
6661 // Chrome OS keyboard top row.
6662 [166, '[BACK]', med(mod(SS3+'P', CSI+'P')), DEFAULT, CSI+"23~", DEFAULT],
6663 [167, '[FWD]', med(mod(SS3+'Q', CSI+'Q')), DEFAULT, CSI+"24~", DEFAULT],
6664 [168, '[RELOAD]', med(mod(SS3+'R', CSI+'R')), DEFAULT, CSI+"25~", DEFAULT],
6665 [183, '[FSCR]', med(mod(SS3+'S', CSI+'S')), DEFAULT, CSI+"26~", DEFAULT],
6666 [182, '[WINS]', med(CSI + '15~'), DEFAULT, CSI+"28~", DEFAULT],
6667 [216, '[BRIT-]', med(CSI + '17~'), DEFAULT, CSI+"29~", DEFAULT],
6668 [217, '[BRIT+]', med(CSI + '18~'), DEFAULT, CSI+"31~", DEFAULT]
6669
6670 // 173 [MUTE], 174 [VOL-] and 175 [VOL+] are trapped by the Chrome OS
6671 // window manager, so we'll never see them. Note that 173 is also
6672 // Firefox's -_ keycode.
6673 );
6674};
6675
6676/**
6677 * Either allow the paste or send a key sequence.
6678 */
6679hterm.Keyboard.KeyMap.prototype.onKeyInsert_ = function(e) {
6680 if (this.keyboard.shiftInsertPaste && e.shiftKey)
6681 return hterm.Keyboard.KeyActions.PASS;
6682
6683 return '\x1b[2~';
6684};
6685
6686/**
6687 * Either scroll the scrollback buffer or send a key sequence.
6688 */
6689hterm.Keyboard.KeyMap.prototype.onKeyHome_ = function(e) {
6690 if (!this.keyboard.homeKeysScroll ^ e.shiftKey) {
6691 if ((e.altey || e.ctrlKey || e.shiftKey) ||
6692 !this.keyboard.applicationCursor) {
6693 return '\x1b[H';
6694 }
6695
6696 return '\x1bOH';
6697 }
6698
6699 this.keyboard.terminal.scrollHome();
6700 return hterm.Keyboard.KeyActions.CANCEL;
6701};
6702
6703/**
6704 * Either scroll the scrollback buffer or send a key sequence.
6705 */
6706hterm.Keyboard.KeyMap.prototype.onKeyEnd_ = function(e) {
6707 if (!this.keyboard.homeKeysScroll ^ e.shiftKey) {
6708 if ((e.altKey || e.ctrlKey || e.shiftKey) ||
6709 !this.keyboard.applicationCursor) {
6710 return '\x1b[F';
6711 }
6712
6713 return '\x1bOF';
6714 }
6715
6716 this.keyboard.terminal.scrollEnd();
6717 return hterm.Keyboard.KeyActions.CANCEL;
6718};
6719
6720/**
6721 * Either scroll the scrollback buffer or send a key sequence.
6722 */
6723hterm.Keyboard.KeyMap.prototype.onKeyPageUp_ = function(e) {
6724 if (!this.keyboard.pageKeysScroll ^ e.shiftKey)
6725 return '\x1b[5~';
6726
6727 this.keyboard.terminal.scrollPageUp();
6728 return hterm.Keyboard.KeyActions.CANCEL;
6729};
6730
6731/**
6732 * Either send a true DEL, or sub in meta-backspace.
6733 *
6734 * On Chrome OS, if we know the alt key is down, but we get a DEL event that
6735 * claims that the alt key is not pressed, we know the DEL was a synthetic
6736 * one from a user that hit alt-backspace. Based on a user pref, we can sub
6737 * in meta-backspace in this case.
6738 */
6739hterm.Keyboard.KeyMap.prototype.onKeyDel_ = function(e) {
6740 if (this.keyboard.altBackspaceIsMetaBackspace &&
6741 this.keyboard.altKeyPressed && !e.altKey)
6742 return '\x1b\x7f';
6743 return '\x1b[3~';
6744};
6745
6746/**
6747 * Either scroll the scrollback buffer or send a key sequence.
6748 */
6749hterm.Keyboard.KeyMap.prototype.onKeyPageDown_ = function(e) {
6750 if (!this.keyboard.pageKeysScroll ^ e.shiftKey)
6751 return '\x1b[6~';
6752
6753 this.keyboard.terminal.scrollPageDown();
6754 return hterm.Keyboard.KeyActions.CANCEL;
6755};
6756
6757/**
6758 * Clear the primary/alternate screens and the scrollback buffer.
6759 */
6760hterm.Keyboard.KeyMap.prototype.onClear_ = function(e, keyDef) {
6761 this.keyboard.terminal.wipeContents();
6762 return hterm.Keyboard.KeyActions.CANCEL;
6763};
6764
6765/**
6766 * Either pass Ctrl-1..9 to the browser or send them to the host.
6767 *
6768 * Note that Ctrl-1 and Ctrl-9 don't actually have special sequences mapped
6769 * to them in xterm or gnome-terminal. The range is really Ctrl-2..8, but
6770 * we handle 1..9 since Chrome treats the whole range special.
6771 */
6772hterm.Keyboard.KeyMap.prototype.onCtrlNum_ = function(e, keyDef) {
6773 // Compute a control character for a given character.
6774 function ctl(ch) { return String.fromCharCode(ch.charCodeAt(0) - 64) }
6775
6776 if (this.keyboard.terminal.passCtrlNumber && !e.shiftKey)
6777 return hterm.Keyboard.KeyActions.PASS;
6778
6779 switch (keyDef.keyCap.substr(0, 1)) {
6780 case '1': return '1';
6781 case '2': return ctl('@');
6782 case '3': return ctl('[');
6783 case '4': return ctl('\\');
6784 case '5': return ctl(']');
6785 case '6': return ctl('^');
6786 case '7': return ctl('_');
6787 case '8': return '\x7f';
6788 case '9': return '9';
6789 }
6790};
6791
6792/**
6793 * Either pass Alt-1..9 to the browser or send them to the host.
6794 */
6795hterm.Keyboard.KeyMap.prototype.onAltNum_ = function(e, keyDef) {
6796 if (this.keyboard.terminal.passAltNumber && !e.shiftKey)
6797 return hterm.Keyboard.KeyActions.PASS;
6798
6799 return hterm.Keyboard.KeyActions.DEFAULT;
6800};
6801
6802/**
6803 * Either pass Meta-1..9 to the browser or send them to the host.
6804 */
6805hterm.Keyboard.KeyMap.prototype.onMetaNum_ = function(e, keyDef) {
6806 if (this.keyboard.terminal.passMetaNumber && !e.shiftKey)
6807 return hterm.Keyboard.KeyActions.PASS;
6808
6809 return hterm.Keyboard.KeyActions.DEFAULT;
6810};
6811
6812/**
6813 * Either send a ^C or interpret the keystroke as a copy command.
6814 */
6815hterm.Keyboard.KeyMap.prototype.onCtrlC_ = function(e, keyDef) {
6816 var selection = this.keyboard.terminal.getDocument().getSelection();
6817
6818 if (!selection.isCollapsed) {
6819 if (this.keyboard.ctrlCCopy && !e.shiftKey) {
6820 // Ctrl-C should copy if there is a selection, send ^C otherwise.
6821 // Perform the copy by letting the browser handle Ctrl-C. On most
6822 // browsers, this is the *only* way to place text on the clipboard from
6823 // the 'drive-by' web.
6824 if (this.keyboard.terminal.clearSelectionAfterCopy) {
6825 setTimeout(selection.collapseToEnd.bind(selection), 50);
6826 }
6827 return hterm.Keyboard.KeyActions.PASS;
6828 }
6829
6830 if (!this.keyboard.ctrlCCopy && e.shiftKey) {
6831 // Ctrl-Shift-C should copy if there is a selection, send ^C otherwise.
6832 // Perform the copy manually. This only works in situations where
6833 // document.execCommand('copy') is allowed.
6834 if (this.keyboard.terminal.clearSelectionAfterCopy) {
6835 setTimeout(selection.collapseToEnd.bind(selection), 50);
6836 }
6837 this.keyboard.terminal.copySelectionToClipboard();
6838 return hterm.Keyboard.KeyActions.CANCEL;
6839 }
6840 }
6841
6842 return '\x03';
6843};
6844
6845/**
6846 * Either send a ^N or open a new window to the same location.
6847 */
6848hterm.Keyboard.KeyMap.prototype.onCtrlN_ = function(e, keyDef) {
6849 if (e.shiftKey) {
6850 window.open(document.location.href, '',
6851 'chrome=no,close=yes,resize=yes,scrollbars=yes,' +
6852 'minimizable=yes,width=' + window.innerWidth +
6853 ',height=' + window.innerHeight);
6854 return hterm.Keyboard.KeyActions.CANCEL;
6855 }
6856
6857 return '\x0e';
6858};
6859
6860/**
6861 * Either send a ^V or allow the browser to interpret the keystroke as a paste
6862 * command.
6863 *
6864 * The default behavior is to paste if the user presses Ctrl-Shift-V, and send
6865 * a ^V if the user presses Ctrl-V. This can be flipped with the
6866 * 'ctrl-v-paste' preference.
6867 */
6868hterm.Keyboard.KeyMap.prototype.onCtrlV_ = function(e, keyDef) {
6869 if ((!e.shiftKey && this.keyboard.ctrlVPaste) ||
6870 (e.shiftKey && !this.keyboard.ctrlVPaste)) {
6871 return hterm.Keyboard.KeyActions.PASS;
6872 }
6873
6874 return '\x16';
6875};
6876
6877/**
6878 * Either the default action or open a new window to the same location.
6879 */
6880hterm.Keyboard.KeyMap.prototype.onMetaN_ = function(e, keyDef) {
6881 if (e.shiftKey) {
6882 window.open(document.location.href, '',
6883 'chrome=no,close=yes,resize=yes,scrollbars=yes,' +
6884 'minimizable=yes,width=' + window.outerWidth +
6885 ',height=' + window.outerHeight);
6886 return hterm.Keyboard.KeyActions.CANCEL;
6887 }
6888
6889 return hterm.Keyboard.KeyActions.DEFAULT;
6890};
6891
6892/**
6893 * Either send a Meta-C or allow the browser to interpret the keystroke as a
6894 * copy command.
6895 *
6896 * If there is no selection, or if the user presses Meta-Shift-C, then we'll
6897 * transmit an '\x1b' (if metaSendsEscape is on) followed by 'c' or 'C'.
6898 *
6899 * If there is a selection, we defer to the browser. In this case we clear out
6900 * the selection so the user knows we heard them, and also to give them a
6901 * chance to send a Meta-C by just hitting the key again.
6902 */
6903hterm.Keyboard.KeyMap.prototype.onMetaC_ = function(e, keyDef) {
6904 var document = this.keyboard.terminal.getDocument();
6905 if (e.shiftKey || document.getSelection().isCollapsed) {
6906 // If the shift key is being held, or there is no document selection, send
6907 // a Meta-C. The keyboard code will add the ESC if metaSendsEscape is true,
6908 // we just have to decide between 'c' and 'C'.
6909 return keyDef.keyCap.substr(e.shiftKey ? 1 : 0, 1);
6910 }
6911
6912 // Otherwise let the browser handle it as a copy command.
6913 if (this.keyboard.terminal.clearSelectionAfterCopy) {
6914 setTimeout(function() { document.getSelection().collapseToEnd() }, 50);
6915 }
6916 return hterm.Keyboard.KeyActions.PASS;
6917};
6918
6919/**
6920 * Either PASS or DEFAULT Meta-V, depending on preference.
6921 *
6922 * Always PASS Meta-Shift-V to allow browser to interpret the keystroke as
6923 * a paste command.
6924 */
6925hterm.Keyboard.KeyMap.prototype.onMetaV_ = function(e, keyDef) {
6926 if (e.shiftKey)
6927 return hterm.Keyboard.KeyActions.PASS;
6928
6929 return this.keyboard.passMetaV ?
6930 hterm.Keyboard.KeyActions.PASS :
6931 hterm.Keyboard.KeyActions.DEFAULT;
6932};
6933
6934/**
6935 * Handle font zooming.
6936 *
6937 * The browser's built-in zoom has a bit of an issue at certain zoom levels.
6938 * At some magnifications, the measured height of a row of text differs from
6939 * the height that was explicitly set.
6940 *
6941 * We override the browser zoom keys to change the ScrollPort's font size to
6942 * avoid the issue.
6943 */
6944hterm.Keyboard.KeyMap.prototype.onPlusMinusZero_ = function(e, keyDef) {
6945 if (!(this.keyboard.ctrlPlusMinusZeroZoom ^ e.shiftKey)) {
6946 // If ctrl-PMZ controls zoom and the shift key is pressed, or
6947 // ctrl-shift-PMZ controls zoom and this shift key is not pressed,
6948 // then we want to send the control code instead of affecting zoom.
6949 if (keyDef.keyCap == '-_')
6950 return '\x1f'; // ^_
6951
6952 // Only ^_ is valid, the other sequences have no meaning.
6953 return hterm.Keyboard.KeyActions.CANCEL;
6954 }
6955
6956 if (this.keyboard.terminal.getZoomFactor() != 1) {
6957 // If we're not at 1:1 zoom factor, let the Ctrl +/-/0 keys control the
6958 // browser zoom, so it's easier to for the user to get back to 100%.
6959 return hterm.Keyboard.KeyActions.PASS;
6960 }
6961
6962 var cap = keyDef.keyCap.substr(0, 1);
6963 if (cap == '0') {
6964 this.keyboard.terminal.setFontSize(0);
6965 } else {
6966 var size = this.keyboard.terminal.getFontSize();
6967
6968 if (cap == '-' || keyDef.keyCap == '[KP-]') {
6969 size -= 1;
6970 } else {
6971 size += 1;
6972 }
6973
6974 this.keyboard.terminal.setFontSize(size);
6975 }
6976
6977 return hterm.Keyboard.KeyActions.CANCEL;
6978};
6979// SOURCE FILE: hterm/js/hterm_keyboard_keypattern.js
6980// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
6981// Use of this source code is governed by a BSD-style license that can be
6982// found in the LICENSE file.
6983
6984'use strict';
6985
6986/**
6987 * A record of modifier bits and keycode used to define a key binding.
6988 *
6989 * The modifier names are enumerated in the static KeyPattern.modifiers
6990 * property below. Each modifier can be true, false, or "*". True means
6991 * the modifier key must be present, false means it must not, and "*" means
6992 * it doesn't matter.
6993 */
6994hterm.Keyboard.KeyPattern = function(spec) {
6995 this.wildcardCount = 0;
6996 this.keyCode = spec.keyCode;
6997
6998 hterm.Keyboard.KeyPattern.modifiers.forEach(function(mod) {
6999 this[mod] = spec[mod] || false;
7000 if (this[mod] == '*')
7001 this.wildcardCount++;
7002 }.bind(this));
7003};
7004
7005/**
7006 * Valid modifier names.
7007 */
7008hterm.Keyboard.KeyPattern.modifiers = [
7009 'shift', 'ctrl', 'alt', 'meta'
7010];
7011
7012/**
7013 * A compare callback for Array.prototype.sort().
7014 *
7015 * The bindings code wants to be sure to search through the strictest key
7016 * patterns first, so that loosely defined patterns have a lower priority than
7017 * exact patterns.
7018 *
7019 * @param {hterm.Keyboard.KeyPattern} a
7020 * @param {hterm.Keyboard.KeyPattern} b
7021 */
7022hterm.Keyboard.KeyPattern.sortCompare = function(a, b) {
7023 if (a.wildcardCount < b.wildcardCount)
7024 return -1;
7025
7026 if (a.wildcardCount > b.wildcardCount)
7027 return 1;
7028
7029 return 0;
7030};
7031
7032/**
7033 * Private method used to match this key pattern against other key patterns
7034 * or key down events.
7035 *
7036 * @param {Object} The object to match.
7037 * @param {boolean} True if we should ignore wildcards. Useful when you want
7038 * to perform and exact match against another key pattern.
7039 */
7040hterm.Keyboard.KeyPattern.prototype.match_ = function(obj, exactMatch) {
7041 if (this.keyCode != obj.keyCode)
7042 return false;
7043
7044 var rv = true;
7045
7046 hterm.Keyboard.KeyPattern.modifiers.forEach(function(mod) {
7047 var modValue = (mod in obj) ? obj[mod] : false;
7048 if (!rv || (!exactMatch && this[mod] == '*') || this[mod] == modValue)
7049 return;
7050
7051 rv = false;
7052 }.bind(this));
7053
7054 return rv;
7055};
7056
7057/**
7058 * Return true if the given keyDown object is a match for this key pattern.
7059 *
7060 * @param {Object} keyDown An object with a keyCode property and zero or
7061 * more boolean properties representing key modifiers. These property names
7062 * must match those defined in hterm.Keyboard.KeyPattern.modifiers.
7063 */
7064hterm.Keyboard.KeyPattern.prototype.matchKeyDown = function(keyDown) {
7065 return this.match_(keyDown, false);
7066};
7067
7068/**
7069 * Return true if the given hterm.Keyboard.KeyPattern is exactly the same as
7070 * this one.
7071 *
7072 * @param {hterm.Keyboard.KeyPattern}
7073 */
7074hterm.Keyboard.KeyPattern.prototype.matchKeyPattern = function(keyPattern) {
7075 return this.match_(keyPattern, true);
7076};
7077// SOURCE FILE: hterm/js/hterm_options.js
7078// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
7079// Use of this source code is governed by a BSD-style license that can be
7080// found in the LICENSE file.
7081
7082'use strict';
7083
7084/**
7085 * @fileoverview This file implements the hterm.Options class,
7086 * which stores current operating conditions for the terminal. This object is
7087 * used instead of a series of parameters to allow saving/restoring of cursor
7088 * conditions easily, and to provide an easy place for common configuration
7089 * options.
7090 *
7091 * Original code by Cory Maccarrone.
7092 */
7093
7094/**
7095 * Constructor for the hterm.Options class, optionally acting as a copy
7096 * constructor.
7097 *
7098 * The defaults are as defined in http://www.vt100.net/docs/vt510-rm/DECSTR
7099 * except that we enable autowrap (wraparound) by default since that seems to
7100 * be what xterm does.
7101 *
7102 * @param {hterm.Options=} opt_copy Optional instance to copy.
7103 * @constructor
7104 */
7105hterm.Options = function(opt_copy) {
7106 // All attributes in this class are public to allow easy access by the
7107 // terminal.
7108
7109 this.wraparound = opt_copy ? opt_copy.wraparound : true;
7110 this.reverseWraparound = opt_copy ? opt_copy.reverseWraparound : false;
7111 this.originMode = opt_copy ? opt_copy.originMode : false;
7112 this.autoCarriageReturn = opt_copy ? opt_copy.autoCarriageReturn : false;
7113 this.cursorVisible = opt_copy ? opt_copy.cursorVisible : false;
7114 this.cursorBlink = opt_copy ? opt_copy.cursorBlink : false;
7115 this.insertMode = opt_copy ? opt_copy.insertMode : false;
7116 this.reverseVideo = opt_copy ? opt_copy.reverseVideo : false;
7117 this.bracketedPaste = opt_copy ? opt_copy.bracketedPaste : false;
7118};
7119// SOURCE FILE: hterm/js/hterm_parser.js
7120// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
7121// Use of this source code is governed by a BSD-style license that can be
7122// found in the LICENSE file.
7123
7124'use strict';
7125
7126lib.rtdep('hterm.Keyboard.KeyActions');
7127
7128/**
7129 * @constructor
7130 * Parses the key definition syntax used for user keyboard customizations.
7131 */
7132hterm.Parser = function() {
7133 /**
7134 * @type {string} The source string.
7135 */
7136 this.source = '';
7137
7138 /**
7139 * @type {number} The current position.
7140 */
7141 this.pos = 0;
7142
7143 /**
7144 * @type {string?} The character at the current position.
7145 */
7146 this.ch = null;
7147};
7148
7149hterm.Parser.prototype.error = function(message) {
7150 return new Error('Parse error at ' + this.pos + ': ' + message);
7151};
7152
7153hterm.Parser.prototype.isComplete = function() {
7154 return this.pos == this.source.length;
7155};
7156
7157hterm.Parser.prototype.reset = function(source, opt_pos) {
7158 this.source = source;
7159 this.pos = opt_pos || 0;
7160 this.ch = source.substr(0, 1);
7161};
7162
7163/**
7164 * Parse a key sequence.
7165 *
7166 * A key sequence is zero or more of the key modifiers defined in
7167 * hterm.Parser.identifiers.modifierKeys followed by a key code. Key
7168 * codes can be an integer or an identifier from
7169 * hterm.Parser.identifiers.keyCodes. Modifiers and keyCodes should be joined
7170 * by the dash character.
7171 *
7172 * An asterisk "*" can be used to indicate that the unspecified modifiers
7173 * are optional.
7174 *
7175 * For example:
7176 * A: Matches only an unmodified "A" character.
7177 * 65: Same as above.
7178 * 0x41: Same as above.
7179 * Ctrl-A: Matches only Ctrl-A.
7180 * Ctrl-65: Same as above.
7181 * Ctrl-0x41: Same as above.
7182 * Ctrl-Shift-A: Matches only Ctrl-Shift-A.
7183 * Ctrl-*-A: Matches Ctrl-A, as well as any other key sequence that includes
7184 * at least the Ctrl and A keys.
7185 *
7186 * @return {Object} An object with shift, ctrl, alt, meta, keyCode
7187 * properties.
7188 */
7189hterm.Parser.prototype.parseKeySequence = function() {
7190 var rv = {
7191 keyCode: null
7192 };
7193
7194 for (var k in hterm.Parser.identifiers.modifierKeys) {
7195 rv[hterm.Parser.identifiers.modifierKeys[k]] = false;
7196 }
7197
7198 while (this.pos < this.source.length) {
7199 this.skipSpace();
7200
7201 var token = this.parseToken();
7202 if (token.type == 'integer') {
7203 rv.keyCode = token.value;
7204
7205 } else if (token.type == 'identifier') {
7206 if (token.value in hterm.Parser.identifiers.modifierKeys) {
7207 var mod = hterm.Parser.identifiers.modifierKeys[token.value];
7208 if (rv[mod] && rv[mod] != '*')
7209 throw this.error('Duplicate modifier: ' + token.value);
7210 rv[mod] = true;
7211
7212 } else if (token.value in hterm.Parser.identifiers.keyCodes) {
7213 rv.keyCode = hterm.Parser.identifiers.keyCodes[token.value];
7214
7215 } else {
7216 throw this.error('Unknown key: ' + token.value);
7217 }
7218
7219 } else if (token.type == 'symbol') {
7220 if (token.value == '*') {
7221 for (var id in hterm.Parser.identifiers.modifierKeys) {
7222 var p = hterm.Parser.identifiers.modifierKeys[id];
7223 if (!rv[p])
7224 rv[p] = '*';
7225 }
7226 } else {
7227 throw this.error('Unexpected symbol: ' + token.value);
7228 }
7229 } else {
7230 throw this.error('Expected integer or identifier');
7231 }
7232
7233 this.skipSpace();
7234
7235 if (this.ch != '-')
7236 break;
7237
7238 if (rv.keyCode != null)
7239 throw this.error('Extra definition after target key');
7240
7241 this.advance(1);
7242 }
7243
7244 if (rv.keyCode == null)
7245 throw this.error('Missing target key');
7246
7247 return rv;
7248};
7249
7250hterm.Parser.prototype.parseKeyAction = function() {
7251 this.skipSpace();
7252
7253 var token = this.parseToken();
7254
7255 if (token.type == 'string')
7256 return token.value;
7257
7258 if (token.type == 'identifier') {
7259 if (token.value in hterm.Parser.identifiers.actions)
7260 return hterm.Parser.identifiers.actions[token.value];
7261
7262 throw this.error('Unknown key action: ' + token.value);
7263 }
7264
7265 throw this.error('Expected string or identifier');
7266
7267};
7268
7269hterm.Parser.prototype.peekString = function() {
7270 return this.ch == '\'' || this.ch == '"';
7271};
7272
7273hterm.Parser.prototype.peekIdentifier = function() {
7274 return this.ch.match(/[a-z_]/i);
7275};
7276
7277hterm.Parser.prototype.peekInteger = function() {
7278 return this.ch.match(/[0-9]/);
7279};
7280
7281hterm.Parser.prototype.parseToken = function() {
7282 if (this.ch == '*') {
7283 var rv = {type: 'symbol', value: this.ch};
7284 this.advance(1);
7285 return rv;
7286 }
7287
7288 if (this.peekIdentifier())
7289 return {type: 'identifier', value: this.parseIdentifier()};
7290
7291 if (this.peekString())
7292 return {type: 'string', value: this.parseString()};
7293
7294 if (this.peekInteger())
7295 return {type: 'integer', value: this.parseInteger()};
7296
7297
7298 throw this.error('Unexpected token');
7299};
7300
7301hterm.Parser.prototype.parseIdentifier = function() {
7302 if (!this.peekIdentifier())
7303 throw this.error('Expected identifier');
7304
7305 return this.parsePattern(/[a-z0-9_]+/ig);
7306};
7307
7308hterm.Parser.prototype.parseInteger = function() {
7309 var base = 10;
7310
7311 if (this.ch == '0' && this.pos < this.source.length - 1 &&
7312 this.source.substr(this.pos + 1, 1) == 'x') {
7313 return parseInt(this.parsePattern(/0x[0-9a-f]+/gi));
7314 }
7315
7316 return parseInt(this.parsePattern(/\d+/g));
7317};
7318
7319/**
7320 * Parse a single or double quoted string.
7321 *
7322 * The current position should point at the initial quote character. Single
7323 * quoted strings will be treated literally, double quoted will process escapes.
7324 *
7325 * TODO(rginda): Variable interpolation.
7326 *
7327 * @param {ParseState} parseState
7328 * @param {string} quote A single or double-quote character.
7329 * @return {string}
7330 */
7331hterm.Parser.prototype.parseString = function() {
7332 var result = '';
7333
7334 var quote = this.ch;
7335 if (quote != '"' && quote != '\'')
7336 throw this.error('String expected');
7337
7338 this.advance(1);
7339
7340 var re = new RegExp('[\\\\' + quote + ']', 'g');
7341
7342 while (this.pos < this.source.length) {
7343 re.lastIndex = this.pos;
7344 if (!re.exec(this.source))
7345 throw this.error('Unterminated string literal');
7346
7347 result += this.source.substring(this.pos, re.lastIndex - 1);
7348
7349 this.advance(re.lastIndex - this.pos - 1);
7350
7351 if (quote == '"' && this.ch == '\\') {
7352 this.advance(1);
7353 result += this.parseEscape();
7354 continue;
7355 }
7356
7357 if (quote == '\'' && this.ch == '\\') {
7358 result += this.ch;
7359 this.advance(1);
7360 continue;
7361 }
7362
7363 if (this.ch == quote) {
7364 this.advance(1);
7365 return result;
7366 }
7367 }
7368
7369 throw this.error('Unterminated string literal');
7370};
7371
7372
7373/**
7374 * Parse an escape code from the current position (which should point to
7375 * the first character AFTER the leading backslash.)
7376 *
7377 * @return {string}
7378 */
7379hterm.Parser.prototype.parseEscape = function() {
7380 var map = {
7381 '"': '"',
7382 '\'': '\'',
7383 '\\': '\\',
7384 'a': '\x07',
7385 'b': '\x08',
7386 'e': '\x1b',
7387 'f': '\x0c',
7388 'n': '\x0a',
7389 'r': '\x0d',
7390 't': '\x09',
7391 'v': '\x0b',
7392 'x': function() {
7393 var value = this.parsePattern(/[a-z0-9]{2}/ig);
7394 return String.fromCharCode(parseInt(value, 16));
7395 },
7396 'u': function() {
7397 var value = this.parsePattern(/[a-z0-9]{4}/ig);
7398 return String.fromCharCode(parseInt(value, 16));
7399 }
7400 };
7401
7402 if (!(this.ch in map))
7403 throw this.error('Unknown escape: ' + this.ch);
7404
7405 var value = map[this.ch];
7406 this.advance(1);
7407
7408 if (typeof value == 'function')
7409 value = value.call(this);
7410
7411 return value;
7412};
7413
7414/**
7415 * Parse the given pattern starting from the current position.
7416 *
7417 * @param {RegExp} pattern A pattern representing the characters to span. MUST
7418 * include the "global" RegExp flag.
7419 * @return {string}
7420 */
7421hterm.Parser.prototype.parsePattern = function(pattern) {
7422 if (!pattern.global)
7423 throw this.error('Internal error: Span patterns must be global');
7424
7425 pattern.lastIndex = this.pos;
7426 var ary = pattern.exec(this.source);
7427
7428 if (!ary || pattern.lastIndex - ary[0].length != this.pos)
7429 throw this.error('Expected match for: ' + pattern);
7430
7431 this.pos = pattern.lastIndex - 1;
7432 this.advance(1);
7433
7434 return ary[0];
7435};
7436
7437
7438/**
7439 * Advance the current position.
7440 *
7441 * @param {number} count
7442 */
7443hterm.Parser.prototype.advance = function(count) {
7444 this.pos += count;
7445 this.ch = this.source.substr(this.pos, 1);
7446};
7447
7448/**
7449 * @param {string=} opt_expect A list of valid non-whitespace characters to
7450 * terminate on.
7451 * @return {void}
7452 */
7453hterm.Parser.prototype.skipSpace = function(opt_expect) {
7454 if (!/\s/.test(this.ch))
7455 return;
7456
7457 var re = /\s+/gm;
7458 re.lastIndex = this.pos;
7459
7460 var source = this.source;
7461 if (re.exec(source))
7462 this.pos = re.lastIndex;
7463
7464 this.ch = this.source.substr(this.pos, 1);
7465
7466 if (opt_expect) {
7467 if (this.ch.indexOf(opt_expect) == -1) {
7468 throw this.error('Expected one of ' + opt_expect + ', found: ' +
7469 this.ch);
7470 }
7471 }
7472};
7473// SOURCE FILE: hterm/js/hterm_parser_identifiers.js
7474// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
7475// Use of this source code is governed by a BSD-style license that can be
7476// found in the LICENSE file.
7477
7478'use strict';
7479
7480/**
7481 * Collections of identifier for hterm.Parser.
7482 */
7483hterm.Parser.identifiers = {};
7484
7485hterm.Parser.identifiers.modifierKeys = {
7486 Shift: 'shift',
7487 Ctrl: 'ctrl',
7488 Alt: 'alt',
7489 Meta: 'meta'
7490};
7491
7492/**
7493 * Key codes useful when defining key sequences.
7494 *
7495 * Punctuation is mostly left out of this list because they can move around
7496 * based on keyboard locale and browser.
7497 *
7498 * In a key sequence like "Ctrl-ESC", the ESC comes from this list of
7499 * identifiers. It is equivalent to "Ctrl-27" and "Ctrl-0x1b".
7500 */
7501hterm.Parser.identifiers.keyCodes = {
7502 // Top row.
7503 ESC: 27,
7504 F1: 112,
7505 F2: 113,
7506 F3: 114,
7507 F4: 115,
7508 F5: 116,
7509 F6: 117,
7510 F7: 118,
7511 F8: 119,
7512 F9: 120,
7513 F10: 121,
7514 F11: 122,
7515 F12: 123,
7516
7517 // Row two.
7518 ONE: 49,
7519 TWO: 50,
7520 THREE: 51,
7521 FOUR: 52,
7522 FIVE: 53,
7523 SIX: 54,
7524 SEVEN: 55,
7525 EIGHT: 56,
7526 NINE: 57,
7527 ZERO: 48,
7528 BACKSPACE: 8,
7529
7530 // Row three.
7531 TAB: 9,
7532 Q: 81,
7533 W: 87,
7534 E: 69,
7535 R: 82,
7536 T: 84,
7537 Y: 89,
7538 U: 85,
7539 I: 73,
7540 O: 79,
7541 P: 80,
7542
7543 // Row four.
7544 CAPSLOCK: 20,
7545 A: 65,
7546 S: 83,
7547 D: 68,
7548 F: 70,
7549 G: 71,
7550 H: 72,
7551 J: 74,
7552 K: 75,
7553 L: 76,
7554 ENTER: 13,
7555
7556 // Row five.
7557 Z: 90,
7558 X: 88,
7559 C: 67,
7560 V: 86,
7561 B: 66,
7562 N: 78,
7563 M: 77,
7564
7565 // Etc.
7566 SPACE: 32,
7567 PRINT_SCREEN: 42,
7568 SCROLL_LOCK: 145,
7569 BREAK: 19,
7570 INSERT: 45,
7571 HOME: 36,
7572 PGUP: 33,
7573 DEL: 46,
7574 END: 35,
7575 PGDOWN: 34,
7576 UP: 38,
7577 DOWN: 40,
7578 RIGHT: 39,
7579 LEFT: 37,
7580 NUMLOCK: 144,
7581
7582 // Keypad
7583 KP0: 96,
7584 KP1: 97,
7585 KP2: 98,
7586 KP3: 99,
7587 KP4: 100,
7588 KP5: 101,
7589 KP6: 102,
7590 KP7: 103,
7591 KP8: 104,
7592 KP9: 105,
7593 KP_PLUS: 107,
7594 KP_MINUS: 109,
7595 KP_STAR: 106,
7596 KP_DIVIDE: 111,
7597 KP_DECIMAL: 110,
7598
7599 // Chrome OS media keys
7600 NAVIGATE_BACK: 166,
7601 NAVIGATE_FORWARD: 167,
7602 RELOAD: 168,
7603 FULL_SCREEN: 183,
7604 WINDOW_OVERVIEW: 182,
7605 BRIGHTNESS_UP: 216,
7606 BRIGHTNESS_DOWN: 217
7607};
7608
7609/**
7610 * Identifiers for use in key actions.
7611 */
7612hterm.Parser.identifiers.actions = {
7613 /**
7614 * Prevent the browser and operating system from handling the event.
7615 */
7616 CANCEL: hterm.Keyboard.KeyActions.CANCEL,
7617
7618 /**
7619 * Wait for a "keypress" event, send the keypress charCode to the host.
7620 */
7621 DEFAULT: hterm.Keyboard.KeyActions.DEFAULT,
7622
7623 /**
7624 * Let the browser or operating system handle the key.
7625 */
7626 PASS: hterm.Keyboard.KeyActions.PASS,
7627
7628 /**
7629 * Scroll the terminal one page up.
7630 */
7631 scrollPageUp: function(terminal) {
7632 terminal.scrollPageUp();
7633 return hterm.Keyboard.KeyActions.CANCEL;
7634 },
7635
7636 /**
7637 * Scroll the terminal one page down.
7638 */
7639 scrollPageDown: function(terminal) {
7640 terminal.scrollPageDown();
7641 return hterm.Keyboard.KeyActions.CANCEL;
7642 },
7643
7644 /**
7645 * Scroll the terminal to the top.
7646 */
7647 scrollToTop: function(terminal) {
7648 terminal.scrollEnd();
7649 return hterm.Keyboard.KeyActions.CANCEL;
7650 },
7651
7652 /**
7653 * Scroll the terminal to the bottom.
7654 */
7655 scrollToBottom: function(terminal) {
7656 terminal.scrollEnd();
7657 return hterm.Keyboard.KeyActions.CANCEL;
7658 },
7659
7660 /**
7661 * Clear the terminal and scrollback buffer.
7662 */
7663 clearScrollback: function(terminal) {
7664 terminal.wipeContents();
7665 return hterm.Keyboard.KeyActions.CANCEL;
7666 }
7667};
7668// SOURCE FILE: hterm/js/hterm_preference_manager.js
7669// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
7670// Use of this source code is governed by a BSD-style license that can be
7671// found in the LICENSE file.
7672
7673'use strict';
7674
7675lib.rtdep('lib.f', 'lib.Storage');
7676
7677/**
7678 * PreferenceManager subclass managing global NaSSH preferences.
7679 *
7680 * This is currently just an ordered list of known connection profiles.
7681 */
7682hterm.PreferenceManager = function(profileId) {
7683 lib.PreferenceManager.call(this, hterm.defaultStorage,
7684 '/hterm/profiles/' + profileId);
7685 var defs = hterm.PreferenceManager.defaultPreferences;
7686 Object.keys(defs).forEach(function(key) {
7687 this.definePreference(key, defs[key][1]);
7688 }.bind(this));
7689};
7690
7691hterm.PreferenceManager.categories = {};
7692hterm.PreferenceManager.categories.Keyboard = 'Keyboard';
7693hterm.PreferenceManager.categories.Appearance = 'Appearance';
7694hterm.PreferenceManager.categories.CopyPaste = 'CopyPaste';
7695hterm.PreferenceManager.categories.Sounds = 'Sounds';
7696hterm.PreferenceManager.categories.Scrolling = 'Scrolling';
7697hterm.PreferenceManager.categories.Encoding = 'Encoding';
7698hterm.PreferenceManager.categories.Miscellaneous = 'Miscellaneous';
7699
7700/**
7701 * List of categories, ordered by display order (top to bottom)
7702 */
7703hterm.PreferenceManager.categoryDefinitions = [
7704 { id: hterm.PreferenceManager.categories.Appearance,
7705 text: 'Appearance (fonts, colors, images)'},
7706 { id: hterm.PreferenceManager.categories.CopyPaste,
7707 text: 'Copy & Paste'},
7708 { id: hterm.PreferenceManager.categories.Encoding,
7709 text: 'Encoding'},
7710 { id: hterm.PreferenceManager.categories.Keyboard,
7711 text: 'Keyboard'},
7712 { id: hterm.PreferenceManager.categories.Scrolling,
7713 text: 'Scrolling'},
7714 { id: hterm.PreferenceManager.categories.Sounds,
7715 text: 'Sounds'},
7716 { id: hterm.PreferenceManager.categories.Miscellaneous,
7717 text: 'Misc.'}
7718];
7719
7720
7721hterm.PreferenceManager.defaultPreferences = {
7722 'alt-gr-mode':
7723 [hterm.PreferenceManager.categories.Keyboard, null,
7724 [null, 'none', 'ctrl-alt', 'left-alt', 'right-alt'],
7725 'Select an AltGr detection hack^Wheuristic.\n' +
7726 '\n' +
7727 '\'null\': Autodetect based on navigator.language:\n' +
7728 ' \'en-us\' => \'none\', else => \'right-alt\'\n' +
7729 '\'none\': Disable any AltGr related munging.\n' +
7730 '\'ctrl-alt\': Assume Ctrl+Alt means AltGr.\n' +
7731 '\'left-alt\': Assume left Alt means AltGr.\n' +
7732 '\'right-alt\': Assume right Alt means AltGr.\n'],
7733
7734 'alt-backspace-is-meta-backspace':
7735 [hterm.PreferenceManager.categories.Keyboard, false, 'bool',
7736 'If set, undoes the Chrome OS Alt-Backspace->DEL remap, so that ' +
7737 'alt-backspace indeed is alt-backspace.'],
7738
7739 'alt-is-meta':
7740 [hterm.PreferenceManager.categories.Keyboard, false, 'bool',
7741 'Set whether the alt key acts as a meta key or as a distinct alt key.'],
7742
7743 'alt-sends-what':
7744 [hterm.PreferenceManager.categories.Keyboard, 'escape',
7745 ['escape', '8-bit', 'browser-key'],
7746 'Controls how the alt key is handled.\n' +
7747 '\n' +
7748 ' escape....... Send an ESC prefix.\n' +
7749 ' 8-bit........ Add 128 to the unshifted character as in xterm.\n' +
7750 ' browser-key.. Wait for the keypress event and see what the browser \n' +
7751 ' says. (This won\'t work well on platforms where the \n' +
7752 ' browser performs a default action for some alt sequences.)'
7753 ],
7754
7755 'audible-bell-sound':
7756 [hterm.PreferenceManager.categories.Sounds, 'lib-resource:hterm/audio/bell',
7757 'url',
7758 'URL of the terminal bell sound. Empty string for no audible bell.'],
7759
7760 'desktop-notification-bell':
7761 [hterm.PreferenceManager.categories.Sounds, false, 'bool',
7762 'If true, terminal bells in the background will create a Web ' +
7763 'Notification. https://www.w3.org/TR/notifications/\n' +
7764 '\n'+
7765 'Displaying notifications requires permission from the user. When this ' +
7766 'option is set to true, hterm will attempt to ask the user for permission ' +
7767 'if necessary. Note browsers may not show this permission request if it ' +
7768 'did not originate from a user action.\n' +
7769 '\n' +
7770 'Chrome extensions with the "notifications" permission have permission to ' +
7771 'display notifications.'],
7772
7773 'background-color':
7774 [hterm.PreferenceManager.categories.Appearance, 'rgb(16, 16, 16)', 'color',
7775 'The background color for text with no other color attributes.'],
7776
7777 'background-image':
7778 [hterm.PreferenceManager.categories.Appearance, '', 'string',
7779 'CSS value of the background image. Empty string for no image.\n' +
7780 '\n' +
7781 'For example:\n' +
7782 ' url(https://goo.gl/anedTK)\n' +
7783 ' linear-gradient(top bottom, blue, red)'],
7784
7785 'background-size':
7786 [hterm.PreferenceManager.categories.Appearance, '', 'string',
7787 'CSS value of the background image size. Defaults to none.'],
7788
7789 'background-position':
7790 [hterm.PreferenceManager.categories.Appearance, '', 'string',
7791 'CSS value of the background image position.\n' +
7792 '\n' +
7793 'For example:\n' +
7794 ' 10% 10%\n' +
7795 ' center'],
7796
7797 'backspace-sends-backspace':
7798 [hterm.PreferenceManager.categories.Keyboard, false, 'bool',
7799 'If true, the backspace should send BS (\'\\x08\', aka ^H). Otherwise ' +
7800 'the backspace key should send \'\\x7f\'.'],
7801
7802 'character-map-overrides':
7803 [hterm.PreferenceManager.categories.Appearance, null, 'value',
7804 'This is specified as an object. It is a sparse array, where each ' +
7805 'property is the character set code and the value is an object that is ' +
7806 'a sparse array itself. In that sparse array, each property is the ' +
7807 'received character and the value is the displayed character.\n' +
7808 '\n' +
7809 'For example:\n' +
7810 ' {"0":{"+":"\\u2192",",":"\\u2190","-":"\\u2191",".":"\\u2193", ' +
7811 '"0":"\\u2588"}}'
7812 ],
7813
7814 'close-on-exit':
7815 [hterm.PreferenceManager.categories.Miscellaneous, true, 'bool',
7816 'Whether or not to close the window when the command exits.'],
7817
7818 'cursor-blink':
7819 [hterm.PreferenceManager.categories.Appearance, false, 'bool',
7820 'Whether or not to blink the cursor by default.'],
7821
7822 'cursor-blink-cycle':
7823 [hterm.PreferenceManager.categories.Appearance, [1000, 500], 'value',
7824 'The cursor blink rate in milliseconds.\n' +
7825 '\n' +
7826 'A two element array, the first of which is how long the cursor should be ' +
7827 'on, second is how long it should be off.'],
7828
7829 'cursor-color':
7830 [hterm.PreferenceManager.categories.Appearance, 'rgba(255, 0, 0, 0.5)',
7831 'color',
7832 'The color of the visible cursor.'],
7833
7834 'color-palette-overrides':
7835 [hterm.PreferenceManager.categories.Appearance, null, 'value',
7836 'Override colors in the default palette.\n' +
7837 '\n' +
7838 'This can be specified as an array or an object. If specified as an ' +
7839 'object it is assumed to be a sparse array, where each property ' +
7840 'is a numeric index into the color palette.\n' +
7841 '\n' +
7842 'Values can be specified as almost any css color value. This ' +
7843 'includes #RGB, #RRGGBB, rgb(...), rgba(...), and any color names ' +
7844 'that are also part of the stock X11 rgb.txt file.\n' +
7845 '\n' +
7846 'You can use \'null\' to specify that the default value should be not ' +
7847 'be changed. This is useful for skipping a small number of indices ' +
7848 'when the value is specified as an array.'],
7849
7850 'copy-on-select':
7851 [hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
7852 'Automatically copy mouse selection to the clipboard.'],
7853
7854 'use-default-window-copy':
7855 [hterm.PreferenceManager.categories.CopyPaste, false, 'bool',
7856 'Whether to use the default window copy behavior'],
7857
7858 'clear-selection-after-copy':
7859 [hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
7860 'Whether to clear the selection after copying.'],
7861
7862 'ctrl-plus-minus-zero-zoom':
7863 [hterm.PreferenceManager.categories.Keyboard, true, 'bool',
7864 'If true, Ctrl-Plus/Minus/Zero controls zoom.\n' +
7865 'If false, Ctrl-Shift-Plus/Minus/Zero controls zoom, Ctrl-Minus sends ^_, ' +
7866 'Ctrl-Plus/Zero do nothing.'],
7867
7868 'ctrl-c-copy':
7869 [hterm.PreferenceManager.categories.Keyboard, false, 'bool',
7870 'Ctrl+C copies if true, send ^C to host if false.\n' +
7871 'Ctrl+Shift+C sends ^C to host if true, copies if false.'],
7872
7873 'ctrl-v-paste':
7874 [hterm.PreferenceManager.categories.Keyboard, false, 'bool',
7875 'Ctrl+V pastes if true, send ^V to host if false.\n' +
7876 'Ctrl+Shift+V sends ^V to host if true, pastes if false.'],
7877
7878 'east-asian-ambiguous-as-two-column':
7879 [hterm.PreferenceManager.categories.Keyboard, false, 'bool',
7880 'Set whether East Asian Ambiguous characters have two column width.'],
7881
7882 'enable-8-bit-control':
7883 [hterm.PreferenceManager.categories.Keyboard, false, 'bool',
7884 'True to enable 8-bit control characters, false to ignore them.\n' +
7885 '\n' +
7886 'We\'ll respect the two-byte versions of these control characters ' +
7887 'regardless of this setting.'],
7888
7889 'enable-bold':
7890 [hterm.PreferenceManager.categories.Appearance, null, 'tristate',
7891 'True if we should use bold weight font for text with the bold/bright ' +
7892 'attribute. False to use the normal weight font. Null to autodetect.'],
7893
7894 'enable-bold-as-bright':
7895 [hterm.PreferenceManager.categories.Appearance, true, 'bool',
7896 'True if we should use bright colors (8-15 on a 16 color palette) ' +
7897 'for any text with the bold attribute. False otherwise.'],
7898
7899 'enable-blink':
7900 [hterm.PreferenceManager.categories.Appearance, true, 'bool',
7901 'True if we should respect the blink attribute. False to ignore it. '],
7902
7903 'enable-clipboard-notice':
7904 [hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
7905 'Show a message in the terminal when the host writes to the clipboard.'],
7906
7907 'enable-clipboard-write':
7908 [hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
7909 'Allow the host to write directly to the system clipboard.'],
7910
7911 'enable-dec12':
7912 [hterm.PreferenceManager.categories.Miscellaneous, false, 'bool',
7913 'Respect the host\'s attempt to change the cursor blink status using ' +
7914 'DEC Private Mode 12.'],
7915
7916 'environment':
7917 [hterm.PreferenceManager.categories.Miscellaneous, {'TERM': 'xterm-256color'},
7918 'value',
7919 'The default environment variables, as an object.'],
7920
7921 'font-family':
7922 [hterm.PreferenceManager.categories.Appearance,
7923 '"DejaVu Sans Mono", "Everson Mono", FreeMono, "Menlo", "Terminal", ' +
7924 'monospace', 'string',
7925 'Default font family for the terminal text.'],
7926
7927 'font-size':
7928 [hterm.PreferenceManager.categories.Appearance, 15, 'int',
7929 'The default font size in pixels.'],
7930
7931 'font-smoothing':
7932 [hterm.PreferenceManager.categories.Appearance, 'antialiased', 'string',
7933 'CSS font-smoothing property.'],
7934
7935 'foreground-color':
7936 [hterm.PreferenceManager.categories.Appearance, 'rgb(240, 240, 240)', 'color',
7937 'The foreground color for text with no other color attributes.'],
7938
7939 'home-keys-scroll':
7940 [hterm.PreferenceManager.categories.Keyboard, false, 'bool',
7941 'If true, home/end will control the terminal scrollbar and shift home/end ' +
7942 'will send the VT keycodes. If false then home/end sends VT codes and ' +
7943 'shift home/end scrolls.'],
7944
7945 'keybindings':
7946 [hterm.PreferenceManager.categories.Keyboard, null, 'value',
7947 'A map of key sequence to key actions. Key sequences include zero or ' +
7948 'more modifier keys followed by a key code. Key codes can be decimal or ' +
7949 'hexadecimal numbers, or a key identifier. Key actions can be specified ' +
7950 'a string to send to the host, or an action identifier. For a full ' +
7951 'list of key code and action identifiers, see https://goo.gl/8AoD09.' +
7952 '\n' +
7953 '\n' +
7954 'Sample keybindings:\n' +
7955 '{ "Ctrl-Alt-K": "clearScrollback",\n' +
7956 ' "Ctrl-Shift-L": "PASS",\n' +
7957 ' "Ctrl-H": "\'HELLO\\n\'"\n' +
7958 '}'],
7959
7960 'max-string-sequence':
7961 [hterm.PreferenceManager.categories.Encoding, 100000, 'int',
7962 'Max length of a DCS, OSC, PM, or APS sequence before we give up and ' +
7963 'ignore the code.'],
7964
7965 'media-keys-are-fkeys':
7966 [hterm.PreferenceManager.categories.Keyboard, false, 'bool',
7967 'If true, convert media keys to their Fkey equivalent. If false, let ' +
7968 'the browser handle the keys.'],
7969
7970 'meta-sends-escape':
7971 [hterm.PreferenceManager.categories.Keyboard, true, 'bool',
7972 'Set whether the meta key sends a leading escape or not.'],
7973
7974 'mouse-paste-button':
7975 [hterm.PreferenceManager.categories.CopyPaste, null,
7976 [null, 0, 1, 2, 3, 4, 5, 6],
7977 'Mouse paste button, or null to autodetect.\n' +
7978 '\n' +
7979 'For autodetect, we\'ll try to enable middle button paste for non-X11 ' +
7980 'platforms. On X11 we move it to button 3.'],
7981
7982 'page-keys-scroll':
7983 [hterm.PreferenceManager.categories.Keyboard, false, 'bool',
7984 'If true, page up/down will control the terminal scrollbar and shift ' +
7985 'page up/down will send the VT keycodes. If false then page up/down ' +
7986 'sends VT codes and shift page up/down scrolls.'],
7987
7988 'pass-alt-number':
7989 [hterm.PreferenceManager.categories.Keyboard, null, 'tristate',
7990 'Set whether we should pass Alt-1..9 to the browser.\n' +
7991 '\n' +
7992 'This is handy when running hterm in a browser tab, so that you don\'t ' +
7993 'lose Chrome\'s "switch to tab" keyboard accelerators. When not running ' +
7994 'in a tab it\'s better to send these keys to the host so they can be ' +
7995 'used in vim or emacs.\n' +
7996 '\n' +
7997 'If true, Alt-1..9 will be handled by the browser. If false, Alt-1..9 ' +
7998 'will be sent to the host. If null, autodetect based on browser platform ' +
7999 'and window type.'],
8000
8001 'pass-ctrl-number':
8002 [hterm.PreferenceManager.categories.Keyboard, null, 'tristate',
8003 'Set whether we should pass Ctrl-1..9 to the browser.\n' +
8004 '\n' +
8005 'This is handy when running hterm in a browser tab, so that you don\'t ' +
8006 'lose Chrome\'s "switch to tab" keyboard accelerators. When not running ' +
8007 'in a tab it\'s better to send these keys to the host so they can be ' +
8008 'used in vim or emacs.\n' +
8009 '\n' +
8010 'If true, Ctrl-1..9 will be handled by the browser. If false, Ctrl-1..9 ' +
8011 'will be sent to the host. If null, autodetect based on browser platform ' +
8012 'and window type.'],
8013
8014 'pass-meta-number':
8015 [hterm.PreferenceManager.categories.Keyboard, null, 'tristate',
8016 'Set whether we should pass Meta-1..9 to the browser.\n' +
8017 '\n' +
8018 'This is handy when running hterm in a browser tab, so that you don\'t ' +
8019 'lose Chrome\'s "switch to tab" keyboard accelerators. When not running ' +
8020 'in a tab it\'s better to send these keys to the host so they can be ' +
8021 'used in vim or emacs.\n' +
8022 '\n' +
8023 'If true, Meta-1..9 will be handled by the browser. If false, Meta-1..9 ' +
8024 'will be sent to the host. If null, autodetect based on browser platform ' +
8025 'and window type.'],
8026
8027 'pass-meta-v':
8028 [hterm.PreferenceManager.categories.Keyboard, true, 'bool',
8029 'Set whether meta-V gets passed to host.'],
8030
8031 'receive-encoding':
8032 [hterm.PreferenceManager.categories.Encoding, 'utf-8', ['utf-8', 'raw'],
8033 'Set the expected encoding for data received from the host.\n' +
8034 '\n' +
8035 'Valid values are \'utf-8\' and \'raw\'.'],
8036
8037 'scroll-on-keystroke':
8038 [hterm.PreferenceManager.categories.Scrolling, true, 'bool',
8039 'If true, scroll to the bottom on any keystroke.'],
8040
8041 'scroll-on-output':
8042 [hterm.PreferenceManager.categories.Scrolling, false, 'bool',
8043 'If true, scroll to the bottom on terminal output.'],
8044
8045 'scrollbar-visible':
8046 [hterm.PreferenceManager.categories.Scrolling, true, 'bool',
8047 'The vertical scrollbar mode.'],
8048
8049 'scroll-wheel-move-multiplier':
8050 [hterm.PreferenceManager.categories.Scrolling, 1, 'int',
8051 'The multiplier for the pixel delta in mousewheel event caused by the ' +
8052 'scroll wheel. Alters how fast the page scrolls.'],
8053
8054 'send-encoding':
8055 [hterm.PreferenceManager.categories.Encoding, 'utf-8', ['utf-8', 'raw'],
8056 'Set the encoding for data sent to host.'],
8057
8058 'shift-insert-paste':
8059 [hterm.PreferenceManager.categories.Keyboard, true, 'bool',
8060 'Shift + Insert pastes if true, sent to host if false.'],
8061
8062 'user-css':
8063 [hterm.PreferenceManager.categories.Appearance, '', 'url',
8064 'URL of user stylesheet to include in the terminal document.']
8065};
8066
8067hterm.PreferenceManager.prototype = {
8068 __proto__: lib.PreferenceManager.prototype
8069};
8070// SOURCE FILE: hterm/js/hterm_pubsub.js
8071// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
8072// Use of this source code is governed by a BSD-style license that can be
8073// found in the LICENSE file.
8074
8075'use strict';
8076
8077/**
8078 * Utility class used to add publish/subscribe/unsubscribe functionality to
8079 * an existing object.
8080 */
8081hterm.PubSub = function() {
8082 this.observers_ = {};
8083};
8084
8085/**
8086 * Add publish, subscribe, and unsubscribe methods to an existing object.
8087 *
8088 * No other properties of the object are touched, so there is no need to
8089 * worry about clashing private properties.
8090 *
8091 * @param {Object} obj The object to add this behavior to.
8092 */
8093hterm.PubSub.addBehavior = function(obj) {
8094 var pubsub = new hterm.PubSub();
8095 for (var m in hterm.PubSub.prototype) {
8096 obj[m] = hterm.PubSub.prototype[m].bind(pubsub);
8097 }
8098};
8099
8100/**
8101 * Subscribe to be notified of messages about a subject.
8102 *
8103 * @param {string} subject The subject to subscribe to.
8104 * @param {function(Object)} callback The function to invoke for notifications.
8105 */
8106hterm.PubSub.prototype.subscribe = function(subject, callback) {
8107 if (!(subject in this.observers_))
8108 this.observers_[subject] = [];
8109
8110 this.observers_[subject].push(callback);
8111};
8112
8113/**
8114 * Unsubscribe from a subject.
8115 *
8116 * @param {string} subject The subject to unsubscribe from.
8117 * @param {function(Object)} callback A callback previously registered via
8118 * subscribe().
8119 */
8120hterm.PubSub.prototype.unsubscribe = function(subject, callback) {
8121 var list = this.observers_[subject];
8122 if (!list)
8123 throw 'Invalid subject: ' + subject;
8124
8125 var i = list.indexOf(callback);
8126 if (i < 0)
8127 throw 'Not subscribed: ' + subject;
8128
8129 list.splice(i, 1);
8130};
8131
8132/**
8133 * Publish a message about a subject.
8134 *
8135 * Subscribers (and the optional final callback) are invoked asynchronously.
8136 * This method will return before anyone is actually notified.
8137 *
8138 * @param {string} subject The subject to publish about.
8139 * @param {Object} e An arbitrary object associated with this notification.
8140 * @param {function(Object)} opt_lastCallback An optional function to call after
8141 * all subscribers have been notified.
8142 */
8143hterm.PubSub.prototype.publish = function(subject, e, opt_lastCallback) {
8144 function notifyList(i) {
8145 // Set this timeout before invoking the callback, so we don't have to
8146 // concern ourselves with exceptions.
8147 if (i < list.length - 1)
8148 setTimeout(notifyList, 0, i + 1);
8149
8150 list[i](e);
8151 }
8152
8153 var list = this.observers_[subject];
8154 if (list) {
8155 // Copy the list, in case it changes while we're notifying.
8156 list = [].concat(list);
8157 }
8158
8159 if (opt_lastCallback) {
8160 if (list) {
8161 list.push(opt_lastCallback);
8162 } else {
8163 list = [opt_lastCallback];
8164 }
8165 }
8166
8167 if (list)
8168 setTimeout(notifyList, 0, 0);
8169};
8170// SOURCE FILE: hterm/js/hterm_screen.js
8171// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
8172// Use of this source code is governed by a BSD-style license that can be
8173// found in the LICENSE file.
8174
8175'use strict';
8176
8177lib.rtdep('lib.f', 'lib.wc',
8178 'hterm.RowCol', 'hterm.Size', 'hterm.TextAttributes');
8179
8180/**
8181 * @fileoverview This class represents a single terminal screen full of text.
8182 *
8183 * It maintains the current cursor position and has basic methods for text
8184 * insert and overwrite, and adding or removing rows from the screen.
8185 *
8186 * This class has no knowledge of the scrollback buffer.
8187 *
8188 * The number of rows on the screen is determined only by the number of rows
8189 * that the caller inserts into the screen. If a caller wants to ensure a
8190 * constant number of rows on the screen, it's their responsibility to remove a
8191 * row for each row inserted.
8192 *
8193 * The screen width, in contrast, is enforced locally.
8194 *
8195 *
8196 * In practice...
8197 * - The hterm.Terminal class holds two hterm.Screen instances. One for the
8198 * primary screen and one for the alternate screen.
8199 *
8200 * - The html.Screen class only cares that rows are HTMLElements. In the
8201 * larger context of hterm, however, the rows happen to be displayed by an
8202 * hterm.ScrollPort and have to follow a few rules as a result. Each
8203 * row must be rooted by the custom HTML tag 'x-row', and each must have a
8204 * rowIndex property that corresponds to the index of the row in the context
8205 * of the scrollback buffer. These invariants are enforced by hterm.Terminal
8206 * because that is the class using the hterm.Screen in the context of an
8207 * hterm.ScrollPort.
8208 */
8209
8210/**
8211 * Create a new screen instance.
8212 *
8213 * The screen initially has no rows and a maximum column count of 0.
8214 *
8215 * @param {integer} opt_columnCount The maximum number of columns for this
8216 * screen. See insertString() and overwriteString() for information about
8217 * what happens when too many characters are added too a row. Defaults to
8218 * 0 if not provided.
8219 */
8220hterm.Screen = function(opt_columnCount) {
8221 /**
8222 * Public, read-only access to the rows in this screen.
8223 */
8224 this.rowsArray = [];
8225
8226 // The max column width for this screen.
8227 this.columnCount_ = opt_columnCount || 80;
8228
8229 // The current color, bold, underline and blink attributes.
8230 this.textAttributes = new hterm.TextAttributes(window.document);
8231
8232 // Current zero-based cursor coordinates.
8233 this.cursorPosition = new hterm.RowCol(0, 0);
8234
8235 // The node containing the row that the cursor is positioned on.
8236 this.cursorRowNode_ = null;
8237
8238 // The node containing the span of text that the cursor is positioned on.
8239 this.cursorNode_ = null;
8240
8241 // The offset in column width into cursorNode_ where the cursor is positioned.
8242 this.cursorOffset_ = null;
8243};
8244
8245/**
8246 * Return the screen size as an hterm.Size object.
8247 *
8248 * @return {hterm.Size} hterm.Size object representing the current number
8249 * of rows and columns in this screen.
8250 */
8251hterm.Screen.prototype.getSize = function() {
8252 return new hterm.Size(this.columnCount_, this.rowsArray.length);
8253};
8254
8255/**
8256 * Return the current number of rows in this screen.
8257 *
8258 * @return {integer} The number of rows in this screen.
8259 */
8260hterm.Screen.prototype.getHeight = function() {
8261 return this.rowsArray.length;
8262};
8263
8264/**
8265 * Return the current number of columns in this screen.
8266 *
8267 * @return {integer} The number of columns in this screen.
8268 */
8269hterm.Screen.prototype.getWidth = function() {
8270 return this.columnCount_;
8271};
8272
8273/**
8274 * Set the maximum number of columns per row.
8275 *
8276 * @param {integer} count The maximum number of columns per row.
8277 */
8278hterm.Screen.prototype.setColumnCount = function(count) {
8279 this.columnCount_ = count;
8280
8281 if (this.cursorPosition.column >= count)
8282 this.setCursorPosition(this.cursorPosition.row, count - 1);
8283};
8284
8285/**
8286 * Remove the first row from the screen and return it.
8287 *
8288 * @return {HTMLElement} The first row in this screen.
8289 */
8290hterm.Screen.prototype.shiftRow = function() {
8291 return this.shiftRows(1)[0];
8292};
8293
8294/**
8295 * Remove rows from the top of the screen and return them as an array.
8296 *
8297 * @param {integer} count The number of rows to remove.
8298 * @return {Array.<HTMLElement>} The selected rows.
8299 */
8300hterm.Screen.prototype.shiftRows = function(count) {
8301 return this.rowsArray.splice(0, count);
8302};
8303
8304/**
8305 * Insert a row at the top of the screen.
8306 *
8307 * @param {HTMLElement} row The row to insert.
8308 */
8309hterm.Screen.prototype.unshiftRow = function(row) {
8310 this.rowsArray.splice(0, 0, row);
8311};
8312
8313/**
8314 * Insert rows at the top of the screen.
8315 *
8316 * @param {Array.<HTMLElement>} rows The rows to insert.
8317 */
8318hterm.Screen.prototype.unshiftRows = function(rows) {
8319 this.rowsArray.unshift.apply(this.rowsArray, rows);
8320};
8321
8322/**
8323 * Remove the last row from the screen and return it.
8324 *
8325 * @return {HTMLElement} The last row in this screen.
8326 */
8327hterm.Screen.prototype.popRow = function() {
8328 return this.popRows(1)[0];
8329};
8330
8331/**
8332 * Remove rows from the bottom of the screen and return them as an array.
8333 *
8334 * @param {integer} count The number of rows to remove.
8335 * @return {Array.<HTMLElement>} The selected rows.
8336 */
8337hterm.Screen.prototype.popRows = function(count) {
8338 return this.rowsArray.splice(this.rowsArray.length - count, count);
8339};
8340
8341/**
8342 * Insert a row at the bottom of the screen.
8343 *
8344 * @param {HTMLElement} row The row to insert.
8345 */
8346hterm.Screen.prototype.pushRow = function(row) {
8347 this.rowsArray.push(row);
8348};
8349
8350/**
8351 * Insert rows at the bottom of the screen.
8352 *
8353 * @param {Array.<HTMLElement>} rows The rows to insert.
8354 */
8355hterm.Screen.prototype.pushRows = function(rows) {
8356 rows.push.apply(this.rowsArray, rows);
8357};
8358
8359/**
8360 * Insert a row at the specified row of the screen.
8361 *
8362 * @param {integer} index The index to insert the row.
8363 * @param {HTMLElement} row The row to insert.
8364 */
8365hterm.Screen.prototype.insertRow = function(index, row) {
8366 this.rowsArray.splice(index, 0, row);
8367};
8368
8369/**
8370 * Insert rows at the specified row of the screen.
8371 *
8372 * @param {integer} index The index to insert the rows.
8373 * @param {Array.<HTMLElement>} rows The rows to insert.
8374 */
8375hterm.Screen.prototype.insertRows = function(index, rows) {
8376 for (var i = 0; i < rows.length; i++) {
8377 this.rowsArray.splice(index + i, 0, rows[i]);
8378 }
8379};
8380
8381/**
8382 * Remove a row from the screen and return it.
8383 *
8384 * @param {integer} index The index of the row to remove.
8385 * @return {HTMLElement} The selected row.
8386 */
8387hterm.Screen.prototype.removeRow = function(index) {
8388 return this.rowsArray.splice(index, 1)[0];
8389};
8390
8391/**
8392 * Remove rows from the bottom of the screen and return them as an array.
8393 *
8394 * @param {integer} index The index to start removing rows.
8395 * @param {integer} count The number of rows to remove.
8396 * @return {Array.<HTMLElement>} The selected rows.
8397 */
8398hterm.Screen.prototype.removeRows = function(index, count) {
8399 return this.rowsArray.splice(index, count);
8400};
8401
8402/**
8403 * Invalidate the current cursor position.
8404 *
8405 * This sets this.cursorPosition to (0, 0) and clears out some internal
8406 * data.
8407 *
8408 * Attempting to insert or overwrite text while the cursor position is invalid
8409 * will raise an obscure exception.
8410 */
8411hterm.Screen.prototype.invalidateCursorPosition = function() {
8412 this.cursorPosition.move(0, 0);
8413 this.cursorRowNode_ = null;
8414 this.cursorNode_ = null;
8415 this.cursorOffset_ = null;
8416};
8417
8418/**
8419 * Clear the contents of the cursor row.
8420 */
8421hterm.Screen.prototype.clearCursorRow = function() {
8422 this.cursorRowNode_.innerHTML = '';
8423 this.cursorRowNode_.removeAttribute('line-overflow');
8424 this.cursorOffset_ = 0;
8425 this.cursorPosition.column = 0;
8426 this.cursorPosition.overflow = false;
8427
8428 var text;
8429 if (this.textAttributes.isDefault()) {
8430 text = '';
8431 } else {
8432 text = lib.f.getWhitespace(this.columnCount_);
8433 }
8434
8435 // We shouldn't honor inverse colors when clearing an area, to match
8436 // xterm's back color erase behavior.
8437 var inverse = this.textAttributes.inverse;
8438 this.textAttributes.inverse = false;
8439 this.textAttributes.syncColors();
8440
8441 var node = this.textAttributes.createContainer(text);
8442 this.cursorRowNode_.appendChild(node);
8443 this.cursorNode_ = node;
8444
8445 this.textAttributes.inverse = inverse;
8446 this.textAttributes.syncColors();
8447};
8448
8449/**
8450 * Mark the current row as having overflowed to the next line.
8451 *
8452 * The line overflow state is used when converting a range of rows into text.
8453 * It makes it possible to recombine two or more overflow terminal rows into
8454 * a single line.
8455 *
8456 * This is distinct from the cursor being in the overflow state. Cursor
8457 * overflow indicates that printing at the cursor position will commit a
8458 * line overflow, unless it is preceded by a repositioning of the cursor
8459 * to a non-overflow state.
8460 */
8461hterm.Screen.prototype.commitLineOverflow = function() {
8462 this.cursorRowNode_.setAttribute('line-overflow', true);
8463};
8464
8465/**
8466 * Relocate the cursor to a give row and column.
8467 *
8468 * @param {integer} row The zero based row.
8469 * @param {integer} column The zero based column.
8470 */
8471hterm.Screen.prototype.setCursorPosition = function(row, column) {
8472 if (!this.rowsArray.length) {
8473 console.warn('Attempt to set cursor position on empty screen.');
8474 return;
8475 }
8476
8477 if (row >= this.rowsArray.length) {
8478 console.error('Row out of bounds: ' + row);
8479 row = this.rowsArray.length - 1;
8480 } else if (row < 0) {
8481 console.error('Row out of bounds: ' + row);
8482 row = 0;
8483 }
8484
8485 if (column >= this.columnCount_) {
8486 console.error('Column out of bounds: ' + column);
8487 column = this.columnCount_ - 1;
8488 } else if (column < 0) {
8489 console.error('Column out of bounds: ' + column);
8490 column = 0;
8491 }
8492
8493 this.cursorPosition.overflow = false;
8494
8495 var rowNode = this.rowsArray[row];
8496 var node = rowNode.firstChild;
8497
8498 if (!node) {
8499 node = rowNode.ownerDocument.createTextNode('');
8500 rowNode.appendChild(node);
8501 }
8502
8503 var currentColumn = 0;
8504
8505 if (rowNode == this.cursorRowNode_) {
8506 if (column >= this.cursorPosition.column - this.cursorOffset_) {
8507 node = this.cursorNode_;
8508 currentColumn = this.cursorPosition.column - this.cursorOffset_;
8509 }
8510 } else {
8511 this.cursorRowNode_ = rowNode;
8512 }
8513
8514 this.cursorPosition.move(row, column);
8515
8516 while (node) {
8517 var offset = column - currentColumn;
8518 var width = hterm.TextAttributes.nodeWidth(node);
8519 if (!node.nextSibling || width > offset) {
8520 this.cursorNode_ = node;
8521 this.cursorOffset_ = offset;
8522 return;
8523 }
8524
8525 currentColumn += width;
8526 node = node.nextSibling;
8527 }
8528};
8529
8530/**
8531 * Set the provided selection object to be a caret selection at the current
8532 * cursor position.
8533 */
8534hterm.Screen.prototype.syncSelectionCaret = function(selection) {
8535 try {
8536 selection.collapse(this.cursorNode_, this.cursorOffset_);
8537 } catch (firefoxIgnoredException) {
8538 // FF can throw an exception if the range is off, rather than just not
8539 // performing the collapse.
8540 }
8541};
8542
8543/**
8544 * Split a single node into two nodes at the given offset.
8545 *
8546 * For example:
8547 * Given the DOM fragment '<div><span>Hello World</span></div>', call splitNode_
8548 * passing the span and an offset of 6. This would modify the fragment to
8549 * become: '<div><span>Hello </span><span>World</span></div>'. If the span
8550 * had any attributes they would have been copied to the new span as well.
8551 *
8552 * The to-be-split node must have a container, so that the new node can be
8553 * placed next to it.
8554 *
8555 * @param {HTMLNode} node The node to split.
8556 * @param {integer} offset The offset into the node where the split should
8557 * occur.
8558 */
8559hterm.Screen.prototype.splitNode_ = function(node, offset) {
8560 var afterNode = node.cloneNode(false);
8561
8562 var textContent = node.textContent;
8563 node.textContent = hterm.TextAttributes.nodeSubstr(node, 0, offset);
8564 afterNode.textContent = lib.wc.substr(textContent, offset);
8565
8566 if (afterNode.textContent)
8567 node.parentNode.insertBefore(afterNode, node.nextSibling);
8568 if (!node.textContent)
8569 node.parentNode.removeChild(node);
8570};
8571
8572/**
8573 * Ensure that text is clipped and the cursor is clamped to the column count.
8574 */
8575hterm.Screen.prototype.maybeClipCurrentRow = function() {
8576 var width = hterm.TextAttributes.nodeWidth(this.cursorRowNode_);
8577
8578 if (width <= this.columnCount_) {
8579 // Current row does not need clipping, but may need clamping.
8580 if (this.cursorPosition.column >= this.columnCount_) {
8581 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
8582 this.cursorPosition.overflow = true;
8583 }
8584
8585 return;
8586 }
8587
8588 // Save off the current column so we can maybe restore it later.
8589 var currentColumn = this.cursorPosition.column;
8590
8591 // Move the cursor to the final column.
8592 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
8593
8594 // Remove any text that partially overflows.
8595 width = hterm.TextAttributes.nodeWidth(this.cursorNode_);
8596
8597 if (this.cursorOffset_ < width - 1) {
8598 this.cursorNode_.textContent = hterm.TextAttributes.nodeSubstr(
8599 this.cursorNode_, 0, this.cursorOffset_ + 1);
8600 }
8601
8602 // Remove all nodes after the cursor.
8603 var rowNode = this.cursorRowNode_;
8604 var node = this.cursorNode_.nextSibling;
8605
8606 while (node) {
8607 rowNode.removeChild(node);
8608 node = this.cursorNode_.nextSibling;
8609 }
8610
8611 if (currentColumn < this.columnCount_) {
8612 // If the cursor was within the screen before we started then restore its
8613 // position.
8614 this.setCursorPosition(this.cursorPosition.row, currentColumn);
8615 } else {
8616 // Otherwise leave it at the the last column in the overflow state.
8617 this.cursorPosition.overflow = true;
8618 }
8619};
8620
8621/**
8622 * Insert a string at the current character position using the current
8623 * text attributes.
8624 *
8625 * You must call maybeClipCurrentRow() after in order to clip overflowed
8626 * text and clamp the cursor.
8627 *
8628 * It is also up to the caller to properly maintain the line overflow state
8629 * using hterm.Screen..commitLineOverflow().
8630 */
8631hterm.Screen.prototype.insertString = function(str) {
8632 var cursorNode = this.cursorNode_;
8633 var cursorNodeText = cursorNode.textContent;
8634
8635 this.cursorRowNode_.removeAttribute('line-overflow');
8636
8637 // We may alter the width of the string by prepending some missing
8638 // whitespaces, so we need to record the string width ahead of time.
8639 var strWidth = lib.wc.strWidth(str);
8640
8641 // No matter what, before this function exits the cursor column will have
8642 // moved this much.
8643 this.cursorPosition.column += strWidth;
8644
8645 // Local cache of the cursor offset.
8646 var offset = this.cursorOffset_;
8647
8648 // Reverse offset is the offset measured from the end of the string.
8649 // Zero implies that the cursor is at the end of the cursor node.
8650 var reverseOffset = hterm.TextAttributes.nodeWidth(cursorNode) - offset;
8651
8652 if (reverseOffset < 0) {
8653 // A negative reverse offset means the cursor is positioned past the end
8654 // of the characters on this line. We'll need to insert the missing
8655 // whitespace.
8656 var ws = lib.f.getWhitespace(-reverseOffset);
8657
8658 // This whitespace should be completely unstyled. Underline, background
8659 // color, and strikethrough would be visible on whitespace, so we can't use
8660 // one of those spans to hold the text.
8661 if (!(this.textAttributes.underline ||
8662 this.textAttributes.strikethrough ||
8663 this.textAttributes.background ||
8664 this.textAttributes.wcNode ||
8665 this.textAttributes.tileData != null)) {
8666 // Best case scenario, we can just pretend the spaces were part of the
8667 // original string.
8668 str = ws + str;
8669 } else if (cursorNode.nodeType == 3 ||
8670 !(cursorNode.wcNode ||
8671 cursorNode.tileNode ||
8672 cursorNode.style.textDecoration ||
8673 cursorNode.style.backgroundColor)) {
8674 // Second best case, the current node is able to hold the whitespace.
8675 cursorNode.textContent = (cursorNodeText += ws);
8676 } else {
8677 // Worst case, we have to create a new node to hold the whitespace.
8678 var wsNode = cursorNode.ownerDocument.createTextNode(ws);
8679 this.cursorRowNode_.insertBefore(wsNode, cursorNode.nextSibling);
8680 this.cursorNode_ = cursorNode = wsNode;
8681 this.cursorOffset_ = offset = -reverseOffset;
8682 cursorNodeText = ws;
8683 }
8684
8685 // We now know for sure that we're at the last character of the cursor node.
8686 reverseOffset = 0;
8687 }
8688
8689 if (this.textAttributes.matchesContainer(cursorNode)) {
8690 // The new text can be placed directly in the cursor node.
8691 if (reverseOffset == 0) {
8692 cursorNode.textContent = cursorNodeText + str;
8693 } else if (offset == 0) {
8694 cursorNode.textContent = str + cursorNodeText;
8695 } else {
8696 cursorNode.textContent =
8697 hterm.TextAttributes.nodeSubstr(cursorNode, 0, offset) +
8698 str + hterm.TextAttributes.nodeSubstr(cursorNode, offset);
8699 }
8700
8701 this.cursorOffset_ += strWidth;
8702 return;
8703 }
8704
8705 // The cursor node is the wrong style for the new text. If we're at the
8706 // beginning or end of the cursor node, then the adjacent node is also a
8707 // potential candidate.
8708
8709 if (offset == 0) {
8710 // At the beginning of the cursor node, the check the previous sibling.
8711 var previousSibling = cursorNode.previousSibling;
8712 if (previousSibling &&
8713 this.textAttributes.matchesContainer(previousSibling)) {
8714 previousSibling.textContent += str;
8715 this.cursorNode_ = previousSibling;
8716 this.cursorOffset_ = lib.wc.strWidth(previousSibling.textContent);
8717 return;
8718 }
8719
8720 var newNode = this.textAttributes.createContainer(str);
8721 this.cursorRowNode_.insertBefore(newNode, cursorNode);
8722 this.cursorNode_ = newNode;
8723 this.cursorOffset_ = strWidth;
8724 return;
8725 }
8726
8727 if (reverseOffset == 0) {
8728 // At the end of the cursor node, the check the next sibling.
8729 var nextSibling = cursorNode.nextSibling;
8730 if (nextSibling &&
8731 this.textAttributes.matchesContainer(nextSibling)) {
8732 nextSibling.textContent = str + nextSibling.textContent;
8733 this.cursorNode_ = nextSibling;
8734 this.cursorOffset_ = lib.wc.strWidth(str);
8735 return;
8736 }
8737
8738 var newNode = this.textAttributes.createContainer(str);
8739 this.cursorRowNode_.insertBefore(newNode, nextSibling);
8740 this.cursorNode_ = newNode;
8741 // We specifically need to include any missing whitespace here, since it's
8742 // going in a new node.
8743 this.cursorOffset_ = hterm.TextAttributes.nodeWidth(newNode);
8744 return;
8745 }
8746
8747 // Worst case, we're somewhere in the middle of the cursor node. We'll
8748 // have to split it into two nodes and insert our new container in between.
8749 this.splitNode_(cursorNode, offset);
8750 var newNode = this.textAttributes.createContainer(str);
8751 this.cursorRowNode_.insertBefore(newNode, cursorNode.nextSibling);
8752 this.cursorNode_ = newNode;
8753 this.cursorOffset_ = strWidth;
8754};
8755
8756/**
8757 * Overwrite the text at the current cursor position.
8758 *
8759 * You must call maybeClipCurrentRow() after in order to clip overflowed
8760 * text and clamp the cursor.
8761 *
8762 * It is also up to the caller to properly maintain the line overflow state
8763 * using hterm.Screen..commitLineOverflow().
8764 */
8765hterm.Screen.prototype.overwriteString = function(str) {
8766 var maxLength = this.columnCount_ - this.cursorPosition.column;
8767 if (!maxLength)
8768 return [str];
8769
8770 var width = lib.wc.strWidth(str);
8771 if (this.textAttributes.matchesContainer(this.cursorNode_) &&
8772 this.cursorNode_.textContent.substr(this.cursorOffset_) == str) {
8773 // This overwrite would be a no-op, just move the cursor and return.
8774 this.cursorOffset_ += width;
8775 this.cursorPosition.column += width;
8776 return;
8777 }
8778
8779 this.deleteChars(Math.min(width, maxLength));
8780 this.insertString(str);
8781};
8782
8783/**
8784 * Forward-delete one or more characters at the current cursor position.
8785 *
8786 * Text to the right of the deleted characters is shifted left. Only affects
8787 * characters on the same row as the cursor.
8788 *
8789 * @param {integer} count The column width of characters to delete. This is
8790 * clamped to the column width minus the cursor column.
8791 * @return {integer} The column width of the characters actually deleted.
8792 */
8793hterm.Screen.prototype.deleteChars = function(count) {
8794 var node = this.cursorNode_;
8795 var offset = this.cursorOffset_;
8796
8797 var currentCursorColumn = this.cursorPosition.column;
8798 count = Math.min(count, this.columnCount_ - currentCursorColumn);
8799 if (!count)
8800 return 0;
8801
8802 var rv = count;
8803 var startLength, endLength;
8804
8805 while (node && count) {
8806 startLength = hterm.TextAttributes.nodeWidth(node);
8807 node.textContent = hterm.TextAttributes.nodeSubstr(node, 0, offset) +
8808 hterm.TextAttributes.nodeSubstr(node, offset + count);
8809 endLength = hterm.TextAttributes.nodeWidth(node);
8810 count -= startLength - endLength;
8811 if (offset < startLength && endLength && startLength == endLength) {
8812 // No characters were deleted when there should be. We're probably trying
8813 // to delete one column width from a wide character node. We remove the
8814 // wide character node here and replace it with a single space.
8815 var spaceNode = this.textAttributes.createContainer(' ');
8816 node.parentNode.insertBefore(spaceNode, node.nextSibling);
8817 node.textContent = '';
8818 endLength = 0;
8819 count -= 1;
8820 }
8821
8822 var nextNode = node.nextSibling;
8823 if (endLength == 0 && node != this.cursorNode_) {
8824 node.parentNode.removeChild(node);
8825 }
8826 node = nextNode;
8827 offset = 0;
8828 }
8829
8830 // Remove this.cursorNode_ if it is an empty non-text node.
8831 if (this.cursorNode_.nodeType != 3 && !this.cursorNode_.textContent) {
8832 var cursorNode = this.cursorNode_;
8833 if (cursorNode.previousSibling) {
8834 this.cursorNode_ = cursorNode.previousSibling;
8835 this.cursorOffset_ = hterm.TextAttributes.nodeWidth(
8836 cursorNode.previousSibling);
8837 } else if (cursorNode.nextSibling) {
8838 this.cursorNode_ = cursorNode.nextSibling;
8839 this.cursorOffset_ = 0;
8840 } else {
8841 var emptyNode = this.cursorRowNode_.ownerDocument.createTextNode('');
8842 this.cursorRowNode_.appendChild(emptyNode);
8843 this.cursorNode_ = emptyNode;
8844 this.cursorOffset_ = 0;
8845 }
8846 this.cursorRowNode_.removeChild(cursorNode);
8847 }
8848
8849 return rv;
8850};
8851
8852/**
8853 * Finds first X-ROW of a line containing specified X-ROW.
8854 * Used to support line overflow.
8855 *
8856 * @param {Node} row X-ROW to begin search for first row of line.
8857 * @return {Node} The X-ROW that is at the beginning of the line.
8858 **/
8859hterm.Screen.prototype.getLineStartRow_ = function(row) {
8860 while (row.previousSibling &&
8861 row.previousSibling.hasAttribute('line-overflow')) {
8862 row = row.previousSibling;
8863 }
8864 return row;
8865};
8866
8867/**
8868 * Gets text of a line beginning with row.
8869 * Supports line overflow.
8870 *
8871 * @param {Node} row First X-ROW of line.
8872 * @return {string} Text content of line.
8873 **/
8874hterm.Screen.prototype.getLineText_ = function(row) {
8875 var rowText = "";
8876 while (row) {
8877 rowText += row.textContent;
8878 if (row.hasAttribute('line-overflow')) {
8879 row = row.nextSibling;
8880 } else {
8881 break;
8882 }
8883 }
8884 return rowText;
8885};
8886
8887/**
8888 * Returns X-ROW that is ancestor of the node.
8889 *
8890 * @param {Node} node Node to get X-ROW ancestor for.
8891 * @return {Node} X-ROW ancestor of node, or null if not found.
8892 **/
8893hterm.Screen.prototype.getXRowAncestor_ = function(node) {
8894 while (node) {
8895 if (node.nodeName === 'X-ROW')
8896 break;
8897 node = node.parentNode;
8898 }
8899 return node;
8900};
8901
8902/**
8903 * Returns position within line of character at offset within node.
8904 * Supports line overflow.
8905 *
8906 * @param {Node} row X-ROW at beginning of line.
8907 * @param {Node} node Node to get position of.
8908 * @param {integer} offset Offset into node.
8909 *
8910 * @return {integer} Position within line of character at offset within node.
8911 **/
8912hterm.Screen.prototype.getPositionWithOverflow_ = function(row, node, offset) {
8913 if (!node)
8914 return -1;
8915 var ancestorRow = this.getXRowAncestor_(node);
8916 if (!ancestorRow)
8917 return -1;
8918 var position = 0;
8919 while (ancestorRow != row) {
8920 position += hterm.TextAttributes.nodeWidth(row);
8921 if (row.hasAttribute('line-overflow') && row.nextSibling) {
8922 row = row.nextSibling;
8923 } else {
8924 return -1;
8925 }
8926 }
8927 return position + this.getPositionWithinRow_(row, node, offset);
8928};
8929
8930/**
8931 * Returns position within row of character at offset within node.
8932 * Does not support line overflow.
8933 *
8934 * @param {Node} row X-ROW to get position within.
8935 * @param {Node} node Node to get position for.
8936 * @param {integer} offset Offset within node to get position for.
8937 * @return {integer} Position within row of character at offset within node.
8938 **/
8939hterm.Screen.prototype.getPositionWithinRow_ = function(row, node, offset) {
8940 if (node.parentNode != row) {
8941 return this.getPositionWithinRow_(node.parentNode, node, offset) +
8942 this.getPositionWithinRow_(row, node.parentNode, 0);
8943 }
8944 var position = 0;
8945 for (var i = 0; i < row.childNodes.length; i++) {
8946 var currentNode = row.childNodes[i];
8947 if (currentNode == node)
8948 return position + offset;
8949 position += hterm.TextAttributes.nodeWidth(currentNode);
8950 }
8951 return -1;
8952};
8953
8954/**
8955 * Returns the node and offset corresponding to position within line.
8956 * Supports line overflow.
8957 *
8958 * @param {Node} row X-ROW at beginning of line.
8959 * @param {integer} position Position within line to retrieve node and offset.
8960 * @return {Array} Two element array containing node and offset respectively.
8961 **/
8962hterm.Screen.prototype.getNodeAndOffsetWithOverflow_ = function(row, position) {
8963 while (row && position > hterm.TextAttributes.nodeWidth(row)) {
8964 if (row.hasAttribute('line-overflow') && row.nextSibling) {
8965 position -= hterm.TextAttributes.nodeWidth(row);
8966 row = row.nextSibling;
8967 } else {
8968 return -1;
8969 }
8970 }
8971 return this.getNodeAndOffsetWithinRow_(row, position);
8972};
8973
8974/**
8975 * Returns the node and offset corresponding to position within row.
8976 * Does not support line overflow.
8977 *
8978 * @param {Node} row X-ROW to get position within.
8979 * @param {integer} position Position within row to retrieve node and offset.
8980 * @return {Array} Two element array containing node and offset respectively.
8981 **/
8982hterm.Screen.prototype.getNodeAndOffsetWithinRow_ = function(row, position) {
8983 for (var i = 0; i < row.childNodes.length; i++) {
8984 var node = row.childNodes[i];
8985 var nodeTextWidth = hterm.TextAttributes.nodeWidth(node);
8986 if (position <= nodeTextWidth) {
8987 if (node.nodeName === 'SPAN') {
8988 /** Drill down to node contained by SPAN. **/
8989 return this.getNodeAndOffsetWithinRow_(node, position);
8990 } else {
8991 return [node, position];
8992 }
8993 }
8994 position -= nodeTextWidth;
8995 }
8996 return null;
8997};
8998
8999/**
9000 * Returns the node and offset corresponding to position within line.
9001 * Supports line overflow.
9002 *
9003 * @param {Node} row X-ROW at beginning of line.
9004 * @param {integer} start Start position of range within line.
9005 * @param {integer} end End position of range within line.
9006 * @param {Range} range Range to modify.
9007 **/
9008hterm.Screen.prototype.setRange_ = function(row, start, end, range) {
9009 var startNodeAndOffset = this.getNodeAndOffsetWithOverflow_(row, start);
9010 if (startNodeAndOffset == null)
9011 return;
9012 var endNodeAndOffset = this.getNodeAndOffsetWithOverflow_(row, end);
9013 if (endNodeAndOffset == null)
9014 return;
9015 range.setStart(startNodeAndOffset[0], startNodeAndOffset[1]);
9016 range.setEnd(endNodeAndOffset[0], endNodeAndOffset[1]);
9017};
9018
9019/**
9020 * Expands selection to surround URLs.
9021 *
9022 * @param {Selection} selection Selection to expand.
9023 **/
9024hterm.Screen.prototype.expandSelection = function(selection) {
9025 if (!selection)
9026 return;
9027
9028 var range = selection.getRangeAt(0);
9029 if (!range || range.toString().match(/\s/))
9030 return;
9031
9032 var row = this.getLineStartRow_(this.getXRowAncestor_(range.startContainer));
9033 if (!row)
9034 return;
9035
9036 var startPosition = this.getPositionWithOverflow_(row,
9037 range.startContainer,
9038 range.startOffset);
9039 if (startPosition == -1)
9040 return;
9041 var endPosition = this.getPositionWithOverflow_(row,
9042 range.endContainer,
9043 range.endOffset);
9044 if (endPosition == -1)
9045 return;
9046
9047 // Matches can start with '~' or '.', since paths frequently do.
9048 var leftMatch = '[^\\s\\[\\](){}<>"\'\\^!@#$%&*,;:`]';
9049 var rightMatch = '[^\\s\\[\\](){}<>"\'\\^!@#$%&*,;:~.`]';
9050 var insideMatch = '[^\\s\\[\\](){}<>"\'\\^]*';
9051
9052 //Move start to the left.
9053 var rowText = this.getLineText_(row);
9054 var lineUpToRange = lib.wc.substring(rowText, 0, endPosition);
9055 var leftRegularExpression = new RegExp(leftMatch + insideMatch + "$");
9056 var expandedStart = lineUpToRange.search(leftRegularExpression);
9057 if (expandedStart == -1 || expandedStart > startPosition)
9058 return;
9059
9060 //Move end to the right.
9061 var lineFromRange = lib.wc.substring(rowText, startPosition,
9062 lib.wc.strWidth(rowText));
9063 var rightRegularExpression = new RegExp("^" + insideMatch + rightMatch);
9064 var found = lineFromRange.match(rightRegularExpression);
9065 if (!found)
9066 return;
9067 var expandedEnd = startPosition + lib.wc.strWidth(found[0]);
9068 if (expandedEnd == -1 || expandedEnd < endPosition)
9069 return;
9070
9071 this.setRange_(row, expandedStart, expandedEnd, range);
9072 selection.addRange(range);
9073};
9074// SOURCE FILE: hterm/js/hterm_scrollport.js
9075// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
9076// Use of this source code is governed by a BSD-style license that can be
9077// found in the LICENSE file.
9078
9079'use strict';
9080
9081lib.rtdep('lib.f', 'hterm.PubSub', 'hterm.Size');
9082
9083/**
9084 * A 'viewport' view of fixed-height rows with support for selection and
9085 * copy-to-clipboard.
9086 *
9087 * 'Viewport' in this case means that only the visible rows are in the DOM.
9088 * If the rowProvider has 100,000 rows, but the ScrollPort is only 25 rows
9089 * tall, then only 25 dom nodes are created. The ScrollPort will ask the
9090 * RowProvider to create new visible rows on demand as they are scrolled in
9091 * to the visible area.
9092 *
9093 * This viewport is designed so that select and copy-to-clipboard still works,
9094 * even when all or part of the selection is scrolled off screen.
9095 *
9096 * Note that the X11 mouse clipboard does not work properly when all or part
9097 * of the selection is off screen. It would be difficult to fix this without
9098 * adding significant overhead to pathologically large selection cases.
9099 *
9100 * The RowProvider should return rows rooted by the custom tag name 'x-row'.
9101 * This ensures that we can quickly assign the correct display height
9102 * to the rows with css.
9103 *
9104 * @param {RowProvider} rowProvider An object capable of providing rows as
9105 * raw text or row nodes.
9106 */
9107hterm.ScrollPort = function(rowProvider) {
9108 hterm.PubSub.addBehavior(this);
9109
9110 this.rowProvider_ = rowProvider;
9111
9112 // SWAG the character size until we can measure it.
9113 this.characterSize = new hterm.Size(10, 10);
9114
9115 // DOM node used for character measurement.
9116 this.ruler_ = null;
9117
9118 this.selection = new hterm.ScrollPort.Selection(this);
9119
9120 // A map of rowIndex => rowNode for each row that is drawn as part of a
9121 // pending redraw_() call. Null if there is no pending redraw_ call.
9122 this.currentRowNodeCache_ = null;
9123
9124 // A map of rowIndex => rowNode for each row that was drawn as part of the
9125 // previous redraw_() call.
9126 this.previousRowNodeCache_ = {};
9127
9128 // Used during scroll events to detect when the underlying cause is a resize.
9129 this.lastScreenWidth_ = null;
9130 this.lastScreenHeight_ = null;
9131
9132 // True if the user should be allowed to select text in the terminal.
9133 // This is disabled when the host requests mouse drag events so that we don't
9134 // end up with two notions of selection.
9135 this.selectionEnabled_ = true;
9136
9137 // The last row count returned by the row provider, re-populated during
9138 // syncScrollHeight().
9139 this.lastRowCount_ = 0;
9140
9141 // The scroll wheel pixel delta multiplier to increase/decrease
9142 // the scroll speed of mouse wheel events. See: https://goo.gl/sXelnq
9143 this.scrollWheelMultiplier_ = 1;
9144
9145 /**
9146 * True if the last scroll caused the scrollport to show the final row.
9147 */
9148 this.isScrolledEnd = true;
9149
9150 // The css rule that we use to control the height of a row.
9151 this.xrowCssRule_ = null;
9152
9153 /**
9154 * A guess at the current scrollbar width, fixed in resize().
9155 */
9156 this.currentScrollbarWidthPx = 16;
9157
9158 /**
9159 * Whether the ctrl-v key on the screen should paste.
9160 */
9161 this.ctrlVPaste = false;
9162
9163 this.div_ = null;
9164 this.document_ = null;
9165
9166 // Collection of active timeout handles.
9167 this.timeouts_ = {};
9168
9169 this.observers_ = {};
9170
9171 this.DEBUG_ = false;
9172}
9173
9174/**
9175 * Proxy for the native selection object which understands how to walk up the
9176 * DOM to find the containing row node and sort out which comes first.
9177 *
9178 * @param {hterm.ScrollPort} scrollPort The parent hterm.ScrollPort instance.
9179 */
9180hterm.ScrollPort.Selection = function(scrollPort) {
9181 this.scrollPort_ = scrollPort;
9182
9183 /**
9184 * The row containing the start of the selection.
9185 *
9186 * This may be partially or fully selected. It may be the selection anchor
9187 * or the focus, but its rowIndex is guaranteed to be less-than-or-equal-to
9188 * that of the endRow.
9189 *
9190 * If only one row is selected then startRow == endRow. If there is no
9191 * selection or the selection is collapsed then startRow == null.
9192 */
9193 this.startRow = null;
9194
9195 /**
9196 * The row containing the end of the selection.
9197 *
9198 * This may be partially or fully selected. It may be the selection anchor
9199 * or the focus, but its rowIndex is guaranteed to be greater-than-or-equal-to
9200 * that of the startRow.
9201 *
9202 * If only one row is selected then startRow == endRow. If there is no
9203 * selection or the selection is collapsed then startRow == null.
9204 */
9205 this.endRow = null;
9206
9207 /**
9208 * True if startRow != endRow.
9209 */
9210 this.isMultiline = null;
9211
9212 /**
9213 * True if the selection is just a point rather than a range.
9214 */
9215 this.isCollapsed = null;
9216};
9217
9218/**
9219 * Given a list of DOM nodes and a container, return the DOM node that
9220 * is first according to a depth-first search.
9221 *
9222 * Returns null if none of the children are found.
9223 */
9224hterm.ScrollPort.Selection.prototype.findFirstChild = function(
9225 parent, childAry) {
9226 var node = parent.firstChild;
9227
9228 while (node) {
9229 if (childAry.indexOf(node) != -1)
9230 return node;
9231
9232 if (node.childNodes.length) {
9233 var rv = this.findFirstChild(node, childAry);
9234 if (rv)
9235 return rv;
9236 }
9237
9238 node = node.nextSibling;
9239 }
9240
9241 return null;
9242};
9243
9244/**
9245 * Synchronize this object with the current DOM selection.
9246 *
9247 * This is a one-way synchronization, the DOM selection is copied to this
9248 * object, not the other way around.
9249 */
9250hterm.ScrollPort.Selection.prototype.sync = function() {
9251 var self = this;
9252
9253 // The dom selection object has no way to tell which nodes come first in
9254 // the document, so we have to figure that out.
9255 //
9256 // This function is used when we detect that the "anchor" node is first.
9257 function anchorFirst() {
9258 self.startRow = anchorRow;
9259 self.startNode = selection.anchorNode;
9260 self.startOffset = selection.anchorOffset;
9261 self.endRow = focusRow;
9262 self.endNode = selection.focusNode;
9263 self.endOffset = selection.focusOffset;
9264 }
9265
9266 // This function is used when we detect that the "focus" node is first.
9267 function focusFirst() {
9268 self.startRow = focusRow;
9269 self.startNode = selection.focusNode;
9270 self.startOffset = selection.focusOffset;
9271 self.endRow = anchorRow;
9272 self.endNode = selection.anchorNode;
9273 self.endOffset = selection.anchorOffset;
9274 }
9275
9276 var selection = this.scrollPort_.getDocument().getSelection();
9277
9278 this.startRow = null;
9279 this.endRow = null;
9280 this.isMultiline = null;
9281 this.isCollapsed = !selection || selection.isCollapsed;
9282
9283 if (this.isCollapsed)
9284 return;
9285
9286 var anchorRow = selection.anchorNode;
9287 while (anchorRow && !('rowIndex' in anchorRow)) {
9288 anchorRow = anchorRow.parentNode;
9289 }
9290
9291 if (!anchorRow) {
9292 console.error('Selection anchor is not rooted in a row node: ' +
9293 selection.anchorNode.nodeName);
9294 return;
9295 }
9296
9297 var focusRow = selection.focusNode;
9298 while (focusRow && !('rowIndex' in focusRow)) {
9299 focusRow = focusRow.parentNode;
9300 }
9301
9302 if (!focusRow) {
9303 console.error('Selection focus is not rooted in a row node: ' +
9304 selection.focusNode.nodeName);
9305 return;
9306 }
9307
9308 if (anchorRow.rowIndex < focusRow.rowIndex) {
9309 anchorFirst();
9310
9311 } else if (anchorRow.rowIndex > focusRow.rowIndex) {
9312 focusFirst();
9313
9314 } else if (selection.focusNode == selection.anchorNode) {
9315 if (selection.anchorOffset < selection.focusOffset) {
9316 anchorFirst();
9317 } else {
9318 focusFirst();
9319 }
9320
9321 } else {
9322 // The selection starts and ends in the same row, but isn't contained all
9323 // in a single node.
9324 var firstNode = this.findFirstChild(
9325 anchorRow, [selection.anchorNode, selection.focusNode]);
9326
9327 if (!firstNode)
9328 throw new Error('Unexpected error syncing selection.');
9329
9330 if (firstNode == selection.anchorNode) {
9331 anchorFirst();
9332 } else {
9333 focusFirst();
9334 }
9335 }
9336
9337 this.isMultiline = anchorRow.rowIndex != focusRow.rowIndex;
9338};
9339
9340
9341/**
9342 * Turn a div into this hterm.ScrollPort.
9343 */
9344hterm.ScrollPort.prototype.decorate = function(div) {
9345 this.div_ = div;
9346
9347 this.iframe_ = div.ownerDocument.createElement('iframe');
9348 this.iframe_.style.cssText = (
9349 'border: 0;' +
9350 'height: 100%;' +
9351 'position: absolute;' +
9352 'width: 100%');
9353
9354 // Set the iframe src to # in FF. Otherwise when the frame's
9355 // load event fires in FF it clears out the content of the iframe.
9356 if ('mozInnerScreenX' in window) // detect a FF only property
9357 this.iframe_.src = '#';
9358
9359 div.appendChild(this.iframe_);
9360
9361 this.iframe_.contentWindow.addEventListener('resize',
9362 this.onResize_.bind(this));
9363
9364 var doc = this.document_ = this.iframe_.contentDocument;
9365 doc.body.style.cssText = (
9366 'margin: 0px;' +
9367 'padding: 0px;' +
9368 'height: 100%;' +
9369 'width: 100%;' +
9370 'overflow: hidden;' +
9371 'cursor: text;' +
9372 '-webkit-user-select: none;' +
9373 '-moz-user-select: none;');
9374
9375 var style = doc.createElement('style');
9376 style.textContent = 'x-row {}';
9377 doc.head.appendChild(style);
9378
9379 this.xrowCssRule_ = doc.styleSheets[0].cssRules[0];
9380 this.xrowCssRule_.style.display = 'block';
9381
9382 this.userCssLink_ = doc.createElement('link');
9383 this.userCssLink_.setAttribute('rel', 'stylesheet');
9384
9385 // TODO(rginda): Sorry, this 'screen_' isn't the same thing as hterm.Screen
9386 // from screen.js. I need to pick a better name for one of them to avoid
9387 // the collision.
9388 this.screen_ = doc.createElement('x-screen');
9389 this.screen_.setAttribute('role', 'textbox');
9390 this.screen_.setAttribute('tabindex', '-1');
9391 this.screen_.style.cssText = (
9392 'display: block;' +
9393 'font-family: monospace;' +
9394 'font-size: 15px;' +
9395 'font-variant-ligatures: none;' +
9396 'height: 100%;' +
9397 'overflow-y: scroll; overflow-x: hidden;' +
9398 'white-space: pre;' +
9399 'width: 100%;' +
9400 'outline: none !important');
9401
9402 doc.body.appendChild(this.screen_);
9403
9404 this.screen_.addEventListener('scroll', this.onScroll_.bind(this));
9405 this.screen_.addEventListener('mousewheel', this.onScrollWheel_.bind(this));
9406 this.screen_.addEventListener(
9407 'DOMMouseScroll', this.onScrollWheel_.bind(this));
9408 this.screen_.addEventListener('copy', this.onCopy_.bind(this));
9409 this.screen_.addEventListener('paste', this.onPaste_.bind(this));
9410
9411 doc.body.addEventListener('keydown', this.onBodyKeyDown_.bind(this));
9412
9413 // This is the main container for the fixed rows.
9414 this.rowNodes_ = doc.createElement('div');
9415 this.rowNodes_.style.cssText = (
9416 'display: block;' +
9417 'position: fixed;' +
9418 'overflow: hidden;' +
9419 '-webkit-user-select: text;' +
9420 '-moz-user-select: text;');
9421 this.screen_.appendChild(this.rowNodes_);
9422
9423 // Two nodes to hold offscreen text during the copy event.
9424 this.topSelectBag_ = doc.createElement('x-select-bag');
9425 this.topSelectBag_.style.cssText = (
9426 'display: block;' +
9427 'overflow: hidden;' +
9428 'white-space: pre;');
9429
9430 this.bottomSelectBag_ = this.topSelectBag_.cloneNode();
9431
9432 // Nodes above the top fold and below the bottom fold are hidden. They are
9433 // only used to hold rows that are part of the selection but are currently
9434 // scrolled off the top or bottom of the visible range.
9435 this.topFold_ = doc.createElement('x-fold');
9436 this.topFold_.style.cssText = 'display: block;';
9437 this.rowNodes_.appendChild(this.topFold_);
9438
9439 this.bottomFold_ = this.topFold_.cloneNode();
9440 this.rowNodes_.appendChild(this.bottomFold_);
9441
9442 // This hidden div accounts for the vertical space that would be consumed by
9443 // all the rows in the buffer if they were visible. It's what causes the
9444 // scrollbar to appear on the 'x-screen', and it moves within the screen when
9445 // the scrollbar is moved.
9446 //
9447 // It is set 'visibility: hidden' to keep the browser from trying to include
9448 // it in the selection when a user 'drag selects' upwards (drag the mouse to
9449 // select and scroll at the same time). Without this, the selection gets
9450 // out of whack.
9451 this.scrollArea_ = doc.createElement('div');
9452 this.scrollArea_.style.cssText = 'visibility: hidden';
9453 this.screen_.appendChild(this.scrollArea_);
9454
9455 // This svg element is used to detect when the browser is zoomed. It must be
9456 // placed in the outermost document for currentScale to be correct.
9457 // TODO(rginda): This means that hterm nested in an iframe will not correctly
9458 // detect browser zoom level. We should come up with a better solution.
9459 // Note: This must be http:// else Chrome cannot create the element correctly.
9460 var xmlns = 'http://www.w3.org/2000/svg';
9461 this.svg_ = this.div_.ownerDocument.createElementNS(xmlns, 'svg');
9462 this.svg_.setAttribute('xmlns', xmlns);
9463 this.svg_.setAttribute('version', '1.1');
9464 this.svg_.style.cssText = (
9465 'position: absolute;' +
9466 'top: 0;' +
9467 'left: 0;' +
9468 'visibility: hidden');
9469
9470
9471 // We send focus to this element just before a paste happens, so we can
9472 // capture the pasted text and forward it on to someone who cares.
9473 this.pasteTarget_ = doc.createElement('textarea');
9474 this.pasteTarget_.setAttribute('tabindex', '-1');
9475 this.pasteTarget_.style.cssText = (
9476 'position: absolute;' +
9477 'height: 1px;' +
9478 'width: 1px;' +
9479 'left: 0px; ' +
9480 'bottom: 0px;' +
9481 'opacity: 0');
9482 this.pasteTarget_.contentEditable = true;
9483
9484 this.screen_.appendChild(this.pasteTarget_);
9485 this.pasteTarget_.addEventListener(
9486 'textInput', this.handlePasteTargetTextInput_.bind(this));
9487
9488 this.resize();
9489};
9490
9491/**
9492 * Select the font-family and font-smoothing for this scrollport.
9493 *
9494 * @param {string} fontFamily Value of the CSS 'font-family' to use for this
9495 * scrollport. Should be a monospace font.
9496 * @param {string} opt_smoothing Optional value for '-webkit-font-smoothing'.
9497 * Defaults to an empty string if not specified.
9498 */
9499hterm.ScrollPort.prototype.setFontFamily = function(fontFamily, opt_smoothing) {
9500 this.screen_.style.fontFamily = fontFamily;
9501 if (opt_smoothing) {
9502 this.screen_.style.webkitFontSmoothing = opt_smoothing;
9503 } else {
9504 this.screen_.style.webkitFontSmoothing = '';
9505 }
9506
9507 this.syncCharacterSize();
9508};
9509
9510hterm.ScrollPort.prototype.getFontFamily = function() {
9511 return this.screen_.style.fontFamily;
9512};
9513
9514/**
9515 * Set a custom stylesheet to include in the scrollport.
9516 *
9517 * Defaults to null, meaning no custom css is loaded. Set it back to null or
9518 * the empty string to remove a previously applied custom css.
9519 */
9520hterm.ScrollPort.prototype.setUserCss = function(url) {
9521 if (url) {
9522 this.userCssLink_.setAttribute('href', url);
9523
9524 if (!this.userCssLink_.parentNode)
9525 this.document_.head.appendChild(this.userCssLink_);
9526 } else if (this.userCssLink_.parentNode) {
9527 this.document_.head.removeChild(this.userCssLink_);
9528 }
9529};
9530
9531hterm.ScrollPort.prototype.focus = function() {
9532 this.iframe_.focus();
9533 this.screen_.focus();
9534};
9535
9536hterm.ScrollPort.prototype.getForegroundColor = function() {
9537 return this.screen_.style.color;
9538};
9539
9540hterm.ScrollPort.prototype.setForegroundColor = function(color) {
9541 this.screen_.style.color = color;
9542};
9543
9544hterm.ScrollPort.prototype.getBackgroundColor = function() {
9545 return this.screen_.style.backgroundColor;
9546};
9547
9548hterm.ScrollPort.prototype.setBackgroundColor = function(color) {
9549 this.screen_.style.backgroundColor = color;
9550};
9551
9552hterm.ScrollPort.prototype.setBackgroundImage = function(image) {
9553 this.screen_.style.backgroundImage = image;
9554};
9555
9556hterm.ScrollPort.prototype.setBackgroundSize = function(size) {
9557 this.screen_.style.backgroundSize = size;
9558};
9559
9560hterm.ScrollPort.prototype.setBackgroundPosition = function(position) {
9561 this.screen_.style.backgroundPosition = position;
9562};
9563
9564hterm.ScrollPort.prototype.setCtrlVPaste = function(ctrlVPaste) {
9565 this.ctrlVPaste = ctrlVPaste;
9566};
9567
9568/**
9569 * Get the usable size of the scrollport screen.
9570 *
9571 * The width will not include the scrollbar width.
9572 */
9573hterm.ScrollPort.prototype.getScreenSize = function() {
9574 var size = hterm.getClientSize(this.screen_);
9575 return {
9576 height: size.height,
9577 width: size.width - this.currentScrollbarWidthPx
9578 };
9579};
9580
9581/**
9582 * Get the usable width of the scrollport screen.
9583 *
9584 * This the widget width minus scrollbar width.
9585 */
9586hterm.ScrollPort.prototype.getScreenWidth = function() {
9587 return this.getScreenSize().width ;
9588};
9589
9590/**
9591 * Get the usable height of the scrollport screen.
9592 */
9593hterm.ScrollPort.prototype.getScreenHeight = function() {
9594 return this.getScreenSize().height;
9595};
9596
9597/**
9598 * Return the document that holds the visible rows of this hterm.ScrollPort.
9599 */
9600hterm.ScrollPort.prototype.getDocument = function() {
9601 return this.document_;
9602};
9603
9604/**
9605 * Returns the x-screen element that holds the rows of this hterm.ScrollPort.
9606 */
9607hterm.ScrollPort.prototype.getScreenNode = function() {
9608 return this.screen_;
9609};
9610
9611/**
9612 * Clear out any cached rowNodes.
9613 */
9614hterm.ScrollPort.prototype.resetCache = function() {
9615 this.currentRowNodeCache_ = null;
9616 this.previousRowNodeCache_ = {};
9617};
9618
9619/**
9620 * Change the current rowProvider.
9621 *
9622 * This will clear the row cache and cause a redraw.
9623 *
9624 * @param {Object} rowProvider An object capable of providing the rows
9625 * in this hterm.ScrollPort.
9626 */
9627hterm.ScrollPort.prototype.setRowProvider = function(rowProvider) {
9628 this.resetCache();
9629 this.rowProvider_ = rowProvider;
9630 this.scheduleRedraw();
9631};
9632
9633/**
9634 * Inform the ScrollPort that the root DOM nodes for some or all of the visible
9635 * rows are no longer valid.
9636 *
9637 * Specifically, this should be called if this.rowProvider_.getRowNode() now
9638 * returns an entirely different node than it did before. It does not
9639 * need to be called if the content of a row node is the only thing that
9640 * changed.
9641 *
9642 * This skips some of the overhead of a full redraw, but should not be used
9643 * in cases where the scrollport has been scrolled, or when the row count has
9644 * changed.
9645 */
9646hterm.ScrollPort.prototype.invalidate = function() {
9647 var node = this.topFold_.nextSibling;
9648 while (node != this.bottomFold_) {
9649 var nextSibling = node.nextSibling;
9650 node.parentElement.removeChild(node);
9651 node = nextSibling;
9652 }
9653
9654 this.previousRowNodeCache_ = null;
9655 var topRowIndex = this.getTopRowIndex();
9656 var bottomRowIndex = this.getBottomRowIndex(topRowIndex);
9657
9658 this.drawVisibleRows_(topRowIndex, bottomRowIndex);
9659};
9660
9661hterm.ScrollPort.prototype.scheduleInvalidate = function() {
9662 if (this.timeouts_.invalidate)
9663 return;
9664
9665 var self = this;
9666 this.timeouts_.invalidate = setTimeout(function () {
9667 delete self.timeouts_.invalidate;
9668 self.invalidate();
9669 }, 0);
9670};
9671
9672/**
9673 * Set the font size of the ScrollPort.
9674 */
9675hterm.ScrollPort.prototype.setFontSize = function(px) {
9676 this.screen_.style.fontSize = px + 'px';
9677 this.syncCharacterSize();
9678};
9679
9680/**
9681 * Return the current font size of the ScrollPort.
9682 */
9683hterm.ScrollPort.prototype.getFontSize = function() {
9684 return parseInt(this.screen_.style.fontSize);
9685};
9686
9687/**
9688 * Measure the size of a single character in pixels.
9689 *
9690 * @param {string} opt_weight The font weight to measure, or 'normal' if
9691 * omitted.
9692 * @return {hterm.Size} A new hterm.Size object.
9693 */
9694hterm.ScrollPort.prototype.measureCharacterSize = function(opt_weight) {
9695 // Number of lines used to average the height of a single character.
9696 var numberOfLines = 100;
9697 var rulerSingleLineContents = ('XXXXXXXXXXXXXXXXXXXX' +
9698 'XXXXXXXXXXXXXXXXXXXX' +
9699 'XXXXXXXXXXXXXXXXXXXX' +
9700 'XXXXXXXXXXXXXXXXXXXX' +
9701 'XXXXXXXXXXXXXXXXXXXX');
9702 if (!this.ruler_) {
9703 this.ruler_ = this.document_.createElement('div');
9704 this.ruler_.style.cssText = (
9705 'position: absolute;' +
9706 'top: 0;' +
9707 'left: 0;' +
9708 'visibility: hidden;' +
9709 'height: auto !important;' +
9710 'width: auto !important;');
9711
9712 // We need to put the text in a span to make the size calculation
9713 // work properly in Firefox
9714 this.rulerSpan_ = this.document_.createElement('span');
9715 var rulerContents = '' + rulerSingleLineContents;
9716 for (var i = 0; i < numberOfLines - 1; ++i)
9717 rulerContents += String.fromCharCode(13) + rulerSingleLineContents;
9718
9719 this.rulerSpan_.innerHTML = rulerContents;
9720 this.ruler_.appendChild(this.rulerSpan_);
9721
9722 this.rulerBaseline_ = this.document_.createElement('span');
9723 // We want to collapse it on the baseline
9724 this.rulerBaseline_.style.fontSize = '0px';
9725 this.rulerBaseline_.textContent = 'X';
9726 }
9727
9728 this.rulerSpan_.style.fontWeight = opt_weight || '';
9729
9730 this.rowNodes_.appendChild(this.ruler_);
9731 var rulerSize = hterm.getClientSize(this.rulerSpan_);
9732
9733 var size = new hterm.Size(rulerSize.width / rulerSingleLineContents.length,
9734 rulerSize.height / numberOfLines);
9735
9736 this.ruler_.appendChild(this.rulerBaseline_);
9737 size.baseline = this.rulerBaseline_.offsetTop;
9738 this.ruler_.removeChild(this.rulerBaseline_);
9739
9740 this.rowNodes_.removeChild(this.ruler_);
9741
9742 this.div_.ownerDocument.body.appendChild(this.svg_);
9743 size.zoomFactor = this.svg_.currentScale;
9744 this.div_.ownerDocument.body.removeChild(this.svg_);
9745
9746 return size;
9747};
9748
9749/**
9750 * Synchronize the character size.
9751 *
9752 * This will re-measure the current character size and adjust the height
9753 * of an x-row to match.
9754 */
9755hterm.ScrollPort.prototype.syncCharacterSize = function() {
9756 this.characterSize = this.measureCharacterSize();
9757
9758 var lineHeight = this.characterSize.height + 'px';
9759 this.xrowCssRule_.style.height = lineHeight;
9760 this.topSelectBag_.style.height = lineHeight;
9761 this.bottomSelectBag_.style.height = lineHeight;
9762
9763 this.resize();
9764
9765 if (this.DEBUG_) {
9766 // When we're debugging we add padding to the body so that the offscreen
9767 // elements are visible.
9768 this.document_.body.style.paddingTop =
9769 this.document_.body.style.paddingBottom =
9770 3 * this.characterSize.height + 'px';
9771 }
9772};
9773
9774/**
9775 * Reset dimensions and visible row count to account for a change in the
9776 * dimensions of the 'x-screen'.
9777 */
9778hterm.ScrollPort.prototype.resize = function() {
9779 this.currentScrollbarWidthPx = hterm.getClientWidth(this.screen_) -
9780 this.screen_.clientWidth;
9781
9782 this.syncScrollHeight();
9783 this.syncRowNodesDimensions_();
9784
9785 var self = this;
9786 this.publish(
9787 'resize', { scrollPort: this },
9788 function() {
9789 self.scrollRowToBottom(self.rowProvider_.getRowCount());
9790 self.scheduleRedraw();
9791 });
9792};
9793
9794/**
9795 * Set the position and size of the row nodes element.
9796 */
9797hterm.ScrollPort.prototype.syncRowNodesDimensions_ = function() {
9798 var screenSize = this.getScreenSize();
9799
9800 this.lastScreenWidth_ = screenSize.width;
9801 this.lastScreenHeight_ = screenSize.height;
9802
9803 // We don't want to show a partial row because it would be distracting
9804 // in a terminal, so we floor any fractional row count.
9805 this.visibleRowCount = lib.f.smartFloorDivide(
9806 screenSize.height, this.characterSize.height);
9807
9808 // Then compute the height of our integral number of rows.
9809 var visibleRowsHeight = this.visibleRowCount * this.characterSize.height;
9810
9811 // Then the difference between the screen height and total row height needs to
9812 // be made up for as top margin. We need to record this value so it
9813 // can be used later to determine the topRowIndex.
9814 this.visibleRowTopMargin = 0;
9815 this.visibleRowBottomMargin = screenSize.height - visibleRowsHeight;
9816
9817 this.topFold_.style.marginBottom = this.visibleRowTopMargin + 'px';
9818
9819
9820 var topFoldOffset = 0;
9821 var node = this.topFold_.previousSibling;
9822 while (node) {
9823 topFoldOffset += hterm.getClientHeight(node);
9824 node = node.previousSibling;
9825 }
9826
9827 // Set the dimensions of the visible rows container.
9828 this.rowNodes_.style.width = screenSize.width + 'px';
9829 this.rowNodes_.style.height = visibleRowsHeight + topFoldOffset + 'px';
9830 this.rowNodes_.style.left = this.screen_.offsetLeft + 'px';
9831 this.rowNodes_.style.top = this.screen_.offsetTop - topFoldOffset + 'px';
9832};
9833
9834hterm.ScrollPort.prototype.syncScrollHeight = function() {
9835 // Resize the scroll area to appear as though it contains every row.
9836 this.lastRowCount_ = this.rowProvider_.getRowCount();
9837 this.scrollArea_.style.height = (this.characterSize.height *
9838 this.lastRowCount_ +
9839 this.visibleRowTopMargin +
9840 this.visibleRowBottomMargin +
9841 'px');
9842};
9843
9844/**
9845 * Schedule a redraw to happen asynchronously.
9846 *
9847 * If this method is called multiple times before the redraw has a chance to
9848 * run only one redraw occurs.
9849 */
9850hterm.ScrollPort.prototype.scheduleRedraw = function() {
9851 if (this.timeouts_.redraw)
9852 return;
9853
9854 var self = this;
9855 this.timeouts_.redraw = setTimeout(function () {
9856 delete self.timeouts_.redraw;
9857 self.redraw_();
9858 }, 0);
9859};
9860
9861/**
9862 * Redraw the current hterm.ScrollPort based on the current scrollbar position.
9863 *
9864 * When redrawing, we are careful to make sure that the rows that start or end
9865 * the current selection are not touched in any way. Doing so would disturb
9866 * the selection, and cleaning up after that would cause flashes at best and
9867 * incorrect selection at worst. Instead, we modify the DOM around these nodes.
9868 * We even stash the selection start/end outside of the visible area if
9869 * they are not supposed to be visible in the hterm.ScrollPort.
9870 */
9871hterm.ScrollPort.prototype.redraw_ = function() {
9872 this.resetSelectBags_();
9873 this.selection.sync();
9874
9875 this.syncScrollHeight();
9876
9877 this.currentRowNodeCache_ = {};
9878
9879 var topRowIndex = this.getTopRowIndex();
9880 var bottomRowIndex = this.getBottomRowIndex(topRowIndex);
9881
9882 this.drawTopFold_(topRowIndex);
9883 this.drawBottomFold_(bottomRowIndex);
9884 this.drawVisibleRows_(topRowIndex, bottomRowIndex);
9885
9886 this.syncRowNodesDimensions_();
9887
9888 this.previousRowNodeCache_ = this.currentRowNodeCache_;
9889 this.currentRowNodeCache_ = null;
9890
9891 this.isScrolledEnd = (
9892 this.getTopRowIndex() + this.visibleRowCount >= this.lastRowCount_);
9893};
9894
9895/**
9896 * Ensure that the nodes above the top fold are as they should be.
9897 *
9898 * If the selection start and/or end nodes are above the visible range
9899 * of this hterm.ScrollPort then the dom will be adjusted so that they appear
9900 * before the top fold (the first x-fold element, aka this.topFold).
9901 *
9902 * If not, the top fold will be the first element.
9903 *
9904 * It is critical that this method does not move the selection nodes. Doing
9905 * so would clear the current selection. Instead, the rest of the DOM is
9906 * adjusted around them.
9907 */
9908hterm.ScrollPort.prototype.drawTopFold_ = function(topRowIndex) {
9909 if (!this.selection.startRow ||
9910 this.selection.startRow.rowIndex >= topRowIndex) {
9911 // Selection is entirely below the top fold, just make sure the fold is
9912 // the first child.
9913 if (this.rowNodes_.firstChild != this.topFold_)
9914 this.rowNodes_.insertBefore(this.topFold_, this.rowNodes_.firstChild);
9915
9916 return;
9917 }
9918
9919 if (!this.selection.isMultiline ||
9920 this.selection.endRow.rowIndex >= topRowIndex) {
9921 // Only the startRow is above the fold.
9922 if (this.selection.startRow.nextSibling != this.topFold_)
9923 this.rowNodes_.insertBefore(this.topFold_,
9924 this.selection.startRow.nextSibling);
9925 } else {
9926 // Both rows are above the fold.
9927 if (this.selection.endRow.nextSibling != this.topFold_) {
9928 this.rowNodes_.insertBefore(this.topFold_,
9929 this.selection.endRow.nextSibling);
9930 }
9931
9932 // Trim any intermediate lines.
9933 while (this.selection.startRow.nextSibling !=
9934 this.selection.endRow) {
9935 this.rowNodes_.removeChild(this.selection.startRow.nextSibling);
9936 }
9937 }
9938
9939 while(this.rowNodes_.firstChild != this.selection.startRow) {
9940 this.rowNodes_.removeChild(this.rowNodes_.firstChild);
9941 }
9942};
9943
9944/**
9945 * Ensure that the nodes below the bottom fold are as they should be.
9946 *
9947 * If the selection start and/or end nodes are below the visible range
9948 * of this hterm.ScrollPort then the dom will be adjusted so that they appear
9949 * after the bottom fold (the second x-fold element, aka this.bottomFold).
9950 *
9951 * If not, the bottom fold will be the last element.
9952 *
9953 * It is critical that this method does not move the selection nodes. Doing
9954 * so would clear the current selection. Instead, the rest of the DOM is
9955 * adjusted around them.
9956 */
9957hterm.ScrollPort.prototype.drawBottomFold_ = function(bottomRowIndex) {
9958 if (!this.selection.endRow ||
9959 this.selection.endRow.rowIndex <= bottomRowIndex) {
9960 // Selection is entirely above the bottom fold, just make sure the fold is
9961 // the last child.
9962 if (this.rowNodes_.lastChild != this.bottomFold_)
9963 this.rowNodes_.appendChild(this.bottomFold_);
9964
9965 return;
9966 }
9967
9968 if (!this.selection.isMultiline ||
9969 this.selection.startRow.rowIndex <= bottomRowIndex) {
9970 // Only the endRow is below the fold.
9971 if (this.bottomFold_.nextSibling != this.selection.endRow)
9972 this.rowNodes_.insertBefore(this.bottomFold_,
9973 this.selection.endRow);
9974 } else {
9975 // Both rows are below the fold.
9976 if (this.bottomFold_.nextSibling != this.selection.startRow) {
9977 this.rowNodes_.insertBefore(this.bottomFold_,
9978 this.selection.startRow);
9979 }
9980
9981 // Trim any intermediate lines.
9982 while (this.selection.startRow.nextSibling !=
9983 this.selection.endRow) {
9984 this.rowNodes_.removeChild(this.selection.startRow.nextSibling);
9985 }
9986 }
9987
9988 while(this.rowNodes_.lastChild != this.selection.endRow) {
9989 this.rowNodes_.removeChild(this.rowNodes_.lastChild);
9990 }
9991};
9992
9993/**
9994 * Ensure that the rows between the top and bottom folds are as they should be.
9995 *
9996 * This method assumes that drawTopFold_() and drawBottomFold_() have already
9997 * run, and that they have left any visible selection row (selection start
9998 * or selection end) between the folds.
9999 *
10000 * It recycles DOM nodes from the previous redraw where possible, but will ask
10001 * the rowSource to make new nodes if necessary.
10002 *
10003 * It is critical that this method does not move the selection nodes. Doing
10004 * so would clear the current selection. Instead, the rest of the DOM is
10005 * adjusted around them.
10006 */
10007hterm.ScrollPort.prototype.drawVisibleRows_ = function(
10008 topRowIndex, bottomRowIndex) {
10009 var self = this;
10010
10011 // Keep removing nodes, starting with currentNode, until we encounter
10012 // targetNode. Throws on failure.
10013 function removeUntilNode(currentNode, targetNode) {
10014 while (currentNode != targetNode) {
10015 if (!currentNode)
10016 throw 'Did not encounter target node';
10017
10018 if (currentNode == self.bottomFold_)
10019 throw 'Encountered bottom fold before target node';
10020
10021 var deadNode = currentNode;
10022 currentNode = currentNode.nextSibling;
10023 deadNode.parentNode.removeChild(deadNode);
10024 }
10025 }
10026
10027 // Shorthand for things we're going to use a lot.
10028 var selectionStartRow = this.selection.startRow;
10029 var selectionEndRow = this.selection.endRow;
10030 var bottomFold = this.bottomFold_;
10031
10032 // The node we're examining during the current iteration.
10033 var node = this.topFold_.nextSibling;
10034
10035 var targetDrawCount = Math.min(this.visibleRowCount,
10036 this.rowProvider_.getRowCount());
10037
10038 for (var drawCount = 0; drawCount < targetDrawCount; drawCount++) {
10039 var rowIndex = topRowIndex + drawCount;
10040
10041 if (node == bottomFold) {
10042 // We've hit the bottom fold, we need to insert a new row.
10043 var newNode = this.fetchRowNode_(rowIndex);
10044 if (!newNode) {
10045 console.log("Couldn't fetch row index: " + rowIndex);
10046 break;
10047 }
10048
10049 this.rowNodes_.insertBefore(newNode, node);
10050 continue;
10051 }
10052
10053 if (node.rowIndex == rowIndex) {
10054 // This node is in the right place, move along.
10055 node = node.nextSibling;
10056 continue;
10057 }
10058
10059 if (selectionStartRow && selectionStartRow.rowIndex == rowIndex) {
10060 // The selection start row is supposed to be here, remove nodes until
10061 // we find it.
10062 removeUntilNode(node, selectionStartRow);
10063 node = selectionStartRow.nextSibling;
10064 continue;
10065 }
10066
10067 if (selectionEndRow && selectionEndRow.rowIndex == rowIndex) {
10068 // The selection end row is supposed to be here, remove nodes until
10069 // we find it.
10070 removeUntilNode(node, selectionEndRow);
10071 node = selectionEndRow.nextSibling;
10072 continue;
10073 }
10074
10075 if (node == selectionStartRow || node == selectionEndRow) {
10076 // We encountered the start/end of the selection, but we don't want it
10077 // yet. Insert a new row instead.
10078 var newNode = this.fetchRowNode_(rowIndex);
10079 if (!newNode) {
10080 console.log("Couldn't fetch row index: " + rowIndex);
10081 break;
10082 }
10083
10084 this.rowNodes_.insertBefore(newNode, node);
10085 continue;
10086 }
10087
10088 // There is nothing special about this node, but it's in our way. Replace
10089 // it with the node that should be here.
10090 var newNode = this.fetchRowNode_(rowIndex);
10091 if (!newNode) {
10092 console.log("Couldn't fetch row index: " + rowIndex);
10093 break;
10094 }
10095
10096 if (node == newNode) {
10097 node = node.nextSibling;
10098 continue;
10099 }
10100
10101 this.rowNodes_.insertBefore(newNode, node);
10102 if (!newNode.nextSibling)
10103 debugger;
10104 this.rowNodes_.removeChild(node);
10105 node = newNode.nextSibling;
10106 }
10107
10108 if (node != this.bottomFold_)
10109 removeUntilNode(node, bottomFold);
10110};
10111
10112/**
10113 * Empty out both select bags and remove them from the document.
10114 *
10115 * These nodes hold the text between the start and end of the selection
10116 * when that text is otherwise off screen. They are filled out in the
10117 * onCopy_ event.
10118 */
10119hterm.ScrollPort.prototype.resetSelectBags_ = function() {
10120 if (this.topSelectBag_.parentNode) {
10121 this.topSelectBag_.textContent = '';
10122 this.topSelectBag_.parentNode.removeChild(this.topSelectBag_);
10123 }
10124
10125 if (this.bottomSelectBag_.parentNode) {
10126 this.bottomSelectBag_.textContent = '';
10127 this.bottomSelectBag_.parentNode.removeChild(this.bottomSelectBag_);
10128 }
10129};
10130
10131/**
10132 * Place a row node in the cache of visible nodes.
10133 *
10134 * This method may only be used during a redraw_.
10135 */
10136hterm.ScrollPort.prototype.cacheRowNode_ = function(rowNode) {
10137 this.currentRowNodeCache_[rowNode.rowIndex] = rowNode;
10138};
10139
10140/**
10141 * Fetch the row node for the given index.
10142 *
10143 * This will return a node from the cache if possible, or will request one
10144 * from the RowProvider if not.
10145 *
10146 * If a redraw_ is in progress the row will be added to the current cache.
10147 */
10148hterm.ScrollPort.prototype.fetchRowNode_ = function(rowIndex) {
10149 var node;
10150
10151 if (this.previousRowNodeCache_ && rowIndex in this.previousRowNodeCache_) {
10152 node = this.previousRowNodeCache_[rowIndex];
10153 } else {
10154 node = this.rowProvider_.getRowNode(rowIndex);
10155 }
10156
10157 if (this.currentRowNodeCache_)
10158 this.cacheRowNode_(node);
10159
10160 return node;
10161};
10162
10163/**
10164 * Select all rows in the viewport.
10165 */
10166hterm.ScrollPort.prototype.selectAll = function() {
10167 var firstRow;
10168
10169 if (this.topFold_.nextSibling.rowIndex != 0) {
10170 while (this.topFold_.previousSibling) {
10171 this.rowNodes_.removeChild(this.topFold_.previousSibling);
10172 }
10173
10174 firstRow = this.fetchRowNode_(0);
10175 this.rowNodes_.insertBefore(firstRow, this.topFold_);
10176 this.syncRowNodesDimensions_();
10177 } else {
10178 firstRow = this.topFold_.nextSibling;
10179 }
10180
10181 var lastRowIndex = this.rowProvider_.getRowCount() - 1;
10182 var lastRow;
10183
10184 if (this.bottomFold_.previousSibling.rowIndex != lastRowIndex) {
10185 while (this.bottomFold_.nextSibling) {
10186 this.rowNodes_.removeChild(this.bottomFold_.nextSibling);
10187 }
10188
10189 lastRow = this.fetchRowNode_(lastRowIndex);
10190 this.rowNodes_.appendChild(lastRow);
10191 } else {
10192 lastRow = this.bottomFold_.previousSibling.rowIndex;
10193 }
10194
10195 var selection = this.document_.getSelection();
10196 selection.collapse(firstRow, 0);
10197 selection.extend(lastRow, lastRow.childNodes.length);
10198
10199 this.selection.sync();
10200};
10201
10202/**
10203 * Return the maximum scroll position in pixels.
10204 */
10205hterm.ScrollPort.prototype.getScrollMax_ = function(e) {
10206 return (hterm.getClientHeight(this.scrollArea_) +
10207 this.visibleRowTopMargin + this.visibleRowBottomMargin -
10208 hterm.getClientHeight(this.screen_));
10209};
10210
10211/**
10212 * Scroll the given rowIndex to the top of the hterm.ScrollPort.
10213 *
10214 * @param {integer} rowIndex Index of the target row.
10215 */
10216hterm.ScrollPort.prototype.scrollRowToTop = function(rowIndex) {
10217 this.syncScrollHeight();
10218
10219 this.isScrolledEnd = (
10220 rowIndex + this.visibleRowCount >= this.lastRowCount_);
10221
10222 var scrollTop = rowIndex * this.characterSize.height +
10223 this.visibleRowTopMargin;
10224
10225 var scrollMax = this.getScrollMax_();
10226 if (scrollTop > scrollMax)
10227 scrollTop = scrollMax;
10228
10229 if (this.screen_.scrollTop == scrollTop)
10230 return;
10231
10232 this.screen_.scrollTop = scrollTop;
10233 this.scheduleRedraw();
10234};
10235
10236/**
10237 * Scroll the given rowIndex to the bottom of the hterm.ScrollPort.
10238 *
10239 * @param {integer} rowIndex Index of the target row.
10240 */
10241hterm.ScrollPort.prototype.scrollRowToBottom = function(rowIndex) {
10242 this.syncScrollHeight();
10243
10244 this.isScrolledEnd = (
10245 rowIndex + this.visibleRowCount >= this.lastRowCount_);
10246
10247 var scrollTop = rowIndex * this.characterSize.height +
10248 this.visibleRowTopMargin + this.visibleRowBottomMargin;
10249 scrollTop -= this.visibleRowCount * this.characterSize.height;
10250
10251 if (scrollTop < 0)
10252 scrollTop = 0;
10253
10254 if (this.screen_.scrollTop == scrollTop)
10255 return;
10256
10257 this.screen_.scrollTop = scrollTop;
10258};
10259
10260/**
10261 * Return the row index of the first visible row.
10262 *
10263 * This is based on the scroll position. If a redraw_ is in progress this
10264 * returns the row that *should* be at the top.
10265 */
10266hterm.ScrollPort.prototype.getTopRowIndex = function() {
10267 return Math.round(this.screen_.scrollTop / this.characterSize.height);
10268};
10269
10270/**
10271 * Return the row index of the last visible row.
10272 *
10273 * This is based on the scroll position. If a redraw_ is in progress this
10274 * returns the row that *should* be at the bottom.
10275 */
10276hterm.ScrollPort.prototype.getBottomRowIndex = function(topRowIndex) {
10277 return topRowIndex + this.visibleRowCount - 1;
10278};
10279
10280/**
10281 * Handler for scroll events.
10282 *
10283 * The onScroll event fires when scrollArea's scrollTop property changes. This
10284 * may be due to the user manually move the scrollbar, or a programmatic change.
10285 */
10286hterm.ScrollPort.prototype.onScroll_ = function(e) {
10287 var screenSize = this.getScreenSize();
10288 if (screenSize.width != this.lastScreenWidth_ ||
10289 screenSize.height != this.lastScreenHeight_) {
10290 // This event may also fire during a resize (but before the resize event!).
10291 // This happens when the browser moves the scrollbar as part of the resize.
10292 // In these cases, we want to ignore the scroll event and let onResize
10293 // handle things. If we don't, then we end up scrolling to the wrong
10294 // position after a resize.
10295 this.resize();
10296 return;
10297 }
10298
10299 this.redraw_();
10300 this.publish('scroll', { scrollPort: this });
10301};
10302
10303/**
10304 * Clients can override this if they want to hear scrollwheel events.
10305 *
10306 * Clients may call event.preventDefault() if they want to keep the scrollport
10307 * from also handling the events.
10308 */
10309hterm.ScrollPort.prototype.onScrollWheel = function(e) {};
10310
10311/**
10312 * Handler for scroll-wheel events.
10313 *
10314 * The onScrollWheel event fires when the user moves their scrollwheel over this
10315 * hterm.ScrollPort. Because the frontmost element in the hterm.ScrollPort is
10316 * a fixed position DIV, the scroll wheel does nothing by default. Instead, we
10317 * have to handle it manually.
10318 */
10319hterm.ScrollPort.prototype.onScrollWheel_ = function(e) {
10320 this.onScrollWheel(e);
10321
10322 if (e.defaultPrevented)
10323 return;
10324
10325 // In FF, the event is DOMMouseScroll and puts the scroll pixel delta in the
10326 // 'detail' field of the event. It also flips the mapping of which direction
10327 // a negative number means in the scroll.
10328 var delta = e.type == 'DOMMouseScroll' ? (-1 * e.detail) : e.wheelDeltaY;
10329 delta *= this.scrollWheelMultiplier_;
10330
10331 var top = this.screen_.scrollTop - delta;
10332 if (top < 0)
10333 top = 0;
10334
10335 var scrollMax = this.getScrollMax_();
10336 if (top > scrollMax)
10337 top = scrollMax;
10338
10339 if (top != this.screen_.scrollTop) {
10340 // Moving scrollTop causes a scroll event, which triggers the redraw.
10341 this.screen_.scrollTop = top;
10342
10343 // Only preventDefault when we've actually scrolled. If there's nothing
10344 // to scroll we want to pass the event through so Chrome can detect the
10345 // overscroll.
10346 e.preventDefault();
10347 }
10348};
10349
10350/**
10351 * Handler for resize events.
10352 *
10353 * The browser will resize us such that the top row stays at the top, but we
10354 * prefer to the bottom row to stay at the bottom.
10355 */
10356hterm.ScrollPort.prototype.onResize_ = function(e) {
10357 // Re-measure, since onResize also happens for browser zoom changes.
10358 this.syncCharacterSize();
10359 this.resize();
10360};
10361
10362/**
10363 * Clients can override this if they want to hear copy events.
10364 *
10365 * Clients may call event.preventDefault() if they want to keep the scrollport
10366 * from also handling the events.
10367 */
10368hterm.ScrollPort.prototype.onCopy = function(e) { };
10369
10370/**
10371 * Handler for copy-to-clipboard events.
10372 *
10373 * If some or all of the selected rows are off screen we may need to fill in
10374 * the rows between selection start and selection end. This handler determines
10375 * if we're missing some of the selected text, and if so populates one or both
10376 * of the "select bags" with the missing text.
10377 */
10378hterm.ScrollPort.prototype.onCopy_ = function(e) {
10379 this.onCopy(e);
10380
10381 if (e.defaultPrevented)
10382 return;
10383
10384 this.resetSelectBags_();
10385 this.selection.sync();
10386
10387 if (!this.selection.startRow ||
10388 this.selection.endRow.rowIndex - this.selection.startRow.rowIndex < 2) {
10389 return;
10390 }
10391
10392 var topRowIndex = this.getTopRowIndex();
10393 var bottomRowIndex = this.getBottomRowIndex(topRowIndex);
10394
10395 if (this.selection.startRow.rowIndex < topRowIndex) {
10396 // Start of selection is above the top fold.
10397 var endBackfillIndex;
10398
10399 if (this.selection.endRow.rowIndex < topRowIndex) {
10400 // Entire selection is above the top fold.
10401 endBackfillIndex = this.selection.endRow.rowIndex;
10402 } else {
10403 // Selection extends below the top fold.
10404 endBackfillIndex = this.topFold_.nextSibling.rowIndex;
10405 }
10406
10407 this.topSelectBag_.textContent = this.rowProvider_.getRowsText(
10408 this.selection.startRow.rowIndex + 1, endBackfillIndex);
10409 this.rowNodes_.insertBefore(this.topSelectBag_,
10410 this.selection.startRow.nextSibling);
10411 this.syncRowNodesDimensions_();
10412 }
10413
10414 if (this.selection.endRow.rowIndex > bottomRowIndex) {
10415 // Selection ends below the bottom fold.
10416 var startBackfillIndex;
10417
10418 if (this.selection.startRow.rowIndex > bottomRowIndex) {
10419 // Entire selection is below the bottom fold.
10420 startBackfillIndex = this.selection.startRow.rowIndex + 1;
10421 } else {
10422 // Selection starts above the bottom fold.
10423 startBackfillIndex = this.bottomFold_.previousSibling.rowIndex + 1;
10424 }
10425
10426 this.bottomSelectBag_.textContent = this.rowProvider_.getRowsText(
10427 startBackfillIndex, this.selection.endRow.rowIndex);
10428 this.rowNodes_.insertBefore(this.bottomSelectBag_, this.selection.endRow);
10429 }
10430};
10431
10432/**
10433 * Focuses on the paste target on a ctrl-v keydown event, as in
10434 * FF a content editable element must be focused before the paste event.
10435 */
10436hterm.ScrollPort.prototype.onBodyKeyDown_ = function(e) {
10437 if (!this.ctrlVPaste)
10438 return;
10439
10440 var key = String.fromCharCode(e.which);
10441 var lowerKey = key.toLowerCase();
10442 if ((e.ctrlKey || e.metaKey) && lowerKey == "v")
10443 this.pasteTarget_.focus();
10444};
10445
10446/**
10447 * Handle a paste event on the the ScrollPort's screen element.
10448 */
10449hterm.ScrollPort.prototype.onPaste_ = function(e) {
10450 this.pasteTarget_.focus();
10451
10452 var self = this;
10453 setTimeout(function() {
10454 self.publish('paste', { text: self.pasteTarget_.value });
10455 self.pasteTarget_.value = '';
10456 self.screen_.focus();
10457 }, 0);
10458};
10459
10460/**
10461 * Handles a textInput event on the paste target. Stops this from
10462 * propagating as we want this to be handled in the onPaste_ method.
10463 */
10464hterm.ScrollPort.prototype.handlePasteTargetTextInput_ = function(e) {
10465 e.stopPropagation();
10466};
10467
10468/**
10469 * Set the vertical scrollbar mode of the ScrollPort.
10470 */
10471hterm.ScrollPort.prototype.setScrollbarVisible = function(state) {
10472 this.screen_.style.overflowY = state ? 'scroll' : 'hidden';
10473};
10474
10475/**
10476 * Set scroll wheel multiplier. This alters how much the screen scrolls on
10477 * mouse wheel events.
10478 */
10479hterm.ScrollPort.prototype.setScrollWheelMoveMultipler = function(multiplier) {
10480 this.scrollWheelMultiplier_ = multiplier;
10481};
10482// SOURCE FILE: hterm/js/hterm_terminal.js
10483// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
10484// Use of this source code is governed by a BSD-style license that can be
10485// found in the LICENSE file.
10486
10487'use strict';
10488
10489lib.rtdep('lib.colors', 'lib.PreferenceManager', 'lib.resource', 'lib.wc',
10490 'lib.f', 'hterm.Keyboard', 'hterm.Options', 'hterm.PreferenceManager',
10491 'hterm.Screen', 'hterm.ScrollPort', 'hterm.Size',
10492 'hterm.TextAttributes', 'hterm.VT');
10493
10494/**
10495 * Constructor for the Terminal class.
10496 *
10497 * A Terminal pulls together the hterm.ScrollPort, hterm.Screen and hterm.VT100
10498 * classes to provide the complete terminal functionality.
10499 *
10500 * There are a number of lower-level Terminal methods that can be called
10501 * directly to manipulate the cursor, text, scroll region, and other terminal
10502 * attributes. However, the primary method is interpret(), which parses VT
10503 * escape sequences and invokes the appropriate Terminal methods.
10504 *
10505 * This class was heavily influenced by Cory Maccarrone's Framebuffer class.
10506 *
10507 * TODO(rginda): Eventually we're going to need to support characters which are
10508 * displayed twice as wide as standard latin characters. This is to support
10509 * CJK (and possibly other character sets).
10510 *
10511 * @param {string} opt_profileId Optional preference profile name. If not
10512 * provided, defaults to 'default'.
10513 */
10514hterm.Terminal = function(opt_profileId) {
10515 this.profileId_ = null;
10516
10517 // Two screen instances.
10518 this.primaryScreen_ = new hterm.Screen();
10519 this.alternateScreen_ = new hterm.Screen();
10520
10521 // The "current" screen.
10522 this.screen_ = this.primaryScreen_;
10523
10524 // The local notion of the screen size. ScreenBuffers also have a size which
10525 // indicates their present size. During size changes, the two may disagree.
10526 // Also, the inactive screen's size is not altered until it is made the active
10527 // screen.
10528 this.screenSize = new hterm.Size(0, 0);
10529
10530 // The scroll port we'll be using to display the visible rows.
10531 this.scrollPort_ = new hterm.ScrollPort(this);
10532 this.scrollPort_.subscribe('resize', this.onResize_.bind(this));
10533 this.scrollPort_.subscribe('scroll', this.onScroll_.bind(this));
10534 this.scrollPort_.subscribe('paste', this.onPaste_.bind(this));
10535 this.scrollPort_.onCopy = this.onCopy_.bind(this);
10536
10537 // The div that contains this terminal.
10538 this.div_ = null;
10539
10540 // The document that contains the scrollPort. Defaulted to the global
10541 // document here so that the terminal is functional even if it hasn't been
10542 // inserted into a document yet, but re-set in decorate().
10543 this.document_ = window.document;
10544
10545 // The rows that have scrolled off screen and are no longer addressable.
10546 this.scrollbackRows_ = [];
10547
10548 // Saved tab stops.
10549 this.tabStops_ = [];
10550
10551 // Keep track of whether default tab stops have been erased; after a TBC
10552 // clears all tab stops, defaults aren't restored on resize until a reset.
10553 this.defaultTabStops = true;
10554
10555 // The VT's notion of the top and bottom rows. Used during some VT
10556 // cursor positioning and scrolling commands.
10557 this.vtScrollTop_ = null;
10558 this.vtScrollBottom_ = null;
10559
10560 // The DIV element for the visible cursor.
10561 this.cursorNode_ = null;
10562
10563 // The current cursor shape of the terminal.
10564 this.cursorShape_ = hterm.Terminal.cursorShape.BLOCK;
10565
10566 // The current color of the cursor.
10567 this.cursorColor_ = null;
10568
10569 // Cursor blink on/off cycle in ms, overwritten by prefs once they're loaded.
10570 this.cursorBlinkCycle_ = [100, 100];
10571
10572 // Pre-bound onCursorBlink_ handler, so we don't have to do this for each
10573 // cursor on/off servicing.
10574 this.myOnCursorBlink_ = this.onCursorBlink_.bind(this);
10575
10576 // These prefs are cached so we don't have to read from local storage with
10577 // each output and keystroke. They are initialized by the preference manager.
10578 this.backgroundColor_ = null;
10579 this.foregroundColor_ = null;
10580 this.scrollOnOutput_ = null;
10581 this.scrollOnKeystroke_ = null;
10582
10583 // True if we should override mouse event reporting to allow local selection.
10584 this.defeatMouseReports_ = false;
10585
10586 // Terminal bell sound.
10587 this.bellAudio_ = this.document_.createElement('audio');
10588 this.bellAudio_.setAttribute('preload', 'auto');
10589
10590 // All terminal bell notifications that have been generated (not necessarily
10591 // shown).
10592 this.bellNotificationList_ = [];
10593
10594 // Whether we have permission to display notifications.
10595 this.desktopNotificationBell_ = false;
10596
10597 // Cursor position and attributes saved with DECSC.
10598 this.savedOptions_ = {};
10599
10600 // The current mode bits for the terminal.
10601 this.options_ = new hterm.Options();
10602
10603 // Timeouts we might need to clear.
10604 this.timeouts_ = {};
10605
10606 // The VT escape sequence interpreter.
10607 this.vt = new hterm.VT(this);
10608
10609 // The keyboard handler.
10610 this.keyboard = new hterm.Keyboard(this);
10611
10612 // General IO interface that can be given to third parties without exposing
10613 // the entire terminal object.
10614 this.io = new hterm.Terminal.IO(this);
10615
10616 // True if mouse-click-drag should scroll the terminal.
10617 this.enableMouseDragScroll = true;
10618
10619 this.copyOnSelect = null;
10620 this.mousePasteButton = null;
10621
10622 // Whether to use the default window copy behavior.
10623 this.useDefaultWindowCopy = false;
10624
10625 this.clearSelectionAfterCopy = true;
10626
10627 this.realizeSize_(80, 24);
10628 this.setDefaultTabStops();
10629
10630 this.setProfile(opt_profileId || 'default',
10631 function() { this.onTerminalReady(); }.bind(this));
10632};
10633
10634/**
10635 * Possible cursor shapes.
10636 */
10637hterm.Terminal.cursorShape = {
10638 BLOCK: 'BLOCK',
10639 BEAM: 'BEAM',
10640 UNDERLINE: 'UNDERLINE'
10641};
10642
10643/**
10644 * Clients should override this to be notified when the terminal is ready
10645 * for use.
10646 *
10647 * The terminal initialization is asynchronous, and shouldn't be used before
10648 * this method is called.
10649 */
10650hterm.Terminal.prototype.onTerminalReady = function() { };
10651
10652/**
10653 * Default tab with of 8 to match xterm.
10654 */
10655hterm.Terminal.prototype.tabWidth = 8;
10656
10657/**
10658 * Select a preference profile.
10659 *
10660 * This will load the terminal preferences for the given profile name and
10661 * associate subsequent preference changes with the new preference profile.
10662 *
10663 * @param {string} profileId The name of the preference profile. Forward slash
10664 * characters will be removed from the name.
10665 * @param {function} opt_callback Optional callback to invoke when the profile
10666 * transition is complete.
10667 */
10668hterm.Terminal.prototype.setProfile = function(profileId, opt_callback) {
10669 this.profileId_ = profileId.replace(/\//g, '');
10670
10671 var terminal = this;
10672
10673 if (this.prefs_)
10674 this.prefs_.deactivate();
10675
10676 this.prefs_ = new hterm.PreferenceManager(this.profileId_);
10677 this.prefs_.addObservers(null, {
10678 'alt-gr-mode': function(v) {
10679 if (v == null) {
10680 if (navigator.language.toLowerCase() == 'en-us') {
10681 v = 'none';
10682 } else {
10683 v = 'right-alt';
10684 }
10685 } else if (typeof v == 'string') {
10686 v = v.toLowerCase();
10687 } else {
10688 v = 'none';
10689 }
10690
10691 if (!/^(none|ctrl-alt|left-alt|right-alt)$/.test(v))
10692 v = 'none';
10693
10694 terminal.keyboard.altGrMode = v;
10695 },
10696
10697 'alt-backspace-is-meta-backspace': function(v) {
10698 terminal.keyboard.altBackspaceIsMetaBackspace = v;
10699 },
10700
10701 'alt-is-meta': function(v) {
10702 terminal.keyboard.altIsMeta = v;
10703 },
10704
10705 'alt-sends-what': function(v) {
10706 if (!/^(escape|8-bit|browser-key)$/.test(v))
10707 v = 'escape';
10708
10709 terminal.keyboard.altSendsWhat = v;
10710 },
10711
10712 'audible-bell-sound': function(v) {
10713 var ary = v.match(/^lib-resource:(\S+)/);
10714 if (ary) {
10715 terminal.bellAudio_.setAttribute('src',
10716 lib.resource.getDataUrl(ary[1]));
10717 } else {
10718 terminal.bellAudio_.setAttribute('src', v);
10719 }
10720 },
10721
10722 'desktop-notification-bell': function(v) {
10723 if (v && Notification) {
10724 terminal.desktopNotificationBell_ =
10725 Notification.permission === 'granted';
10726 if (!terminal.desktopNotificationBell_) {
10727 // Note: We don't call Notification.requestPermission here because
10728 // Chrome requires the call be the result of a user action (such as an
10729 // onclick handler), and pref listeners are run asynchronously.
10730 //
10731 // A way of working around this would be to display a dialog in the
10732 // terminal with a "click-to-request-permission" button.
10733 console.warn('desktop-notification-bell is true but we do not have ' +
10734 'permission to display notifications.');
10735 }
10736 } else {
10737 terminal.desktopNotificationBell_ = false;
10738 }
10739 },
10740
10741 'background-color': function(v) {
10742 terminal.setBackgroundColor(v);
10743 },
10744
10745 'background-image': function(v) {
10746 terminal.scrollPort_.setBackgroundImage(v);
10747 },
10748
10749 'background-size': function(v) {
10750 terminal.scrollPort_.setBackgroundSize(v);
10751 },
10752
10753 'background-position': function(v) {
10754 terminal.scrollPort_.setBackgroundPosition(v);
10755 },
10756
10757 'backspace-sends-backspace': function(v) {
10758 terminal.keyboard.backspaceSendsBackspace = v;
10759 },
10760
10761 'character-map-overrides': function(v) {
10762 if (!(v == null || v instanceof Object)) {
10763 console.warn('Preference character-map-modifications is not an ' +
10764 'object: ' + v);
10765 return;
10766 }
10767
10768 for (var code in v) {
10769 var glmap = hterm.VT.CharacterMap.maps[code].glmap;
10770 for (var received in v[code]) {
10771 glmap[received] = v[code][received];
10772 }
10773 hterm.VT.CharacterMap.maps[code].reset(glmap);
10774 }
10775 },
10776
10777 'cursor-blink': function(v) {
10778 terminal.setCursorBlink(!!v);
10779 },
10780
10781 'cursor-blink-cycle': function(v) {
10782 if (v instanceof Array &&
10783 typeof v[0] == 'number' &&
10784 typeof v[1] == 'number') {
10785 terminal.cursorBlinkCycle_ = v;
10786 } else if (typeof v == 'number') {
10787 terminal.cursorBlinkCycle_ = [v, v];
10788 } else {
10789 // Fast blink indicates an error.
10790 terminal.cursorBlinkCycle_ = [100, 100];
10791 }
10792 },
10793
10794 'cursor-color': function(v) {
10795 terminal.setCursorColor(v);
10796 },
10797
10798 'color-palette-overrides': function(v) {
10799 if (!(v == null || v instanceof Object || v instanceof Array)) {
10800 console.warn('Preference color-palette-overrides is not an array or ' +
10801 'object: ' + v);
10802 return;
10803 }
10804
10805 lib.colors.colorPalette = lib.colors.stockColorPalette.concat();
10806
10807 if (v) {
10808 for (var key in v) {
10809 var i = parseInt(key);
10810 if (isNaN(i) || i < 0 || i > 255) {
10811 console.log('Invalid value in palette: ' + key + ': ' + v[key]);
10812 continue;
10813 }
10814
10815 if (v[i]) {
10816 var rgb = lib.colors.normalizeCSS(v[i]);
10817 if (rgb)
10818 lib.colors.colorPalette[i] = rgb;
10819 }
10820 }
10821 }
10822
10823 terminal.primaryScreen_.textAttributes.resetColorPalette();
10824 terminal.alternateScreen_.textAttributes.resetColorPalette();
10825 },
10826
10827 'copy-on-select': function(v) {
10828 terminal.copyOnSelect = !!v;
10829 },
10830
10831 'use-default-window-copy': function(v) {
10832 terminal.useDefaultWindowCopy = !!v;
10833 },
10834
10835 'clear-selection-after-copy': function(v) {
10836 terminal.clearSelectionAfterCopy = !!v;
10837 },
10838
10839 'ctrl-plus-minus-zero-zoom': function(v) {
10840 terminal.keyboard.ctrlPlusMinusZeroZoom = v;
10841 },
10842
10843 'ctrl-c-copy': function(v) {
10844 terminal.keyboard.ctrlCCopy = v;
10845 },
10846
10847 'ctrl-v-paste': function(v) {
10848 terminal.keyboard.ctrlVPaste = v;
10849 terminal.scrollPort_.setCtrlVPaste(v);
10850 },
10851
10852 'east-asian-ambiguous-as-two-column': function(v) {
10853 lib.wc.regardCjkAmbiguous = v;
10854 },
10855
10856 'enable-8-bit-control': function(v) {
10857 terminal.vt.enable8BitControl = !!v;
10858 },
10859
10860 'enable-bold': function(v) {
10861 terminal.syncBoldSafeState();
10862 },
10863
10864 'enable-bold-as-bright': function(v) {
10865 terminal.primaryScreen_.textAttributes.enableBoldAsBright = !!v;
10866 terminal.alternateScreen_.textAttributes.enableBoldAsBright = !!v;
10867 },
10868
10869 'enable-blink': function(v) {
10870 terminal.syncBlinkState();
10871 },
10872
10873 'enable-clipboard-write': function(v) {
10874 terminal.vt.enableClipboardWrite = !!v;
10875 },
10876
10877 'enable-dec12': function(v) {
10878 terminal.vt.enableDec12 = !!v;
10879 },
10880
10881 'font-family': function(v) {
10882 terminal.syncFontFamily();
10883 },
10884
10885 'font-size': function(v) {
10886 terminal.setFontSize(v);
10887 },
10888
10889 'font-smoothing': function(v) {
10890 terminal.syncFontFamily();
10891 },
10892
10893 'foreground-color': function(v) {
10894 terminal.setForegroundColor(v);
10895 },
10896
10897 'home-keys-scroll': function(v) {
10898 terminal.keyboard.homeKeysScroll = v;
10899 },
10900
10901 'keybindings': function(v) {
10902 terminal.keyboard.bindings.clear();
10903
10904 if (!v)
10905 return;
10906
10907 if (!(v instanceof Object)) {
10908 console.error('Error in keybindings preference: Expected object');
10909 return;
10910 }
10911
10912 try {
10913 terminal.keyboard.bindings.addBindings(v);
10914 } catch (ex) {
10915 console.error('Error in keybindings preference: ' + ex);
10916 }
10917 },
10918
10919 'max-string-sequence': function(v) {
10920 terminal.vt.maxStringSequence = v;
10921 },
10922
10923 'media-keys-are-fkeys': function(v) {
10924 terminal.keyboard.mediaKeysAreFKeys = v;
10925 },
10926
10927 'meta-sends-escape': function(v) {
10928 terminal.keyboard.metaSendsEscape = v;
10929 },
10930
10931 'mouse-paste-button': function(v) {
10932 terminal.syncMousePasteButton();
10933 },
10934
10935 'page-keys-scroll': function(v) {
10936 terminal.keyboard.pageKeysScroll = v;
10937 },
10938
10939 'pass-alt-number': function(v) {
10940 if (v == null) {
10941 var osx = window.navigator.userAgent.match(/Mac OS X/);
10942
10943 // Let Alt-1..9 pass to the browser (to control tab switching) on
10944 // non-OS X systems, or if hterm is not opened in an app window.
10945 v = (!osx && hterm.windowType != 'popup');
10946 }
10947
10948 terminal.passAltNumber = v;
10949 },
10950
10951 'pass-ctrl-number': function(v) {
10952 if (v == null) {
10953 var osx = window.navigator.userAgent.match(/Mac OS X/);
10954
10955 // Let Ctrl-1..9 pass to the browser (to control tab switching) on
10956 // non-OS X systems, or if hterm is not opened in an app window.
10957 v = (!osx && hterm.windowType != 'popup');
10958 }
10959
10960 terminal.passCtrlNumber = v;
10961 },
10962
10963 'pass-meta-number': function(v) {
10964 if (v == null) {
10965 var osx = window.navigator.userAgent.match(/Mac OS X/);
10966
10967 // Let Meta-1..9 pass to the browser (to control tab switching) on
10968 // OS X systems, or if hterm is not opened in an app window.
10969 v = (osx && hterm.windowType != 'popup');
10970 }
10971
10972 terminal.passMetaNumber = v;
10973 },
10974
10975 'pass-meta-v': function(v) {
10976 terminal.keyboard.passMetaV = v;
10977 },
10978
10979 'receive-encoding': function(v) {
10980 if (!(/^(utf-8|raw)$/).test(v)) {
10981 console.warn('Invalid value for "receive-encoding": ' + v);
10982 v = 'utf-8';
10983 }
10984
10985 terminal.vt.characterEncoding = v;
10986 },
10987
10988 'scroll-on-keystroke': function(v) {
10989 terminal.scrollOnKeystroke_ = v;
10990 },
10991
10992 'scroll-on-output': function(v) {
10993 terminal.scrollOnOutput_ = v;
10994 },
10995
10996 'scrollbar-visible': function(v) {
10997 terminal.setScrollbarVisible(v);
10998 },
10999
11000 'scroll-wheel-move-multiplier': function(v) {
11001 terminal.setScrollWheelMoveMultipler(v);
11002 },
11003
11004 'send-encoding': function(v) {
11005 if (!(/^(utf-8|raw)$/).test(v)) {
11006 console.warn('Invalid value for "send-encoding": ' + v);
11007 v = 'utf-8';
11008 }
11009
11010 terminal.keyboard.characterEncoding = v;
11011 },
11012
11013 'shift-insert-paste': function(v) {
11014 terminal.keyboard.shiftInsertPaste = v;
11015 },
11016
11017 'user-css': function(v) {
11018 terminal.scrollPort_.setUserCss(v);
11019 }
11020 });
11021
11022 this.prefs_.readStorage(function() {
11023 this.prefs_.notifyAll();
11024
11025 if (opt_callback)
11026 opt_callback();
11027 }.bind(this));
11028};
11029
11030
11031/**
11032 * Returns the preferences manager used for configuring this terminal.
11033 *
11034 * @return {hterm.PreferenceManager}
11035 */
11036hterm.Terminal.prototype.getPrefs = function() {
11037 return this.prefs_;
11038};
11039
11040/**
11041 * Enable or disable bracketed paste mode.
11042 *
11043 * @param {boolean} state The value to set.
11044 */
11045hterm.Terminal.prototype.setBracketedPaste = function(state) {
11046 this.options_.bracketedPaste = state;
11047};
11048
11049/**
11050 * Set the color for the cursor.
11051 *
11052 * If you want this setting to persist, set it through prefs_, rather than
11053 * with this method.
11054 *
11055 * @param {string} color The color to set.
11056 */
11057hterm.Terminal.prototype.setCursorColor = function(color) {
11058 this.cursorColor_ = color;
11059 this.cursorNode_.style.backgroundColor = color;
11060 this.cursorNode_.style.borderColor = color;
11061};
11062
11063/**
11064 * Return the current cursor color as a string.
11065 * @return {string}
11066 */
11067hterm.Terminal.prototype.getCursorColor = function() {
11068 return this.cursorColor_;
11069};
11070
11071/**
11072 * Enable or disable mouse based text selection in the terminal.
11073 *
11074 * @param {boolean} state The value to set.
11075 */
11076hterm.Terminal.prototype.setSelectionEnabled = function(state) {
11077 this.enableMouseDragScroll = state;
11078};
11079
11080/**
11081 * Set the background color.
11082 *
11083 * If you want this setting to persist, set it through prefs_, rather than
11084 * with this method.
11085 *
11086 * @param {string} color The color to set.
11087 */
11088hterm.Terminal.prototype.setBackgroundColor = function(color) {
11089 this.backgroundColor_ = lib.colors.normalizeCSS(color);
11090 this.primaryScreen_.textAttributes.setDefaults(
11091 this.foregroundColor_, this.backgroundColor_);
11092 this.alternateScreen_.textAttributes.setDefaults(
11093 this.foregroundColor_, this.backgroundColor_);
11094 this.scrollPort_.setBackgroundColor(color);
11095};
11096
11097/**
11098 * Return the current terminal background color.
11099 *
11100 * Intended for use by other classes, so we don't have to expose the entire
11101 * prefs_ object.
11102 *
11103 * @return {string}
11104 */
11105hterm.Terminal.prototype.getBackgroundColor = function() {
11106 return this.backgroundColor_;
11107};
11108
11109/**
11110 * Set the foreground color.
11111 *
11112 * If you want this setting to persist, set it through prefs_, rather than
11113 * with this method.
11114 *
11115 * @param {string} color The color to set.
11116 */
11117hterm.Terminal.prototype.setForegroundColor = function(color) {
11118 this.foregroundColor_ = lib.colors.normalizeCSS(color);
11119 this.primaryScreen_.textAttributes.setDefaults(
11120 this.foregroundColor_, this.backgroundColor_);
11121 this.alternateScreen_.textAttributes.setDefaults(
11122 this.foregroundColor_, this.backgroundColor_);
11123 this.scrollPort_.setForegroundColor(color);
11124};
11125
11126/**
11127 * Return the current terminal foreground color.
11128 *
11129 * Intended for use by other classes, so we don't have to expose the entire
11130 * prefs_ object.
11131 *
11132 * @return {string}
11133 */
11134hterm.Terminal.prototype.getForegroundColor = function() {
11135 return this.foregroundColor_;
11136};
11137
11138/**
11139 * Create a new instance of a terminal command and run it with a given
11140 * argument string.
11141 *
11142 * @param {function} commandClass The constructor for a terminal command.
11143 * @param {string} argString The argument string to pass to the command.
11144 */
11145hterm.Terminal.prototype.runCommandClass = function(commandClass, argString) {
11146 var environment = this.prefs_.get('environment');
11147 if (typeof environment != 'object' || environment == null)
11148 environment = {};
11149
11150 var self = this;
11151 this.command = new commandClass(
11152 { argString: argString || '',
11153 io: this.io.push(),
11154 environment: environment,
11155 onExit: function(code) {
11156 self.io.pop();
11157 self.uninstallKeyboard();
11158 if (self.prefs_.get('close-on-exit'))
11159 window.close();
11160 }
11161 });
11162
11163 this.installKeyboard();
11164 this.command.run();
11165};
11166
11167/**
11168 * Returns true if the current screen is the primary screen, false otherwise.
11169 *
11170 * @return {boolean}
11171 */
11172hterm.Terminal.prototype.isPrimaryScreen = function() {
11173 return this.screen_ == this.primaryScreen_;
11174};
11175
11176/**
11177 * Install the keyboard handler for this terminal.
11178 *
11179 * This will prevent the browser from seeing any keystrokes sent to the
11180 * terminal.
11181 */
11182hterm.Terminal.prototype.installKeyboard = function() {
11183 this.keyboard.installKeyboard(this.scrollPort_.getDocument().body);
11184}
11185
11186/**
11187 * Uninstall the keyboard handler for this terminal.
11188 */
11189hterm.Terminal.prototype.uninstallKeyboard = function() {
11190 this.keyboard.installKeyboard(null);
11191}
11192
11193/**
11194 * Set the font size for this terminal.
11195 *
11196 * Call setFontSize(0) to reset to the default font size.
11197 *
11198 * This function does not modify the font-size preference.
11199 *
11200 * @param {number} px The desired font size, in pixels.
11201 */
11202hterm.Terminal.prototype.setFontSize = function(px) {
11203 if (px === 0)
11204 px = this.prefs_.get('font-size');
11205
11206 this.scrollPort_.setFontSize(px);
11207 if (this.wcCssRule_) {
11208 this.wcCssRule_.style.width = this.scrollPort_.characterSize.width * 2 +
11209 'px';
11210 }
11211};
11212
11213/**
11214 * Get the current font size.
11215 *
11216 * @return {number}
11217 */
11218hterm.Terminal.prototype.getFontSize = function() {
11219 return this.scrollPort_.getFontSize();
11220};
11221
11222/**
11223 * Get the current font family.
11224 *
11225 * @return {string}
11226 */
11227hterm.Terminal.prototype.getFontFamily = function() {
11228 return this.scrollPort_.getFontFamily();
11229};
11230
11231/**
11232 * Set the CSS "font-family" for this terminal.
11233 */
11234hterm.Terminal.prototype.syncFontFamily = function() {
11235 this.scrollPort_.setFontFamily(this.prefs_.get('font-family'),
11236 this.prefs_.get('font-smoothing'));
11237 this.syncBoldSafeState();
11238};
11239
11240/**
11241 * Set this.mousePasteButton based on the mouse-paste-button pref,
11242 * autodetecting if necessary.
11243 */
11244hterm.Terminal.prototype.syncMousePasteButton = function() {
11245 var button = this.prefs_.get('mouse-paste-button');
11246 if (typeof button == 'number') {
11247 this.mousePasteButton = button;
11248 return;
11249 }
11250
11251 var ary = navigator.userAgent.match(/\(X11;\s+(\S+)/);
11252 if (!ary || ary[2] == 'CrOS') {
11253 this.mousePasteButton = 2;
11254 } else {
11255 this.mousePasteButton = 3;
11256 }
11257};
11258
11259/**
11260 * Enable or disable bold based on the enable-bold pref, autodetecting if
11261 * necessary.
11262 */
11263hterm.Terminal.prototype.syncBoldSafeState = function() {
11264 var enableBold = this.prefs_.get('enable-bold');
11265 if (enableBold !== null) {
11266 this.primaryScreen_.textAttributes.enableBold = enableBold;
11267 this.alternateScreen_.textAttributes.enableBold = enableBold;
11268 return;
11269 }
11270
11271 var normalSize = this.scrollPort_.measureCharacterSize();
11272 var boldSize = this.scrollPort_.measureCharacterSize('bold');
11273
11274 var isBoldSafe = normalSize.equals(boldSize);
11275 if (!isBoldSafe) {
11276 console.warn('Bold characters disabled: Size of bold weight differs ' +
11277 'from normal. Font family is: ' +
11278 this.scrollPort_.getFontFamily());
11279 }
11280
11281 this.primaryScreen_.textAttributes.enableBold = isBoldSafe;
11282 this.alternateScreen_.textAttributes.enableBold = isBoldSafe;
11283};
11284
11285/**
11286 * Enable or disable blink based on the enable-blink pref.
11287 */
11288hterm.Terminal.prototype.syncBlinkState = function() {
11289 this.document_.documentElement.style.setProperty(
11290 '--hterm-blink-node-duration',
11291 this.prefs_.get('enable-blink') ? '0.7s' : '0');
11292};
11293
11294/**
11295 * Return a copy of the current cursor position.
11296 *
11297 * @return {hterm.RowCol} The RowCol object representing the current position.
11298 */
11299hterm.Terminal.prototype.saveCursor = function() {
11300 return this.screen_.cursorPosition.clone();
11301};
11302
11303/**
11304 * Return the current text attributes.
11305 *
11306 * @return {string}
11307 */
11308hterm.Terminal.prototype.getTextAttributes = function() {
11309 return this.screen_.textAttributes;
11310};
11311
11312/**
11313 * Set the text attributes.
11314 *
11315 * @param {string} textAttributes The attributes to set.
11316 */
11317hterm.Terminal.prototype.setTextAttributes = function(textAttributes) {
11318 this.screen_.textAttributes = textAttributes;
11319};
11320
11321/**
11322 * Return the current browser zoom factor applied to the terminal.
11323 *
11324 * @return {number} The current browser zoom factor.
11325 */
11326hterm.Terminal.prototype.getZoomFactor = function() {
11327 return this.scrollPort_.characterSize.zoomFactor;
11328};
11329
11330/**
11331 * Change the title of this terminal's window.
11332 *
11333 * @param {string} title The title to set.
11334 */
11335hterm.Terminal.prototype.setWindowTitle = function(title) {
11336 window.document.title = title;
11337};
11338
11339/**
11340 * Restore a previously saved cursor position.
11341 *
11342 * @param {hterm.RowCol} cursor The position to restore.
11343 */
11344hterm.Terminal.prototype.restoreCursor = function(cursor) {
11345 var row = lib.f.clamp(cursor.row, 0, this.screenSize.height - 1);
11346 var column = lib.f.clamp(cursor.column, 0, this.screenSize.width - 1);
11347 this.screen_.setCursorPosition(row, column);
11348 if (cursor.column > column ||
11349 cursor.column == column && cursor.overflow) {
11350 this.screen_.cursorPosition.overflow = true;
11351 }
11352};
11353
11354/**
11355 * Clear the cursor's overflow flag.
11356 */
11357hterm.Terminal.prototype.clearCursorOverflow = function() {
11358 this.screen_.cursorPosition.overflow = false;
11359};
11360
11361/**
11362 * Sets the cursor shape
11363 *
11364 * @param {string} shape The shape to set.
11365 */
11366hterm.Terminal.prototype.setCursorShape = function(shape) {
11367 this.cursorShape_ = shape;
11368 this.restyleCursor_();
11369}
11370
11371/**
11372 * Get the cursor shape
11373 *
11374 * @return {string}
11375 */
11376hterm.Terminal.prototype.getCursorShape = function() {
11377 return this.cursorShape_;
11378}
11379
11380/**
11381 * Set the width of the terminal, resizing the UI to match.
11382 *
11383 * @param {number} columnCount
11384 */
11385hterm.Terminal.prototype.setWidth = function(columnCount) {
11386 if (columnCount == null) {
11387 this.div_.style.width = '100%';
11388 return;
11389 }
11390
11391 this.div_.style.width = Math.ceil(
11392 this.scrollPort_.characterSize.width *
11393 columnCount + this.scrollPort_.currentScrollbarWidthPx) + 'px';
11394 this.realizeSize_(columnCount, this.screenSize.height);
11395 this.scheduleSyncCursorPosition_();
11396};
11397
11398/**
11399 * Set the height of the terminal, resizing the UI to match.
11400 *
11401 * @param {number} rowCount The height in rows.
11402 */
11403hterm.Terminal.prototype.setHeight = function(rowCount) {
11404 if (rowCount == null) {
11405 this.div_.style.height = '100%';
11406 return;
11407 }
11408
11409 this.div_.style.height =
11410 this.scrollPort_.characterSize.height * rowCount + 'px';
11411 this.realizeSize_(this.screenSize.width, rowCount);
11412 this.scheduleSyncCursorPosition_();
11413};
11414
11415/**
11416 * Deal with terminal size changes.
11417 *
11418 * @param {number} columnCount The number of columns.
11419 * @param {number} rowCount The number of rows.
11420 */
11421hterm.Terminal.prototype.realizeSize_ = function(columnCount, rowCount) {
11422 if (columnCount != this.screenSize.width)
11423 this.realizeWidth_(columnCount);
11424
11425 if (rowCount != this.screenSize.height)
11426 this.realizeHeight_(rowCount);
11427
11428 // Send new terminal size to plugin.
11429 this.io.onTerminalResize_(columnCount, rowCount);
11430};
11431
11432/**
11433 * Deal with terminal width changes.
11434 *
11435 * This function does what needs to be done when the terminal width changes
11436 * out from under us. It happens here rather than in onResize_() because this
11437 * code may need to run synchronously to handle programmatic changes of
11438 * terminal width.
11439 *
11440 * Relying on the browser to send us an async resize event means we may not be
11441 * in the correct state yet when the next escape sequence hits.
11442 *
11443 * @param {number} columnCount The number of columns.
11444 */
11445hterm.Terminal.prototype.realizeWidth_ = function(columnCount) {
11446 if (columnCount <= 0)
11447 throw new Error('Attempt to realize bad width: ' + columnCount);
11448
11449 var deltaColumns = columnCount - this.screen_.getWidth();
11450
11451 this.screenSize.width = columnCount;
11452 this.screen_.setColumnCount(columnCount);
11453
11454 if (deltaColumns > 0) {
11455 if (this.defaultTabStops)
11456 this.setDefaultTabStops(this.screenSize.width - deltaColumns);
11457 } else {
11458 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
11459 if (this.tabStops_[i] < columnCount)
11460 break;
11461
11462 this.tabStops_.pop();
11463 }
11464 }
11465
11466 this.screen_.setColumnCount(this.screenSize.width);
11467};
11468
11469/**
11470 * Deal with terminal height changes.
11471 *
11472 * This function does what needs to be done when the terminal height changes
11473 * out from under us. It happens here rather than in onResize_() because this
11474 * code may need to run synchronously to handle programmatic changes of
11475 * terminal height.
11476 *
11477 * Relying on the browser to send us an async resize event means we may not be
11478 * in the correct state yet when the next escape sequence hits.
11479 *
11480 * @param {number} rowCount The number of rows.
11481 */
11482hterm.Terminal.prototype.realizeHeight_ = function(rowCount) {
11483 if (rowCount <= 0)
11484 throw new Error('Attempt to realize bad height: ' + rowCount);
11485
11486 var deltaRows = rowCount - this.screen_.getHeight();
11487
11488 this.screenSize.height = rowCount;
11489
11490 var cursor = this.saveCursor();
11491
11492 if (deltaRows < 0) {
11493 // Screen got smaller.
11494 deltaRows *= -1;
11495 while (deltaRows) {
11496 var lastRow = this.getRowCount() - 1;
11497 if (lastRow - this.scrollbackRows_.length == cursor.row)
11498 break;
11499
11500 if (this.getRowText(lastRow))
11501 break;
11502
11503 this.screen_.popRow();
11504 deltaRows--;
11505 }
11506
11507 var ary = this.screen_.shiftRows(deltaRows);
11508 this.scrollbackRows_.push.apply(this.scrollbackRows_, ary);
11509
11510 // We just removed rows from the top of the screen, we need to update
11511 // the cursor to match.
11512 cursor.row = Math.max(cursor.row - deltaRows, 0);
11513 } else if (deltaRows > 0) {
11514 // Screen got larger.
11515
11516 if (deltaRows <= this.scrollbackRows_.length) {
11517 var scrollbackCount = Math.min(deltaRows, this.scrollbackRows_.length);
11518 var rows = this.scrollbackRows_.splice(
11519 this.scrollbackRows_.length - scrollbackCount, scrollbackCount);
11520 this.screen_.unshiftRows(rows);
11521 deltaRows -= scrollbackCount;
11522 cursor.row += scrollbackCount;
11523 }
11524
11525 if (deltaRows)
11526 this.appendRows_(deltaRows);
11527 }
11528
11529 this.setVTScrollRegion(null, null);
11530 this.restoreCursor(cursor);
11531};
11532
11533/**
11534 * Scroll the terminal to the top of the scrollback buffer.
11535 */
11536hterm.Terminal.prototype.scrollHome = function() {
11537 this.scrollPort_.scrollRowToTop(0);
11538};
11539
11540/**
11541 * Scroll the terminal to the end.
11542 */
11543hterm.Terminal.prototype.scrollEnd = function() {
11544 this.scrollPort_.scrollRowToBottom(this.getRowCount());
11545};
11546
11547/**
11548 * Scroll the terminal one page up (minus one line) relative to the current
11549 * position.
11550 */
11551hterm.Terminal.prototype.scrollPageUp = function() {
11552 var i = this.scrollPort_.getTopRowIndex();
11553 this.scrollPort_.scrollRowToTop(i - this.screenSize.height + 1);
11554};
11555
11556/**
11557 * Scroll the terminal one page down (minus one line) relative to the current
11558 * position.
11559 */
11560hterm.Terminal.prototype.scrollPageDown = function() {
11561 var i = this.scrollPort_.getTopRowIndex();
11562 this.scrollPort_.scrollRowToTop(i + this.screenSize.height - 1);
11563};
11564
11565/**
11566 * Clear primary screen, secondary screen, and the scrollback buffer.
11567 */
11568hterm.Terminal.prototype.wipeContents = function() {
11569 this.scrollbackRows_.length = 0;
11570 this.scrollPort_.resetCache();
11571
11572 [this.primaryScreen_, this.alternateScreen_].forEach(function(screen) {
11573 var bottom = screen.getHeight();
11574 if (bottom > 0) {
11575 this.renumberRows_(0, bottom);
11576 this.clearHome(screen);
11577 }
11578 }.bind(this));
11579
11580 this.syncCursorPosition_();
11581 this.scrollPort_.invalidate();
11582};
11583
11584/**
11585 * Full terminal reset.
11586 */
11587hterm.Terminal.prototype.reset = function() {
11588 this.clearAllTabStops();
11589 this.setDefaultTabStops();
11590
11591 this.clearHome(this.primaryScreen_);
11592 this.primaryScreen_.textAttributes.reset();
11593
11594 this.clearHome(this.alternateScreen_);
11595 this.alternateScreen_.textAttributes.reset();
11596
11597 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
11598
11599 this.vt.reset();
11600
11601 this.softReset();
11602};
11603
11604/**
11605 * Soft terminal reset.
11606 *
11607 * Perform a soft reset to the default values listed in
11608 * http://www.vt100.net/docs/vt510-rm/DECSTR#T5-9
11609 */
11610hterm.Terminal.prototype.softReset = function() {
11611 // Reset terminal options to their default values.
11612 this.options_ = new hterm.Options();
11613
11614 // We show the cursor on soft reset but do not alter the blink state.
11615 this.options_.cursorBlink = !!this.timeouts_.cursorBlink;
11616
11617 // Xterm also resets the color palette on soft reset, even though it doesn't
11618 // seem to be documented anywhere.
11619 this.primaryScreen_.textAttributes.resetColorPalette();
11620 this.alternateScreen_.textAttributes.resetColorPalette();
11621
11622 // The xterm man page explicitly says this will happen on soft reset.
11623 this.setVTScrollRegion(null, null);
11624
11625 // Xterm also shows the cursor on soft reset, but does not alter the blink
11626 // state.
11627 this.setCursorVisible(true);
11628};
11629
11630/**
11631 * Move the cursor forward to the next tab stop, or to the last column
11632 * if no more tab stops are set.
11633 */
11634hterm.Terminal.prototype.forwardTabStop = function() {
11635 var column = this.screen_.cursorPosition.column;
11636
11637 for (var i = 0; i < this.tabStops_.length; i++) {
11638 if (this.tabStops_[i] > column) {
11639 this.setCursorColumn(this.tabStops_[i]);
11640 return;
11641 }
11642 }
11643
11644 // xterm does not clear the overflow flag on HT or CHT.
11645 var overflow = this.screen_.cursorPosition.overflow;
11646 this.setCursorColumn(this.screenSize.width - 1);
11647 this.screen_.cursorPosition.overflow = overflow;
11648};
11649
11650/**
11651 * Move the cursor backward to the previous tab stop, or to the first column
11652 * if no previous tab stops are set.
11653 */
11654hterm.Terminal.prototype.backwardTabStop = function() {
11655 var column = this.screen_.cursorPosition.column;
11656
11657 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
11658 if (this.tabStops_[i] < column) {
11659 this.setCursorColumn(this.tabStops_[i]);
11660 return;
11661 }
11662 }
11663
11664 this.setCursorColumn(1);
11665};
11666
11667/**
11668 * Set a tab stop at the given column.
11669 *
11670 * @param {integer} column Zero based column.
11671 */
11672hterm.Terminal.prototype.setTabStop = function(column) {
11673 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
11674 if (this.tabStops_[i] == column)
11675 return;
11676
11677 if (this.tabStops_[i] < column) {
11678 this.tabStops_.splice(i + 1, 0, column);
11679 return;
11680 }
11681 }
11682
11683 this.tabStops_.splice(0, 0, column);
11684};
11685
11686/**
11687 * Clear the tab stop at the current cursor position.
11688 *
11689 * No effect if there is no tab stop at the current cursor position.
11690 */
11691hterm.Terminal.prototype.clearTabStopAtCursor = function() {
11692 var column = this.screen_.cursorPosition.column;
11693
11694 var i = this.tabStops_.indexOf(column);
11695 if (i == -1)
11696 return;
11697
11698 this.tabStops_.splice(i, 1);
11699};
11700
11701/**
11702 * Clear all tab stops.
11703 */
11704hterm.Terminal.prototype.clearAllTabStops = function() {
11705 this.tabStops_.length = 0;
11706 this.defaultTabStops = false;
11707};
11708
11709/**
11710 * Set up the default tab stops, starting from a given column.
11711 *
11712 * This sets a tabstop every (column % this.tabWidth) column, starting
11713 * from the specified column, or 0 if no column is provided. It also flags
11714 * future resizes to set them up.
11715 *
11716 * This does not clear the existing tab stops first, use clearAllTabStops
11717 * for that.
11718 *
11719 * @param {integer} opt_start Optional starting zero based starting column, useful
11720 * for filling out missing tab stops when the terminal is resized.
11721 */
11722hterm.Terminal.prototype.setDefaultTabStops = function(opt_start) {
11723 var start = opt_start || 0;
11724 var w = this.tabWidth;
11725 // Round start up to a default tab stop.
11726 start = start - 1 - ((start - 1) % w) + w;
11727 for (var i = start; i < this.screenSize.width; i += w) {
11728 this.setTabStop(i);
11729 }
11730
11731 this.defaultTabStops = true;
11732};
11733
11734/**
11735 * Interpret a sequence of characters.
11736 *
11737 * Incomplete escape sequences are buffered until the next call.
11738 *
11739 * @param {string} str Sequence of characters to interpret or pass through.
11740 */
11741hterm.Terminal.prototype.interpret = function(str) {
11742 this.vt.interpret(str);
11743 this.scheduleSyncCursorPosition_();
11744};
11745
11746/**
11747 * Take over the given DIV for use as the terminal display.
11748 *
11749 * @param {HTMLDivElement} div The div to use as the terminal display.
11750 */
11751hterm.Terminal.prototype.decorate = function(div) {
11752 this.div_ = div;
11753
11754 this.scrollPort_.decorate(div);
11755 this.scrollPort_.setBackgroundImage(this.prefs_.get('background-image'));
11756 this.scrollPort_.setBackgroundSize(this.prefs_.get('background-size'));
11757 this.scrollPort_.setBackgroundPosition(
11758 this.prefs_.get('background-position'));
11759 this.scrollPort_.setUserCss(this.prefs_.get('user-css'));
11760
11761 this.div_.focus = this.focus.bind(this);
11762
11763 this.setFontSize(this.prefs_.get('font-size'));
11764 this.syncFontFamily();
11765
11766 this.setScrollbarVisible(this.prefs_.get('scrollbar-visible'));
11767 this.setScrollWheelMoveMultipler(
11768 this.prefs_.get('scroll-wheel-move-multiplier'));
11769
11770 this.document_ = this.scrollPort_.getDocument();
11771
11772 this.document_.body.oncontextmenu = function() { return false; };
11773
11774 var onMouse = this.onMouse_.bind(this);
11775 var screenNode = this.scrollPort_.getScreenNode();
11776 screenNode.addEventListener('mousedown', onMouse);
11777 screenNode.addEventListener('mouseup', onMouse);
11778 screenNode.addEventListener('mousemove', onMouse);
11779 this.scrollPort_.onScrollWheel = onMouse;
11780
11781 screenNode.addEventListener(
11782 'focus', this.onFocusChange_.bind(this, true));
11783 // Listen for mousedown events on the screenNode as in FF the focus
11784 // events don't bubble.
11785 screenNode.addEventListener('mousedown', function() {
11786 setTimeout(this.onFocusChange_.bind(this, true));
11787 }.bind(this));
11788
11789 screenNode.addEventListener(
11790 'blur', this.onFocusChange_.bind(this, false));
11791
11792 var style = this.document_.createElement('style');
11793 style.textContent =
11794 ('.cursor-node[focus="false"] {' +
11795 ' box-sizing: border-box;' +
11796 ' background-color: transparent !important;' +
11797 ' border-width: 2px;' +
11798 ' border-style: solid;' +
11799 '}' +
11800 '.wc-node {' +
11801 ' display: inline-block;' +
11802 ' text-align: center;' +
11803 ' width: ' + this.scrollPort_.characterSize.width * 2 + 'px;' +
11804 '}' +
11805 ':root {' +
11806 ' --hterm-blink-node-duration: 0.7s;' +
11807 '}' +
11808 '@keyframes blink {' +
11809 ' from { opacity: 1.0; }' +
11810 ' to { opacity: 0.0; }' +
11811 '}' +
11812 '.blink-node {' +
11813 ' animation-name: blink;' +
11814 ' animation-duration: var(--hterm-blink-node-duration);' +
11815 ' animation-iteration-count: infinite;' +
11816 ' animation-timing-function: ease-in-out;' +
11817 ' animation-direction: alternate;' +
11818 '}');
11819 this.document_.head.appendChild(style);
11820
11821 var styleSheets = this.document_.styleSheets;
11822 var cssRules = styleSheets[styleSheets.length - 1].cssRules;
11823 this.wcCssRule_ = cssRules[cssRules.length - 1];
11824
11825 this.cursorNode_ = this.document_.createElement('div');
11826 this.cursorNode_.className = 'cursor-node';
11827 this.cursorNode_.style.cssText =
11828 ('position: absolute;' +
11829 'top: -99px;' +
11830 'display: block;' +
11831 'width: ' + this.scrollPort_.characterSize.width + 'px;' +
11832 'height: ' + this.scrollPort_.characterSize.height + 'px;' +
11833 '-webkit-transition: opacity, background-color 100ms linear;' +
11834 '-moz-transition: opacity, background-color 100ms linear;');
11835
11836 this.setCursorColor(this.prefs_.get('cursor-color'));
11837 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
11838 this.restyleCursor_();
11839
11840 this.document_.body.appendChild(this.cursorNode_);
11841
11842 // When 'enableMouseDragScroll' is off we reposition this element directly
11843 // under the mouse cursor after a click. This makes Chrome associate
11844 // subsequent mousemove events with the scroll-blocker. Since the
11845 // scroll-blocker is a peer (not a child) of the scrollport, the mousemove
11846 // events do not cause the scrollport to scroll.
11847 //
11848 // It's a hack, but it's the cleanest way I could find.
11849 this.scrollBlockerNode_ = this.document_.createElement('div');
11850 this.scrollBlockerNode_.style.cssText =
11851 ('position: absolute;' +
11852 'top: -99px;' +
11853 'display: block;' +
11854 'width: 10px;' +
11855 'height: 10px;');
11856 this.document_.body.appendChild(this.scrollBlockerNode_);
11857
11858 this.scrollPort_.onScrollWheel = onMouse;
11859 ['mousedown', 'mouseup', 'mousemove', 'click', 'dblclick',
11860 ].forEach(function(event) {
11861 this.scrollBlockerNode_.addEventListener(event, onMouse);
11862 this.cursorNode_.addEventListener(event, onMouse);
11863 this.document_.addEventListener(event, onMouse);
11864 }.bind(this));
11865
11866 this.cursorNode_.addEventListener('mousedown', function() {
11867 setTimeout(this.focus.bind(this));
11868 }.bind(this));
11869
11870 this.setReverseVideo(false);
11871
11872 this.scrollPort_.focus();
11873 this.scrollPort_.scheduleRedraw();
11874};
11875
11876/**
11877 * Return the HTML document that contains the terminal DOM nodes.
11878 *
11879 * @return {HTMLDocument}
11880 */
11881hterm.Terminal.prototype.getDocument = function() {
11882 return this.document_;
11883};
11884
11885/**
11886 * Focus the terminal.
11887 */
11888hterm.Terminal.prototype.focus = function() {
11889 this.scrollPort_.focus();
11890};
11891
11892/**
11893 * Return the HTML Element for a given row index.
11894 *
11895 * This is a method from the RowProvider interface. The ScrollPort uses
11896 * it to fetch rows on demand as they are scrolled into view.
11897 *
11898 * TODO(rginda): Consider saving scrollback rows as (HTML source, text content)
11899 * pairs to conserve memory.
11900 *
11901 * @param {integer} index The zero-based row index, measured relative to the
11902 * start of the scrollback buffer. On-screen rows will always have the
11903 * largest indices.
11904 * @return {HTMLElement} The 'x-row' element containing for the requested row.
11905 */
11906hterm.Terminal.prototype.getRowNode = function(index) {
11907 if (index < this.scrollbackRows_.length)
11908 return this.scrollbackRows_[index];
11909
11910 var screenIndex = index - this.scrollbackRows_.length;
11911 return this.screen_.rowsArray[screenIndex];
11912};
11913
11914/**
11915 * Return the text content for a given range of rows.
11916 *
11917 * This is a method from the RowProvider interface. The ScrollPort uses
11918 * it to fetch text content on demand when the user attempts to copy their
11919 * selection to the clipboard.
11920 *
11921 * @param {integer} start The zero-based row index to start from, measured
11922 * relative to the start of the scrollback buffer. On-screen rows will
11923 * always have the largest indices.
11924 * @param {integer} end The zero-based row index to end on, measured
11925 * relative to the start of the scrollback buffer.
11926 * @return {string} A single string containing the text value of the range of
11927 * rows. Lines will be newline delimited, with no trailing newline.
11928 */
11929hterm.Terminal.prototype.getRowsText = function(start, end) {
11930 var ary = [];
11931 for (var i = start; i < end; i++) {
11932 var node = this.getRowNode(i);
11933 ary.push(node.textContent);
11934 if (i < end - 1 && !node.getAttribute('line-overflow'))
11935 ary.push('\n');
11936 }
11937
11938 return ary.join('');
11939};
11940
11941/**
11942 * Return the text content for a given row.
11943 *
11944 * This is a method from the RowProvider interface. The ScrollPort uses
11945 * it to fetch text content on demand when the user attempts to copy their
11946 * selection to the clipboard.
11947 *
11948 * @param {integer} index The zero-based row index to return, measured
11949 * relative to the start of the scrollback buffer. On-screen rows will
11950 * always have the largest indices.
11951 * @return {string} A string containing the text value of the selected row.
11952 */
11953hterm.Terminal.prototype.getRowText = function(index) {
11954 var node = this.getRowNode(index);
11955 return node.textContent;
11956};
11957
11958/**
11959 * Return the total number of rows in the addressable screen and in the
11960 * scrollback buffer of this terminal.
11961 *
11962 * This is a method from the RowProvider interface. The ScrollPort uses
11963 * it to compute the size of the scrollbar.
11964 *
11965 * @return {integer} The number of rows in this terminal.
11966 */
11967hterm.Terminal.prototype.getRowCount = function() {
11968 return this.scrollbackRows_.length + this.screen_.rowsArray.length;
11969};
11970
11971/**
11972 * Create DOM nodes for new rows and append them to the end of the terminal.
11973 *
11974 * This is the only correct way to add a new DOM node for a row. Notice that
11975 * the new row is appended to the bottom of the list of rows, and does not
11976 * require renumbering (of the rowIndex property) of previous rows.
11977 *
11978 * If you think you want a new blank row somewhere in the middle of the
11979 * terminal, look into moveRows_().
11980 *
11981 * This method does not pay attention to vtScrollTop/Bottom, since you should
11982 * be using moveRows() in cases where they would matter.
11983 *
11984 * The cursor will be positioned at column 0 of the first inserted line.
11985 *
11986 * @param {number} count The number of rows to created.
11987 */
11988hterm.Terminal.prototype.appendRows_ = function(count) {
11989 var cursorRow = this.screen_.rowsArray.length;
11990 var offset = this.scrollbackRows_.length + cursorRow;
11991 for (var i = 0; i < count; i++) {
11992 var row = this.document_.createElement('x-row');
11993 row.appendChild(this.document_.createTextNode(''));
11994 row.rowIndex = offset + i;
11995 this.screen_.pushRow(row);
11996 }
11997
11998 var extraRows = this.screen_.rowsArray.length - this.screenSize.height;
11999 if (extraRows > 0) {
12000 var ary = this.screen_.shiftRows(extraRows);
12001 Array.prototype.push.apply(this.scrollbackRows_, ary);
12002 if (this.scrollPort_.isScrolledEnd)
12003 this.scheduleScrollDown_();
12004 }
12005
12006 if (cursorRow >= this.screen_.rowsArray.length)
12007 cursorRow = this.screen_.rowsArray.length - 1;
12008
12009 this.setAbsoluteCursorPosition(cursorRow, 0);
12010};
12011
12012/**
12013 * Relocate rows from one part of the addressable screen to another.
12014 *
12015 * This is used to recycle rows during VT scrolls (those which are driven
12016 * by VT commands, rather than by the user manipulating the scrollbar.)
12017 *
12018 * In this case, the blank lines scrolled into the scroll region are made of
12019 * the nodes we scrolled off. These have their rowIndex properties carefully
12020 * renumbered so as not to confuse the ScrollPort.
12021 *
12022 * @param {number} fromIndex The start index.
12023 * @param {number} count The number of rows to move.
12024 * @param {number} toIndex The destination index.
12025 */
12026hterm.Terminal.prototype.moveRows_ = function(fromIndex, count, toIndex) {
12027 var ary = this.screen_.removeRows(fromIndex, count);
12028 this.screen_.insertRows(toIndex, ary);
12029
12030 var start, end;
12031 if (fromIndex < toIndex) {
12032 start = fromIndex;
12033 end = toIndex + count;
12034 } else {
12035 start = toIndex;
12036 end = fromIndex + count;
12037 }
12038
12039 this.renumberRows_(start, end);
12040 this.scrollPort_.scheduleInvalidate();
12041};
12042
12043/**
12044 * Renumber the rowIndex property of the given range of rows.
12045 *
12046 * The start and end indices are relative to the screen, not the scrollback.
12047 * Rows in the scrollback buffer cannot be renumbered. Since they are not
12048 * addressable (you can't delete them, scroll them, etc), you should have
12049 * no need to renumber scrollback rows.
12050 *
12051 * @param {number} start The start index.
12052 * @param {number} end The end index.
12053 * @param {hterm.Screen} opt_screen The screen to renumber.
12054 */
12055hterm.Terminal.prototype.renumberRows_ = function(start, end, opt_screen) {
12056 var screen = opt_screen || this.screen_;
12057
12058 var offset = this.scrollbackRows_.length;
12059 for (var i = start; i < end; i++) {
12060 screen.rowsArray[i].rowIndex = offset + i;
12061 }
12062};
12063
12064/**
12065 * Print a string to the terminal.
12066 *
12067 * This respects the current insert and wraparound modes. It will add new lines
12068 * to the end of the terminal, scrolling off the top into the scrollback buffer
12069 * if necessary.
12070 *
12071 * The string is *not* parsed for escape codes. Use the interpret() method if
12072 * that's what you're after.
12073 *
12074 * @param{string} str The string to print.
12075 */
12076hterm.Terminal.prototype.print = function(str) {
12077 var startOffset = 0;
12078
12079 var strWidth = lib.wc.strWidth(str);
12080
12081 while (startOffset < strWidth) {
12082 if (this.options_.wraparound && this.screen_.cursorPosition.overflow) {
12083 this.screen_.commitLineOverflow();
12084 this.newLine();
12085 }
12086
12087 var count = strWidth - startOffset;
12088 var didOverflow = false;
12089 var substr;
12090
12091 if (this.screen_.cursorPosition.column + count >= this.screenSize.width) {
12092 didOverflow = true;
12093 count = this.screenSize.width - this.screen_.cursorPosition.column;
12094 }
12095
12096 if (didOverflow && !this.options_.wraparound) {
12097 // If the string overflowed the line but wraparound is off, then the
12098 // last printed character should be the last of the string.
12099 // TODO: This will add to our problems with multibyte UTF-16 characters.
12100 substr = lib.wc.substr(str, startOffset, count - 1) +
12101 lib.wc.substr(str, strWidth - 1);
12102 count = strWidth;
12103 } else {
12104 substr = lib.wc.substr(str, startOffset, count);
12105 }
12106
12107 var tokens = hterm.TextAttributes.splitWidecharString(substr);
12108 for (var i = 0; i < tokens.length; i++) {
12109 if (tokens[i].wcNode)
12110 this.screen_.textAttributes.wcNode = true;
12111
12112 if (this.options_.insertMode) {
12113 this.screen_.insertString(tokens[i].str);
12114 } else {
12115 this.screen_.overwriteString(tokens[i].str);
12116 }
12117 this.screen_.textAttributes.wcNode = false;
12118 }
12119
12120 this.screen_.maybeClipCurrentRow();
12121 startOffset += count;
12122 }
12123
12124 this.scheduleSyncCursorPosition_();
12125
12126 if (this.scrollOnOutput_)
12127 this.scrollPort_.scrollRowToBottom(this.getRowCount());
12128};
12129
12130/**
12131 * Set the VT scroll region.
12132 *
12133 * This also resets the cursor position to the absolute (0, 0) position, since
12134 * that's what xterm appears to do.
12135 *
12136 * Setting the scroll region to the full height of the terminal will clear
12137 * the scroll region. This is *NOT* what most terminals do. We're explicitly
12138 * going "off-spec" here because it makes `screen` and `tmux` overflow into the
12139 * local scrollback buffer, which means the scrollbars and shift-pgup/pgdn
12140 * continue to work as most users would expect.
12141 *
12142 * @param {integer} scrollTop The zero-based top of the scroll region.
12143 * @param {integer} scrollBottom The zero-based bottom of the scroll region,
12144 * inclusive.
12145 */
12146hterm.Terminal.prototype.setVTScrollRegion = function(scrollTop, scrollBottom) {
12147 if (scrollTop == 0 && scrollBottom == this.screenSize.height - 1) {
12148 this.vtScrollTop_ = null;
12149 this.vtScrollBottom_ = null;
12150 } else {
12151 this.vtScrollTop_ = scrollTop;
12152 this.vtScrollBottom_ = scrollBottom;
12153 }
12154};
12155
12156/**
12157 * Return the top row index according to the VT.
12158 *
12159 * This will return 0 unless the terminal has been told to restrict scrolling
12160 * to some lower row. It is used for some VT cursor positioning and scrolling
12161 * commands.
12162 *
12163 * @return {integer} The topmost row in the terminal's scroll region.
12164 */
12165hterm.Terminal.prototype.getVTScrollTop = function() {
12166 if (this.vtScrollTop_ != null)
12167 return this.vtScrollTop_;
12168
12169 return 0;
12170};
12171
12172/**
12173 * Return the bottom row index according to the VT.
12174 *
12175 * This will return the height of the terminal unless the it has been told to
12176 * restrict scrolling to some higher row. It is used for some VT cursor
12177 * positioning and scrolling commands.
12178 *
12179 * @return {integer} The bottom most row in the terminal's scroll region.
12180 */
12181hterm.Terminal.prototype.getVTScrollBottom = function() {
12182 if (this.vtScrollBottom_ != null)
12183 return this.vtScrollBottom_;
12184
12185 return this.screenSize.height - 1;
12186}
12187
12188/**
12189 * Process a '\n' character.
12190 *
12191 * If the cursor is on the final row of the terminal this will append a new
12192 * blank row to the screen and scroll the topmost row into the scrollback
12193 * buffer.
12194 *
12195 * Otherwise, this moves the cursor to column zero of the next row.
12196 */
12197hterm.Terminal.prototype.newLine = function() {
12198 var cursorAtEndOfScreen = (this.screen_.cursorPosition.row ==
12199 this.screen_.rowsArray.length - 1);
12200
12201 if (this.vtScrollBottom_ != null) {
12202 // A VT Scroll region is active, we never append new rows.
12203 if (this.screen_.cursorPosition.row == this.vtScrollBottom_) {
12204 // We're at the end of the VT Scroll Region, perform a VT scroll.
12205 this.vtScrollUp(1);
12206 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
12207 } else if (cursorAtEndOfScreen) {
12208 // We're at the end of the screen, the only thing to do is put the
12209 // cursor to column 0.
12210 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
12211 } else {
12212 // Anywhere else, advance the cursor row, and reset the column.
12213 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
12214 }
12215 } else if (cursorAtEndOfScreen) {
12216 // We're at the end of the screen. Append a new row to the terminal,
12217 // shifting the top row into the scrollback.
12218 this.appendRows_(1);
12219 } else {
12220 // Anywhere else in the screen just moves the cursor.
12221 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
12222 }
12223};
12224
12225/**
12226 * Like newLine(), except maintain the cursor column.
12227 */
12228hterm.Terminal.prototype.lineFeed = function() {
12229 var column = this.screen_.cursorPosition.column;
12230 this.newLine();
12231 this.setCursorColumn(column);
12232};
12233
12234/**
12235 * If autoCarriageReturn is set then newLine(), else lineFeed().
12236 */
12237hterm.Terminal.prototype.formFeed = function() {
12238 if (this.options_.autoCarriageReturn) {
12239 this.newLine();
12240 } else {
12241 this.lineFeed();
12242 }
12243};
12244
12245/**
12246 * Move the cursor up one row, possibly inserting a blank line.
12247 *
12248 * The cursor column is not changed.
12249 */
12250hterm.Terminal.prototype.reverseLineFeed = function() {
12251 var scrollTop = this.getVTScrollTop();
12252 var currentRow = this.screen_.cursorPosition.row;
12253
12254 if (currentRow == scrollTop) {
12255 this.insertLines(1);
12256 } else {
12257 this.setAbsoluteCursorRow(currentRow - 1);
12258 }
12259};
12260
12261/**
12262 * Replace all characters to the left of the current cursor with the space
12263 * character.
12264 *
12265 * TODO(rginda): This should probably *remove* the characters (not just replace
12266 * with a space) if there are no characters at or beyond the current cursor
12267 * position.
12268 */
12269hterm.Terminal.prototype.eraseToLeft = function() {
12270 var cursor = this.saveCursor();
12271 this.setCursorColumn(0);
12272 this.screen_.overwriteString(lib.f.getWhitespace(cursor.column + 1));
12273 this.restoreCursor(cursor);
12274};
12275
12276/**
12277 * Erase a given number of characters to the right of the cursor.
12278 *
12279 * The cursor position is unchanged.
12280 *
12281 * If the current background color is not the default background color this
12282 * will insert spaces rather than delete. This is unfortunate because the
12283 * trailing space will affect text selection, but it's difficult to come up
12284 * with a way to style empty space that wouldn't trip up the hterm.Screen
12285 * code.
12286 *
12287 * eraseToRight is ignored in the presence of a cursor overflow. This deviates
12288 * from xterm, but agrees with gnome-terminal and konsole, xfce4-terminal. See
12289 * crbug.com/232390 for details.
12290 *
12291 * @param {number} opt_count The number of characters to erase.
12292 */
12293hterm.Terminal.prototype.eraseToRight = function(opt_count) {
12294 if (this.screen_.cursorPosition.overflow)
12295 return;
12296
12297 var maxCount = this.screenSize.width - this.screen_.cursorPosition.column;
12298 var count = opt_count ? Math.min(opt_count, maxCount) : maxCount;
12299
12300 if (this.screen_.textAttributes.background ===
12301 this.screen_.textAttributes.DEFAULT_COLOR) {
12302 var cursorRow = this.screen_.rowsArray[this.screen_.cursorPosition.row];
12303 if (hterm.TextAttributes.nodeWidth(cursorRow) <=
12304 this.screen_.cursorPosition.column + count) {
12305 this.screen_.deleteChars(count);
12306 this.clearCursorOverflow();
12307 return;
12308 }
12309 }
12310
12311 var cursor = this.saveCursor();
12312 this.screen_.overwriteString(lib.f.getWhitespace(count));
12313 this.restoreCursor(cursor);
12314 this.clearCursorOverflow();
12315};
12316
12317/**
12318 * Erase the current line.
12319 *
12320 * The cursor position is unchanged.
12321 */
12322hterm.Terminal.prototype.eraseLine = function() {
12323 var cursor = this.saveCursor();
12324 this.screen_.clearCursorRow();
12325 this.restoreCursor(cursor);
12326 this.clearCursorOverflow();
12327};
12328
12329/**
12330 * Erase all characters from the start of the screen to the current cursor
12331 * position, regardless of scroll region.
12332 *
12333 * The cursor position is unchanged.
12334 */
12335hterm.Terminal.prototype.eraseAbove = function() {
12336 var cursor = this.saveCursor();
12337
12338 this.eraseToLeft();
12339
12340 for (var i = 0; i < cursor.row; i++) {
12341 this.setAbsoluteCursorPosition(i, 0);
12342 this.screen_.clearCursorRow();
12343 }
12344
12345 this.restoreCursor(cursor);
12346 this.clearCursorOverflow();
12347};
12348
12349/**
12350 * Erase all characters from the current cursor position to the end of the
12351 * screen, regardless of scroll region.
12352 *
12353 * The cursor position is unchanged.
12354 */
12355hterm.Terminal.prototype.eraseBelow = function() {
12356 var cursor = this.saveCursor();
12357
12358 this.eraseToRight();
12359
12360 var bottom = this.screenSize.height - 1;
12361 for (var i = cursor.row + 1; i <= bottom; i++) {
12362 this.setAbsoluteCursorPosition(i, 0);
12363 this.screen_.clearCursorRow();
12364 }
12365
12366 this.restoreCursor(cursor);
12367 this.clearCursorOverflow();
12368};
12369
12370/**
12371 * Fill the terminal with a given character.
12372 *
12373 * This methods does not respect the VT scroll region.
12374 *
12375 * @param {string} ch The character to use for the fill.
12376 */
12377hterm.Terminal.prototype.fill = function(ch) {
12378 var cursor = this.saveCursor();
12379
12380 this.setAbsoluteCursorPosition(0, 0);
12381 for (var row = 0; row < this.screenSize.height; row++) {
12382 for (var col = 0; col < this.screenSize.width; col++) {
12383 this.setAbsoluteCursorPosition(row, col);
12384 this.screen_.overwriteString(ch);
12385 }
12386 }
12387
12388 this.restoreCursor(cursor);
12389};
12390
12391/**
12392 * Erase the entire display and leave the cursor at (0, 0).
12393 *
12394 * This does not respect the scroll region.
12395 *
12396 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
12397 * to the current screen.
12398 */
12399hterm.Terminal.prototype.clearHome = function(opt_screen) {
12400 var screen = opt_screen || this.screen_;
12401 var bottom = screen.getHeight();
12402
12403 if (bottom == 0) {
12404 // Empty screen, nothing to do.
12405 return;
12406 }
12407
12408 for (var i = 0; i < bottom; i++) {
12409 screen.setCursorPosition(i, 0);
12410 screen.clearCursorRow();
12411 }
12412
12413 screen.setCursorPosition(0, 0);
12414};
12415
12416/**
12417 * Erase the entire display without changing the cursor position.
12418 *
12419 * The cursor position is unchanged. This does not respect the scroll
12420 * region.
12421 *
12422 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
12423 * to the current screen.
12424 */
12425hterm.Terminal.prototype.clear = function(opt_screen) {
12426 var screen = opt_screen || this.screen_;
12427 var cursor = screen.cursorPosition.clone();
12428 this.clearHome(screen);
12429 screen.setCursorPosition(cursor.row, cursor.column);
12430};
12431
12432/**
12433 * VT command to insert lines at the current cursor row.
12434 *
12435 * This respects the current scroll region. Rows pushed off the bottom are
12436 * lost (they won't show up in the scrollback buffer).
12437 *
12438 * @param {integer} count The number of lines to insert.
12439 */
12440hterm.Terminal.prototype.insertLines = function(count) {
12441 var cursorRow = this.screen_.cursorPosition.row;
12442
12443 var bottom = this.getVTScrollBottom();
12444 count = Math.min(count, bottom - cursorRow);
12445
12446 // The moveCount is the number of rows we need to relocate to make room for
12447 // the new row(s). The count is the distance to move them.
12448 var moveCount = bottom - cursorRow - count + 1;
12449 if (moveCount)
12450 this.moveRows_(cursorRow, moveCount, cursorRow + count);
12451
12452 for (var i = count - 1; i >= 0; i--) {
12453 this.setAbsoluteCursorPosition(cursorRow + i, 0);
12454 this.screen_.clearCursorRow();
12455 }
12456};
12457
12458/**
12459 * VT command to delete lines at the current cursor row.
12460 *
12461 * New rows are added to the bottom of scroll region to take their place. New
12462 * rows are strictly there to take up space and have no content or style.
12463 *
12464 * @param {number} count The number of lines to delete.
12465 */
12466hterm.Terminal.prototype.deleteLines = function(count) {
12467 var cursor = this.saveCursor();
12468
12469 var top = cursor.row;
12470 var bottom = this.getVTScrollBottom();
12471
12472 var maxCount = bottom - top + 1;
12473 count = Math.min(count, maxCount);
12474
12475 var moveStart = bottom - count + 1;
12476 if (count != maxCount)
12477 this.moveRows_(top, count, moveStart);
12478
12479 for (var i = 0; i < count; i++) {
12480 this.setAbsoluteCursorPosition(moveStart + i, 0);
12481 this.screen_.clearCursorRow();
12482 }
12483
12484 this.restoreCursor(cursor);
12485 this.clearCursorOverflow();
12486};
12487
12488/**
12489 * Inserts the given number of spaces at the current cursor position.
12490 *
12491 * The cursor position is not changed.
12492 *
12493 * @param {number} count The number of spaces to insert.
12494 */
12495hterm.Terminal.prototype.insertSpace = function(count) {
12496 var cursor = this.saveCursor();
12497
12498 var ws = lib.f.getWhitespace(count || 1);
12499 this.screen_.insertString(ws);
12500 this.screen_.maybeClipCurrentRow();
12501
12502 this.restoreCursor(cursor);
12503 this.clearCursorOverflow();
12504};
12505
12506/**
12507 * Forward-delete the specified number of characters starting at the cursor
12508 * position.
12509 *
12510 * @param {integer} count The number of characters to delete.
12511 */
12512hterm.Terminal.prototype.deleteChars = function(count) {
12513 var deleted = this.screen_.deleteChars(count);
12514 if (deleted && !this.screen_.textAttributes.isDefault()) {
12515 var cursor = this.saveCursor();
12516 this.setCursorColumn(this.screenSize.width - deleted);
12517 this.screen_.insertString(lib.f.getWhitespace(deleted));
12518 this.restoreCursor(cursor);
12519 }
12520
12521 this.clearCursorOverflow();
12522};
12523
12524/**
12525 * Shift rows in the scroll region upwards by a given number of lines.
12526 *
12527 * New rows are inserted at the bottom of the scroll region to fill the
12528 * vacated rows. The new rows not filled out with the current text attributes.
12529 *
12530 * This function does not affect the scrollback rows at all. Rows shifted
12531 * off the top are lost.
12532 *
12533 * The cursor position is not altered.
12534 *
12535 * @param {integer} count The number of rows to scroll.
12536 */
12537hterm.Terminal.prototype.vtScrollUp = function(count) {
12538 var cursor = this.saveCursor();
12539
12540 this.setAbsoluteCursorRow(this.getVTScrollTop());
12541 this.deleteLines(count);
12542
12543 this.restoreCursor(cursor);
12544};
12545
12546/**
12547 * Shift rows below the cursor down by a given number of lines.
12548 *
12549 * This function respects the current scroll region.
12550 *
12551 * New rows are inserted at the top of the scroll region to fill the
12552 * vacated rows. The new rows not filled out with the current text attributes.
12553 *
12554 * This function does not affect the scrollback rows at all. Rows shifted
12555 * off the bottom are lost.
12556 *
12557 * @param {integer} count The number of rows to scroll.
12558 */
12559hterm.Terminal.prototype.vtScrollDown = function(opt_count) {
12560 var cursor = this.saveCursor();
12561
12562 this.setAbsoluteCursorPosition(this.getVTScrollTop(), 0);
12563 this.insertLines(opt_count);
12564
12565 this.restoreCursor(cursor);
12566};
12567
12568
12569/**
12570 * Set the cursor position.
12571 *
12572 * The cursor row is relative to the scroll region if the terminal has
12573 * 'origin mode' enabled, or relative to the addressable screen otherwise.
12574 *
12575 * @param {integer} row The new zero-based cursor row.
12576 * @param {integer} row The new zero-based cursor column.
12577 */
12578hterm.Terminal.prototype.setCursorPosition = function(row, column) {
12579 if (this.options_.originMode) {
12580 this.setRelativeCursorPosition(row, column);
12581 } else {
12582 this.setAbsoluteCursorPosition(row, column);
12583 }
12584};
12585
12586/**
12587 * Move the cursor relative to its current position.
12588 *
12589 * @param {number} row
12590 * @param {number} column
12591 */
12592hterm.Terminal.prototype.setRelativeCursorPosition = function(row, column) {
12593 var scrollTop = this.getVTScrollTop();
12594 row = lib.f.clamp(row + scrollTop, scrollTop, this.getVTScrollBottom());
12595 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
12596 this.screen_.setCursorPosition(row, column);
12597};
12598
12599/**
12600 * Move the cursor to the specified position.
12601 *
12602 * @param {number} row
12603 * @param {number} column
12604 */
12605hterm.Terminal.prototype.setAbsoluteCursorPosition = function(row, column) {
12606 row = lib.f.clamp(row, 0, this.screenSize.height - 1);
12607 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
12608 this.screen_.setCursorPosition(row, column);
12609};
12610
12611/**
12612 * Set the cursor column.
12613 *
12614 * @param {integer} column The new zero-based cursor column.
12615 */
12616hterm.Terminal.prototype.setCursorColumn = function(column) {
12617 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, column);
12618};
12619
12620/**
12621 * Return the cursor column.
12622 *
12623 * @return {integer} The zero-based cursor column.
12624 */
12625hterm.Terminal.prototype.getCursorColumn = function() {
12626 return this.screen_.cursorPosition.column;
12627};
12628
12629/**
12630 * Set the cursor row.
12631 *
12632 * The cursor row is relative to the scroll region if the terminal has
12633 * 'origin mode' enabled, or relative to the addressable screen otherwise.
12634 *
12635 * @param {integer} row The new cursor row.
12636 */
12637hterm.Terminal.prototype.setAbsoluteCursorRow = function(row) {
12638 this.setAbsoluteCursorPosition(row, this.screen_.cursorPosition.column);
12639};
12640
12641/**
12642 * Return the cursor row.
12643 *
12644 * @return {integer} The zero-based cursor row.
12645 */
12646hterm.Terminal.prototype.getCursorRow = function() {
12647 return this.screen_.cursorPosition.row;
12648};
12649
12650/**
12651 * Request that the ScrollPort redraw itself soon.
12652 *
12653 * The redraw will happen asynchronously, soon after the call stack winds down.
12654 * Multiple calls will be coalesced into a single redraw.
12655 */
12656hterm.Terminal.prototype.scheduleRedraw_ = function() {
12657 if (this.timeouts_.redraw)
12658 return;
12659
12660 var self = this;
12661 this.timeouts_.redraw = setTimeout(function() {
12662 delete self.timeouts_.redraw;
12663 self.scrollPort_.redraw_();
12664 }, 0);
12665};
12666
12667/**
12668 * Request that the ScrollPort be scrolled to the bottom.
12669 *
12670 * The scroll will happen asynchronously, soon after the call stack winds down.
12671 * Multiple calls will be coalesced into a single scroll.
12672 *
12673 * This affects the scrollbar position of the ScrollPort, and has nothing to
12674 * do with the VT scroll commands.
12675 */
12676hterm.Terminal.prototype.scheduleScrollDown_ = function() {
12677 if (this.timeouts_.scrollDown)
12678 return;
12679
12680 var self = this;
12681 this.timeouts_.scrollDown = setTimeout(function() {
12682 delete self.timeouts_.scrollDown;
12683 self.scrollPort_.scrollRowToBottom(self.getRowCount());
12684 }, 10);
12685};
12686
12687/**
12688 * Move the cursor up a specified number of rows.
12689 *
12690 * @param {integer} count The number of rows to move the cursor.
12691 */
12692hterm.Terminal.prototype.cursorUp = function(count) {
12693 return this.cursorDown(-(count || 1));
12694};
12695
12696/**
12697 * Move the cursor down a specified number of rows.
12698 *
12699 * @param {integer} count The number of rows to move the cursor.
12700 */
12701hterm.Terminal.prototype.cursorDown = function(count) {
12702 count = count || 1;
12703 var minHeight = (this.options_.originMode ? this.getVTScrollTop() : 0);
12704 var maxHeight = (this.options_.originMode ? this.getVTScrollBottom() :
12705 this.screenSize.height - 1);
12706
12707 var row = lib.f.clamp(this.screen_.cursorPosition.row + count,
12708 minHeight, maxHeight);
12709 this.setAbsoluteCursorRow(row);
12710};
12711
12712/**
12713 * Move the cursor left a specified number of columns.
12714 *
12715 * If reverse wraparound mode is enabled and the previous row wrapped into
12716 * the current row then we back up through the wraparound as well.
12717 *
12718 * @param {integer} count The number of columns to move the cursor.
12719 */
12720hterm.Terminal.prototype.cursorLeft = function(count) {
12721 count = count || 1;
12722
12723 if (count < 1)
12724 return;
12725
12726 var currentColumn = this.screen_.cursorPosition.column;
12727 if (this.options_.reverseWraparound) {
12728 if (this.screen_.cursorPosition.overflow) {
12729 // If this cursor is in the right margin, consume one count to get it
12730 // back to the last column. This only applies when we're in reverse
12731 // wraparound mode.
12732 count--;
12733 this.clearCursorOverflow();
12734
12735 if (!count)
12736 return;
12737 }
12738
12739 var newRow = this.screen_.cursorPosition.row;
12740 var newColumn = currentColumn - count;
12741 if (newColumn < 0) {
12742 newRow = newRow - Math.floor(count / this.screenSize.width) - 1;
12743 if (newRow < 0) {
12744 // xterm also wraps from row 0 to the last row.
12745 newRow = this.screenSize.height + newRow % this.screenSize.height;
12746 }
12747 newColumn = this.screenSize.width + newColumn % this.screenSize.width;
12748 }
12749
12750 this.setCursorPosition(Math.max(newRow, 0), newColumn);
12751
12752 } else {
12753 var newColumn = Math.max(currentColumn - count, 0);
12754 this.setCursorColumn(newColumn);
12755 }
12756};
12757
12758/**
12759 * Move the cursor right a specified number of columns.
12760 *
12761 * @param {integer} count The number of columns to move the cursor.
12762 */
12763hterm.Terminal.prototype.cursorRight = function(count) {
12764 count = count || 1;
12765
12766 if (count < 1)
12767 return;
12768
12769 var column = lib.f.clamp(this.screen_.cursorPosition.column + count,
12770 0, this.screenSize.width - 1);
12771 this.setCursorColumn(column);
12772};
12773
12774/**
12775 * Reverse the foreground and background colors of the terminal.
12776 *
12777 * This only affects text that was drawn with no attributes.
12778 *
12779 * TODO(rginda): Test xterm to see if reverse is respected for text that has
12780 * been drawn with attributes that happen to coincide with the default
12781 * 'no-attribute' colors. My guess is probably not.
12782 *
12783 * @param {boolean} state The state to set.
12784 */
12785hterm.Terminal.prototype.setReverseVideo = function(state) {
12786 this.options_.reverseVideo = state;
12787 if (state) {
12788 this.scrollPort_.setForegroundColor(this.prefs_.get('background-color'));
12789 this.scrollPort_.setBackgroundColor(this.prefs_.get('foreground-color'));
12790 } else {
12791 this.scrollPort_.setForegroundColor(this.prefs_.get('foreground-color'));
12792 this.scrollPort_.setBackgroundColor(this.prefs_.get('background-color'));
12793 }
12794};
12795
12796/**
12797 * Ring the terminal bell.
12798 *
12799 * This will not play the bell audio more than once per second.
12800 */
12801hterm.Terminal.prototype.ringBell = function() {
12802 this.cursorNode_.style.backgroundColor =
12803 this.scrollPort_.getForegroundColor();
12804
12805 var self = this;
12806 setTimeout(function() {
12807 self.cursorNode_.style.backgroundColor = self.prefs_.get('cursor-color');
12808 }, 200);
12809
12810 // bellSquelchTimeout_ affects both audio and notification bells.
12811 if (this.bellSquelchTimeout_)
12812 return;
12813
12814 if (this.bellAudio_.getAttribute('src')) {
12815 this.bellAudio_.play();
12816 this.bellSequelchTimeout_ = setTimeout(function() {
12817 delete this.bellSquelchTimeout_;
12818 }.bind(this), 500);
12819 } else {
12820 delete this.bellSquelchTimeout_;
12821 }
12822
12823 if (this.desktopNotificationBell_ && !this.document_.hasFocus()) {
12824 var n = new Notification(
12825 lib.f.replaceVars(hterm.desktopNotificationTitle,
12826 {'title': window.document.title || 'hterm'}));
12827 this.bellNotificationList_.push(n);
12828 // TODO: Should we try to raise the window here?
12829 n.onclick = function() { self.closeBellNotifications_(); };
12830 }
12831};
12832
12833/**
12834 * Set the origin mode bit.
12835 *
12836 * If origin mode is on, certain VT cursor and scrolling commands measure their
12837 * row parameter relative to the VT scroll region. Otherwise, row 0 corresponds
12838 * to the top of the addressable screen.
12839 *
12840 * Defaults to off.
12841 *
12842 * @param {boolean} state True to set origin mode, false to unset.
12843 */
12844hterm.Terminal.prototype.setOriginMode = function(state) {
12845 this.options_.originMode = state;
12846 this.setCursorPosition(0, 0);
12847};
12848
12849/**
12850 * Set the insert mode bit.
12851 *
12852 * If insert mode is on, existing text beyond the cursor position will be
12853 * shifted right to make room for new text. Otherwise, new text overwrites
12854 * any existing text.
12855 *
12856 * Defaults to off.
12857 *
12858 * @param {boolean} state True to set insert mode, false to unset.
12859 */
12860hterm.Terminal.prototype.setInsertMode = function(state) {
12861 this.options_.insertMode = state;
12862};
12863
12864/**
12865 * Set the auto carriage return bit.
12866 *
12867 * If auto carriage return is on then a formfeed character is interpreted
12868 * as a newline, otherwise it's the same as a linefeed. The difference boils
12869 * down to whether or not the cursor column is reset.
12870 *
12871 * @param {boolean} state The state to set.
12872 */
12873hterm.Terminal.prototype.setAutoCarriageReturn = function(state) {
12874 this.options_.autoCarriageReturn = state;
12875};
12876
12877/**
12878 * Set the wraparound mode bit.
12879 *
12880 * If wraparound mode is on, certain VT commands will allow the cursor to wrap
12881 * to the start of the following row. Otherwise, the cursor is clamped to the
12882 * end of the screen and attempts to write past it are ignored.
12883 *
12884 * Defaults to on.
12885 *
12886 * @param {boolean} state True to set wraparound mode, false to unset.
12887 */
12888hterm.Terminal.prototype.setWraparound = function(state) {
12889 this.options_.wraparound = state;
12890};
12891
12892/**
12893 * Set the reverse-wraparound mode bit.
12894 *
12895 * If wraparound mode is off, certain VT commands will allow the cursor to wrap
12896 * to the end of the previous row. Otherwise, the cursor is clamped to column
12897 * 0.
12898 *
12899 * Defaults to off.
12900 *
12901 * @param {boolean} state True to set reverse-wraparound mode, false to unset.
12902 */
12903hterm.Terminal.prototype.setReverseWraparound = function(state) {
12904 this.options_.reverseWraparound = state;
12905};
12906
12907/**
12908 * Selects between the primary and alternate screens.
12909 *
12910 * If alternate mode is on, the alternate screen is active. Otherwise the
12911 * primary screen is active.
12912 *
12913 * Swapping screens has no effect on the scrollback buffer.
12914 *
12915 * Each screen maintains its own cursor position.
12916 *
12917 * Defaults to off.
12918 *
12919 * @param {boolean} state True to set alternate mode, false to unset.
12920 */
12921hterm.Terminal.prototype.setAlternateMode = function(state) {
12922 var cursor = this.saveCursor();
12923 this.screen_ = state ? this.alternateScreen_ : this.primaryScreen_;
12924
12925 if (this.screen_.rowsArray.length &&
12926 this.screen_.rowsArray[0].rowIndex != this.scrollbackRows_.length) {
12927 // If the screen changed sizes while we were away, our rowIndexes may
12928 // be incorrect.
12929 var offset = this.scrollbackRows_.length;
12930 var ary = this.screen_.rowsArray;
12931 for (var i = 0; i < ary.length; i++) {
12932 ary[i].rowIndex = offset + i;
12933 }
12934 }
12935
12936 this.realizeWidth_(this.screenSize.width);
12937 this.realizeHeight_(this.screenSize.height);
12938 this.scrollPort_.syncScrollHeight();
12939 this.scrollPort_.invalidate();
12940
12941 this.restoreCursor(cursor);
12942 this.scrollPort_.resize();
12943};
12944
12945/**
12946 * Set the cursor-blink mode bit.
12947 *
12948 * If cursor-blink is on, the cursor will blink when it is visible. Otherwise
12949 * a visible cursor does not blink.
12950 *
12951 * You should make sure to turn blinking off if you're going to dispose of a
12952 * terminal, otherwise you'll leak a timeout.
12953 *
12954 * Defaults to on.
12955 *
12956 * @param {boolean} state True to set cursor-blink mode, false to unset.
12957 */
12958hterm.Terminal.prototype.setCursorBlink = function(state) {
12959 this.options_.cursorBlink = state;
12960
12961 if (!state && this.timeouts_.cursorBlink) {
12962 clearTimeout(this.timeouts_.cursorBlink);
12963 delete this.timeouts_.cursorBlink;
12964 }
12965
12966 if (this.options_.cursorVisible)
12967 this.setCursorVisible(true);
12968};
12969
12970/**
12971 * Set the cursor-visible mode bit.
12972 *
12973 * If cursor-visible is on, the cursor will be visible. Otherwise it will not.
12974 *
12975 * Defaults to on.
12976 *
12977 * @param {boolean} state True to set cursor-visible mode, false to unset.
12978 */
12979hterm.Terminal.prototype.setCursorVisible = function(state) {
12980 this.options_.cursorVisible = state;
12981
12982 if (!state) {
12983 if (this.timeouts_.cursorBlink) {
12984 clearTimeout(this.timeouts_.cursorBlink);
12985 delete this.timeouts_.cursorBlink;
12986 }
12987 this.cursorNode_.style.opacity = '0';
12988 return;
12989 }
12990
12991 this.syncCursorPosition_();
12992
12993 this.cursorNode_.style.opacity = '1';
12994
12995 if (this.options_.cursorBlink) {
12996 if (this.timeouts_.cursorBlink)
12997 return;
12998
12999 this.onCursorBlink_();
13000 } else {
13001 if (this.timeouts_.cursorBlink) {
13002 clearTimeout(this.timeouts_.cursorBlink);
13003 delete this.timeouts_.cursorBlink;
13004 }
13005 }
13006};
13007
13008/**
13009 * Synchronizes the visible cursor and document selection with the current
13010 * cursor coordinates.
13011 */
13012hterm.Terminal.prototype.syncCursorPosition_ = function() {
13013 var topRowIndex = this.scrollPort_.getTopRowIndex();
13014 var bottomRowIndex = this.scrollPort_.getBottomRowIndex(topRowIndex);
13015 var cursorRowIndex = this.scrollbackRows_.length +
13016 this.screen_.cursorPosition.row;
13017
13018 if (cursorRowIndex > bottomRowIndex) {
13019 // Cursor is scrolled off screen, move it outside of the visible area.
13020 this.cursorNode_.style.top = -this.scrollPort_.characterSize.height + 'px';
13021 return;
13022 }
13023
13024 if (this.options_.cursorVisible &&
13025 this.cursorNode_.style.display == 'none') {
13026 // Re-display the terminal cursor if it was hidden by the mouse cursor.
13027 this.cursorNode_.style.display = '';
13028 }
13029
13030
13031 this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin +
13032 this.scrollPort_.characterSize.height * (cursorRowIndex - topRowIndex) +
13033 'px';
13034 this.cursorNode_.style.left = this.scrollPort_.characterSize.width *
13035 this.screen_.cursorPosition.column + 'px';
13036
13037 this.cursorNode_.setAttribute('title',
13038 '(' + this.screen_.cursorPosition.row +
13039 ', ' + this.screen_.cursorPosition.column +
13040 ')');
13041
13042 // Update the caret for a11y purposes.
13043 var selection = this.document_.getSelection();
13044 if (selection && selection.isCollapsed)
13045 this.screen_.syncSelectionCaret(selection);
13046};
13047
13048/**
13049 * Adjusts the style of this.cursorNode_ according to the current cursor shape
13050 * and character cell dimensions.
13051 */
13052hterm.Terminal.prototype.restyleCursor_ = function() {
13053 var shape = this.cursorShape_;
13054
13055 if (this.cursorNode_.getAttribute('focus') == 'false') {
13056 // Always show a block cursor when unfocused.
13057 shape = hterm.Terminal.cursorShape.BLOCK;
13058 }
13059
13060 var style = this.cursorNode_.style;
13061
13062 style.width = this.scrollPort_.characterSize.width + 'px';
13063
13064 switch (shape) {
13065 case hterm.Terminal.cursorShape.BEAM:
13066 style.height = this.scrollPort_.characterSize.height + 'px';
13067 style.backgroundColor = 'transparent';
13068 style.borderBottomStyle = null;
13069 style.borderLeftStyle = 'solid';
13070 break;
13071
13072 case hterm.Terminal.cursorShape.UNDERLINE:
13073 style.height = this.scrollPort_.characterSize.baseline + 'px';
13074 style.backgroundColor = 'transparent';
13075 style.borderBottomStyle = 'solid';
13076 // correct the size to put it exactly at the baseline
13077 style.borderLeftStyle = null;
13078 break;
13079
13080 default:
13081 style.height = this.scrollPort_.characterSize.height + 'px';
13082 style.backgroundColor = this.cursorColor_;
13083 style.borderBottomStyle = null;
13084 style.borderLeftStyle = null;
13085 break;
13086 }
13087};
13088
13089/**
13090 * Synchronizes the visible cursor with the current cursor coordinates.
13091 *
13092 * The sync will happen asynchronously, soon after the call stack winds down.
13093 * Multiple calls will be coalesced into a single sync.
13094 */
13095hterm.Terminal.prototype.scheduleSyncCursorPosition_ = function() {
13096 if (this.timeouts_.syncCursor)
13097 return;
13098
13099 var self = this;
13100 this.timeouts_.syncCursor = setTimeout(function() {
13101 self.syncCursorPosition_();
13102 delete self.timeouts_.syncCursor;
13103 }, 0);
13104};
13105
13106/**
13107 * Show or hide the zoom warning.
13108 *
13109 * The zoom warning is a message warning the user that their browser zoom must
13110 * be set to 100% in order for hterm to function properly.
13111 *
13112 * @param {boolean} state True to show the message, false to hide it.
13113 */
13114hterm.Terminal.prototype.showZoomWarning_ = function(state) {
13115 if (!this.zoomWarningNode_) {
13116 if (!state)
13117 return;
13118
13119 this.zoomWarningNode_ = this.document_.createElement('div');
13120 this.zoomWarningNode_.style.cssText = (
13121 'color: black;' +
13122 'background-color: #ff2222;' +
13123 'font-size: large;' +
13124 'border-radius: 8px;' +
13125 'opacity: 0.75;' +
13126 'padding: 0.2em 0.5em 0.2em 0.5em;' +
13127 'top: 0.5em;' +
13128 'right: 1.2em;' +
13129 'position: absolute;' +
13130 '-webkit-text-size-adjust: none;' +
13131 '-webkit-user-select: none;' +
13132 '-moz-text-size-adjust: none;' +
13133 '-moz-user-select: none;');
13134
13135 this.zoomWarningNode_.addEventListener('click', function(e) {
13136 this.parentNode.removeChild(this);
13137 });
13138 }
13139
13140 this.zoomWarningNode_.textContent = lib.MessageManager.replaceReferences(
13141 hterm.zoomWarningMessage,
13142 [parseInt(this.scrollPort_.characterSize.zoomFactor * 100)]);
13143
13144 this.zoomWarningNode_.style.fontFamily = this.prefs_.get('font-family');
13145
13146 if (state) {
13147 if (!this.zoomWarningNode_.parentNode)
13148 this.div_.parentNode.appendChild(this.zoomWarningNode_);
13149 } else if (this.zoomWarningNode_.parentNode) {
13150 this.zoomWarningNode_.parentNode.removeChild(this.zoomWarningNode_);
13151 }
13152};
13153
13154/**
13155 * Show the terminal overlay for a given amount of time.
13156 *
13157 * The terminal overlay appears in inverse video in a large font, centered
13158 * over the terminal. You should probably keep the overlay message brief,
13159 * since it's in a large font and you probably aren't going to check the size
13160 * of the terminal first.
13161 *
13162 * @param {string} msg The text (not HTML) message to display in the overlay.
13163 * @param {number} opt_timeout The amount of time to wait before fading out
13164 * the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
13165 * stay up forever (or until the next overlay).
13166 */
13167hterm.Terminal.prototype.showOverlay = function(msg, opt_timeout) {
13168 if (!this.overlayNode_) {
13169 if (!this.div_)
13170 return;
13171
13172 this.overlayNode_ = this.document_.createElement('div');
13173 this.overlayNode_.style.cssText = (
13174 'border-radius: 15px;' +
13175 'font-size: xx-large;' +
13176 'opacity: 0.75;' +
13177 'padding: 0.2em 0.5em 0.2em 0.5em;' +
13178 'position: absolute;' +
13179 '-webkit-user-select: none;' +
13180 '-webkit-transition: opacity 180ms ease-in;' +
13181 '-moz-user-select: none;' +
13182 '-moz-transition: opacity 180ms ease-in;');
13183
13184 this.overlayNode_.addEventListener('mousedown', function(e) {
13185 e.preventDefault();
13186 e.stopPropagation();
13187 }, true);
13188 }
13189
13190 this.overlayNode_.style.color = this.prefs_.get('background-color');
13191 this.overlayNode_.style.backgroundColor = this.prefs_.get('foreground-color');
13192 this.overlayNode_.style.fontFamily = this.prefs_.get('font-family');
13193
13194 this.overlayNode_.textContent = msg;
13195 this.overlayNode_.style.opacity = '0.75';
13196
13197 if (!this.overlayNode_.parentNode)
13198 this.div_.appendChild(this.overlayNode_);
13199
13200 var divSize = hterm.getClientSize(this.div_);
13201 var overlaySize = hterm.getClientSize(this.overlayNode_);
13202
13203 this.overlayNode_.style.top =
13204 (divSize.height - overlaySize.height) / 2 + 'px';
13205 this.overlayNode_.style.left = (divSize.width - overlaySize.width -
13206 this.scrollPort_.currentScrollbarWidthPx) / 2 + 'px';
13207
13208 var self = this;
13209
13210 if (this.overlayTimeout_)
13211 clearTimeout(this.overlayTimeout_);
13212
13213 if (opt_timeout === null)
13214 return;
13215
13216 this.overlayTimeout_ = setTimeout(function() {
13217 self.overlayNode_.style.opacity = '0';
13218 self.overlayTimeout_ = setTimeout(function() {
13219 if (self.overlayNode_.parentNode)
13220 self.overlayNode_.parentNode.removeChild(self.overlayNode_);
13221 self.overlayTimeout_ = null;
13222 self.overlayNode_.style.opacity = '0.75';
13223 }, 200);
13224 }, opt_timeout || 1500);
13225};
13226
13227/**
13228 * Paste from the system clipboard to the terminal.
13229 */
13230hterm.Terminal.prototype.paste = function() {
13231 hterm.pasteFromClipboard(this.document_);
13232};
13233
13234/**
13235 * Copy a string to the system clipboard.
13236 *
13237 * Note: If there is a selected range in the terminal, it'll be cleared.
13238 *
13239 * @param {string} str The string to copy.
13240 */
13241hterm.Terminal.prototype.copyStringToClipboard = function(str) {
13242 if (this.prefs_.get('enable-clipboard-notice'))
13243 setTimeout(this.showOverlay.bind(this, hterm.notifyCopyMessage, 500), 200);
13244
13245 var copySource = this.document_.createElement('pre');
13246 copySource.textContent = str;
13247 copySource.style.cssText = (
13248 '-webkit-user-select: text;' +
13249 '-moz-user-select: text;' +
13250 'position: absolute;' +
13251 'top: -99px');
13252
13253 this.document_.body.appendChild(copySource);
13254
13255 var selection = this.document_.getSelection();
13256 var anchorNode = selection.anchorNode;
13257 var anchorOffset = selection.anchorOffset;
13258 var focusNode = selection.focusNode;
13259 var focusOffset = selection.focusOffset;
13260
13261 selection.selectAllChildren(copySource);
13262
13263 hterm.copySelectionToClipboard(this.document_);
13264
13265 // IE doesn't support selection.extend. This means that the selection
13266 // won't return on IE.
13267 if (selection.extend) {
13268 selection.collapse(anchorNode, anchorOffset);
13269 selection.extend(focusNode, focusOffset);
13270 }
13271
13272 copySource.parentNode.removeChild(copySource);
13273};
13274
13275/**
13276 * Returns the selected text, or null if no text is selected.
13277 *
13278 * @return {string|null}
13279 */
13280hterm.Terminal.prototype.getSelectionText = function() {
13281 var selection = this.scrollPort_.selection;
13282 selection.sync();
13283
13284 if (selection.isCollapsed)
13285 return null;
13286
13287
13288 // Start offset measures from the beginning of the line.
13289 var startOffset = selection.startOffset;
13290 var node = selection.startNode;
13291
13292 if (node.nodeName != 'X-ROW') {
13293 // If the selection doesn't start on an x-row node, then it must be
13294 // somewhere inside the x-row. Add any characters from previous siblings
13295 // into the start offset.
13296
13297 if (node.nodeName == '#text' && node.parentNode.nodeName == 'SPAN') {
13298 // If node is the text node in a styled span, move up to the span node.
13299 node = node.parentNode;
13300 }
13301
13302 while (node.previousSibling) {
13303 node = node.previousSibling;
13304 startOffset += hterm.TextAttributes.nodeWidth(node);
13305 }
13306 }
13307
13308 // End offset measures from the end of the line.
13309 var endOffset = (hterm.TextAttributes.nodeWidth(selection.endNode) -
13310 selection.endOffset);
13311 node = selection.endNode;
13312
13313 if (node.nodeName != 'X-ROW') {
13314 // If the selection doesn't end on an x-row node, then it must be
13315 // somewhere inside the x-row. Add any characters from following siblings
13316 // into the end offset.
13317
13318 if (node.nodeName == '#text' && node.parentNode.nodeName == 'SPAN') {
13319 // If node is the text node in a styled span, move up to the span node.
13320 node = node.parentNode;
13321 }
13322
13323 while (node.nextSibling) {
13324 node = node.nextSibling;
13325 endOffset += hterm.TextAttributes.nodeWidth(node);
13326 }
13327 }
13328
13329 var rv = this.getRowsText(selection.startRow.rowIndex,
13330 selection.endRow.rowIndex + 1);
13331 return lib.wc.substring(rv, startOffset, lib.wc.strWidth(rv) - endOffset);
13332};
13333
13334/**
13335 * Copy the current selection to the system clipboard, then clear it after a
13336 * short delay.
13337 */
13338hterm.Terminal.prototype.copySelectionToClipboard = function() {
13339 var text = this.getSelectionText();
13340 if (text != null)
13341 this.copyStringToClipboard(text);
13342};
13343
13344hterm.Terminal.prototype.overlaySize = function() {
13345 this.showOverlay(this.screenSize.width + 'x' + this.screenSize.height);
13346};
13347
13348/**
13349 * Invoked by hterm.Terminal.Keyboard when a VT keystroke is detected.
13350 *
13351 * @param {string} string The VT string representing the keystroke, in UTF-16.
13352 */
13353hterm.Terminal.prototype.onVTKeystroke = function(string) {
13354 if (this.scrollOnKeystroke_)
13355 this.scrollPort_.scrollRowToBottom(this.getRowCount());
13356
13357 this.io.onVTKeystroke(this.keyboard.encode(string));
13358};
13359
13360/**
13361 * Launches url in a new tab.
13362 *
13363 * @param {string} url URL to launch in a new tab.
13364 */
13365hterm.Terminal.prototype.openUrl = function(url) {
13366 var win = window.open(url, '_blank');
13367 win.focus();
13368}
13369
13370/**
13371 * Open the selected url.
13372 */
13373hterm.Terminal.prototype.openSelectedUrl_ = function() {
13374 var str = this.getSelectionText();
13375
13376 // If there is no selection, try and expand wherever they clicked.
13377 if (str == null) {
13378 this.screen_.expandSelection(this.document_.getSelection());
13379 str = this.getSelectionText();
13380 }
13381
13382 // Make sure URL is valid before opening.
13383 if (str.length > 2048 || str.search(/[\s\[\](){}<>"'\\^`]/) >= 0)
13384 return;
13385 // If the URL isn't anchored, it'll open relative to the extension.
13386 // We have no way of knowing the correct schema, so assume http.
13387 if (str.search('^[a-zA-Z][a-zA-Z0-9+.-]*://') < 0)
13388 str = 'http://' + str;
13389
13390 this.openUrl(str);
13391}
13392
13393
13394/**
13395 * Add the terminalRow and terminalColumn properties to mouse events and
13396 * then forward on to onMouse().
13397 *
13398 * The terminalRow and terminalColumn properties contain the (row, column)
13399 * coordinates for the mouse event.
13400 *
13401 * @param {Event} e The mouse event to handle.
13402 */
13403hterm.Terminal.prototype.onMouse_ = function(e) {
13404 if (e.processedByTerminalHandler_) {
13405 // We register our event handlers on the document, as well as the cursor
13406 // and the scroll blocker. Mouse events that occur on the cursor or
13407 // scroll blocker will also appear on the document, but we don't want to
13408 // process them twice.
13409 //
13410 // We can't just prevent bubbling because that has other side effects, so
13411 // we decorate the event object with this property instead.
13412 return;
13413 }
13414
13415 var reportMouseEvents = (!this.defeatMouseReports_ &&
13416 this.vt.mouseReport != this.vt.MOUSE_REPORT_DISABLED);
13417
13418 e.processedByTerminalHandler_ = true;
13419
13420 // One based row/column stored on the mouse event.
13421 e.terminalRow = parseInt((e.clientY - this.scrollPort_.visibleRowTopMargin) /
13422 this.scrollPort_.characterSize.height) + 1;
13423 e.terminalColumn = parseInt(e.clientX /
13424 this.scrollPort_.characterSize.width) + 1;
13425
13426 if (e.type == 'mousedown' && e.terminalColumn > this.screenSize.width) {
13427 // Mousedown in the scrollbar area.
13428 return;
13429 }
13430
13431 if (this.options_.cursorVisible && !reportMouseEvents) {
13432 // If the cursor is visible and we're not sending mouse events to the
13433 // host app, then we want to hide the terminal cursor when the mouse
13434 // cursor is over top. This keeps the terminal cursor from interfering
13435 // with local text selection.
13436 if (e.terminalRow - 1 == this.screen_.cursorPosition.row &&
13437 e.terminalColumn - 1 == this.screen_.cursorPosition.column) {
13438 this.cursorNode_.style.display = 'none';
13439 } else if (this.cursorNode_.style.display == 'none') {
13440 this.cursorNode_.style.display = '';
13441 }
13442 }
13443
13444 if (e.type == 'mousedown') {
13445 if (e.altKey || !reportMouseEvents) {
13446 // If VT mouse reporting is disabled, or has been defeated with
13447 // alt-mousedown, then the mouse will act on the local selection.
13448 this.defeatMouseReports_ = true;
13449 this.setSelectionEnabled(true);
13450 } else {
13451 // Otherwise we defer ownership of the mouse to the VT.
13452 this.defeatMouseReports_ = false;
13453 this.document_.getSelection().collapseToEnd();
13454 this.setSelectionEnabled(false);
13455 e.preventDefault();
13456 }
13457 }
13458
13459 if (!reportMouseEvents) {
13460 if (e.type == 'dblclick' && this.copyOnSelect) {
13461 this.screen_.expandSelection(this.document_.getSelection());
13462 this.copySelectionToClipboard(this.document_);
13463 }
13464
13465 if (e.type == 'click' && !e.shiftKey && e.ctrlKey) {
13466 // Debounce this event with the dblclick event. If you try to doubleclick
13467 // a URL to open it, Chrome will fire click then dblclick, but we won't
13468 // have expanded the selection text at the first click event.
13469 clearTimeout(this.timeouts_.openUrl);
13470 this.timeouts_.openUrl = setTimeout(this.openSelectedUrl_.bind(this),
13471 500);
13472 return;
13473 }
13474
13475 if (e.type == 'mousedown' && e.which == this.mousePasteButton)
13476 this.paste();
13477
13478 if (e.type == 'mouseup' && e.which == 1 && this.copyOnSelect &&
13479 !this.document_.getSelection().isCollapsed) {
13480 this.copySelectionToClipboard(this.document_);
13481 }
13482
13483 if ((e.type == 'mousemove' || e.type == 'mouseup') &&
13484 this.scrollBlockerNode_.engaged) {
13485 // Disengage the scroll-blocker after one of these events.
13486 this.scrollBlockerNode_.engaged = false;
13487 this.scrollBlockerNode_.style.top = '-99px';
13488 }
13489
13490 } else /* if (this.reportMouseEvents) */ {
13491 if (!this.scrollBlockerNode_.engaged) {
13492 if (e.type == 'mousedown') {
13493 // Move the scroll-blocker into place if we want to keep the scrollport
13494 // from scrolling.
13495 this.scrollBlockerNode_.engaged = true;
13496 this.scrollBlockerNode_.style.top = (e.clientY - 5) + 'px';
13497 this.scrollBlockerNode_.style.left = (e.clientX - 5) + 'px';
13498 } else if (e.type == 'mousemove') {
13499 // Oh. This means that drag-scroll was disabled AFTER the mouse down,
13500 // in which case it's too late to engage the scroll-blocker.
13501 this.document_.getSelection().collapseToEnd();
13502 e.preventDefault();
13503 }
13504 }
13505
13506 this.onMouse(e);
13507 }
13508
13509 if (e.type == 'mouseup' && this.document_.getSelection().isCollapsed) {
13510 // Restore this on mouseup in case it was temporarily defeated with a
13511 // alt-mousedown. Only do this when the selection is empty so that
13512 // we don't immediately kill the users selection.
13513 this.defeatMouseReports_ = false;
13514 }
13515};
13516
13517/**
13518 * Clients should override this if they care to know about mouse events.
13519 *
13520 * The event parameter will be a normal DOM mouse click event with additional
13521 * 'terminalRow' and 'terminalColumn' properties.
13522 *
13523 * @param {Event} e The mouse event to handle.
13524 */
13525hterm.Terminal.prototype.onMouse = function(e) { };
13526
13527/**
13528 * React when focus changes.
13529 *
13530 * @param {boolean} focused True if focused, false otherwise.
13531 */
13532hterm.Terminal.prototype.onFocusChange_ = function(focused) {
13533 this.cursorNode_.setAttribute('focus', focused);
13534 this.restyleCursor_();
13535 if (focused === true)
13536 this.closeBellNotifications_();
13537};
13538
13539/**
13540 * React when the ScrollPort is scrolled.
13541 */
13542hterm.Terminal.prototype.onScroll_ = function() {
13543 this.scheduleSyncCursorPosition_();
13544};
13545
13546/**
13547 * React when text is pasted into the scrollPort.
13548 *
13549 * @param {Event} e The DOM paste event to handle.
13550 */
13551hterm.Terminal.prototype.onPaste_ = function(e) {
13552 var data = e.text.replace(/\n/mg, '\r');
13553 data = this.keyboard.encode(data);
13554 if (this.options_.bracketedPaste)
13555 data = '\x1b[200~' + data + '\x1b[201~';
13556
13557 this.io.sendString(data);
13558};
13559
13560/**
13561 * React when the user tries to copy from the scrollPort.
13562 *
13563 * @param {Event} e The DOM copy event.
13564 */
13565hterm.Terminal.prototype.onCopy_ = function(e) {
13566 if (!this.useDefaultWindowCopy) {
13567 e.preventDefault();
13568 setTimeout(this.copySelectionToClipboard.bind(this), 0);
13569 }
13570};
13571
13572/**
13573 * React when the ScrollPort is resized.
13574 *
13575 * Note: This function should not directly contain code that alters the internal
13576 * state of the terminal. That kind of code belongs in realizeWidth or
13577 * realizeHeight, so that it can be executed synchronously in the case of a
13578 * programmatic width change.
13579 */
13580hterm.Terminal.prototype.onResize_ = function() {
13581 var columnCount = Math.floor(this.scrollPort_.getScreenWidth() /
13582 this.scrollPort_.characterSize.width) || 0;
13583 var rowCount = lib.f.smartFloorDivide(this.scrollPort_.getScreenHeight(),
13584 this.scrollPort_.characterSize.height) || 0;
13585
13586 if (columnCount <= 0 || rowCount <= 0) {
13587 // We avoid these situations since they happen sometimes when the terminal
13588 // gets removed from the document or during the initial load, and we can't
13589 // deal with that.
13590 // This can also happen if called before the scrollPort calculates the
13591 // character size, meaning we dived by 0 above and default to 0 values.
13592 return;
13593 }
13594
13595 var isNewSize = (columnCount != this.screenSize.width ||
13596 rowCount != this.screenSize.height);
13597
13598 // We do this even if the size didn't change, just to be sure everything is
13599 // in sync.
13600 this.realizeSize_(columnCount, rowCount);
13601 this.showZoomWarning_(this.scrollPort_.characterSize.zoomFactor != 1);
13602
13603 if (isNewSize)
13604 this.overlaySize();
13605
13606 this.restyleCursor_();
13607 this.scheduleSyncCursorPosition_();
13608};
13609
13610/**
13611 * Service the cursor blink timeout.
13612 */
13613hterm.Terminal.prototype.onCursorBlink_ = function() {
13614 if (!this.options_.cursorBlink) {
13615 delete this.timeouts_.cursorBlink;
13616 return;
13617 }
13618
13619 if (this.cursorNode_.getAttribute('focus') == 'false' ||
13620 this.cursorNode_.style.opacity == '0') {
13621 this.cursorNode_.style.opacity = '1';
13622 this.timeouts_.cursorBlink = setTimeout(this.myOnCursorBlink_,
13623 this.cursorBlinkCycle_[0]);
13624 } else {
13625 this.cursorNode_.style.opacity = '0';
13626 this.timeouts_.cursorBlink = setTimeout(this.myOnCursorBlink_,
13627 this.cursorBlinkCycle_[1]);
13628 }
13629};
13630
13631/**
13632 * Set the scrollbar-visible mode bit.
13633 *
13634 * If scrollbar-visible is on, the vertical scrollbar will be visible.
13635 * Otherwise it will not.
13636 *
13637 * Defaults to on.
13638 *
13639 * @param {boolean} state True to set scrollbar-visible mode, false to unset.
13640 */
13641hterm.Terminal.prototype.setScrollbarVisible = function(state) {
13642 this.scrollPort_.setScrollbarVisible(state);
13643};
13644
13645/**
13646 * Set the scroll wheel move multiplier. This will affect how fast the page
13647 * scrolls on mousewheel events.
13648 *
13649 * Defaults to 1.
13650 *
13651 * @param {number} multiplier The multiplier to set.
13652 */
13653hterm.Terminal.prototype.setScrollWheelMoveMultipler = function(multiplier) {
13654 this.scrollPort_.setScrollWheelMoveMultipler(multiplier);
13655};
13656
13657/**
13658 * Close all web notifications created by terminal bells.
13659 */
13660hterm.Terminal.prototype.closeBellNotifications_ = function() {
13661 this.bellNotificationList_.forEach(function(n) {
13662 n.close();
13663 });
13664 this.bellNotificationList_.length = 0;
13665};
13666// SOURCE FILE: hterm/js/hterm_terminal_io.js
13667// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
13668// Use of this source code is governed by a BSD-style license that can be
13669// found in the LICENSE file.
13670
13671'use strict';
13672
13673lib.rtdep('lib.encodeUTF8');
13674
13675/**
13676 * Input/Output interface used by commands to communicate with the terminal.
13677 *
13678 * Commands like `nassh` and `crosh` receive an instance of this class as
13679 * part of their argv object. This allows them to write to and read from the
13680 * terminal without exposing them to an entire hterm.Terminal instance.
13681 *
13682 * The active command must override the onVTKeystroke() and sendString() methods
13683 * of this class in order to receive keystrokes and send output to the correct
13684 * destination.
13685 *
13686 * Isolating commands from the terminal provides the following benefits:
13687 * - Provides a mechanism to save and restore onVTKeystroke and sendString
13688 * handlers when invoking subcommands (see the push() and pop() methods).
13689 * - The isolation makes it easier to make changes in Terminal and supporting
13690 * classes without affecting commands.
13691 * - In The Future commands may run in web workers where they would only be able
13692 * to talk to a Terminal instance through an IPC mechanism.
13693 *
13694 * @param {hterm.Terminal}
13695 */
13696hterm.Terminal.IO = function(terminal) {
13697 this.terminal_ = terminal;
13698
13699 // The IO object to restore on IO.pop().
13700 this.previousIO_ = null;
13701};
13702
13703/**
13704 * Show the terminal overlay for a given amount of time.
13705 *
13706 * The terminal overlay appears in inverse video in a large font, centered
13707 * over the terminal. You should probably keep the overlay message brief,
13708 * since it's in a large font and you probably aren't going to check the size
13709 * of the terminal first.
13710 *
13711 * @param {string} msg The text (not HTML) message to display in the overlay.
13712 * @param {number} opt_timeout The amount of time to wait before fading out
13713 * the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
13714 * stay up forever (or until the next overlay).
13715 */
13716hterm.Terminal.IO.prototype.showOverlay = function(message, opt_timeout) {
13717 this.terminal_.showOverlay(message, opt_timeout);
13718};
13719
13720/**
13721 * Open an frame in the current terminal window, pointed to the specified
13722 * url.
13723 *
13724 * Eventually we'll probably need size/position/decoration options.
13725 * The user should also be able to move/resize the frame.
13726 *
13727 * @param {string} url The URL to load in the frame.
13728 * @param {Object} opt_options Optional frame options. Not implemented.
13729 */
13730hterm.Terminal.IO.prototype.createFrame = function(url, opt_options) {
13731 return new hterm.Frame(this.terminal_, url, opt_options);
13732};
13733
13734/**
13735 * Change the preference profile for the terminal.
13736 *
13737 * @param profileName {string} The name of the preference profile to activate.
13738 */
13739hterm.Terminal.IO.prototype.setTerminalProfile = function(profileName) {
13740 this.terminal_.setProfile(profileName);
13741};
13742
13743/**
13744 * Create a new hterm.Terminal.IO instance and make it active on the Terminal
13745 * object associated with this instance.
13746 *
13747 * This is used to pass control of the terminal IO off to a subcommand. The
13748 * IO.pop() method can be used to restore control when the subcommand completes.
13749 */
13750hterm.Terminal.IO.prototype.push = function() {
13751 var io = new hterm.Terminal.IO(this.terminal_);
13752 io.keyboardCaptured_ = this.keyboardCaptured_;
13753
13754 io.columnCount = this.columnCount;
13755 io.rowCount = this.rowCount;
13756
13757 io.previousIO_ = this.terminal_.io;
13758 this.terminal_.io = io;
13759
13760 return io;
13761};
13762
13763/**
13764 * Restore the Terminal's previous IO object.
13765 */
13766hterm.Terminal.IO.prototype.pop = function() {
13767 this.terminal_.io = this.previousIO_;
13768};
13769
13770/**
13771 * Called when data needs to be sent to the current command.
13772 *
13773 * Clients should override this to receive notification of pending data.
13774 *
13775 * @param {string} string The data to send.
13776 */
13777hterm.Terminal.IO.prototype.sendString = function(string) {
13778 // Override this.
13779 console.log('Unhandled sendString: ' + string);
13780};
13781
13782/**
13783 * Called when a terminal keystroke is detected.
13784 *
13785 * Clients should override this to receive notification of keystrokes.
13786 *
13787 * The keystroke data will be encoded according to the 'send-encoding'
13788 * preference.
13789 *
13790 * @param {string} string The VT key sequence.
13791 */
13792hterm.Terminal.IO.prototype.onVTKeystroke = function(string) {
13793 // Override this.
13794 console.log('Unobserverd VT keystroke: ' + JSON.stringify(string));
13795};
13796
13797hterm.Terminal.IO.prototype.onTerminalResize_ = function(width, height) {
13798 var obj = this;
13799 while (obj) {
13800 obj.columnCount = width;
13801 obj.rowCount = height;
13802 obj = obj.previousIO_;
13803 }
13804
13805 this.onTerminalResize(width, height);
13806};
13807
13808/**
13809 * Called when terminal size is changed.
13810 *
13811 * Clients should override this to receive notification of resize.
13812 *
13813 * @param {string|integer} terminal width.
13814 * @param {string|integer} terminal height.
13815 */
13816hterm.Terminal.IO.prototype.onTerminalResize = function(width, height) {
13817 // Override this.
13818};
13819
13820/**
13821 * Write a UTF-8 encoded byte string to the terminal.
13822 *
13823 * @param {string} string The UTF-8 encoded string to print.
13824 */
13825hterm.Terminal.IO.prototype.writeUTF8 = function(string) {
13826 if (this.terminal_.io != this)
13827 throw 'Attempt to print from inactive IO object.';
13828
13829 this.terminal_.interpret(string);
13830};
13831
13832/**
13833 * Write a UTF-8 encoded byte string to the terminal followed by crlf.
13834 *
13835 * @param {string} string The UTF-8 encoded string to print.
13836 */
13837hterm.Terminal.IO.prototype.writelnUTF8 = function(string) {
13838 if (this.terminal_.io != this)
13839 throw 'Attempt to print from inactive IO object.';
13840
13841 this.terminal_.interpret(string + '\r\n');
13842};
13843
13844/**
13845 * Write a UTF-16 JavaScript string to the terminal.
13846 *
13847 * @param {string} string The string to print.
13848 */
13849hterm.Terminal.IO.prototype.print =
13850hterm.Terminal.IO.prototype.writeUTF16 = function(string) {
13851 this.writeUTF8(lib.encodeUTF8(string));
13852};
13853
13854/**
13855 * Print a UTF-16 JavaScript string to the terminal followed by a newline.
13856 *
13857 * @param {string} string The string to print.
13858 */
13859hterm.Terminal.IO.prototype.println =
13860hterm.Terminal.IO.prototype.writelnUTF16 = function(string) {
13861 this.writelnUTF8(lib.encodeUTF8(string));
13862};
13863// SOURCE FILE: hterm/js/hterm_text_attributes.js
13864// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
13865// Use of this source code is governed by a BSD-style license that can be
13866// found in the LICENSE file.
13867
13868'use strict';
13869
13870lib.rtdep('lib.colors');
13871
13872/**
13873 * Constructor for TextAttribute objects.
13874 *
13875 * These objects manage a set of text attributes such as foreground/
13876 * background color, bold, faint, italic, blink, underline, and strikethrough.
13877 *
13878 * TextAttribute instances can be used to construct a DOM container implementing
13879 * the current attributes, or to test an existing DOM container for
13880 * compatibility with the current attributes.
13881 *
13882 * @constructor
13883 * @param {HTMLDocument} document The parent document to use when creating
13884 * new DOM containers.
13885 */
13886hterm.TextAttributes = function(document) {
13887 this.document_ = document;
13888 // These variables contain the source of the color as either:
13889 // SRC_DEFAULT (use context default)
13890 // SRC_RGB (specified in 'rgb( r, g, b)' form)
13891 // number (representing the index from color palette to use)
13892 this.foregroundSource = this.SRC_DEFAULT;
13893 this.backgroundSource = this.SRC_DEFAULT;
13894
13895 // These properties cache the value in the color table, but foregroundSource
13896 // and backgroundSource contain the canonical values.
13897 this.foreground = this.DEFAULT_COLOR;
13898 this.background = this.DEFAULT_COLOR;
13899
13900 this.defaultForeground = 'rgb(255, 255, 255)';
13901 this.defaultBackground = 'rgb(0, 0, 0)';
13902
13903 this.bold = false;
13904 this.faint = false;
13905 this.italic = false;
13906 this.blink = false;
13907 this.underline = false;
13908 this.strikethrough = false;
13909 this.inverse = false;
13910 this.invisible = false;
13911 this.wcNode = false;
13912 this.tileData = null;
13913
13914 this.colorPalette = null;
13915 this.resetColorPalette();
13916};
13917
13918/**
13919 * If false, we ignore the bold attribute.
13920 *
13921 * This is used for fonts that have a bold version that is a different size
13922 * than the normal weight version.
13923 */
13924hterm.TextAttributes.prototype.enableBold = true;
13925
13926/**
13927 * If true, use bright colors (if available) for bold text.
13928 *
13929 * This setting is independent of the enableBold setting.
13930 */
13931hterm.TextAttributes.prototype.enableBoldAsBright = true;
13932
13933/**
13934 * A sentinel constant meaning "whatever the default color is in this context".
13935 */
13936hterm.TextAttributes.prototype.DEFAULT_COLOR = new String('');
13937
13938/**
13939 * A constant string used to specify that source color is context default.
13940 */
13941hterm.TextAttributes.prototype.SRC_DEFAULT = 'default';
13942
13943
13944/**
13945 * A constant string used to specify that the source of a color is a valid
13946 * rgb( r, g, b) specifier.
13947 */
13948hterm.TextAttributes.prototype.SRC_RGB = 'rgb';
13949
13950/**
13951 * The document object which should own the DOM nodes created by this instance.
13952 *
13953 * @param {HTMLDocument} document The parent document.
13954 */
13955hterm.TextAttributes.prototype.setDocument = function(document) {
13956 this.document_ = document;
13957};
13958
13959/**
13960 * Create a deep copy of this object.
13961 *
13962 * @return {hterm.TextAttributes} A deep copy of this object.
13963 */
13964hterm.TextAttributes.prototype.clone = function() {
13965 var rv = new hterm.TextAttributes(null);
13966
13967 for (var key in this) {
13968 rv[key] = this[key];
13969 }
13970
13971 rv.colorPalette = this.colorPalette.concat();
13972 return rv;
13973};
13974
13975/**
13976 * Reset the current set of attributes.
13977 *
13978 * This does not affect the palette. Use resetColorPalette() for that.
13979 * It also doesn't affect the tile data, it's not meant to.
13980 */
13981hterm.TextAttributes.prototype.reset = function() {
13982 this.foregroundSource = this.SRC_DEFAULT;
13983 this.backgroundSource = this.SRC_DEFAULT;
13984 this.foreground = this.DEFAULT_COLOR;
13985 this.background = this.DEFAULT_COLOR;
13986 this.bold = false;
13987 this.faint = false;
13988 this.italic = false;
13989 this.blink = false;
13990 this.underline = false;
13991 this.strikethrough = false;
13992 this.inverse = false;
13993 this.invisible = false;
13994 this.wcNode = false;
13995};
13996
13997/**
13998 * Reset the color palette to the default state.
13999 */
14000hterm.TextAttributes.prototype.resetColorPalette = function() {
14001 this.colorPalette = lib.colors.colorPalette.concat();
14002 this.syncColors();
14003};
14004
14005/**
14006 * Test if the current attributes describe unstyled text.
14007 *
14008 * @return {boolean} True if the current attributes describe unstyled text.
14009 */
14010hterm.TextAttributes.prototype.isDefault = function() {
14011 return (this.foregroundSource == this.SRC_DEFAULT &&
14012 this.backgroundSource == this.SRC_DEFAULT &&
14013 !this.bold &&
14014 !this.faint &&
14015 !this.italic &&
14016 !this.blink &&
14017 !this.underline &&
14018 !this.strikethrough &&
14019 !this.inverse &&
14020 !this.invisible &&
14021 !this.wcNode &&
14022 this.tileData == null);
14023};
14024
14025/**
14026 * Create a DOM container (a span or a text node) with a style to match the
14027 * current set of attributes.
14028 *
14029 * This method will create a plain text node if the text is unstyled, or
14030 * an HTML span if the text is styled. Due to lack of monospace wide character
14031 * fonts on certain systems (e.g. Chrome OS), we need to put each wide character
14032 * in a span of CSS class '.wc-node' which has double column width.
14033 * Each vt_tiledata tile is also represented by a span with a single
14034 * character, with CSS classes '.tile' and '.tile_<glyph number>'.
14035 *
14036 * @param {string} opt_textContent Optional text content for the new container.
14037 * @return {HTMLNode} An HTML span or text nodes styled to match the current
14038 * attributes.
14039 */
14040hterm.TextAttributes.prototype.createContainer = function(opt_textContent) {
14041 if (this.isDefault())
14042 return this.document_.createTextNode(opt_textContent);
14043
14044 var span = this.document_.createElement('span');
14045 var style = span.style;
14046 var classes = [];
14047
14048 if (this.foreground != this.DEFAULT_COLOR)
14049 style.color = this.foreground;
14050
14051 if (this.background != this.DEFAULT_COLOR)
14052 style.backgroundColor = this.background;
14053
14054 if (this.enableBold && this.bold)
14055 style.fontWeight = 'bold';
14056
14057 if (this.faint)
14058 span.faint = true;
14059
14060 if (this.italic)
14061 style.fontStyle = 'italic';
14062
14063 if (this.blink) {
14064 classes.push('blink-node');
14065 span.blinkNode = true;
14066 }
14067
14068 var textDecoration = '';
14069 if (this.underline) {
14070 textDecoration += ' underline';
14071 span.underline = true;
14072 }
14073 if (this.strikethrough) {
14074 textDecoration += ' line-through';
14075 span.strikethrough = true;
14076 }
14077 if (textDecoration) {
14078 style.textDecoration = textDecoration;
14079 }
14080
14081 if (this.wcNode) {
14082 classes.push('wc-node');
14083 span.wcNode = true;
14084 }
14085
14086 if (this.tileData != null) {
14087 classes.push('tile');
14088 classes.push('tile_' + this.tileData);
14089 span.tileNode = true;
14090 }
14091
14092 if (opt_textContent)
14093 span.textContent = opt_textContent;
14094
14095 if (classes.length)
14096 span.className = classes.join(' ');
14097
14098 return span;
14099};
14100
14101/**
14102 * Tests if the provided object (string, span or text node) has the same
14103 * style as this TextAttributes instance.
14104 *
14105 * This indicates that text with these attributes could be inserted directly
14106 * into the target DOM node.
14107 *
14108 * For the purposes of this method, a string is considered a text node.
14109 *
14110 * @param {string|HTMLNode} obj The object to test.
14111 * @return {boolean} True if the provided container has the same style as
14112 * this attributes instance.
14113 */
14114hterm.TextAttributes.prototype.matchesContainer = function(obj) {
14115 if (typeof obj == 'string' || obj.nodeType == 3)
14116 return this.isDefault();
14117
14118 var style = obj.style;
14119
14120 // We don't want to put multiple characters in a wcNode or a tile.
14121 // See the comments in createContainer.
14122 return (!(this.wcNode || obj.wcNode) &&
14123 !(this.tileData != null || obj.tileNode) &&
14124 this.foreground == style.color &&
14125 this.background == style.backgroundColor &&
14126 (this.enableBold && this.bold) == !!style.fontWeight &&
14127 this.blink == obj.blinkNode &&
14128 this.italic == !!style.fontStyle &&
14129 !!this.underline == !!obj.underline &&
14130 !!this.strikethrough == !!obj.strikethrough);
14131};
14132
14133hterm.TextAttributes.prototype.setDefaults = function(foreground, background) {
14134 this.defaultForeground = foreground;
14135 this.defaultBackground = background;
14136
14137 this.syncColors();
14138};
14139
14140/**
14141 * Updates foreground and background properties based on current indices and
14142 * other state.
14143 *
14144 * @param {string} terminalForeground The terminal foreground color for use as
14145 * inverse text background.
14146 * @param {string} terminalBackground The terminal background color for use as
14147 * inverse text foreground.
14148 *
14149 */
14150hterm.TextAttributes.prototype.syncColors = function() {
14151 function getBrightIndex(i) {
14152 if (i < 8) {
14153 // If the color is from the lower half of the ANSI 16, add 8.
14154 return i + 8;
14155 }
14156
14157 // If it's not from the 16 color palette, ignore bold requests. This
14158 // matches the behavior of gnome-terminal.
14159 return i;
14160 }
14161
14162 var foregroundSource = this.foregroundSource;
14163 var backgroundSource = this.backgroundSource;
14164 var defaultForeground = this.DEFAULT_COLOR;
14165 var defaultBackground = this.DEFAULT_COLOR;
14166
14167 if (this.inverse) {
14168 foregroundSource = this.backgroundSource;
14169 backgroundSource = this.foregroundSource;
14170 // We can't inherit the container's color anymore.
14171 defaultForeground = this.defaultBackground;
14172 defaultBackground = this.defaultForeground;
14173 }
14174
14175 if (this.enableBoldAsBright && this.bold) {
14176 if (foregroundSource != this.SRC_DEFAULT &&
14177 foregroundSource != this.SRC_RGB) {
14178 foregroundSource = getBrightIndex(foregroundSource);
14179 }
14180 }
14181
14182 if (this.invisible) {
14183 foregroundSource = backgroundSource;
14184 defaultForeground = this.defaultBackground;
14185 }
14186
14187 // Set fore/background colors unless already specified in rgb(r, g, b) form.
14188 if (foregroundSource != this.SRC_RGB) {
14189 this.foreground = ((foregroundSource == this.SRC_DEFAULT) ?
14190 defaultForeground : this.colorPalette[foregroundSource]);
14191 }
14192
14193 if (this.faint && !this.invisible) {
14194 var colorToMakeFaint = ((this.foreground == this.DEFAULT_COLOR) ?
14195 this.defaultForeground : this.foreground);
14196 this.foreground = lib.colors.mix(colorToMakeFaint, 'rgb(0, 0, 0)', 0.3333);
14197 }
14198
14199 if (backgroundSource != this.SRC_RGB) {
14200 this.background = ((backgroundSource == this.SRC_DEFAULT) ?
14201 defaultBackground : this.colorPalette[backgroundSource]);
14202 }
14203};
14204
14205/**
14206 * Static method used to test if the provided objects (strings, spans or
14207 * text nodes) have the same style.
14208 *
14209 * For the purposes of this method, a string is considered a text node.
14210 *
14211 * @param {string|HTMLNode} obj1 An object to test.
14212 * @param {string|HTMLNode} obj2 Another object to test.
14213 * @return {boolean} True if the containers have the same style.
14214 */
14215hterm.TextAttributes.containersMatch = function(obj1, obj2) {
14216 if (typeof obj1 == 'string')
14217 return hterm.TextAttributes.containerIsDefault(obj2);
14218
14219 if (obj1.nodeType != obj2.nodeType)
14220 return false;
14221
14222 if (obj1.nodeType == 3)
14223 return true;
14224
14225 var style1 = obj1.style;
14226 var style2 = obj2.style;
14227
14228 return (style1.color == style2.color &&
14229 style1.backgroundColor == style2.backgroundColor &&
14230 style1.fontWeight == style2.fontWeight &&
14231 style1.fontStyle == style2.fontStyle &&
14232 style1.textDecoration == style2.textDecoration);
14233};
14234
14235/**
14236 * Static method to test if a given DOM container represents unstyled text.
14237 *
14238 * For the purposes of this method, a string is considered a text node.
14239 *
14240 * @param {string|HTMLNode} obj1 An object to test.
14241 * @return {boolean} True if the object is unstyled.
14242 */
14243hterm.TextAttributes.containerIsDefault = function(obj) {
14244 return typeof obj == 'string' || obj.nodeType == 3;
14245};
14246
14247/**
14248 * Static method to get the column width of a node's textContent.
14249 *
14250 * @param {HTMLElement} node The HTML element to get the width of textContent
14251 * from.
14252 * @return {integer} The column width of the node's textContent.
14253 */
14254hterm.TextAttributes.nodeWidth = function(node) {
14255 if (node.wcNode) {
14256 return lib.wc.strWidth(node.textContent);
14257 } else {
14258 return node.textContent.length;
14259 }
14260}
14261
14262/**
14263 * Static method to get the substr of a node's textContent. The start index
14264 * and substr width are computed in column width.
14265 *
14266 * @param {HTMLElement} node The HTML element to get the substr of textContent
14267 * from.
14268 * @param {integer} start The starting offset in column width.
14269 * @param {integer} width The width to capture in column width.
14270 * @return {integer} The extracted substr of the node's textContent.
14271 */
14272hterm.TextAttributes.nodeSubstr = function(node, start, width) {
14273 if (node.wcNode) {
14274 return lib.wc.substr(node.textContent, start, width);
14275 } else {
14276 return node.textContent.substr(start, width);
14277 }
14278}
14279
14280/**
14281 * Static method to get the substring based of a node's textContent. The
14282 * start index of end index are computed in column width.
14283 *
14284 * @param {HTMLElement} node The HTML element to get the substr of textContent
14285 * from.
14286 * @param {integer} start The starting offset in column width.
14287 * @param {integer} end The ending offset in column width.
14288 * @return {integer} The extracted substring of the node's textContent.
14289 */
14290hterm.TextAttributes.nodeSubstring = function(node, start, end) {
14291 if (node.wcNode) {
14292 return lib.wc.substring(node.textContent, start, end);
14293 } else {
14294 return node.textContent.substring(start, end);
14295 }
14296};
14297
14298/**
14299 * Static method to split a string into contiguous runs of single-width
14300 * characters and runs of double-width characters.
14301 *
14302 * @param {string} str The string to split.
14303 * @return {Array} An array of objects that contain substrings of str, where
14304 * each substring is either a contiguous runs of single-width characters
14305 * or a double-width character. For object that contains a double-width
14306 * character, its wcNode property is set to true.
14307 */
14308hterm.TextAttributes.splitWidecharString = function(str) {
14309 var rv = [];
14310 var base = 0, length = 0;
14311
14312 for (var i = 0; i < str.length;) {
14313 var c = str.codePointAt(i);
14314 var increment = (c <= 0xffff) ? 1 : 2;
14315 if (c < 128 || lib.wc.charWidth(c) == 1) {
14316 length += increment;
14317 } else {
14318 if (length) {
14319 rv.push({str: str.substr(base, length)});
14320 }
14321 rv.push({str: str.substr(i, increment), wcNode: true});
14322 base = i + increment;
14323 length = 0;
14324 }
14325 i += increment;
14326 }
14327
14328 if (length)
14329 rv.push({str: str.substr(base, length)});
14330
14331 return rv;
14332};
14333// SOURCE FILE: hterm/js/hterm_vt.js
14334// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
14335// Use of this source code is governed by a BSD-style license that can be
14336// found in the LICENSE file.
14337
14338'use strict';
14339
14340lib.rtdep('lib.colors', 'lib.f', 'lib.UTF8Decoder',
14341 'hterm.VT.CharacterMap');
14342
14343/**
14344 * Constructor for the VT escape sequence interpreter.
14345 *
14346 * The interpreter operates on a terminal object capable of performing cursor
14347 * move operations, painting characters, etc.
14348 *
14349 * This interpreter is intended to be compatible with xterm, though it
14350 * ignores some of the more esoteric escape sequences.
14351 *
14352 * Some sequences are marked "Will not implement", meaning that they aren't
14353 * considered relevant to hterm and will probably never be implemented.
14354 *
14355 * Others are marked "Not currently implemented", meaning that they are lower
14356 * priority items that may be useful to implement at some point.
14357 *
14358 * See also:
14359 * [VT100] VT100 User Guide
14360 * http://vt100.net/docs/vt100-ug/chapter3.html
14361 * [VT510] VT510 Video Terminal Programmer Information
14362 * http://vt100.net/docs/vt510-rm/contents
14363 * [XTERM] Xterm Control Sequences
14364 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
14365 * [CTRL] Wikipedia: C0 and C1 Control Codes
14366 * https://en.wikipedia.org/wiki/C0_and_C1_control_codes
14367 * [CSI] Wikipedia: ANSI Escape Code
14368 * https://en.wikipedia.org/wiki/Control_Sequence_Introducer
14369 * man 5 terminfo, man infocmp, infocmp -L xterm-new
14370 *
14371 * @param {hterm.Terminal} terminal Terminal to use with the interpreter.
14372 */
14373hterm.VT = function(terminal) {
14374 /**
14375 * The display terminal object associated with this virtual terminal.
14376 */
14377 this.terminal = terminal;
14378
14379 terminal.onMouse = this.onTerminalMouse_.bind(this);
14380 this.mouseReport = this.MOUSE_REPORT_DISABLED;
14381
14382 // Parse state left over from the last parse. You should use the parseState
14383 // instance passed into your parse routine, rather than reading
14384 // this.parseState_ directly.
14385 this.parseState_ = new hterm.VT.ParseState(this.parseUnknown_);
14386
14387 // Any "leading modifiers" for the escape sequence, such as '?', ' ', or the
14388 // other modifiers handled in this.parseCSI_.
14389 this.leadingModifier_ = '';
14390
14391 // Any "trailing modifiers". Same character set as a leading modifier,
14392 // except these are found after the numeric arguments.
14393 this.trailingModifier_ = '';
14394
14395 // Whether or not to respect the escape codes for setting terminal width.
14396 this.allowColumnWidthChanges_ = false;
14397
14398 // The amount of time we're willing to wait for the end of an OSC sequence.
14399 this.oscTimeLimit_ = 20000;
14400
14401 // Construct a regular expression to match the known one-byte control chars.
14402 // This is used in parseUnknown_ to quickly scan a string for the next
14403 // control character.
14404 var cc1 = Object.keys(hterm.VT.CC1).map(
14405 function(e) {
14406 return '\\x' + lib.f.zpad(e.charCodeAt().toString(16), 2)
14407 }).join('');
14408 this.cc1Pattern_ = new RegExp('[' + cc1 + ']');
14409
14410 // Decoder to maintain UTF-8 decode state.
14411 this.utf8Decoder_ = new lib.UTF8Decoder();
14412
14413 /**
14414 * Whether to accept the 8-bit control characters.
14415 *
14416 * An 8-bit control character is one with the eighth bit set. These
14417 * didn't work on 7-bit terminals so they all have two byte equivalents.
14418 * Most hosts still only use the two-byte versions.
14419 *
14420 * We ignore 8-bit control codes by default. This is in order to avoid
14421 * issues with "accidental" usage of codes that need to be terminated.
14422 * The "accident" usually involves cat'ing binary data.
14423 */
14424 this.enable8BitControl = false;
14425
14426 /**
14427 * Whether to allow the OSC 52 sequence to write to the system clipboard.
14428 */
14429 this.enableClipboardWrite = true;
14430
14431 /**
14432 * Respect the host's attempt to change the cursor blink status using
14433 * the DEC Private mode 12.
14434 */
14435 this.enableDec12 = false;
14436
14437 /**
14438 * The expected encoding method for data received from the host.
14439 */
14440 this.characterEncoding = 'utf-8';
14441
14442 /**
14443 * Max length of an unterminated DCS, OSC, PM or APC sequence before we give
14444 * up and ignore the code.
14445 *
14446 * These all end with a String Terminator (ST, '\x9c', ESC '\\') or
14447 * (BEL, '\x07') character, hence the "string sequence" moniker.
14448 */
14449 this.maxStringSequence = 1024;
14450
14451 /**
14452 * If true, emit warnings when we encounter a control character or escape
14453 * sequence that we don't recognize or explicitly ignore.
14454 */
14455 this.warnUnimplemented = true;
14456
14457 /**
14458 * The default G0...G3 character maps.
14459 */
14460 this.G0 = hterm.VT.CharacterMap.maps['B'];
14461 this.G1 = hterm.VT.CharacterMap.maps['0'];
14462 this.G2 = hterm.VT.CharacterMap.maps['B'];
14463 this.G3 = hterm.VT.CharacterMap.maps['B'];
14464
14465 /**
14466 * The 7-bit visible character set.
14467 *
14468 * This is a mapping from inbound data to display glyph. The GL set
14469 * contains the 94 bytes from 0x21 to 0x7e.
14470 *
14471 * The default GL set is 'B', US ASCII.
14472 */
14473 this.GL = 'G0';
14474
14475 /**
14476 * The 8-bit visible character set.
14477 *
14478 * This is a mapping from inbound data to display glyph. The GR set
14479 * contains the 94 bytes from 0xa1 to 0xfe.
14480 */
14481 this.GR = 'G0';
14482
14483 // Saved state used in DECSC.
14484 //
14485 // This is a place to store a copy VT state, it is *not* the active state.
14486 this.savedState_ = new hterm.VT.CursorState(this);
14487};
14488
14489/**
14490 * No mouse events.
14491 */
14492hterm.VT.prototype.MOUSE_REPORT_DISABLED = 0;
14493
14494/**
14495 * DECSET mode 1000.
14496 *
14497 * Report mouse down/up events only.
14498 */
14499hterm.VT.prototype.MOUSE_REPORT_CLICK = 1;
14500
14501/**
14502 * DECSET mode 1002.
14503 *
14504 * Report mouse down/up and movement while a button is down.
14505 */
14506hterm.VT.prototype.MOUSE_REPORT_DRAG = 3;
14507
14508/**
14509 * ParseState constructor.
14510 *
14511 * This object tracks the current state of the parse. It has fields for the
14512 * current buffer, position in the buffer, and the parse function.
14513 *
14514 * @param {function} defaultFunc The default parser function.
14515 * @param {string} opt_buf Optional string to use as the current buffer.
14516 */
14517hterm.VT.ParseState = function(defaultFunction, opt_buf) {
14518 this.defaultFunction = defaultFunction;
14519 this.buf = opt_buf || null;
14520 this.pos = 0;
14521 this.func = defaultFunction;
14522 this.args = [];
14523};
14524
14525/**
14526 * Reset the parser function, buffer, and position.
14527 */
14528hterm.VT.ParseState.prototype.reset = function(opt_buf) {
14529 this.resetParseFunction();
14530 this.resetBuf(opt_buf || '');
14531 this.resetArguments();
14532};
14533
14534/**
14535 * Reset the parser function only.
14536 */
14537hterm.VT.ParseState.prototype.resetParseFunction = function() {
14538 this.func = this.defaultFunction;
14539};
14540
14541/**
14542 * Reset the buffer and position only.
14543 *
14544 * @param {string} buf Optional new value for buf, defaults to null.
14545 */
14546hterm.VT.ParseState.prototype.resetBuf = function(opt_buf) {
14547 this.buf = (typeof opt_buf == 'string') ? opt_buf : null;
14548 this.pos = 0;
14549};
14550
14551/**
14552 * Reset the arguments list only.
14553 *
14554 * @param {string} opt_arg_zero Optional initial value for args[0].
14555 */
14556hterm.VT.ParseState.prototype.resetArguments = function(opt_arg_zero) {
14557 this.args.length = 0;
14558 if (typeof opt_arg_zero != 'undefined')
14559 this.args[0] = opt_arg_zero;
14560};
14561
14562/**
14563 * Get an argument as an integer.
14564 *
14565 * @param {number} argnum The argument number to retrieve.
14566 */
14567hterm.VT.ParseState.prototype.iarg = function(argnum, defaultValue) {
14568 var str = this.args[argnum];
14569 if (str) {
14570 var ret = parseInt(str, 10);
14571 // An argument of zero is treated as the default value.
14572 if (ret == 0)
14573 ret = defaultValue;
14574 return ret;
14575 }
14576 return defaultValue;
14577};
14578
14579/**
14580 * Advance the parse position.
14581 *
14582 * @param {integer} count The number of bytes to advance.
14583 */
14584hterm.VT.ParseState.prototype.advance = function(count) {
14585 this.pos += count;
14586};
14587
14588/**
14589 * Return the remaining portion of the buffer without affecting the parse
14590 * position.
14591 *
14592 * @return {string} The remaining portion of the buffer.
14593 */
14594hterm.VT.ParseState.prototype.peekRemainingBuf = function() {
14595 return this.buf.substr(this.pos);
14596};
14597
14598/**
14599 * Return the next single character in the buffer without affecting the parse
14600 * position.
14601 *
14602 * @return {string} The next character in the buffer.
14603 */
14604hterm.VT.ParseState.prototype.peekChar = function() {
14605 return this.buf.substr(this.pos, 1);
14606};
14607
14608/**
14609 * Return the next single character in the buffer and advance the parse
14610 * position one byte.
14611 *
14612 * @return {string} The next character in the buffer.
14613 */
14614hterm.VT.ParseState.prototype.consumeChar = function() {
14615 return this.buf.substr(this.pos++, 1);
14616};
14617
14618/**
14619 * Return true if the buffer is empty, or the position is past the end.
14620 */
14621hterm.VT.ParseState.prototype.isComplete = function() {
14622 return this.buf == null || this.buf.length <= this.pos;
14623};
14624
14625hterm.VT.CursorState = function(vt) {
14626 this.vt_ = vt;
14627 this.save();
14628};
14629
14630hterm.VT.CursorState.prototype.save = function() {
14631 this.cursor = this.vt_.terminal.saveCursor();
14632
14633 this.textAttributes = this.vt_.terminal.getTextAttributes().clone();
14634
14635 this.GL = this.vt_.GL;
14636 this.GR = this.vt_.GR;
14637
14638 this.G0 = this.vt_.G0;
14639 this.G1 = this.vt_.G1;
14640 this.G2 = this.vt_.G2;
14641 this.G3 = this.vt_.G3;
14642};
14643
14644hterm.VT.CursorState.prototype.restore = function() {
14645 this.vt_.terminal.restoreCursor(this.cursor);
14646
14647 this.vt_.terminal.setTextAttributes(this.textAttributes.clone());
14648
14649 this.vt_.GL = this.GL;
14650 this.vt_.GR = this.GR;
14651
14652 this.vt_.G0 = this.G0;
14653 this.vt_.G1 = this.G1;
14654 this.vt_.G2 = this.G2;
14655 this.vt_.G3 = this.G3;
14656};
14657
14658hterm.VT.prototype.reset = function() {
14659 this.G0 = hterm.VT.CharacterMap.maps['B'];
14660 this.G1 = hterm.VT.CharacterMap.maps['0'];
14661 this.G2 = hterm.VT.CharacterMap.maps['B'];
14662 this.G3 = hterm.VT.CharacterMap.maps['B'];
14663
14664 this.GL = 'G0';
14665 this.GR = 'G0';
14666
14667 this.savedState_ = new hterm.VT.CursorState(this);
14668
14669 this.mouseReport = this.MOUSE_REPORT_DISABLED;
14670};
14671
14672/**
14673 * Handle terminal mouse events.
14674 *
14675 * See the "Mouse Tracking" section of [xterm].
14676 */
14677hterm.VT.prototype.onTerminalMouse_ = function(e) {
14678 if (this.mouseReport == this.MOUSE_REPORT_DISABLED)
14679 return;
14680
14681 // Temporary storage for our response.
14682 var response;
14683
14684 // Modifier key state.
14685 var mod = 0;
14686 if (e.shiftKey)
14687 mod |= 4;
14688 if (e.metaKey || (this.terminal.keyboard.altIsMeta && e.altKey))
14689 mod |= 8;
14690 if (e.ctrlKey)
14691 mod |= 16;
14692
14693 // TODO(rginda): We should also support mode 1005 and/or 1006 to extend the
14694 // coordinate space. Though, after poking around just a little, I wasn't
14695 // able to get vi or emacs to use either of these modes.
14696 var x = String.fromCharCode(lib.f.clamp(e.terminalColumn + 32, 32, 255));
14697 var y = String.fromCharCode(lib.f.clamp(e.terminalRow + 32, 32, 255));
14698
14699 switch (e.type) {
14700 case 'mousewheel':
14701 // Mouse wheel is treated as button 1 or 2 plus an additional 64.
14702 b = ((e.wheelDeltaY > 0) ? 0 : 1) + 96;
14703 b |= mod;
14704 response = '\x1b[M' + String.fromCharCode(b) + x + y;
14705
14706 // Keep the terminal from scrolling.
14707 e.preventDefault();
14708 break;
14709
14710 case 'mousedown':
14711 // Buttons are encoded as button number plus 32.
14712 var b = Math.min(e.which - 1, 2) + 32;
14713
14714 // And mix in the modifier keys.
14715 b |= mod;
14716
14717 response = '\x1b[M' + String.fromCharCode(b) + x + y;
14718 break;
14719
14720 case 'mouseup':
14721 // Mouse up has no indication of which button was released.
14722 response = '\x1b[M\x23' + x + y;
14723 break;
14724
14725 case 'mousemove':
14726 if (this.mouseReport == this.MOUSE_REPORT_DRAG && e.which) {
14727 // Standard button bits.
14728 b = 32 + Math.min(e.which - 1, 2);
14729
14730 // Add 32 to indicate mouse motion.
14731 b += 32;
14732
14733 // And mix in the modifier keys.
14734 b |= mod;
14735
14736 response = '\x1b[M' + String.fromCharCode(b) + x + y;
14737 }
14738
14739 break;
14740
14741 case 'click':
14742 case 'dblclick':
14743 break;
14744
14745 default:
14746 console.error('Unknown mouse event: ' + e.type, e);
14747 break;
14748 }
14749
14750 if (response)
14751 this.terminal.io.sendString(response);
14752};
14753
14754/**
14755 * Interpret a string of characters, displaying the results on the associated
14756 * terminal object.
14757 *
14758 * The buffer will be decoded according to the 'receive-encoding' preference.
14759 */
14760hterm.VT.prototype.interpret = function(buf) {
14761 this.parseState_.resetBuf(this.decode(buf));
14762
14763 while (!this.parseState_.isComplete()) {
14764 var func = this.parseState_.func;
14765 var pos = this.parseState_.pos;
14766 var buf = this.parseState_.buf;
14767
14768 this.parseState_.func.call(this, this.parseState_);
14769
14770 if (this.parseState_.func == func && this.parseState_.pos == pos &&
14771 this.parseState_.buf == buf) {
14772 throw 'Parser did not alter the state!';
14773 }
14774 }
14775};
14776
14777/**
14778 * Decode a string according to the 'receive-encoding' preference.
14779 */
14780hterm.VT.prototype.decode = function(str) {
14781 if (this.characterEncoding == 'utf-8')
14782 return this.decodeUTF8(str);
14783
14784 return str;
14785};
14786
14787/**
14788 * Encode a UTF-16 string as UTF-8.
14789 *
14790 * See also: https://en.wikipedia.org/wiki/UTF-16
14791 */
14792hterm.VT.prototype.encodeUTF8 = function(str) {
14793 return lib.encodeUTF8(str);
14794};
14795
14796/**
14797 * Decode a UTF-8 string into UTF-16.
14798 */
14799hterm.VT.prototype.decodeUTF8 = function(str) {
14800 return this.utf8Decoder_.decode(str);
14801};
14802
14803/**
14804 * The default parse function.
14805 *
14806 * This will scan the string for the first 1-byte control character (C0/C1
14807 * characters from [CTRL]). Any plain text coming before the code will be
14808 * printed to the terminal, then the control character will be dispatched.
14809 */
14810hterm.VT.prototype.parseUnknown_ = function(parseState) {
14811 var self = this;
14812
14813 function print(str) {
14814 if (self[self.GL].GL)
14815 str = self[self.GL].GL(str);
14816
14817 if (self[self.GR].GR)
14818 str = self[self.GR].GR(str);
14819
14820 self.terminal.print(str);
14821 };
14822
14823 // Search for the next contiguous block of plain text.
14824 var buf = parseState.peekRemainingBuf();
14825 var nextControl = buf.search(this.cc1Pattern_);
14826
14827 if (nextControl == 0) {
14828 // We've stumbled right into a control character.
14829 this.dispatch('CC1', buf.substr(0, 1), parseState);
14830 parseState.advance(1);
14831 return;
14832 }
14833
14834 if (nextControl == -1) {
14835 // There are no control characters in this string.
14836 print(buf);
14837 parseState.reset();
14838 return;
14839 }
14840
14841 print(buf.substr(0, nextControl));
14842 this.dispatch('CC1', buf.substr(nextControl, 1), parseState);
14843 parseState.advance(nextControl + 1);
14844};
14845
14846/**
14847 * Parse a Control Sequence Introducer code and dispatch it.
14848 *
14849 * See [CSI] for some useful information about these codes.
14850 */
14851hterm.VT.prototype.parseCSI_ = function(parseState) {
14852 var ch = parseState.peekChar();
14853 var args = parseState.args;
14854
14855 if (ch >= '@' && ch <= '~') {
14856 // This is the final character.
14857 this.dispatch('CSI', this.leadingModifier_ + this.trailingModifier_ + ch,
14858 parseState);
14859 parseState.resetParseFunction();
14860
14861 } else if (ch == ';') {
14862 // Parameter delimiter.
14863 if (this.trailingModifier_) {
14864 // Parameter delimiter after the trailing modifier. That's a paddlin'.
14865 parseState.resetParseFunction();
14866
14867 } else {
14868 if (!args.length) {
14869 // They omitted the first param, we need to supply it.
14870 args.push('');
14871 }
14872
14873 args.push('');
14874 }
14875
14876 } else if (ch >= '0' && ch <= '9') {
14877 // Next byte in the current parameter.
14878
14879 if (this.trailingModifier_) {
14880 // Numeric parameter after the trailing modifier. That's a paddlin'.
14881 parseState.resetParseFunction();
14882 } else {
14883 if (!args.length) {
14884 args[0] = ch;
14885 } else {
14886 args[args.length - 1] += ch;
14887 }
14888 }
14889
14890 } else if (ch >= ' ' && ch <= '?' && ch != ':') {
14891 // Modifier character.
14892 if (!args.length) {
14893 this.leadingModifier_ += ch;
14894 } else {
14895 this.trailingModifier_ += ch;
14896 }
14897
14898 } else if (this.cc1Pattern_.test(ch)) {
14899 // Control character.
14900 this.dispatch('CC1', ch, parseState);
14901
14902 } else {
14903 // Unexpected character in sequence, bail out.
14904 parseState.resetParseFunction();
14905 }
14906
14907 parseState.advance(1);
14908};
14909
14910/**
14911 * Skip over the string until the next String Terminator (ST, 'ESC \') or
14912 * Bell (BEL, '\x07').
14913 *
14914 * The string is accumulated in parseState.args[0]. Make sure to reset the
14915 * arguments (with parseState.resetArguments) before starting the parse.
14916 *
14917 * You can detect that parsing in complete by checking that the parse
14918 * function has changed back to the default parse function.
14919 *
14920 * If we encounter more than maxStringSequence characters, we send back
14921 * the unterminated sequence to be re-parsed with the default parser function.
14922 *
14923 * @return {boolean} If true, parsing is ongoing or complete. If false, we've
14924 * exceeded the max string sequence.
14925 */
14926hterm.VT.prototype.parseUntilStringTerminator_ = function(parseState) {
14927 var buf = parseState.peekRemainingBuf();
14928 var nextTerminator = buf.search(/(\x1b\\|\x07)/);
14929 var args = parseState.args;
14930
14931 if (!args.length) {
14932 args[0] = '';
14933 args[1] = new Date();
14934 }
14935
14936 if (nextTerminator == -1) {
14937 // No terminator here, have to wait for the next string.
14938
14939 args[0] += buf;
14940
14941 var abortReason;
14942
14943 if (args[0].length > this.maxStringSequence)
14944 abortReason = 'too long: ' + args[0].length;
14945
14946 if (args[0].indexOf('\x1b') != -1)
14947 abortReason = 'embedded escape: ' + args[0].indexOf('\x1b');
14948
14949 if (new Date() - args[1] > this.oscTimeLimit_)
14950 abortReason = 'timeout expired: ' + new Date() - args[1];
14951
14952 if (abortReason) {
14953 console.log('parseUntilStringTerminator_: aborting: ' + abortReason,
14954 args[0]);
14955 parseState.reset(args[0]);
14956 return false;
14957 }
14958
14959 parseState.advance(buf.length);
14960 return true;
14961 }
14962
14963 if (args[0].length + nextTerminator > this.maxStringSequence) {
14964 // We found the end of the sequence, but we still think it's too long.
14965 parseState.reset(args[0] + buf);
14966 return false;
14967 }
14968
14969 args[0] += buf.substr(0, nextTerminator);
14970
14971 parseState.resetParseFunction();
14972 parseState.advance(nextTerminator +
14973 (buf.substr(nextTerminator, 1) == '\x1b' ? 2 : 1));
14974
14975 return true;
14976};
14977
14978/**
14979 * Dispatch to the function that handles a given CC1, ESC, or CSI or VT52 code.
14980 */
14981hterm.VT.prototype.dispatch = function(type, code, parseState) {
14982 var handler = hterm.VT[type][code];
14983 if (!handler) {
14984 if (this.warnUnimplemented)
14985 console.warn('Unknown ' + type + ' code: ' + JSON.stringify(code));
14986 return;
14987 }
14988
14989 if (handler == hterm.VT.ignore) {
14990 if (this.warnUnimplemented)
14991 console.warn('Ignored ' + type + ' code: ' + JSON.stringify(code));
14992 return;
14993 }
14994
14995 if (type == 'CC1' && code > '\x7f' && !this.enable8BitControl) {
14996 // It's kind of a hack to put this here, but...
14997 //
14998 // If we're dispatching a 'CC1' code, and it's got the eighth bit set,
14999 // but we're not supposed to handle 8-bit codes? Just ignore it.
15000 //
15001 // This prevents an errant (DCS, '\x90'), (OSC, '\x9d'), (PM, '\x9e') or
15002 // (APC, '\x9f') from locking up the terminal waiting for its expected
15003 // (ST, '\x9c') or (BEL, '\x07').
15004 console.warn('Ignoring 8-bit control code: 0x' +
15005 code.charCodeAt(0).toString(16));
15006 return;
15007 }
15008
15009 handler.apply(this, [parseState, code]);
15010};
15011
15012/**
15013 * Set one of the ANSI defined terminal mode bits.
15014 *
15015 * Invoked in response to SM/RM.
15016 *
15017 * Expected values for code:
15018 * 2 - Keyboard Action Mode (AM). Will not implement.
15019 * 4 - Insert Mode (IRM).
15020 * 12 - Send/receive (SRM). Will not implement.
15021 * 20 - Automatic Newline (LNM).
15022 *
15023 * Unexpected and unimplemented values are silently ignored.
15024 */
15025hterm.VT.prototype.setANSIMode = function(code, state) {
15026 if (code == '4') {
15027 this.terminal.setInsertMode(state);
15028 } else if (code == '20') {
15029 this.terminal.setAutoCarriageReturn(state);
15030 } else if (this.warnUnimplemented) {
15031 console.warn('Unimplemented ANSI Mode: ' + code);
15032 }
15033};
15034
15035/**
15036 * Set or reset one of the DEC Private modes.
15037 *
15038 * Invoked in response to DECSET/DECRST.
15039 *
15040 * Expected values for code:
15041 * 1 - Application Cursor Keys (DECCKM).
15042 * 2 - [!] Designate USASCII for character sets G0-G3 (DECANM), and set
15043 * VT100 mode.
15044 * 3 - 132 Column Mode (DECCOLM).
15045 * 4 - [x] Smooth (Slow) Scroll (DECSCLM).
15046 * 5 - Reverse Video (DECSCNM).
15047 * 6 - Origin Mode (DECOM).
15048 * 7 - Wraparound Mode (DECAWM).
15049 * 8 - [x] Auto-repeat Keys (DECARM).
15050 * 9 - [!] Send Mouse X & Y on button press.
15051 * 10 - [x] Show toolbar (rxvt).
15052 * 12 - Start Blinking Cursor (att610).
15053 * 18 - [!] Print form feed (DECPFF).
15054 * 19 - [x] Set print extent to full screen (DECPEX).
15055 * 25 - Show Cursor (DECTCEM).
15056 * 30 - [!] Show scrollbar (rxvt).
15057 * 35 - [x] Enable font-shifting functions (rxvt).
15058 * 38 - [x] Enter Tektronix Mode (DECTEK).
15059 * 40 - Allow 80 - 132 Mode.
15060 * 41 - [!] more(1) fix (see curses resource).
15061 * 42 - [!] Enable Nation Replacement Character sets (DECNRCM).
15062 * 44 - [!] Turn On Margin Bell.
15063 * 45 - Reverse-wraparound Mode.
15064 * 46 - [x] Start Logging.
15065 * 47 - [!] Use Alternate Screen Buffer.
15066 * 66 - [!] Application keypad (DECNKM).
15067 * 67 - Backarrow key sends backspace (DECBKM).
15068 * 1000 - Send Mouse X & Y on button press and release. (MOUSE_REPORT_CLICK)
15069 * 1001 - [!] Use Hilite Mouse Tracking.
15070 * 1002 - Use Cell Motion Mouse Tracking. (MOUSE_REPORT_DRAG)
15071 * 1003 - [!] Use All Motion Mouse Tracking.
15072 * 1004 - [!] Send FocusIn/FocusOut events.
15073 * 1005 - [!] Enable Extended Mouse Mode.
15074 * 1010 - Scroll to bottom on tty output (rxvt).
15075 * 1011 - Scroll to bottom on key press (rxvt).
15076 * 1034 - [x] Interpret "meta" key, sets eighth bit.
15077 * 1035 - [x] Enable special modifiers for Alt and NumLock keys.
15078 * 1036 - Send ESC when Meta modifies a key.
15079 * 1037 - [!] Send DEL from the editing-keypad Delete key.
15080 * 1039 - Send ESC when Alt modifies a key.
15081 * 1040 - [x] Keep selection even if not highlighted.
15082 * 1041 - [x] Use the CLIPBOARD selection.
15083 * 1042 - [!] Enable Urgency window manager hint when Control-G is received.
15084 * 1043 - [!] Enable raising of the window when Control-G is received.
15085 * 1047 - [!] Use Alternate Screen Buffer.
15086 * 1048 - Save cursor as in DECSC.
15087 * 1049 - Save cursor as in DECSC and use Alternate Screen Buffer, clearing
15088 * it first. (This may be disabled by the titeInhibit resource). This
15089 * combines the effects of the 1047 and 1048 modes. Use this with
15090 * terminfo-based applications rather than the 47 mode.
15091 * 1050 - [!] Set terminfo/termcap function-key mode.
15092 * 1051 - [x] Set Sun function-key mode.
15093 * 1052 - [x] Set HP function-key mode.
15094 * 1053 - [x] Set SCO function-key mode.
15095 * 1060 - [x] Set legacy keyboard emulation (X11R6).
15096 * 1061 - [!] Set VT220 keyboard emulation.
15097 * 2004 - Set bracketed paste mode.
15098 *
15099 * [!] - Not currently implemented, may be in the future.
15100 * [x] - Will not implement.
15101 */
15102hterm.VT.prototype.setDECMode = function(code, state) {
15103 switch (code) {
15104 case '1': // DECCKM
15105 this.terminal.keyboard.applicationCursor = state;
15106 break;
15107
15108 case '3': // DECCOLM
15109 if (this.allowColumnWidthChanges_) {
15110 this.terminal.setWidth(state ? 132 : 80);
15111
15112 this.terminal.clearHome();
15113 this.terminal.setVTScrollRegion(null, null);
15114 }
15115 break;
15116
15117 case '5': // DECSCNM
15118 this.terminal.setReverseVideo(state);
15119 break;
15120
15121 case '6': // DECOM
15122 this.terminal.setOriginMode(state);
15123 break;
15124
15125 case '7': // DECAWM
15126 this.terminal.setWraparound(state);
15127 break;
15128
15129 case '12': // att610
15130 if (this.enableDec12)
15131 this.terminal.setCursorBlink(state);
15132 break;
15133
15134 case '25': // DECTCEM
15135 this.terminal.setCursorVisible(state);
15136 break;
15137
15138 case '40': // no-spec
15139 this.terminal.allowColumnWidthChanges_ = state;
15140 break;
15141
15142 case '45': // no-spec
15143 this.terminal.setReverseWraparound(state);
15144 break;
15145
15146 case '67': // DECBKM
15147 this.terminal.keyboard.backspaceSendsBackspace = state;
15148 break;
15149
15150 case '1000': // Report on mouse clicks only.
15151 this.mouseReport = (
15152 state ? this.MOUSE_REPORT_CLICK : this.MOUSE_REPORT_DISABLED);
15153 break;
15154
15155 case '1002': // Report on mouse clicks and drags
15156 this.mouseReport = (
15157 state ? this.MOUSE_REPORT_DRAG : this.MOUSE_REPORT_DISABLED);
15158 break;
15159
15160 case '1010': // rxvt
15161 this.terminal.scrollOnOutput = state;
15162 break;
15163
15164 case '1011': // rxvt
15165 this.terminal.scrollOnKeystroke = state;
15166 break;
15167
15168 case '1036': // no-spec
15169 this.terminal.keyboard.metaSendsEscape = state;
15170 break;
15171
15172 case '1039': // no-spec
15173 if (state) {
15174 if (!this.terminal.keyboard.previousAltSendsWhat_) {
15175 this.terminal.keyboard.previousAltSendsWhat_ =
15176 this.terminal.keyboard.altSendsWhat;
15177 this.terminal.keyboard.altSendsWhat = 'escape';
15178 }
15179 } else if (this.terminal.keyboard.previousAltSendsWhat_) {
15180 this.terminal.keyboard.altSendsWhat =
15181 this.terminal.keyboard.previousAltSendsWhat_;
15182 this.terminal.keyboard.previousAltSendsWhat_ = null;
15183 }
15184 break;
15185
15186 case '47':
15187 case '1047': // no-spec
15188 this.terminal.setAlternateMode(state);
15189 break;
15190
15191 case '1048': // Save cursor as in DECSC.
15192 this.savedState_.save();
15193
15194 case '1049': // 1047 + 1048 + clear.
15195 if (state) {
15196 this.savedState_.save();
15197 this.terminal.setAlternateMode(state);
15198 this.terminal.clear();
15199 } else {
15200 this.terminal.setAlternateMode(state);
15201 this.savedState_.restore();
15202 }
15203
15204 break;
15205
15206 case '2004': // Bracketed paste mode.
15207 this.terminal.setBracketedPaste(state);
15208 break;
15209
15210 default:
15211 if (this.warnUnimplemented)
15212 console.warn('Unimplemented DEC Private Mode: ' + code);
15213 break;
15214 }
15215};
15216
15217/**
15218 * Function shared by control characters and escape sequences that are
15219 * ignored.
15220 */
15221hterm.VT.ignore = function() {};
15222
15223/**
15224 * Collection of control characters expressed in a single byte.
15225 *
15226 * This includes the characters from the C0 and C1 sets (see [CTRL]) that we
15227 * care about. Two byte versions of the C1 codes are defined in the
15228 * hterm.VT.ESC collection.
15229 *
15230 * The 'CC1' mnemonic here refers to the fact that these are one-byte Control
15231 * Codes. It's only used in this source file and not defined in any of the
15232 * referenced documents.
15233 */
15234hterm.VT.CC1 = {};
15235
15236/**
15237 * Collection of two-byte and three-byte sequences starting with ESC.
15238 */
15239hterm.VT.ESC = {};
15240
15241/**
15242 * Collection of CSI (Control Sequence Introducer) sequences.
15243 *
15244 * These sequences begin with 'ESC [', and may take zero or more arguments.
15245 */
15246hterm.VT.CSI = {};
15247
15248/**
15249 * Collection of OSC (Operating System Control) sequences.
15250 *
15251 * These sequences begin with 'ESC ]', followed by a function number and a
15252 * string terminated by either ST or BEL.
15253 */
15254hterm.VT.OSC = {};
15255
15256/**
15257 * Collection of VT52 sequences.
15258 *
15259 * When in VT52 mode, other sequences are disabled.
15260 */
15261hterm.VT.VT52 = {};
15262
15263/**
15264 * Null (NUL).
15265 *
15266 * Silently ignored.
15267 */
15268hterm.VT.CC1['\x00'] = function () {};
15269
15270/**
15271 * Enquiry (ENQ).
15272 *
15273 * Transmit answerback message.
15274 *
15275 * The default answerback message in xterm is an empty string, so we just
15276 * ignore this.
15277 */
15278hterm.VT.CC1['\x05'] = hterm.VT.ignore;
15279
15280/**
15281 * Ring Bell (BEL).
15282 */
15283hterm.VT.CC1['\x07'] = function() {
15284 this.terminal.ringBell();
15285};
15286
15287/**
15288 * Backspace (BS).
15289 *
15290 * Move the cursor to the left one character position, unless it is at the
15291 * left margin, in which case no action occurs.
15292 */
15293hterm.VT.CC1['\x08'] = function() {
15294 this.terminal.cursorLeft(1);
15295};
15296
15297/**
15298 * Horizontal Tab (HT).
15299 *
15300 * Move the cursor to the next tab stop, or to the right margin if no further
15301 * tab stops are present on the line.
15302 */
15303hterm.VT.CC1['\x09'] = function() {
15304 this.terminal.forwardTabStop();
15305};
15306
15307/**
15308 * Line Feed (LF).
15309 *
15310 * This code causes a line feed or a new line operation. See Automatic
15311 * Newline (LNM).
15312 */
15313hterm.VT.CC1['\x0a'] = function() {
15314 this.terminal.formFeed();
15315};
15316
15317/**
15318 * Vertical Tab (VT).
15319 *
15320 * Interpreted as LF.
15321 */
15322hterm.VT.CC1['\x0b'] = hterm.VT.CC1['\x0a'];
15323
15324/**
15325 * Form Feed (FF).
15326 *
15327 * Interpreted as LF.
15328 */
15329hterm.VT.CC1['\x0c'] = function() {
15330 this.terminal.formFeed();
15331};
15332
15333/**
15334 * Carriage Return (CR).
15335 *
15336 * Move cursor to the left margin on the current line.
15337 */
15338hterm.VT.CC1['\x0d'] = function() {
15339 this.terminal.setCursorColumn(0);
15340};
15341
15342/**
15343 * Shift Out (SO), aka Lock Shift 0 (LS1).
15344 *
15345 * Invoke G1 character set in GL.
15346 */
15347hterm.VT.CC1['\x0e'] = function() {
15348 this.GL = 'G1';
15349};
15350
15351/**
15352 * Shift In (SI), aka Lock Shift 0 (LS0).
15353 *
15354 * Invoke G0 character set in GL.
15355 */
15356hterm.VT.CC1['\x0f'] = function() {
15357 this.GL = 'G0';
15358};
15359
15360/**
15361 * Transmit On (XON).
15362 *
15363 * Not currently implemented.
15364 *
15365 * TODO(rginda): Implement?
15366 */
15367hterm.VT.CC1['\x11'] = hterm.VT.ignore;
15368
15369/**
15370 * Transmit Off (XOFF).
15371 *
15372 * Not currently implemented.
15373 *
15374 * TODO(rginda): Implement?
15375 */
15376hterm.VT.CC1['\x13'] = hterm.VT.ignore;
15377
15378/**
15379 * Cancel (CAN).
15380 *
15381 * If sent during a control sequence, the sequence is immediately terminated
15382 * and not executed.
15383 *
15384 * It also causes the error character to be displayed.
15385 */
15386hterm.VT.CC1['\x18'] = function(parseState) {
15387 // If we've shifted in the G1 character set, shift it back out to
15388 // the default character set.
15389 if (this.GL == 'G1') {
15390 this.GL = 'G0';
15391 }
15392 parseState.resetParseFunction();
15393 this.terminal.print('?');
15394};
15395
15396/**
15397 * Substitute (SUB).
15398 *
15399 * Interpreted as CAN.
15400 */
15401hterm.VT.CC1['\x1a'] = hterm.VT.CC1['\x18'];
15402
15403/**
15404 * Escape (ESC).
15405 */
15406hterm.VT.CC1['\x1b'] = function(parseState) {
15407 function parseESC(parseState) {
15408 var ch = parseState.consumeChar();
15409
15410 if (ch == '\x1b')
15411 return;
15412
15413 this.dispatch('ESC', ch, parseState);
15414
15415 if (parseState.func == parseESC)
15416 parseState.resetParseFunction();
15417 };
15418
15419 parseState.func = parseESC;
15420};
15421
15422/**
15423 * Delete (DEL).
15424 */
15425hterm.VT.CC1['\x7f'] = hterm.VT.ignore;
15426
15427// 8 bit control characters and their two byte equivalents, below...
15428
15429/**
15430 * Index (IND).
15431 *
15432 * Like newline, only keep the X position
15433 */
15434hterm.VT.CC1['\x84'] =
15435hterm.VT.ESC['D'] = function() {
15436 this.terminal.lineFeed();
15437};
15438
15439/**
15440 * Next Line (NEL).
15441 *
15442 * Like newline, but doesn't add lines.
15443 */
15444hterm.VT.CC1['\x85'] =
15445hterm.VT.ESC['E'] = function() {
15446 this.terminal.setCursorColumn(0);
15447 this.terminal.cursorDown(1);
15448};
15449
15450/**
15451 * Horizontal Tabulation Set (HTS).
15452 */
15453hterm.VT.CC1['\x88'] =
15454hterm.VT.ESC['H'] = function() {
15455 this.terminal.setTabStop(this.terminal.getCursorColumn());
15456};
15457
15458/**
15459 * Reverse Index (RI).
15460 *
15461 * Move up one line.
15462 */
15463hterm.VT.CC1['\x8d'] =
15464hterm.VT.ESC['M'] = function() {
15465 this.terminal.reverseLineFeed();
15466};
15467
15468/**
15469 * Single Shift 2 (SS2).
15470 *
15471 * Select of G2 Character Set for the next character only.
15472 *
15473 * Not currently implemented.
15474 */
15475hterm.VT.CC1['\x8e'] =
15476hterm.VT.ESC['N'] = hterm.VT.ignore;
15477
15478/**
15479 * Single Shift 3 (SS3).
15480 *
15481 * Select of G3 Character Set for the next character only.
15482 *
15483 * Not currently implemented.
15484 */
15485hterm.VT.CC1['\x8f'] =
15486hterm.VT.ESC['O'] = hterm.VT.ignore;
15487
15488/**
15489 * Device Control String (DCS).
15490 *
15491 * Indicate a DCS sequence. See Device-Control functions in [XTERM].
15492 * Not currently implemented.
15493 *
15494 * TODO(rginda): Consider implementing DECRQSS, the rest don't seem applicable.
15495 */
15496hterm.VT.CC1['\x90'] =
15497hterm.VT.ESC['P'] = function(parseState) {
15498 parseState.resetArguments();
15499 parseState.func = this.parseUntilStringTerminator_;
15500};
15501
15502/**
15503 * Start of Protected Area (SPA).
15504 *
15505 * Will not implement.
15506 */
15507hterm.VT.CC1['\x96'] =
15508hterm.VT.ESC['V'] = hterm.VT.ignore;
15509
15510/**
15511 * End of Protected Area (EPA).
15512 *
15513 * Will not implement.
15514 */
15515hterm.VT.CC1['\x97'] =
15516hterm.VT.ESC['W'] = hterm.VT.ignore;
15517
15518/**
15519 * Start of String (SOS).
15520 *
15521 * Will not implement.
15522 */
15523hterm.VT.CC1['\x98'] =
15524hterm.VT.ESC['X'] = hterm.VT.ignore;
15525
15526/**
15527 * Single Character Introducer (SCI, also DECID).
15528 *
15529 * Return Terminal ID. Obsolete form of 'ESC [ c' (DA).
15530 */
15531hterm.VT.CC1['\x9a'] =
15532hterm.VT.ESC['Z'] = function() {
15533 this.terminal.io.sendString('\x1b[?1;2c');
15534};
15535
15536/**
15537 * Control Sequence Introducer (CSI).
15538 *
15539 * The lead into most escape sequences. See [CSI].
15540 */
15541hterm.VT.CC1['\x9b'] =
15542hterm.VT.ESC['['] = function(parseState) {
15543 parseState.resetArguments();
15544 this.leadingModifier_ = '';
15545 this.trailingModifier_ = '';
15546 parseState.func = this.parseCSI_;
15547};
15548
15549/**
15550 * String Terminator (ST).
15551 *
15552 * Used to terminate DCS/OSC/PM/APC commands which may take string arguments.
15553 *
15554 * We don't directly handle it here, as it's only used to terminate other
15555 * sequences. See the 'parseUntilStringTerminator_' method.
15556 */
15557hterm.VT.CC1['\x9c'] =
15558hterm.VT.ESC['\\'] = hterm.VT.ignore;
15559
15560/**
15561 * Operating System Command (OSC).
15562 *
15563 * Commands relating to the operating system.
15564 */
15565hterm.VT.CC1['\x9d'] =
15566hterm.VT.ESC[']'] = function(parseState) {
15567 parseState.resetArguments();
15568
15569 function parseOSC(parseState) {
15570 if (!this.parseUntilStringTerminator_(parseState)) {
15571 // The string sequence was too long.
15572 return;
15573 }
15574
15575 if (parseState.func == parseOSC) {
15576 // We're not done parsing the string yet.
15577 return;
15578 }
15579
15580 // We're done.
15581 var ary = parseState.args[0].match(/^(\d+);(.*)$/);
15582 if (ary) {
15583 parseState.args[0] = ary[2];
15584 this.dispatch('OSC', ary[1], parseState);
15585 } else {
15586 console.warn('Invalid OSC: ' + JSON.stringify(parseState.args[0]));
15587 }
15588 };
15589
15590 parseState.func = parseOSC;
15591};
15592
15593/**
15594 * Privacy Message (PM).
15595 *
15596 * Will not implement.
15597 */
15598hterm.VT.CC1['\x9e'] =
15599hterm.VT.ESC['^'] = function(parseState) {
15600 parseState.resetArguments();
15601 parseState.func = this.parseUntilStringTerminator_;
15602};
15603
15604/**
15605 * Application Program Control (APC).
15606 *
15607 * Will not implement.
15608 */
15609hterm.VT.CC1['\x9f'] =
15610hterm.VT.ESC['_'] = function(parseState) {
15611 parseState.resetArguments();
15612 parseState.func = this.parseUntilStringTerminator_;
15613};
15614
15615/**
15616 * ESC \x20 - Unclear to me where these originated, possibly in xterm.
15617 *
15618 * Not currently implemented:
15619 * ESC \x20 F - Select 7 bit escape codes in responses (S7C1T).
15620 * ESC \x20 G - Select 8 bit escape codes in responses (S8C1T).
15621 * NB: We currently assume S7C1T always.
15622 *
15623 * Will not implement:
15624 * ESC \x20 L - Set ANSI conformance level 1.
15625 * ESC \x20 M - Set ANSI conformance level 2.
15626 * ESC \x20 N - Set ANSI conformance level 3.
15627 */
15628hterm.VT.ESC['\x20'] = function(parseState) {
15629 parseState.func = function(parseState) {
15630 var ch = parseState.consumeChar();
15631 if (this.warnUnimplemented)
15632 console.warn('Unimplemented sequence: ESC 0x20 ' + ch);
15633 parseState.resetParseFunction();
15634 };
15635};
15636
15637/**
15638 * DEC 'ESC #' sequences.
15639 *
15640 * Handled:
15641 * ESC # 8 - DEC Screen Alignment Test (DECALN).
15642 * Fills the terminal with 'E's. Used liberally by vttest.
15643 *
15644 * Ignored:
15645 * ESC # 3 - DEC double-height line, top half (DECDHL).
15646 * ESC # 4 - DEC double-height line, bottom half (DECDHL).
15647 * ESC # 5 - DEC single-width line (DECSWL).
15648 * ESC # 6 - DEC double-width line (DECDWL).
15649 */
15650hterm.VT.ESC['#'] = function(parseState) {
15651 parseState.func = function(parseState) {
15652 var ch = parseState.consumeChar();
15653 if (ch == '8')
15654 this.terminal.fill('E');
15655
15656 parseState.resetParseFunction();
15657 };
15658};
15659
15660/**
15661 * 'ESC %' sequences, character set control. Not currently implemented.
15662 *
15663 * To be implemented (currently ignored):
15664 * ESC % @ - Set ISO 8859-1 character set.
15665 * ESC % G - Set UTF-8 character set.
15666 *
15667 * All other ESC # sequences are echoed to the terminal.
15668 *
15669 * TODO(rginda): Implement.
15670 */
15671hterm.VT.ESC['%'] = function(parseState) {
15672 parseState.func = function(parseState) {
15673 var ch = parseState.consumeChar();
15674 if (ch != '@' && ch != 'G' && this.warnUnimplemented)
15675 console.warn('Unknown ESC % argument: ' + JSON.stringify(ch));
15676 parseState.resetParseFunction();
15677 };
15678};
15679
15680/**
15681 * Character Set Selection (SCS).
15682 *
15683 * ESC ( Ps - Set G0 character set (VT100).
15684 * ESC ) Ps - Set G1 character set (VT220).
15685 * ESC * Ps - Set G2 character set (VT220).
15686 * ESC + Ps - Set G3 character set (VT220).
15687 * ESC - Ps - Set G1 character set (VT300).
15688 * ESC . Ps - Set G2 character set (VT300).
15689 * ESC / Ps - Set G3 character set (VT300).
15690 *
15691 * Values for Ps are:
15692 * 0 - DEC Special Character and Line Drawing Set.
15693 * A - United Kingdom (UK).
15694 * B - United States (USASCII).
15695 * 4 - Dutch.
15696 * C or 5 - Finnish.
15697 * R - French.
15698 * Q - French Canadian.
15699 * K - German.
15700 * Y - Italian.
15701 * E or 6 - Norwegian/Danish.
15702 * Z - Spanish.
15703 * H or 7 - Swedish.
15704 * = - Swiss.
15705 *
15706 * All other sequences are echoed to the terminal.
15707 *
15708 * TODO(rginda): Implement.
15709 */
15710hterm.VT.ESC['('] =
15711hterm.VT.ESC[')'] =
15712hterm.VT.ESC['*'] =
15713hterm.VT.ESC['+'] =
15714hterm.VT.ESC['-'] =
15715hterm.VT.ESC['.'] =
15716hterm.VT.ESC['/'] = function(parseState, code) {
15717 parseState.func = function(parseState) {
15718 var ch = parseState.consumeChar();
15719 if (ch == '\x1b') {
15720 parseState.resetParseFunction();
15721 parseState.func();
15722 return;
15723 }
15724
15725 if (ch in hterm.VT.CharacterMap.maps) {
15726 if (code == '(') {
15727 this.G0 = hterm.VT.CharacterMap.maps[ch];
15728 } else if (code == ')' || code == '-') {
15729 this.G1 = hterm.VT.CharacterMap.maps[ch];
15730 } else if (code == '*' || code == '.') {
15731 this.G2 = hterm.VT.CharacterMap.maps[ch];
15732 } else if (code == '+' || code == '/') {
15733 this.G3 = hterm.VT.CharacterMap.maps[ch];
15734 }
15735 } else if (this.warnUnimplemented) {
15736 console.log('Invalid character set for "' + code + '": ' + ch);
15737 }
15738
15739 parseState.resetParseFunction();
15740 };
15741};
15742
15743/**
15744 * Back Index (DECBI).
15745 *
15746 * VT420 and up. Not currently implemented.
15747 */
15748hterm.VT.ESC['6'] = hterm.VT.ignore;
15749
15750/**
15751 * Save Cursor (DECSC).
15752 */
15753hterm.VT.ESC['7'] = function() {
15754 this.savedState_.save();
15755};
15756
15757/**
15758 * Restore Cursor (DECSC).
15759 */
15760hterm.VT.ESC['8'] = function() {
15761 this.savedState_.restore();
15762};
15763
15764/**
15765 * Forward Index (DECFI).
15766 *
15767 * VT210 and up. Not currently implemented.
15768 */
15769hterm.VT.ESC['9'] = hterm.VT.ignore;
15770
15771/**
15772 * Application keypad (DECPAM).
15773 */
15774hterm.VT.ESC['='] = function() {
15775 this.terminal.keyboard.applicationKeypad = true;
15776};
15777
15778/**
15779 * Normal keypad (DECPNM).
15780 */
15781hterm.VT.ESC['>'] = function() {
15782 this.terminal.keyboard.applicationKeypad = false;
15783};
15784
15785/**
15786 * Cursor to lower left corner of screen.
15787 *
15788 * Will not implement.
15789 *
15790 * This is only recognized by xterm when the hpLowerleftBugCompat resource is
15791 * set.
15792 */
15793hterm.VT.ESC['F'] = hterm.VT.ignore;
15794
15795/**
15796 * Full Reset (RIS).
15797 */
15798hterm.VT.ESC['c'] = function() {
15799 this.reset();
15800 this.terminal.reset();
15801};
15802
15803/**
15804 * Memory lock/unlock.
15805 *
15806 * Will not implement.
15807 */
15808hterm.VT.ESC['l'] =
15809hterm.VT.ESC['m'] = hterm.VT.ignore;
15810
15811/**
15812 * Lock Shift 2 (LS2)
15813 *
15814 * Invoke the G2 Character Set as GL.
15815 */
15816hterm.VT.ESC['n'] = function() {
15817 this.GL = 'G2';
15818};
15819
15820/**
15821 * Lock Shift 3 (LS3)
15822 *
15823 * Invoke the G3 Character Set as GL.
15824 */
15825hterm.VT.ESC['o'] = function() {
15826 this.GL = 'G3';
15827};
15828
15829/**
15830 * Lock Shift 2, Right (LS3R)
15831 *
15832 * Invoke the G3 Character Set as GR.
15833 */
15834hterm.VT.ESC['|'] = function() {
15835 this.GR = 'G3';
15836};
15837
15838/**
15839 * Lock Shift 2, Right (LS2R)
15840 *
15841 * Invoke the G2 Character Set as GR.
15842 */
15843hterm.VT.ESC['}'] = function() {
15844 this.GR = 'G2';
15845};
15846
15847/**
15848 * Lock Shift 1, Right (LS1R)
15849 *
15850 * Invoke the G1 Character Set as GR.
15851 */
15852hterm.VT.ESC['~'] = function() {
15853 this.GR = 'G1';
15854};
15855
15856/**
15857 * Change icon name and window title.
15858 *
15859 * We only change the window title.
15860 */
15861hterm.VT.OSC['0'] = function(parseState) {
15862 this.terminal.setWindowTitle(parseState.args[0]);
15863};
15864
15865/**
15866 * Change window title.
15867 */
15868hterm.VT.OSC['2'] = hterm.VT.OSC['0'];
15869
15870/**
15871 * Set/read color palette.
15872 */
15873hterm.VT.OSC['4'] = function(parseState) {
15874 // Args come in as a single 'index1;rgb1 ... ;indexN;rgbN' string.
15875 // We split on the semicolon and iterate through the pairs.
15876 var args = parseState.args[0].split(';');
15877
15878 var pairCount = parseInt(args.length / 2);
15879 var colorPalette = this.terminal.getTextAttributes().colorPalette;
15880 var responseArray = [];
15881
15882 for (var pairNumber = 0; pairNumber < pairCount; ++pairNumber) {
15883 var colorIndex = parseInt(args[pairNumber * 2]);
15884 var colorValue = args[pairNumber * 2 + 1];
15885
15886 if (colorIndex >= colorPalette.length)
15887 continue;
15888
15889 if (colorValue == '?') {
15890 // '?' means we should report back the current color value.
15891 colorValue = lib.colors.rgbToX11(colorPalette[colorIndex]);
15892 if (colorValue)
15893 responseArray.push(colorIndex + ';' + colorValue);
15894
15895 continue;
15896 }
15897
15898 colorValue = lib.colors.x11ToCSS(colorValue);
15899 if (colorValue)
15900 colorPalette[colorIndex] = colorValue;
15901 }
15902
15903 if (responseArray.length)
15904 this.terminal.io.sendString('\x1b]4;' + responseArray.join(';') + '\x07');
15905};
15906
15907/**
15908 * Set the cursor shape.
15909 *
15910 * Parameter is expected to be in the form "CursorShape=number", where number is
15911 * one of:
15912 *
15913 * 0 - Block
15914 * 1 - I-Beam
15915 * 2 - Underline
15916 *
15917 * This is a bit of a de-facto standard supported by iTerm 2 and Konsole. See
15918 * also: DECSCUSR.
15919 *
15920 * Invalid numbers will restore the cursor to the block shape.
15921 */
15922hterm.VT.OSC['50'] = function(parseState) {
15923 var args = parseState.args[0].match(/CursorShape=(.)/i);
15924 if (!args) {
15925 console.warn('Could not parse OSC 50 args: ' + parseState.args[0]);
15926 return;
15927 }
15928
15929 switch (args[1]) {
15930 case '1':
15931 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
15932 break;
15933
15934 case '2':
15935 this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
15936 break;
15937
15938 default:
15939 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
15940 }
15941};
15942
15943/**
15944 * Set/read system clipboard.
15945 *
15946 * Read is not implemented due to security considerations. A remote app
15947 * that is able to both write and read to the clipboard could essentially
15948 * take over your session.
15949 *
15950 * The clipboard data will be decoded according to the 'receive-encoding'
15951 * preference.
15952 */
15953hterm.VT.OSC['52'] = function(parseState) {
15954 // Args come in as a single 'clipboard;b64-data' string. The clipboard
15955 // parameter is used to select which of the X clipboards to address. Since
15956 // we're not integrating with X, we treat them all the same.
15957 var args = parseState.args[0].match(/^[cps01234567]*;(.*)/);
15958 if (!args)
15959 return;
15960
15961 var data = window.atob(args[1]);
15962 if (data)
15963 this.terminal.copyStringToClipboard(this.decode(data));
15964};
15965
15966/**
15967 * Insert (blank) characters (ICH).
15968 */
15969hterm.VT.CSI['@'] = function(parseState) {
15970 this.terminal.insertSpace(parseState.iarg(0, 1));
15971};
15972
15973/**
15974 * Cursor Up (CUU).
15975 */
15976hterm.VT.CSI['A'] = function(parseState) {
15977 this.terminal.cursorUp(parseState.iarg(0, 1));
15978};
15979
15980/**
15981 * Cursor Down (CUD).
15982 */
15983hterm.VT.CSI['B'] = function(parseState) {
15984 this.terminal.cursorDown(parseState.iarg(0, 1));
15985};
15986
15987/**
15988 * Cursor Forward (CUF).
15989 */
15990hterm.VT.CSI['C'] = function(parseState) {
15991 this.terminal.cursorRight(parseState.iarg(0, 1));
15992};
15993
15994/**
15995 * Cursor Backward (CUB).
15996 */
15997hterm.VT.CSI['D'] = function(parseState) {
15998 this.terminal.cursorLeft(parseState.iarg(0, 1));
15999};
16000
16001/**
16002 * Cursor Next Line (CNL).
16003 *
16004 * This is like Cursor Down, except the cursor moves to the beginning of the
16005 * line as well.
16006 */
16007hterm.VT.CSI['E'] = function(parseState) {
16008 this.terminal.cursorDown(parseState.iarg(0, 1));
16009 this.terminal.setCursorColumn(0);
16010};
16011
16012/**
16013 * Cursor Preceding Line (CPL).
16014 *
16015 * This is like Cursor Up, except the cursor moves to the beginning of the
16016 * line as well.
16017 */
16018hterm.VT.CSI['F'] = function(parseState) {
16019 this.terminal.cursorUp(parseState.iarg(0, 1));
16020 this.terminal.setCursorColumn(0);
16021};
16022
16023/**
16024 * Cursor Character Absolute (CHA).
16025 */
16026hterm.VT.CSI['G'] = function(parseState) {
16027 this.terminal.setCursorColumn(parseState.iarg(0, 1) - 1);
16028};
16029
16030/**
16031 * Cursor Position (CUP).
16032 */
16033hterm.VT.CSI['H'] = function(parseState) {
16034 this.terminal.setCursorPosition(parseState.iarg(0, 1) - 1,
16035 parseState.iarg(1, 1) - 1);
16036};
16037
16038/**
16039 * Cursor Forward Tabulation (CHT).
16040 */
16041hterm.VT.CSI['I'] = function(parseState) {
16042 var count = parseState.iarg(0, 1);
16043 count = lib.f.clamp(count, 1, this.terminal.screenSize.width);
16044 for (var i = 0; i < count; i++) {
16045 this.terminal.forwardTabStop();
16046 }
16047};
16048
16049/**
16050 * Erase in Display (ED, DECSED).
16051 */
16052hterm.VT.CSI['J'] =
16053hterm.VT.CSI['?J'] = function(parseState, code) {
16054 var arg = parseState.args[0];
16055
16056 if (!arg || arg == '0') {
16057 this.terminal.eraseBelow();
16058 } else if (arg == '1') {
16059 this.terminal.eraseAbove();
16060 } else if (arg == '2') {
16061 this.terminal.clear();
16062 } else if (arg == '3') {
16063 // The xterm docs say this means "Erase saved lines", but we'll just clear
16064 // the display since killing the scrollback seems rude.
16065 this.terminal.clear();
16066 }
16067};
16068
16069/**
16070 * Erase in line (EL, DECSEL).
16071 */
16072hterm.VT.CSI['K'] =
16073hterm.VT.CSI['?K'] = function(parseState, code) {
16074 var arg = parseState.args[0];
16075
16076 if (!arg || arg == '0') {
16077 this.terminal.eraseToRight();
16078 } else if (arg == '1'){
16079 this.terminal.eraseToLeft();
16080 } else if (arg == '2') {
16081 this.terminal.eraseLine();
16082 }
16083};
16084
16085/**
16086 * Insert Lines (IL).
16087 */
16088hterm.VT.CSI['L'] = function(parseState) {
16089 this.terminal.insertLines(parseState.iarg(0, 1));
16090};
16091
16092/**
16093 * Delete Lines (DL).
16094 */
16095hterm.VT.CSI['M'] = function(parseState) {
16096 this.terminal.deleteLines(parseState.iarg(0, 1));
16097};
16098
16099/**
16100 * Delete Characters (DCH).
16101 *
16102 * This command shifts the line contents left, starting at the cursor position.
16103 */
16104hterm.VT.CSI['P'] = function(parseState) {
16105 this.terminal.deleteChars(parseState.iarg(0, 1));
16106};
16107
16108/**
16109 * Scroll Up (SU).
16110 */
16111hterm.VT.CSI['S'] = function(parseState) {
16112 this.terminal.vtScrollUp(parseState.iarg(0, 1));
16113};
16114
16115/**
16116 * Scroll Down (SD).
16117 * Also 'Initiate highlight mouse tracking'. Will not implement this part.
16118 */
16119hterm.VT.CSI['T'] = function(parseState) {
16120 if (parseState.args.length <= 1)
16121 this.terminal.vtScrollDown(parseState.iarg(0, 1));
16122};
16123
16124/**
16125 * Reset one or more features of the title modes to the default value.
16126 *
16127 * ESC [ > Ps T
16128 *
16129 * Normally, "reset" disables the feature. It is possible to disable the
16130 * ability to reset features by compiling a different default for the title
16131 * modes into xterm.
16132 *
16133 * Ps values:
16134 * 0 - Do not set window/icon labels using hexadecimal.
16135 * 1 - Do not query window/icon labels using hexadecimal.
16136 * 2 - Do not set window/icon labels using UTF-8.
16137 * 3 - Do not query window/icon labels using UTF-8.
16138 *
16139 * Will not implement.
16140 */
16141hterm.VT.CSI['>T'] = hterm.VT.ignore;
16142
16143/**
16144 * Erase Characters (ECH).
16145 */
16146hterm.VT.CSI['X'] = function(parseState) {
16147 this.terminal.eraseToRight(parseState.iarg(0, 1));
16148};
16149
16150/**
16151 * Cursor Backward Tabulation (CBT).
16152 */
16153hterm.VT.CSI['Z'] = function(parseState) {
16154 var count = parseState.iarg(0, 1);
16155 count = lib.f.clamp(count, 1, this.terminal.screenSize.width);
16156 for (var i = 0; i < count; i++) {
16157 this.terminal.backwardTabStop();
16158 }
16159};
16160
16161/**
16162 * Character Position Absolute (HPA).
16163 */
16164hterm.VT.CSI['`'] = function(parseState) {
16165 this.terminal.setCursorColumn(parseState.iarg(0, 1) - 1);
16166};
16167
16168/**
16169 * Repeat the preceding graphic character.
16170 *
16171 * Not currently implemented.
16172 */
16173hterm.VT.CSI['b'] = hterm.VT.ignore;
16174
16175/**
16176 * Send Device Attributes (Primary DA).
16177 *
16178 * TODO(rginda): This is hardcoded to send back 'VT100 with Advanced Video
16179 * Option', but it may be more correct to send a VT220 response once
16180 * we fill out the 'Not currently implemented' parts.
16181 */
16182hterm.VT.CSI['c'] = function(parseState) {
16183 if (!parseState.args[0] || parseState.args[0] == '0') {
16184 this.terminal.io.sendString('\x1b[?1;2c');
16185 }
16186};
16187
16188/**
16189 * Send Device Attributes (Secondary DA).
16190 *
16191 * TODO(rginda): This is hardcoded to send back 'VT100' but it may be more
16192 * correct to send a VT220 response once we fill out more 'Not currently
16193 * implemented' parts.
16194 */
16195hterm.VT.CSI['>c'] = function(parseState) {
16196 this.terminal.io.sendString('\x1b[>0;256;0c');
16197};
16198
16199/**
16200 * Line Position Absolute (VPA).
16201 */
16202hterm.VT.CSI['d'] = function(parseState) {
16203 this.terminal.setAbsoluteCursorRow(parseState.iarg(0, 1) - 1);
16204};
16205
16206/**
16207 * Horizontal and Vertical Position (HVP).
16208 *
16209 * Same as Cursor Position (CUP).
16210 */
16211hterm.VT.CSI['f'] = hterm.VT.CSI['H'];
16212
16213/**
16214 * Tab Clear (TBC).
16215 */
16216hterm.VT.CSI['g'] = function(parseState) {
16217 if (!parseState.args[0] || parseState.args[0] == '0') {
16218 // Clear tab stop at cursor.
16219 this.terminal.clearTabStopAtCursor(false);
16220 } else if (parseState.args[0] == '3') {
16221 // Clear all tab stops.
16222 this.terminal.clearAllTabStops();
16223 }
16224};
16225
16226/**
16227 * Set Mode (SM).
16228 */
16229hterm.VT.CSI['h'] = function(parseState) {
16230 for (var i = 0; i < parseState.args.length; i++) {
16231 this.setANSIMode(parseState.args[i], true);
16232 }
16233};
16234
16235/**
16236 * DEC Private Mode Set (DECSET).
16237 */
16238hterm.VT.CSI['?h'] = function(parseState) {
16239 for (var i = 0; i < parseState.args.length; i++) {
16240 this.setDECMode(parseState.args[i], true);
16241 }
16242};
16243
16244/**
16245 * Media Copy (MC).
16246 * Media Copy (MC, DEC Specific).
16247 *
16248 * These commands control the printer. Will not implement.
16249 */
16250hterm.VT.CSI['i'] =
16251hterm.VT.CSI['?i'] = hterm.VT.ignore;
16252
16253/**
16254 * Reset Mode (RM).
16255 */
16256hterm.VT.CSI['l'] = function(parseState) {
16257 for (var i = 0; i < parseState.args.length; i++) {
16258 this.setANSIMode(parseState.args[i], false);
16259 }
16260};
16261
16262/**
16263 * DEC Private Mode Reset (DECRST).
16264 */
16265hterm.VT.CSI['?l'] = function(parseState) {
16266 for (var i = 0; i < parseState.args.length; i++) {
16267 this.setDECMode(parseState.args[i], false);
16268 }
16269};
16270
16271/**
16272 * Character Attributes (SGR).
16273 *
16274 * Iterate through the list of arguments, applying the following attribute
16275 * changes based on the argument value...
16276 *
16277 * 0 Normal (default).
16278 * 1 Bold.
16279 * 2 Faint.
16280 * 3 Italic (non-xterm).
16281 * 4 Underlined.
16282 * 5 Blink (appears as Bold).
16283 * 7 Inverse.
16284 * 8 Invisible, i.e., hidden (VT300).
16285 * 9 Crossed out (ECMA-48).
16286 * 22 Normal (neither bold nor faint).
16287 * 23 Not italic (non-xterm).
16288 * 24 Not underlined.
16289 * 25 Steady (not blinking).
16290 * 27 Positive (not inverse).
16291 * 28 Visible, i.e., not hidden (VT300).
16292 * 29 Not crossed out (ECMA-48).
16293 * 30 Set foreground color to Black.
16294 * 31 Set foreground color to Red.
16295 * 32 Set foreground color to Green.
16296 * 33 Set foreground color to Yellow.
16297 * 34 Set foreground color to Blue.
16298 * 35 Set foreground color to Magenta.
16299 * 36 Set foreground color to Cyan.
16300 * 37 Set foreground color to White.
16301 * 39 Set foreground color to default (original).
16302 * 40 Set background color to Black.
16303 * 41 Set background color to Red.
16304 * 42 Set background color to Green.
16305 * 43 Set background color to Yellow.
16306 * 44 Set background color to Blue.
16307 * 45 Set background color to Magenta.
16308 * 46 Set background color to Cyan.
16309 * 47 Set background color to White.
16310 * 49 Set background color to default (original)
16311 *
16312 * Non-xterm (italic) codes have mixed support, but are supported by both
16313 * gnome-terminal and rxvt and are recognized as CSI codes on Wikipedia
16314 * (https://en.wikipedia.org/wiki/ANSI_escape_code).
16315 *
16316 * For 16-color support, the following apply.
16317 *
16318 * 90 Set foreground color to Bright Black.
16319 * 91 Set foreground color to Bright Red.
16320 * 92 Set foreground color to Bright Green.
16321 * 93 Set foreground color to Bright Yellow.
16322 * 94 Set foreground color to Bright Blue.
16323 * 95 Set foreground color to Bright Magenta.
16324 * 96 Set foreground color to Bright Cyan.
16325 * 97 Set foreground color to Bright White.
16326 * 100 Set background color to Bright Black.
16327 * 101 Set background color to Bright Red.
16328 * 102 Set background color to Bright Green.
16329 * 103 Set background color to Bright Yellow.
16330 * 104 Set background color to Bright Blue.
16331 * 105 Set background color to Bright Magenta.
16332 * 106 Set background color to Bright Cyan.
16333 * 107 Set background color to Bright White.
16334 *
16335 * For 88- or 256-color support, the following apply.
16336 * 38 ; 5 ; P Set foreground color to P.
16337 * 48 ; 5 ; P Set background color to P.
16338 *
16339 * For true color (24-bit) support, the following apply.
16340 * 38 ; 2 ; R ; G ; B Set foreground color to rgb(R, G, B)
16341 * 48 ; 2 ; R ; G ; B Set background color to rgb(R, G, B)
16342 *
16343 * Note that most terminals consider "bold" to be "bold and bright". In
16344 * some documents the bold state is even referred to as bright. We interpret
16345 * bold as bold-bright here too, but only when the "bold" setting comes before
16346 * the color selection.
16347 */
16348hterm.VT.CSI['m'] = function(parseState) {
16349 function get256(i) {
16350 if (parseState.args.length < i + 2 || parseState.args[i + 1] != '5')
16351 return null;
16352
16353 return parseState.iarg(i + 2, 0);
16354 }
16355
16356 function getTrueColor(i) {
16357 if (parseState.args.length < i + 5 || parseState.args[i + 1] != '2')
16358 return null;
16359 var r = parseState.iarg(i + 2, 0);
16360 var g = parseState.iarg(i + 3, 0);
16361 var b = parseState.iarg(i + 4, 0);
16362
16363 return 'rgb(' + r + ' ,' + g + ' ,' + b + ')';
16364 }
16365
16366 var attrs = this.terminal.getTextAttributes();
16367
16368 if (!parseState.args.length) {
16369 attrs.reset();
16370 return;
16371 }
16372
16373 for (var i = 0; i < parseState.args.length; i++) {
16374 var arg = parseState.iarg(i, 0);
16375
16376 if (arg < 30) {
16377 if (arg == 0) {
16378 attrs.reset();
16379 } else if (arg == 1) {
16380 attrs.bold = true;
16381 } else if (arg == 2) {
16382 attrs.faint = true;
16383 } else if (arg == 3) {
16384 attrs.italic = true;
16385 } else if (arg == 4) {
16386 attrs.underline = true;
16387 } else if (arg == 5) {
16388 attrs.blink = true;
16389 } else if (arg == 7) { // Inverse.
16390 attrs.inverse = true;
16391 } else if (arg == 8) { // Invisible.
16392 attrs.invisible = true;
16393 } else if (arg == 9) {
16394 attrs.strikethrough = true;
16395 } else if (arg == 22) {
16396 attrs.bold = false;
16397 attrs.faint = false;
16398 } else if (arg == 23) {
16399 attrs.italic = false;
16400 } else if (arg == 24) {
16401 attrs.underline = false;
16402 } else if (arg == 25) {
16403 attrs.blink = false;
16404 } else if (arg == 27) {
16405 attrs.inverse = false;
16406 } else if (arg == 28) {
16407 attrs.invisible = false;
16408 } else if (arg == 29) {
16409 attrs.strikethrough = false;
16410 }
16411
16412 } else if (arg < 50) {
16413 // Select fore/background color from bottom half of 16 color palette
16414 // or from the 256 color palette or alternative specify color in fully
16415 // qualified rgb(r, g, b) form.
16416 if (arg < 38) {
16417 attrs.foregroundSource = arg - 30;
16418
16419 } else if (arg == 38) {
16420 // First check for true color definition
16421 var trueColor = getTrueColor(i);
16422 if (trueColor != null) {
16423 attrs.foregroundSource = attrs.SRC_RGB;
16424 attrs.foreground = trueColor;
16425
16426 i += 5;
16427 } else {
16428 // Check for 256 color
16429 var c = get256(i);
16430 if (c == null)
16431 break;
16432
16433 i += 2;
16434
16435 if (c >= attrs.colorPalette.length)
16436 continue;
16437
16438 attrs.foregroundSource = c;
16439 }
16440
16441 } else if (arg == 39) {
16442 attrs.foregroundSource = attrs.SRC_DEFAULT;
16443
16444 } else if (arg < 48) {
16445 attrs.backgroundSource = arg - 40;
16446
16447 } else if (arg == 48) {
16448 // First check for true color definition
16449 var trueColor = getTrueColor(i);
16450 if (trueColor != null) {
16451 attrs.backgroundSource = attrs.SRC_RGB;
16452 attrs.background = trueColor;
16453
16454 i += 5;
16455 } else {
16456 // Check for 256 color
16457 var c = get256(i);
16458 if (c == null)
16459 break;
16460
16461 i += 2;
16462
16463 if (c >= attrs.colorPalette.length)
16464 continue;
16465
16466 attrs.backgroundSource = c;
16467 }
16468 } else {
16469 attrs.backgroundSource = attrs.SRC_DEFAULT;
16470 }
16471
16472 } else if (arg >= 90 && arg <= 97) {
16473 attrs.foregroundSource = arg - 90 + 8;
16474
16475 } else if (arg >= 100 && arg <= 107) {
16476 attrs.backgroundSource = arg - 100 + 8;
16477 }
16478 }
16479
16480 attrs.setDefaults(this.terminal.getForegroundColor(),
16481 this.terminal.getBackgroundColor());
16482};
16483
16484/**
16485 * Set xterm-specific keyboard modes.
16486 *
16487 * Will not implement.
16488 */
16489hterm.VT.CSI['>m'] = hterm.VT.ignore;
16490
16491/**
16492 * Device Status Report (DSR, DEC Specific).
16493 *
16494 * 5 - Status Report. Result (OK) is CSI 0 n
16495 * 6 - Report Cursor Position (CPR) [row;column]. Result is CSI r ; c R
16496 */
16497hterm.VT.CSI['n'] = function(parseState) {
16498 if (parseState.args[0] == '5') {
16499 this.terminal.io.sendString('\x1b0n');
16500 } else if (parseState.args[0] == '6') {
16501 var row = this.terminal.getCursorRow() + 1;
16502 var col = this.terminal.getCursorColumn() + 1;
16503 this.terminal.io.sendString('\x1b[' + row + ';' + col + 'R');
16504 }
16505};
16506
16507/**
16508 * Disable modifiers which may be enabled via CSI['>m'].
16509 *
16510 * Will not implement.
16511 */
16512hterm.VT.CSI['>n'] = hterm.VT.ignore;
16513
16514/**
16515 * Device Status Report (DSR, DEC Specific).
16516 *
16517 * 6 - Report Cursor Position (CPR) [row;column] as CSI ? r ; c R
16518 * 15 - Report Printer status as CSI ? 1 0 n (ready) or
16519 * CSI ? 1 1 n (not ready).
16520 * 25 - Report UDK status as CSI ? 2 0 n (unlocked) or CSI ? 2 1 n (locked).
16521 * 26 - Report Keyboard status as CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).
16522 * The last two parameters apply to VT400 & up, and denote keyboard ready
16523 * and LK01 respectively.
16524 * 53 - Report Locator status as CSI ? 5 3 n Locator available, if compiled-in,
16525 * or CSI ? 5 0 n No Locator, if not.
16526 */
16527hterm.VT.CSI['?n'] = function(parseState) {
16528 if (parseState.args[0] == '6') {
16529 var row = this.terminal.getCursorRow() + 1;
16530 var col = this.terminal.getCursorColumn() + 1;
16531 this.terminal.io.sendString('\x1b[' + row + ';' + col + 'R');
16532 } else if (parseState.args[0] == '15') {
16533 this.terminal.io.sendString('\x1b[?11n');
16534 } else if (parseState.args[0] == '25') {
16535 this.terminal.io.sendString('\x1b[?21n');
16536 } else if (parseState.args[0] == '26') {
16537 this.terminal.io.sendString('\x1b[?12;1;0;0n');
16538 } else if (parseState.args[0] == '53') {
16539 this.terminal.io.sendString('\x1b[?50n');
16540 }
16541};
16542
16543/**
16544 * This is used by xterm to decide whether to hide the pointer cursor as the
16545 * user types.
16546 *
16547 * Valid values for the parameter:
16548 * 0 - Never hide the pointer.
16549 * 1 - Hide if the mouse tracking mode is not enabled.
16550 * 2 - Always hide the pointer.
16551 *
16552 * If no parameter is given, xterm uses the default, which is 1.
16553 *
16554 * Not currently implemented.
16555 */
16556hterm.VT.CSI['>p'] = hterm.VT.ignore;
16557
16558/**
16559 * Soft terminal reset (DECSTR).
16560 */
16561hterm.VT.CSI['!p'] = function() {
16562 this.reset();
16563 this.terminal.softReset();
16564};
16565
16566/**
16567 * Request ANSI Mode (DECRQM).
16568 *
16569 * Not currently implemented.
16570 */
16571hterm.VT.CSI['$p'] = hterm.VT.ignore;
16572hterm.VT.CSI['?$p'] = hterm.VT.ignore;
16573
16574/**
16575 * Set conformance level (DECSCL).
16576 *
16577 * Not currently implemented.
16578 */
16579hterm.VT.CSI['"p'] = hterm.VT.ignore;
16580
16581/**
16582 * Load LEDs (DECLL).
16583 *
16584 * Not currently implemented. Could be implemented as virtual LEDs overlaying
16585 * the terminal if anyone cares.
16586 */
16587hterm.VT.CSI['q'] = hterm.VT.ignore;
16588
16589/**
16590 * Set cursor style (DECSCUSR, VT520).
16591 *
16592 * 0 - Blinking block.
16593 * 1 - Blinking block (default).
16594 * 2 - Steady block.
16595 * 3 - Blinking underline.
16596 * 4 - Steady underline.
16597 */
16598hterm.VT.CSI[' q'] = function(parseState) {
16599 var arg = parseState.args[0];
16600
16601 if (arg == '0' || arg == '1') {
16602 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
16603 this.terminal.setCursorBlink(true);
16604 } else if (arg == '2') {
16605 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
16606 this.terminal.setCursorBlink(false);
16607 } else if (arg == '3') {
16608 this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
16609 this.terminal.setCursorBlink(true);
16610 } else if (arg == '4') {
16611 this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
16612 this.terminal.setCursorBlink(false);
16613 } else {
16614 console.warn('Unknown cursor style: ' + arg);
16615 }
16616};
16617
16618/**
16619 * Select character protection attribute (DECSCA).
16620 *
16621 * Will not implement.
16622 */
16623hterm.VT.CSI['"q'] = hterm.VT.ignore;
16624
16625/**
16626 * Set Scrolling Region (DECSTBM).
16627 */
16628hterm.VT.CSI['r'] = function(parseState) {
16629 var args = parseState.args;
16630 var scrollTop = args[0] ? parseInt(args[0], 10) -1 : null;
16631 var scrollBottom = args[1] ? parseInt(args[1], 10) - 1 : null;
16632 this.terminal.setVTScrollRegion(scrollTop, scrollBottom);
16633 this.terminal.setCursorPosition(0, 0);
16634};
16635
16636/**
16637 * Restore DEC Private Mode Values.
16638 *
16639 * Will not implement.
16640 */
16641hterm.VT.CSI['?r'] = hterm.VT.ignore;
16642
16643/**
16644 * Change Attributes in Rectangular Area (DECCARA)
16645 *
16646 * Will not implement.
16647 */
16648hterm.VT.CSI['$r'] = hterm.VT.ignore;
16649
16650/**
16651 * Save cursor (ANSI.SYS)
16652 */
16653hterm.VT.CSI['s'] = function() {
16654 this.savedState_.save();
16655};
16656
16657/**
16658 * Save DEC Private Mode Values.
16659 *
16660 * Will not implement.
16661 */
16662hterm.VT.CSI['?s'] = hterm.VT.ignore;
16663
16664/**
16665 * Window manipulation (from dtterm, as well as extensions).
16666 *
16667 * Will not implement.
16668 */
16669hterm.VT.CSI['t'] = hterm.VT.ignore;
16670
16671/**
16672 * Reverse Attributes in Rectangular Area (DECRARA).
16673 *
16674 * Will not implement.
16675 */
16676hterm.VT.CSI['$t'] = hterm.VT.ignore;
16677
16678/**
16679 * Set one or more features of the title modes.
16680 *
16681 * Will not implement.
16682 */
16683hterm.VT.CSI['>t'] = hterm.VT.ignore;
16684
16685/**
16686 * Set warning-bell volume (DECSWBV, VT520).
16687 *
16688 * Will not implement.
16689 */
16690hterm.VT.CSI[' t'] = hterm.VT.ignore;
16691
16692/**
16693 * Restore cursor (ANSI.SYS).
16694 */
16695hterm.VT.CSI['u'] = function() {
16696 this.savedState_.restore();
16697};
16698
16699/**
16700 * Set margin-bell volume (DECSMBV, VT520).
16701 *
16702 * Will not implement.
16703 */
16704hterm.VT.CSI[' u'] = hterm.VT.ignore;
16705
16706/**
16707 * Copy Rectangular Area (DECCRA, VT400 and up).
16708 *
16709 * Will not implement.
16710 */
16711hterm.VT.CSI['$v'] = hterm.VT.ignore;
16712
16713/**
16714 * Enable Filter Rectangle (DECEFR).
16715 *
16716 * Will not implement.
16717 */
16718hterm.VT.CSI['\'w'] = hterm.VT.ignore;
16719
16720/**
16721 * Request Terminal Parameters (DECREQTPARM).
16722 *
16723 * Not currently implemented.
16724 */
16725hterm.VT.CSI['x'] = hterm.VT.ignore;
16726
16727/**
16728 * Select Attribute Change Extent (DECSACE).
16729 *
16730 * Will not implement.
16731 */
16732hterm.VT.CSI['*x'] = hterm.VT.ignore;
16733
16734/**
16735 * Fill Rectangular Area (DECFRA), VT420 and up.
16736 *
16737 * Will not implement.
16738 */
16739hterm.VT.CSI['$x'] = hterm.VT.ignore;
16740
16741/**
16742 * vt_tiledata (as used by NAOhack and UnNetHack)
16743 * (see https://nethackwiki.com/wiki/Vt_tiledata for more info)
16744 *
16745 * Implemented as far as we care (start a glyph and end a glyph).
16746 */
16747hterm.VT.CSI['z'] = function(parseState) {
16748 if (parseState.args.length < 1)
16749 return;
16750 var arg = parseState.args[0];
16751 if (arg == '0') {
16752 // Start a glyph (one parameter, the glyph number).
16753 if (parseState.args.length < 2)
16754 return;
16755 this.terminal.getTextAttributes().tileData = parseState.args[1];
16756 } else if (arg == '1') {
16757 // End a glyph.
16758 this.terminal.getTextAttributes().tileData = null;
16759 }
16760};
16761
16762/**
16763 * Enable Locator Reporting (DECELR).
16764 *
16765 * Not currently implemented.
16766 */
16767hterm.VT.CSI['\'z'] = hterm.VT.ignore;
16768
16769/**
16770 * Erase Rectangular Area (DECERA), VT400 and up.
16771 *
16772 * Will not implement.
16773 */
16774hterm.VT.CSI['$z'] = hterm.VT.ignore;
16775
16776/**
16777 * Select Locator Events (DECSLE).
16778 *
16779 * Not currently implemented.
16780 */
16781hterm.VT.CSI['\'{'] = hterm.VT.ignore;
16782
16783/**
16784 * Request Locator Position (DECRQLP).
16785 *
16786 * Not currently implemented.
16787 */
16788hterm.VT.CSI['\'|'] = hterm.VT.ignore;
16789
16790/**
16791 * Insert Columns (DECIC), VT420 and up.
16792 *
16793 * Will not implement.
16794 */
16795hterm.VT.CSI[' }'] = hterm.VT.ignore;
16796
16797/**
16798 * Delete P s Columns (DECDC), VT420 and up.
16799 *
16800 * Will not implement.
16801 */
16802hterm.VT.CSI[' ~'] = hterm.VT.ignore;
16803// SOURCE FILE: hterm/js/hterm_vt_character_map.js
16804// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
16805// Use of this source code is governed by a BSD-style license that can be
16806// found in the LICENSE file.
16807
16808'use strict';
16809
16810lib.rtdep('lib.f');
16811
16812/**
16813 * Character map object.
16814 *
16815 * @param {object} The GL mapping from input characters to output characters.
16816 * The GR mapping will be automatically created.
16817 */
16818hterm.VT.CharacterMap = function(name, glmap) {
16819 /**
16820 * Short name for this character set, useful for debugging.
16821 */
16822 this.name = name;
16823
16824 /**
16825 * The function to call to when this map is installed in GL.
16826 */
16827 this.GL = null;
16828
16829 /**
16830 * The function to call to when this map is installed in GR.
16831 */
16832 this.GR = null;
16833
16834 if (glmap)
16835 this.reset(glmap);
16836};
16837
16838/**
16839 * @param {object} The GL mapping from input characters to output characters.
16840 * The GR mapping will be automatically created.
16841 */
16842hterm.VT.CharacterMap.prototype.reset = function(glmap) {
16843 // Set the the GL mapping.
16844 this.glmap = glmap;
16845
16846 var glkeys = Object.keys(this.glmap).map(function(key) {
16847 return '\\x' + lib.f.zpad(key.charCodeAt(0).toString(16));
16848 });
16849
16850 this.glre = new RegExp('[' + glkeys.join('') + ']', 'g');
16851
16852 // Compute the GR mapping.
16853 // This is the same as GL except all keys have their MSB set.
16854 this.grmap = {};
16855
16856 glkeys.forEach(function(glkey) {
16857 var grkey = String.fromCharCode(glkey.charCodeAt(0) & 0x80);
16858 this.grmap[grkey] = this.glmap[glkey];
16859 }.bind(this));
16860
16861 var grkeys = Object.keys(this.grmap).map(function(key) {
16862 return '\\x' + lib.f.zpad(key.charCodeAt(0).toString(16), 2);
16863 });
16864
16865 this.grre = new RegExp('[' + grkeys.join('') + ']', 'g');
16866
16867 this.GL = function(str) {
16868 return str.replace(this.glre,
16869 function(ch) { return this.glmap[ch] }.bind(this));
16870 }.bind(this);
16871
16872 this.GR = function(str) {
16873 return str.replace(this.grre,
16874 function(ch) { return this.grmap[ch] }.bind(this));
16875 }.bind(this);
16876};
16877
16878/**
16879 * Mapping from received to display character, used depending on the active
16880 * VT character set.
16881 */
16882hterm.VT.CharacterMap.maps = {};
16883
16884/**
16885 * VT100 Graphic character map.
16886 * http://vt100.net/docs/vt220-rm/table2-4.html
16887 */
16888hterm.VT.CharacterMap.maps['0'] = new hterm.VT.CharacterMap(
16889 'graphic', {
16890 '\x60':'\u25c6', // ` -> diamond
16891 '\x61':'\u2592', // a -> grey-box
16892 '\x62':'\u2409', // b -> h/t
16893 '\x63':'\u240c', // c -> f/f
16894 '\x64':'\u240d', // d -> c/r
16895 '\x65':'\u240a', // e -> l/f
16896 '\x66':'\u00b0', // f -> degree
16897 '\x67':'\u00b1', // g -> +/-
16898 '\x68':'\u2424', // h -> n/l
16899 '\x69':'\u240b', // i -> v/t
16900 '\x6a':'\u2518', // j -> bottom-right
16901 '\x6b':'\u2510', // k -> top-right
16902 '\x6c':'\u250c', // l -> top-left
16903 '\x6d':'\u2514', // m -> bottom-left
16904 '\x6e':'\u253c', // n -> line-cross
16905 '\x6f':'\u23ba', // o -> scan1
16906 '\x70':'\u23bb', // p -> scan3
16907 '\x71':'\u2500', // q -> scan5
16908 '\x72':'\u23bc', // r -> scan7
16909 '\x73':'\u23bd', // s -> scan9
16910 '\x74':'\u251c', // t -> left-tee
16911 '\x75':'\u2524', // u -> right-tee
16912 '\x76':'\u2534', // v -> bottom-tee
16913 '\x77':'\u252c', // w -> top-tee
16914 '\x78':'\u2502', // x -> vertical-line
16915 '\x79':'\u2264', // y -> less-equal
16916 '\x7a':'\u2265', // z -> greater-equal
16917 '\x7b':'\u03c0', // { -> pi
16918 '\x7c':'\u2260', // | -> not-equal
16919 '\x7d':'\u00a3', // } -> british-pound
16920 '\x7e':'\u00b7', // ~ -> dot
16921 });
16922
16923/**
16924 * British character map.
16925 * http://vt100.net/docs/vt220-rm/table2-5.html
16926 */
16927hterm.VT.CharacterMap.maps['A'] = new hterm.VT.CharacterMap(
16928 'british', {
16929 '\x23': '\u00a3', // # -> british-pound
16930 });
16931
16932/**
16933 * US ASCII map, no changes.
16934 */
16935hterm.VT.CharacterMap.maps['B'] = new hterm.VT.CharacterMap(
16936 'us', null);
16937
16938/**
16939 * Dutch character map.
16940 * http://vt100.net/docs/vt220-rm/table2-6.html
16941 */
16942hterm.VT.CharacterMap.maps['4'] = new hterm.VT.CharacterMap(
16943 'dutch', {
16944 '\x23': '\u00a3', // # -> british-pound
16945
16946 '\x40': '\u00be', // @ -> 3/4
16947
16948 '\x5b': '\u0132', // [ -> 'ij' ligature (xterm goes with \u00ff?)
16949 '\x5c': '\u00bd', // \ -> 1/2
16950 '\x5d': '\u007c', // ] -> vertical bar
16951
16952 '\x7b': '\u00a8', // { -> two dots
16953 '\x7c': '\u0066', // | -> f
16954 '\x7d': '\u00bc', // } -> 1/4
16955 '\x7e': '\u00b4', // ~ -> acute
16956 });
16957
16958/**
16959 * Finnish character map.
16960 * http://vt100.net/docs/vt220-rm/table2-7.html
16961 */
16962hterm.VT.CharacterMap.maps['C'] =
16963hterm.VT.CharacterMap.maps['5'] = new hterm.VT.CharacterMap(
16964 'finnish', {
16965 '\x5b': '\u00c4', // [ -> 'A' umlaut
16966 '\x5c': '\u00d6', // \ -> 'O' umlaut
16967 '\x5d': '\u00c5', // ] -> 'A' ring
16968 '\x5e': '\u00dc', // ~ -> 'u' umlaut
16969
16970 '\x60': '\u00e9', // ` -> 'e' acute
16971
16972 '\x7b': '\u00e4', // { -> 'a' umlaut
16973 '\x7c': '\u00f6', // | -> 'o' umlaut
16974 '\x7d': '\u00e5', // } -> 'a' ring
16975 '\x7e': '\u00fc', // ~ -> 'u' umlaut
16976 });
16977
16978/**
16979 * French character map.
16980 * http://vt100.net/docs/vt220-rm/table2-8.html
16981 */
16982hterm.VT.CharacterMap.maps['R'] = new hterm.VT.CharacterMap(
16983 'french', {
16984 '\x23': '\u00a3', // # -> british-pound
16985
16986 '\x40': '\u00e0', // @ -> 'a' grave
16987
16988 '\x5b': '\u00b0', // [ -> ring
16989 '\x5c': '\u00e7', // \ -> 'c' cedilla
16990 '\x5d': '\u00a7', // ] -> section symbol (double s)
16991
16992 '\x7b': '\u00e9', // { -> 'e' acute
16993 '\x7c': '\u00f9', // | -> 'u' grave
16994 '\x7d': '\u00e8', // } -> 'e' grave
16995 '\x7e': '\u00a8', // ~ -> umlaut
16996 });
16997
16998/**
16999 * French Canadian character map.
17000 * http://vt100.net/docs/vt220-rm/table2-9.html
17001 */
17002hterm.VT.CharacterMap.maps['Q'] = new hterm.VT.CharacterMap(
17003 'french canadian', {
17004 '\x40': '\u00e0', // @ -> 'a' grave
17005
17006 '\x5b': '\u00e2', // [ -> 'a' circumflex
17007 '\x5c': '\u00e7', // \ -> 'c' cedilla
17008 '\x5d': '\u00ea', // ] -> 'e' circumflex
17009 '\x5e': '\u00ee', // ^ -> 'i' circumflex
17010
17011 '\x60': '\u00f4', // ` -> 'o' circumflex
17012
17013 '\x7b': '\u00e9', // { -> 'e' acute
17014 '\x7c': '\u00f9', // | -> 'u' grave
17015 '\x7d': '\u00e8', // } -> 'e' grave
17016 '\x7e': '\u00fb', // ~ -> 'u' circumflex
17017 });
17018
17019/**
17020 * German character map.
17021 * http://vt100.net/docs/vt220-rm/table2-10.html
17022 */
17023hterm.VT.CharacterMap.maps['K'] = new hterm.VT.CharacterMap(
17024 'german', {
17025 '\x40': '\u00a7', // @ -> section symbol (double s)
17026
17027 '\x5b': '\u00c4', // [ -> 'A' umlaut
17028 '\x5c': '\u00d6', // \ -> 'O' umlaut
17029 '\x5d': '\u00dc', // ] -> 'U' umlaut
17030
17031 '\x7b': '\u00e4', // { -> 'a' umlaut
17032 '\x7c': '\u00f6', // | -> 'o' umlaut
17033 '\x7d': '\u00fc', // } -> 'u' umlaut
17034 '\x7e': '\u00df', // ~ -> eszett
17035 });
17036
17037/**
17038 * Italian character map.
17039 * http://vt100.net/docs/vt220-rm/table2-11.html
17040 */
17041hterm.VT.CharacterMap.maps['Y'] = new hterm.VT.CharacterMap(
17042 'italian', {
17043 '\x23': '\u00a3', // # -> british-pound
17044
17045 '\x40': '\u00a7', // @ -> section symbol (double s)
17046
17047 '\x5b': '\u00b0', // [ -> ring
17048 '\x5c': '\u00e7', // \ -> 'c' cedilla
17049 '\x5d': '\u00e9', // ] -> 'e' acute
17050
17051 '\x60': '\u00f9', // ` -> 'u' grave
17052
17053 '\x7b': '\u00e0', // { -> 'a' grave
17054 '\x7c': '\u00f2', // | -> 'o' grave
17055 '\x7d': '\u00e8', // } -> 'e' grave
17056 '\x7e': '\u00ec', // ~ -> 'i' grave
17057 });
17058
17059/**
17060 * Norwegian/Danish character map.
17061 * http://vt100.net/docs/vt220-rm/table2-12.html
17062 */
17063hterm.VT.CharacterMap.maps['E'] =
17064hterm.VT.CharacterMap.maps['6'] = new hterm.VT.CharacterMap(
17065 'norwegian/danish', {
17066 '\x40': '\u00c4', // @ -> 'A' umlaut
17067
17068 '\x5b': '\u00c6', // [ -> 'AE' ligature
17069 '\x5c': '\u00d8', // \ -> 'O' stroke
17070 '\x5d': '\u00c5', // ] -> 'A' ring
17071 '\x5e': '\u00dc', // ^ -> 'U' umlaut
17072
17073 '\x60': '\u00e4', // ` -> 'a' umlaut
17074
17075 '\x7b': '\u00e6', // { -> 'ae' ligature
17076 '\x7c': '\u00f8', // | -> 'o' stroke
17077 '\x7d': '\u00e5', // } -> 'a' ring
17078 '\x7e': '\u00fc', // ~ -> 'u' umlaut
17079 });
17080
17081/**
17082 * Spanish character map.
17083 * http://vt100.net/docs/vt220-rm/table2-13.html
17084 */
17085hterm.VT.CharacterMap.maps['Z'] = new hterm.VT.CharacterMap(
17086 'spanish', {
17087 '\x23': '\u00a3', // # -> british-pound
17088
17089 '\x40': '\u00a7', // @ -> section symbol (double s)
17090
17091 '\x5b': '\u00a1', // [ -> '!' inverted
17092 '\x5c': '\u00d1', // \ -> 'N' tilde
17093 '\x5d': '\u00bf', // ] -> '?' inverted
17094
17095 '\x7b': '\u00b0', // { -> ring
17096 '\x7c': '\u00f1', // | -> 'n' tilde
17097 '\x7d': '\u00e7', // } -> 'c' cedilla
17098 });
17099
17100/**
17101 * Swedish character map.
17102 * http://vt100.net/docs/vt220-rm/table2-14.html
17103 */
17104hterm.VT.CharacterMap.maps['7'] =
17105hterm.VT.CharacterMap.maps['H'] = new hterm.VT.CharacterMap(
17106 'swedish', {
17107 '\x40': '\u00c9', // @ -> 'E' acute
17108
17109 '\x5b': '\u00c4', // [ -> 'A' umlaut
17110 '\x5c': '\u00d6', // \ -> 'O' umlaut
17111 '\x5d': '\u00c5', // ] -> 'A' ring
17112 '\x5e': '\u00dc', // ^ -> 'U' umlaut
17113
17114 '\x60': '\u00e9', // ` -> 'e' acute
17115
17116 '\x7b': '\u00e4', // { -> 'a' umlaut
17117 '\x7c': '\u00f6', // | -> 'o' umlaut
17118 '\x7d': '\u00e5', // } -> 'a' ring
17119 '\x7e': '\u00fc', // ~ -> 'u' umlaut
17120 });
17121
17122/**
17123 * Swiss character map.
17124 * http://vt100.net/docs/vt220-rm/table2-15.html
17125 */
17126hterm.VT.CharacterMap.maps['='] = new hterm.VT.CharacterMap(
17127 'swiss', {
17128 '\x23': '\u00f9', // # -> 'u' grave
17129
17130 '\x40': '\u00e0', // @ -> 'a' grave
17131
17132 '\x5b': '\u00e9', // [ -> 'e' acute
17133 '\x5c': '\u00e7', // \ -> 'c' cedilla
17134 '\x5d': '\u00ea', // ] -> 'e' circumflex
17135 '\x5e': '\u00ee', // ^ -> 'i' circumflex
17136 '\x5f': '\u00e8', // _ -> 'e' grave
17137
17138 '\x60': '\u00f4', // ` -> 'o' circumflex
17139
17140 '\x7b': '\u00e4', // { -> 'a' umlaut
17141 '\x7c': '\u00f6', // | -> 'o' umlaut
17142 '\x7d': '\u00fc', // } -> 'u' umlaut
17143 '\x7e': '\u00fb', // ~ -> 'u' circumflex
17144 });
17145lib.resource.add('hterm/audio/bell', 'audio/ogg;base64',
17146'T2dnUwACAAAAAAAAAADhqW5KAAAAAMFvEjYBHgF2b3JiaXMAAAAAAYC7AAAAAAAAAHcBAAAAAAC4' +
17147'AU9nZ1MAAAAAAAAAAAAA4aluSgEAAAAAesI3EC3//////////////////8kDdm9yYmlzHQAAAFhp' +
17148'cGguT3JnIGxpYlZvcmJpcyBJIDIwMDkwNzA5AAAAAAEFdm9yYmlzKUJDVgEACAAAADFMIMWA0JBV' +
17149'AAAQAABgJCkOk2ZJKaWUoSh5mJRISSmllMUwiZiUicUYY4wxxhhjjDHGGGOMIDRkFQAABACAKAmO' +
17150'o+ZJas45ZxgnjnKgOWlOOKcgB4pR4DkJwvUmY26mtKZrbs4pJQgNWQUAAAIAQEghhRRSSCGFFGKI' +
17151'IYYYYoghhxxyyCGnnHIKKqigggoyyCCDTDLppJNOOumoo4466ii00EILLbTSSkwx1VZjrr0GXXxz' +
17152'zjnnnHPOOeecc84JQkNWAQAgAAAEQgYZZBBCCCGFFFKIKaaYcgoyyIDQkFUAACAAgAAAAABHkRRJ' +
17153'sRTLsRzN0SRP8ixREzXRM0VTVE1VVVVVdV1XdmXXdnXXdn1ZmIVbuH1ZuIVb2IVd94VhGIZhGIZh' +
17154'GIZh+H3f933f930gNGQVACABAKAjOZbjKaIiGqLiOaIDhIasAgBkAAAEACAJkiIpkqNJpmZqrmmb' +
17155'tmirtm3LsizLsgyEhqwCAAABAAQAAAAAAKBpmqZpmqZpmqZpmqZpmqZpmqZpmmZZlmVZlmVZlmVZ' +
17156'lmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZQGjIKgBAAgBAx3Ecx3EkRVIkx3IsBwgNWQUAyAAA' +
17157'CABAUizFcjRHczTHczzHczxHdETJlEzN9EwPCA1ZBQAAAgAIAAAAAABAMRzFcRzJ0SRPUi3TcjVX' +
17158'cz3Xc03XdV1XVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYHQkFUAAAQAACGdZpZq' +
17159'gAgzkGEgNGQVAIAAAAAYoQhDDAgNWQUAAAQAAIih5CCa0JrzzTkOmuWgqRSb08GJVJsnuamYm3PO' +
17160'OeecbM4Z45xzzinKmcWgmdCac85JDJqloJnQmnPOeRKbB62p0ppzzhnnnA7GGWGcc85p0poHqdlY' +
17161'm3POWdCa5qi5FJtzzomUmye1uVSbc84555xzzjnnnHPOqV6czsE54Zxzzonam2u5CV2cc875ZJzu' +
17162'zQnhnHPOOeecc84555xzzglCQ1YBAEAAAARh2BjGnYIgfY4GYhQhpiGTHnSPDpOgMcgppB6NjkZK' +
17163'qYNQUhknpXSC0JBVAAAgAACEEFJIIYUUUkghhRRSSCGGGGKIIaeccgoqqKSSiirKKLPMMssss8wy' +
17164'y6zDzjrrsMMQQwwxtNJKLDXVVmONteaec645SGultdZaK6WUUkoppSA0ZBUAAAIAQCBkkEEGGYUU' +
17165'UkghhphyyimnoIIKCA1ZBQAAAgAIAAAA8CTPER3RER3RER3RER3RER3P8RxREiVREiXRMi1TMz1V' +
17166'VFVXdm1Zl3Xbt4Vd2HXf133f141fF4ZlWZZlWZZlWZZlWZZlWZZlCUJDVgEAIAAAAEIIIYQUUkgh' +
17167'hZRijDHHnINOQgmB0JBVAAAgAIAAAAAAR3EUx5EcyZEkS7IkTdIszfI0T/M00RNFUTRNUxVd0RV1' +
17168'0xZlUzZd0zVl01Vl1XZl2bZlW7d9WbZ93/d93/d93/d93/d939d1IDRkFQAgAQCgIzmSIimSIjmO' +
17169'40iSBISGrAIAZAAABACgKI7iOI4jSZIkWZImeZZniZqpmZ7pqaIKhIasAgAAAQAEAAAAAACgaIqn' +
17170'mIqniIrniI4oiZZpiZqquaJsyq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7rukBo' +
17171'yCoAQAIAQEdyJEdyJEVSJEVyJAcIDVkFAMgAAAgAwDEcQ1Ikx7IsTfM0T/M00RM90TM9VXRFFwgN' +
17172'WQUAAAIACAAAAAAAwJAMS7EczdEkUVIt1VI11VItVVQ9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV' +
17173'VVVVVVVVVVVV1TRN0zSB0JCVAAAZAAAjQQYZhBCKcpBCbj1YCDHmJAWhOQahxBiEpxAzDDkNInSQ' +
17174'QSc9uJI5wwzz4FIoFURMg40lN44gDcKmXEnlOAhCQ1YEAFEAAIAxyDHEGHLOScmgRM4xCZ2UyDkn' +
17175'pZPSSSktlhgzKSWmEmPjnKPSScmklBhLip2kEmOJrQAAgAAHAIAAC6HQkBUBQBQAAGIMUgophZRS' +
17176'zinmkFLKMeUcUko5p5xTzjkIHYTKMQadgxAppRxTzinHHITMQeWcg9BBKAAAIMABACDAQig0ZEUA' +
17177'ECcA4HAkz5M0SxQlSxNFzxRl1xNN15U0zTQ1UVRVyxNV1VRV2xZNVbYlTRNNTfRUVRNFVRVV05ZN' +
17178'VbVtzzRl2VRV3RZV1bZl2xZ+V5Z13zNNWRZV1dZNVbV115Z9X9ZtXZg0zTQ1UVRVTRRV1VRV2zZV' +
17179'17Y1UXRVUVVlWVRVWXZlWfdVV9Z9SxRV1VNN2RVVVbZV2fVtVZZ94XRVXVdl2fdVWRZ+W9eF4fZ9' +
17180'4RhV1dZN19V1VZZ9YdZlYbd13yhpmmlqoqiqmiiqqqmqtm2qrq1bouiqoqrKsmeqrqzKsq+rrmzr' +
17181'miiqrqiqsiyqqiyrsqz7qizrtqiquq3KsrCbrqvrtu8LwyzrunCqrq6rsuz7qizruq3rxnHrujB8' +
17182'pinLpqvquqm6um7runHMtm0co6rqvirLwrDKsu/rui+0dSFRVXXdlF3jV2VZ921fd55b94WybTu/' +
17183'rfvKceu60vg5z28cubZtHLNuG7+t+8bzKz9hOI6lZ5q2baqqrZuqq+uybivDrOtCUVV9XZVl3zdd' +
17184'WRdu3zeOW9eNoqrquirLvrDKsjHcxm8cuzAcXds2jlvXnbKtC31jyPcJz2vbxnH7OuP2daOvDAnH' +
17185'jwAAgAEHAIAAE8pAoSErAoA4AQAGIecUUxAqxSB0EFLqIKRUMQYhc05KxRyUUEpqIZTUKsYgVI5J' +
17186'yJyTEkpoKZTSUgehpVBKa6GU1lJrsabUYu0gpBZKaS2U0lpqqcbUWowRYxAy56RkzkkJpbQWSmkt' +
17187'c05K56CkDkJKpaQUS0otVsxJyaCj0kFIqaQSU0mptVBKa6WkFktKMbYUW24x1hxKaS2kEltJKcYU' +
17188'U20txpojxiBkzknJnJMSSmktlNJa5ZiUDkJKmYOSSkqtlZJSzJyT0kFIqYOOSkkptpJKTKGU1kpK' +
17189'sYVSWmwx1pxSbDWU0lpJKcaSSmwtxlpbTLV1EFoLpbQWSmmttVZraq3GUEprJaUYS0qxtRZrbjHm' +
17190'GkppraQSW0mpxRZbji3GmlNrNabWam4x5hpbbT3WmnNKrdbUUo0txppjbb3VmnvvIKQWSmktlNJi' +
17191'ai3G1mKtoZTWSiqxlZJabDHm2lqMOZTSYkmpxZJSjC3GmltsuaaWamwx5ppSi7Xm2nNsNfbUWqwt' +
17192'xppTS7XWWnOPufVWAADAgAMAQIAJZaDQkJUAQBQAAEGIUs5JaRByzDkqCULMOSepckxCKSlVzEEI' +
17193'JbXOOSkpxdY5CCWlFksqLcVWaykptRZrLQAAoMABACDABk2JxQEKDVkJAEQBACDGIMQYhAYZpRiD' +
17194'0BikFGMQIqUYc05KpRRjzknJGHMOQioZY85BKCmEUEoqKYUQSkklpQIAAAocAAACbNCUWByg0JAV' +
17195'AUAUAABgDGIMMYYgdFQyKhGETEonqYEQWgutddZSa6XFzFpqrbTYQAithdYySyXG1FpmrcSYWisA' +
17196'AOzAAQDswEIoNGQlAJAHAEAYoxRjzjlnEGLMOegcNAgx5hyEDirGnIMOQggVY85BCCGEzDkIIYQQ' +
17197'QuYchBBCCKGDEEIIpZTSQQghhFJK6SCEEEIppXQQQgihlFIKAAAqcAAACLBRZHOCkaBCQ1YCAHkA' +
17198'AIAxSjkHoZRGKcYglJJSoxRjEEpJqXIMQikpxVY5B6GUlFrsIJTSWmw1dhBKaS3GWkNKrcVYa64h' +
17199'pdZirDXX1FqMteaaa0otxlprzbkAANwFBwCwAxtFNicYCSo0ZCUAkAcAgCCkFGOMMYYUYoox55xD' +
17200'CCnFmHPOKaYYc84555RijDnnnHOMMeecc845xphzzjnnHHPOOeecc44555xzzjnnnHPOOeecc845' +
17201'55xzzgkAACpwAAAIsFFkc4KRoEJDVgIAqQAAABFWYowxxhgbCDHGGGOMMUYSYowxxhhjbDHGGGOM' +
17202'McaYYowxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHG' +
17203'GFtrrbXWWmuttdZaa6211lprrQBAvwoHAP8HG1ZHOCkaCyw0ZCUAEA4AABjDmHOOOQYdhIYp6KSE' +
17204'DkIIoUNKOSglhFBKKSlzTkpKpaSUWkqZc1JSKiWlllLqIKTUWkottdZaByWl1lJqrbXWOgiltNRa' +
17205'a6212EFIKaXWWostxlBKSq212GKMNYZSUmqtxdhirDGk0lJsLcYYY6yhlNZaazHGGGstKbXWYoy1' +
17206'xlprSam11mKLNdZaCwDgbnAAgEiwcYaVpLPC0eBCQ1YCACEBAARCjDnnnHMQQgghUoox56CDEEII' +
17207'IURKMeYcdBBCCCGEjDHnoIMQQgghhJAx5hx0EEIIIYQQOucchBBCCKGEUkrnHHQQQgghlFBC6SCE' +
17208'EEIIoYRSSikdhBBCKKGEUkopJYQQQgmllFJKKaWEEEIIoYQSSimllBBCCKWUUkoppZQSQgghlFJK' +
17209'KaWUUkIIoZRQSimllFJKCCGEUkoppZRSSgkhhFBKKaWUUkopIYQSSimllFJKKaUAAIADBwCAACPo' +
17210'JKPKImw04cIDUGjISgCADAAAcdhq6ynWyCDFnISWS4SQchBiLhFSijlHsWVIGcUY1ZQxpRRTUmvo' +
17211'nGKMUU+dY0oxw6yUVkookYLScqy1dswBAAAgCAAwECEzgUABFBjIAIADhAQpAKCwwNAxXAQE5BIy' +
17212'CgwKx4Rz0mkDABCEyAyRiFgMEhOqgaJiOgBYXGDIB4AMjY20iwvoMsAFXdx1IIQgBCGIxQEUkICD' +
17213'E2544g1PuMEJOkWlDgIAAAAA4AAAHgAAkg0gIiKaOY4Ojw+QEJERkhKTE5QAAAAAALABgA8AgCQF' +
17214'iIiIZo6jw+MDJERkhKTE5AQlAAAAAAAAAAAACAgIAAAAAAAEAAAACAhPZ2dTAAQYOwAAAAAAAOGp' +
17215'bkoCAAAAmc74DRgyNjM69TAzOTk74dnLubewsbagmZiNp4d0KbsExSY/I3XUTwJgkeZdn1HY4zoj' +
17216'33/q9DFtv3Ui1/jmx7lCUtPt18/sYf9MkgAsAGRBd3gMGP4sU+qCPYBy9VrA3YqJosW3W2/ef1iO' +
17217'/u3cg8ZG/57jU+pPmbGEJUgkfnaI39DbPqxddZphbMRmCc5rKlkUMkyx8iIoug5dJv1OYH9a59c+' +
17218'3Gevqc7Z2XFdDjL/qHztRfjWEWxJ/aiGezjohu9HsCZdQBKbiH0VtU/3m85lDG2T/+xkZcYnX+E+' +
17219'aqzv/xTgOoTFG+x7SNqQ4N+oAABSxuVXw77Jd5bmmTmuJakX7509HH0kGYKvARPpwfOSAPySPAc2' +
17220'EkneDwB2HwAAJlQDYK5586N79GJCjx4+p6aDUd27XSvRyXLJkIC5YZ1jLv5lpOhZTz0s+DmnF1di' +
17221'ptrnM6UDgIW11Xh8cHTd0/SmbgOAdxcyWwMAAGIrZ3fNSfZbzKiYrK4+tPqtnMVLOeWOG2kVvUY+' +
17222'p2PJ/hkCl5aFRO4TLGYPZcIU3vYM1hohS4jHFlnyW/2T5J7kGsShXWT8N05V+3C/GPqJ1QdWisGP' +
17223'xEzHqXISBPIinWDUt7IeJv/f5OtzBxpTzZZQ+CYEhHXfqG4aABQli72GJhN4oJv+hXcApAJSErAW' +
17224'8G2raAX4NUcABnVt77CzZAB+LsHcVe+Q4h+QB1wh/ZrJTPxSBdI8mgTeAdTsQOoFUEng9BHcVPhx' +
17225'SRRYkKWZJXOFYP6V4AEripJoEjXgA2wJRZHSExmJDm8F0A6gEXsg5a4ZsALItrMB7+fh7UKLvYWS' +
17226'dtsDwFf1mzYzS1F82N1h2Oyt2e76B1QdS0SAsQigLPMOgJS9JRC7hFXA6kUsLFNKD5cA5cTRvgSq' +
17227'Pc3Fl99xW3QTi/MHR8DEm6WnvaVQATwRqRKjywQ9BrrhugR2AKTsPQeQckrAOgDOhbTESyrXQ50C' +
17228'kNpXdtWjW7W2/3UjeX3U95gIdalfRAoAmqUEiwp53hCdcCwlg47fcbfzlmQMAgaBkh7c+fcDgF+i' +
17229'fwDXfzegLPcLYJsAAJQArTXjnh/uXGy3v1Hk3pV6/3t5ruW81f6prfbM2Q3WNVy98BwUtbCwhFhA' +
17230'WuPev6Oe/4ZaFQUcgKrVs4defzh1TADA1DEh5b3VlDaECw5b+bPfkKos3tIAue3vJZOih3ga3l6O' +
17231'3PSfIkrLv0PAS86PPdL7g8oc2KteNFKKzKRehOv2gJoFLBPXmaXvPBQILgJon0bbWBszrYZYYwE7' +
17232'jl2j+vTdU7Vpk21LiU0QajPkywAAHqbUC0/YsYOdb4e6BOp7E0cCi04Ao/TgD8ZVAMid6h/A8IeB' +
17233'Nkp6/xsAACZELEYIk+yvI6Qz1NN6lIftB/6IMWjWJNOqPTMedAmyaj6Es0QBklJpiSWWHnQ2CoYb' +
17234'GWAmt+0gLQBFKCBnp2QUUQZ/1thtZDBJUpFWY82z34ocorB62oX7qB5y0oPAv/foxH25wVmgIHf2' +
17235'xFOr8leZcBq1Kx3ZvCq9Bga639AxuHuPNL/71YCF4EywJpqHFAX6XF0sjVbuANnvvdLcrufYwOM/' +
17236'iDa6iA468AYAAB6mNBMXcgTD8HSRqJ4vw8CjAlCEPACASlX/APwPOJKl9xQAAAPmnev2eWp33Xgy' +
17237'w3Dvfz6myGk3oyP8YTKsCOvzAgALQi0o1c6Nzs2O2Pg2h4ACIJAgAGP0aNn5x0BDgVfH7u2TtyfD' +
17238'cRIuYAyQhBF/lvSRAttgA6TPbWZA9gaUrZWAUEAA+Dx47Q3/r87HxUUqZmB0BmUuMlojFjHt1gDu' +
17239'nnvuX8MImsjSq5WkzSzGS62OEIlOufWWezxWpv6FBgDgJVltfXFYtNAAnqU0xQoD0YLiXo5cF5QV' +
17240'4CnY1tBLAkZCOABAhbk/AM+/AwSCCdlWAAAMcFjS7owb8GVDzveDiZvznbt2tF4bL5odN1YKl88T' +
17241'AEABCZvufq9YCTBtMwVAQUEAwGtNltzSaHvADYC3TxLVjqiRA+OZAMhzcqEgRcAOwoCgvdTxsTHL' +
17242'QEF6+oOb2+PAI8ciPQcXg7pOY+LjxQSv2fjmFuj34gGwz310/bGK6z3xgT887eomWULEaDd04wHe' +
17243'tYxdjcgV2SxvSwn0VoZXJRqkRC5ASQ/muVoAUsX7AgAQMBNaVwAAlABRxT/1PmfqLqSRNDbhXb07' +
17244'berpB3b94jpuWEZjBCD2OcdXFpCKEgCDfcFPMw8AAADUwT4lnUm50lmwrpMMhPQIKj6u0E8fr2vG' +
17245'BngMNdIlrZsigjahljud6AFVg+tzXwUnXL3TJLpajaWKA4VAAAAMiFfqJgKAZ08XrtS3dxtQNYcp' +
17246'PvYEG8ClvrQRJgBephwnNWJjtGqmp6VEPSvBe7EBiU3qgJbQAwD4Le8LAMDMhHbNAAAlgK+tFs5O' +
17247'+YyJc9yCnJa3rxLPulGnxwsXV9Fsk2k4PisCAHC8FkwbGE9gJQAAoMnyksj0CdFMZLLgoz8M+Fxz' +
17248'iwYBgIx+zHiCBAKAlBKNpF1sO9JpVcyEi9ar15YlHgrut5fPJnkdJ6vEwZPyAHQBIEDUrlMcBAAd' +
17249'2KAS0Qq+JwRsE4AJZtMnAD6GnOYwYlOIZvtzUNdjreB7fiMkWI0CmBB6AIAKc38A9osEFlTSGECB' +
17250'+cbeRDC0aRpLHqNPplcK/76Lxn2rpmqyXsYJWRi/FQAAAKBQk9MCAOibrQBQADCDsqpooPutd+05' +
17251'Ce9g6iEdiYXgVmQAI4+4wskEBEiBloNQ6Ki0/KTQ0QjWfjxzi+AeuXKoMjEVfQOZzr0y941qLgM2' +
17252'AExvbZOqcxZ6J6krlrj4y2j9AdgKDx6GnJsVLhbc42uq584+ouSdNBpoCiCVHrz+WzUA/DDtD8AT' +
17253'gA3h0lMCAAzcFv+S+fSSNkeYWlTpb34mf2RfmqqJeMeklhHAfu7VoAEACgAApKRktL+KkQDWMwYC' +
17254'UAAAAHCKsp80xhp91UjqQBw3x45cetqkjQEyu3G9B6N+R650Uq8OVig7wOm6Wun0ea4lKDPoabJs' +
17255'6aLqgbhPzpv4KR4iODilw88ZpY7q1IOMcbASAOAVtmcCnobcrkG4KGS7/ZnskVWRNF9J0RUHKOnB' +
17256'yy9WA8Dv6L4AAARMCQUA4GritfVM2lcZfH3Q3T/vZ47J2YHhcmBazjfdyuV25gLAzrc0cwAAAAAY' +
17257'Ch6PdwAAAGyWjFW4yScjaWa2mGcofHxWxewKALglWBpLUvwwk+UOh5eNGyUOs1/EF+pZr+ud5Ozo' +
17258'GwYdAABg2p52LiSgAY/ZVlOmilEgHn6G3OcwYjzI7vOj1t6xsx4S3lBY96EUQBF6AIBAmPYH4PoG' +
17259'YCoJAADWe+OZJZi7/x76/yH7Lzf9M5XzRKnFPmveMsilQHwVAAAAAKB3LQD8PCIAAADga0QujBLy' +
17260'wzeJ4a6Z/ERVBAUlAEDqvoM7BQBAuAguzFqILtmjH3Kd4wfKobnOhA3z85qWoRPm9hwoOHoDAAlC' +
17261'bwDAA56FHAuXflHo3fe2ttG9XUDeA9YmYCBQ0oPr/1QC8IvuCwAAApbUAQCK22MmE3O78VAbHQT9' +
17262'PIPNoT9zNc3l2Oe7TAVLANBufT8MAQAAAGzT4PS8AQAAoELGHb2uaCwwEv1EWhFriUkbAaAZ27/f' +
17263'VZnTZXbWz3BwWpjUaMZKRj7dZ0J//gUeTdpVEwAAZOFsNxKAjQSgA+ABPoY8Jj5y2wje81jsXc/1' +
17264'TOQWTDYZBmAkNDiqVwuA2NJ9AQAAEBKAt9Vrsfs/2N19MO91S9rd8EHTZHnzC5MYmfQEACy/FBcA' +
17265'AADA5c4gi4z8RANs/m6FNXVo9DV46JG1BBDukqlw/Va5G7QbuGVSI+2aZaoLXJrdVj2zlC9Z5QEA' +
17266'EFz/5QzgVZwAAAAA/oXcxyC6WfTu+09Ve/c766J4VTAGUFmA51+VANKi/QPoPwYgYAkA715OH4S0' +
17267's5KDHvj99MMq8TPFc3roKZnGOoT1bmIhVgc7XAMBAAAAAMAW1VbQw3gapzOpJd+Kd2fc4iSO62fJ' +
17268'v9+movui1wUNPAj059N3OVxzk4gV73PmE8FIA2F5mRq37Evc76vLXfF4rD5UJJAw46hW6LZCb5sN' +
17269'Ldx+kzMCAAB+hfy95+965ZCLP7B3/VlTHCvDEKtQhTm4KiCgAEAbrfbWTPssAAAAXpee1tVrozYY' +
17270'n41wD1aeYtkKfswN5/SXPO0JDnhO/4laUortv/s412fybe/nONdncoCHnBVliu0CQGBWlPY/5Kwo' +
17271'm2L/kruPM6Q7oz4tvDQy+bZ3HzOi+gNHA4DZEgA=' +
17272''
17273);
17274
17275lib.resource.add('hterm/concat/date', 'text/plain',
17276'Tue, 25 Apr 2017 15:12:45 +0000' +
17277''
17278);
17279
17280lib.resource.add('hterm/changelog/version', 'text/plain',
17281'1.62' +
17282''
17283);
17284
17285lib.resource.add('hterm/changelog/date', 'text/plain',
17286'2017-04-17' +
17287''
17288);
17289
17290lib.resource.add('hterm/git/HEAD', 'text/plain',
17291'git rev-parse HEAD' +
17292''
17293);
17294
17295