blob: a4b6fbf1cbaae3383b6d77695b1396abbdf06d26 [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();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070093 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050094 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+/, '');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070099 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500100 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);
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700108 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500109 var ary = this.runtimeDependencies_[path];
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700110 if (!ary) ary = this.runtimeDependencies_[path] = [];
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500111 ary.push(source);
112 }
113 }
114};
115
116/**
117 * Ensures that all runtime dependencies are met, or an exception is thrown.
118 *
119 * Every unmet runtime dependency will be logged to the JS console. If at
120 * least one dependency is unmet this will raise an exception.
121 */
122lib.ensureRuntimeDependencies_ = function() {
123 var passed = true;
124
125 for (var path in lib.runtimeDependencies_) {
126 var sourceList = lib.runtimeDependencies_[path];
127 var names = path.split('.');
128
129 // In a document context 'window' is the global object. In a worker it's
130 // called 'self'.
131 var obj = (window || self);
132 for (var i = 0; i < names.length; i++) {
133 if (!(names[i] in obj)) {
134 console.warn('Missing "' + path + '" is needed by', sourceList);
135 passed = false;
136 break;
137 }
138
139 obj = obj[names[i]];
140 }
141 }
142
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700143 if (!passed) throw new Error('Failed runtime dependency check');
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500144};
145
146/**
147 * Register an initialization function.
148 *
149 * The initialization functions are invoked in registration order when
150 * lib.init() is invoked. Each function will receive a single parameter, which
151 * is a function to be invoked when it completes its part of the initialization.
152 *
153 * @param {string} name A short descriptive name of the init routine useful for
154 * debugging.
155 * @param {function(function)} callback The initialization function to register.
156 * @return {function} The callback parameter.
157 */
158lib.registerInit = function(name, callback) {
159 lib.initCallbacks_.push([name, callback]);
160 return callback;
161};
162
163/**
164 * Initialize the library.
165 *
166 * This will ensure that all registered runtime dependencies are met, and
167 * invoke any registered initialization functions.
168 *
169 * Initialization is asynchronous. The library is not ready for use until
170 * the onInit function is invoked.
171 *
172 * @param {function()} onInit The function to invoke when initialization is
173 * complete.
174 * @param {function(*)} opt_logFunction An optional function to send
175 * initialization related log messages to.
176 */
177lib.init = function(onInit, opt_logFunction) {
178 var ary = lib.initCallbacks_;
179
180 var initNext = function() {
181 if (ary.length) {
182 var rec = ary.shift();
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700183 if (opt_logFunction) opt_logFunction('init: ' + rec[0]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500184 rec[1](lib.f.alarm(initNext));
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700185 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500186 onInit();
187 }
188 };
189
190 if (typeof onInit != 'function')
191 throw new Error('Missing or invalid argument: onInit');
192
193 lib.ensureRuntimeDependencies_();
194
195 setTimeout(initNext, 0);
196};
197// SOURCE FILE: libdot/js/lib_colors.js
198// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
199// Use of this source code is governed by a BSD-style license that can be
200// found in the LICENSE file.
201
202'use strict';
203
204/**
205 * Namespace for color utilities.
206 */
207lib.colors = {};
208
209/**
210 * First, some canned regular expressions we're going to use in this file.
211 *
212 *
213 * BRACE YOURSELF
214 *
215 * ,~~~~.
216 * |>_< ~~
217 * 3`---'-/.
218 * 3:::::\v\
219 * =o=:::::\,\
220 * | :::::\,,\
221 *
222 * THE REGULAR EXPRESSIONS
223 * ARE COMING.
224 *
225 * There's no way to break long RE literals in JavaScript. Fix that why don't
226 * you? Oh, and also there's no way to write a string that doesn't interpret
227 * escapes.
228 *
229 * Instead, we stoop to this .replace() trick.
230 */
231lib.colors.re_ = {
232 // CSS hex color, #RGB.
233 hex16: /#([a-f0-9])([a-f0-9])([a-f0-9])/i,
234
235 // CSS hex color, #RRGGBB.
236 hex24: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i,
237
238 // CSS rgb color, rgb(rrr,ggg,bbb).
239 rgb: new RegExp(
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700240 ('^/s*rgb/s*/(/s*(/d{1,3})/s*,/s*(/d{1,3})/s*,' +
241 '/s*(/d{1,3})/s*/)/s*$')
242 .replace(/\//g, '\\'),
243 'i'),
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500244
245 // CSS rgb color, rgb(rrr,ggg,bbb,aaa).
246 rgba: new RegExp(
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700247 ('^/s*rgba/s*' +
248 '/(/s*(/d{1,3})/s*,/s*(/d{1,3})/s*,/s*(/d{1,3})/s*' +
249 '(?:,/s*(/d+(?:/./d+)?)/s*)/)/s*$')
250 .replace(/\//g, '\\'),
251 'i'),
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500252
253 // Either RGB or RGBA.
254 rgbx: new RegExp(
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700255 ('^/s*rgba?/s*' +
256 '/(/s*(/d{1,3})/s*,/s*(/d{1,3})/s*,/s*(/d{1,3})/s*' +
257 '(?:,/s*(/d+(?:/./d+)?)/s*)?/)/s*$')
258 .replace(/\//g, '\\'),
259 'i'),
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500260
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);
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700286 if (!ary) return null;
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500287
288 return 'rgb:' + scale(ary[1]) + '/' + scale(ary[2]) + '/' + scale(ary[3]);
289};
290
291/**
292 * Convert a legacy X11 colover value into an CSS rgb(...) color value.
293 *
294 * They take the form:
295 * 12 bit: #RGB -> #R000G000B000
296 * 24 bit: #RRGGBB -> #RR00GG00BB00
297 * 36 bit: #RRRGGGBBB -> #RRR0GGG0BBB0
298 * 48 bit: #RRRRGGGGBBBB
299 * These are the most significant bits.
300 *
301 * Truncate values back down to 24 bit since that's all CSS supports.
302 */
303lib.colors.x11HexToCSS = function(v) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700304 if (!v.startsWith('#')) return null;
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500305 // Strip the leading # off.
306 v = v.substr(1);
307
308 // Reject unknown sizes.
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700309 if ([3, 6, 9, 12].indexOf(v.length) == -1) return null;
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500310
311 // Reject non-hex values.
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700312 if (v.match(/[^a-f0-9]/i)) return null;
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500313
314 // Split the colors out.
315 var size = v.length / 3;
316 var r = v.substr(0, size);
317 var g = v.substr(size, size);
318 var b = v.substr(size + size, size);
319
320 // Normalize to 16 bits.
321 function norm16(v) {
322 v = parseInt(v, 16);
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700323 return size == 2 ? v : // 16 bit
324 size == 1 ? v << 4 : // 8 bit
325 v >> (4 * (size - 2)); // 24 or 32 bit
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500326 }
327 return lib.colors.arrayToRGBA([r, g, b].map(norm16));
328};
329
330/**
331 * Convert an X11 color value into an CSS rgb(...) color value.
332 *
333 * The X11 value may be an X11 color name, or an RGB value of the form
334 * rgb:hhhh/hhhh/hhhh. If a component value is less than 4 digits it is
335 * padded out to 4, then scaled down to fit in a single byte.
336 *
337 * @param {string} value The X11 color value to convert.
338 * @return {string} The CSS color value or null if the value could not be
339 * converted.
340 */
341lib.colors.x11ToCSS = function(v) {
342 function scale(v) {
343 // Pad out values with less than four digits. This padding (probably)
344 // matches xterm. It's difficult to say for sure since xterm seems to
345 // arrive at a padded value and then perform some combination of
346 // gamma correction, color space transformation, and quantization.
347
348 if (v.length == 1) {
349 // Single digits pad out to four by repeating the character. "f" becomes
350 // "ffff". Scaling down a hex value of this pattern by 257 is the same
351 // as cutting off one byte. We skip the middle step and just double
352 // the character.
353 return parseInt(v + v, 16);
354 }
355
356 if (v.length == 2) {
357 // Similar deal here. X11 pads two digit values by repeating the
358 // byte (or scale up by 257). Since we're going to scale it back
359 // down anyway, we can just return the original value.
360 return parseInt(v, 16);
361 }
362
363 if (v.length == 3) {
364 // Three digit values seem to be padded by repeating the final digit.
365 // e.g. 10f becomes 10ff.
366 v = v + v.substr(2);
367 }
368
369 // Scale down the 2 byte value.
370 return Math.round(parseInt(v, 16) / 257);
371 }
372
373 var ary = v.match(lib.colors.re_.x11rgb);
374 if (!ary) {
375 // Handle the legacy format.
376 if (v.startsWith('#'))
377 return lib.colors.x11HexToCSS(v);
378 else
379 return lib.colors.nameToRGB(v);
380 }
381
382 ary.splice(0, 1);
383 return lib.colors.arrayToRGBA(ary.map(scale));
384};
385
386/**
387 * Converts one or more CSS '#RRGGBB' color values into their rgb(...)
388 * form.
389 *
390 * Arrays are converted in place. If a value cannot be converted, it is
391 * replaced with null.
392 *
393 * @param {string|Array.<string>} A single RGB value or array of RGB values to
394 * convert.
395 * @return {string|Array.<string>} The converted value or values.
396 */
397lib.colors.hexToRGB = function(arg) {
398 var hex16 = lib.colors.re_.hex16;
399 var hex24 = lib.colors.re_.hex24;
400
401 function convert(hex) {
402 if (hex.length == 4) {
403 hex = hex.replace(hex16, function(h, r, g, b) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -0700404 return '#' + r + r + g + g + b + b;
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500405 });
406 }
407 var ary = hex.match(hex24);
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700408 if (!ary) return null;
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500409
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700410 return 'rgb(' + parseInt(ary[1], 16) + ', ' + parseInt(ary[2], 16) + ', ' +
411 parseInt(ary[3], 16) + ')';
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500412 }
413
414 if (arg instanceof Array) {
415 for (var i = 0; i < arg.length; i++) {
416 arg[i] = convert(arg[i]);
417 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700418 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500419 arg = convert(arg);
420 }
421
422 return arg;
423};
424
425/**
426 * Converts one or more CSS rgb(...) forms into their '#RRGGBB' color values.
427 *
428 * If given an rgba(...) form, the alpha field is thrown away.
429 *
430 * Arrays are converted in place. If a value cannot be converted, it is
431 * replaced with null.
432 *
433 * @param {string|Array.<string>} A single rgb(...) value or array of rgb(...)
434 * values to convert.
435 * @return {string|Array.<string>} The converted value or values.
436 */
437lib.colors.rgbToHex = function(arg) {
438 function convert(rgb) {
439 var ary = lib.colors.crackRGB(rgb);
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700440 if (!ary) return null;
441 return '#' +
442 lib.f.zpad(
443 ((parseInt(ary[0]) << 16) | (parseInt(ary[1]) << 8) |
444 (parseInt(ary[2]) << 0))
445 .toString(16),
446 6);
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500447 }
448
449 if (arg instanceof Array) {
450 for (var i = 0; i < arg.length; i++) {
451 arg[i] = convert(arg[i]);
452 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700453 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500454 arg = convert(arg);
455 }
456
457 return arg;
458};
459
460/**
461 * Take any valid css color definition and turn it into an rgb or rgba value.
462 *
463 * Returns null if the value could not be normalized.
464 */
465lib.colors.normalizeCSS = function(def) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700466 if (def.substr(0, 1) == '#') return lib.colors.hexToRGB(def);
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500467
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700468 if (lib.colors.re_.rgbx.test(def)) return def;
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500469
470 return lib.colors.nameToRGB(def);
471};
472
473/**
474 * Convert a 3 or 4 element array into an rgba(...) string.
475 */
476lib.colors.arrayToRGBA = function(ary) {
477 var alpha = (ary.length > 3) ? ary[3] : 1;
478 return 'rgba(' + ary[0] + ', ' + ary[1] + ', ' + ary[2] + ', ' + alpha + ')';
479};
480
481/**
482 * Overwrite the alpha channel of an rgb/rgba color.
483 */
484lib.colors.setAlpha = function(rgb, alpha) {
485 var ary = lib.colors.crackRGB(rgb);
486 ary[3] = alpha;
487 return lib.colors.arrayToRGBA(ary);
488};
489
490/**
491 * Mix a percentage of a tint color into a base color.
492 */
493lib.colors.mix = function(base, tint, percent) {
494 var ary1 = lib.colors.crackRGB(base);
495 var ary2 = lib.colors.crackRGB(tint);
496
497 for (var i = 0; i < 4; ++i) {
498 var diff = ary2[i] - ary1[i];
499 ary1[i] = Math.round(parseInt(ary1[i]) + diff * percent);
500 }
501
502 return lib.colors.arrayToRGBA(ary1);
503};
504
505/**
506 * Split an rgb/rgba color into an array of its components.
507 *
508 * On success, a 4 element array will be returned. For rgb values, the alpha
509 * will be set to 1.
510 */
511lib.colors.crackRGB = function(color) {
512 if (color.substr(0, 4) == 'rgba') {
513 var ary = color.match(lib.colors.re_.rgba);
514 if (ary) {
515 ary.shift();
516 return ary;
517 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700518 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500519 var ary = color.match(lib.colors.re_.rgb);
520 if (ary) {
521 ary.shift();
522 ary.push(1);
523 return ary;
524 }
525 }
526
527 console.error('Couldn\'t crack: ' + color);
528 return null;
529};
530
531/**
532 * Convert an X11 color name into a CSS rgb(...) value.
533 *
534 * Names are stripped of spaces and converted to lowercase. If the name is
535 * unknown, null is returned.
536 *
537 * This list of color name to RGB mapping is derived from the stock X11
538 * rgb.txt file.
539 *
540 * @param {string} name The color name to convert.
541 * @return {string} The corresponding CSS rgb(...) value.
542 */
543lib.colors.nameToRGB = function(name) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700544 if (name in lib.colors.colorNames) return lib.colors.colorNames[name];
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500545
546 name = name.toLowerCase();
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700547 if (name in lib.colors.colorNames) return lib.colors.colorNames[name];
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500548
549 name = name.replace(/\s+/g, '');
Andrew Geisslerd27bb132018-05-24 11:07:27 -0700550 if (name in lib.colors.colorNames) return lib.colors.colorNames[name];
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500551
552 return null;
553};
554
555/**
556 * The stock color palette.
557 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -0700558lib.colors.stockColorPalette = lib.colors.hexToRGB([ // The "ANSI 16"...
559 '#000000', '#CC0000', '#4E9A06', '#C4A000',
560 '#3465A4', '#75507B', '#06989A', '#D3D7CF',
561 '#555753', '#EF2929', '#00BA13', '#FCE94F',
562 '#729FCF', '#F200CB', '#00B5BD', '#EEEEEC',
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500563
Andrew Geisslerba5e3f32018-05-24 10:58:00 -0700564 // The 6x6 color cubes...
565 '#000000', '#00005F', '#000087', '#0000AF', '#0000D7', '#0000FF',
566 '#005F00', '#005F5F', '#005F87', '#005FAF', '#005FD7', '#005FFF',
567 '#008700', '#00875F', '#008787', '#0087AF', '#0087D7', '#0087FF',
568 '#00AF00', '#00AF5F', '#00AF87', '#00AFAF', '#00AFD7', '#00AFFF',
569 '#00D700', '#00D75F', '#00D787', '#00D7AF', '#00D7D7', '#00D7FF',
570 '#00FF00', '#00FF5F', '#00FF87', '#00FFAF', '#00FFD7', '#00FFFF',
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500571
Andrew Geisslerba5e3f32018-05-24 10:58:00 -0700572 '#5F0000', '#5F005F', '#5F0087', '#5F00AF', '#5F00D7', '#5F00FF',
573 '#5F5F00', '#5F5F5F', '#5F5F87', '#5F5FAF', '#5F5FD7', '#5F5FFF',
574 '#5F8700', '#5F875F', '#5F8787', '#5F87AF', '#5F87D7', '#5F87FF',
575 '#5FAF00', '#5FAF5F', '#5FAF87', '#5FAFAF', '#5FAFD7', '#5FAFFF',
576 '#5FD700', '#5FD75F', '#5FD787', '#5FD7AF', '#5FD7D7', '#5FD7FF',
577 '#5FFF00', '#5FFF5F', '#5FFF87', '#5FFFAF', '#5FFFD7', '#5FFFFF',
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500578
Andrew Geisslerba5e3f32018-05-24 10:58:00 -0700579 '#870000', '#87005F', '#870087', '#8700AF', '#8700D7', '#8700FF',
580 '#875F00', '#875F5F', '#875F87', '#875FAF', '#875FD7', '#875FFF',
581 '#878700', '#87875F', '#878787', '#8787AF', '#8787D7', '#8787FF',
582 '#87AF00', '#87AF5F', '#87AF87', '#87AFAF', '#87AFD7', '#87AFFF',
583 '#87D700', '#87D75F', '#87D787', '#87D7AF', '#87D7D7', '#87D7FF',
584 '#87FF00', '#87FF5F', '#87FF87', '#87FFAF', '#87FFD7', '#87FFFF',
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500585
Andrew Geisslerba5e3f32018-05-24 10:58:00 -0700586 '#AF0000', '#AF005F', '#AF0087', '#AF00AF', '#AF00D7', '#AF00FF',
587 '#AF5F00', '#AF5F5F', '#AF5F87', '#AF5FAF', '#AF5FD7', '#AF5FFF',
588 '#AF8700', '#AF875F', '#AF8787', '#AF87AF', '#AF87D7', '#AF87FF',
589 '#AFAF00', '#AFAF5F', '#AFAF87', '#AFAFAF', '#AFAFD7', '#AFAFFF',
590 '#AFD700', '#AFD75F', '#AFD787', '#AFD7AF', '#AFD7D7', '#AFD7FF',
591 '#AFFF00', '#AFFF5F', '#AFFF87', '#AFFFAF', '#AFFFD7', '#AFFFFF',
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500592
Andrew Geisslerba5e3f32018-05-24 10:58:00 -0700593 '#D70000', '#D7005F', '#D70087', '#D700AF', '#D700D7', '#D700FF',
594 '#D75F00', '#D75F5F', '#D75F87', '#D75FAF', '#D75FD7', '#D75FFF',
595 '#D78700', '#D7875F', '#D78787', '#D787AF', '#D787D7', '#D787FF',
596 '#D7AF00', '#D7AF5F', '#D7AF87', '#D7AFAF', '#D7AFD7', '#D7AFFF',
597 '#D7D700', '#D7D75F', '#D7D787', '#D7D7AF', '#D7D7D7', '#D7D7FF',
598 '#D7FF00', '#D7FF5F', '#D7FF87', '#D7FFAF', '#D7FFD7', '#D7FFFF',
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500599
Andrew Geisslerba5e3f32018-05-24 10:58:00 -0700600 '#FF0000', '#FF005F', '#FF0087', '#FF00AF', '#FF00D7', '#FF00FF',
601 '#FF5F00', '#FF5F5F', '#FF5F87', '#FF5FAF', '#FF5FD7', '#FF5FFF',
602 '#FF8700', '#FF875F', '#FF8787', '#FF87AF', '#FF87D7', '#FF87FF',
603 '#FFAF00', '#FFAF5F', '#FFAF87', '#FFAFAF', '#FFAFD7', '#FFAFFF',
604 '#FFD700', '#FFD75F', '#FFD787', '#FFD7AF', '#FFD7D7', '#FFD7FF',
605 '#FFFF00', '#FFFF5F', '#FFFF87', '#FFFFAF', '#FFFFD7', '#FFFFFF',
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500606
Andrew Geisslerba5e3f32018-05-24 10:58:00 -0700607 // The greyscale ramp...
608 '#080808', '#121212', '#1C1C1C', '#262626', '#303030', '#3A3A3A',
609 '#444444', '#4E4E4E', '#585858', '#626262', '#6C6C6C', '#767676',
610 '#808080', '#8A8A8A', '#949494', '#9E9E9E', '#A8A8A8', '#B2B2B2',
611 '#BCBCBC', '#C6C6C6', '#D0D0D0', '#DADADA', '#E4E4E4', '#EEEEEE'
612]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -0500613
614/**
615 * The current color palette, possibly with user changes.
616 */
617lib.colors.colorPalette = lib.colors.stockColorPalette;
618
619/**
620 * Named colors according to the stock X11 rgb.txt file.
621 */
622lib.colors.colorNames = {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -0700623 'aliceblue': 'rgb(240, 248, 255)',
624 'antiquewhite': 'rgb(250, 235, 215)',
625 'antiquewhite1': 'rgb(255, 239, 219)',
626 'antiquewhite2': 'rgb(238, 223, 204)',
627 'antiquewhite3': 'rgb(205, 192, 176)',
628 'antiquewhite4': 'rgb(139, 131, 120)',
629 'aquamarine': 'rgb(127, 255, 212)',
630 'aquamarine1': 'rgb(127, 255, 212)',
631 'aquamarine2': 'rgb(118, 238, 198)',
632 'aquamarine3': 'rgb(102, 205, 170)',
633 'aquamarine4': 'rgb(69, 139, 116)',
634 'azure': 'rgb(240, 255, 255)',
635 'azure1': 'rgb(240, 255, 255)',
636 'azure2': 'rgb(224, 238, 238)',
637 'azure3': 'rgb(193, 205, 205)',
638 'azure4': 'rgb(131, 139, 139)',
639 'beige': 'rgb(245, 245, 220)',
640 'bisque': 'rgb(255, 228, 196)',
641 'bisque1': 'rgb(255, 228, 196)',
642 'bisque2': 'rgb(238, 213, 183)',
643 'bisque3': 'rgb(205, 183, 158)',
644 'bisque4': 'rgb(139, 125, 107)',
645 'black': 'rgb(0, 0, 0)',
646 'blanchedalmond': 'rgb(255, 235, 205)',
647 'blue': 'rgb(0, 0, 255)',
648 'blue1': 'rgb(0, 0, 255)',
649 'blue2': 'rgb(0, 0, 238)',
650 'blue3': 'rgb(0, 0, 205)',
651 'blue4': 'rgb(0, 0, 139)',
652 'blueviolet': 'rgb(138, 43, 226)',
653 'brown': 'rgb(165, 42, 42)',
654 'brown1': 'rgb(255, 64, 64)',
655 'brown2': 'rgb(238, 59, 59)',
656 'brown3': 'rgb(205, 51, 51)',
657 'brown4': 'rgb(139, 35, 35)',
658 'burlywood': 'rgb(222, 184, 135)',
659 'burlywood1': 'rgb(255, 211, 155)',
660 'burlywood2': 'rgb(238, 197, 145)',
661 'burlywood3': 'rgb(205, 170, 125)',
662 'burlywood4': 'rgb(139, 115, 85)',
663 'cadetblue': 'rgb(95, 158, 160)',
664 'cadetblue1': 'rgb(152, 245, 255)',
665 'cadetblue2': 'rgb(142, 229, 238)',
666 'cadetblue3': 'rgb(122, 197, 205)',
667 'cadetblue4': 'rgb(83, 134, 139)',
668 'chartreuse': 'rgb(127, 255, 0)',
669 'chartreuse1': 'rgb(127, 255, 0)',
670 'chartreuse2': 'rgb(118, 238, 0)',
671 'chartreuse3': 'rgb(102, 205, 0)',
672 'chartreuse4': 'rgb(69, 139, 0)',
673 'chocolate': 'rgb(210, 105, 30)',
674 'chocolate1': 'rgb(255, 127, 36)',
675 'chocolate2': 'rgb(238, 118, 33)',
676 'chocolate3': 'rgb(205, 102, 29)',
677 'chocolate4': 'rgb(139, 69, 19)',
678 'coral': 'rgb(255, 127, 80)',
679 'coral1': 'rgb(255, 114, 86)',
680 'coral2': 'rgb(238, 106, 80)',
681 'coral3': 'rgb(205, 91, 69)',
682 'coral4': 'rgb(139, 62, 47)',
683 'cornflowerblue': 'rgb(100, 149, 237)',
684 'cornsilk': 'rgb(255, 248, 220)',
685 'cornsilk1': 'rgb(255, 248, 220)',
686 'cornsilk2': 'rgb(238, 232, 205)',
687 'cornsilk3': 'rgb(205, 200, 177)',
688 'cornsilk4': 'rgb(139, 136, 120)',
689 'cyan': 'rgb(0, 255, 255)',
690 'cyan1': 'rgb(0, 255, 255)',
691 'cyan2': 'rgb(0, 238, 238)',
692 'cyan3': 'rgb(0, 205, 205)',
693 'cyan4': 'rgb(0, 139, 139)',
694 'darkblue': 'rgb(0, 0, 139)',
695 'darkcyan': 'rgb(0, 139, 139)',
696 'darkgoldenrod': 'rgb(184, 134, 11)',
697 'darkgoldenrod1': 'rgb(255, 185, 15)',
698 'darkgoldenrod2': 'rgb(238, 173, 14)',
699 'darkgoldenrod3': 'rgb(205, 149, 12)',
700 'darkgoldenrod4': 'rgb(139, 101, 8)',
701 'darkgray': 'rgb(169, 169, 169)',
702 'darkgreen': 'rgb(0, 100, 0)',
703 'darkgrey': 'rgb(169, 169, 169)',
704 'darkkhaki': 'rgb(189, 183, 107)',
705 'darkmagenta': 'rgb(139, 0, 139)',
706 'darkolivegreen': 'rgb(85, 107, 47)',
707 'darkolivegreen1': 'rgb(202, 255, 112)',
708 'darkolivegreen2': 'rgb(188, 238, 104)',
709 'darkolivegreen3': 'rgb(162, 205, 90)',
710 'darkolivegreen4': 'rgb(110, 139, 61)',
711 'darkorange': 'rgb(255, 140, 0)',
712 'darkorange1': 'rgb(255, 127, 0)',
713 'darkorange2': 'rgb(238, 118, 0)',
714 'darkorange3': 'rgb(205, 102, 0)',
715 'darkorange4': 'rgb(139, 69, 0)',
716 'darkorchid': 'rgb(153, 50, 204)',
717 'darkorchid1': 'rgb(191, 62, 255)',
718 'darkorchid2': 'rgb(178, 58, 238)',
719 'darkorchid3': 'rgb(154, 50, 205)',
720 'darkorchid4': 'rgb(104, 34, 139)',
721 'darkred': 'rgb(139, 0, 0)',
722 'darksalmon': 'rgb(233, 150, 122)',
723 'darkseagreen': 'rgb(143, 188, 143)',
724 'darkseagreen1': 'rgb(193, 255, 193)',
725 'darkseagreen2': 'rgb(180, 238, 180)',
726 'darkseagreen3': 'rgb(155, 205, 155)',
727 'darkseagreen4': 'rgb(105, 139, 105)',
728 'darkslateblue': 'rgb(72, 61, 139)',
729 'darkslategray': 'rgb(47, 79, 79)',
730 'darkslategray1': 'rgb(151, 255, 255)',
731 'darkslategray2': 'rgb(141, 238, 238)',
732 'darkslategray3': 'rgb(121, 205, 205)',
733 'darkslategray4': 'rgb(82, 139, 139)',
734 'darkslategrey': 'rgb(47, 79, 79)',
735 'darkturquoise': 'rgb(0, 206, 209)',
736 'darkviolet': 'rgb(148, 0, 211)',
737 'debianred': 'rgb(215, 7, 81)',
738 'deeppink': 'rgb(255, 20, 147)',
739 'deeppink1': 'rgb(255, 20, 147)',
740 'deeppink2': 'rgb(238, 18, 137)',
741 'deeppink3': 'rgb(205, 16, 118)',
742 'deeppink4': 'rgb(139, 10, 80)',
743 'deepskyblue': 'rgb(0, 191, 255)',
744 'deepskyblue1': 'rgb(0, 191, 255)',
745 'deepskyblue2': 'rgb(0, 178, 238)',
746 'deepskyblue3': 'rgb(0, 154, 205)',
747 'deepskyblue4': 'rgb(0, 104, 139)',
748 'dimgray': 'rgb(105, 105, 105)',
749 'dimgrey': 'rgb(105, 105, 105)',
750 'dodgerblue': 'rgb(30, 144, 255)',
751 'dodgerblue1': 'rgb(30, 144, 255)',
752 'dodgerblue2': 'rgb(28, 134, 238)',
753 'dodgerblue3': 'rgb(24, 116, 205)',
754 'dodgerblue4': 'rgb(16, 78, 139)',
755 'firebrick': 'rgb(178, 34, 34)',
756 'firebrick1': 'rgb(255, 48, 48)',
757 'firebrick2': 'rgb(238, 44, 44)',
758 'firebrick3': 'rgb(205, 38, 38)',
759 'firebrick4': 'rgb(139, 26, 26)',
760 'floralwhite': 'rgb(255, 250, 240)',
761 'forestgreen': 'rgb(34, 139, 34)',
762 'gainsboro': 'rgb(220, 220, 220)',
763 'ghostwhite': 'rgb(248, 248, 255)',
764 'gold': 'rgb(255, 215, 0)',
765 'gold1': 'rgb(255, 215, 0)',
766 'gold2': 'rgb(238, 201, 0)',
767 'gold3': 'rgb(205, 173, 0)',
768 'gold4': 'rgb(139, 117, 0)',
769 'goldenrod': 'rgb(218, 165, 32)',
770 'goldenrod1': 'rgb(255, 193, 37)',
771 'goldenrod2': 'rgb(238, 180, 34)',
772 'goldenrod3': 'rgb(205, 155, 29)',
773 'goldenrod4': 'rgb(139, 105, 20)',
774 'gray': 'rgb(190, 190, 190)',
775 'gray0': 'rgb(0, 0, 0)',
776 'gray1': 'rgb(3, 3, 3)',
777 'gray10': 'rgb(26, 26, 26)',
778 'gray100': 'rgb(255, 255, 255)',
779 'gray11': 'rgb(28, 28, 28)',
780 'gray12': 'rgb(31, 31, 31)',
781 'gray13': 'rgb(33, 33, 33)',
782 'gray14': 'rgb(36, 36, 36)',
783 'gray15': 'rgb(38, 38, 38)',
784 'gray16': 'rgb(41, 41, 41)',
785 'gray17': 'rgb(43, 43, 43)',
786 'gray18': 'rgb(46, 46, 46)',
787 'gray19': 'rgb(48, 48, 48)',
788 'gray2': 'rgb(5, 5, 5)',
789 'gray20': 'rgb(51, 51, 51)',
790 'gray21': 'rgb(54, 54, 54)',
791 'gray22': 'rgb(56, 56, 56)',
792 'gray23': 'rgb(59, 59, 59)',
793 'gray24': 'rgb(61, 61, 61)',
794 'gray25': 'rgb(64, 64, 64)',
795 'gray26': 'rgb(66, 66, 66)',
796 'gray27': 'rgb(69, 69, 69)',
797 'gray28': 'rgb(71, 71, 71)',
798 'gray29': 'rgb(74, 74, 74)',
799 'gray3': 'rgb(8, 8, 8)',
800 'gray30': 'rgb(77, 77, 77)',
801 'gray31': 'rgb(79, 79, 79)',
802 'gray32': 'rgb(82, 82, 82)',
803 'gray33': 'rgb(84, 84, 84)',
804 'gray34': 'rgb(87, 87, 87)',
805 'gray35': 'rgb(89, 89, 89)',
806 'gray36': 'rgb(92, 92, 92)',
807 'gray37': 'rgb(94, 94, 94)',
808 'gray38': 'rgb(97, 97, 97)',
809 'gray39': 'rgb(99, 99, 99)',
810 'gray4': 'rgb(10, 10, 10)',
811 'gray40': 'rgb(102, 102, 102)',
812 'gray41': 'rgb(105, 105, 105)',
813 'gray42': 'rgb(107, 107, 107)',
814 'gray43': 'rgb(110, 110, 110)',
815 'gray44': 'rgb(112, 112, 112)',
816 'gray45': 'rgb(115, 115, 115)',
817 'gray46': 'rgb(117, 117, 117)',
818 'gray47': 'rgb(120, 120, 120)',
819 'gray48': 'rgb(122, 122, 122)',
820 'gray49': 'rgb(125, 125, 125)',
821 'gray5': 'rgb(13, 13, 13)',
822 'gray50': 'rgb(127, 127, 127)',
823 'gray51': 'rgb(130, 130, 130)',
824 'gray52': 'rgb(133, 133, 133)',
825 'gray53': 'rgb(135, 135, 135)',
826 'gray54': 'rgb(138, 138, 138)',
827 'gray55': 'rgb(140, 140, 140)',
828 'gray56': 'rgb(143, 143, 143)',
829 'gray57': 'rgb(145, 145, 145)',
830 'gray58': 'rgb(148, 148, 148)',
831 'gray59': 'rgb(150, 150, 150)',
832 'gray6': 'rgb(15, 15, 15)',
833 'gray60': 'rgb(153, 153, 153)',
834 'gray61': 'rgb(156, 156, 156)',
835 'gray62': 'rgb(158, 158, 158)',
836 'gray63': 'rgb(161, 161, 161)',
837 'gray64': 'rgb(163, 163, 163)',
838 'gray65': 'rgb(166, 166, 166)',
839 'gray66': 'rgb(168, 168, 168)',
840 'gray67': 'rgb(171, 171, 171)',
841 'gray68': 'rgb(173, 173, 173)',
842 'gray69': 'rgb(176, 176, 176)',
843 'gray7': 'rgb(18, 18, 18)',
844 'gray70': 'rgb(179, 179, 179)',
845 'gray71': 'rgb(181, 181, 181)',
846 'gray72': 'rgb(184, 184, 184)',
847 'gray73': 'rgb(186, 186, 186)',
848 'gray74': 'rgb(189, 189, 189)',
849 'gray75': 'rgb(191, 191, 191)',
850 'gray76': 'rgb(194, 194, 194)',
851 'gray77': 'rgb(196, 196, 196)',
852 'gray78': 'rgb(199, 199, 199)',
853 'gray79': 'rgb(201, 201, 201)',
854 'gray8': 'rgb(20, 20, 20)',
855 'gray80': 'rgb(204, 204, 204)',
856 'gray81': 'rgb(207, 207, 207)',
857 'gray82': 'rgb(209, 209, 209)',
858 'gray83': 'rgb(212, 212, 212)',
859 'gray84': 'rgb(214, 214, 214)',
860 'gray85': 'rgb(217, 217, 217)',
861 'gray86': 'rgb(219, 219, 219)',
862 'gray87': 'rgb(222, 222, 222)',
863 'gray88': 'rgb(224, 224, 224)',
864 'gray89': 'rgb(227, 227, 227)',
865 'gray9': 'rgb(23, 23, 23)',
866 'gray90': 'rgb(229, 229, 229)',
867 'gray91': 'rgb(232, 232, 232)',
868 'gray92': 'rgb(235, 235, 235)',
869 'gray93': 'rgb(237, 237, 237)',
870 'gray94': 'rgb(240, 240, 240)',
871 'gray95': 'rgb(242, 242, 242)',
872 'gray96': 'rgb(245, 245, 245)',
873 'gray97': 'rgb(247, 247, 247)',
874 'gray98': 'rgb(250, 250, 250)',
875 'gray99': 'rgb(252, 252, 252)',
876 'green': 'rgb(0, 255, 0)',
877 'green1': 'rgb(0, 255, 0)',
878 'green2': 'rgb(0, 238, 0)',
879 'green3': 'rgb(0, 205, 0)',
880 'green4': 'rgb(0, 139, 0)',
881 'greenyellow': 'rgb(173, 255, 47)',
882 'grey': 'rgb(190, 190, 190)',
883 'grey0': 'rgb(0, 0, 0)',
884 'grey1': 'rgb(3, 3, 3)',
885 'grey10': 'rgb(26, 26, 26)',
886 'grey100': 'rgb(255, 255, 255)',
887 'grey11': 'rgb(28, 28, 28)',
888 'grey12': 'rgb(31, 31, 31)',
889 'grey13': 'rgb(33, 33, 33)',
890 'grey14': 'rgb(36, 36, 36)',
891 'grey15': 'rgb(38, 38, 38)',
892 'grey16': 'rgb(41, 41, 41)',
893 'grey17': 'rgb(43, 43, 43)',
894 'grey18': 'rgb(46, 46, 46)',
895 'grey19': 'rgb(48, 48, 48)',
896 'grey2': 'rgb(5, 5, 5)',
897 'grey20': 'rgb(51, 51, 51)',
898 'grey21': 'rgb(54, 54, 54)',
899 'grey22': 'rgb(56, 56, 56)',
900 'grey23': 'rgb(59, 59, 59)',
901 'grey24': 'rgb(61, 61, 61)',
902 'grey25': 'rgb(64, 64, 64)',
903 'grey26': 'rgb(66, 66, 66)',
904 'grey27': 'rgb(69, 69, 69)',
905 'grey28': 'rgb(71, 71, 71)',
906 'grey29': 'rgb(74, 74, 74)',
907 'grey3': 'rgb(8, 8, 8)',
908 'grey30': 'rgb(77, 77, 77)',
909 'grey31': 'rgb(79, 79, 79)',
910 'grey32': 'rgb(82, 82, 82)',
911 'grey33': 'rgb(84, 84, 84)',
912 'grey34': 'rgb(87, 87, 87)',
913 'grey35': 'rgb(89, 89, 89)',
914 'grey36': 'rgb(92, 92, 92)',
915 'grey37': 'rgb(94, 94, 94)',
916 'grey38': 'rgb(97, 97, 97)',
917 'grey39': 'rgb(99, 99, 99)',
918 'grey4': 'rgb(10, 10, 10)',
919 'grey40': 'rgb(102, 102, 102)',
920 'grey41': 'rgb(105, 105, 105)',
921 'grey42': 'rgb(107, 107, 107)',
922 'grey43': 'rgb(110, 110, 110)',
923 'grey44': 'rgb(112, 112, 112)',
924 'grey45': 'rgb(115, 115, 115)',
925 'grey46': 'rgb(117, 117, 117)',
926 'grey47': 'rgb(120, 120, 120)',
927 'grey48': 'rgb(122, 122, 122)',
928 'grey49': 'rgb(125, 125, 125)',
929 'grey5': 'rgb(13, 13, 13)',
930 'grey50': 'rgb(127, 127, 127)',
931 'grey51': 'rgb(130, 130, 130)',
932 'grey52': 'rgb(133, 133, 133)',
933 'grey53': 'rgb(135, 135, 135)',
934 'grey54': 'rgb(138, 138, 138)',
935 'grey55': 'rgb(140, 140, 140)',
936 'grey56': 'rgb(143, 143, 143)',
937 'grey57': 'rgb(145, 145, 145)',
938 'grey58': 'rgb(148, 148, 148)',
939 'grey59': 'rgb(150, 150, 150)',
940 'grey6': 'rgb(15, 15, 15)',
941 'grey60': 'rgb(153, 153, 153)',
942 'grey61': 'rgb(156, 156, 156)',
943 'grey62': 'rgb(158, 158, 158)',
944 'grey63': 'rgb(161, 161, 161)',
945 'grey64': 'rgb(163, 163, 163)',
946 'grey65': 'rgb(166, 166, 166)',
947 'grey66': 'rgb(168, 168, 168)',
948 'grey67': 'rgb(171, 171, 171)',
949 'grey68': 'rgb(173, 173, 173)',
950 'grey69': 'rgb(176, 176, 176)',
951 'grey7': 'rgb(18, 18, 18)',
952 'grey70': 'rgb(179, 179, 179)',
953 'grey71': 'rgb(181, 181, 181)',
954 'grey72': 'rgb(184, 184, 184)',
955 'grey73': 'rgb(186, 186, 186)',
956 'grey74': 'rgb(189, 189, 189)',
957 'grey75': 'rgb(191, 191, 191)',
958 'grey76': 'rgb(194, 194, 194)',
959 'grey77': 'rgb(196, 196, 196)',
960 'grey78': 'rgb(199, 199, 199)',
961 'grey79': 'rgb(201, 201, 201)',
962 'grey8': 'rgb(20, 20, 20)',
963 'grey80': 'rgb(204, 204, 204)',
964 'grey81': 'rgb(207, 207, 207)',
965 'grey82': 'rgb(209, 209, 209)',
966 'grey83': 'rgb(212, 212, 212)',
967 'grey84': 'rgb(214, 214, 214)',
968 'grey85': 'rgb(217, 217, 217)',
969 'grey86': 'rgb(219, 219, 219)',
970 'grey87': 'rgb(222, 222, 222)',
971 'grey88': 'rgb(224, 224, 224)',
972 'grey89': 'rgb(227, 227, 227)',
973 'grey9': 'rgb(23, 23, 23)',
974 'grey90': 'rgb(229, 229, 229)',
975 'grey91': 'rgb(232, 232, 232)',
976 'grey92': 'rgb(235, 235, 235)',
977 'grey93': 'rgb(237, 237, 237)',
978 'grey94': 'rgb(240, 240, 240)',
979 'grey95': 'rgb(242, 242, 242)',
980 'grey96': 'rgb(245, 245, 245)',
981 'grey97': 'rgb(247, 247, 247)',
982 'grey98': 'rgb(250, 250, 250)',
983 'grey99': 'rgb(252, 252, 252)',
984 'honeydew': 'rgb(240, 255, 240)',
985 'honeydew1': 'rgb(240, 255, 240)',
986 'honeydew2': 'rgb(224, 238, 224)',
987 'honeydew3': 'rgb(193, 205, 193)',
988 'honeydew4': 'rgb(131, 139, 131)',
989 'hotpink': 'rgb(255, 105, 180)',
990 'hotpink1': 'rgb(255, 110, 180)',
991 'hotpink2': 'rgb(238, 106, 167)',
992 'hotpink3': 'rgb(205, 96, 144)',
993 'hotpink4': 'rgb(139, 58, 98)',
994 'indianred': 'rgb(205, 92, 92)',
995 'indianred1': 'rgb(255, 106, 106)',
996 'indianred2': 'rgb(238, 99, 99)',
997 'indianred3': 'rgb(205, 85, 85)',
998 'indianred4': 'rgb(139, 58, 58)',
999 'ivory': 'rgb(255, 255, 240)',
1000 'ivory1': 'rgb(255, 255, 240)',
1001 'ivory2': 'rgb(238, 238, 224)',
1002 'ivory3': 'rgb(205, 205, 193)',
1003 'ivory4': 'rgb(139, 139, 131)',
1004 'khaki': 'rgb(240, 230, 140)',
1005 'khaki1': 'rgb(255, 246, 143)',
1006 'khaki2': 'rgb(238, 230, 133)',
1007 'khaki3': 'rgb(205, 198, 115)',
1008 'khaki4': 'rgb(139, 134, 78)',
1009 'lavender': 'rgb(230, 230, 250)',
1010 'lavenderblush': 'rgb(255, 240, 245)',
1011 'lavenderblush1': 'rgb(255, 240, 245)',
1012 'lavenderblush2': 'rgb(238, 224, 229)',
1013 'lavenderblush3': 'rgb(205, 193, 197)',
1014 'lavenderblush4': 'rgb(139, 131, 134)',
1015 'lawngreen': 'rgb(124, 252, 0)',
1016 'lemonchiffon': 'rgb(255, 250, 205)',
1017 'lemonchiffon1': 'rgb(255, 250, 205)',
1018 'lemonchiffon2': 'rgb(238, 233, 191)',
1019 'lemonchiffon3': 'rgb(205, 201, 165)',
1020 'lemonchiffon4': 'rgb(139, 137, 112)',
1021 'lightblue': 'rgb(173, 216, 230)',
1022 'lightblue1': 'rgb(191, 239, 255)',
1023 'lightblue2': 'rgb(178, 223, 238)',
1024 'lightblue3': 'rgb(154, 192, 205)',
1025 'lightblue4': 'rgb(104, 131, 139)',
1026 'lightcoral': 'rgb(240, 128, 128)',
1027 'lightcyan': 'rgb(224, 255, 255)',
1028 'lightcyan1': 'rgb(224, 255, 255)',
1029 'lightcyan2': 'rgb(209, 238, 238)',
1030 'lightcyan3': 'rgb(180, 205, 205)',
1031 'lightcyan4': 'rgb(122, 139, 139)',
1032 'lightgoldenrod': 'rgb(238, 221, 130)',
1033 'lightgoldenrod1': 'rgb(255, 236, 139)',
1034 'lightgoldenrod2': 'rgb(238, 220, 130)',
1035 'lightgoldenrod3': 'rgb(205, 190, 112)',
1036 'lightgoldenrod4': 'rgb(139, 129, 76)',
1037 'lightgoldenrodyellow': 'rgb(250, 250, 210)',
1038 'lightgray': 'rgb(211, 211, 211)',
1039 'lightgreen': 'rgb(144, 238, 144)',
1040 'lightgrey': 'rgb(211, 211, 211)',
1041 'lightpink': 'rgb(255, 182, 193)',
1042 'lightpink1': 'rgb(255, 174, 185)',
1043 'lightpink2': 'rgb(238, 162, 173)',
1044 'lightpink3': 'rgb(205, 140, 149)',
1045 'lightpink4': 'rgb(139, 95, 101)',
1046 'lightsalmon': 'rgb(255, 160, 122)',
1047 'lightsalmon1': 'rgb(255, 160, 122)',
1048 'lightsalmon2': 'rgb(238, 149, 114)',
1049 'lightsalmon3': 'rgb(205, 129, 98)',
1050 'lightsalmon4': 'rgb(139, 87, 66)',
1051 'lightseagreen': 'rgb(32, 178, 170)',
1052 'lightskyblue': 'rgb(135, 206, 250)',
1053 'lightskyblue1': 'rgb(176, 226, 255)',
1054 'lightskyblue2': 'rgb(164, 211, 238)',
1055 'lightskyblue3': 'rgb(141, 182, 205)',
1056 'lightskyblue4': 'rgb(96, 123, 139)',
1057 'lightslateblue': 'rgb(132, 112, 255)',
1058 'lightslategray': 'rgb(119, 136, 153)',
1059 'lightslategrey': 'rgb(119, 136, 153)',
1060 'lightsteelblue': 'rgb(176, 196, 222)',
1061 'lightsteelblue1': 'rgb(202, 225, 255)',
1062 'lightsteelblue2': 'rgb(188, 210, 238)',
1063 'lightsteelblue3': 'rgb(162, 181, 205)',
1064 'lightsteelblue4': 'rgb(110, 123, 139)',
1065 'lightyellow': 'rgb(255, 255, 224)',
1066 'lightyellow1': 'rgb(255, 255, 224)',
1067 'lightyellow2': 'rgb(238, 238, 209)',
1068 'lightyellow3': 'rgb(205, 205, 180)',
1069 'lightyellow4': 'rgb(139, 139, 122)',
1070 'limegreen': 'rgb(50, 205, 50)',
1071 'linen': 'rgb(250, 240, 230)',
1072 'magenta': 'rgb(255, 0, 255)',
1073 'magenta1': 'rgb(255, 0, 255)',
1074 'magenta2': 'rgb(238, 0, 238)',
1075 'magenta3': 'rgb(205, 0, 205)',
1076 'magenta4': 'rgb(139, 0, 139)',
1077 'maroon': 'rgb(176, 48, 96)',
1078 'maroon1': 'rgb(255, 52, 179)',
1079 'maroon2': 'rgb(238, 48, 167)',
1080 'maroon3': 'rgb(205, 41, 144)',
1081 'maroon4': 'rgb(139, 28, 98)',
1082 'mediumaquamarine': 'rgb(102, 205, 170)',
1083 'mediumblue': 'rgb(0, 0, 205)',
1084 'mediumorchid': 'rgb(186, 85, 211)',
1085 'mediumorchid1': 'rgb(224, 102, 255)',
1086 'mediumorchid2': 'rgb(209, 95, 238)',
1087 'mediumorchid3': 'rgb(180, 82, 205)',
1088 'mediumorchid4': 'rgb(122, 55, 139)',
1089 'mediumpurple': 'rgb(147, 112, 219)',
1090 'mediumpurple1': 'rgb(171, 130, 255)',
1091 'mediumpurple2': 'rgb(159, 121, 238)',
1092 'mediumpurple3': 'rgb(137, 104, 205)',
1093 'mediumpurple4': 'rgb(93, 71, 139)',
1094 'mediumseagreen': 'rgb(60, 179, 113)',
1095 'mediumslateblue': 'rgb(123, 104, 238)',
1096 'mediumspringgreen': 'rgb(0, 250, 154)',
1097 'mediumturquoise': 'rgb(72, 209, 204)',
1098 'mediumvioletred': 'rgb(199, 21, 133)',
1099 'midnightblue': 'rgb(25, 25, 112)',
1100 'mintcream': 'rgb(245, 255, 250)',
1101 'mistyrose': 'rgb(255, 228, 225)',
1102 'mistyrose1': 'rgb(255, 228, 225)',
1103 'mistyrose2': 'rgb(238, 213, 210)',
1104 'mistyrose3': 'rgb(205, 183, 181)',
1105 'mistyrose4': 'rgb(139, 125, 123)',
1106 'moccasin': 'rgb(255, 228, 181)',
1107 'navajowhite': 'rgb(255, 222, 173)',
1108 'navajowhite1': 'rgb(255, 222, 173)',
1109 'navajowhite2': 'rgb(238, 207, 161)',
1110 'navajowhite3': 'rgb(205, 179, 139)',
1111 'navajowhite4': 'rgb(139, 121, 94)',
1112 'navy': 'rgb(0, 0, 128)',
1113 'navyblue': 'rgb(0, 0, 128)',
1114 'oldlace': 'rgb(253, 245, 230)',
1115 'olivedrab': 'rgb(107, 142, 35)',
1116 'olivedrab1': 'rgb(192, 255, 62)',
1117 'olivedrab2': 'rgb(179, 238, 58)',
1118 'olivedrab3': 'rgb(154, 205, 50)',
1119 'olivedrab4': 'rgb(105, 139, 34)',
1120 'orange': 'rgb(255, 165, 0)',
1121 'orange1': 'rgb(255, 165, 0)',
1122 'orange2': 'rgb(238, 154, 0)',
1123 'orange3': 'rgb(205, 133, 0)',
1124 'orange4': 'rgb(139, 90, 0)',
1125 'orangered': 'rgb(255, 69, 0)',
1126 'orangered1': 'rgb(255, 69, 0)',
1127 'orangered2': 'rgb(238, 64, 0)',
1128 'orangered3': 'rgb(205, 55, 0)',
1129 'orangered4': 'rgb(139, 37, 0)',
1130 'orchid': 'rgb(218, 112, 214)',
1131 'orchid1': 'rgb(255, 131, 250)',
1132 'orchid2': 'rgb(238, 122, 233)',
1133 'orchid3': 'rgb(205, 105, 201)',
1134 'orchid4': 'rgb(139, 71, 137)',
1135 'palegoldenrod': 'rgb(238, 232, 170)',
1136 'palegreen': 'rgb(152, 251, 152)',
1137 'palegreen1': 'rgb(154, 255, 154)',
1138 'palegreen2': 'rgb(144, 238, 144)',
1139 'palegreen3': 'rgb(124, 205, 124)',
1140 'palegreen4': 'rgb(84, 139, 84)',
1141 'paleturquoise': 'rgb(175, 238, 238)',
1142 'paleturquoise1': 'rgb(187, 255, 255)',
1143 'paleturquoise2': 'rgb(174, 238, 238)',
1144 'paleturquoise3': 'rgb(150, 205, 205)',
1145 'paleturquoise4': 'rgb(102, 139, 139)',
1146 'palevioletred': 'rgb(219, 112, 147)',
1147 'palevioletred1': 'rgb(255, 130, 171)',
1148 'palevioletred2': 'rgb(238, 121, 159)',
1149 'palevioletred3': 'rgb(205, 104, 137)',
1150 'palevioletred4': 'rgb(139, 71, 93)',
1151 'papayawhip': 'rgb(255, 239, 213)',
1152 'peachpuff': 'rgb(255, 218, 185)',
1153 'peachpuff1': 'rgb(255, 218, 185)',
1154 'peachpuff2': 'rgb(238, 203, 173)',
1155 'peachpuff3': 'rgb(205, 175, 149)',
1156 'peachpuff4': 'rgb(139, 119, 101)',
1157 'peru': 'rgb(205, 133, 63)',
1158 'pink': 'rgb(255, 192, 203)',
1159 'pink1': 'rgb(255, 181, 197)',
1160 'pink2': 'rgb(238, 169, 184)',
1161 'pink3': 'rgb(205, 145, 158)',
1162 'pink4': 'rgb(139, 99, 108)',
1163 'plum': 'rgb(221, 160, 221)',
1164 'plum1': 'rgb(255, 187, 255)',
1165 'plum2': 'rgb(238, 174, 238)',
1166 'plum3': 'rgb(205, 150, 205)',
1167 'plum4': 'rgb(139, 102, 139)',
1168 'powderblue': 'rgb(176, 224, 230)',
1169 'purple': 'rgb(160, 32, 240)',
1170 'purple1': 'rgb(155, 48, 255)',
1171 'purple2': 'rgb(145, 44, 238)',
1172 'purple3': 'rgb(125, 38, 205)',
1173 'purple4': 'rgb(85, 26, 139)',
1174 'red': 'rgb(255, 0, 0)',
1175 'red1': 'rgb(255, 0, 0)',
1176 'red2': 'rgb(238, 0, 0)',
1177 'red3': 'rgb(205, 0, 0)',
1178 'red4': 'rgb(139, 0, 0)',
1179 'rosybrown': 'rgb(188, 143, 143)',
1180 'rosybrown1': 'rgb(255, 193, 193)',
1181 'rosybrown2': 'rgb(238, 180, 180)',
1182 'rosybrown3': 'rgb(205, 155, 155)',
1183 'rosybrown4': 'rgb(139, 105, 105)',
1184 'royalblue': 'rgb(65, 105, 225)',
1185 'royalblue1': 'rgb(72, 118, 255)',
1186 'royalblue2': 'rgb(67, 110, 238)',
1187 'royalblue3': 'rgb(58, 95, 205)',
1188 'royalblue4': 'rgb(39, 64, 139)',
1189 'saddlebrown': 'rgb(139, 69, 19)',
1190 'salmon': 'rgb(250, 128, 114)',
1191 'salmon1': 'rgb(255, 140, 105)',
1192 'salmon2': 'rgb(238, 130, 98)',
1193 'salmon3': 'rgb(205, 112, 84)',
1194 'salmon4': 'rgb(139, 76, 57)',
1195 'sandybrown': 'rgb(244, 164, 96)',
1196 'seagreen': 'rgb(46, 139, 87)',
1197 'seagreen1': 'rgb(84, 255, 159)',
1198 'seagreen2': 'rgb(78, 238, 148)',
1199 'seagreen3': 'rgb(67, 205, 128)',
1200 'seagreen4': 'rgb(46, 139, 87)',
1201 'seashell': 'rgb(255, 245, 238)',
1202 'seashell1': 'rgb(255, 245, 238)',
1203 'seashell2': 'rgb(238, 229, 222)',
1204 'seashell3': 'rgb(205, 197, 191)',
1205 'seashell4': 'rgb(139, 134, 130)',
1206 'sienna': 'rgb(160, 82, 45)',
1207 'sienna1': 'rgb(255, 130, 71)',
1208 'sienna2': 'rgb(238, 121, 66)',
1209 'sienna3': 'rgb(205, 104, 57)',
1210 'sienna4': 'rgb(139, 71, 38)',
1211 'skyblue': 'rgb(135, 206, 235)',
1212 'skyblue1': 'rgb(135, 206, 255)',
1213 'skyblue2': 'rgb(126, 192, 238)',
1214 'skyblue3': 'rgb(108, 166, 205)',
1215 'skyblue4': 'rgb(74, 112, 139)',
1216 'slateblue': 'rgb(106, 90, 205)',
1217 'slateblue1': 'rgb(131, 111, 255)',
1218 'slateblue2': 'rgb(122, 103, 238)',
1219 'slateblue3': 'rgb(105, 89, 205)',
1220 'slateblue4': 'rgb(71, 60, 139)',
1221 'slategray': 'rgb(112, 128, 144)',
1222 'slategray1': 'rgb(198, 226, 255)',
1223 'slategray2': 'rgb(185, 211, 238)',
1224 'slategray3': 'rgb(159, 182, 205)',
1225 'slategray4': 'rgb(108, 123, 139)',
1226 'slategrey': 'rgb(112, 128, 144)',
1227 'snow': 'rgb(255, 250, 250)',
1228 'snow1': 'rgb(255, 250, 250)',
1229 'snow2': 'rgb(238, 233, 233)',
1230 'snow3': 'rgb(205, 201, 201)',
1231 'snow4': 'rgb(139, 137, 137)',
1232 'springgreen': 'rgb(0, 255, 127)',
1233 'springgreen1': 'rgb(0, 255, 127)',
1234 'springgreen2': 'rgb(0, 238, 118)',
1235 'springgreen3': 'rgb(0, 205, 102)',
1236 'springgreen4': 'rgb(0, 139, 69)',
1237 'steelblue': 'rgb(70, 130, 180)',
1238 'steelblue1': 'rgb(99, 184, 255)',
1239 'steelblue2': 'rgb(92, 172, 238)',
1240 'steelblue3': 'rgb(79, 148, 205)',
1241 'steelblue4': 'rgb(54, 100, 139)',
1242 'tan': 'rgb(210, 180, 140)',
1243 'tan1': 'rgb(255, 165, 79)',
1244 'tan2': 'rgb(238, 154, 73)',
1245 'tan3': 'rgb(205, 133, 63)',
1246 'tan4': 'rgb(139, 90, 43)',
1247 'thistle': 'rgb(216, 191, 216)',
1248 'thistle1': 'rgb(255, 225, 255)',
1249 'thistle2': 'rgb(238, 210, 238)',
1250 'thistle3': 'rgb(205, 181, 205)',
1251 'thistle4': 'rgb(139, 123, 139)',
1252 'tomato': 'rgb(255, 99, 71)',
1253 'tomato1': 'rgb(255, 99, 71)',
1254 'tomato2': 'rgb(238, 92, 66)',
1255 'tomato3': 'rgb(205, 79, 57)',
1256 'tomato4': 'rgb(139, 54, 38)',
1257 'turquoise': 'rgb(64, 224, 208)',
1258 'turquoise1': 'rgb(0, 245, 255)',
1259 'turquoise2': 'rgb(0, 229, 238)',
1260 'turquoise3': 'rgb(0, 197, 205)',
1261 'turquoise4': 'rgb(0, 134, 139)',
1262 'violet': 'rgb(238, 130, 238)',
1263 'violetred': 'rgb(208, 32, 144)',
1264 'violetred1': 'rgb(255, 62, 150)',
1265 'violetred2': 'rgb(238, 58, 140)',
1266 'violetred3': 'rgb(205, 50, 120)',
1267 'violetred4': 'rgb(139, 34, 82)',
1268 'wheat': 'rgb(245, 222, 179)',
1269 'wheat1': 'rgb(255, 231, 186)',
1270 'wheat2': 'rgb(238, 216, 174)',
1271 'wheat3': 'rgb(205, 186, 150)',
1272 'wheat4': 'rgb(139, 126, 102)',
1273 'white': 'rgb(255, 255, 255)',
1274 'whitesmoke': 'rgb(245, 245, 245)',
1275 'yellow': 'rgb(255, 255, 0)',
1276 'yellow1': 'rgb(255, 255, 0)',
1277 'yellow2': 'rgb(238, 238, 0)',
1278 'yellow3': 'rgb(205, 205, 0)',
1279 'yellow4': 'rgb(139, 139, 0)',
1280 'yellowgreen': 'rgb(154, 205, 50)'
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001281};
1282// SOURCE FILE: libdot/js/lib_f.js
1283// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
1284// Use of this source code is governed by a BSD-style license that can be
1285// found in the LICENSE file.
1286
1287'use strict';
1288
1289/**
1290 * Grab bag of utility functions.
1291 */
1292lib.f = {};
1293
1294/**
1295 * Replace variable references in a string.
1296 *
1297 * Variables are of the form %FUNCTION(VARNAME). FUNCTION is an optional
1298 * escape function to apply to the value.
1299 *
1300 * For example
1301 * lib.f.replaceVars("%(greeting), %encodeURIComponent(name)",
1302 * { greeting: "Hello",
1303 * name: "Google+" });
1304 *
1305 * Will result in "Hello, Google%2B".
1306 */
1307lib.f.replaceVars = function(str, vars) {
1308 return str.replace(/%([a-z]*)\(([^\)]+)\)/gi, function(match, fn, varname) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001309 if (typeof vars[varname] == 'undefined')
1310 throw 'Unknown variable: ' + varname;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001311
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001312 var rv = vars[varname];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001313
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001314 if (fn in lib.f.replaceVars.functions) {
1315 rv = lib.f.replaceVars.functions[fn](rv);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001316 } else if (fn) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001317 throw 'Unknown escape function: ' + fn;
1318 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001319
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001320 return rv;
1321 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001322};
1323
1324/**
1325 * Functions that can be used with replaceVars.
1326 *
1327 * Clients can add to this list to extend lib.f.replaceVars().
1328 */
1329lib.f.replaceVars.functions = {
1330 encodeURI: encodeURI,
1331 encodeURIComponent: encodeURIComponent,
1332 escapeHTML: function(str) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001333 var map =
1334 {'<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', '\'': '&#39;'};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001335
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001336 return str.replace(/[<>&\"\']/g, function(m) {
1337 return map[m];
1338 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001339 }
1340};
1341
1342/**
1343 * Get the list of accepted UI languages.
1344 *
1345 * @param {function(Array)} callback Function to invoke with the results. The
1346 * parameter is a list of locale names.
1347 */
1348lib.f.getAcceptLanguages = function(callback) {
1349 if (lib.f.getAcceptLanguages.chromeSupported()) {
1350 chrome.i18n.getAcceptLanguages(callback);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001351 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001352 setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001353 callback([navigator.language.replace(/-/g, '_')]);
1354 }, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001355 }
1356};
1357
1358lib.f.getAcceptLanguages.chromeSupported = function() {
1359 return window.chrome && chrome.i18n;
1360};
1361
1362/**
1363 * Parse a query string into a hash.
1364 *
1365 * This takes a url query string in the form 'name1=value&name2=value' and
1366 * converts it into an object of the form { name1: 'value', name2: 'value' }.
1367 * If a given name appears multiple times in the query string, only the
1368 * last value will appear in the result.
1369 *
1370 * Names and values are passed through decodeURIComponent before being added
1371 * to the result object.
1372 *
1373 * @param {string} queryString The string to parse. If it starts with a
1374 * leading '?', the '?' will be ignored.
1375 */
1376lib.f.parseQuery = function(queryString) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001377 if (queryString.substr(0, 1) == '?') queryString = queryString.substr(1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001378
1379 var rv = {};
1380
1381 var pairs = queryString.split('&');
1382 for (var i = 0; i < pairs.length; i++) {
1383 var pair = pairs[i].split('=');
1384 rv[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
1385 }
1386
1387 return rv;
1388};
1389
1390lib.f.getURL = function(path) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001391 if (lib.f.getURL.chromeSupported()) return chrome.runtime.getURL(path);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001392
1393 return path;
1394};
1395
1396lib.f.getURL.chromeSupported = function() {
1397 return window.chrome && chrome.runtime && chrome.runtime.getURL;
1398};
1399
1400/**
1401 * Clamp a given integer to a specified range.
1402 *
1403 * @param {integer} v The value to be clamped.
1404 * @param {integer} min The minimum acceptable value.
1405 * @param {integer} max The maximum acceptable value.
1406 */
1407lib.f.clamp = function(v, min, max) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001408 if (v < min) return min;
1409 if (v > max) return max;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001410 return v;
1411};
1412
1413/**
1414 * Left pad a string to a given length using a given character.
1415 *
1416 * @param {string} str The string to pad.
1417 * @param {integer} length The desired length.
1418 * @param {string} opt_ch The optional padding character, defaults to ' '.
1419 * @return {string} The padded string.
1420 */
1421lib.f.lpad = function(str, length, opt_ch) {
1422 str = String(str);
1423 opt_ch = opt_ch || ' ';
1424
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001425 while (str.length < length) str = opt_ch + str;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001426
1427 return str;
1428};
1429
1430/**
1431 * Left pad a number to a given length with leading zeros.
1432 *
1433 * @param {string|integer} number The number to pad.
1434 * @param {integer} length The desired length.
1435 * @return {string} The padded number as a string.
1436 */
1437lib.f.zpad = function(number, length) {
1438 return lib.f.lpad(number, length, '0');
1439};
1440
1441/**
1442 * Return a string containing a given number of space characters.
1443 *
1444 * This method maintains a static cache of the largest amount of whitespace
1445 * ever requested. It shouldn't be used to generate an insanely huge amount of
1446 * whitespace.
1447 *
1448 * @param {integer} length The desired amount of whitespace.
1449 * @param {string} A string of spaces of the requested length.
1450 */
1451lib.f.getWhitespace = function(length) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001452 if (length <= 0) return '';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001453
1454 var f = this.getWhitespace;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001455 if (!f.whitespace) f.whitespace = ' ';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001456
1457 while (length > f.whitespace.length) {
1458 f.whitespace += f.whitespace;
1459 }
1460
1461 return f.whitespace.substr(0, length);
1462};
1463
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001464/**
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001465 * Ensure that a function is called within a certain time limit.
1466 *
1467 * Simple usage looks like this...
1468 *
1469 * lib.registerInit(lib.f.alarm(onInit));
1470 *
1471 * This will log a warning to the console if onInit() is not invoked within
1472 * 5 seconds.
1473 *
1474 * If you're performing some operation that may take longer than 5 seconds you
1475 * can pass a duration in milliseconds as the optional second parameter.
1476 *
1477 * If you pass a string identifier instead of a callback function, you'll get a
1478 * wrapper generator rather than a single wrapper. Each call to the
1479 * generator will return a wrapped version of the callback wired to
1480 * a shared timeout. This is for cases where you want to ensure that at least
1481 * one of a set of callbacks is invoked before a timeout expires.
1482 *
1483 * var alarm = lib.f.alarm('fetch object');
1484 * lib.foo.fetchObject(alarm(onSuccess), alarm(onFailure));
1485 *
1486 * @param {function(*)} callback The function to wrap in an alarm.
1487 * @param {int} opt_ms Optional number of milliseconds to wait before raising
1488 * an alarm. Default is 5000 (5 seconds).
1489 * @return {function} If callback is a function then the return value will be
1490 * the wrapped callback. If callback is a string then the return value will
1491 * be a function that generates new wrapped callbacks.
1492 */
1493lib.f.alarm = function(callback, opt_ms) {
1494 var ms = opt_ms || 5 * 1000;
1495 var stack = lib.f.getStack(1);
1496
1497 return (function() {
1498 // This outer function is called immediately. It's here to capture a new
1499 // scope for the timeout variable.
1500
1501 // The 'timeout' variable is shared by this timeout function, and the
1502 // callback wrapper.
1503 var timeout = setTimeout(function() {
1504 var name = (typeof callback == 'string') ? name : callback.name;
1505 name = name ? (': ' + name) : '';
1506 console.warn('lib.f.alarm: timeout expired: ' + (ms / 1000) + 's' + name);
1507 console.log(stack);
1508 timeout = null;
1509 }, ms);
1510
1511 var wrapperGenerator = function(callback) {
1512 return function() {
1513 if (timeout) {
1514 clearTimeout(timeout);
1515 timeout = null;
1516 }
1517
1518 return callback.apply(null, arguments);
1519 }
1520 };
1521
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001522 if (typeof callback == 'string') return wrapperGenerator;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001523
1524 return wrapperGenerator(callback);
1525 })();
1526};
1527
1528/**
1529 * Return the current call stack after skipping a given number of frames.
1530 *
1531 * This method is intended to be used for debugging only. It returns an
1532 * Object instead of an Array, because the console stringifies arrays by
1533 * default and that's not what we want.
1534 *
1535 * A typical call might look like...
1536 *
1537 * console.log('Something wicked this way came', lib.f.getStack());
1538 * // Notice the comma ^
1539 *
1540 * This would print the message to the js console, followed by an object
1541 * which can be clicked to reveal the stack.
1542 *
1543 * @param {number} opt_ignoreFrames The optional number of stack frames to
1544 * ignore. The actual 'getStack' call is always ignored.
1545 */
1546lib.f.getStack = function(opt_ignoreFrames) {
1547 var ignoreFrames = opt_ignoreFrames ? opt_ignoreFrames + 2 : 2;
1548
1549 var stackArray;
1550
1551 try {
1552 throw new Error();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001553 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001554 stackArray = ex.stack.split('\n');
1555 }
1556
1557 var stackObject = {};
1558 for (var i = ignoreFrames; i < stackArray.length; i++) {
1559 stackObject[i - ignoreFrames] = stackArray[i].replace(/^\s*at\s+/, '');
1560 }
1561
1562 return stackObject;
1563};
1564
1565/**
1566 * Divides the two numbers and floors the results, unless the remainder is less
1567 * than an incredibly small value, in which case it returns the ceiling.
1568 * This is useful when the number are truncated approximations of longer
1569 * values, and so doing division with these numbers yields a result incredibly
1570 * close to a whole number.
1571 *
1572 * @param {number} numerator
1573 * @param {number} denominator
1574 * @return {number}
1575 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001576lib.f.smartFloorDivide = function(numerator, denominator) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001577 var val = numerator / denominator;
1578 var ceiling = Math.ceil(val);
1579 if (ceiling - val < .0001) {
1580 return ceiling;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001581 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001582 return Math.floor(val);
1583 }
1584};
1585// SOURCE FILE: libdot/js/lib_message_manager.js
1586// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
1587// Use of this source code is governed by a BSD-style license that can be
1588// found in the LICENSE file.
1589
1590'use strict';
1591
1592/**
1593 * MessageManager class handles internationalized strings.
1594 *
1595 * Note: chrome.i18n isn't sufficient because...
1596 * 1. There's a bug in chrome that makes it unavailable in iframes:
1597 * https://crbug.com/130200
1598 * 2. The client code may not be packaged in a Chrome extension.
1599 * 3. The client code may be part of a library packaged in a third-party
1600 * Chrome extension.
1601 *
1602 * @param {Array} languages List of languages to load, in the order they
1603 * should be loaded. Newer messages replace older ones. 'en' is
1604 * automatically added as the first language if it is not already present.
1605 */
1606lib.MessageManager = function(languages) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001607 this.languages_ = languages.map(function(el) {
1608 return el.replace(/-/g, '_');
1609 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001610
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001611 if (this.languages_.indexOf('en') == -1) this.languages_.unshift('en');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001612
1613 this.messages = {};
1614};
1615
1616/**
1617 * Add message definitions to the message manager.
1618 *
1619 * This takes an object of the same format of a Chrome messages.json file. See
1620 * <https://developer.chrome.com/extensions/i18n-messages>.
1621 */
1622lib.MessageManager.prototype.addMessages = function(defs) {
1623 for (var key in defs) {
1624 var def = defs[key];
1625
1626 if (!def.placeholders) {
1627 this.messages[key] = def.message;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001628 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001629 // Replace "$NAME$" placeholders with "$1", etc.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001630 this.messages[key] =
1631 def.message.replace(/\$([a-z][^\s\$]+)\$/ig, function(m, name) {
1632 return defs[key].placeholders[name.toLowerCase()].content;
1633 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001634 }
1635 }
1636};
1637
1638/**
1639 * Load the first available language message bundle.
1640 *
1641 * @param {string} pattern A url pattern containing a "$1" where the locale
1642 * name should go.
1643 * @param {function(Array,Array)} onComplete Function to be called when loading
1644 * is complete. The two arrays are the list of successful and failed
1645 * locale names. If the first parameter is length 0, no locales were
1646 * loaded.
1647 */
1648lib.MessageManager.prototype.findAndLoadMessages = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001649 pattern, onComplete) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001650 var languages = this.languages_.concat();
1651 var loaded = [];
1652 var failed = [];
1653
1654 function onLanguageComplete(state) {
1655 if (state) {
1656 loaded = languages.shift();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001657 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001658 failed = languages.shift();
1659 }
1660
1661 if (languages.length) {
1662 tryNextLanguage();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001663 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001664 onComplete(loaded, failed);
1665 }
1666 }
1667
1668 var tryNextLanguage = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001669 this.loadMessages(
1670 this.replaceReferences(pattern, languages),
1671 onLanguageComplete.bind(this, true),
1672 onLanguageComplete.bind(this, false));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001673 }.bind(this);
1674
1675 tryNextLanguage();
1676};
1677
1678/**
1679 * Load messages from a messages.json file.
1680 */
1681lib.MessageManager.prototype.loadMessages = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001682 url, onSuccess, opt_onError) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001683 var xhr = new XMLHttpRequest();
1684
1685 xhr.onloadend = function() {
1686 if (xhr.status != 200) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001687 if (opt_onError) opt_onError(xhr.status);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001688
1689 return;
1690 }
1691
1692 this.addMessages(JSON.parse(xhr.responseText));
1693 onSuccess();
1694 }.bind(this);
1695
1696 xhr.open('GET', url);
1697 xhr.send();
1698};
1699
1700/**
1701 * Replace $1...$n references with the elements of the args array.
1702 *
1703 * @param {string} msg String containing the message and argument references.
1704 * @param {Array} args Array containing the argument values.
1705 */
1706lib.MessageManager.replaceReferences = function(msg, args) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001707 return msg.replace(/\$(\d+)/g, function(m, index) {
1708 return args[index - 1];
1709 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001710};
1711
1712/**
1713 * Per-instance copy of replaceReferences.
1714 */
1715lib.MessageManager.prototype.replaceReferences =
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001716 lib.MessageManager.replaceReferences;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001717
1718/**
1719 * Get a message by name, optionally replacing arguments too.
1720 *
1721 * @param {string} msgname String containing the name of the message to get.
1722 * @param {Array} opt_args Optional array containing the argument values.
1723 * @param {string} opt_default Optional value to return if the msgname is not
1724 * found. Returns the message name by default.
1725 */
1726lib.MessageManager.prototype.get = function(msgname, opt_args, opt_default) {
1727 var message;
1728
1729 if (msgname in this.messages) {
1730 message = this.messages[msgname];
1731
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001732 } else {
1733 if (window.chrome.i18n) message = chrome.i18n.getMessage(msgname);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001734
1735 if (!message) {
1736 console.warn('Unknown message: ' + msgname);
1737 return (typeof opt_default == 'undefined') ? msgname : opt_default;
1738 }
1739 }
1740
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001741 if (!opt_args) return message;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001742
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001743 if (!(opt_args instanceof Array)) opt_args = [opt_args];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001744
1745 return this.replaceReferences(message, opt_args);
1746};
1747
1748/**
1749 * Process all of the "i18n" html attributes found in a given dom fragment.
1750 *
1751 * Each i18n attribute should contain a JSON object. The keys are taken to
1752 * be attribute names, and the values are message names.
1753 *
1754 * If the JSON object has a "_" (underscore) key, it's value is used as the
1755 * textContent of the element.
1756 *
1757 * Message names can refer to other attributes on the same element with by
1758 * prefixing with a dollar sign. For example...
1759 *
1760 * <button id='send-button'
1761 * i18n='{"aria-label": "$id", "_": "SEND_BUTTON_LABEL"}'
1762 * ></button>
1763 *
1764 * The aria-label message name will be computed as "SEND_BUTTON_ARIA_LABEL".
1765 * Notice that the "id" attribute was appended to the target attribute, and
1766 * the result converted to UPPER_AND_UNDER style.
1767 */
1768lib.MessageManager.prototype.processI18nAttributes = function(dom) {
1769 // Convert the "lower-and-dashes" attribute names into
1770 // "UPPER_AND_UNDER" style.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001771 function thunk(str) {
1772 return str.replace(/-/g, '_').toUpperCase();
1773 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001774
1775 var nodes = dom.querySelectorAll('[i18n]');
1776
1777 for (var i = 0; i < nodes.length; i++) {
1778 var node = nodes[i];
1779 var i18n = node.getAttribute('i18n');
1780
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001781 if (!i18n) continue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001782
1783 try {
1784 i18n = JSON.parse(i18n);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001785 } catch (ex) {
1786 console.error(
1787 'Can\'t parse ' + node.tagName + '#' + node.id + ': ' + i18n);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001788 throw ex;
1789 }
1790
1791 for (var key in i18n) {
1792 var msgname = i18n[key];
1793 if (msgname.substr(0, 1) == '$')
1794 msgname = thunk(node.getAttribute(msgname.substr(1)) + '_' + key);
1795
1796 var msg = this.get(msgname);
1797 if (key == '_') {
1798 node.textContent = msg;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001799 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001800 node.setAttribute(key, msg);
1801 }
1802 }
1803 }
1804};
1805// SOURCE FILE: libdot/js/lib_preference_manager.js
1806// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
1807// Use of this source code is governed by a BSD-style license that can be
1808// found in the LICENSE file.
1809
1810'use strict';
1811
1812/**
1813 * Constructor for lib.PreferenceManager objects.
1814 *
1815 * These objects deal with persisting changes to stable storage and notifying
1816 * consumers when preferences change.
1817 *
1818 * It is intended that the backing store could be something other than HTML5
1819 * storage, but there aren't any use cases at the moment. In the future there
1820 * may be a chrome api to store sync-able name/value pairs, and we'd want
1821 * that.
1822 *
1823 * @param {lib.Storage.*} storage The storage object to use as a backing
1824 * store.
1825 * @param {string} opt_prefix The optional prefix to be used for all preference
1826 * names. The '/' character should be used to separate levels of hierarchy,
1827 * if you're going to have that kind of thing. If provided, the prefix
1828 * should start with a '/'. If not provided, it defaults to '/'.
1829 */
1830lib.PreferenceManager = function(storage, opt_prefix) {
1831 this.storage = storage;
1832 this.storageObserver_ = this.onStorageChange_.bind(this);
1833
1834 this.isActive_ = false;
1835 this.activate();
1836
1837 this.trace = false;
1838
1839 var prefix = opt_prefix || '/';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001840 if (prefix.substr(prefix.length - 1) != '/') prefix += '/';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001841
1842 this.prefix = prefix;
1843
1844 this.prefRecords_ = {};
1845 this.globalObservers_ = [];
1846
1847 this.childFactories_ = {};
1848
1849 // Map of list-name to {map of child pref managers}
1850 // As in...
1851 //
1852 // this.childLists_ = {
1853 // 'profile-ids': {
1854 // 'one': PreferenceManager,
1855 // 'two': PreferenceManager,
1856 // ...
1857 // },
1858 //
1859 // 'frob-ids': {
1860 // ...
1861 // }
1862 // }
1863 this.childLists_ = {};
1864};
1865
1866/**
1867 * Used internally to indicate that the current value of the preference should
1868 * be taken from the default value defined with the preference.
1869 *
1870 * Equality tests against this value MUST use '===' or '!==' to be accurate.
1871 */
1872lib.PreferenceManager.prototype.DEFAULT_VALUE = new String('DEFAULT');
1873
1874/**
1875 * An individual preference.
1876 *
1877 * These objects are managed by the PreferenceManager, you shouldn't need to
1878 * handle them directly.
1879 */
1880lib.PreferenceManager.Record = function(name, defaultValue) {
1881 this.name = name;
1882 this.defaultValue = defaultValue;
1883 this.currentValue = this.DEFAULT_VALUE;
1884 this.observers = [];
1885};
1886
1887/**
1888 * A local copy of the DEFAULT_VALUE constant to make it less verbose.
1889 */
1890lib.PreferenceManager.Record.prototype.DEFAULT_VALUE =
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001891 lib.PreferenceManager.prototype.DEFAULT_VALUE;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001892
1893/**
1894 * Register a callback to be invoked when this preference changes.
1895 *
1896 * @param {function(value, string, lib.PreferenceManager} observer The function
1897 * to invoke. It will receive the new value, the name of the preference,
1898 * and a reference to the PreferenceManager as parameters.
1899 */
1900lib.PreferenceManager.Record.prototype.addObserver = function(observer) {
1901 this.observers.push(observer);
1902};
1903
1904/**
1905 * Unregister an observer callback.
1906 *
1907 * @param {function} observer A previously registered callback.
1908 */
1909lib.PreferenceManager.Record.prototype.removeObserver = function(observer) {
1910 var i = this.observers.indexOf(observer);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001911 if (i >= 0) this.observers.splice(i, 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001912};
1913
1914/**
1915 * Fetch the value of this preference.
1916 */
1917lib.PreferenceManager.Record.prototype.get = function() {
1918 if (this.currentValue === this.DEFAULT_VALUE) {
1919 if (/^(string|number)$/.test(typeof this.defaultValue))
1920 return this.defaultValue;
1921
1922 if (typeof this.defaultValue == 'object') {
1923 // We want to return a COPY of the default value so that users can
1924 // modify the array or object without changing the default value.
1925 return JSON.parse(JSON.stringify(this.defaultValue));
1926 }
1927
1928 return this.defaultValue;
1929 }
1930
1931 return this.currentValue;
1932};
1933
1934/**
1935 * Stop this preference manager from tracking storage changes.
1936 *
1937 * Call this if you're going to swap out one preference manager for another so
1938 * that you don't get notified about irrelevant changes.
1939 */
1940lib.PreferenceManager.prototype.deactivate = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001941 if (!this.isActive_) throw new Error('Not activated');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001942
1943 this.isActive_ = false;
1944 this.storage.removeObserver(this.storageObserver_);
1945};
1946
1947/**
1948 * Start tracking storage changes.
1949 *
1950 * If you previously deactivated this preference manager, you can reactivate it
1951 * with this method. You don't need to call this at initialization time, as
1952 * it's automatically called as part of the constructor.
1953 */
1954lib.PreferenceManager.prototype.activate = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001955 if (this.isActive_) throw new Error('Already activated');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001956
1957 this.isActive_ = true;
1958 this.storage.addObserver(this.storageObserver_);
1959};
1960
1961/**
1962 * Read the backing storage for these preferences.
1963 *
1964 * You should do this once at initialization time to prime the local cache
1965 * of preference values. The preference manager will monitor the backing
1966 * storage for changes, so you should not need to call this more than once.
1967 *
1968 * This function recursively reads storage for all child preference managers as
1969 * well.
1970 *
1971 * This function is asynchronous, if you need to read preference values, you
1972 * *must* wait for the callback.
1973 *
1974 * @param {function()} opt_callback Optional function to invoke when the read
1975 * has completed.
1976 */
1977lib.PreferenceManager.prototype.readStorage = function(opt_callback) {
1978 var pendingChildren = 0;
1979
1980 function onChildComplete() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001981 if (--pendingChildren == 0 && opt_callback) opt_callback();
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001982 }
1983
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001984 var keys = Object.keys(this.prefRecords_).map(function(el) {
1985 return this.prefix + el;
1986 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001987
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001988 if (this.trace) console.log('Preferences read: ' + this.prefix);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001989
1990 this.storage.getItems(keys, function(items) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001991 var prefixLength = this.prefix.length;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05001992
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07001993 for (var key in items) {
1994 var value = items[key];
1995 var name = key.substr(prefixLength);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07001996 var needSync =
1997 (name in this.childLists_ &&
1998 (JSON.stringify(value) !=
1999 JSON.stringify(this.prefRecords_[name].currentValue)));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002000
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07002001 this.prefRecords_[name].currentValue = value;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002002
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07002003 if (needSync) {
2004 pendingChildren++;
2005 this.syncChildList(name, onChildComplete);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002006 }
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07002007 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002008
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002009 if (pendingChildren == 0 && opt_callback) setTimeout(opt_callback);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07002010 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002011};
2012
2013/**
2014 * Define a preference.
2015 *
2016 * This registers a name, default value, and onChange handler for a preference.
2017 *
2018 * @param {string} name The name of the preference. This will be prefixed by
2019 * the prefix of this PreferenceManager before written to local storage.
2020 * @param {string|number|boolean|Object|Array|null} value The default value of
2021 * this preference. Anything that can be represented in JSON is a valid
2022 * default value.
2023 * @param {function(value, string, lib.PreferenceManager} opt_observer A
2024 * function to invoke when the preference changes. It will receive the new
2025 * value, the name of the preference, and a reference to the
2026 * PreferenceManager as parameters.
2027 */
2028lib.PreferenceManager.prototype.definePreference = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002029 name, value, opt_onChange) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002030
2031 var record = this.prefRecords_[name];
2032 if (record) {
2033 this.changeDefault(name, value);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002034 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002035 record = this.prefRecords_[name] =
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002036 new lib.PreferenceManager.Record(name, value);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002037 }
2038
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002039 if (opt_onChange) record.addObserver(opt_onChange);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002040};
2041
2042/**
2043 * Define multiple preferences with a single function call.
2044 *
2045 * @param {Array} defaults An array of 3-element arrays. Each three element
2046 * array should contain the [key, value, onChange] parameters for a
2047 * preference.
2048 */
2049lib.PreferenceManager.prototype.definePreferences = function(defaults) {
2050 for (var i = 0; i < defaults.length; i++) {
2051 this.definePreference(defaults[i][0], defaults[i][1], defaults[i][2]);
2052 }
2053};
2054
2055/**
2056 * Define an ordered list of child preferences.
2057 *
2058 * Child preferences are different from just storing an array of JSON objects
2059 * in that each child is an instance of a preference manager. This means you
2060 * can observe changes to individual child preferences, and get some validation
2061 * that you're not reading or writing to an undefined child preference value.
2062 *
2063 * @param {string} listName A name for the list of children. This must be
2064 * unique in this preference manager. The listName will become a
2065 * preference on this PreferenceManager used to store the ordered list of
2066 * child ids. It is also used in get/add/remove operations to identify the
2067 * list of children to operate on.
2068 * @param {function} childFactory A function that will be used to generate
2069 * instances of these children. The factory function will receive the
2070 * parent lib.PreferenceManager object and a unique id for the new child
2071 * preferences.
2072 */
2073lib.PreferenceManager.prototype.defineChildren = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002074 listName, childFactory) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002075
2076 // Define a preference to hold the ordered list of child ids.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002077 this.definePreference(
2078 listName, [], this.onChildListChange_.bind(this, listName));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002079 this.childFactories_[listName] = childFactory;
2080 this.childLists_[listName] = {};
2081};
2082
2083/**
2084 * Register to observe preference changes.
2085 *
2086 * @param {Function} global A callback that will happen for every preference.
2087 * Pass null if you don't need one.
2088 * @param {Object} map A map of preference specific callbacks. Pass null if
2089 * you don't need any.
2090 */
2091lib.PreferenceManager.prototype.addObservers = function(global, map) {
2092 if (global && typeof global != 'function')
2093 throw new Error('Invalid param: globals');
2094
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002095 if (global) this.globalObservers_.push(global);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002096
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002097 if (!map) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002098
2099 for (var name in map) {
2100 if (!(name in this.prefRecords_))
2101 throw new Error('Unknown preference: ' + name);
2102
2103 this.prefRecords_[name].addObserver(map[name]);
2104 }
2105};
2106
2107/**
2108 * Dispatch the change observers for all known preferences.
2109 *
2110 * It may be useful to call this after readStorage completes, in order to
2111 * get application state in sync with user preferences.
2112 *
2113 * This can be used if you've changed a preference manager out from under
2114 * a live object, for example when switching to a different prefix.
2115 */
2116lib.PreferenceManager.prototype.notifyAll = function() {
2117 for (var name in this.prefRecords_) {
2118 this.notifyChange_(name);
2119 }
2120};
2121
2122/**
2123 * Notify the change observers for a given preference.
2124 *
2125 * @param {string} name The name of the preference that changed.
2126 */
2127lib.PreferenceManager.prototype.notifyChange_ = function(name) {
2128 var record = this.prefRecords_[name];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002129 if (!record) throw new Error('Unknown preference: ' + name);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002130
2131 var currentValue = record.get();
2132
2133 for (var i = 0; i < this.globalObservers_.length; i++)
2134 this.globalObservers_[i](name, currentValue);
2135
2136 for (var i = 0; i < record.observers.length; i++) {
2137 record.observers[i](currentValue, name, this);
2138 }
2139};
2140
2141/**
2142 * Create a new child PreferenceManager for the given child list.
2143 *
2144 * The optional hint parameter is an opaque prefix added to the auto-generated
2145 * unique id for this child. Your child factory can parse out the prefix
2146 * and use it.
2147 *
2148 * @param {string} listName The child list to create the new instance from.
2149 * @param {string} opt_hint Optional hint to include in the child id.
2150 * @param {string} opt_id Optional id to override the generated id.
2151 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002152lib.PreferenceManager.prototype.createChild = function(
2153 listName, opt_hint, opt_id) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002154 var ids = this.get(listName);
2155 var id;
2156
2157 if (opt_id) {
2158 id = opt_id;
2159 if (ids.indexOf(id) != -1)
2160 throw new Error('Duplicate child: ' + listName + ': ' + id);
2161
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002162 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002163 // Pick a random, unique 4-digit hex identifier for the new profile.
2164 while (!id || ids.indexOf(id) != -1) {
2165 id = Math.floor(Math.random() * 0xffff + 1).toString(16);
2166 id = lib.f.zpad(id, 4);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002167 if (opt_hint) id = opt_hint + ':' + id;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002168 }
2169 }
2170
2171 var childManager = this.childFactories_[listName](this, id);
2172 childManager.trace = this.trace;
2173 childManager.resetAll();
2174
2175 this.childLists_[listName][id] = childManager;
2176
2177 ids.push(id);
2178 this.set(listName, ids);
2179
2180 return childManager;
2181};
2182
2183/**
2184 * Remove a child preferences instance.
2185 *
2186 * Removes a child preference manager and clears any preferences stored in it.
2187 *
2188 * @param {string} listName The name of the child list containing the child to
2189 * remove.
2190 * @param {string} id The child ID.
2191 */
2192lib.PreferenceManager.prototype.removeChild = function(listName, id) {
2193 var prefs = this.getChild(listName, id);
2194 prefs.resetAll();
2195
2196 var ids = this.get(listName);
2197 var i = ids.indexOf(id);
2198 if (i != -1) {
2199 ids.splice(i, 1);
2200 this.set(listName, ids);
2201 }
2202
2203 delete this.childLists_[listName][id];
2204};
2205
2206/**
2207 * Return a child PreferenceManager instance for a given id.
2208 *
2209 * If the child list or child id is not known this will return the specified
2210 * default value or throw an exception if no default value is provided.
2211 *
2212 * @param {string} listName The child list to look in.
2213 * @param {string} id The child ID.
2214 * @param {*} opt_default The optional default value to return if the child
2215 * is not found.
2216 */
2217lib.PreferenceManager.prototype.getChild = function(listName, id, opt_default) {
2218 if (!(listName in this.childLists_))
2219 throw new Error('Unknown child list: ' + listName);
2220
2221 var childList = this.childLists_[listName];
2222 if (!(id in childList)) {
2223 if (typeof opt_default == 'undefined')
2224 throw new Error('Unknown "' + listName + '" child: ' + id);
2225
2226 return opt_default;
2227 }
2228
2229 return childList[id];
2230};
2231
2232/**
2233 * Calculate the difference between two lists of child ids.
2234 *
2235 * Given two arrays of child ids, this function will return an object
2236 * with "added", "removed", and "common" properties. Each property is
2237 * a map of child-id to `true`. For example, given...
2238 *
2239 * a = ['child-x', 'child-y']
2240 * b = ['child-y']
2241 *
2242 * diffChildLists(a, b) =>
2243 * { added: { 'child-x': true }, removed: {}, common: { 'child-y': true } }
2244 *
2245 * The added/removed properties assume that `a` is the current list.
2246 *
2247 * @param {Array[string]} a The most recent list of child ids.
2248 * @param {Array[string]} b An older list of child ids.
2249 * @return {Object} An object with added/removed/common properties.
2250 */
2251lib.PreferenceManager.diffChildLists = function(a, b) {
2252 var rv = {
2253 added: {},
2254 removed: {},
2255 common: {},
2256 };
2257
2258 for (var i = 0; i < a.length; i++) {
2259 if (b.indexOf(a[i]) != -1) {
2260 rv.common[a[i]] = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002261 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002262 rv.added[a[i]] = true;
2263 }
2264 }
2265
2266 for (var i = 0; i < b.length; i++) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002267 if ((b[i] in rv.added) || (b[i] in rv.common)) continue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002268
2269 rv.removed[b[i]] = true;
2270 }
2271
2272 return rv;
2273};
2274
2275/**
2276 * Synchronize a list of child PreferenceManagers instances with the current
2277 * list stored in prefs.
2278 *
2279 * This will instantiate any missing managers and read current preference values
2280 * from storage. Any active managers that no longer appear in preferences will
2281 * be deleted.
2282 *
2283 * @param {string} listName The child list to synchronize.
2284 * @param {function()} opt_callback Optional function to invoke when the sync
2285 * is complete.
2286 */
2287lib.PreferenceManager.prototype.syncChildList = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002288 listName, opt_callback) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002289
2290 var pendingChildren = 0;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07002291
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002292 function onChildStorage() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002293 if (--pendingChildren == 0 && opt_callback) opt_callback();
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002294 }
2295
2296 // The list of child ids that we *should* have a manager for.
2297 var currentIds = this.get(listName);
2298
2299 // The known managers at the start of the sync. Any manager still in this
2300 // list at the end should be discarded.
2301 var oldIds = Object.keys(this.childLists_[listName]);
2302
2303 var rv = lib.PreferenceManager.diffChildLists(currentIds, oldIds);
2304
2305 for (var i = 0; i < currentIds.length; i++) {
2306 var id = currentIds[i];
2307
2308 var managerIndex = oldIds.indexOf(id);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002309 if (managerIndex >= 0) oldIds.splice(managerIndex, 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002310
2311 if (!this.childLists_[listName][id]) {
2312 var childManager = this.childFactories_[listName](this, id);
2313 if (!childManager) {
2314 console.warn('Unable to restore child: ' + listName + ': ' + id);
2315 continue;
2316 }
2317
2318 childManager.trace = this.trace;
2319 this.childLists_[listName][id] = childManager;
2320 pendingChildren++;
2321 childManager.readStorage(onChildStorage);
2322 }
2323 }
2324
2325 for (var i = 0; i < oldIds.length; i++) {
2326 delete this.childLists_[listName][oldIds[i]];
2327 }
2328
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002329 if (!pendingChildren && opt_callback) setTimeout(opt_callback);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002330};
2331
2332/**
2333 * Reset a preference to its default state.
2334 *
2335 * This will dispatch the onChange handler if the preference value actually
2336 * changes.
2337 *
2338 * @param {string} name The preference to reset.
2339 */
2340lib.PreferenceManager.prototype.reset = function(name) {
2341 var record = this.prefRecords_[name];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002342 if (!record) throw new Error('Unknown preference: ' + name);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002343
2344 this.storage.removeItem(this.prefix + name);
2345
2346 if (record.currentValue !== this.DEFAULT_VALUE) {
2347 record.currentValue = this.DEFAULT_VALUE;
2348 this.notifyChange_(name);
2349 }
2350};
2351
2352/**
2353 * Reset all preferences back to their default state.
2354 */
2355lib.PreferenceManager.prototype.resetAll = function() {
2356 var changed = [];
2357
2358 for (var listName in this.childLists_) {
2359 var childList = this.childLists_[listName];
2360 for (var id in childList) {
2361 childList[id].resetAll();
2362 }
2363 }
2364
2365 for (var name in this.prefRecords_) {
2366 if (this.prefRecords_[name].currentValue !== this.DEFAULT_VALUE) {
2367 this.prefRecords_[name].currentValue = this.DEFAULT_VALUE;
2368 changed.push(name);
2369 }
2370 }
2371
2372 var keys = Object.keys(this.prefRecords_).map(function(el) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07002373 return this.prefix + el;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002374 }.bind(this));
2375
2376 this.storage.removeItems(keys);
2377
2378 changed.forEach(this.notifyChange_.bind(this));
2379};
2380
2381/**
2382 * Return true if two values should be considered not-equal.
2383 *
2384 * If both values are the same scalar type and compare equal this function
2385 * returns false (no difference), otherwise return true.
2386 *
2387 * This is used in places where we want to check if a preference has changed.
2388 * Rather than take the time to compare complex values we just consider them
2389 * to always be different.
2390 *
2391 * @param {*} a A value to compare.
2392 * @param {*} b A value to compare.
2393 */
2394lib.PreferenceManager.prototype.diff = function(a, b) {
2395 // If the types are different, or the type is not a simple primitive one.
2396 if ((typeof a) !== (typeof b) ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002397 !(/^(undefined|boolean|number|string)$/.test(typeof a))) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002398 return true;
2399 }
2400
2401 return a !== b;
2402};
2403
2404/**
2405 * Change the default value of a preference.
2406 *
2407 * This is useful when subclassing preference managers.
2408 *
2409 * The function does not alter the current value of the preference, unless
2410 * it has the old default value. When that happens, the change observers
2411 * will be notified.
2412 *
2413 * @param {string} name The name of the parameter to change.
2414 * @param {*} newValue The new default value for the preference.
2415 */
2416lib.PreferenceManager.prototype.changeDefault = function(name, newValue) {
2417 var record = this.prefRecords_[name];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002418 if (!record) throw new Error('Unknown preference: ' + name);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002419
2420 if (!this.diff(record.defaultValue, newValue)) {
2421 // Default value hasn't changed.
2422 return;
2423 }
2424
2425 if (record.currentValue !== this.DEFAULT_VALUE) {
2426 // This pref has a specific value, just change the default and we're done.
2427 record.defaultValue = newValue;
2428 return;
2429 }
2430
2431 record.defaultValue = newValue;
2432
2433 this.notifyChange_(name);
2434};
2435
2436/**
2437 * Change the default value of multiple preferences.
2438 *
2439 * @param {Object} map A map of name -> value pairs specifying the new default
2440 * values.
2441 */
2442lib.PreferenceManager.prototype.changeDefaults = function(map) {
2443 for (var key in map) {
2444 this.changeDefault(key, map[key]);
2445 }
2446};
2447
2448/**
2449 * Set a preference to a specific value.
2450 *
2451 * This will dispatch the onChange handler if the preference value actually
2452 * changes.
2453 *
2454 * @param {string} key The preference to set.
2455 * @param {*} value The value to set. Anything that can be represented in
2456 * JSON is a valid value.
2457 */
2458lib.PreferenceManager.prototype.set = function(name, newValue) {
2459 var record = this.prefRecords_[name];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002460 if (!record) throw new Error('Unknown preference: ' + name);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002461
2462 var oldValue = record.get();
2463
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002464 if (!this.diff(oldValue, newValue)) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002465
2466 if (this.diff(record.defaultValue, newValue)) {
2467 record.currentValue = newValue;
2468 this.storage.setItem(this.prefix + name, newValue);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002469 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002470 record.currentValue = this.DEFAULT_VALUE;
2471 this.storage.removeItem(this.prefix + name);
2472 }
2473
2474 // We need to manually send out the notification on this instance. If we
2475 // The storage event won't fire a notification because we've already changed
2476 // the currentValue, so it won't see a difference. If we delayed changing
2477 // currentValue until the storage event, a pref read immediately after a write
2478 // would return the previous value.
2479 //
2480 // The notification is in a timeout so clients don't accidentally depend on
2481 // a synchronous notification.
2482 setTimeout(this.notifyChange_.bind(this, name), 0);
2483};
2484
2485/**
2486 * Get the value of a preference.
2487 *
2488 * @param {string} key The preference to get.
2489 */
2490lib.PreferenceManager.prototype.get = function(name) {
2491 var record = this.prefRecords_[name];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002492 if (!record) throw new Error('Unknown preference: ' + name);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002493
2494 return record.get();
2495};
2496
2497/**
2498 * Return all non-default preferences as a JSON object.
2499 *
2500 * This includes any nested preference managers as well.
2501 */
2502lib.PreferenceManager.prototype.exportAsJson = function() {
2503 var rv = {};
2504
2505 for (var name in this.prefRecords_) {
2506 if (name in this.childLists_) {
2507 rv[name] = [];
2508 var childIds = this.get(name);
2509 for (var i = 0; i < childIds.length; i++) {
2510 var id = childIds[i];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002511 rv[name].push({id: id, json: this.getChild(name, id).exportAsJson()});
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002512 }
2513
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002514 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002515 var record = this.prefRecords_[name];
2516 if (record.currentValue != this.DEFAULT_VALUE)
2517 rv[name] = record.currentValue;
2518 }
2519 }
2520
2521 return rv;
2522};
2523
2524/**
2525 * Import a JSON blob of preferences previously generated with exportAsJson.
2526 *
2527 * This will create nested preference managers as well.
2528 */
2529lib.PreferenceManager.prototype.importFromJson = function(json) {
2530 for (var name in json) {
2531 if (name in this.childLists_) {
2532 var childList = json[name];
2533 for (var i = 0; i < childList.length; i++) {
2534 var id = childList[i].id;
2535
2536 var childPrefManager = this.childLists_[name][id];
2537 if (!childPrefManager)
2538 childPrefManager = this.createChild(name, null, id);
2539
2540 childPrefManager.importFromJson(childList[i].json);
2541 }
2542
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002543 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002544 this.set(name, json[name]);
2545 }
2546 }
2547};
2548
2549/**
2550 * Called when one of the child list preferences changes.
2551 */
2552lib.PreferenceManager.prototype.onChildListChange_ = function(listName) {
2553 this.syncChildList(listName);
2554};
2555
2556/**
2557 * Called when a key in the storage changes.
2558 */
2559lib.PreferenceManager.prototype.onStorageChange_ = function(map) {
2560 for (var key in map) {
2561 if (this.prefix) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002562 if (key.lastIndexOf(this.prefix, 0) != 0) continue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002563 }
2564
2565 var name = key.substr(this.prefix.length);
2566
2567 if (!(name in this.prefRecords_)) {
2568 // Sometimes we'll get notified about prefs that are no longer defined.
2569 continue;
2570 }
2571
2572 var record = this.prefRecords_[name];
2573
2574 var newValue = map[key].newValue;
2575 var currentValue = record.currentValue;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002576 if (currentValue === record.DEFAULT_VALUE) currentValue = (void 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002577
2578 if (this.diff(currentValue, newValue)) {
2579 if (typeof newValue == 'undefined') {
2580 record.currentValue = record.DEFAULT_VALUE;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002581 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002582 record.currentValue = newValue;
2583 }
2584
2585 this.notifyChange_(name);
2586 }
2587 }
2588};
2589// SOURCE FILE: libdot/js/lib_resource.js
2590// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2591// Use of this source code is governed by a BSD-style license that can be
2592// found in the LICENSE file.
2593
2594'use strict';
2595
2596/**
2597 * Storage for canned resources.
2598 *
2599 * These are usually non-JavaScript things that are collected during a build
2600 * step and converted into a series of 'lib.resource.add(...)' calls. See
2601 * the "@resource" directive from libdot/bin/concat.sh for the canonical use
2602 * case.
2603 *
2604 * This is global storage, so you should prefix your resource names to avoid
2605 * collisions.
2606 */
2607lib.resource = {
2608 resources_: {}
2609};
2610
2611/**
2612 * Add a resource.
2613 *
2614 * @param {string} name A name for the resource. You should prefix this to
2615 * avoid collisions with resources from a shared library.
2616 * @param {string} type A mime type for the resource, or "raw" if not
2617 * applicable.
2618 * @param {*} data The value of the resource.
2619 */
2620lib.resource.add = function(name, type, data) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002621 lib.resource.resources_[name] = {type: type, name: name, data: data};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002622};
2623
2624/**
2625 * Retrieve a resource record.
2626 *
2627 * The resource data is stored on the "data" property of the returned object.
2628 *
2629 * @param {string} name The name of the resource to get.
2630 * @param {*} opt_defaultValue The optional value to return if the resource is
2631 * not defined.
2632 * @return {object} An object with "type", "name", and "data" properties.
2633 */
2634lib.resource.get = function(name, opt_defaultValue) {
2635 if (!(name in lib.resource.resources_)) {
2636 if (typeof opt_defaultValue == 'undefined')
2637 throw 'Unknown resource: ' + name;
2638
2639 return opt_defaultValue;
2640 }
2641
2642 return lib.resource.resources_[name];
2643};
2644
2645/**
2646 * Retrieve resource data.
2647 *
2648 * @param {string} name The name of the resource to get.
2649 * @param {*} opt_defaultValue The optional value to return if the resource is
2650 * not defined.
2651 * @return {*} The resource data.
2652 */
2653lib.resource.getData = function(name, opt_defaultValue) {
2654 if (!(name in lib.resource.resources_)) {
2655 if (typeof opt_defaultValue == 'undefined')
2656 throw 'Unknown resource: ' + name;
2657
2658 return opt_defaultValue;
2659 }
2660
2661 return lib.resource.resources_[name].data;
2662};
2663
2664/**
2665 * Retrieve resource as a data: url.
2666 *
2667 * @param {string} name The name of the resource to get.
2668 * @param {*} opt_defaultValue The optional value to return if the resource is
2669 * not defined.
2670 * @return {*} A data: url encoded version of the resource.
2671 */
2672lib.resource.getDataUrl = function(name, opt_defaultValue) {
2673 var resource = lib.resource.get(name, opt_defaultValue);
2674 return 'data:' + resource.type + ',' + resource.data;
2675};
2676// SOURCE FILE: libdot/js/lib_storage.js
2677// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2678// Use of this source code is governed by a BSD-style license that can be
2679// found in the LICENSE file.
2680
2681'use strict';
2682
2683/**
2684 * Namespace for implementations of persistent, possibly cloud-backed
2685 * storage.
2686 */
2687lib.Storage = new Object();
2688// SOURCE FILE: libdot/js/lib_storage_chrome.js
2689// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2690// Use of this source code is governed by a BSD-style license that can be
2691// found in the LICENSE file.
2692
2693'use strict';
2694
2695/**
2696 * chrome.storage based class with an async interface that is interchangeable
2697 * with other lib.Storage.* implementations.
2698 */
2699lib.Storage.Chrome = function(storage) {
2700 this.storage_ = storage;
2701 this.observers_ = [];
2702
2703 chrome.storage.onChanged.addListener(this.onChanged_.bind(this));
2704};
2705
2706/**
2707 * Called by the storage implementation when the storage is modified.
2708 */
2709lib.Storage.Chrome.prototype.onChanged_ = function(changes, areaname) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002710 if (chrome.storage[areaname] != this.storage_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002711
2712 for (var i = 0; i < this.observers_.length; i++) {
2713 this.observers_[i](changes);
2714 }
2715};
2716
2717/**
2718 * Register a function to observe storage changes.
2719 *
2720 * @param {function(map)} callback The function to invoke when the storage
2721 * changes.
2722 */
2723lib.Storage.Chrome.prototype.addObserver = function(callback) {
2724 this.observers_.push(callback);
2725};
2726
2727/**
2728 * Unregister a change observer.
2729 *
2730 * @param {function} observer A previously registered callback.
2731 */
2732lib.Storage.Chrome.prototype.removeObserver = function(callback) {
2733 var i = this.observers_.indexOf(callback);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002734 if (i != -1) this.observers_.splice(i, 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002735};
2736
2737/**
2738 * Delete everything in this storage.
2739 *
2740 * @param {function(map)} callback The function to invoke when the delete
2741 * has completed.
2742 */
2743lib.Storage.Chrome.prototype.clear = function(opt_callback) {
2744 this.storage_.clear();
2745
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002746 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002747};
2748
2749/**
2750 * Return the current value of a storage item.
2751 *
2752 * @param {string} key The key to look up.
2753 * @param {function(value) callback The function to invoke when the value has
2754 * been retrieved.
2755 */
2756lib.Storage.Chrome.prototype.getItem = function(key, callback) {
2757 this.storage_.get(key, callback);
2758};
2759/**
2760 * Fetch the values of multiple storage items.
2761 *
2762 * @param {Array} keys The keys to look up.
2763 * @param {function(map) callback The function to invoke when the values have
2764 * been retrieved.
2765 */
2766
2767lib.Storage.Chrome.prototype.getItems = function(keys, callback) {
2768 this.storage_.get(keys, callback);
2769};
2770
2771/**
2772 * Set a value in storage.
2773 *
2774 * @param {string} key The key for the value to be stored.
2775 * @param {*} value The value to be stored. Anything that can be serialized
2776 * with JSON is acceptable.
2777 * @param {function()} opt_callback Optional function to invoke when the
2778 * set is complete. You don't have to wait for the set to complete in order
2779 * to read the value, since the local cache is updated synchronously.
2780 */
2781lib.Storage.Chrome.prototype.setItem = function(key, value, opt_callback) {
2782 var obj = {};
2783 obj[key] = value;
2784 this.storage_.set(obj, opt_callback);
2785};
2786
2787/**
2788 * Set multiple values in storage.
2789 *
2790 * @param {Object} map A map of key/values to set in storage.
2791 * @param {function()} opt_callback Optional function to invoke when the
2792 * set is complete. You don't have to wait for the set to complete in order
2793 * to read the value, since the local cache is updated synchronously.
2794 */
2795lib.Storage.Chrome.prototype.setItems = function(obj, opt_callback) {
2796 this.storage_.set(obj, opt_callback);
2797};
2798
2799/**
2800 * Remove an item from storage.
2801 *
2802 * @param {string} key The key to be removed.
2803 * @param {function()} opt_callback Optional function to invoke when the
2804 * remove is complete. You don't have to wait for the set to complete in
2805 * order to read the value, since the local cache is updated synchronously.
2806 */
2807lib.Storage.Chrome.prototype.removeItem = function(key, opt_callback) {
2808 this.storage_.remove(key, opt_callback);
2809};
2810
2811/**
2812 * Remove multiple items from storage.
2813 *
2814 * @param {Array} keys The keys to be removed.
2815 * @param {function()} opt_callback Optional function to invoke when the
2816 * remove is complete. You don't have to wait for the set to complete in
2817 * order to read the value, since the local cache is updated synchronously.
2818 */
2819lib.Storage.Chrome.prototype.removeItems = function(keys, opt_callback) {
2820 this.storage_.remove(keys, opt_callback);
2821};
2822// SOURCE FILE: libdot/js/lib_storage_local.js
2823// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2824// Use of this source code is governed by a BSD-style license that can be
2825// found in the LICENSE file.
2826
2827'use strict';
2828
2829/**
2830 * window.localStorage based class with an async interface that is
2831 * interchangeable with other lib.Storage.* implementations.
2832 */
2833lib.Storage.Local = function() {
2834 this.observers_ = [];
2835 this.storage_ = window.localStorage;
2836 window.addEventListener('storage', this.onStorage_.bind(this));
2837};
2838
2839/**
2840 * Called by the storage implementation when the storage is modified.
2841 */
2842lib.Storage.Local.prototype.onStorage_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002843 if (e.storageArea != this.storage_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002844
2845 // IE throws an exception if JSON.parse is given an empty string.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07002846 var prevValue = e.oldValue ? JSON.parse(e.oldValue) : '';
2847 var curValue = e.newValue ? JSON.parse(e.newValue) : '';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002848 var o = {};
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002849 o[e.key] = {oldValue: prevValue, newValue: curValue};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002850
2851 for (var i = 0; i < this.observers_.length; i++) {
2852 this.observers_[i](o);
2853 }
2854};
2855
2856/**
2857 * Register a function to observe storage changes.
2858 *
2859 * @param {function(map)} callback The function to invoke when the storage
2860 * changes.
2861 */
2862lib.Storage.Local.prototype.addObserver = function(callback) {
2863 this.observers_.push(callback);
2864};
2865
2866/**
2867 * Unregister a change observer.
2868 *
2869 * @param {function} observer A previously registered callback.
2870 */
2871lib.Storage.Local.prototype.removeObserver = function(callback) {
2872 var i = this.observers_.indexOf(callback);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002873 if (i != -1) this.observers_.splice(i, 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002874};
2875
2876/**
2877 * Delete everything in this storage.
2878 *
2879 * @param {function(map)} callback The function to invoke when the delete
2880 * has completed.
2881 */
2882lib.Storage.Local.prototype.clear = function(opt_callback) {
2883 this.storage_.clear();
2884
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002885 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002886};
2887
2888/**
2889 * Return the current value of a storage item.
2890 *
2891 * @param {string} key The key to look up.
2892 * @param {function(value) callback The function to invoke when the value has
2893 * been retrieved.
2894 */
2895lib.Storage.Local.prototype.getItem = function(key, callback) {
2896 var value = this.storage_.getItem(key);
2897
2898 if (typeof value == 'string') {
2899 try {
2900 value = JSON.parse(value);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002901 } catch (e) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002902 // If we can't parse the value, just return it unparsed.
2903 }
2904 }
2905
2906 setTimeout(callback.bind(null, value), 0);
2907};
2908
2909/**
2910 * Fetch the values of multiple storage items.
2911 *
2912 * @param {Array} keys The keys to look up.
2913 * @param {function(map) callback The function to invoke when the values have
2914 * been retrieved.
2915 */
2916lib.Storage.Local.prototype.getItems = function(keys, callback) {
2917 var rv = {};
2918
2919 for (var i = keys.length - 1; i >= 0; i--) {
2920 var key = keys[i];
2921 var value = this.storage_.getItem(key);
2922 if (typeof value == 'string') {
2923 try {
2924 rv[key] = JSON.parse(value);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002925 } catch (e) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002926 // If we can't parse the value, just return it unparsed.
2927 rv[key] = value;
2928 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002929 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002930 keys.splice(i, 1);
2931 }
2932 }
2933
2934 setTimeout(callback.bind(null, rv), 0);
2935};
2936
2937/**
2938 * Set a value in storage.
2939 *
2940 * @param {string} key The key for the value to be stored.
2941 * @param {*} value The value to be stored. Anything that can be serialized
2942 * with JSON is acceptable.
2943 * @param {function()} opt_callback Optional function to invoke when the
2944 * set is complete. You don't have to wait for the set to complete in order
2945 * to read the value, since the local cache is updated synchronously.
2946 */
2947lib.Storage.Local.prototype.setItem = function(key, value, opt_callback) {
2948 this.storage_.setItem(key, JSON.stringify(value));
2949
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002950 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002951};
2952
2953/**
2954 * Set multiple values in storage.
2955 *
2956 * @param {Object} map A map of key/values to set in storage.
2957 * @param {function()} opt_callback Optional function to invoke when the
2958 * set is complete. You don't have to wait for the set to complete in order
2959 * to read the value, since the local cache is updated synchronously.
2960 */
2961lib.Storage.Local.prototype.setItems = function(obj, opt_callback) {
2962 for (var key in obj) {
2963 this.storage_.setItem(key, JSON.stringify(obj[key]));
2964 }
2965
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002966 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002967};
2968
2969/**
2970 * Remove an item from storage.
2971 *
2972 * @param {string} key The key to be removed.
2973 * @param {function()} opt_callback Optional function to invoke when the
2974 * remove is complete. You don't have to wait for the set to complete in
2975 * order to read the value, since the local cache is updated synchronously.
2976 */
2977lib.Storage.Local.prototype.removeItem = function(key, opt_callback) {
2978 this.storage_.removeItem(key);
2979
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002980 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002981};
2982
2983/**
2984 * Remove multiple items from storage.
2985 *
2986 * @param {Array} keys The keys to be removed.
2987 * @param {function()} opt_callback Optional function to invoke when the
2988 * remove is complete. You don't have to wait for the set to complete in
2989 * order to read the value, since the local cache is updated synchronously.
2990 */
2991lib.Storage.Local.prototype.removeItems = function(ary, opt_callback) {
2992 for (var i = 0; i < ary.length; i++) {
2993 this.storage_.removeItem(ary[i]);
2994 }
2995
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002996 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002997};
2998// SOURCE FILE: libdot/js/lib_storage_memory.js
2999// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3000// Use of this source code is governed by a BSD-style license that can be
3001// found in the LICENSE file.
3002
3003'use strict';
3004
3005/**
3006 * In-memory storage class with an async interface that is interchangeable with
3007 * other lib.Storage.* implementations.
3008 */
3009lib.Storage.Memory = function() {
3010 this.observers_ = [];
3011 this.storage_ = {};
3012};
3013
3014/**
3015 * Register a function to observe storage changes.
3016 *
3017 * @param {function(map)} callback The function to invoke when the storage
3018 * changes.
3019 */
3020lib.Storage.Memory.prototype.addObserver = function(callback) {
3021 this.observers_.push(callback);
3022};
3023
3024/**
3025 * Unregister a change observer.
3026 *
3027 * @param {function} observer A previously registered callback.
3028 */
3029lib.Storage.Memory.prototype.removeObserver = function(callback) {
3030 var i = this.observers_.indexOf(callback);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003031 if (i != -1) this.observers_.splice(i, 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003032};
3033
3034/**
3035 * Delete everything in this storage.
3036 *
3037 * @param {function(map)} callback The function to invoke when the delete
3038 * has completed.
3039 */
3040lib.Storage.Memory.prototype.clear = function(opt_callback) {
3041 var e = {};
3042 for (var key in this.storage_) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003043 e[key] = {oldValue: this.storage_[key], newValue: (void 0)};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003044 }
3045
3046 this.storage_ = {};
3047
3048 setTimeout(function() {
3049 for (var i = 0; i < this.observers_.length; i++) {
3050 this.observers_[i](e);
3051 }
3052 }.bind(this), 0);
3053
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003054 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003055};
3056
3057/**
3058 * Return the current value of a storage item.
3059 *
3060 * @param {string} key The key to look up.
3061 * @param {function(value) callback The function to invoke when the value has
3062 * been retrieved.
3063 */
3064lib.Storage.Memory.prototype.getItem = function(key, callback) {
3065 var value = this.storage_[key];
3066
3067 if (typeof value == 'string') {
3068 try {
3069 value = JSON.parse(value);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003070 } catch (e) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003071 // If we can't parse the value, just return it unparsed.
3072 }
3073 }
3074
3075 setTimeout(callback.bind(null, value), 0);
3076};
3077
3078/**
3079 * Fetch the values of multiple storage items.
3080 *
3081 * @param {Array} keys The keys to look up.
3082 * @param {function(map) callback The function to invoke when the values have
3083 * been retrieved.
3084 */
3085lib.Storage.Memory.prototype.getItems = function(keys, callback) {
3086 var rv = {};
3087
3088 for (var i = keys.length - 1; i >= 0; i--) {
3089 var key = keys[i];
3090 var value = this.storage_[key];
3091 if (typeof value == 'string') {
3092 try {
3093 rv[key] = JSON.parse(value);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003094 } catch (e) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003095 // If we can't parse the value, just return it unparsed.
3096 rv[key] = value;
3097 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003098 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003099 keys.splice(i, 1);
3100 }
3101 }
3102
3103 setTimeout(callback.bind(null, rv), 0);
3104};
3105
3106/**
3107 * Set a value in storage.
3108 *
3109 * @param {string} key The key for the value to be stored.
3110 * @param {*} value The value to be stored. Anything that can be serialized
3111 * with JSON is acceptable.
3112 * @param {function()} opt_callback Optional function to invoke when the
3113 * set is complete. You don't have to wait for the set to complete in order
3114 * to read the value, since the local cache is updated synchronously.
3115 */
3116lib.Storage.Memory.prototype.setItem = function(key, value, opt_callback) {
3117 var oldValue = this.storage_[key];
3118 this.storage_[key] = JSON.stringify(value);
3119
3120 var e = {};
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003121 e[key] = {oldValue: oldValue, newValue: value};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003122
3123 setTimeout(function() {
3124 for (var i = 0; i < this.observers_.length; i++) {
3125 this.observers_[i](e);
3126 }
3127 }.bind(this), 0);
3128
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003129 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003130};
3131
3132/**
3133 * Set multiple values in storage.
3134 *
3135 * @param {Object} map A map of key/values to set in storage.
3136 * @param {function()} opt_callback Optional function to invoke when the
3137 * set is complete. You don't have to wait for the set to complete in order
3138 * to read the value, since the local cache is updated synchronously.
3139 */
3140lib.Storage.Memory.prototype.setItems = function(obj, opt_callback) {
3141 var e = {};
3142
3143 for (var key in obj) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003144 e[key] = {oldValue: this.storage_[key], newValue: obj[key]};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003145 this.storage_[key] = JSON.stringify(obj[key]);
3146 }
3147
3148 setTimeout(function() {
3149 for (var i = 0; i < this.observers_.length; i++) {
3150 this.observers_[i](e);
3151 }
3152 }.bind(this));
3153
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003154 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003155};
3156
3157/**
3158 * Remove an item from storage.
3159 *
3160 * @param {string} key The key to be removed.
3161 * @param {function()} opt_callback Optional function to invoke when the
3162 * remove is complete. You don't have to wait for the set to complete in
3163 * order to read the value, since the local cache is updated synchronously.
3164 */
3165lib.Storage.Memory.prototype.removeItem = function(key, opt_callback) {
3166 delete this.storage_[key];
3167
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003168 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003169};
3170
3171/**
3172 * Remove multiple items from storage.
3173 *
3174 * @param {Array} keys The keys to be removed.
3175 * @param {function()} opt_callback Optional function to invoke when the
3176 * remove is complete. You don't have to wait for the set to complete in
3177 * order to read the value, since the local cache is updated synchronously.
3178 */
3179lib.Storage.Memory.prototype.removeItems = function(ary, opt_callback) {
3180 for (var i = 0; i < ary.length; i++) {
3181 delete this.storage_[ary[i]];
3182 }
3183
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003184 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003185};
3186// SOURCE FILE: libdot/js/lib_test_manager.js
3187// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3188// Use of this source code is governed by a BSD-style license that can be
3189// found in the LICENSE file.
3190
3191'use strict';
3192
3193/**
3194 * @fileoverview JavaScript unit testing framework for synchronous and
3195 * asynchronous tests.
3196 *
3197 * This file contains the lib.TestManager and related classes. At the moment
3198 * it's all collected in a single file since it's reasonably small
3199 * (=~1k lines), and it's a lot easier to include one file into your test
3200 * harness than it is to include seven.
3201 *
3202 * The following classes are defined...
3203 *
3204 * lib.TestManager - The root class and entrypoint for creating test runs.
3205 * lib.TestManager.Log - Logging service.
3206 * lib.TestManager.Suite - A collection of tests.
3207 * lib.TestManager.Test - A single test.
3208 * lib.TestManager.TestRun - Manages the execution of a set of tests.
3209 * lib.TestManager.Result - A single test result.
3210 */
3211
3212/**
3213 * Root object in the unit test hierarchy, and keeper of the log object.
3214 *
3215 * @param {lib.TestManager.Log} opt_log Optional lib.TestManager.Log object.
3216 * Logs to the JavaScript console if omitted.
3217 */
3218lib.TestManager = function(opt_log) {
3219 this.log = opt_log || new lib.TestManager.Log();
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07003220};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003221
3222/**
3223 * Create a new test run object for this test manager.
3224 *
3225 * @param {Object} opt_cx An object to be passed to test suite setup(),
3226 * preamble(), and test cases during this test run. This object is opaque
3227 * to lib.TestManager.* code. It's entirely up to the test suite what it's
3228 * used for.
3229 */
3230lib.TestManager.prototype.createTestRun = function(opt_cx) {
3231 return new lib.TestManager.TestRun(this, opt_cx);
3232};
3233
3234/**
3235 * Called when a test run associated with this test manager completes.
3236 *
3237 * Clients may override this to call an appropriate function.
3238 */
3239lib.TestManager.prototype.onTestRunComplete = function(testRun) {};
3240
3241/**
3242 * Called before a test associated with this test manager is run.
3243 *
3244 * @param {lib.TestManager.Result} result The result object for the upcoming
3245 * test.
3246 * @param {Object} cx The context object for a test run.
3247 */
3248lib.TestManager.prototype.testPreamble = function(result, cx) {};
3249
3250/**
3251 * Called after a test associated with this test manager finishes.
3252 *
3253 * @param {lib.TestManager.Result} result The result object for the finished
3254 * test.
3255 * @param {Object} cx The context object for a test run.
3256 */
3257lib.TestManager.prototype.testPostamble = function(result, cx) {};
3258
3259/**
3260 * Destination for test case output.
3261 *
3262 * @param {function(string)} opt_logFunction Optional function to call to
3263 * write a string to the log. If omitted, console.log is used.
3264 */
3265lib.TestManager.Log = function(opt_logFunction) {
3266 this.save = false;
3267 this.data = '';
3268 this.logFunction_ = opt_logFunction || function(s) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003269 if (this.save) this.data += s + '\n';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003270 console.log(s);
3271 };
3272 this.pending_ = '';
3273 this.prefix_ = '';
3274 this.prefixStack_ = [];
3275};
3276
3277/**
3278 * Add a prefix to log messages.
3279 *
3280 * This only affects log messages that are added after the prefix is pushed.
3281 *
3282 * @param {string} str The prefix to prepend to future log messages.
3283 */
3284lib.TestManager.Log.prototype.pushPrefix = function(str) {
3285 this.prefixStack_.push(str);
3286 this.prefix_ = this.prefixStack_.join('');
3287};
3288
3289/**
3290 * Remove the most recently added message prefix.
3291 */
3292lib.TestManager.Log.prototype.popPrefix = function() {
3293 this.prefixStack_.pop();
3294 this.prefix_ = this.prefixStack_.join('');
3295};
3296
3297/**
3298 * Queue up a string to print to the log.
3299 *
3300 * If a line is already pending, this string is added to it.
3301 *
3302 * The string is not actually printed to the log until flush() or println()
3303 * is called. The following call sequence will result in TWO lines in the
3304 * log...
3305 *
3306 * log.print('hello');
3307 * log.print(' ');
3308 * log.println('world');
3309 *
3310 * While a typical stream-like thing would result in 'hello world\n', this one
3311 * results in 'hello \nworld\n'.
3312 *
3313 * @param {string} str The string to add to the log.
3314 */
3315lib.TestManager.Log.prototype.print = function(str) {
3316 if (this.pending_) {
3317 this.pending_ += str;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003318 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003319 this.pending_ = this.prefix_ + str;
3320 }
3321};
3322
3323/**
3324 * Print a line to the log and flush it immediately.
3325 *
3326 * @param {string} str The string to add to the log.
3327 */
3328lib.TestManager.Log.prototype.println = function(str) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003329 if (this.pending_) this.flush();
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003330
3331 this.logFunction_(this.prefix_ + str);
3332};
3333
3334/**
3335 * Flush any pending log message.
3336 */
3337lib.TestManager.Log.prototype.flush = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003338 if (!this.pending_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003339
3340 this.logFunction_(this.pending_);
3341 this.pending_ = '';
3342};
3343
3344/**
3345 * Returns a new constructor function that will inherit from
3346 * lib.TestManager.Suite.
3347 *
3348 * Use this function to create a new test suite subclass. It will return a
3349 * properly initialized constructor function for the subclass. You can then
3350 * override the setup() and preamble() methods if necessary and add test cases
3351 * to the subclass.
3352 *
3353 * var MyTests = new lib.TestManager.Suite('MyTests');
3354 *
3355 * MyTests.prototype.setup = function(cx) {
3356 * // Sets this.size to cx.size if it exists, or the default value of 10
3357 * // if not.
3358 * this.setDefault(cx, {size: 10});
3359 * };
3360 *
3361 * MyTests.prototype.preamble = function(result, cx) {
3362 * // Some tests (even successful ones) may side-effect this list, so
3363 * // recreate it before every test.
3364 * this.list = [];
3365 * for (var i = 0; i < this.size; i++) {
3366 * this.list[i] = i;
3367 * }
3368 * };
3369 *
3370 * // Basic synchronous test case.
3371 * MyTests.addTest('pop-length', function(result, cx) {
3372 * this.list.pop();
3373 *
3374 * // If this assertion fails, the testcase will stop here.
3375 * result.assertEQ(this.list.length, this.size - 1);
3376 *
3377 * // A test must indicate it has passed by calling this method.
3378 * result.pass();
3379 * });
3380 *
3381 * // Sample asynchronous test case.
3382 * MyTests.addTest('async-pop-length', function(result, cx) {
3383 * var self = this;
3384 *
3385 * var callback = function() {
3386 * result.assertEQ(self.list.length, self.size - 1);
3387 * result.pass();
3388 * };
3389 *
3390 * // Wait 100ms to check the array length for the sake of this example.
3391 * setTimeout(callback, 100);
3392 *
3393 * this.list.pop();
3394 *
3395 * // Indicate that this test needs another 200ms to complete.
3396 * // If the test does not report pass/fail by then, it is considered to
3397 * // have timed out.
3398 * result.requestTime(200);
3399 * });
3400 *
3401 * ...
3402 *
3403 * @param {string} suiteName The name of the test suite.
3404 */
3405lib.TestManager.Suite = function(suiteName) {
3406 function ctor(testManager, cx) {
3407 this.testManager_ = testManager;
3408 this.suiteName = suiteName;
3409
3410 this.setup(cx);
3411 }
3412
3413 ctor.suiteName = suiteName;
3414 ctor.addTest = lib.TestManager.Suite.addTest;
3415 ctor.disableTest = lib.TestManager.Suite.disableTest;
3416 ctor.getTest = lib.TestManager.Suite.getTest;
3417 ctor.getTestList = lib.TestManager.Suite.getTestList;
3418 ctor.testList_ = [];
3419 ctor.testMap_ = {};
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003420 ctor.prototype = {__proto__: lib.TestManager.Suite.prototype};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003421
3422 lib.TestManager.Suite.subclasses.push(ctor);
3423
3424 return ctor;
3425};
3426
3427/**
3428 * List of lib.TestManager.Suite subclasses, in the order they were defined.
3429 */
3430lib.TestManager.Suite.subclasses = [];
3431
3432/**
3433 * Add a test to a lib.TestManager.Suite.
3434 *
3435 * This method is copied to new subclasses when they are created.
3436 */
3437lib.TestManager.Suite.addTest = function(testName, testFunction) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003438 if (testName in this.testMap_) throw 'Duplicate test name: ' + testName;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003439
3440 var test = new lib.TestManager.Test(this, testName, testFunction);
3441 this.testMap_[testName] = test;
3442 this.testList_.push(test);
3443};
3444
3445/**
3446 * Defines a disabled test.
3447 */
3448lib.TestManager.Suite.disableTest = function(testName, testFunction) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003449 if (testName in this.testMap_) throw 'Duplicate test name: ' + testName;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003450
3451 var test = new lib.TestManager.Test(this, testName, testFunction);
3452 console.log('Disabled test: ' + test.fullName);
3453};
3454
3455/**
3456 * Get a lib.TestManager.Test instance by name.
3457 *
3458 * This method is copied to new subclasses when they are created.
3459 *
3460 * @param {string} testName The name of the desired test.
3461 * @return {lib.TestManager.Test} The requested test, or undefined if it was not
3462 * found.
3463 */
3464lib.TestManager.Suite.getTest = function(testName) {
3465 return this.testMap_[testName];
3466};
3467
3468/**
3469 * Get an array of lib.TestManager.Tests associated with this Suite.
3470 *
3471 * This method is copied to new subclasses when they are created.
3472 */
3473lib.TestManager.Suite.getTestList = function() {
3474 return this.testList_;
3475};
3476
3477/**
3478 * Set properties on a test suite instance, pulling the property value from
3479 * the context if it exists and from the defaults dictionary if not.
3480 *
3481 * This is intended to be used in your test suite's setup() method to
3482 * define parameters for the test suite which may be overridden through the
3483 * context object. For example...
3484 *
3485 * MySuite.prototype.setup = function(cx) {
3486 * this.setDefaults(cx, {size: 10});
3487 * };
3488 *
3489 * If the context object has a 'size' property then this.size will be set to
3490 * the value of cx.size, otherwise this.size will get a default value of 10.
3491 *
3492 * @param {Object} cx The context object for a test run.
3493 * @param {Object} defaults An object containing name/value pairs to set on
3494 * this test suite instance. The value listed here will be used if the
3495 * name is not defined on the context object.
3496 */
3497lib.TestManager.Suite.prototype.setDefaults = function(cx, defaults) {
3498 for (var k in defaults) {
3499 this[k] = (k in cx) ? cx[k] : defaults[k];
3500 }
3501};
3502
3503/**
3504 * Subclassable method called to set up the test suite.
3505 *
3506 * The default implementation of this method is a no-op. If your test suite
3507 * requires some kind of suite-wide setup, this is the place to do it.
3508 *
3509 * It's fine to store state on the test suite instance, that state will be
3510 * accessible to all tests in the suite. If any test case fails, the entire
3511 * test suite object will be discarded and a new one will be created for
3512 * the remaining tests.
3513 *
3514 * Any side effects outside of this test suite instance must be idempotent.
3515 * For example, if you're adding DOM nodes to a document, make sure to first
3516 * test that they're not already there. If they are, remove them rather than
3517 * reuse them. You should not count on their state, since they were probably
3518 * left behind by a failed testcase.
3519 *
3520 * Any exception here will abort the remainder of the test run.
3521 *
3522 * @param {Object} cx The context object for a test run.
3523 */
3524lib.TestManager.Suite.prototype.setup = function(cx) {};
3525
3526/**
3527 * Subclassable method called to do pre-test set up.
3528 *
3529 * The default implementation of this method is a no-op. If your test suite
3530 * requires some kind of pre-test setup, this is the place to do it.
3531 *
3532 * This can be used to avoid a bunch of boilerplate setup/teardown code in
3533 * this suite's testcases.
3534 *
3535 * Any exception here will abort the remainder of the test run.
3536 *
3537 * @param {lib.TestManager.Result} result The result object for the upcoming
3538 * test.
3539 * @param {Object} cx The context object for a test run.
3540 */
3541lib.TestManager.Suite.prototype.preamble = function(result, cx) {};
3542
3543/**
3544 * Subclassable method called to do post-test tear-down.
3545 *
3546 * The default implementation of this method is a no-op. If your test suite
3547 * requires some kind of pre-test setup, this is the place to do it.
3548 *
3549 * This can be used to avoid a bunch of boilerplate setup/teardown code in
3550 * this suite's testcases.
3551 *
3552 * Any exception here will abort the remainder of the test run.
3553 *
3554 * @param {lib.TestManager.Result} result The result object for the finished
3555 * test.
3556 * @param {Object} cx The context object for a test run.
3557 */
3558lib.TestManager.Suite.prototype.postamble = function(result, cx) {};
3559
3560/**
3561 * Object representing a single test in a test suite.
3562 *
3563 * These are created as part of the lib.TestManager.Suite.addTest() method.
3564 * You should never have to construct one by hand.
3565 *
3566 * @param {lib.TestManager.Suite} suiteClass The test suite class containing
3567 * this test.
3568 * @param {string} testName The local name of this test case, not including the
3569 * test suite name.
3570 * @param {function(lib.TestManager.Result, Object)} testFunction The function
3571 * to invoke for this test case. This is passed a Result instance and the
3572 * context object associated with the test run.
3573 *
3574 */
3575lib.TestManager.Test = function(suiteClass, testName, testFunction) {
3576 /**
3577 * The test suite class containing this function.
3578 */
3579 this.suiteClass = suiteClass;
3580
3581 /**
3582 * The local name of this test, not including the test suite name.
3583 */
3584 this.testName = testName;
3585
3586 /**
3587 * The global name of this test, including the test suite name.
3588 */
3589 this.fullName = suiteClass.suiteName + '[' + testName + ']';
3590
3591 // The function to call for this test.
3592 this.testFunction_ = testFunction;
3593};
3594
3595/**
3596 * Execute this test.
3597 *
3598 * This is called by a lib.TestManager.Result instance, as part of a
3599 * lib.TestManager.TestRun. You should not call it by hand.
3600 *
3601 * @param {lib.TestManager.Result} result The result object for the test.
3602 */
3603lib.TestManager.Test.prototype.run = function(result) {
3604 try {
3605 // Tests are applied to the parent lib.TestManager.Suite subclass.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07003606 this.testFunction_.apply(result.suite, [result, result.testRun.cx]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003607 } catch (ex) {
3608 if (ex instanceof lib.TestManager.Result.TestComplete) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003609
3610 result.println('Test raised an exception: ' + ex);
3611
3612 if (ex.stack) {
3613 if (ex.stack instanceof Array) {
3614 result.println(ex.stack.join('\n'));
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003615 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003616 result.println(ex.stack);
3617 }
3618 }
3619
3620 result.completeTest_(result.FAILED, false);
3621 }
3622};
3623
3624/**
3625 * Used to choose a set of tests and run them.
3626 *
3627 * It's slightly more convenient to construct one of these from
3628 * lib.TestManager.prototype.createTestRun().
3629 *
3630 * @param {lib.TestManager} testManager The testManager associated with this
3631 * TestRun.
3632 * @param {Object} cx A context to be passed into the tests. This can be used
3633 * to set parameters for the test suite or individual test cases.
3634 */
3635lib.TestManager.TestRun = function(testManager, cx) {
3636 /**
3637 * The associated lib.TestManager instance.
3638 */
3639 this.testManager = testManager;
3640
3641 /**
3642 * Shortcut to the lib.TestManager's log.
3643 */
3644 this.log = testManager.log;
3645
3646 /**
3647 * The test run context. It's entirely up to the test suite and test cases
3648 * how this is used. It is opaque to lib.TestManager.* classes.
3649 */
3650 this.cx = cx || {};
3651
3652 /**
3653 * The list of test cases that encountered failures.
3654 */
3655 this.failures = [];
3656
3657 /**
3658 * The list of test cases that passed.
3659 */
3660 this.passes = [];
3661
3662 /**
3663 * The time the test run started, or null if it hasn't been started yet.
3664 */
3665 this.startDate = null;
3666
3667 /**
3668 * The time in milliseconds that the test run took to complete, or null if
3669 * it hasn't completed yet.
3670 */
3671 this.duration = null;
3672
3673 /**
3674 * The most recent result object, or null if the test run hasn't started
3675 * yet. In order to detect late failures, this is not cleared when the test
3676 * completes.
3677 */
3678 this.currentResult = null;
3679
3680 /**
3681 * Number of maximum failures. The test run will stop when this number is
3682 * reached. If 0 or omitted, the entire set of selected tests is run, even
3683 * if some fail.
3684 */
3685 this.maxFailures = 0;
3686
3687 /**
3688 * True if this test run ended early because of an unexpected condition.
3689 */
3690 this.panic = false;
3691
3692 // List of pending test cases.
3693 this.testQueue_ = [];
3694
3695};
3696
3697/**
3698 * This value can be passed to select() to indicate that all tests should
3699 * be selected.
3700 */
3701lib.TestManager.TestRun.prototype.ALL_TESTS = new String('<all-tests>');
3702
3703/**
3704 * Add a single test to the test run.
3705 */
3706lib.TestManager.TestRun.prototype.selectTest = function(test) {
3707 this.testQueue_.push(test);
3708};
3709
3710lib.TestManager.TestRun.prototype.selectSuite = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003711 suiteClass, opt_pattern) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003712 var pattern = opt_pattern || this.ALL_TESTS;
3713 var selectCount = 0;
3714 var testList = suiteClass.getTestList();
3715
3716 for (var j = 0; j < testList.length; j++) {
3717 var test = testList[j];
3718 // Note that we're using "!==" rather than "!=" so that we're matching
3719 // the ALL_TESTS String object, rather than the contents of the string.
3720 if (pattern !== this.ALL_TESTS) {
3721 if (pattern instanceof RegExp) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003722 if (!pattern.test(test.testName)) continue;
3723 } else if (test.testName != pattern) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003724 continue;
3725 }
3726 }
3727
3728 this.selectTest(test);
3729 selectCount++;
3730 }
3731
3732 return selectCount;
3733};
3734
3735/**
3736 * Selects one or more tests to gather results for.
3737 *
3738 * Selecting the same test more than once is allowed.
3739 *
3740 * @param {string|RegExp} pattern Pattern used to select tests.
3741 * If TestRun.prototype.ALL_TESTS, all tests are selected.
3742 * If a string, only the test that exactly matches is selected.
3743 * If a RegExp, only tests matching the RegExp are added.
3744 *
3745 * @return {int} The number of additional tests that have been selected into
3746 * this TestRun.
3747 */
3748lib.TestManager.TestRun.prototype.selectPattern = function(pattern) {
3749 var selectCount = 0;
3750
3751 for (var i = 0; i < lib.TestManager.Suite.subclasses.length; i++) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003752 selectCount +=
3753 this.selectSuite(lib.TestManager.Suite.subclasses[i], pattern);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003754 }
3755
3756 if (!selectCount) {
3757 this.log.println('No tests matched selection criteria: ' + pattern);
3758 }
3759
3760 return selectCount;
3761};
3762
3763/**
3764 * Hooked up to window.onerror during a test run in order to catch exceptions
3765 * that would otherwise go uncaught.
3766 */
3767lib.TestManager.TestRun.prototype.onUncaughtException_ = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003768 message, file, line) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003769
3770 if (message.indexOf('Uncaught lib.TestManager.Result.TestComplete') == 0 ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003771 message.indexOf('status: passed') != -1) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003772 // This is a result.pass() or result.fail() call from a callback. We're
3773 // already going to deal with it as part of the completeTest_() call
3774 // that raised it. We can safely squelch this error message.
3775 return true;
3776 }
3777
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003778 if (!this.currentResult) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003779
3780 if (message == 'Uncaught ' + this.currentResult.expectedErrorMessage_) {
3781 // Test cases may need to raise an unhandled exception as part of the test.
3782 return;
3783 }
3784
3785 var when = 'during';
3786
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003787 if (this.currentResult.status != this.currentResult.PENDING) when = 'after';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003788
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003789 this.log.println(
3790 'Uncaught exception ' + when +
3791 ' test case: ' + this.currentResult.test.fullName);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003792 this.log.println(message + ', ' + file + ':' + line);
3793
3794 this.currentResult.completeTest_(this.currentResult.FAILED, false);
3795
3796 return false;
3797};
3798
3799/**
3800 * Called to when this test run has completed.
3801 *
3802 * This method typically re-runs itself asynchronously, in order to let the
3803 * DOM stabilize and short-term timeouts to complete before declaring the
3804 * test run complete.
3805 *
3806 * @param {boolean} opt_skipTimeout If true, the timeout is skipped and the
3807 * test run is completed immediately. This should only be used from within
3808 * this function.
3809 */
3810lib.TestManager.TestRun.prototype.onTestRunComplete_ = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003811 opt_skipTimeout) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003812 if (!opt_skipTimeout) {
3813 // The final test may have left a lingering setTimeout(..., 0), or maybe
3814 // poked at the DOM in a way that will trigger a event to fire at the end
3815 // of this stack, so we give things a chance to settle down before our
3816 // final cleanup...
3817 setTimeout(this.onTestRunComplete_.bind(this), 0, true);
3818 return;
3819 }
3820
3821 this.duration = (new Date()) - this.startDate;
3822
3823 this.log.popPrefix();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003824 this.log.println(
3825 '} ' + this.passes.length + ' passed, ' + this.failures.length +
3826 ' failed, ' + this.msToSeconds_(this.duration));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003827 this.log.println('');
3828
3829 this.summarize();
3830
3831 window.onerror = null;
3832
3833 this.testManager.onTestRunComplete(this);
3834};
3835
3836/**
3837 * Called by the lib.TestManager.Result object when a test completes.
3838 *
3839 * @param {lib.TestManager.Result} result The result object which has just
3840 * completed.
3841 */
3842lib.TestManager.TestRun.prototype.onResultComplete = function(result) {
3843 try {
3844 this.testManager.testPostamble(result, this.cx);
3845 result.suite.postamble(result, this.ctx);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003846 } catch (ex) {
3847 this.log.println(
3848 'Unexpected exception in postamble: ' + (ex.stack ? ex.stack : ex));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003849 this.panic = true;
3850 }
3851
3852 this.log.popPrefix();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003853 this.log.print(
3854 '} ' + result.status + ', ' + this.msToSeconds_(result.duration));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003855 this.log.flush();
3856
3857 if (result.status == result.FAILED) {
3858 this.failures.push(result);
3859 this.currentSuite = null;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003860 } else if (result.status == result.PASSED) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003861 this.passes.push(result);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003862 } else {
3863 this.log.println(
3864 'Unknown result status: ' + result.test.fullName + ': ' +
3865 result.status);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003866 return this.panic = true;
3867 }
3868
3869 this.runNextTest_();
3870};
3871
3872/**
3873 * Called by the lib.TestManager.Result object when a test which has already
3874 * completed reports another completion.
3875 *
3876 * This is usually indicative of a buggy testcase. It is probably reporting a
3877 * result on exit and then again from an asynchronous callback.
3878 *
3879 * It may also be the case that the last act of the testcase causes a DOM change
3880 * which triggers some event to run after the test returns. If the event
3881 * handler reports a failure or raises an uncaught exception, the test will
3882 * fail even though it has already completed.
3883 *
3884 * In any case, re-completing a test ALWAYS moves it into the failure pile.
3885 *
3886 * @param {lib.TestManager.Result} result The result object which has just
3887 * completed.
3888 * @param {string} lateStatus The status that the test attempted to record this
3889 * time around.
3890 */
3891lib.TestManager.TestRun.prototype.onResultReComplete = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003892 result, lateStatus) {
3893 this.log.println(
3894 'Late complete for test: ' + result.test.fullName + ': ' + lateStatus);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003895
3896 // Consider any late completion a failure, even if it's a double-pass, since
3897 // it's a misuse of the testing API.
3898 var index = this.passes.indexOf(result);
3899 if (index >= 0) {
3900 this.passes.splice(index, 1);
3901 this.failures.push(result);
3902 }
3903};
3904
3905/**
3906 * Run the next test in the queue.
3907 */
3908lib.TestManager.TestRun.prototype.runNextTest_ = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003909 if (this.panic || !this.testQueue_.length) return this.onTestRunComplete_();
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003910
3911 if (this.maxFailures && this.failures.length >= this.maxFailures) {
3912 this.log.println('Maximum failure count reached, aborting test run.');
3913 return this.onTestRunComplete_();
3914 }
3915
3916 // Peek at the top test first. We remove it later just before it's about
3917 // to run, so that we don't disturb the incomplete test count in the
3918 // event that we fail before running it.
3919 var test = this.testQueue_[0];
3920 var suite = this.currentResult ? this.currentResult.suite : null;
3921
3922 try {
3923 if (!suite || !(suite instanceof test.suiteClass)) {
3924 this.log.println('Initializing suite: ' + test.suiteClass.suiteName);
3925 suite = new test.suiteClass(this.testManager, this.cx);
3926 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003927 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003928 // If test suite setup fails we're not even going to try to run the tests.
3929 this.log.println('Exception during setup: ' + (ex.stack ? ex.stack : ex));
3930 this.panic = true;
3931 this.onTestRunComplete_();
3932 return;
3933 }
3934
3935 try {
3936 this.log.print('Test: ' + test.fullName + ' {');
3937 this.log.pushPrefix(' ');
3938
3939 this.currentResult = new lib.TestManager.Result(this, suite, test);
3940 this.testManager.testPreamble(this.currentResult, this.cx);
3941 suite.preamble(this.currentResult, this.cx);
3942
3943 this.testQueue_.shift();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003944 } catch (ex) {
3945 this.log.println(
3946 'Unexpected exception during test preamble: ' +
3947 (ex.stack ? ex.stack : ex));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003948 this.log.popPrefix();
3949 this.log.println('}');
3950
3951 this.panic = true;
3952 this.onTestRunComplete_();
3953 return;
3954 }
3955
3956 try {
3957 this.currentResult.run();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003958 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003959 // Result.run() should catch test exceptions and turn them into failures.
3960 // If we got here, it means there is trouble in the testing framework.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003961 this.log.println(
3962 'Unexpected exception during test run: ' + (ex.stack ? ex.stack : ex));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003963 this.panic = true;
3964 }
3965};
3966
3967/**
3968 * Run the selected list of tests.
3969 *
3970 * Some tests may need to run asynchronously, so you cannot assume the run is
3971 * complete when this function returns. Instead, pass in a function to be
3972 * called back when the run has completed.
3973 *
3974 * This function will log the results of the test run as they happen into the
3975 * log defined by the associated lib.TestManager. By default this is
3976 * console.log, which can be viewed in the JavaScript console of most browsers.
3977 *
3978 * The browser state is determined by the last test to run. We intentionally
3979 * don't do any cleanup so that you can inspect the state of a failed test, or
3980 * leave the browser ready for manual testing.
3981 *
3982 * Any failures in lib.TestManager.* code or test suite setup or test case
3983 * preamble will cause the test run to abort.
3984 */
3985lib.TestManager.TestRun.prototype.run = function() {
3986 this.log.println('Running ' + this.testQueue_.length + ' test(s) {');
3987 this.log.pushPrefix(' ');
3988
3989 window.onerror = this.onUncaughtException_.bind(this);
3990 this.startDate = new Date();
3991 this.runNextTest_();
3992};
3993
3994/**
3995 * Format milliseconds as fractional seconds.
3996 */
3997lib.TestManager.TestRun.prototype.msToSeconds_ = function(ms) {
3998 var secs = (ms / 1000).toFixed(2);
3999 return secs + 's';
4000};
4001
4002/**
4003 * Log the current result summary.
4004 */
4005lib.TestManager.TestRun.prototype.summarize = function() {
4006 if (this.failures.length) {
4007 for (var i = 0; i < this.failures.length; i++) {
4008 this.log.println('FAILED: ' + this.failures[i].test.fullName);
4009 }
4010 }
4011
4012 if (this.testQueue_.length) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004013 this.log.println(
4014 'Test run incomplete: ' + this.testQueue_.length +
4015 ' test(s) were not run.');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004016 }
4017};
4018
4019/**
4020 * Record of the result of a single test.
4021 *
4022 * These are constructed during a test run, you shouldn't have to make one
4023 * on your own.
4024 *
4025 * An instance of this class is passed in to each test function. It can be
4026 * used to add messages to the test log, to record a test pass/fail state, to
4027 * test assertions, or to create exception-proof wrappers for callback
4028 * functions.
4029 *
4030 * @param {lib.TestManager.TestRun} testRun The TestRun instance associated with
4031 * this result.
4032 * @param {lib.TestManager.Suit} suite The Suite containing the test we're
4033 * collecting this result for.
4034 * @param {lib.TestManager.Test} test The test we're collecting this result for.
4035 */
4036lib.TestManager.Result = function(testRun, suite, test) {
4037 /**
4038 * The TestRun instance associated with this result.
4039 */
4040 this.testRun = testRun;
4041
4042 /**
4043 * The Suite containing the test we're collecting this result for.
4044 */
4045 this.suite = suite;
4046
4047 /**
4048 * The test we're collecting this result for.
4049 */
4050 this.test = test;
4051
4052 /**
4053 * The time we started to collect this result, or null if we haven't started.
4054 */
4055 this.startDate = null;
4056
4057 /**
4058 * The time in milliseconds that the test took to complete, or null if
4059 * it hasn't completed yet.
4060 */
4061 this.duration = null;
4062
4063 /**
4064 * The current status of this test result.
4065 */
4066 this.status = this.PENDING;
4067
4068 // An error message that the test case is expected to generate.
4069 this.expectedErrorMessage_ = null;
4070};
4071
4072/**
4073 * Possible values for this.status.
4074 */
4075lib.TestManager.Result.prototype.PENDING = 'pending';
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004076lib.TestManager.Result.prototype.FAILED = 'FAILED';
4077lib.TestManager.Result.prototype.PASSED = 'passed';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004078
4079/**
4080 * Exception thrown when a test completes (pass or fail), to ensure no more of
4081 * the test is run.
4082 */
4083lib.TestManager.Result.TestComplete = function(result) {
4084 this.result = result;
4085};
4086
4087lib.TestManager.Result.TestComplete.prototype.toString = function() {
4088 return 'lib.TestManager.Result.TestComplete: ' + this.result.test.fullName +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004089 ', status: ' + this.result.status;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004090};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004091
4092/**
4093 * Start the test associated with this result.
4094 */
4095lib.TestManager.Result.prototype.run = function() {
4096 var self = this;
4097
4098 this.startDate = new Date();
4099 this.test.run(this);
4100
4101 if (this.status == this.PENDING && !this.timeout_) {
4102 this.println('Test did not return a value and did not request more time.');
4103 this.completeTest_(this.FAILED, false);
4104 }
4105};
4106
4107/**
4108 * Unhandled error message this test expects to generate.
4109 *
4110 * This must be the exact string that would appear in the JavaScript console,
4111 * minus the 'Uncaught ' prefix.
4112 *
4113 * The test case does *not* automatically fail if the error message is not
4114 * encountered.
4115 */
4116lib.TestManager.Result.prototype.expectErrorMessage = function(str) {
4117 this.expectedErrorMessage_ = str;
4118};
4119
4120/**
4121 * Function called when a test times out.
4122 */
4123lib.TestManager.Result.prototype.onTimeout_ = function() {
4124 this.timeout_ = null;
4125
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004126 if (this.status != this.PENDING) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004127
4128 this.println('Test timed out.');
4129 this.completeTest_(this.FAILED, false);
4130};
4131
4132/**
4133 * Indicate that a test case needs more time to complete.
4134 *
4135 * Before a test case returns it must report a pass/fail result, or request more
4136 * time to do so.
4137 *
4138 * If a test does not report pass/fail before the time expires it will
4139 * be reported as a timeout failure. Any late pass/fails will be noted in the
4140 * test log, but will not affect the final result of the test.
4141 *
4142 * Test cases may call requestTime more than once. If you have a few layers
4143 * of asynchronous API to go through, you should call this once per layer with
4144 * an estimate of how long each callback will take to complete.
4145 *
4146 * @param {int} ms Number of milliseconds requested.
4147 */
4148lib.TestManager.Result.prototype.requestTime = function(ms) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004149 if (this.timeout_) clearTimeout(this.timeout_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004150
4151 this.timeout_ = setTimeout(this.onTimeout_.bind(this), ms);
4152};
4153
4154/**
4155 * Report the completion of a test.
4156 *
4157 * @param {string} status The status of the test case.
4158 * @param {boolean} opt_throw Optional boolean indicating whether or not
4159 * to throw the TestComplete exception.
4160 */
4161lib.TestManager.Result.prototype.completeTest_ = function(status, opt_throw) {
4162 if (this.status == this.PENDING) {
4163 this.duration = (new Date()) - this.startDate;
4164 this.status = status;
4165
4166 this.testRun.onResultComplete(this);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004167 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004168 this.testRun.onResultReComplete(this, status);
4169 }
4170
4171 if (arguments.length < 2 || opt_throw)
4172 throw new lib.TestManager.Result.TestComplete(this);
4173};
4174
4175/**
4176 * Check that two arrays are equal.
4177 */
4178lib.TestManager.Result.prototype.arrayEQ_ = function(actual, expected) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004179 if (!actual || !expected) return (!actual && !expected);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004180
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004181 if (actual.length != expected.length) return false;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004182
4183 for (var i = 0; i < actual.length; ++i)
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004184 if (actual[i] != expected[i]) return false;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004185
4186 return true;
4187};
4188
4189/**
4190 * Assert that an actual value is exactly equal to the expected value.
4191 *
4192 * This uses the JavaScript '===' operator in order to avoid type coercion.
4193 *
4194 * If the assertion fails, the test is marked as a failure and a TestCompleted
4195 * exception is thrown.
4196 *
4197 * @param {*} actual The actual measured value.
4198 * @param {*} expected The value expected.
4199 * @param {string} opt_name An optional name used to identify this
4200 * assertion in the test log. If omitted it will be the file:line
4201 * of the caller.
4202 */
4203lib.TestManager.Result.prototype.assertEQ = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004204 actual, expected, opt_name) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004205 // Utility function to pretty up the log.
4206 function format(value) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004207 if (typeof value == 'number') return value;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004208
4209 var str = String(value);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004210 var ary = str.split('\n').map(function(e) {
4211 return JSON.stringify(e);
4212 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004213 if (ary.length > 1) {
4214 // If the string has newlines, start it off on its own line so that
4215 // it's easier to compare against another string with newlines.
4216 return '\n' + ary.join('\n');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004217 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004218 return ary.join('\n');
4219 }
4220 }
4221
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004222 if (actual === expected) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004223
4224 // Deal with common object types since JavaScript can't.
4225 if (expected instanceof Array)
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004226 if (this.arrayEQ_(actual, expected)) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004227
4228 var name = opt_name ? '[' + opt_name + ']' : '';
4229
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004230 this.fail(
4231 'assertEQ' + name + ': ' + this.getCallerLocation_(1) + ': ' +
4232 format(actual) + ' !== ' + format(expected));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004233};
4234
4235/**
4236 * Assert that a value is true.
4237 *
4238 * This uses the JavaScript '===' operator in order to avoid type coercion.
4239 * The must be the boolean value `true`, not just some "truish" value.
4240 *
4241 * If the assertion fails, the test is marked as a failure and a TestCompleted
4242 * exception is thrown.
4243 *
4244 * @param {boolean} actual The actual measured value.
4245 * @param {string} opt_name An optional name used to identify this
4246 * assertion in the test log. If omitted it will be the file:line
4247 * of the caller.
4248 */
4249lib.TestManager.Result.prototype.assert = function(actual, opt_name) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004250 if (actual === true) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004251
4252 var name = opt_name ? '[' + opt_name + ']' : '';
4253
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004254 this.fail(
4255 'assert' + name + ': ' + this.getCallerLocation_(1) + ': ' +
4256 String(actual));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004257};
4258
4259/**
4260 * Return the filename:line of a calling stack frame.
4261 *
4262 * This uses a dirty hack. It throws an exception, catches it, and examines
4263 * the stack property of the caught exception.
4264 *
4265 * @param {int} frameIndex The stack frame to return. 0 is the frame that
4266 * called this method, 1 is its caller, and so on.
4267 * @return {string} A string of the format "filename:linenumber".
4268 */
4269lib.TestManager.Result.prototype.getCallerLocation_ = function(frameIndex) {
4270 try {
4271 throw new Error();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004272 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004273 var frame = ex.stack.split('\n')[frameIndex + 2];
4274 var ary = frame.match(/([^/]+:\d+):\d+\)?$/);
4275 return ary ? ary[1] : '???';
4276 }
4277};
4278
4279/**
4280 * Write a message to the result log.
4281 */
4282lib.TestManager.Result.prototype.println = function(message) {
4283 this.testRun.log.println(message);
4284};
4285
4286/**
4287 * Mark a failed test and exit out of the rest of the test.
4288 *
4289 * This will throw a TestCompleted exception, causing the current test to stop.
4290 *
4291 * @param {string} opt_message Optional message to add to the log.
4292 */
4293lib.TestManager.Result.prototype.fail = function(opt_message) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004294 if (arguments.length) this.println(opt_message);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004295
4296 this.completeTest_(this.FAILED, true);
4297};
4298
4299/**
4300 * Mark a passed test and exit out of the rest of the test.
4301 *
4302 * This will throw a TestCompleted exception, causing the current test to stop.
4303 */
4304lib.TestManager.Result.prototype.pass = function() {
4305 this.completeTest_(this.PASSED, true);
4306};
4307// SOURCE FILE: libdot/js/lib_utf8.js
4308// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4309// Use of this source code is governed by a BSD-style license that can be
4310// found in the LICENSE file.
4311
4312'use strict';
4313
4314// TODO(davidben): When the string encoding API is implemented,
4315// replace this with the native in-browser implementation.
4316//
4317// https://wiki.whatwg.org/wiki/StringEncoding
4318// https://encoding.spec.whatwg.org/
4319
4320/**
4321 * A stateful UTF-8 decoder.
4322 */
4323lib.UTF8Decoder = function() {
4324 // The number of bytes left in the current sequence.
4325 this.bytesLeft = 0;
4326 // The in-progress code point being decoded, if bytesLeft > 0.
4327 this.codePoint = 0;
4328 // The lower bound on the final code point, if bytesLeft > 0.
4329 this.lowerBound = 0;
4330};
4331
4332/**
4333 * Decodes a some UTF-8 data, taking into account state from previous
4334 * data streamed through the encoder.
4335 *
4336 * @param {String} str data to decode, represented as a JavaScript
4337 * String with each code unit representing a byte between 0x00 to
4338 * 0xFF.
4339 * @return {String} The data decoded into a JavaScript UTF-16 string.
4340 */
4341lib.UTF8Decoder.prototype.decode = function(str) {
4342 var ret = '';
4343 for (var i = 0; i < str.length; i++) {
4344 var c = str.charCodeAt(i);
4345 if (this.bytesLeft == 0) {
4346 if (c <= 0x7F) {
4347 ret += str.charAt(i);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004348 } else if (0xC0 <= c && c <= 0xDF) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004349 this.codePoint = c - 0xC0;
4350 this.bytesLeft = 1;
4351 this.lowerBound = 0x80;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004352 } else if (0xE0 <= c && c <= 0xEF) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004353 this.codePoint = c - 0xE0;
4354 this.bytesLeft = 2;
4355 this.lowerBound = 0x800;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004356 } else if (0xF0 <= c && c <= 0xF7) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004357 this.codePoint = c - 0xF0;
4358 this.bytesLeft = 3;
4359 this.lowerBound = 0x10000;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004360 } else if (0xF8 <= c && c <= 0xFB) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004361 this.codePoint = c - 0xF8;
4362 this.bytesLeft = 4;
4363 this.lowerBound = 0x200000;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004364 } else if (0xFC <= c && c <= 0xFD) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004365 this.codePoint = c - 0xFC;
4366 this.bytesLeft = 5;
4367 this.lowerBound = 0x4000000;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004368 } else {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004369 ret += '�';
4370 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004371 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004372 if (0x80 <= c && c <= 0xBF) {
4373 this.bytesLeft--;
4374 this.codePoint = (this.codePoint << 6) + (c - 0x80);
4375 if (this.bytesLeft == 0) {
4376 // Got a full sequence. Check if it's within bounds and
4377 // filter out surrogate pairs.
4378 var codePoint = this.codePoint;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004379 if (codePoint < this.lowerBound ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004380 (0xD800 <= codePoint && codePoint <= 0xDFFF) ||
4381 codePoint > 0x10FFFF) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004382 ret += '�';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004383 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004384 // Encode as UTF-16 in the output.
4385 if (codePoint < 0x10000) {
4386 ret += String.fromCharCode(codePoint);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004387 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004388 // Surrogate pair.
4389 codePoint -= 0x10000;
4390 ret += String.fromCharCode(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004391 0xD800 + ((codePoint >>> 10) & 0x3FF),
4392 0xDC00 + (codePoint & 0x3FF));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004393 }
4394 }
4395 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004396 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004397 // Too few bytes in multi-byte sequence. Rewind stream so we
4398 // don't lose the next byte.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004399 ret += '�';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004400 this.bytesLeft = 0;
4401 i--;
4402 }
4403 }
4404 }
4405 return ret;
4406};
4407
4408/**
4409 * Decodes UTF-8 data. This is a convenience function for when all the
4410 * data is already known.
4411 *
4412 * @param {String} str data to decode, represented as a JavaScript
4413 * String with each code unit representing a byte between 0x00 to
4414 * 0xFF.
4415 * @return {String} The data decoded into a JavaScript UTF-16 string.
4416 */
4417lib.decodeUTF8 = function(utf8) {
4418 return (new lib.UTF8Decoder()).decode(utf8);
4419};
4420
4421/**
4422 * Encodes a UTF-16 string into UTF-8.
4423 *
4424 * TODO(davidben): Do we need a stateful version of this that can
4425 * handle a surrogate pair split in two calls? What happens if a
4426 * keypress event would have contained a character outside the BMP?
4427 *
4428 * @param {String} str The string to encode.
4429 * @return {String} The string encoded as UTF-8, as a JavaScript
4430 * string with bytes represented as code units from 0x00 to 0xFF.
4431 */
4432lib.encodeUTF8 = function(str) {
4433 var ret = '';
4434 for (var i = 0; i < str.length; i++) {
4435 // Get a unicode code point out of str.
4436 var c = str.charCodeAt(i);
4437 if (0xDC00 <= c && c <= 0xDFFF) {
4438 c = 0xFFFD;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004439 } else if (0xD800 <= c && c <= 0xDBFF) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004440 if (i + 1 < str.length) {
4441 var d = str.charCodeAt(i + 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004442 if (0xDC00 <= d && d <= 0xDFFF) {
4443 // Swallow a surrogate pair.
4444 c = 0x10000 + ((c & 0x3FF) << 10) + (d & 0x3FF);
4445 i++;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004446 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004447 c = 0xFFFD;
4448 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004449 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004450 c = 0xFFFD;
4451 }
4452 }
4453
4454 // Encode c in UTF-8.
4455 var bytesLeft;
4456 if (c <= 0x7F) {
4457 ret += str.charAt(i);
4458 continue;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004459 } else if (c <= 0x7FF) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004460 ret += String.fromCharCode(0xC0 | (c >>> 6));
4461 bytesLeft = 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004462 } else if (c <= 0xFFFF) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004463 ret += String.fromCharCode(0xE0 | (c >>> 12));
4464 bytesLeft = 2;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004465 } else /* if (c <= 0x10FFFF) */ {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004466 ret += String.fromCharCode(0xF0 | (c >>> 18));
4467 bytesLeft = 3;
4468 }
4469
4470 while (bytesLeft > 0) {
4471 bytesLeft--;
4472 ret += String.fromCharCode(0x80 | ((c >>> (6 * bytesLeft)) & 0x3F));
4473 }
4474 }
4475 return ret;
4476};
4477// SOURCE FILE: libdot/js/lib_wc.js
4478// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
4479// Use of lib.wc source code is governed by a BSD-style license that can be
4480// found in the LICENSE file.
4481
4482'use strict';
4483
4484/**
4485 * This JavaScript library is ported from the wcwidth.js module of node.js.
4486 * The original implementation can be found at:
4487 * https://npmjs.org/package/wcwidth.js
4488 */
4489
4490/**
4491 * JavaScript porting of Markus Kuhn's wcwidth() implementation
4492 *
4493 * The following explanation comes from the original C implementation:
4494 *
4495 * This is an implementation of wcwidth() and wcswidth() (defined in
4496 * IEEE Std 1002.1-2001) for Unicode.
4497 *
4498 * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
4499 * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
4500 *
4501 * In fixed-width output devices, Latin characters all occupy a single
4502 * "cell" position of equal width, whereas ideographic CJK characters
4503 * occupy two such cells. Interoperability between terminal-line
4504 * applications and (teletype-style) character terminals using the
4505 * UTF-8 encoding requires agreement on which character should advance
4506 * the cursor by how many cell positions. No established formal
4507 * standards exist at present on which Unicode character shall occupy
4508 * how many cell positions on character terminals. These routines are
4509 * a first attempt of defining such behavior based on simple rules
4510 * applied to data provided by the Unicode Consortium.
4511 *
4512 * For some graphical characters, the Unicode standard explicitly
4513 * defines a character-cell width via the definition of the East Asian
4514 * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
4515 * In all these cases, there is no ambiguity about which width a
4516 * terminal shall use. For characters in the East Asian Ambiguous (A)
4517 * class, the width choice depends purely on a preference of backward
4518 * compatibility with either historic CJK or Western practice.
4519 * Choosing single-width for these characters is easy to justify as
4520 * the appropriate long-term solution, as the CJK practice of
4521 * displaying these characters as double-width comes from historic
4522 * implementation simplicity (8-bit encoded characters were displayed
4523 * single-width and 16-bit ones double-width, even for Greek,
4524 * Cyrillic, etc.) and not any typographic considerations.
4525 *
4526 * Much less clear is the choice of width for the Not East Asian
4527 * (Neutral) class. Existing practice does not dictate a width for any
4528 * of these characters. It would nevertheless make sense
4529 * typographically to allocate two character cells to characters such
4530 * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
4531 * represented adequately with a single-width glyph. The following
4532 * routines at present merely assign a single-cell width to all
4533 * neutral characters, in the interest of simplicity. This is not
4534 * entirely satisfactory and should be reconsidered before
4535 * establishing a formal standard in lib.wc area. At the moment, the
4536 * decision which Not East Asian (Neutral) characters should be
4537 * represented by double-width glyphs cannot yet be answered by
4538 * applying a simple rule from the Unicode database content. Setting
4539 * up a proper standard for the behavior of UTF-8 character terminals
4540 * will require a careful analysis not only of each Unicode character,
4541 * but also of each presentation form, something the author of these
4542 * routines has avoided to do so far.
4543 *
4544 * http://www.unicode.org/unicode/reports/tr11/
4545 *
4546 * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
4547 *
4548 * Permission to use, copy, modify, and distribute lib.wc software
4549 * for any purpose and without fee is hereby granted. The author
4550 * disclaims all warranties with regard to lib.wc software.
4551 *
4552 * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
4553 */
4554
4555/**
4556 * The following function defines the column width of an ISO 10646 character
4557 * as follows:
4558 *
4559 * - The null character (U+0000) has a column width of 0.
4560 * - Other C0/C1 control characters and DEL will lead to a return value of -1.
4561 * - Non-spacing and enclosing combining characters (general category code Mn
4562 * or Me in the Unicode database) have a column width of 0.
4563 * - SOFT HYPHEN (U+00AD) has a column width of 1.
4564 * - Other format characters (general category code Cf in the Unicode database)
4565 * and ZERO WIDTH SPACE (U+200B) have a column width of 0.
4566 * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) have a
4567 * column width of 0.
4568 * - Spacing characters in the East Asian Wide (W) or East Asian Full-width (F)
4569 * category as defined in Unicode Technical Report #11 have a column width of
4570 * 2.
4571 * - East Asian Ambiguous characters are taken into account if
4572 * regardCjkAmbiguous flag is enabled. They have a column width of 2.
4573 * - All remaining characters (including all printable ISO 8859-1 and WGL4
4574 * characters, Unicode control characters, etc.) have a column width of 1.
4575 *
4576 * This implementation assumes that characters are encoded in ISO 10646.
4577 */
4578
4579/**
4580 * This library relies on the use of codePointAt, which is not supported in
4581 * all browsers. Polyfill if not. See
4582 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt#Polyfill
4583 */
4584if (!String.prototype.codePointAt) {
4585 (function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004586 'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004587 var codePointAt = function(position) {
4588 if (this == null) {
4589 throw TypeError();
4590 }
4591 var string = String(this);
4592 var size = string.length;
4593 // `ToInteger`
4594 var index = position ? Number(position) : 0;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004595 if (index != index) { // better `isNaN`
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004596 index = 0;
4597 }
4598 // Account for out-of-bounds indices:
4599 if (index < 0 || index >= size) {
4600 return undefined;
4601 }
4602 // Get the first code unit
4603 var first = string.charCodeAt(index);
4604 var second;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004605 if (
4606 // check if it’s the start of a surrogate pair
4607 first >= 0xD800 && first <= 0xDBFF && // high surrogate
4608 size > index + 1 // there is a next code unit
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004609 ) {
4610 second = string.charCodeAt(index + 1);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004611 if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004612 // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
4613 return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
4614 }
4615 }
4616 return first;
4617 };
4618 if (Object.defineProperty) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004619 Object.defineProperty(
4620 String.prototype, 'codePointAt',
4621 {'value': codePointAt, 'configurable': true, 'writable': true});
4622 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004623 String.prototype.codePointAt = codePointAt;
4624 }
4625 }());
4626}
4627
4628lib.wc = {};
4629
4630// Width of a nul character.
4631lib.wc.nulWidth = 0;
4632
4633// Width of a control character.
4634lib.wc.controlWidth = 0;
4635
4636// Flag whether to consider East Asian Ambiguous characters.
4637lib.wc.regardCjkAmbiguous = false;
4638
4639// Width of an East Asian Ambiguous character.
4640lib.wc.cjkAmbiguousWidth = 2;
4641
4642// Sorted list of non-overlapping intervals of non-spacing characters
4643// generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c"
4644lib.wc.combining = [
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004645 [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
4646 [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
4647 [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
4648 [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
4649 [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
4650 [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
4651 [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
4652 [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
4653 [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
4654 [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
4655 [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
4656 [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
4657 [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
4658 [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
4659 [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
4660 [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
4661 [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
4662 [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
4663 [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
4664 [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
4665 [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
4666 [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
4667 [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
4668 [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
4669 [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
4670 [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
4671 [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
4672 [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
4673 [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
4674 [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
4675 [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
4676 [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
4677 [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
4678 [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
4679 [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
4680 [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
4681 [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
4682 [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
4683 [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
4684 [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
4685 [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
4686 [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
4687 [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
4688 [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
4689 [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
4690 [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
4691 [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004692 [0xE0100, 0xE01EF]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004693];
4694
4695// Sorted list of non-overlapping intervals of East Asian Ambiguous characters
4696// generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c"
4697lib.wc.ambiguous = [
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004698 [0x00A1, 0x00A1], [0x00A4, 0x00A4], [0x00A7, 0x00A8], [0x00AA, 0x00AA],
4699 [0x00AE, 0x00AE], [0x00B0, 0x00B4], [0x00B6, 0x00BA], [0x00BC, 0x00BF],
4700 [0x00C6, 0x00C6], [0x00D0, 0x00D0], [0x00D7, 0x00D8], [0x00DE, 0x00E1],
4701 [0x00E6, 0x00E6], [0x00E8, 0x00EA], [0x00EC, 0x00ED], [0x00F0, 0x00F0],
4702 [0x00F2, 0x00F3], [0x00F7, 0x00FA], [0x00FC, 0x00FC], [0x00FE, 0x00FE],
4703 [0x0101, 0x0101], [0x0111, 0x0111], [0x0113, 0x0113], [0x011B, 0x011B],
4704 [0x0126, 0x0127], [0x012B, 0x012B], [0x0131, 0x0133], [0x0138, 0x0138],
4705 [0x013F, 0x0142], [0x0144, 0x0144], [0x0148, 0x014B], [0x014D, 0x014D],
4706 [0x0152, 0x0153], [0x0166, 0x0167], [0x016B, 0x016B], [0x01CE, 0x01CE],
4707 [0x01D0, 0x01D0], [0x01D2, 0x01D2], [0x01D4, 0x01D4], [0x01D6, 0x01D6],
4708 [0x01D8, 0x01D8], [0x01DA, 0x01DA], [0x01DC, 0x01DC], [0x0251, 0x0251],
4709 [0x0261, 0x0261], [0x02C4, 0x02C4], [0x02C7, 0x02C7], [0x02C9, 0x02CB],
4710 [0x02CD, 0x02CD], [0x02D0, 0x02D0], [0x02D8, 0x02DB], [0x02DD, 0x02DD],
4711 [0x02DF, 0x02DF], [0x0391, 0x03A1], [0x03A3, 0x03A9], [0x03B1, 0x03C1],
4712 [0x03C3, 0x03C9], [0x0401, 0x0401], [0x0410, 0x044F], [0x0451, 0x0451],
4713 [0x2010, 0x2010], [0x2013, 0x2016], [0x2018, 0x2019], [0x201C, 0x201D],
4714 [0x2020, 0x2022], [0x2024, 0x2027], [0x2030, 0x2030], [0x2032, 0x2033],
4715 [0x2035, 0x2035], [0x203B, 0x203B], [0x203E, 0x203E], [0x2074, 0x2074],
4716 [0x207F, 0x207F], [0x2081, 0x2084], [0x20AC, 0x20AC], [0x2103, 0x2103],
4717 [0x2105, 0x2105], [0x2109, 0x2109], [0x2113, 0x2113], [0x2116, 0x2116],
4718 [0x2121, 0x2122], [0x2126, 0x2126], [0x212B, 0x212B], [0x2153, 0x2154],
4719 [0x215B, 0x215E], [0x2160, 0x216B], [0x2170, 0x2179], [0x2190, 0x2199],
4720 [0x21B8, 0x21B9], [0x21D2, 0x21D2], [0x21D4, 0x21D4], [0x21E7, 0x21E7],
4721 [0x2200, 0x2200], [0x2202, 0x2203], [0x2207, 0x2208], [0x220B, 0x220B],
4722 [0x220F, 0x220F], [0x2211, 0x2211], [0x2215, 0x2215], [0x221A, 0x221A],
4723 [0x221D, 0x2220], [0x2223, 0x2223], [0x2225, 0x2225], [0x2227, 0x222C],
4724 [0x222E, 0x222E], [0x2234, 0x2237], [0x223C, 0x223D], [0x2248, 0x2248],
4725 [0x224C, 0x224C], [0x2252, 0x2252], [0x2260, 0x2261], [0x2264, 0x2267],
4726 [0x226A, 0x226B], [0x226E, 0x226F], [0x2282, 0x2283], [0x2286, 0x2287],
4727 [0x2295, 0x2295], [0x2299, 0x2299], [0x22A5, 0x22A5], [0x22BF, 0x22BF],
4728 [0x2312, 0x2312], [0x2460, 0x24E9], [0x24EB, 0x254B], [0x2550, 0x2573],
4729 [0x2580, 0x258F], [0x2592, 0x2595], [0x25A0, 0x25A1], [0x25A3, 0x25A9],
4730 [0x25B2, 0x25B3], [0x25B6, 0x25B7], [0x25BC, 0x25BD], [0x25C0, 0x25C1],
4731 [0x25C6, 0x25C8], [0x25CB, 0x25CB], [0x25CE, 0x25D1], [0x25E2, 0x25E5],
4732 [0x25EF, 0x25EF], [0x2605, 0x2606], [0x2609, 0x2609], [0x260E, 0x260F],
4733 [0x2614, 0x2615], [0x261C, 0x261C], [0x261E, 0x261E], [0x2640, 0x2640],
4734 [0x2642, 0x2642], [0x2660, 0x2661], [0x2663, 0x2665], [0x2667, 0x266A],
4735 [0x266C, 0x266D], [0x266F, 0x266F], [0x273D, 0x273D], [0x2776, 0x277F],
4736 [0xE000, 0xF8FF], [0xFFFD, 0xFFFD], [0xF0000, 0xFFFFD], [0x100000, 0x10FFFD]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004737];
4738
4739/**
4740 * Binary search to check if the given unicode character is a space character.
4741 *
4742 * @param {integer} ucs A unicode character code.
4743 *
4744 * @return {boolean} True if the given character is a space character; false
4745 * otherwise.
4746 */
4747lib.wc.isSpace = function(ucs) {
4748 // Auxiliary function for binary search in interval table.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004749 var min = 0, max = lib.wc.combining.length - 1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004750 var mid;
4751
4752 if (ucs < lib.wc.combining[0][0] || ucs > lib.wc.combining[max][1])
4753 return false;
4754 while (max >= min) {
4755 mid = Math.floor((min + max) / 2);
4756 if (ucs > lib.wc.combining[mid][1]) {
4757 min = mid + 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004758 } else if (ucs < lib.wc.combining[mid][0]) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004759 max = mid - 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004760 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004761 return true;
4762 }
4763 }
4764
4765 return false;
4766};
4767
4768/**
4769 * Auxiliary function for checking if the given unicode character is a East
4770 * Asian Ambiguous character.
4771 *
4772 * @param {integer} ucs A unicode character code.
4773 *
4774 * @return {boolean} True if the given character is a East Asian Ambiguous
4775 * character.
4776 */
4777lib.wc.isCjkAmbiguous = function(ucs) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004778 var min = 0, max = lib.wc.ambiguous.length - 1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004779 var mid;
4780
4781 if (ucs < lib.wc.ambiguous[0][0] || ucs > lib.wc.ambiguous[max][1])
4782 return false;
4783 while (max >= min) {
4784 mid = Math.floor((min + max) / 2);
4785 if (ucs > lib.wc.ambiguous[mid][1]) {
4786 min = mid + 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004787 } else if (ucs < lib.wc.ambiguous[mid][0]) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004788 max = mid - 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004789 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004790 return true;
4791 }
4792 }
4793
4794 return false;
4795};
4796
4797/**
4798 * Determine the column width of the given character.
4799 *
4800 * @param {integer} ucs A unicode character code.
4801 *
4802 * @return {integer} The column width of the given character.
4803 */
4804lib.wc.charWidth = function(ucs) {
4805 if (lib.wc.regardCjkAmbiguous) {
4806 return lib.wc.charWidthRegardAmbiguous(ucs);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004807 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004808 return lib.wc.charWidthDisregardAmbiguous(ucs);
4809 }
4810};
4811
4812/**
4813 * Determine the column width of the given character without considering East
4814 * Asian Ambiguous characters.
4815 *
4816 * @param {integer} ucs A unicode character code.
4817 *
4818 * @return {integer} The column width of the given character.
4819 */
4820lib.wc.charWidthDisregardAmbiguous = function(ucs) {
4821 // Test for 8-bit control characters.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004822 if (ucs === 0) return lib.wc.nulWidth;
4823 if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) return lib.wc.controlWidth;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004824
4825 // Optimize for ASCII characters.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004826 if (ucs < 0x7f) return 1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004827
4828 // Binary search in table of non-spacing characters.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004829 if (lib.wc.isSpace(ucs)) return 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004830
4831 // If we arrive here, ucs is not a combining or C0/C1 control character.
4832 return 1 +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004833 (ucs >= 0x1100 &&
4834 (ucs <= 0x115f || // Hangul Jamo init. consonants
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004835 ucs == 0x2329 || ucs == 0x232a ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004836 (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || // CJK ... Yi
4837 (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
4838 (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compatibility Ideographs
4839 (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
4840 (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compatibility Forms
4841 (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004842 (ucs >= 0xffe0 && ucs <= 0xffe6) ||
4843 (ucs >= 0x20000 && ucs <= 0x2fffd) ||
4844 (ucs >= 0x30000 && ucs <= 0x3fffd)));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004845 // TODO: emoji characters usually require space for wide characters although
4846 // East Asian width spec says nothing. Should we add special cases for them?
4847};
4848
4849/**
4850 * Determine the column width of the given character considering East Asian
4851 * Ambiguous characters.
4852 *
4853 * @param {integer} ucs A unicode character code.
4854 *
4855 * @return {integer} The column width of the given character.
4856 */
4857lib.wc.charWidthRegardAmbiguous = function(ucs) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004858 if (lib.wc.isCjkAmbiguous(ucs)) return lib.wc.cjkAmbiguousWidth;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004859
4860 return lib.wc.charWidthDisregardAmbiguous(ucs);
4861};
4862
4863/**
4864 * Determine the column width of the given string.
4865 *
4866 * @param {string} str A string.
4867 *
4868 * @return {integer} The column width of the given string.
4869 */
4870lib.wc.strWidth = function(str) {
4871 var width, rv = 0;
4872
4873 for (var i = 0; i < str.length;) {
4874 var codePoint = str.codePointAt(i);
4875 width = lib.wc.charWidth(codePoint);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004876 if (width < 0) return -1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004877 rv += width;
4878 i += (codePoint <= 0xffff) ? 1 : 2;
4879 }
4880
4881 return rv;
4882};
4883
4884/**
4885 * Get the substring at the given column offset of the given column width.
4886 *
4887 * @param {string} str The string to get substring from.
4888 * @param {integer} start The starting column offset to get substring.
4889 * @param {integer} opt_width The column width of the substring.
4890 *
4891 * @return {string} The substring.
4892 */
4893lib.wc.substr = function(str, start, opt_width) {
4894 var startIndex, endIndex, width;
4895
4896 for (startIndex = 0, width = 0; startIndex < str.length; startIndex++) {
4897 width += lib.wc.charWidth(str.charCodeAt(startIndex));
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004898 if (width > start) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004899 }
4900
4901 if (opt_width != undefined) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004902 for (endIndex = startIndex, width = 0;
4903 endIndex < str.length && width < opt_width;
4904 width += lib.wc.charWidth(str.charCodeAt(endIndex)), endIndex++)
4905 ;
4906 if (width > opt_width) endIndex--;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004907 return str.substring(startIndex, endIndex);
4908 }
4909
4910 return str.substr(startIndex);
4911};
4912
4913/**
4914 * Get substring at the given start and end column offset.
4915 *
4916 * @param {string} str The string to get substring from.
4917 * @param {integer} start The starting column offset.
4918 * @param {integer} end The ending column offset.
4919 *
4920 * @return {string} The substring.
4921 */
4922lib.wc.substring = function(str, start, end) {
4923 return lib.wc.substr(str, start, end - start);
4924};
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004925lib.resource.add(
4926 'libdot/changelog/version', 'text/plain',
4927 '1.11' +
4928 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004929
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004930lib.resource.add(
4931 'libdot/changelog/date', 'text/plain',
4932 '2017-04-17' +
4933 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004934
4935// SOURCE FILE: hterm/js/hterm.js
4936// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4937// Use of this source code is governed by a BSD-style license that can be
4938// found in the LICENSE file.
4939
4940'use strict';
4941
4942lib.rtdep('lib.Storage');
4943
4944/**
4945 * @fileoverview Declares the hterm.* namespace and some basic shared utilities
4946 * that are too small to deserve dedicated files.
4947 */
4948var hterm = {};
4949
4950/**
4951 * The type of window hosting hterm.
4952 *
4953 * This is set as part of hterm.init(). The value is invalid until
4954 * initialization completes.
4955 */
4956hterm.windowType = null;
4957
4958/**
4959 * Warning message to display in the terminal when browser zoom is enabled.
4960 *
4961 * You can replace it with your own localized message.
4962 */
4963hterm.zoomWarningMessage = 'ZOOM != 100%';
4964
4965/**
4966 * Brief overlay message displayed when text is copied to the clipboard.
4967 *
4968 * By default it is the unicode BLACK SCISSORS character, but you can
4969 * replace it with your own localized message.
4970 *
4971 * This is only displayed when the 'enable-clipboard-notice' preference
4972 * is enabled.
4973 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004974hterm.notifyCopyMessage = '✂';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004975
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004976/**
4977 * Text shown in a desktop notification for the terminal
4978 * bell. \u226a is a unicode EIGHTH NOTE, %(title) will
4979 * be replaced by the terminal title.
4980 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004981hterm.desktopNotificationTitle = '♪ %(title) ♪';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004982
4983/**
4984 * List of known hterm test suites.
4985 *
4986 * A test harness should ensure that they all exist before running.
4987 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004988hterm.testDeps = [
4989 'hterm.ScrollPort.Tests', 'hterm.Screen.Tests', 'hterm.Terminal.Tests',
4990 'hterm.VT.Tests', 'hterm.VT.CannedTests'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004991];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004992
4993/**
4994 * The hterm init function, registered with lib.registerInit().
4995 *
4996 * This is called during lib.init().
4997 *
4998 * @param {function} onInit The function lib.init() wants us to invoke when
4999 * initialization is complete.
5000 */
5001lib.registerInit('hterm', function(onInit) {
5002 function onWindow(window) {
5003 hterm.windowType = window.type;
5004 setTimeout(onInit, 0);
5005 }
5006
5007 function onTab(tab) {
5008 if (tab && window.chrome) {
5009 chrome.windows.get(tab.windowId, null, onWindow);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005010 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005011 // TODO(rginda): This is where we end up for a v1 app's background page.
5012 // Maybe windowType = 'none' would be more appropriate, or something.
5013 hterm.windowType = 'normal';
5014 setTimeout(onInit, 0);
5015 }
5016 }
5017
5018 if (!hterm.defaultStorage) {
5019 var ary = navigator.userAgent.match(/\sChrome\/(\d\d)/);
5020 var version = ary ? parseInt(ary[1]) : -1;
5021 if (window.chrome && chrome.storage && chrome.storage.sync &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005022 version > 21) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005023 hterm.defaultStorage = new lib.Storage.Chrome(chrome.storage.sync);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005024 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005025 hterm.defaultStorage = new lib.Storage.Local();
5026 }
5027 }
5028
5029 // The chrome.tabs API is not supported in packaged apps, and detecting if
5030 // you're a packaged app is a little awkward.
5031 var isPackagedApp = false;
5032 if (window.chrome && chrome.runtime && chrome.runtime.getManifest) {
5033 var manifest = chrome.runtime.getManifest();
5034 isPackagedApp = manifest.app && manifest.app.background;
5035 }
5036
5037 if (isPackagedApp) {
5038 // Packaged apps are never displayed in browser tabs.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005039 setTimeout(onWindow.bind(null, {type: 'popup'}), 0);
5040 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005041 if (window.chrome && chrome.tabs) {
5042 // The getCurrent method gets the tab that is "currently running", not the
5043 // topmost or focused tab.
5044 chrome.tabs.getCurrent(onTab);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005045 } else {
5046 setTimeout(onWindow.bind(null, {type: 'normal'}), 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005047 }
5048 }
5049});
5050
5051/**
5052 * Return decimal { width, height } for a given dom node.
5053 */
5054hterm.getClientSize = function(dom) {
5055 return dom.getBoundingClientRect();
5056};
5057
5058/**
5059 * Return decimal width for a given dom node.
5060 */
5061hterm.getClientWidth = function(dom) {
5062 return dom.getBoundingClientRect().width;
5063};
5064
5065/**
5066 * Return decimal height for a given dom node.
5067 */
5068hterm.getClientHeight = function(dom) {
5069 return dom.getBoundingClientRect().height;
5070};
5071
5072/**
5073 * Copy the current selection to the system clipboard.
5074 *
5075 * @param {HTMLDocument} The document with the selection to copy.
5076 */
5077hterm.copySelectionToClipboard = function(document) {
5078 try {
5079 document.execCommand('copy');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005080 } catch (firefoxException) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005081 // Ignore this. FF throws an exception if there was an error, even though
5082 // the spec says just return false.
5083 }
5084};
5085
5086/**
5087 * Paste the system clipboard into the element with focus.
5088 *
5089 * @param {HTMLDocument} The document to paste into.
5090 */
5091hterm.pasteFromClipboard = function(document) {
5092 try {
5093 document.execCommand('paste');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005094 } catch (firefoxException) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005095 // Ignore this. FF throws an exception if there was an error, even though
5096 // the spec says just return false.
5097 }
5098};
5099
5100/**
5101 * Constructor for a hterm.Size record.
5102 *
5103 * Instances of this class have public read/write members for width and height.
5104 *
5105 * @param {integer} width The width of this record.
5106 * @param {integer} height The height of this record.
5107 */
5108hterm.Size = function(width, height) {
5109 this.width = width;
5110 this.height = height;
5111};
5112
5113/**
5114 * Adjust the width and height of this record.
5115 *
5116 * @param {integer} width The new width of this record.
5117 * @param {integer} height The new height of this record.
5118 */
5119hterm.Size.prototype.resize = function(width, height) {
5120 this.width = width;
5121 this.height = height;
5122};
5123
5124/**
5125 * Return a copy of this record.
5126 *
5127 * @return {hterm.Size} A new hterm.Size instance with the same width and
5128 * height.
5129 */
5130hterm.Size.prototype.clone = function() {
5131 return new hterm.Size(this.width, this.height);
5132};
5133
5134/**
5135 * Set the height and width of this instance based on another hterm.Size.
5136 *
5137 * @param {hterm.Size} that The object to copy from.
5138 */
5139hterm.Size.prototype.setTo = function(that) {
5140 this.width = that.width;
5141 this.height = that.height;
5142};
5143
5144/**
5145 * Test if another hterm.Size instance is equal to this one.
5146 *
5147 * @param {hterm.Size} that The other hterm.Size instance.
5148 * @return {boolean} True if both instances have the same width/height, false
5149 * otherwise.
5150 */
5151hterm.Size.prototype.equals = function(that) {
5152 return this.width == that.width && this.height == that.height;
5153};
5154
5155/**
5156 * Return a string representation of this instance.
5157 *
5158 * @return {string} A string that identifies the width and height of this
5159 * instance.
5160 */
5161hterm.Size.prototype.toString = function() {
5162 return '[hterm.Size: ' + this.width + ', ' + this.height + ']';
5163};
5164
5165/**
5166 * Constructor for a hterm.RowCol record.
5167 *
5168 * Instances of this class have public read/write members for row and column.
5169 *
5170 * This class includes an 'overflow' bit which is use to indicate that an
5171 * attempt has been made to move the cursor column passed the end of the
5172 * screen. When this happens we leave the cursor column set to the last column
5173 * of the screen but set the overflow bit. In this state cursor movement
5174 * happens normally, but any attempt to print new characters causes a cr/lf
5175 * first.
5176 *
5177 * @param {integer} row The row of this record.
5178 * @param {integer} column The column of this record.
5179 * @param {boolean} opt_overflow Optional boolean indicating that the RowCol
5180 * has overflowed.
5181 */
5182hterm.RowCol = function(row, column, opt_overflow) {
5183 this.row = row;
5184 this.column = column;
5185 this.overflow = !!opt_overflow;
5186};
5187
5188/**
5189 * Adjust the row and column of this record.
5190 *
5191 * @param {integer} row The new row of this record.
5192 * @param {integer} column The new column of this record.
5193 * @param {boolean} opt_overflow Optional boolean indicating that the RowCol
5194 * has overflowed.
5195 */
5196hterm.RowCol.prototype.move = function(row, column, opt_overflow) {
5197 this.row = row;
5198 this.column = column;
5199 this.overflow = !!opt_overflow;
5200};
5201
5202/**
5203 * Return a copy of this record.
5204 *
5205 * @return {hterm.RowCol} A new hterm.RowCol instance with the same row and
5206 * column.
5207 */
5208hterm.RowCol.prototype.clone = function() {
5209 return new hterm.RowCol(this.row, this.column, this.overflow);
5210};
5211
5212/**
5213 * Set the row and column of this instance based on another hterm.RowCol.
5214 *
5215 * @param {hterm.RowCol} that The object to copy from.
5216 */
5217hterm.RowCol.prototype.setTo = function(that) {
5218 this.row = that.row;
5219 this.column = that.column;
5220 this.overflow = that.overflow;
5221};
5222
5223/**
5224 * Test if another hterm.RowCol instance is equal to this one.
5225 *
5226 * @param {hterm.RowCol} that The other hterm.RowCol instance.
5227 * @return {boolean} True if both instances have the same row/column, false
5228 * otherwise.
5229 */
5230hterm.RowCol.prototype.equals = function(that) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005231 return (
5232 this.row == that.row && this.column == that.column &&
5233 this.overflow == that.overflow);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005234};
5235
5236/**
5237 * Return a string representation of this instance.
5238 *
5239 * @return {string} A string that identifies the row and column of this
5240 * instance.
5241 */
5242hterm.RowCol.prototype.toString = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005243 return (
5244 '[hterm.RowCol: ' + this.row + ', ' + this.column + ', ' + this.overflow +
5245 ']');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005246};
5247// SOURCE FILE: hterm/js/hterm_frame.js
5248// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
5249// Use of this source code is governed by a BSD-style license that can be
5250// found in the LICENSE file.
5251
5252'use strict';
5253
5254lib.rtdep('lib.f');
5255
5256/**
5257 * First draft of the interface between the terminal and a third party dialog.
5258 *
5259 * This is rough. It's just the terminal->dialog layer. To complete things
5260 * we'll also need a command->terminal layer. That will have to facilitate
5261 * command->terminal->dialog or direct command->dialog communication.
5262 *
5263 * I imagine this class will change significantly when that happens.
5264 */
5265
5266/**
5267 * Construct a new frame for the given terminal.
5268 *
5269 * @param terminal {hterm.Terminal} The parent terminal object.
5270 * @param url {String} The url to load in the frame.
5271 * @param opt_options {Object} Optional options for the frame. Not implemented.
5272 */
5273hterm.Frame = function(terminal, url, opt_options) {
5274 this.terminal_ = terminal;
5275 this.div_ = terminal.div_;
5276 this.url = url;
5277 this.options = opt_options || {};
5278 this.iframe_ = null;
5279 this.container_ = null;
5280 this.messageChannel_ = null;
5281};
5282
5283/**
5284 * Handle messages from the iframe.
5285 */
5286hterm.Frame.prototype.onMessage_ = function(e) {
5287 if (e.data.name != 'ipc-init-ok') {
5288 console.log('Unknown message from frame:', e.data);
5289 return;
5290 }
5291
5292 this.sendTerminalInfo_();
5293 this.messageChannel_.port1.onmessage = this.onMessage.bind(this);
5294 this.onLoad();
5295};
5296
5297/**
5298 * Clients could override this, I guess.
5299 *
5300 * It doesn't support multiple listeners, but I'm not sure that would make sense
5301 * here. It's probably better to speak directly to our parents.
5302 */
5303hterm.Frame.prototype.onMessage = function() {};
5304
5305/**
5306 * Handle iframe onLoad event.
5307 */
5308hterm.Frame.prototype.onLoad_ = function() {
5309 this.messageChannel_ = new MessageChannel();
5310 this.messageChannel_.port1.onmessage = this.onMessage_.bind(this);
5311 this.messageChannel_.port1.start();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005312 this.iframe_.contentWindow.postMessage(
5313 {name: 'ipc-init', argv: [{messagePort: this.messageChannel_.port2}]},
5314 this.url, [this.messageChannel_.port2]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005315};
5316
5317/**
5318 * Clients may override this.
5319 */
5320hterm.Frame.prototype.onLoad = function() {};
5321
5322/**
5323 * Sends the terminal-info message to the iframe.
5324 */
5325hterm.Frame.prototype.sendTerminalInfo_ = function() {
5326 lib.f.getAcceptLanguages(function(languages) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005327 this.postMessage('terminal-info', [{
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005328 acceptLanguages: languages,
5329 foregroundColor: this.terminal_.getForegroundColor(),
5330 backgroundColor: this.terminal_.getBackgroundColor(),
5331 cursorColor: this.terminal_.getCursorColor(),
5332 fontSize: this.terminal_.getFontSize(),
5333 fontFamily: this.terminal_.getFontFamily(),
5334 baseURL: lib.f.getURL('/')
5335 }]);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005336 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005337};
5338
5339/**
5340 * User clicked the close button on the frame decoration.
5341 */
5342hterm.Frame.prototype.onCloseClicked_ = function() {
5343 this.close();
5344};
5345
5346/**
5347 * Close this frame.
5348 */
5349hterm.Frame.prototype.close = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005350 if (!this.container_ || !this.container_.parentNode) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005351
5352 this.container_.parentNode.removeChild(this.container_);
5353 this.onClose();
5354};
5355
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005356/**
5357 * Clients may override this.
5358 */
5359hterm.Frame.prototype.onClose = function() {};
5360
5361/**
5362 * Send a message to the iframe.
5363 */
5364hterm.Frame.prototype.postMessage = function(name, argv) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005365 if (!this.messageChannel_) throw new Error('Message channel is not set up.');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005366
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005367 this.messageChannel_.port1.postMessage({name: name, argv: argv});
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005368};
5369
5370/**
5371 * Show the UI for this frame.
5372 *
5373 * The iframe src is not loaded until this method is called.
5374 */
5375hterm.Frame.prototype.show = function() {
5376 var self = this;
5377
5378 function opt(name, defaultValue) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005379 if (name in self.options) return self.options[name];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005380
5381 return defaultValue;
5382 }
5383
5384 var self = this;
5385
5386 if (this.container_ && this.container_.parentNode) {
5387 console.error('Frame already visible');
5388 return;
5389 }
5390
5391 var headerHeight = '16px';
5392
5393 var divSize = hterm.getClientSize(this.div_);
5394
5395 var width = opt('width', 640);
5396 var height = opt('height', 480);
5397 var left = (divSize.width - width) / 2;
5398 var top = (divSize.height - height) / 2;
5399
5400 var document = this.terminal_.document_;
5401
5402 var container = this.container_ = document.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005403 container.style.cssText =
5404 ('position: absolute;' +
5405 'display: -webkit-flex;' +
5406 '-webkit-flex-direction: column;' +
5407 'top: 10%;' +
5408 'left: 4%;' +
5409 'width: 90%;' +
5410 'height: 80%;' +
5411 'box-shadow: 0 0 2px ' + this.terminal_.getForegroundColor() + ';' +
5412 'border: 2px ' + this.terminal_.getForegroundColor() + ' solid;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005413
5414 var header = document.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005415 header.style.cssText =
5416 ('display: -webkit-flex;' +
5417 '-webkit-justify-content: flex-end;' +
5418 'height: ' + headerHeight + ';' +
5419 'background-color: ' + this.terminal_.getForegroundColor() + ';' +
5420 'color: ' + this.terminal_.getBackgroundColor() + ';' +
5421 'font-size: 16px;' +
5422 'font-family: ' + this.terminal_.getFontFamily());
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005423 container.appendChild(header);
5424
5425 if (false) {
5426 // No use for the close button.
5427 var button = document.createElement('div');
5428 button.setAttribute('role', 'button');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005429 button.style.cssText =
5430 ('margin-top: -3px;' +
5431 'margin-right: 3px;' +
5432 'cursor: pointer;');
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005433 button.textContent = '⨯';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005434 button.addEventListener('click', this.onCloseClicked_.bind(this));
5435 header.appendChild(button);
5436 }
5437
5438 var iframe = this.iframe_ = document.createElement('iframe');
5439 iframe.onload = this.onLoad_.bind(this);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005440 iframe.style.cssText =
5441 ('display: -webkit-flex;' +
5442 '-webkit-flex: 1;' +
5443 'width: 100%');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005444 iframe.setAttribute('src', this.url);
5445 iframe.setAttribute('seamless', true);
5446 container.appendChild(iframe);
5447
5448 this.div_.appendChild(container);
5449};
5450// SOURCE FILE: hterm/js/hterm_keyboard.js
5451// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
5452// Use of this source code is governed by a BSD-style license that can be
5453// found in the LICENSE file.
5454
5455'use strict';
5456
5457lib.rtdep('hterm.Keyboard.KeyMap');
5458
5459/**
5460 * Keyboard handler.
5461 *
5462 * Consumes onKey* events and invokes onVTKeystroke on the associated
5463 * hterm.Terminal object.
5464 *
5465 * See also: [XTERM] as referenced in vt.js.
5466 *
5467 * @param {hterm.Terminal} The Terminal object associated with this keyboard.
5468 */
5469hterm.Keyboard = function(terminal) {
5470 // The parent vt interpreter.
5471 this.terminal = terminal;
5472
5473 // The element we're currently capturing keyboard events for.
5474 this.keyboardElement_ = null;
5475
5476 // The event handlers we are interested in, and their bound callbacks, saved
5477 // so they can be uninstalled with removeEventListener, when required.
5478 this.handlers_ = [
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005479 ['focusout', this.onFocusOut_.bind(this)],
5480 ['keydown', this.onKeyDown_.bind(this)],
5481 ['keypress', this.onKeyPress_.bind(this)],
5482 ['keyup', this.onKeyUp_.bind(this)],
5483 ['textInput', this.onTextInput_.bind(this)]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005484 ];
5485
5486 /**
5487 * The current key map.
5488 */
5489 this.keyMap = new hterm.Keyboard.KeyMap(this);
5490
5491 this.bindings = new hterm.Keyboard.Bindings(this);
5492
5493 /**
5494 * none: Disable any AltGr related munging.
5495 * ctrl-alt: Assume Ctrl+Alt means AltGr.
5496 * left-alt: Assume left Alt means AltGr.
5497 * right-alt: Assume right Alt means AltGr.
5498 */
5499 this.altGrMode = 'none';
5500
5501 /**
5502 * If true, Shift-Insert will fall through to the browser as a paste.
5503 * If false, the keystroke will be sent to the host.
5504 */
5505 this.shiftInsertPaste = true;
5506
5507 /**
5508 * If true, home/end will control the terminal scrollbar and shift home/end
5509 * will send the VT keycodes. If false then home/end sends VT codes and
5510 * shift home/end scrolls.
5511 */
5512 this.homeKeysScroll = false;
5513
5514 /**
5515 * Same as above, except for page up/page down.
5516 */
5517 this.pageKeysScroll = false;
5518
5519 /**
5520 * If true, Ctrl-Plus/Minus/Zero controls zoom.
5521 * If false, Ctrl-Shift-Plus/Minus/Zero controls zoom, Ctrl-Minus sends ^_,
5522 * Ctrl-Plus/Zero do nothing.
5523 */
5524 this.ctrlPlusMinusZeroZoom = true;
5525
5526 /**
5527 * Ctrl+C copies if true, sends ^C to host if false.
5528 * Ctrl+Shift+C sends ^C to host if true, copies if false.
5529 */
5530 this.ctrlCCopy = false;
5531
5532 /**
5533 * Ctrl+V pastes if true, sends ^V to host if false.
5534 * Ctrl+Shift+V sends ^V to host if true, pastes if false.
5535 */
5536 this.ctrlVPaste = false;
5537
5538 /**
5539 * Enable/disable application keypad.
5540 *
5541 * This changes the way numeric keys are sent from the keyboard.
5542 */
5543 this.applicationKeypad = false;
5544
5545 /**
5546 * Enable/disable the application cursor mode.
5547 *
5548 * This changes the way cursor keys are sent from the keyboard.
5549 */
5550 this.applicationCursor = false;
5551
5552 /**
5553 * If true, the backspace should send BS ('\x08', aka ^H). Otherwise
5554 * the backspace key should send '\x7f'.
5555 */
5556 this.backspaceSendsBackspace = false;
5557
5558 /**
5559 * The encoding method for data sent to the host.
5560 */
5561 this.characterEncoding = 'utf-8';
5562
5563 /**
5564 * Set whether the meta key sends a leading escape or not.
5565 */
5566 this.metaSendsEscape = true;
5567
5568 /**
5569 * Set whether meta-V gets passed to host.
5570 */
5571 this.passMetaV = true;
5572
5573 /**
5574 * Controls how the alt key is handled.
5575 *
5576 * escape....... Send an ESC prefix.
5577 * 8-bit........ Add 128 to the unshifted character as in xterm.
5578 * browser-key.. Wait for the keypress event and see what the browser says.
5579 * (This won't work well on platforms where the browser
5580 * performs a default action for some alt sequences.)
5581 *
5582 * This setting only matters when alt is distinct from meta (altIsMeta is
5583 * false.)
5584 */
5585 this.altSendsWhat = 'escape';
5586
5587 /**
5588 * Set whether the alt key acts as a meta key, instead of producing 8-bit
5589 * characters.
5590 *
5591 * True to enable, false to disable, null to autodetect based on platform.
5592 */
5593 this.altIsMeta = false;
5594
5595 /**
5596 * If true, tries to detect DEL key events that are from alt-backspace on
5597 * Chrome OS vs from a true DEL key press.
5598 *
5599 * Background: At the time of writing, on Chrome OS, alt-backspace is mapped
5600 * to DEL. Some users may be happy with this, but others may be frustrated
5601 * that it's impossible to do meta-backspace. If the user enables this pref,
5602 * we use a trick to tell a true DEL keypress from alt-backspace: on
5603 * alt-backspace, we will see the alt key go down, then get a DEL keystroke
5604 * that indicates that alt is not pressed. See https://crbug.com/174410 .
5605 */
5606 this.altBackspaceIsMetaBackspace = false;
5607
5608 /**
5609 * Used to keep track of the current alt-key state, which is necessary for
5610 * the altBackspaceIsMetaBackspace preference above and for the altGrMode
5611 * preference. This is a bitmap with where bit positions correspond to the
5612 * "location" property of the key event.
5613 */
5614 this.altKeyPressed = 0;
5615
5616 /**
5617 * If true, Chrome OS media keys will be mapped to their F-key equivalent.
5618 * E.g. "Back" will be mapped to F1. If false, Chrome will handle the keys.
5619 */
5620 this.mediaKeysAreFKeys = false;
5621
5622 /**
5623 * Holds the previous setting of altSendsWhat when DECSET 1039 is used. When
5624 * DECRST 1039 is used, altSendsWhat is changed back to this and this is
5625 * nulled out.
5626 */
5627 this.previousAltSendsWhat_ = null;
5628};
5629
5630/**
5631 * Special handling for keyCodes in a keyboard layout.
5632 */
5633hterm.Keyboard.KeyActions = {
5634 /**
5635 * Call preventDefault and stopPropagation for this key event and nothing
5636 * else.
5637 */
5638 CANCEL: new String('CANCEL'),
5639
5640 /**
5641 * This performs the default terminal action for the key. If used in the
5642 * 'normal' action and the the keystroke represents a printable key, the
5643 * character will be sent to the host. If used in one of the modifier
5644 * actions, the terminal will perform the normal action after (possibly)
5645 * altering it.
5646 *
5647 * - If the normal sequence starts with CSI, the sequence will be adjusted
5648 * to include the modifier parameter as described in [XTERM] in the final
5649 * table of the "PC-Style Function Keys" section.
5650 *
5651 * - If the control key is down and the key represents a printable character,
5652 * and the uppercase version of the unshifted keycap is between
5653 * 64 (ASCII '@') and 95 (ASCII '_'), then the uppercase version of the
5654 * unshifted keycap minus 64 is sent. This makes '^@' send '\x00' and
5655 * '^_' send '\x1f'. (Note that one higher that 0x1f is 0x20, which is
5656 * the first printable ASCII value.)
5657 *
5658 * - If the alt key is down and the key represents a printable character then
5659 * the value of the character is shifted up by 128.
5660 *
5661 * - If meta is down and configured to send an escape, '\x1b' will be sent
5662 * before the normal action is performed.
5663 */
5664 DEFAULT: new String('DEFAULT'),
5665
5666 /**
5667 * Causes the terminal to opt out of handling the key event, instead letting
5668 * the browser deal with it.
5669 */
5670 PASS: new String('PASS'),
5671
5672 /**
5673 * Insert the first or second character of the keyCap, based on e.shiftKey.
5674 * The key will be handled in onKeyDown, and e.preventDefault() will be
5675 * called.
5676 *
5677 * It is useful for a modified key action, where it essentially strips the
5678 * modifier while preventing the browser from reacting to the key.
5679 */
5680 STRIP: new String('STRIP')
5681};
5682
5683/**
5684 * Encode a string according to the 'send-encoding' preference.
5685 */
5686hterm.Keyboard.prototype.encode = function(str) {
5687 if (this.characterEncoding == 'utf-8')
5688 return this.terminal.vt.encodeUTF8(str);
5689
5690 return str;
5691};
5692
5693/**
5694 * Capture keyboard events sent to the associated element.
5695 *
5696 * This enables the keyboard. Captured events are consumed by this class
5697 * and will not perform their default action or bubble to other elements.
5698 *
5699 * Passing a null element will uninstall the keyboard handlers.
5700 *
5701 * @param {HTMLElement} element The element whose events should be captured, or
5702 * null to disable the keyboard.
5703 */
5704hterm.Keyboard.prototype.installKeyboard = function(element) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005705 if (element == this.keyboardElement_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005706
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005707 if (element && this.keyboardElement_) this.installKeyboard(null);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005708
5709 for (var i = 0; i < this.handlers_.length; i++) {
5710 var handler = this.handlers_[i];
5711 if (element) {
5712 element.addEventListener(handler[0], handler[1]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005713 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005714 this.keyboardElement_.removeEventListener(handler[0], handler[1]);
5715 }
5716 }
5717
5718 this.keyboardElement_ = element;
5719};
5720
5721/**
5722 * Disable keyboard event capture.
5723 *
5724 * This will allow the browser to process key events normally.
5725 */
5726hterm.Keyboard.prototype.uninstallKeyboard = function() {
5727 this.installKeyboard(null);
5728};
5729
5730/**
5731 * Handle onTextInput events.
5732 *
5733 * We're not actually supposed to get these, but we do on the Mac in the case
5734 * where a third party app sends synthetic keystrokes to Chrome.
5735 */
5736hterm.Keyboard.prototype.onTextInput_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005737 if (!e.data) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005738
5739 e.data.split('').forEach(this.terminal.onVTKeystroke.bind(this.terminal));
5740};
5741
5742/**
5743 * Handle onKeyPress events.
5744 */
5745hterm.Keyboard.prototype.onKeyPress_ = function(e) {
5746 var code;
5747
5748 var key = String.fromCharCode(e.which);
5749 var lowerKey = key.toLowerCase();
5750 if ((e.ctrlKey || e.metaKey) && (lowerKey == 'c' || lowerKey == 'v')) {
5751 // On FF the key press (not key down) event gets fired for copy/paste.
5752 // Let it fall through for the default browser behavior.
5753 return;
5754 }
5755
5756 if (e.altKey && this.altSendsWhat == 'browser-key' && e.charCode == 0) {
5757 // If we got here because we were expecting the browser to handle an
5758 // alt sequence but it didn't do it, then we might be on an OS without
5759 // an enabled IME system. In that case we fall back to xterm-like
5760 // behavior.
5761 //
5762 // This happens here only as a fallback. Typically these platforms should
5763 // set altSendsWhat to either 'escape' or '8-bit'.
5764 var ch = String.fromCharCode(e.keyCode);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005765 if (!e.shiftKey) ch = ch.toLowerCase();
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005766 code = ch.charCodeAt(0) + 128;
5767
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005768 } else if (e.charCode >= 32) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005769 ch = e.charCode;
5770 }
5771
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005772 if (ch) this.terminal.onVTKeystroke(String.fromCharCode(ch));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005773
5774 e.preventDefault();
5775 e.stopPropagation();
5776};
5777
5778/**
5779 * Prevent default handling for non-ctrl-shifted event.
5780 *
5781 * When combined with Chrome permission 'app.window.fullscreen.overrideEsc',
5782 * and called for both key down and key up events,
5783 * the ESC key remains usable within fullscreen Chrome app windows.
5784 */
5785hterm.Keyboard.prototype.preventChromeAppNonCtrlShiftDefault_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005786 if (!window.chrome || !window.chrome.app || !window.chrome.app.window) return;
5787 if (!e.ctrlKey || !e.shiftKey) e.preventDefault();
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005788};
5789
5790hterm.Keyboard.prototype.onFocusOut_ = function(e) {
5791 this.altKeyPressed = 0;
5792};
5793
5794hterm.Keyboard.prototype.onKeyUp_ = function(e) {
5795 if (e.keyCode == 18)
5796 this.altKeyPressed = this.altKeyPressed & ~(1 << (e.location - 1));
5797
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005798 if (e.keyCode == 27) this.preventChromeAppNonCtrlShiftDefault_(e);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005799};
5800
5801/**
5802 * Handle onKeyDown events.
5803 */
5804hterm.Keyboard.prototype.onKeyDown_ = function(e) {
5805 if (e.keyCode == 18)
5806 this.altKeyPressed = this.altKeyPressed | (1 << (e.location - 1));
5807
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005808 if (e.keyCode == 27) this.preventChromeAppNonCtrlShiftDefault_(e);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005809
5810 var keyDef = this.keyMap.keyDefs[e.keyCode];
5811 if (!keyDef) {
5812 console.warn('No definition for keyCode: ' + e.keyCode);
5813 return;
5814 }
5815
5816 // The type of action we're going to use.
5817 var resolvedActionType = null;
5818
5819 var self = this;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005820
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005821 function getAction(name) {
5822 // Get the key action for the given action name. If the action is a
5823 // function, dispatch it. If the action defers to the normal action,
5824 // resolve that instead.
5825
5826 resolvedActionType = name;
5827
5828 var action = keyDef[name];
5829 if (typeof action == 'function')
5830 action = action.apply(self.keyMap, [e, keyDef]);
5831
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005832 if (action === DEFAULT && name != 'normal') action = getAction('normal');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005833
5834 return action;
5835 }
5836
5837 // Note that we use the triple-equals ('===') operator to test equality for
5838 // these constants, in order to distinguish usage of the constant from usage
5839 // of a literal string that happens to contain the same bytes.
5840 var CANCEL = hterm.Keyboard.KeyActions.CANCEL;
5841 var DEFAULT = hterm.Keyboard.KeyActions.DEFAULT;
5842 var PASS = hterm.Keyboard.KeyActions.PASS;
5843 var STRIP = hterm.Keyboard.KeyActions.STRIP;
5844
5845 var control = e.ctrlKey;
5846 var alt = this.altIsMeta ? false : e.altKey;
5847 var meta = this.altIsMeta ? (e.altKey || e.metaKey) : e.metaKey;
5848
5849 // In the key-map, we surround the keyCap for non-printables in "[...]"
5850 var isPrintable = !(/^\[\w+\]$/.test(keyDef.keyCap));
5851
5852 switch (this.altGrMode) {
5853 case 'ctrl-alt':
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005854 if (isPrintable && control && alt) {
5855 // ctrl-alt-printable means altGr. We clear out the control and
5856 // alt modifiers and wait to see the charCode in the keydown event.
5857 control = false;
5858 alt = false;
5859 }
5860 break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005861
5862 case 'right-alt':
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005863 if (isPrintable && (this.terminal.keyboard.altKeyPressed & 2)) {
5864 control = false;
5865 alt = false;
5866 }
5867 break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005868
5869 case 'left-alt':
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005870 if (isPrintable && (this.terminal.keyboard.altKeyPressed & 1)) {
5871 control = false;
5872 alt = false;
5873 }
5874 break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005875 }
5876
5877 var action;
5878
5879 if (control) {
5880 action = getAction('control');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005881 } else if (alt) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005882 action = getAction('alt');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005883 } else if (meta) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005884 action = getAction('meta');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005885 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005886 action = getAction('normal');
5887 }
5888
5889 // If e.maskShiftKey was set (during getAction) it means the shift key is
5890 // already accounted for in the action, and we should not act on it any
5891 // further. This is currently only used for Ctrl-Shift-Tab, which should send
5892 // "CSI Z", not "CSI 1 ; 2 Z".
5893 var shift = !e.maskShiftKey && e.shiftKey;
5894
5895 var keyDown = {
5896 keyCode: e.keyCode,
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005897 shift: e.shiftKey, // not `var shift` from above.
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005898 ctrl: control,
5899 alt: alt,
5900 meta: meta
5901 };
5902
5903 var binding = this.bindings.getBinding(keyDown);
5904
5905 if (binding) {
5906 // Clear out the modifier bits so we don't try to munge the sequence
5907 // further.
5908 shift = control = alt = meta = false;
5909 resolvedActionType = 'normal';
5910 action = binding.action;
5911
5912 if (typeof action == 'function')
5913 action = action.call(this, this.terminal, keyDown);
5914 }
5915
5916 if (alt && this.altSendsWhat == 'browser-key' && action == DEFAULT) {
5917 // When altSendsWhat is 'browser-key', we wait for the keypress event.
5918 // In keypress, the browser should have set the event.charCode to the
5919 // appropriate character.
5920 // TODO(rginda): Character compositions will need some black magic.
5921 action = PASS;
5922 }
5923
5924 if (action === PASS || (action === DEFAULT && !(control || alt || meta))) {
5925 // If this key is supposed to be handled by the browser, or it is an
5926 // unmodified key with the default action, then exit this event handler.
5927 // If it's an unmodified key, it'll be handled in onKeyPress where we
5928 // can tell for sure which ASCII code to insert.
5929 //
5930 // This block needs to come before the STRIP test, otherwise we'll strip
5931 // the modifier and think it's ok to let the browser handle the keypress.
5932 // The browser won't know we're trying to ignore the modifiers and might
5933 // perform some default action.
5934 return;
5935 }
5936
5937 if (action === STRIP) {
5938 alt = control = false;
5939 action = keyDef.normal;
5940 if (typeof action == 'function')
5941 action = action.apply(this.keyMap, [e, keyDef]);
5942
5943 if (action == DEFAULT && keyDef.keyCap.length == 2)
5944 action = keyDef.keyCap.substr((shift ? 1 : 0), 1);
5945 }
5946
5947 e.preventDefault();
5948 e.stopPropagation();
5949
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005950 if (action === CANCEL) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005951
5952 if (action !== DEFAULT && typeof action != 'string') {
5953 console.warn('Invalid action: ' + JSON.stringify(action));
5954 return;
5955 }
5956
5957 // Strip the modifier that is associated with the action, since we assume that
5958 // modifier has already been accounted for in the action.
5959 if (resolvedActionType == 'control') {
5960 control = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005961 } else if (resolvedActionType == 'alt') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005962 alt = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005963 } else if (resolvedActionType == 'meta') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005964 meta = false;
5965 }
5966
5967 if (action.substr(0, 2) == '\x1b[' && (alt || control || shift)) {
5968 // The action is an escape sequence that and it was triggered in the
5969 // presence of a keyboard modifier, we may need to alter the action to
5970 // include the modifier before sending it.
5971
5972 var mod;
5973
5974 if (shift && !(alt || control)) {
5975 mod = ';2';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005976 } else if (alt && !(shift || control)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005977 mod = ';3';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005978 } else if (shift && alt && !control) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005979 mod = ';4';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005980 } else if (control && !(shift || alt)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005981 mod = ';5';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005982 } else if (shift && control && !alt) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005983 mod = ';6';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005984 } else if (alt && control && !shift) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005985 mod = ';7';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005986 } else if (shift && alt && control) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005987 mod = ';8';
5988 }
5989
5990 if (action.length == 3) {
5991 // Some of the CSI sequences have zero parameters unless modified.
5992 action = '\x1b[1' + mod + action.substr(2, 1);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005993 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005994 // Others always have at least one parameter.
5995 action = action.substr(0, action.length - 1) + mod +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005996 action.substr(action.length - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005997 }
5998
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005999 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006000 if (action === DEFAULT) {
6001 action = keyDef.keyCap.substr((shift ? 1 : 0), 1);
6002
6003 if (control) {
6004 var unshifted = keyDef.keyCap.substr(0, 1);
6005 var code = unshifted.charCodeAt(0);
6006 if (code >= 64 && code <= 95) {
6007 action = String.fromCharCode(code - 64);
6008 }
6009 }
6010 }
6011
6012 if (alt && this.altSendsWhat == '8-bit' && action.length == 1) {
6013 var code = action.charCodeAt(0) + 128;
6014 action = String.fromCharCode(code);
6015 }
6016
6017 // We respect alt/metaSendsEscape even if the keymap action was a literal
6018 // string. Otherwise, every overridden alt/meta action would have to
6019 // check alt/metaSendsEscape.
6020 if ((alt && this.altSendsWhat == 'escape') ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006021 (meta && this.metaSendsEscape)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006022 action = '\x1b' + action;
6023 }
6024 }
6025
6026 this.terminal.onVTKeystroke(action);
6027};
6028// SOURCE FILE: hterm/js/hterm_keyboard_bindings.js
6029// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
6030// Use of this source code is governed by a BSD-style license that can be
6031// found in the LICENSE file.
6032
6033'use strict';
6034
6035/**
6036 * A mapping from hterm.Keyboard.KeyPattern to an action.
6037 *
6038 * TODO(rginda): For now this bindings code is only used for user overrides.
6039 * hterm.Keyboard.KeyMap still handles all of the built-in key mappings.
6040 * It'd be nice if we migrated that over to be hterm.Keyboard.Bindings based.
6041 */
6042hterm.Keyboard.Bindings = function() {
6043 this.bindings_ = {};
6044};
6045
6046/**
6047 * Remove all bindings.
6048 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006049hterm.Keyboard.Bindings.prototype.clear = function() {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006050 this.bindings_ = {};
6051};
6052
6053/**
6054 * Add a new binding.
6055 *
6056 * Internal API that assumes parsed objects as inputs.
6057 * See the public addBinding for more details.
6058 *
6059 * @param {hterm.Keyboard.KeyPattern} keyPattern
6060 * @param {string|function|hterm.Keyboard.KeyAction} action
6061 */
6062hterm.Keyboard.Bindings.prototype.addBinding_ = function(keyPattern, action) {
6063 var binding = null;
6064 var list = this.bindings_[keyPattern.keyCode];
6065 if (list) {
6066 for (var i = 0; i < list.length; i++) {
6067 if (list[i].keyPattern.matchKeyPattern(keyPattern)) {
6068 binding = list[i];
6069 break;
6070 }
6071 }
6072 }
6073
6074 if (binding) {
6075 binding.action = action;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006076 } else {
6077 binding = {keyPattern: keyPattern, action: action};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006078
6079 if (!list) {
6080 this.bindings_[keyPattern.keyCode] = [binding];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006081 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006082 this.bindings_[keyPattern.keyCode].push(binding);
6083
6084 list.sort(function(a, b) {
6085 return hterm.Keyboard.KeyPattern.sortCompare(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006086 a.keyPattern, b.keyPattern);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006087 });
6088 }
6089 }
6090};
6091
6092/**
6093 * Add a new binding.
6094 *
6095 * If a binding for the keyPattern already exists it will be overridden.
6096 *
6097 * More specific keyPatterns take precedence over those with wildcards. Given
6098 * bindings for "Ctrl-A" and "Ctrl-*-A", and a "Ctrl-A" keydown, the "Ctrl-A"
6099 * binding will match even if "Ctrl-*-A" was created last.
6100 *
6101 * If action is a string, it will be passed through hterm.Parser.parseKeyAction.
6102 *
6103 * For example:
6104 * // Will replace Ctrl-P keystrokes with the string "hiya!".
6105 * addBinding('Ctrl-P', "'hiya!'");
6106 * // Will cancel the keystroke entirely (make it do nothing).
6107 * addBinding('Alt-D', hterm.Keyboard.KeyActions.CANCEL);
6108 * // Will execute the code and return the action.
6109 * addBinding('Ctrl-T', function() {
6110 * console.log('Got a T!');
6111 * return hterm.Keyboard.KeyActions.PASS;
6112 * });
6113 *
6114 * @param {string|hterm.Keyboard.KeyPattern} keyPattern
6115 * @param {string|function|hterm.Keyboard.KeyAction} action
6116 */
6117hterm.Keyboard.Bindings.prototype.addBinding = function(key, action) {
6118 // If we're given a hterm.Keyboard.KeyPattern object, pass it down.
6119 if (typeof key != 'string') {
6120 this.addBinding_(key, action);
6121 return;
6122 }
6123
6124 // Here we treat key as a string.
6125 var p = new hterm.Parser();
6126
6127 p.reset(key);
6128 var sequence;
6129
6130 try {
6131 sequence = p.parseKeySequence();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006132 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006133 console.error(ex);
6134 return;
6135 }
6136
6137 if (!p.isComplete()) {
6138 console.error(p.error('Expected end of sequence: ' + sequence));
6139 return;
6140 }
6141
6142 // If action is a string, parse it. Otherwise assume it's callable.
6143 if (typeof action == 'string') {
6144 p.reset(action);
6145 try {
6146 action = p.parseKeyAction();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006147 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006148 console.error(ex);
6149 return;
6150 }
6151 }
6152
6153 if (!p.isComplete()) {
6154 console.error(p.error('Expected end of sequence: ' + sequence));
6155 return;
6156 }
6157
6158 this.addBinding_(new hterm.Keyboard.KeyPattern(sequence), action);
6159};
6160
6161/**
6162 * Add multiple bindings at a time using a map of {string: string, ...}
6163 *
6164 * This uses hterm.Parser to parse the maps key into KeyPatterns, and the
6165 * map values into {string|function|KeyAction}.
6166 *
6167 * For example:
6168 * {
6169 * // Will replace Ctrl-P keystrokes with the string "hiya!".
6170 * 'Ctrl-P': "'hiya!'",
6171 * // Will cancel the keystroke entirely (make it do nothing).
6172 * 'Alt-D': hterm.Keyboard.KeyActions.CANCEL,
6173 * }
6174 *
6175 * @param {Object} map
6176 */
6177hterm.Keyboard.Bindings.prototype.addBindings = function(map) {
6178 for (var key in map) {
6179 this.addBinding(key, map[key]);
6180 }
6181};
6182
6183/**
6184 * Return the binding that is the best match for the given keyDown record,
6185 * or null if there is no match.
6186 *
6187 * @param {Object} keyDown An object with a keyCode property and zero or
6188 * more boolean properties representing key modifiers. These property names
6189 * must match those defined in hterm.Keyboard.KeyPattern.modifiers.
6190 */
6191hterm.Keyboard.Bindings.prototype.getBinding = function(keyDown) {
6192 var list = this.bindings_[keyDown.keyCode];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006193 if (!list) return null;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006194
6195 for (var i = 0; i < list.length; i++) {
6196 var binding = list[i];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006197 if (binding.keyPattern.matchKeyDown(keyDown)) return binding;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006198 }
6199
6200 return null;
6201};
6202// SOURCE FILE: hterm/js/hterm_keyboard_keymap.js
6203// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
6204// Use of this source code is governed by a BSD-style license that can be
6205// found in the LICENSE file.
6206
6207'use strict';
6208
6209lib.rtdep('hterm.Keyboard.KeyActions');
6210
6211/**
6212 * The default key map for hterm.
6213 *
6214 * Contains a mapping of keyCodes to keyDefs (aka key definitions). The key
6215 * definition tells the hterm.Keyboard class how to handle keycodes.
6216 *
6217 * This should work for most cases, as the printable characters get handled
6218 * in the keypress event. In that case, even if the keycap is wrong in the
6219 * key map, the correct character should be sent.
6220 *
6221 * Different layouts, such as Dvorak should work with this keymap, as those
6222 * layouts typically move keycodes around on the keyboard without disturbing
6223 * the actual keycaps.
6224 *
6225 * There may be issues with control keys on non-US keyboards or with keyboards
6226 * that very significantly from the expectations here, in which case we may
6227 * have to invent new key maps.
6228 *
6229 * The sequences defined in this key map come from [XTERM] as referenced in
6230 * vt.js, starting with the section titled "Alt and Meta Keys".
6231 */
6232hterm.Keyboard.KeyMap = function(keyboard) {
6233 this.keyboard = keyboard;
6234 this.keyDefs = {};
6235 this.reset();
6236};
6237
6238/**
6239 * Add a single key definition.
6240 *
6241 * The definition is a hash containing the following keys: 'keyCap', 'normal',
6242 * 'control', and 'alt'.
6243 *
6244 * - keyCap is a string identifying the key. For printable
6245 * keys, the key cap should be exactly two characters, starting with the
6246 * unshifted version. For example, 'aA', 'bB', '1!' and '=+'. For
6247 * non-printable the key cap should be surrounded in square braces, as in
6248 * '[INS]', '[LEFT]'. By convention, non-printable keycaps are in uppercase
6249 * but this is not a strict requirement.
6250 *
6251 * - Normal is the action that should be performed when they key is pressed
6252 * in the absence of any modifier. See below for the supported actions.
6253 *
6254 * - Control is the action that should be performed when they key is pressed
6255 * along with the control modifier. See below for the supported actions.
6256 *
6257 * - Alt is the action that should be performed when they key is pressed
6258 * along with the alt modifier. See below for the supported actions.
6259 *
6260 * - Meta is the action that should be performed when they key is pressed
6261 * along with the meta modifier. See below for the supported actions.
6262 *
6263 * Actions can be one of the hterm.Keyboard.KeyActions as documented below,
6264 * a literal string, or an array. If the action is a literal string then
6265 * the string is sent directly to the host. If the action is an array it
6266 * is taken to be an escape sequence that may be altered by modifier keys.
6267 * The second-to-last element of the array will be overwritten with the
6268 * state of the modifier keys, as specified in the final table of "PC-Style
6269 * Function Keys" from [XTERM].
6270 */
6271hterm.Keyboard.KeyMap.prototype.addKeyDef = function(keyCode, def) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006272 if (keyCode in this.keyDefs) console.warn('Duplicate keyCode: ' + keyCode);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006273
6274 this.keyDefs[keyCode] = def;
6275};
6276
6277/**
6278 * Add multiple key definitions in a single call.
6279 *
6280 * This function takes the key definitions as variable argument list. Each
6281 * argument is the key definition specified as an array.
6282 *
6283 * (If the function took everything as one big hash we couldn't detect
6284 * duplicates, and there would be a lot more typing involved.)
6285 *
6286 * Each key definition should have 6 elements: (keyCode, keyCap, normal action,
6287 * control action, alt action and meta action). See KeyMap.addKeyDef for the
6288 * meaning of these elements.
6289 */
6290hterm.Keyboard.KeyMap.prototype.addKeyDefs = function(var_args) {
6291 for (var i = 0; i < arguments.length; i++) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006292 this.addKeyDef(arguments[i][0], {
6293 keyCap: arguments[i][1],
6294 normal: arguments[i][2],
6295 control: arguments[i][3],
6296 alt: arguments[i][4],
6297 meta: arguments[i][5]
6298 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006299 }
6300};
6301
6302/**
6303 * Set up the default state for this keymap.
6304 */
6305hterm.Keyboard.KeyMap.prototype.reset = function() {
6306 this.keyDefs = {};
6307
6308 var self = this;
6309
6310 // This function is used by the "macro" functions below. It makes it
6311 // possible to use the call() macro as an argument to any other macro.
6312 function resolve(action, e, k) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006313 if (typeof action == 'function') return action.apply(self, [e, k]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006314
6315 return action;
6316 }
6317
6318 // If not application keypad a, else b. The keys that care about
6319 // application keypad ignore it when the key is modified.
6320 function ak(a, b) {
6321 return function(e, k) {
6322 var action = (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006323 !self.keyboard.applicationKeypad) ?
6324 a :
6325 b;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006326 return resolve(action, e, k);
6327 };
6328 }
6329
6330 // If mod or not application cursor a, else b. The keys that care about
6331 // application cursor ignore it when the key is modified.
6332 function ac(a, b) {
6333 return function(e, k) {
6334 var action = (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006335 !self.keyboard.applicationCursor) ?
6336 a :
6337 b;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006338 return resolve(action, e, k);
6339 };
6340 }
6341
6342 // If not backspace-sends-backspace keypad a, else b.
6343 function bs(a, b) {
6344 return function(e, k) {
6345 var action = !self.keyboard.backspaceSendsBackspace ? a : b;
6346 return resolve(action, e, k);
6347 };
6348 }
6349
6350 // If not e.shiftKey a, else b.
6351 function sh(a, b) {
6352 return function(e, k) {
6353 var action = !e.shiftKey ? a : b;
6354 e.maskShiftKey = true;
6355 return resolve(action, e, k);
6356 };
6357 }
6358
6359 // If not e.altKey a, else b.
6360 function alt(a, b) {
6361 return function(e, k) {
6362 var action = !e.altKey ? a : b;
6363 return resolve(action, e, k);
6364 };
6365 }
6366
6367 // If no modifiers a, else b.
6368 function mod(a, b) {
6369 return function(e, k) {
6370 var action = !(e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) ? a : b;
6371 return resolve(action, e, k);
6372 };
6373 }
6374
6375 // Compute a control character for a given character.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006376 function ctl(ch) {
6377 return String.fromCharCode(ch.charCodeAt(0) - 64);
6378 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006379
6380 // Call a method on the keymap instance.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006381 function c(m) {
6382 return function(e, k) {
6383 return this[m](e, k);
6384 }
6385 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006386
6387 // Ignore if not trapping media keys.
6388 function med(fn) {
6389 return function(e, k) {
6390 if (!self.keyboard.mediaKeysAreFKeys) {
6391 // Block Back, Forward, and Reload keys to avoid navigating away from
6392 // the current page.
6393 return (e.keyCode == 166 || e.keyCode == 167 || e.keyCode == 168) ?
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006394 hterm.Keyboard.KeyActions.CANCEL :
6395 hterm.Keyboard.KeyActions.PASS;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006396 }
6397 return resolve(fn, e, k);
6398 };
6399 }
6400
6401 var ESC = '\x1b';
6402 var CSI = '\x1b[';
6403 var SS3 = '\x1bO';
6404
6405 var CANCEL = hterm.Keyboard.KeyActions.CANCEL;
6406 var DEFAULT = hterm.Keyboard.KeyActions.DEFAULT;
6407 var PASS = hterm.Keyboard.KeyActions.PASS;
6408 var STRIP = hterm.Keyboard.KeyActions.STRIP;
6409
6410 this.addKeyDefs(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006411 // These fields are: [keycode, keycap, normal, control, alt, meta]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006412
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006413 // The browser sends the keycode 0 for some keys. We'll just assume it's
6414 // going to do the right thing by default for those keys.
6415 [0, '[UNKNOWN]', PASS, PASS, PASS, PASS],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006416
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006417 // First row.
6418 [27, '[ESC]', ESC, DEFAULT, DEFAULT, DEFAULT],
6419 [112, '[F1]', mod(SS3 + 'P', CSI + 'P'), DEFAULT, CSI + '23~', DEFAULT],
6420 [113, '[F2]', mod(SS3 + 'Q', CSI + 'Q'), DEFAULT, CSI + '24~', DEFAULT],
6421 [114, '[F3]', mod(SS3 + 'R', CSI + 'R'), DEFAULT, CSI + '25~', DEFAULT],
6422 [115, '[F4]', mod(SS3 + 'S', CSI + 'S'), DEFAULT, CSI + '26~', DEFAULT],
6423 [116, '[F5]', CSI + '15~', DEFAULT, CSI + '28~', DEFAULT],
6424 [117, '[F6]', CSI + '17~', DEFAULT, CSI + '29~', DEFAULT],
6425 [118, '[F7]', CSI + '18~', DEFAULT, CSI + '31~', DEFAULT],
6426 [119, '[F8]', CSI + '19~', DEFAULT, CSI + '32~', DEFAULT],
6427 [120, '[F9]', CSI + '20~', DEFAULT, CSI + '33~', DEFAULT],
6428 [121, '[F10]', CSI + '21~', DEFAULT, CSI + '34~', DEFAULT],
6429 [122, '[F11]', CSI + '23~', DEFAULT, CSI + '42~', DEFAULT],
6430 [123, '[F12]', CSI + '24~', DEFAULT, CSI + '43~', DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006431
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006432 // Second row.
6433 [192, '`~', DEFAULT, sh(ctl('@'), ctl('^')), DEFAULT, PASS],
6434 [49, '1!', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6435 [50, '2@', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6436 [51, '3#', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6437 [52, '4$', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6438 [53, '5%', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6439 [54, '6^', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6440 [55, '7&', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6441 [56, '8*', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6442 [57, '9(', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6443 [
6444 48, '0)', DEFAULT, c('onPlusMinusZero_'), c('onAltNum_'),
6445 c('onPlusMinusZero_')
6446 ],
6447 [
6448 189, '-_', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6449 c('onPlusMinusZero_')
6450 ],
6451 [
6452 187, '=+', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6453 c('onPlusMinusZero_')
6454 ],
6455 // Firefox -_ and =+
6456 [
6457 173, '-_', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6458 c('onPlusMinusZero_')
6459 ],
6460 [
6461 61, '=+', DEFAULT, c('onPlusMinusZero_'), DEFAULT, c('onPlusMinusZero_')
6462 ],
6463 // Firefox Italian +*
6464 [
6465 171, '+*', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6466 c('onPlusMinusZero_')
6467 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006468
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006469 [8, '[BKSP]', bs('\x7f', '\b'), bs('\b', '\x7f'), DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006470
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006471 // Third row.
6472 [9, '[TAB]', sh('\t', CSI + 'Z'), STRIP, PASS, DEFAULT],
6473 [81, 'qQ', DEFAULT, ctl('Q'), DEFAULT, DEFAULT],
6474 [87, 'wW', DEFAULT, ctl('W'), DEFAULT, DEFAULT],
6475 [69, 'eE', DEFAULT, ctl('E'), DEFAULT, DEFAULT],
6476 [82, 'rR', DEFAULT, ctl('R'), DEFAULT, DEFAULT],
6477 [84, 'tT', DEFAULT, ctl('T'), DEFAULT, DEFAULT],
6478 [89, 'yY', DEFAULT, ctl('Y'), DEFAULT, DEFAULT],
6479 [85, 'uU', DEFAULT, ctl('U'), DEFAULT, DEFAULT],
6480 [73, 'iI', DEFAULT, ctl('I'), DEFAULT, DEFAULT],
6481 [79, 'oO', DEFAULT, ctl('O'), DEFAULT, DEFAULT],
6482 [80, 'pP', DEFAULT, ctl('P'), DEFAULT, DEFAULT],
6483 [219, '[{', DEFAULT, ctl('['), DEFAULT, DEFAULT],
6484 [221, ']}', DEFAULT, ctl(']'), DEFAULT, DEFAULT],
6485 [220, '\\|', DEFAULT, ctl('\\'), DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006486
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006487 // Fourth row. (We let Ctrl-Shift-J pass for Chrome DevTools.)
6488 [20, '[CAPS]', PASS, PASS, PASS, DEFAULT],
6489 [65, 'aA', DEFAULT, ctl('A'), DEFAULT, DEFAULT],
6490 [83, 'sS', DEFAULT, ctl('S'), DEFAULT, DEFAULT],
6491 [68, 'dD', DEFAULT, ctl('D'), DEFAULT, DEFAULT],
6492 [70, 'fF', DEFAULT, ctl('F'), DEFAULT, DEFAULT],
6493 [71, 'gG', DEFAULT, ctl('G'), DEFAULT, DEFAULT],
6494 [72, 'hH', DEFAULT, ctl('H'), DEFAULT, DEFAULT],
6495 [74, 'jJ', DEFAULT, sh(ctl('J'), PASS), DEFAULT, DEFAULT],
6496 [75, 'kK', DEFAULT, sh(ctl('K'), c('onClear_')), DEFAULT, DEFAULT],
6497 [76, 'lL', DEFAULT, sh(ctl('L'), PASS), DEFAULT, DEFAULT],
6498 [186, ';:', DEFAULT, STRIP, DEFAULT, DEFAULT],
6499 [222, '\'"', DEFAULT, STRIP, DEFAULT, DEFAULT],
6500 [13, '[ENTER]', '\r', CANCEL, CANCEL, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006501
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006502 // Fifth row. This includes the copy/paste shortcuts. On some
6503 // platforms it's Ctrl-C/V, on others it's Meta-C/V. We assume either
6504 // Ctrl-C/Meta-C should pass to the browser when there is a selection,
6505 // and Ctrl-Shift-V/Meta-*-V should always pass to the browser (since
6506 // these seem to be recognized as paste too).
6507 [16, '[SHIFT]', PASS, PASS, PASS, DEFAULT],
6508 [90, 'zZ', DEFAULT, ctl('Z'), DEFAULT, DEFAULT],
6509 [88, 'xX', DEFAULT, ctl('X'), DEFAULT, DEFAULT],
6510 [67, 'cC', DEFAULT, c('onCtrlC_'), DEFAULT, c('onMetaC_')],
6511 [86, 'vV', DEFAULT, c('onCtrlV_'), DEFAULT, c('onMetaV_')],
6512 [66, 'bB', DEFAULT, sh(ctl('B'), PASS), DEFAULT, sh(DEFAULT, PASS)],
6513 [78, 'nN', DEFAULT, c('onCtrlN_'), DEFAULT, c('onMetaN_')],
6514 [77, 'mM', DEFAULT, ctl('M'), DEFAULT, DEFAULT],
6515 [188, ',<', DEFAULT, alt(STRIP, PASS), DEFAULT, DEFAULT],
6516 [190, '.>', DEFAULT, alt(STRIP, PASS), DEFAULT, DEFAULT],
6517 [191, '/?', DEFAULT, sh(ctl('_'), ctl('?')), DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006518
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006519 // Sixth and final row.
6520 [17, '[CTRL]', PASS, PASS, PASS, PASS],
6521 [18, '[ALT]', PASS, PASS, PASS, PASS],
6522 [91, '[LAPL]', PASS, PASS, PASS, PASS],
6523 [32, ' ', DEFAULT, ctl('@'), DEFAULT, DEFAULT],
6524 [92, '[RAPL]', PASS, PASS, PASS, PASS],
6525 [93, '[RMENU]', PASS, PASS, PASS, PASS],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006526
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006527 // These things.
6528 [42, '[PRTSCR]', PASS, PASS, PASS, PASS],
6529 [145, '[SCRLK]', PASS, PASS, PASS, PASS],
6530 [19, '[BREAK]', PASS, PASS, PASS, PASS],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006531
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006532 // The block of six keys above the arrows.
6533 [45, '[INSERT]', c('onKeyInsert_'), DEFAULT, DEFAULT, DEFAULT],
6534 [36, '[HOME]', c('onKeyHome_'), DEFAULT, DEFAULT, DEFAULT],
6535 [33, '[PGUP]', c('onKeyPageUp_'), DEFAULT, DEFAULT, DEFAULT],
6536 [46, '[DEL]', c('onKeyDel_'), DEFAULT, DEFAULT, DEFAULT],
6537 [35, '[END]', c('onKeyEnd_'), DEFAULT, DEFAULT, DEFAULT],
6538 [34, '[PGDOWN]', c('onKeyPageDown_'), DEFAULT, DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006539
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006540 // Arrow keys. When unmodified they respect the application cursor state,
6541 // otherwise they always send the CSI codes.
6542 [38, '[UP]', ac(CSI + 'A', SS3 + 'A'), DEFAULT, DEFAULT, DEFAULT],
6543 [40, '[DOWN]', ac(CSI + 'B', SS3 + 'B'), DEFAULT, DEFAULT, DEFAULT],
6544 [39, '[RIGHT]', ac(CSI + 'C', SS3 + 'C'), DEFAULT, DEFAULT, DEFAULT],
6545 [37, '[LEFT]', ac(CSI + 'D', SS3 + 'D'), DEFAULT, DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006546
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006547 [144, '[NUMLOCK]', PASS, PASS, PASS, PASS],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006548
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006549 // With numlock off, the keypad generates the same key codes as the arrows
6550 // and 'block of six' for some keys, and null key codes for the rest.
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006551
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006552 // Keypad with numlock on generates unique key codes...
6553 [96, '[KP0]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6554 [97, '[KP1]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6555 [98, '[KP2]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6556 [99, '[KP3]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6557 [100, '[KP4]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6558 [101, '[KP5]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6559 [102, '[KP6]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6560 [103, '[KP7]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6561 [104, '[KP8]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6562 [105, '[KP9]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6563 [
6564 107, '[KP+]', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6565 c('onPlusMinusZero_')
6566 ],
6567 [
6568 109, '[KP-]', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6569 c('onPlusMinusZero_')
6570 ],
6571 [106, '[KP*]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6572 [111, '[KP/]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6573 [110, '[KP.]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006574
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006575 // Chrome OS keyboard top row.
6576 [
6577 166, '[BACK]', med(mod(SS3 + 'P', CSI + 'P')), DEFAULT, CSI + '23~',
6578 DEFAULT
6579 ],
6580 [
6581 167, '[FWD]', med(mod(SS3 + 'Q', CSI + 'Q')), DEFAULT, CSI + '24~',
6582 DEFAULT
6583 ],
6584 [
6585 168, '[RELOAD]', med(mod(SS3 + 'R', CSI + 'R')), DEFAULT, CSI + '25~',
6586 DEFAULT
6587 ],
6588 [
6589 183, '[FSCR]', med(mod(SS3 + 'S', CSI + 'S')), DEFAULT, CSI + '26~',
6590 DEFAULT
6591 ],
6592 [182, '[WINS]', med(CSI + '15~'), DEFAULT, CSI + '28~', DEFAULT],
6593 [216, '[BRIT-]', med(CSI + '17~'), DEFAULT, CSI + '29~', DEFAULT],
6594 [217, '[BRIT+]', med(CSI + '18~'), DEFAULT, CSI + '31~', DEFAULT]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006595
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006596 // 173 [MUTE], 174 [VOL-] and 175 [VOL+] are trapped by the Chrome OS
6597 // window manager, so we'll never see them. Note that 173 is also
6598 // Firefox's -_ keycode.
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006599 );
6600};
6601
6602/**
6603 * Either allow the paste or send a key sequence.
6604 */
6605hterm.Keyboard.KeyMap.prototype.onKeyInsert_ = function(e) {
6606 if (this.keyboard.shiftInsertPaste && e.shiftKey)
6607 return hterm.Keyboard.KeyActions.PASS;
6608
6609 return '\x1b[2~';
6610};
6611
6612/**
6613 * Either scroll the scrollback buffer or send a key sequence.
6614 */
6615hterm.Keyboard.KeyMap.prototype.onKeyHome_ = function(e) {
6616 if (!this.keyboard.homeKeysScroll ^ e.shiftKey) {
6617 if ((e.altey || e.ctrlKey || e.shiftKey) ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006618 !this.keyboard.applicationCursor) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006619 return '\x1b[H';
6620 }
6621
6622 return '\x1bOH';
6623 }
6624
6625 this.keyboard.terminal.scrollHome();
6626 return hterm.Keyboard.KeyActions.CANCEL;
6627};
6628
6629/**
6630 * Either scroll the scrollback buffer or send a key sequence.
6631 */
6632hterm.Keyboard.KeyMap.prototype.onKeyEnd_ = function(e) {
6633 if (!this.keyboard.homeKeysScroll ^ e.shiftKey) {
6634 if ((e.altKey || e.ctrlKey || e.shiftKey) ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006635 !this.keyboard.applicationCursor) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006636 return '\x1b[F';
6637 }
6638
6639 return '\x1bOF';
6640 }
6641
6642 this.keyboard.terminal.scrollEnd();
6643 return hterm.Keyboard.KeyActions.CANCEL;
6644};
6645
6646/**
6647 * Either scroll the scrollback buffer or send a key sequence.
6648 */
6649hterm.Keyboard.KeyMap.prototype.onKeyPageUp_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006650 if (!this.keyboard.pageKeysScroll ^ e.shiftKey) return '\x1b[5~';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006651
6652 this.keyboard.terminal.scrollPageUp();
6653 return hterm.Keyboard.KeyActions.CANCEL;
6654};
6655
6656/**
6657 * Either send a true DEL, or sub in meta-backspace.
6658 *
6659 * On Chrome OS, if we know the alt key is down, but we get a DEL event that
6660 * claims that the alt key is not pressed, we know the DEL was a synthetic
6661 * one from a user that hit alt-backspace. Based on a user pref, we can sub
6662 * in meta-backspace in this case.
6663 */
6664hterm.Keyboard.KeyMap.prototype.onKeyDel_ = function(e) {
6665 if (this.keyboard.altBackspaceIsMetaBackspace &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006666 this.keyboard.altKeyPressed && !e.altKey)
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006667 return '\x1b\x7f';
6668 return '\x1b[3~';
6669};
6670
6671/**
6672 * Either scroll the scrollback buffer or send a key sequence.
6673 */
6674hterm.Keyboard.KeyMap.prototype.onKeyPageDown_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006675 if (!this.keyboard.pageKeysScroll ^ e.shiftKey) return '\x1b[6~';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006676
6677 this.keyboard.terminal.scrollPageDown();
6678 return hterm.Keyboard.KeyActions.CANCEL;
6679};
6680
6681/**
6682 * Clear the primary/alternate screens and the scrollback buffer.
6683 */
6684hterm.Keyboard.KeyMap.prototype.onClear_ = function(e, keyDef) {
6685 this.keyboard.terminal.wipeContents();
6686 return hterm.Keyboard.KeyActions.CANCEL;
6687};
6688
6689/**
6690 * Either pass Ctrl-1..9 to the browser or send them to the host.
6691 *
6692 * Note that Ctrl-1 and Ctrl-9 don't actually have special sequences mapped
6693 * to them in xterm or gnome-terminal. The range is really Ctrl-2..8, but
6694 * we handle 1..9 since Chrome treats the whole range special.
6695 */
6696hterm.Keyboard.KeyMap.prototype.onCtrlNum_ = function(e, keyDef) {
6697 // Compute a control character for a given character.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006698 function ctl(ch) {
6699 return String.fromCharCode(ch.charCodeAt(0) - 64);
6700 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006701
6702 if (this.keyboard.terminal.passCtrlNumber && !e.shiftKey)
6703 return hterm.Keyboard.KeyActions.PASS;
6704
6705 switch (keyDef.keyCap.substr(0, 1)) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006706 case '1':
6707 return '1';
6708 case '2':
6709 return ctl('@');
6710 case '3':
6711 return ctl('[');
6712 case '4':
6713 return ctl('\\');
6714 case '5':
6715 return ctl(']');
6716 case '6':
6717 return ctl('^');
6718 case '7':
6719 return ctl('_');
6720 case '8':
6721 return '\x7f';
6722 case '9':
6723 return '9';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006724 }
6725};
6726
6727/**
6728 * Either pass Alt-1..9 to the browser or send them to the host.
6729 */
6730hterm.Keyboard.KeyMap.prototype.onAltNum_ = function(e, keyDef) {
6731 if (this.keyboard.terminal.passAltNumber && !e.shiftKey)
6732 return hterm.Keyboard.KeyActions.PASS;
6733
6734 return hterm.Keyboard.KeyActions.DEFAULT;
6735};
6736
6737/**
6738 * Either pass Meta-1..9 to the browser or send them to the host.
6739 */
6740hterm.Keyboard.KeyMap.prototype.onMetaNum_ = function(e, keyDef) {
6741 if (this.keyboard.terminal.passMetaNumber && !e.shiftKey)
6742 return hterm.Keyboard.KeyActions.PASS;
6743
6744 return hterm.Keyboard.KeyActions.DEFAULT;
6745};
6746
6747/**
6748 * Either send a ^C or interpret the keystroke as a copy command.
6749 */
6750hterm.Keyboard.KeyMap.prototype.onCtrlC_ = function(e, keyDef) {
6751 var selection = this.keyboard.terminal.getDocument().getSelection();
6752
6753 if (!selection.isCollapsed) {
6754 if (this.keyboard.ctrlCCopy && !e.shiftKey) {
6755 // Ctrl-C should copy if there is a selection, send ^C otherwise.
6756 // Perform the copy by letting the browser handle Ctrl-C. On most
6757 // browsers, this is the *only* way to place text on the clipboard from
6758 // the 'drive-by' web.
6759 if (this.keyboard.terminal.clearSelectionAfterCopy) {
6760 setTimeout(selection.collapseToEnd.bind(selection), 50);
6761 }
6762 return hterm.Keyboard.KeyActions.PASS;
6763 }
6764
6765 if (!this.keyboard.ctrlCCopy && e.shiftKey) {
6766 // Ctrl-Shift-C should copy if there is a selection, send ^C otherwise.
6767 // Perform the copy manually. This only works in situations where
6768 // document.execCommand('copy') is allowed.
6769 if (this.keyboard.terminal.clearSelectionAfterCopy) {
6770 setTimeout(selection.collapseToEnd.bind(selection), 50);
6771 }
6772 this.keyboard.terminal.copySelectionToClipboard();
6773 return hterm.Keyboard.KeyActions.CANCEL;
6774 }
6775 }
6776
6777 return '\x03';
6778};
6779
6780/**
6781 * Either send a ^N or open a new window to the same location.
6782 */
6783hterm.Keyboard.KeyMap.prototype.onCtrlN_ = function(e, keyDef) {
6784 if (e.shiftKey) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006785 window.open(
6786 document.location.href, '',
6787 'chrome=no,close=yes,resize=yes,scrollbars=yes,' +
6788 'minimizable=yes,width=' + window.innerWidth +
6789 ',height=' + window.innerHeight);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006790 return hterm.Keyboard.KeyActions.CANCEL;
6791 }
6792
6793 return '\x0e';
6794};
6795
6796/**
6797 * Either send a ^V or allow the browser to interpret the keystroke as a paste
6798 * command.
6799 *
6800 * The default behavior is to paste if the user presses Ctrl-Shift-V, and send
6801 * a ^V if the user presses Ctrl-V. This can be flipped with the
6802 * 'ctrl-v-paste' preference.
6803 */
6804hterm.Keyboard.KeyMap.prototype.onCtrlV_ = function(e, keyDef) {
6805 if ((!e.shiftKey && this.keyboard.ctrlVPaste) ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006806 (e.shiftKey && !this.keyboard.ctrlVPaste)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006807 return hterm.Keyboard.KeyActions.PASS;
6808 }
6809
6810 return '\x16';
6811};
6812
6813/**
6814 * Either the default action or open a new window to the same location.
6815 */
6816hterm.Keyboard.KeyMap.prototype.onMetaN_ = function(e, keyDef) {
6817 if (e.shiftKey) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006818 window.open(
6819 document.location.href, '',
6820 'chrome=no,close=yes,resize=yes,scrollbars=yes,' +
6821 'minimizable=yes,width=' + window.outerWidth +
6822 ',height=' + window.outerHeight);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006823 return hterm.Keyboard.KeyActions.CANCEL;
6824 }
6825
6826 return hterm.Keyboard.KeyActions.DEFAULT;
6827};
6828
6829/**
6830 * Either send a Meta-C or allow the browser to interpret the keystroke as a
6831 * copy command.
6832 *
6833 * If there is no selection, or if the user presses Meta-Shift-C, then we'll
6834 * transmit an '\x1b' (if metaSendsEscape is on) followed by 'c' or 'C'.
6835 *
6836 * If there is a selection, we defer to the browser. In this case we clear out
6837 * the selection so the user knows we heard them, and also to give them a
6838 * chance to send a Meta-C by just hitting the key again.
6839 */
6840hterm.Keyboard.KeyMap.prototype.onMetaC_ = function(e, keyDef) {
6841 var document = this.keyboard.terminal.getDocument();
6842 if (e.shiftKey || document.getSelection().isCollapsed) {
6843 // If the shift key is being held, or there is no document selection, send
6844 // a Meta-C. The keyboard code will add the ESC if metaSendsEscape is true,
6845 // we just have to decide between 'c' and 'C'.
6846 return keyDef.keyCap.substr(e.shiftKey ? 1 : 0, 1);
6847 }
6848
6849 // Otherwise let the browser handle it as a copy command.
6850 if (this.keyboard.terminal.clearSelectionAfterCopy) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006851 setTimeout(function() {
6852 document.getSelection().collapseToEnd();
6853 }, 50);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006854 }
6855 return hterm.Keyboard.KeyActions.PASS;
6856};
6857
6858/**
6859 * Either PASS or DEFAULT Meta-V, depending on preference.
6860 *
6861 * Always PASS Meta-Shift-V to allow browser to interpret the keystroke as
6862 * a paste command.
6863 */
6864hterm.Keyboard.KeyMap.prototype.onMetaV_ = function(e, keyDef) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006865 if (e.shiftKey) return hterm.Keyboard.KeyActions.PASS;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006866
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006867 return this.keyboard.passMetaV ? hterm.Keyboard.KeyActions.PASS :
6868 hterm.Keyboard.KeyActions.DEFAULT;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006869};
6870
6871/**
6872 * Handle font zooming.
6873 *
6874 * The browser's built-in zoom has a bit of an issue at certain zoom levels.
6875 * At some magnifications, the measured height of a row of text differs from
6876 * the height that was explicitly set.
6877 *
6878 * We override the browser zoom keys to change the ScrollPort's font size to
6879 * avoid the issue.
6880 */
6881hterm.Keyboard.KeyMap.prototype.onPlusMinusZero_ = function(e, keyDef) {
6882 if (!(this.keyboard.ctrlPlusMinusZeroZoom ^ e.shiftKey)) {
6883 // If ctrl-PMZ controls zoom and the shift key is pressed, or
6884 // ctrl-shift-PMZ controls zoom and this shift key is not pressed,
6885 // then we want to send the control code instead of affecting zoom.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006886 if (keyDef.keyCap == '-_') return '\x1f'; // ^_
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006887
6888 // Only ^_ is valid, the other sequences have no meaning.
6889 return hterm.Keyboard.KeyActions.CANCEL;
6890 }
6891
6892 if (this.keyboard.terminal.getZoomFactor() != 1) {
6893 // If we're not at 1:1 zoom factor, let the Ctrl +/-/0 keys control the
6894 // browser zoom, so it's easier to for the user to get back to 100%.
6895 return hterm.Keyboard.KeyActions.PASS;
6896 }
6897
6898 var cap = keyDef.keyCap.substr(0, 1);
6899 if (cap == '0') {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006900 this.keyboard.terminal.setFontSize(0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006901 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006902 var size = this.keyboard.terminal.getFontSize();
6903
6904 if (cap == '-' || keyDef.keyCap == '[KP-]') {
6905 size -= 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006906 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006907 size += 1;
6908 }
6909
6910 this.keyboard.terminal.setFontSize(size);
6911 }
6912
6913 return hterm.Keyboard.KeyActions.CANCEL;
6914};
6915// SOURCE FILE: hterm/js/hterm_keyboard_keypattern.js
6916// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
6917// Use of this source code is governed by a BSD-style license that can be
6918// found in the LICENSE file.
6919
6920'use strict';
6921
6922/**
6923 * A record of modifier bits and keycode used to define a key binding.
6924 *
6925 * The modifier names are enumerated in the static KeyPattern.modifiers
6926 * property below. Each modifier can be true, false, or "*". True means
6927 * the modifier key must be present, false means it must not, and "*" means
6928 * it doesn't matter.
6929 */
6930hterm.Keyboard.KeyPattern = function(spec) {
6931 this.wildcardCount = 0;
6932 this.keyCode = spec.keyCode;
6933
6934 hterm.Keyboard.KeyPattern.modifiers.forEach(function(mod) {
6935 this[mod] = spec[mod] || false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006936 if (this[mod] == '*') this.wildcardCount++;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006937 }.bind(this));
6938};
6939
6940/**
6941 * Valid modifier names.
6942 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006943hterm.Keyboard.KeyPattern.modifiers = ['shift', 'ctrl', 'alt', 'meta'];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006944
6945/**
6946 * A compare callback for Array.prototype.sort().
6947 *
6948 * The bindings code wants to be sure to search through the strictest key
6949 * patterns first, so that loosely defined patterns have a lower priority than
6950 * exact patterns.
6951 *
6952 * @param {hterm.Keyboard.KeyPattern} a
6953 * @param {hterm.Keyboard.KeyPattern} b
6954 */
6955hterm.Keyboard.KeyPattern.sortCompare = function(a, b) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006956 if (a.wildcardCount < b.wildcardCount) return -1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006957
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006958 if (a.wildcardCount > b.wildcardCount) return 1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006959
6960 return 0;
6961};
6962
6963/**
6964 * Private method used to match this key pattern against other key patterns
6965 * or key down events.
6966 *
6967 * @param {Object} The object to match.
6968 * @param {boolean} True if we should ignore wildcards. Useful when you want
6969 * to perform and exact match against another key pattern.
6970 */
6971hterm.Keyboard.KeyPattern.prototype.match_ = function(obj, exactMatch) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006972 if (this.keyCode != obj.keyCode) return false;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006973
6974 var rv = true;
6975
6976 hterm.Keyboard.KeyPattern.modifiers.forEach(function(mod) {
6977 var modValue = (mod in obj) ? obj[mod] : false;
6978 if (!rv || (!exactMatch && this[mod] == '*') || this[mod] == modValue)
6979 return;
6980
6981 rv = false;
6982 }.bind(this));
6983
6984 return rv;
6985};
6986
6987/**
6988 * Return true if the given keyDown object is a match for this key pattern.
6989 *
6990 * @param {Object} keyDown An object with a keyCode property and zero or
6991 * more boolean properties representing key modifiers. These property names
6992 * must match those defined in hterm.Keyboard.KeyPattern.modifiers.
6993 */
6994hterm.Keyboard.KeyPattern.prototype.matchKeyDown = function(keyDown) {
6995 return this.match_(keyDown, false);
6996};
6997
6998/**
6999 * Return true if the given hterm.Keyboard.KeyPattern is exactly the same as
7000 * this one.
7001 *
7002 * @param {hterm.Keyboard.KeyPattern}
7003 */
7004hterm.Keyboard.KeyPattern.prototype.matchKeyPattern = function(keyPattern) {
7005 return this.match_(keyPattern, true);
7006};
7007// SOURCE FILE: hterm/js/hterm_options.js
7008// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
7009// Use of this source code is governed by a BSD-style license that can be
7010// found in the LICENSE file.
7011
7012'use strict';
7013
7014/**
7015 * @fileoverview This file implements the hterm.Options class,
7016 * which stores current operating conditions for the terminal. This object is
7017 * used instead of a series of parameters to allow saving/restoring of cursor
7018 * conditions easily, and to provide an easy place for common configuration
7019 * options.
7020 *
7021 * Original code by Cory Maccarrone.
7022 */
7023
7024/**
7025 * Constructor for the hterm.Options class, optionally acting as a copy
7026 * constructor.
7027 *
7028 * The defaults are as defined in http://www.vt100.net/docs/vt510-rm/DECSTR
7029 * except that we enable autowrap (wraparound) by default since that seems to
7030 * be what xterm does.
7031 *
7032 * @param {hterm.Options=} opt_copy Optional instance to copy.
7033 * @constructor
7034 */
7035hterm.Options = function(opt_copy) {
7036 // All attributes in this class are public to allow easy access by the
7037 // terminal.
7038
7039 this.wraparound = opt_copy ? opt_copy.wraparound : true;
7040 this.reverseWraparound = opt_copy ? opt_copy.reverseWraparound : false;
7041 this.originMode = opt_copy ? opt_copy.originMode : false;
7042 this.autoCarriageReturn = opt_copy ? opt_copy.autoCarriageReturn : false;
7043 this.cursorVisible = opt_copy ? opt_copy.cursorVisible : false;
7044 this.cursorBlink = opt_copy ? opt_copy.cursorBlink : false;
7045 this.insertMode = opt_copy ? opt_copy.insertMode : false;
7046 this.reverseVideo = opt_copy ? opt_copy.reverseVideo : false;
7047 this.bracketedPaste = opt_copy ? opt_copy.bracketedPaste : false;
7048};
7049// SOURCE FILE: hterm/js/hterm_parser.js
7050// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
7051// Use of this source code is governed by a BSD-style license that can be
7052// found in the LICENSE file.
7053
7054'use strict';
7055
7056lib.rtdep('hterm.Keyboard.KeyActions');
7057
7058/**
7059 * @constructor
7060 * Parses the key definition syntax used for user keyboard customizations.
7061 */
7062hterm.Parser = function() {
7063 /**
7064 * @type {string} The source string.
7065 */
7066 this.source = '';
7067
7068 /**
7069 * @type {number} The current position.
7070 */
7071 this.pos = 0;
7072
7073 /**
7074 * @type {string?} The character at the current position.
7075 */
7076 this.ch = null;
7077};
7078
7079hterm.Parser.prototype.error = function(message) {
7080 return new Error('Parse error at ' + this.pos + ': ' + message);
7081};
7082
7083hterm.Parser.prototype.isComplete = function() {
7084 return this.pos == this.source.length;
7085};
7086
7087hterm.Parser.prototype.reset = function(source, opt_pos) {
7088 this.source = source;
7089 this.pos = opt_pos || 0;
7090 this.ch = source.substr(0, 1);
7091};
7092
7093/**
7094 * Parse a key sequence.
7095 *
7096 * A key sequence is zero or more of the key modifiers defined in
7097 * hterm.Parser.identifiers.modifierKeys followed by a key code. Key
7098 * codes can be an integer or an identifier from
7099 * hterm.Parser.identifiers.keyCodes. Modifiers and keyCodes should be joined
7100 * by the dash character.
7101 *
7102 * An asterisk "*" can be used to indicate that the unspecified modifiers
7103 * are optional.
7104 *
7105 * For example:
7106 * A: Matches only an unmodified "A" character.
7107 * 65: Same as above.
7108 * 0x41: Same as above.
7109 * Ctrl-A: Matches only Ctrl-A.
7110 * Ctrl-65: Same as above.
7111 * Ctrl-0x41: Same as above.
7112 * Ctrl-Shift-A: Matches only Ctrl-Shift-A.
7113 * Ctrl-*-A: Matches Ctrl-A, as well as any other key sequence that includes
7114 * at least the Ctrl and A keys.
7115 *
7116 * @return {Object} An object with shift, ctrl, alt, meta, keyCode
7117 * properties.
7118 */
7119hterm.Parser.prototype.parseKeySequence = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007120 var rv = {keyCode: null};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007121
7122 for (var k in hterm.Parser.identifiers.modifierKeys) {
7123 rv[hterm.Parser.identifiers.modifierKeys[k]] = false;
7124 }
7125
7126 while (this.pos < this.source.length) {
7127 this.skipSpace();
7128
7129 var token = this.parseToken();
7130 if (token.type == 'integer') {
7131 rv.keyCode = token.value;
7132
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007133 } else if (token.type == 'identifier') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007134 if (token.value in hterm.Parser.identifiers.modifierKeys) {
7135 var mod = hterm.Parser.identifiers.modifierKeys[token.value];
7136 if (rv[mod] && rv[mod] != '*')
7137 throw this.error('Duplicate modifier: ' + token.value);
7138 rv[mod] = true;
7139
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007140 } else if (token.value in hterm.Parser.identifiers.keyCodes) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007141 rv.keyCode = hterm.Parser.identifiers.keyCodes[token.value];
7142
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007143 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007144 throw this.error('Unknown key: ' + token.value);
7145 }
7146
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007147 } else if (token.type == 'symbol') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007148 if (token.value == '*') {
7149 for (var id in hterm.Parser.identifiers.modifierKeys) {
7150 var p = hterm.Parser.identifiers.modifierKeys[id];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007151 if (!rv[p]) rv[p] = '*';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007152 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007153 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007154 throw this.error('Unexpected symbol: ' + token.value);
7155 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007156 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007157 throw this.error('Expected integer or identifier');
7158 }
7159
7160 this.skipSpace();
7161
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007162 if (this.ch != '-') break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007163
7164 if (rv.keyCode != null)
7165 throw this.error('Extra definition after target key');
7166
7167 this.advance(1);
7168 }
7169
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007170 if (rv.keyCode == null) throw this.error('Missing target key');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007171
7172 return rv;
7173};
7174
7175hterm.Parser.prototype.parseKeyAction = function() {
7176 this.skipSpace();
7177
7178 var token = this.parseToken();
7179
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007180 if (token.type == 'string') return token.value;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007181
7182 if (token.type == 'identifier') {
7183 if (token.value in hterm.Parser.identifiers.actions)
7184 return hterm.Parser.identifiers.actions[token.value];
7185
7186 throw this.error('Unknown key action: ' + token.value);
7187 }
7188
7189 throw this.error('Expected string or identifier');
7190
7191};
7192
7193hterm.Parser.prototype.peekString = function() {
7194 return this.ch == '\'' || this.ch == '"';
7195};
7196
7197hterm.Parser.prototype.peekIdentifier = function() {
7198 return this.ch.match(/[a-z_]/i);
7199};
7200
7201hterm.Parser.prototype.peekInteger = function() {
7202 return this.ch.match(/[0-9]/);
7203};
7204
7205hterm.Parser.prototype.parseToken = function() {
7206 if (this.ch == '*') {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007207 var rv = {type: 'symbol', value: this.ch};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007208 this.advance(1);
7209 return rv;
7210 }
7211
7212 if (this.peekIdentifier())
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007213 return {type: 'identifier', value: this.parseIdentifier()};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007214
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007215 if (this.peekString()) return {type: 'string', value: this.parseString()};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007216
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007217 if (this.peekInteger()) return {type: 'integer', value: this.parseInteger()};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007218
7219 throw this.error('Unexpected token');
7220};
7221
7222hterm.Parser.prototype.parseIdentifier = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007223 if (!this.peekIdentifier()) throw this.error('Expected identifier');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007224
7225 return this.parsePattern(/[a-z0-9_]+/ig);
7226};
7227
7228hterm.Parser.prototype.parseInteger = function() {
7229 var base = 10;
7230
7231 if (this.ch == '0' && this.pos < this.source.length - 1 &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007232 this.source.substr(this.pos + 1, 1) == 'x') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007233 return parseInt(this.parsePattern(/0x[0-9a-f]+/gi));
7234 }
7235
7236 return parseInt(this.parsePattern(/\d+/g));
7237};
7238
7239/**
7240 * Parse a single or double quoted string.
7241 *
7242 * The current position should point at the initial quote character. Single
7243 * quoted strings will be treated literally, double quoted will process escapes.
7244 *
7245 * TODO(rginda): Variable interpolation.
7246 *
7247 * @param {ParseState} parseState
7248 * @param {string} quote A single or double-quote character.
7249 * @return {string}
7250 */
7251hterm.Parser.prototype.parseString = function() {
7252 var result = '';
7253
7254 var quote = this.ch;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007255 if (quote != '"' && quote != '\'') throw this.error('String expected');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007256
7257 this.advance(1);
7258
7259 var re = new RegExp('[\\\\' + quote + ']', 'g');
7260
7261 while (this.pos < this.source.length) {
7262 re.lastIndex = this.pos;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007263 if (!re.exec(this.source)) throw this.error('Unterminated string literal');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007264
7265 result += this.source.substring(this.pos, re.lastIndex - 1);
7266
7267 this.advance(re.lastIndex - this.pos - 1);
7268
7269 if (quote == '"' && this.ch == '\\') {
7270 this.advance(1);
7271 result += this.parseEscape();
7272 continue;
7273 }
7274
7275 if (quote == '\'' && this.ch == '\\') {
7276 result += this.ch;
7277 this.advance(1);
7278 continue;
7279 }
7280
7281 if (this.ch == quote) {
7282 this.advance(1);
7283 return result;
7284 }
7285 }
7286
7287 throw this.error('Unterminated string literal');
7288};
7289
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007290/**
7291 * Parse an escape code from the current position (which should point to
7292 * the first character AFTER the leading backslash.)
7293 *
7294 * @return {string}
7295 */
7296hterm.Parser.prototype.parseEscape = function() {
7297 var map = {
7298 '"': '"',
7299 '\'': '\'',
7300 '\\': '\\',
7301 'a': '\x07',
7302 'b': '\x08',
7303 'e': '\x1b',
7304 'f': '\x0c',
7305 'n': '\x0a',
7306 'r': '\x0d',
7307 't': '\x09',
7308 'v': '\x0b',
7309 'x': function() {
7310 var value = this.parsePattern(/[a-z0-9]{2}/ig);
7311 return String.fromCharCode(parseInt(value, 16));
7312 },
7313 'u': function() {
7314 var value = this.parsePattern(/[a-z0-9]{4}/ig);
7315 return String.fromCharCode(parseInt(value, 16));
7316 }
7317 };
7318
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007319 if (!(this.ch in map)) throw this.error('Unknown escape: ' + this.ch);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007320
7321 var value = map[this.ch];
7322 this.advance(1);
7323
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007324 if (typeof value == 'function') value = value.call(this);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007325
7326 return value;
7327};
7328
7329/**
7330 * Parse the given pattern starting from the current position.
7331 *
7332 * @param {RegExp} pattern A pattern representing the characters to span. MUST
7333 * include the "global" RegExp flag.
7334 * @return {string}
7335 */
7336hterm.Parser.prototype.parsePattern = function(pattern) {
7337 if (!pattern.global)
7338 throw this.error('Internal error: Span patterns must be global');
7339
7340 pattern.lastIndex = this.pos;
7341 var ary = pattern.exec(this.source);
7342
7343 if (!ary || pattern.lastIndex - ary[0].length != this.pos)
7344 throw this.error('Expected match for: ' + pattern);
7345
7346 this.pos = pattern.lastIndex - 1;
7347 this.advance(1);
7348
7349 return ary[0];
7350};
7351
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007352/**
7353 * Advance the current position.
7354 *
7355 * @param {number} count
7356 */
7357hterm.Parser.prototype.advance = function(count) {
7358 this.pos += count;
7359 this.ch = this.source.substr(this.pos, 1);
7360};
7361
7362/**
7363 * @param {string=} opt_expect A list of valid non-whitespace characters to
7364 * terminate on.
7365 * @return {void}
7366 */
7367hterm.Parser.prototype.skipSpace = function(opt_expect) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007368 if (!/\s/.test(this.ch)) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007369
7370 var re = /\s+/gm;
7371 re.lastIndex = this.pos;
7372
7373 var source = this.source;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007374 if (re.exec(source)) this.pos = re.lastIndex;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007375
7376 this.ch = this.source.substr(this.pos, 1);
7377
7378 if (opt_expect) {
7379 if (this.ch.indexOf(opt_expect) == -1) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007380 throw this.error('Expected one of ' + opt_expect + ', found: ' + this.ch);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007381 }
7382 }
7383};
7384// SOURCE FILE: hterm/js/hterm_parser_identifiers.js
7385// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
7386// Use of this source code is governed by a BSD-style license that can be
7387// found in the LICENSE file.
7388
7389'use strict';
7390
7391/**
7392 * Collections of identifier for hterm.Parser.
7393 */
7394hterm.Parser.identifiers = {};
7395
7396hterm.Parser.identifiers.modifierKeys = {
7397 Shift: 'shift',
7398 Ctrl: 'ctrl',
7399 Alt: 'alt',
7400 Meta: 'meta'
7401};
7402
7403/**
7404 * Key codes useful when defining key sequences.
7405 *
7406 * Punctuation is mostly left out of this list because they can move around
7407 * based on keyboard locale and browser.
7408 *
7409 * In a key sequence like "Ctrl-ESC", the ESC comes from this list of
7410 * identifiers. It is equivalent to "Ctrl-27" and "Ctrl-0x1b".
7411 */
7412hterm.Parser.identifiers.keyCodes = {
7413 // Top row.
7414 ESC: 27,
7415 F1: 112,
7416 F2: 113,
7417 F3: 114,
7418 F4: 115,
7419 F5: 116,
7420 F6: 117,
7421 F7: 118,
7422 F8: 119,
7423 F9: 120,
7424 F10: 121,
7425 F11: 122,
7426 F12: 123,
7427
7428 // Row two.
7429 ONE: 49,
7430 TWO: 50,
7431 THREE: 51,
7432 FOUR: 52,
7433 FIVE: 53,
7434 SIX: 54,
7435 SEVEN: 55,
7436 EIGHT: 56,
7437 NINE: 57,
7438 ZERO: 48,
7439 BACKSPACE: 8,
7440
7441 // Row three.
7442 TAB: 9,
7443 Q: 81,
7444 W: 87,
7445 E: 69,
7446 R: 82,
7447 T: 84,
7448 Y: 89,
7449 U: 85,
7450 I: 73,
7451 O: 79,
7452 P: 80,
7453
7454 // Row four.
7455 CAPSLOCK: 20,
7456 A: 65,
7457 S: 83,
7458 D: 68,
7459 F: 70,
7460 G: 71,
7461 H: 72,
7462 J: 74,
7463 K: 75,
7464 L: 76,
7465 ENTER: 13,
7466
7467 // Row five.
7468 Z: 90,
7469 X: 88,
7470 C: 67,
7471 V: 86,
7472 B: 66,
7473 N: 78,
7474 M: 77,
7475
7476 // Etc.
7477 SPACE: 32,
7478 PRINT_SCREEN: 42,
7479 SCROLL_LOCK: 145,
7480 BREAK: 19,
7481 INSERT: 45,
7482 HOME: 36,
7483 PGUP: 33,
7484 DEL: 46,
7485 END: 35,
7486 PGDOWN: 34,
7487 UP: 38,
7488 DOWN: 40,
7489 RIGHT: 39,
7490 LEFT: 37,
7491 NUMLOCK: 144,
7492
7493 // Keypad
7494 KP0: 96,
7495 KP1: 97,
7496 KP2: 98,
7497 KP3: 99,
7498 KP4: 100,
7499 KP5: 101,
7500 KP6: 102,
7501 KP7: 103,
7502 KP8: 104,
7503 KP9: 105,
7504 KP_PLUS: 107,
7505 KP_MINUS: 109,
7506 KP_STAR: 106,
7507 KP_DIVIDE: 111,
7508 KP_DECIMAL: 110,
7509
7510 // Chrome OS media keys
7511 NAVIGATE_BACK: 166,
7512 NAVIGATE_FORWARD: 167,
7513 RELOAD: 168,
7514 FULL_SCREEN: 183,
7515 WINDOW_OVERVIEW: 182,
7516 BRIGHTNESS_UP: 216,
7517 BRIGHTNESS_DOWN: 217
7518};
7519
7520/**
7521 * Identifiers for use in key actions.
7522 */
7523hterm.Parser.identifiers.actions = {
7524 /**
7525 * Prevent the browser and operating system from handling the event.
7526 */
7527 CANCEL: hterm.Keyboard.KeyActions.CANCEL,
7528
7529 /**
7530 * Wait for a "keypress" event, send the keypress charCode to the host.
7531 */
7532 DEFAULT: hterm.Keyboard.KeyActions.DEFAULT,
7533
7534 /**
7535 * Let the browser or operating system handle the key.
7536 */
7537 PASS: hterm.Keyboard.KeyActions.PASS,
7538
7539 /**
7540 * Scroll the terminal one page up.
7541 */
7542 scrollPageUp: function(terminal) {
7543 terminal.scrollPageUp();
7544 return hterm.Keyboard.KeyActions.CANCEL;
7545 },
7546
7547 /**
7548 * Scroll the terminal one page down.
7549 */
7550 scrollPageDown: function(terminal) {
7551 terminal.scrollPageDown();
7552 return hterm.Keyboard.KeyActions.CANCEL;
7553 },
7554
7555 /**
7556 * Scroll the terminal to the top.
7557 */
7558 scrollToTop: function(terminal) {
7559 terminal.scrollEnd();
7560 return hterm.Keyboard.KeyActions.CANCEL;
7561 },
7562
7563 /**
7564 * Scroll the terminal to the bottom.
7565 */
7566 scrollToBottom: function(terminal) {
7567 terminal.scrollEnd();
7568 return hterm.Keyboard.KeyActions.CANCEL;
7569 },
7570
7571 /**
7572 * Clear the terminal and scrollback buffer.
7573 */
7574 clearScrollback: function(terminal) {
7575 terminal.wipeContents();
7576 return hterm.Keyboard.KeyActions.CANCEL;
7577 }
7578};
7579// SOURCE FILE: hterm/js/hterm_preference_manager.js
7580// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
7581// Use of this source code is governed by a BSD-style license that can be
7582// found in the LICENSE file.
7583
7584'use strict';
7585
7586lib.rtdep('lib.f', 'lib.Storage');
7587
7588/**
7589 * PreferenceManager subclass managing global NaSSH preferences.
7590 *
7591 * This is currently just an ordered list of known connection profiles.
7592 */
7593hterm.PreferenceManager = function(profileId) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007594 lib.PreferenceManager.call(
7595 this, hterm.defaultStorage, '/hterm/profiles/' + profileId);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007596 var defs = hterm.PreferenceManager.defaultPreferences;
7597 Object.keys(defs).forEach(function(key) {
7598 this.definePreference(key, defs[key][1]);
7599 }.bind(this));
7600};
7601
7602hterm.PreferenceManager.categories = {};
7603hterm.PreferenceManager.categories.Keyboard = 'Keyboard';
7604hterm.PreferenceManager.categories.Appearance = 'Appearance';
7605hterm.PreferenceManager.categories.CopyPaste = 'CopyPaste';
7606hterm.PreferenceManager.categories.Sounds = 'Sounds';
7607hterm.PreferenceManager.categories.Scrolling = 'Scrolling';
7608hterm.PreferenceManager.categories.Encoding = 'Encoding';
7609hterm.PreferenceManager.categories.Miscellaneous = 'Miscellaneous';
7610
7611/**
7612 * List of categories, ordered by display order (top to bottom)
7613 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007614hterm.PreferenceManager.categoryDefinitions = [
7615 {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007616 id: hterm.PreferenceManager.categories.Appearance,
7617 text: 'Appearance (fonts, colors, images)'
7618 },
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007619 {id: hterm.PreferenceManager.categories.CopyPaste, text: 'Copy & Paste'},
7620 {id: hterm.PreferenceManager.categories.Encoding, text: 'Encoding'},
7621 {id: hterm.PreferenceManager.categories.Keyboard, text: 'Keyboard'},
7622 {id: hterm.PreferenceManager.categories.Scrolling, text: 'Scrolling'},
7623 {id: hterm.PreferenceManager.categories.Sounds, text: 'Sounds'},
7624 {id: hterm.PreferenceManager.categories.Miscellaneous, text: 'Misc.'}
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007625];
7626
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007627hterm.PreferenceManager.defaultPreferences = {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007628 'alt-gr-mode': [
7629 hterm.PreferenceManager.categories.Keyboard, null,
7630 [null, 'none', 'ctrl-alt', 'left-alt', 'right-alt'],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007631 'Select an AltGr detection hack^Wheuristic.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007632 '\n' +
7633 '\'null\': Autodetect based on navigator.language:\n' +
7634 ' \'en-us\' => \'none\', else => \'right-alt\'\n' +
7635 '\'none\': Disable any AltGr related munging.\n' +
7636 '\'ctrl-alt\': Assume Ctrl+Alt means AltGr.\n' +
7637 '\'left-alt\': Assume left Alt means AltGr.\n' +
7638 '\'right-alt\': Assume right Alt means AltGr.\n'
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007639 ],
7640
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007641 'alt-backspace-is-meta-backspace': [
7642 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007643 'If set, undoes the Chrome OS Alt-Backspace->DEL remap, so that ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007644 'alt-backspace indeed is alt-backspace.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007645 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007646
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007647 'alt-is-meta': [
7648 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007649 'Set whether the alt key acts as a meta key or as a distinct alt key.'
7650 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007651
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007652 'alt-sends-what': [
7653 hterm.PreferenceManager.categories.Keyboard, 'escape',
7654 ['escape', '8-bit', 'browser-key'],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007655 'Controls how the alt key is handled.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007656 '\n' +
7657 ' escape....... Send an ESC prefix.\n' +
7658 ' 8-bit........ Add 128 to the unshifted character as in xterm.\n' +
7659 ' browser-key.. Wait for the keypress event and see what the browser \n' +
7660 ' says. (This won\'t work well on platforms where the \n' +
7661 ' browser performs a default action for some alt sequences.)'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007662 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007663
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007664 'audible-bell-sound': [
7665 hterm.PreferenceManager.categories.Sounds, 'lib-resource:hterm/audio/bell',
7666 'url', 'URL of the terminal bell sound. Empty string for no audible bell.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007667 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007668
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007669 'desktop-notification-bell': [
7670 hterm.PreferenceManager.categories.Sounds, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007671 'If true, terminal bells in the background will create a Web ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007672 'Notification. https://www.w3.org/TR/notifications/\n' +
7673 '\n' +
7674 'Displaying notifications requires permission from the user. When this ' +
7675 'option is set to true, hterm will attempt to ask the user for permission ' +
7676 'if necessary. Note browsers may not show this permission request if it ' +
7677 'did not originate from a user action.\n' +
7678 '\n' +
7679 'Chrome extensions with the "notifications" permission have permission to ' +
7680 'display notifications.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007681 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007682
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007683 'background-color': [
7684 hterm.PreferenceManager.categories.Appearance, 'rgb(16, 16, 16)', 'color',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007685 'The background color for text with no other color attributes.'
7686 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007687
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007688 'background-image': [
7689 hterm.PreferenceManager.categories.Appearance, '', 'string',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007690 'CSS value of the background image. Empty string for no image.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007691 '\n' +
7692 'For example:\n' +
7693 ' url(https://goo.gl/anedTK)\n' +
7694 ' linear-gradient(top bottom, blue, red)'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007695 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007696
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007697 'background-size': [
7698 hterm.PreferenceManager.categories.Appearance, '', 'string',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007699 'CSS value of the background image size. Defaults to none.'
7700 ],
7701
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007702 'background-position': [
7703 hterm.PreferenceManager.categories.Appearance, '', 'string',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007704 'CSS value of the background image position.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007705 '\n' +
7706 'For example:\n' +
7707 ' 10% 10%\n' +
7708 ' center'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007709 ],
7710
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007711 'backspace-sends-backspace': [
7712 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007713 'If true, the backspace should send BS (\'\\x08\', aka ^H). Otherwise ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007714 'the backspace key should send \'\\x7f\'.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007715 ],
7716
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007717 'character-map-overrides': [
7718 hterm.PreferenceManager.categories.Appearance, null, 'value',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007719 'This is specified as an object. It is a sparse array, where each ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007720 'property is the character set code and the value is an object that is ' +
7721 'a sparse array itself. In that sparse array, each property is the ' +
7722 'received character and the value is the displayed character.\n' +
7723 '\n' +
7724 'For example:\n' +
7725 ' {"0":{"+":"\\u2192",",":"\\u2190","-":"\\u2191",".":"\\u2193", ' +
7726 '"0":"\\u2588"}}'
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007727 ],
7728
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007729 'close-on-exit': [
7730 hterm.PreferenceManager.categories.Miscellaneous, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007731 'Whether or not to close the window when the command exits.'
7732 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007733
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007734 'cursor-blink': [
7735 hterm.PreferenceManager.categories.Appearance, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007736 'Whether or not to blink the cursor by default.'
7737 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007738
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007739 'cursor-blink-cycle': [
7740 hterm.PreferenceManager.categories.Appearance, [1000, 500], 'value',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007741 'The cursor blink rate in milliseconds.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007742 '\n' +
7743 'A two element array, the first of which is how long the cursor should be ' +
7744 'on, second is how long it should be off.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007745 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007746
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007747 'cursor-color': [
7748 hterm.PreferenceManager.categories.Appearance, 'rgba(255, 0, 0, 0.5)',
7749 'color', 'The color of the visible cursor.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007750 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007751
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007752 'color-palette-overrides': [
7753 hterm.PreferenceManager.categories.Appearance, null, 'value',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007754 'Override colors in the default palette.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007755 '\n' +
7756 'This can be specified as an array or an object. If specified as an ' +
7757 'object it is assumed to be a sparse array, where each property ' +
7758 'is a numeric index into the color palette.\n' +
7759 '\n' +
7760 'Values can be specified as almost any css color value. This ' +
7761 'includes #RGB, #RRGGBB, rgb(...), rgba(...), and any color names ' +
7762 'that are also part of the stock X11 rgb.txt file.\n' +
7763 '\n' +
7764 'You can use \'null\' to specify that the default value should be not ' +
7765 'be changed. This is useful for skipping a small number of indices ' +
7766 'when the value is specified as an array.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007767 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007768
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007769 'copy-on-select': [
7770 hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007771 'Automatically copy mouse selection to the clipboard.'
7772 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007773
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007774 'use-default-window-copy': [
7775 hterm.PreferenceManager.categories.CopyPaste, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007776 'Whether to use the default window copy behavior'
7777 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007778
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007779 'clear-selection-after-copy': [
7780 hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007781 'Whether to clear the selection after copying.'
7782 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007783
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007784 'ctrl-plus-minus-zero-zoom': [
7785 hterm.PreferenceManager.categories.Keyboard, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007786 'If true, Ctrl-Plus/Minus/Zero controls zoom.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007787 'If false, Ctrl-Shift-Plus/Minus/Zero controls zoom, Ctrl-Minus sends ^_, ' +
7788 'Ctrl-Plus/Zero do nothing.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007789 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007790
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007791 'ctrl-c-copy': [
7792 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007793 'Ctrl+C copies if true, send ^C to host if false.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007794 'Ctrl+Shift+C sends ^C to host if true, copies if false.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007795 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007796
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007797 'ctrl-v-paste': [
7798 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007799 'Ctrl+V pastes if true, send ^V to host if false.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007800 'Ctrl+Shift+V sends ^V to host if true, pastes if false.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007801 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007802
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007803 'east-asian-ambiguous-as-two-column': [
7804 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007805 'Set whether East Asian Ambiguous characters have two column width.'
7806 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007807
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007808 'enable-8-bit-control': [
7809 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007810 'True to enable 8-bit control characters, false to ignore them.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007811 '\n' +
7812 'We\'ll respect the two-byte versions of these control characters ' +
7813 'regardless of this setting.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007814 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007815
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007816 'enable-bold': [
7817 hterm.PreferenceManager.categories.Appearance, null, 'tristate',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007818 'True if we should use bold weight font for text with the bold/bright ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007819 'attribute. False to use the normal weight font. Null to autodetect.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007820 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007821
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007822 'enable-bold-as-bright': [
7823 hterm.PreferenceManager.categories.Appearance, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007824 'True if we should use bright colors (8-15 on a 16 color palette) ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007825 'for any text with the bold attribute. False otherwise.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007826 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007827
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007828 'enable-blink': [
7829 hterm.PreferenceManager.categories.Appearance, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007830 'True if we should respect the blink attribute. False to ignore it. '
7831 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007832
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007833 'enable-clipboard-notice': [
7834 hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007835 'Show a message in the terminal when the host writes to the clipboard.'
7836 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007837
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007838 'enable-clipboard-write': [
7839 hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007840 'Allow the host to write directly to the system clipboard.'
7841 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007842
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007843 'enable-dec12': [
7844 hterm.PreferenceManager.categories.Miscellaneous, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007845 'Respect the host\'s attempt to change the cursor blink status using ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007846 'DEC Private Mode 12.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007847 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007848
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007849 'environment': [
7850 hterm.PreferenceManager.categories.Miscellaneous,
7851 {'TERM': 'xterm-256color'}, 'value',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007852 'The default environment variables, as an object.'
7853 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007854
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007855 'font-family': [
7856 hterm.PreferenceManager.categories.Appearance,
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007857 '"DejaVu Sans Mono", "Everson Mono", FreeMono, "Menlo", "Terminal", ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007858 'monospace',
7859 'string', 'Default font family for the terminal text.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007860 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007861
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007862 'font-size': [
7863 hterm.PreferenceManager.categories.Appearance, 15, 'int',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007864 'The default font size in pixels.'
7865 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007866
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007867 'font-smoothing': [
7868 hterm.PreferenceManager.categories.Appearance, 'antialiased', 'string',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007869 'CSS font-smoothing property.'
7870 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007871
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007872 'foreground-color': [
7873 hterm.PreferenceManager.categories.Appearance, 'rgb(240, 240, 240)',
7874 'color', 'The foreground color for text with no other color attributes.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007875 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007876
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007877 'home-keys-scroll': [
7878 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007879 'If true, home/end will control the terminal scrollbar and shift home/end ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007880 'will send the VT keycodes. If false then home/end sends VT codes and ' +
7881 'shift home/end scrolls.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007882 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007883
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007884 'keybindings': [
7885 hterm.PreferenceManager.categories.Keyboard, null, 'value',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007886 'A map of key sequence to key actions. Key sequences include zero or ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007887 'more modifier keys followed by a key code. Key codes can be decimal or ' +
7888 'hexadecimal numbers, or a key identifier. Key actions can be specified ' +
7889 'a string to send to the host, or an action identifier. For a full ' +
7890 'list of key code and action identifiers, see https://goo.gl/8AoD09.' +
7891 '\n' +
7892 '\n' +
7893 'Sample keybindings:\n' +
7894 '{ "Ctrl-Alt-K": "clearScrollback",\n' +
7895 ' "Ctrl-Shift-L": "PASS",\n' +
7896 ' "Ctrl-H": "\'HELLO\\n\'"\n' +
7897 '}'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007898 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007899
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007900 'max-string-sequence': [
7901 hterm.PreferenceManager.categories.Encoding, 100000, 'int',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007902 'Max length of a DCS, OSC, PM, or APS sequence before we give up and ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007903 'ignore the code.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007904 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007905
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007906 'media-keys-are-fkeys': [
7907 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007908 'If true, convert media keys to their Fkey equivalent. If false, let ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007909 'the browser handle the keys.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007910 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007911
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007912 'meta-sends-escape': [
7913 hterm.PreferenceManager.categories.Keyboard, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007914 'Set whether the meta key sends a leading escape or not.'
7915 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007916
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007917 'mouse-paste-button': [
7918 hterm.PreferenceManager.categories.CopyPaste, null,
7919 [null, 0, 1, 2, 3, 4, 5, 6],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007920 'Mouse paste button, or null to autodetect.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007921 '\n' +
7922 'For autodetect, we\'ll try to enable middle button paste for non-X11 ' +
7923 'platforms. On X11 we move it to button 3.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007924 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007925
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007926 'page-keys-scroll': [
7927 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007928 'If true, page up/down will control the terminal scrollbar and shift ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007929 'page up/down will send the VT keycodes. If false then page up/down ' +
7930 'sends VT codes and shift page up/down scrolls.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007931 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007932
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007933 'pass-alt-number': [
7934 hterm.PreferenceManager.categories.Keyboard, null, 'tristate',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007935 'Set whether we should pass Alt-1..9 to the browser.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007936 '\n' +
7937 'This is handy when running hterm in a browser tab, so that you don\'t ' +
7938 'lose Chrome\'s "switch to tab" keyboard accelerators. When not running ' +
7939 'in a tab it\'s better to send these keys to the host so they can be ' +
7940 'used in vim or emacs.\n' +
7941 '\n' +
7942 'If true, Alt-1..9 will be handled by the browser. If false, Alt-1..9 ' +
7943 'will be sent to the host. If null, autodetect based on browser platform ' +
7944 'and window type.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007945 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007946
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007947 'pass-ctrl-number': [
7948 hterm.PreferenceManager.categories.Keyboard, null, 'tristate',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007949 'Set whether we should pass Ctrl-1..9 to the browser.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007950 '\n' +
7951 'This is handy when running hterm in a browser tab, so that you don\'t ' +
7952 'lose Chrome\'s "switch to tab" keyboard accelerators. When not running ' +
7953 'in a tab it\'s better to send these keys to the host so they can be ' +
7954 'used in vim or emacs.\n' +
7955 '\n' +
7956 'If true, Ctrl-1..9 will be handled by the browser. If false, Ctrl-1..9 ' +
7957 'will be sent to the host. If null, autodetect based on browser platform ' +
7958 'and window type.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007959 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007960
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007961 'pass-meta-number': [
7962 hterm.PreferenceManager.categories.Keyboard, null, 'tristate',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007963 'Set whether we should pass Meta-1..9 to the browser.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007964 '\n' +
7965 'This is handy when running hterm in a browser tab, so that you don\'t ' +
7966 'lose Chrome\'s "switch to tab" keyboard accelerators. When not running ' +
7967 'in a tab it\'s better to send these keys to the host so they can be ' +
7968 'used in vim or emacs.\n' +
7969 '\n' +
7970 'If true, Meta-1..9 will be handled by the browser. If false, Meta-1..9 ' +
7971 'will be sent to the host. If null, autodetect based on browser platform ' +
7972 'and window type.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007973 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007974
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007975 'pass-meta-v': [
7976 hterm.PreferenceManager.categories.Keyboard, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007977 'Set whether meta-V gets passed to host.'
7978 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007979
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007980 'receive-encoding': [
7981 hterm.PreferenceManager.categories.Encoding, 'utf-8', ['utf-8', 'raw'],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007982 'Set the expected encoding for data received from the host.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007983 '\n' +
7984 'Valid values are \'utf-8\' and \'raw\'.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007985 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007986
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007987 'scroll-on-keystroke': [
7988 hterm.PreferenceManager.categories.Scrolling, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007989 'If true, scroll to the bottom on any keystroke.'
7990 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007991
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007992 'scroll-on-output': [
7993 hterm.PreferenceManager.categories.Scrolling, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007994 'If true, scroll to the bottom on terminal output.'
7995 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007996
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007997 'scrollbar-visible': [
7998 hterm.PreferenceManager.categories.Scrolling, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007999 'The vertical scrollbar mode.'
8000 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008001
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008002 'scroll-wheel-move-multiplier': [
8003 hterm.PreferenceManager.categories.Scrolling, 1, 'int',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008004 'The multiplier for the pixel delta in mousewheel event caused by the ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008005 'scroll wheel. Alters how fast the page scrolls.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008006 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008007
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008008 'send-encoding': [
8009 hterm.PreferenceManager.categories.Encoding, 'utf-8', ['utf-8', 'raw'],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008010 'Set the encoding for data sent to host.'
8011 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008012
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008013 'shift-insert-paste': [
8014 hterm.PreferenceManager.categories.Keyboard, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008015 'Shift + Insert pastes if true, sent to host if false.'
8016 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008017
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008018 'user-css': [
8019 hterm.PreferenceManager.categories.Appearance, '', 'url',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008020 'URL of user stylesheet to include in the terminal document.'
8021 ]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008022};
8023
8024hterm.PreferenceManager.prototype = {
8025 __proto__: lib.PreferenceManager.prototype
8026};
8027// SOURCE FILE: hterm/js/hterm_pubsub.js
8028// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
8029// Use of this source code is governed by a BSD-style license that can be
8030// found in the LICENSE file.
8031
8032'use strict';
8033
8034/**
8035 * Utility class used to add publish/subscribe/unsubscribe functionality to
8036 * an existing object.
8037 */
8038hterm.PubSub = function() {
8039 this.observers_ = {};
8040};
8041
8042/**
8043 * Add publish, subscribe, and unsubscribe methods to an existing object.
8044 *
8045 * No other properties of the object are touched, so there is no need to
8046 * worry about clashing private properties.
8047 *
8048 * @param {Object} obj The object to add this behavior to.
8049 */
8050hterm.PubSub.addBehavior = function(obj) {
8051 var pubsub = new hterm.PubSub();
8052 for (var m in hterm.PubSub.prototype) {
8053 obj[m] = hterm.PubSub.prototype[m].bind(pubsub);
8054 }
8055};
8056
8057/**
8058 * Subscribe to be notified of messages about a subject.
8059 *
8060 * @param {string} subject The subject to subscribe to.
8061 * @param {function(Object)} callback The function to invoke for notifications.
8062 */
8063hterm.PubSub.prototype.subscribe = function(subject, callback) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008064 if (!(subject in this.observers_)) this.observers_[subject] = [];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008065
8066 this.observers_[subject].push(callback);
8067};
8068
8069/**
8070 * Unsubscribe from a subject.
8071 *
8072 * @param {string} subject The subject to unsubscribe from.
8073 * @param {function(Object)} callback A callback previously registered via
8074 * subscribe().
8075 */
8076hterm.PubSub.prototype.unsubscribe = function(subject, callback) {
8077 var list = this.observers_[subject];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008078 if (!list) throw 'Invalid subject: ' + subject;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008079
8080 var i = list.indexOf(callback);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008081 if (i < 0) throw 'Not subscribed: ' + subject;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008082
8083 list.splice(i, 1);
8084};
8085
8086/**
8087 * Publish a message about a subject.
8088 *
8089 * Subscribers (and the optional final callback) are invoked asynchronously.
8090 * This method will return before anyone is actually notified.
8091 *
8092 * @param {string} subject The subject to publish about.
8093 * @param {Object} e An arbitrary object associated with this notification.
8094 * @param {function(Object)} opt_lastCallback An optional function to call after
8095 * all subscribers have been notified.
8096 */
8097hterm.PubSub.prototype.publish = function(subject, e, opt_lastCallback) {
8098 function notifyList(i) {
8099 // Set this timeout before invoking the callback, so we don't have to
8100 // concern ourselves with exceptions.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008101 if (i < list.length - 1) setTimeout(notifyList, 0, i + 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008102
8103 list[i](e);
8104 }
8105
8106 var list = this.observers_[subject];
8107 if (list) {
8108 // Copy the list, in case it changes while we're notifying.
8109 list = [].concat(list);
8110 }
8111
8112 if (opt_lastCallback) {
8113 if (list) {
8114 list.push(opt_lastCallback);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008115 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008116 list = [opt_lastCallback];
8117 }
8118 }
8119
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008120 if (list) setTimeout(notifyList, 0, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008121};
8122// SOURCE FILE: hterm/js/hterm_screen.js
8123// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
8124// Use of this source code is governed by a BSD-style license that can be
8125// found in the LICENSE file.
8126
8127'use strict';
8128
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008129lib.rtdep(
8130 'lib.f', 'lib.wc', 'hterm.RowCol', 'hterm.Size', 'hterm.TextAttributes');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008131
8132/**
8133 * @fileoverview This class represents a single terminal screen full of text.
8134 *
8135 * It maintains the current cursor position and has basic methods for text
8136 * insert and overwrite, and adding or removing rows from the screen.
8137 *
8138 * This class has no knowledge of the scrollback buffer.
8139 *
8140 * The number of rows on the screen is determined only by the number of rows
8141 * that the caller inserts into the screen. If a caller wants to ensure a
8142 * constant number of rows on the screen, it's their responsibility to remove a
8143 * row for each row inserted.
8144 *
8145 * The screen width, in contrast, is enforced locally.
8146 *
8147 *
8148 * In practice...
8149 * - The hterm.Terminal class holds two hterm.Screen instances. One for the
8150 * primary screen and one for the alternate screen.
8151 *
8152 * - The html.Screen class only cares that rows are HTMLElements. In the
8153 * larger context of hterm, however, the rows happen to be displayed by an
8154 * hterm.ScrollPort and have to follow a few rules as a result. Each
8155 * row must be rooted by the custom HTML tag 'x-row', and each must have a
8156 * rowIndex property that corresponds to the index of the row in the context
8157 * of the scrollback buffer. These invariants are enforced by hterm.Terminal
8158 * because that is the class using the hterm.Screen in the context of an
8159 * hterm.ScrollPort.
8160 */
8161
8162/**
8163 * Create a new screen instance.
8164 *
8165 * The screen initially has no rows and a maximum column count of 0.
8166 *
8167 * @param {integer} opt_columnCount The maximum number of columns for this
8168 * screen. See insertString() and overwriteString() for information about
8169 * what happens when too many characters are added too a row. Defaults to
8170 * 0 if not provided.
8171 */
8172hterm.Screen = function(opt_columnCount) {
8173 /**
8174 * Public, read-only access to the rows in this screen.
8175 */
8176 this.rowsArray = [];
8177
8178 // The max column width for this screen.
8179 this.columnCount_ = opt_columnCount || 80;
8180
8181 // The current color, bold, underline and blink attributes.
8182 this.textAttributes = new hterm.TextAttributes(window.document);
8183
8184 // Current zero-based cursor coordinates.
8185 this.cursorPosition = new hterm.RowCol(0, 0);
8186
8187 // The node containing the row that the cursor is positioned on.
8188 this.cursorRowNode_ = null;
8189
8190 // The node containing the span of text that the cursor is positioned on.
8191 this.cursorNode_ = null;
8192
8193 // The offset in column width into cursorNode_ where the cursor is positioned.
8194 this.cursorOffset_ = null;
8195};
8196
8197/**
8198 * Return the screen size as an hterm.Size object.
8199 *
8200 * @return {hterm.Size} hterm.Size object representing the current number
8201 * of rows and columns in this screen.
8202 */
8203hterm.Screen.prototype.getSize = function() {
8204 return new hterm.Size(this.columnCount_, this.rowsArray.length);
8205};
8206
8207/**
8208 * Return the current number of rows in this screen.
8209 *
8210 * @return {integer} The number of rows in this screen.
8211 */
8212hterm.Screen.prototype.getHeight = function() {
8213 return this.rowsArray.length;
8214};
8215
8216/**
8217 * Return the current number of columns in this screen.
8218 *
8219 * @return {integer} The number of columns in this screen.
8220 */
8221hterm.Screen.prototype.getWidth = function() {
8222 return this.columnCount_;
8223};
8224
8225/**
8226 * Set the maximum number of columns per row.
8227 *
8228 * @param {integer} count The maximum number of columns per row.
8229 */
8230hterm.Screen.prototype.setColumnCount = function(count) {
8231 this.columnCount_ = count;
8232
8233 if (this.cursorPosition.column >= count)
8234 this.setCursorPosition(this.cursorPosition.row, count - 1);
8235};
8236
8237/**
8238 * Remove the first row from the screen and return it.
8239 *
8240 * @return {HTMLElement} The first row in this screen.
8241 */
8242hterm.Screen.prototype.shiftRow = function() {
8243 return this.shiftRows(1)[0];
8244};
8245
8246/**
8247 * Remove rows from the top of the screen and return them as an array.
8248 *
8249 * @param {integer} count The number of rows to remove.
8250 * @return {Array.<HTMLElement>} The selected rows.
8251 */
8252hterm.Screen.prototype.shiftRows = function(count) {
8253 return this.rowsArray.splice(0, count);
8254};
8255
8256/**
8257 * Insert a row at the top of the screen.
8258 *
8259 * @param {HTMLElement} row The row to insert.
8260 */
8261hterm.Screen.prototype.unshiftRow = function(row) {
8262 this.rowsArray.splice(0, 0, row);
8263};
8264
8265/**
8266 * Insert rows at the top of the screen.
8267 *
8268 * @param {Array.<HTMLElement>} rows The rows to insert.
8269 */
8270hterm.Screen.prototype.unshiftRows = function(rows) {
8271 this.rowsArray.unshift.apply(this.rowsArray, rows);
8272};
8273
8274/**
8275 * Remove the last row from the screen and return it.
8276 *
8277 * @return {HTMLElement} The last row in this screen.
8278 */
8279hterm.Screen.prototype.popRow = function() {
8280 return this.popRows(1)[0];
8281};
8282
8283/**
8284 * Remove rows from the bottom of the screen and return them as an array.
8285 *
8286 * @param {integer} count The number of rows to remove.
8287 * @return {Array.<HTMLElement>} The selected rows.
8288 */
8289hterm.Screen.prototype.popRows = function(count) {
8290 return this.rowsArray.splice(this.rowsArray.length - count, count);
8291};
8292
8293/**
8294 * Insert a row at the bottom of the screen.
8295 *
8296 * @param {HTMLElement} row The row to insert.
8297 */
8298hterm.Screen.prototype.pushRow = function(row) {
8299 this.rowsArray.push(row);
8300};
8301
8302/**
8303 * Insert rows at the bottom of the screen.
8304 *
8305 * @param {Array.<HTMLElement>} rows The rows to insert.
8306 */
8307hterm.Screen.prototype.pushRows = function(rows) {
8308 rows.push.apply(this.rowsArray, rows);
8309};
8310
8311/**
8312 * Insert a row at the specified row of the screen.
8313 *
8314 * @param {integer} index The index to insert the row.
8315 * @param {HTMLElement} row The row to insert.
8316 */
8317hterm.Screen.prototype.insertRow = function(index, row) {
8318 this.rowsArray.splice(index, 0, row);
8319};
8320
8321/**
8322 * Insert rows at the specified row of the screen.
8323 *
8324 * @param {integer} index The index to insert the rows.
8325 * @param {Array.<HTMLElement>} rows The rows to insert.
8326 */
8327hterm.Screen.prototype.insertRows = function(index, rows) {
8328 for (var i = 0; i < rows.length; i++) {
8329 this.rowsArray.splice(index + i, 0, rows[i]);
8330 }
8331};
8332
8333/**
8334 * Remove a row from the screen and return it.
8335 *
8336 * @param {integer} index The index of the row to remove.
8337 * @return {HTMLElement} The selected row.
8338 */
8339hterm.Screen.prototype.removeRow = function(index) {
8340 return this.rowsArray.splice(index, 1)[0];
8341};
8342
8343/**
8344 * Remove rows from the bottom of the screen and return them as an array.
8345 *
8346 * @param {integer} index The index to start removing rows.
8347 * @param {integer} count The number of rows to remove.
8348 * @return {Array.<HTMLElement>} The selected rows.
8349 */
8350hterm.Screen.prototype.removeRows = function(index, count) {
8351 return this.rowsArray.splice(index, count);
8352};
8353
8354/**
8355 * Invalidate the current cursor position.
8356 *
8357 * This sets this.cursorPosition to (0, 0) and clears out some internal
8358 * data.
8359 *
8360 * Attempting to insert or overwrite text while the cursor position is invalid
8361 * will raise an obscure exception.
8362 */
8363hterm.Screen.prototype.invalidateCursorPosition = function() {
8364 this.cursorPosition.move(0, 0);
8365 this.cursorRowNode_ = null;
8366 this.cursorNode_ = null;
8367 this.cursorOffset_ = null;
8368};
8369
8370/**
8371 * Clear the contents of the cursor row.
8372 */
8373hterm.Screen.prototype.clearCursorRow = function() {
8374 this.cursorRowNode_.innerHTML = '';
8375 this.cursorRowNode_.removeAttribute('line-overflow');
8376 this.cursorOffset_ = 0;
8377 this.cursorPosition.column = 0;
8378 this.cursorPosition.overflow = false;
8379
8380 var text;
8381 if (this.textAttributes.isDefault()) {
8382 text = '';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008383 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008384 text = lib.f.getWhitespace(this.columnCount_);
8385 }
8386
8387 // We shouldn't honor inverse colors when clearing an area, to match
8388 // xterm's back color erase behavior.
8389 var inverse = this.textAttributes.inverse;
8390 this.textAttributes.inverse = false;
8391 this.textAttributes.syncColors();
8392
8393 var node = this.textAttributes.createContainer(text);
8394 this.cursorRowNode_.appendChild(node);
8395 this.cursorNode_ = node;
8396
8397 this.textAttributes.inverse = inverse;
8398 this.textAttributes.syncColors();
8399};
8400
8401/**
8402 * Mark the current row as having overflowed to the next line.
8403 *
8404 * The line overflow state is used when converting a range of rows into text.
8405 * It makes it possible to recombine two or more overflow terminal rows into
8406 * a single line.
8407 *
8408 * This is distinct from the cursor being in the overflow state. Cursor
8409 * overflow indicates that printing at the cursor position will commit a
8410 * line overflow, unless it is preceded by a repositioning of the cursor
8411 * to a non-overflow state.
8412 */
8413hterm.Screen.prototype.commitLineOverflow = function() {
8414 this.cursorRowNode_.setAttribute('line-overflow', true);
8415};
8416
8417/**
8418 * Relocate the cursor to a give row and column.
8419 *
8420 * @param {integer} row The zero based row.
8421 * @param {integer} column The zero based column.
8422 */
8423hterm.Screen.prototype.setCursorPosition = function(row, column) {
8424 if (!this.rowsArray.length) {
8425 console.warn('Attempt to set cursor position on empty screen.');
8426 return;
8427 }
8428
8429 if (row >= this.rowsArray.length) {
8430 console.error('Row out of bounds: ' + row);
8431 row = this.rowsArray.length - 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008432 } else if (row < 0) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008433 console.error('Row out of bounds: ' + row);
8434 row = 0;
8435 }
8436
8437 if (column >= this.columnCount_) {
8438 console.error('Column out of bounds: ' + column);
8439 column = this.columnCount_ - 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008440 } else if (column < 0) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008441 console.error('Column out of bounds: ' + column);
8442 column = 0;
8443 }
8444
8445 this.cursorPosition.overflow = false;
8446
8447 var rowNode = this.rowsArray[row];
8448 var node = rowNode.firstChild;
8449
8450 if (!node) {
8451 node = rowNode.ownerDocument.createTextNode('');
8452 rowNode.appendChild(node);
8453 }
8454
8455 var currentColumn = 0;
8456
8457 if (rowNode == this.cursorRowNode_) {
8458 if (column >= this.cursorPosition.column - this.cursorOffset_) {
8459 node = this.cursorNode_;
8460 currentColumn = this.cursorPosition.column - this.cursorOffset_;
8461 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008462 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008463 this.cursorRowNode_ = rowNode;
8464 }
8465
8466 this.cursorPosition.move(row, column);
8467
8468 while (node) {
8469 var offset = column - currentColumn;
8470 var width = hterm.TextAttributes.nodeWidth(node);
8471 if (!node.nextSibling || width > offset) {
8472 this.cursorNode_ = node;
8473 this.cursorOffset_ = offset;
8474 return;
8475 }
8476
8477 currentColumn += width;
8478 node = node.nextSibling;
8479 }
8480};
8481
8482/**
8483 * Set the provided selection object to be a caret selection at the current
8484 * cursor position.
8485 */
8486hterm.Screen.prototype.syncSelectionCaret = function(selection) {
8487 try {
8488 selection.collapse(this.cursorNode_, this.cursorOffset_);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008489 } catch (firefoxIgnoredException) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008490 // FF can throw an exception if the range is off, rather than just not
8491 // performing the collapse.
8492 }
8493};
8494
8495/**
8496 * Split a single node into two nodes at the given offset.
8497 *
8498 * For example:
8499 * Given the DOM fragment '<div><span>Hello World</span></div>', call splitNode_
8500 * passing the span and an offset of 6. This would modify the fragment to
8501 * become: '<div><span>Hello </span><span>World</span></div>'. If the span
8502 * had any attributes they would have been copied to the new span as well.
8503 *
8504 * The to-be-split node must have a container, so that the new node can be
8505 * placed next to it.
8506 *
8507 * @param {HTMLNode} node The node to split.
8508 * @param {integer} offset The offset into the node where the split should
8509 * occur.
8510 */
8511hterm.Screen.prototype.splitNode_ = function(node, offset) {
8512 var afterNode = node.cloneNode(false);
8513
8514 var textContent = node.textContent;
8515 node.textContent = hterm.TextAttributes.nodeSubstr(node, 0, offset);
8516 afterNode.textContent = lib.wc.substr(textContent, offset);
8517
8518 if (afterNode.textContent)
8519 node.parentNode.insertBefore(afterNode, node.nextSibling);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008520 if (!node.textContent) node.parentNode.removeChild(node);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008521};
8522
8523/**
8524 * Ensure that text is clipped and the cursor is clamped to the column count.
8525 */
8526hterm.Screen.prototype.maybeClipCurrentRow = function() {
8527 var width = hterm.TextAttributes.nodeWidth(this.cursorRowNode_);
8528
8529 if (width <= this.columnCount_) {
8530 // Current row does not need clipping, but may need clamping.
8531 if (this.cursorPosition.column >= this.columnCount_) {
8532 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
8533 this.cursorPosition.overflow = true;
8534 }
8535
8536 return;
8537 }
8538
8539 // Save off the current column so we can maybe restore it later.
8540 var currentColumn = this.cursorPosition.column;
8541
8542 // Move the cursor to the final column.
8543 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
8544
8545 // Remove any text that partially overflows.
8546 width = hterm.TextAttributes.nodeWidth(this.cursorNode_);
8547
8548 if (this.cursorOffset_ < width - 1) {
8549 this.cursorNode_.textContent = hterm.TextAttributes.nodeSubstr(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008550 this.cursorNode_, 0, this.cursorOffset_ + 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008551 }
8552
8553 // Remove all nodes after the cursor.
8554 var rowNode = this.cursorRowNode_;
8555 var node = this.cursorNode_.nextSibling;
8556
8557 while (node) {
8558 rowNode.removeChild(node);
8559 node = this.cursorNode_.nextSibling;
8560 }
8561
8562 if (currentColumn < this.columnCount_) {
8563 // If the cursor was within the screen before we started then restore its
8564 // position.
8565 this.setCursorPosition(this.cursorPosition.row, currentColumn);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008566 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008567 // Otherwise leave it at the the last column in the overflow state.
8568 this.cursorPosition.overflow = true;
8569 }
8570};
8571
8572/**
8573 * Insert a string at the current character position using the current
8574 * text attributes.
8575 *
8576 * You must call maybeClipCurrentRow() after in order to clip overflowed
8577 * text and clamp the cursor.
8578 *
8579 * It is also up to the caller to properly maintain the line overflow state
8580 * using hterm.Screen..commitLineOverflow().
8581 */
8582hterm.Screen.prototype.insertString = function(str) {
8583 var cursorNode = this.cursorNode_;
8584 var cursorNodeText = cursorNode.textContent;
8585
8586 this.cursorRowNode_.removeAttribute('line-overflow');
8587
8588 // We may alter the width of the string by prepending some missing
8589 // whitespaces, so we need to record the string width ahead of time.
8590 var strWidth = lib.wc.strWidth(str);
8591
8592 // No matter what, before this function exits the cursor column will have
8593 // moved this much.
8594 this.cursorPosition.column += strWidth;
8595
8596 // Local cache of the cursor offset.
8597 var offset = this.cursorOffset_;
8598
8599 // Reverse offset is the offset measured from the end of the string.
8600 // Zero implies that the cursor is at the end of the cursor node.
8601 var reverseOffset = hterm.TextAttributes.nodeWidth(cursorNode) - offset;
8602
8603 if (reverseOffset < 0) {
8604 // A negative reverse offset means the cursor is positioned past the end
8605 // of the characters on this line. We'll need to insert the missing
8606 // whitespace.
8607 var ws = lib.f.getWhitespace(-reverseOffset);
8608
8609 // This whitespace should be completely unstyled. Underline, background
8610 // color, and strikethrough would be visible on whitespace, so we can't use
8611 // one of those spans to hold the text.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008612 if (!(this.textAttributes.underline || this.textAttributes.strikethrough ||
8613 this.textAttributes.background || this.textAttributes.wcNode ||
8614 this.textAttributes.tileData != null)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008615 // Best case scenario, we can just pretend the spaces were part of the
8616 // original string.
8617 str = ws + str;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008618 } else if (
8619 cursorNode.nodeType == 3 ||
8620 !(cursorNode.wcNode || cursorNode.tileNode ||
8621 cursorNode.style.textDecoration ||
8622 cursorNode.style.backgroundColor)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008623 // Second best case, the current node is able to hold the whitespace.
8624 cursorNode.textContent = (cursorNodeText += ws);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008625 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008626 // Worst case, we have to create a new node to hold the whitespace.
8627 var wsNode = cursorNode.ownerDocument.createTextNode(ws);
8628 this.cursorRowNode_.insertBefore(wsNode, cursorNode.nextSibling);
8629 this.cursorNode_ = cursorNode = wsNode;
8630 this.cursorOffset_ = offset = -reverseOffset;
8631 cursorNodeText = ws;
8632 }
8633
8634 // We now know for sure that we're at the last character of the cursor node.
8635 reverseOffset = 0;
8636 }
8637
8638 if (this.textAttributes.matchesContainer(cursorNode)) {
8639 // The new text can be placed directly in the cursor node.
8640 if (reverseOffset == 0) {
8641 cursorNode.textContent = cursorNodeText + str;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008642 } else if (offset == 0) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008643 cursorNode.textContent = str + cursorNodeText;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008644 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008645 cursorNode.textContent =
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008646 hterm.TextAttributes.nodeSubstr(cursorNode, 0, offset) + str +
8647 hterm.TextAttributes.nodeSubstr(cursorNode, offset);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008648 }
8649
8650 this.cursorOffset_ += strWidth;
8651 return;
8652 }
8653
8654 // The cursor node is the wrong style for the new text. If we're at the
8655 // beginning or end of the cursor node, then the adjacent node is also a
8656 // potential candidate.
8657
8658 if (offset == 0) {
8659 // At the beginning of the cursor node, the check the previous sibling.
8660 var previousSibling = cursorNode.previousSibling;
8661 if (previousSibling &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008662 this.textAttributes.matchesContainer(previousSibling)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008663 previousSibling.textContent += str;
8664 this.cursorNode_ = previousSibling;
8665 this.cursorOffset_ = lib.wc.strWidth(previousSibling.textContent);
8666 return;
8667 }
8668
8669 var newNode = this.textAttributes.createContainer(str);
8670 this.cursorRowNode_.insertBefore(newNode, cursorNode);
8671 this.cursorNode_ = newNode;
8672 this.cursorOffset_ = strWidth;
8673 return;
8674 }
8675
8676 if (reverseOffset == 0) {
8677 // At the end of the cursor node, the check the next sibling.
8678 var nextSibling = cursorNode.nextSibling;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008679 if (nextSibling && this.textAttributes.matchesContainer(nextSibling)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008680 nextSibling.textContent = str + nextSibling.textContent;
8681 this.cursorNode_ = nextSibling;
8682 this.cursorOffset_ = lib.wc.strWidth(str);
8683 return;
8684 }
8685
8686 var newNode = this.textAttributes.createContainer(str);
8687 this.cursorRowNode_.insertBefore(newNode, nextSibling);
8688 this.cursorNode_ = newNode;
8689 // We specifically need to include any missing whitespace here, since it's
8690 // going in a new node.
8691 this.cursorOffset_ = hterm.TextAttributes.nodeWidth(newNode);
8692 return;
8693 }
8694
8695 // Worst case, we're somewhere in the middle of the cursor node. We'll
8696 // have to split it into two nodes and insert our new container in between.
8697 this.splitNode_(cursorNode, offset);
8698 var newNode = this.textAttributes.createContainer(str);
8699 this.cursorRowNode_.insertBefore(newNode, cursorNode.nextSibling);
8700 this.cursorNode_ = newNode;
8701 this.cursorOffset_ = strWidth;
8702};
8703
8704/**
8705 * Overwrite the text at the current cursor position.
8706 *
8707 * You must call maybeClipCurrentRow() after in order to clip overflowed
8708 * text and clamp the cursor.
8709 *
8710 * It is also up to the caller to properly maintain the line overflow state
8711 * using hterm.Screen..commitLineOverflow().
8712 */
8713hterm.Screen.prototype.overwriteString = function(str) {
8714 var maxLength = this.columnCount_ - this.cursorPosition.column;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008715 if (!maxLength) return [str];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008716
8717 var width = lib.wc.strWidth(str);
8718 if (this.textAttributes.matchesContainer(this.cursorNode_) &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008719 this.cursorNode_.textContent.substr(this.cursorOffset_) == str) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008720 // This overwrite would be a no-op, just move the cursor and return.
8721 this.cursorOffset_ += width;
8722 this.cursorPosition.column += width;
8723 return;
8724 }
8725
8726 this.deleteChars(Math.min(width, maxLength));
8727 this.insertString(str);
8728};
8729
8730/**
8731 * Forward-delete one or more characters at the current cursor position.
8732 *
8733 * Text to the right of the deleted characters is shifted left. Only affects
8734 * characters on the same row as the cursor.
8735 *
8736 * @param {integer} count The column width of characters to delete. This is
8737 * clamped to the column width minus the cursor column.
8738 * @return {integer} The column width of the characters actually deleted.
8739 */
8740hterm.Screen.prototype.deleteChars = function(count) {
8741 var node = this.cursorNode_;
8742 var offset = this.cursorOffset_;
8743
8744 var currentCursorColumn = this.cursorPosition.column;
8745 count = Math.min(count, this.columnCount_ - currentCursorColumn);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008746 if (!count) return 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008747
8748 var rv = count;
8749 var startLength, endLength;
8750
8751 while (node && count) {
8752 startLength = hterm.TextAttributes.nodeWidth(node);
8753 node.textContent = hterm.TextAttributes.nodeSubstr(node, 0, offset) +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008754 hterm.TextAttributes.nodeSubstr(node, offset + count);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008755 endLength = hterm.TextAttributes.nodeWidth(node);
8756 count -= startLength - endLength;
8757 if (offset < startLength && endLength && startLength == endLength) {
8758 // No characters were deleted when there should be. We're probably trying
8759 // to delete one column width from a wide character node. We remove the
8760 // wide character node here and replace it with a single space.
8761 var spaceNode = this.textAttributes.createContainer(' ');
8762 node.parentNode.insertBefore(spaceNode, node.nextSibling);
8763 node.textContent = '';
8764 endLength = 0;
8765 count -= 1;
8766 }
8767
8768 var nextNode = node.nextSibling;
8769 if (endLength == 0 && node != this.cursorNode_) {
8770 node.parentNode.removeChild(node);
8771 }
8772 node = nextNode;
8773 offset = 0;
8774 }
8775
8776 // Remove this.cursorNode_ if it is an empty non-text node.
8777 if (this.cursorNode_.nodeType != 3 && !this.cursorNode_.textContent) {
8778 var cursorNode = this.cursorNode_;
8779 if (cursorNode.previousSibling) {
8780 this.cursorNode_ = cursorNode.previousSibling;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008781 this.cursorOffset_ =
8782 hterm.TextAttributes.nodeWidth(cursorNode.previousSibling);
8783 } else if (cursorNode.nextSibling) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008784 this.cursorNode_ = cursorNode.nextSibling;
8785 this.cursorOffset_ = 0;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008786 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008787 var emptyNode = this.cursorRowNode_.ownerDocument.createTextNode('');
8788 this.cursorRowNode_.appendChild(emptyNode);
8789 this.cursorNode_ = emptyNode;
8790 this.cursorOffset_ = 0;
8791 }
8792 this.cursorRowNode_.removeChild(cursorNode);
8793 }
8794
8795 return rv;
8796};
8797
8798/**
8799 * Finds first X-ROW of a line containing specified X-ROW.
8800 * Used to support line overflow.
8801 *
8802 * @param {Node} row X-ROW to begin search for first row of line.
8803 * @return {Node} The X-ROW that is at the beginning of the line.
8804 **/
8805hterm.Screen.prototype.getLineStartRow_ = function(row) {
8806 while (row.previousSibling &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008807 row.previousSibling.hasAttribute('line-overflow')) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008808 row = row.previousSibling;
8809 }
8810 return row;
8811};
8812
8813/**
8814 * Gets text of a line beginning with row.
8815 * Supports line overflow.
8816 *
8817 * @param {Node} row First X-ROW of line.
8818 * @return {string} Text content of line.
8819 **/
8820hterm.Screen.prototype.getLineText_ = function(row) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008821 var rowText = '';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008822 while (row) {
8823 rowText += row.textContent;
8824 if (row.hasAttribute('line-overflow')) {
8825 row = row.nextSibling;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008826 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008827 break;
8828 }
8829 }
8830 return rowText;
8831};
8832
8833/**
8834 * Returns X-ROW that is ancestor of the node.
8835 *
8836 * @param {Node} node Node to get X-ROW ancestor for.
8837 * @return {Node} X-ROW ancestor of node, or null if not found.
8838 **/
8839hterm.Screen.prototype.getXRowAncestor_ = function(node) {
8840 while (node) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008841 if (node.nodeName === 'X-ROW') break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008842 node = node.parentNode;
8843 }
8844 return node;
8845};
8846
8847/**
8848 * Returns position within line of character at offset within node.
8849 * Supports line overflow.
8850 *
8851 * @param {Node} row X-ROW at beginning of line.
8852 * @param {Node} node Node to get position of.
8853 * @param {integer} offset Offset into node.
8854 *
8855 * @return {integer} Position within line of character at offset within node.
8856 **/
8857hterm.Screen.prototype.getPositionWithOverflow_ = function(row, node, offset) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008858 if (!node) return -1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008859 var ancestorRow = this.getXRowAncestor_(node);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008860 if (!ancestorRow) return -1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008861 var position = 0;
8862 while (ancestorRow != row) {
8863 position += hterm.TextAttributes.nodeWidth(row);
8864 if (row.hasAttribute('line-overflow') && row.nextSibling) {
8865 row = row.nextSibling;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008866 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008867 return -1;
8868 }
8869 }
8870 return position + this.getPositionWithinRow_(row, node, offset);
8871};
8872
8873/**
8874 * Returns position within row of character at offset within node.
8875 * Does not support line overflow.
8876 *
8877 * @param {Node} row X-ROW to get position within.
8878 * @param {Node} node Node to get position for.
8879 * @param {integer} offset Offset within node to get position for.
8880 * @return {integer} Position within row of character at offset within node.
8881 **/
8882hterm.Screen.prototype.getPositionWithinRow_ = function(row, node, offset) {
8883 if (node.parentNode != row) {
8884 return this.getPositionWithinRow_(node.parentNode, node, offset) +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008885 this.getPositionWithinRow_(row, node.parentNode, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008886 }
8887 var position = 0;
8888 for (var i = 0; i < row.childNodes.length; i++) {
8889 var currentNode = row.childNodes[i];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008890 if (currentNode == node) return position + offset;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008891 position += hterm.TextAttributes.nodeWidth(currentNode);
8892 }
8893 return -1;
8894};
8895
8896/**
8897 * Returns the node and offset corresponding to position within line.
8898 * Supports line overflow.
8899 *
8900 * @param {Node} row X-ROW at beginning of line.
8901 * @param {integer} position Position within line to retrieve node and offset.
8902 * @return {Array} Two element array containing node and offset respectively.
8903 **/
8904hterm.Screen.prototype.getNodeAndOffsetWithOverflow_ = function(row, position) {
8905 while (row && position > hterm.TextAttributes.nodeWidth(row)) {
8906 if (row.hasAttribute('line-overflow') && row.nextSibling) {
8907 position -= hterm.TextAttributes.nodeWidth(row);
8908 row = row.nextSibling;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008909 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008910 return -1;
8911 }
8912 }
8913 return this.getNodeAndOffsetWithinRow_(row, position);
8914};
8915
8916/**
8917 * Returns the node and offset corresponding to position within row.
8918 * Does not support line overflow.
8919 *
8920 * @param {Node} row X-ROW to get position within.
8921 * @param {integer} position Position within row to retrieve node and offset.
8922 * @return {Array} Two element array containing node and offset respectively.
8923 **/
8924hterm.Screen.prototype.getNodeAndOffsetWithinRow_ = function(row, position) {
8925 for (var i = 0; i < row.childNodes.length; i++) {
8926 var node = row.childNodes[i];
8927 var nodeTextWidth = hterm.TextAttributes.nodeWidth(node);
8928 if (position <= nodeTextWidth) {
8929 if (node.nodeName === 'SPAN') {
8930 /** Drill down to node contained by SPAN. **/
8931 return this.getNodeAndOffsetWithinRow_(node, position);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008932 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008933 return [node, position];
8934 }
8935 }
8936 position -= nodeTextWidth;
8937 }
8938 return null;
8939};
8940
8941/**
8942 * Returns the node and offset corresponding to position within line.
8943 * Supports line overflow.
8944 *
8945 * @param {Node} row X-ROW at beginning of line.
8946 * @param {integer} start Start position of range within line.
8947 * @param {integer} end End position of range within line.
8948 * @param {Range} range Range to modify.
8949 **/
8950hterm.Screen.prototype.setRange_ = function(row, start, end, range) {
8951 var startNodeAndOffset = this.getNodeAndOffsetWithOverflow_(row, start);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008952 if (startNodeAndOffset == null) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008953 var endNodeAndOffset = this.getNodeAndOffsetWithOverflow_(row, end);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008954 if (endNodeAndOffset == null) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008955 range.setStart(startNodeAndOffset[0], startNodeAndOffset[1]);
8956 range.setEnd(endNodeAndOffset[0], endNodeAndOffset[1]);
8957};
8958
8959/**
8960 * Expands selection to surround URLs.
8961 *
8962 * @param {Selection} selection Selection to expand.
8963 **/
8964hterm.Screen.prototype.expandSelection = function(selection) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008965 if (!selection) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008966
8967 var range = selection.getRangeAt(0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008968 if (!range || range.toString().match(/\s/)) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008969
8970 var row = this.getLineStartRow_(this.getXRowAncestor_(range.startContainer));
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008971 if (!row) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008972
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008973 var startPosition = this.getPositionWithOverflow_(
8974 row, range.startContainer, range.startOffset);
8975 if (startPosition == -1) return;
8976 var endPosition =
8977 this.getPositionWithOverflow_(row, range.endContainer, range.endOffset);
8978 if (endPosition == -1) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008979
8980 // Matches can start with '~' or '.', since paths frequently do.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008981 var leftMatch = '[^\\s\\[\\](){}<>"\'\\^!@#$%&*,;:`]';
8982 var rightMatch = '[^\\s\\[\\](){}<>"\'\\^!@#$%&*,;:~.`]';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008983 var insideMatch = '[^\\s\\[\\](){}<>"\'\\^]*';
8984
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008985 // Move start to the left.
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008986 var rowText = this.getLineText_(row);
8987 var lineUpToRange = lib.wc.substring(rowText, 0, endPosition);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008988 var leftRegularExpression = new RegExp(leftMatch + insideMatch + '$');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008989 var expandedStart = lineUpToRange.search(leftRegularExpression);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008990 if (expandedStart == -1 || expandedStart > startPosition) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008991
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008992 // Move end to the right.
8993 var lineFromRange =
8994 lib.wc.substring(rowText, startPosition, lib.wc.strWidth(rowText));
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008995 var rightRegularExpression = new RegExp('^' + insideMatch + rightMatch);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008996 var found = lineFromRange.match(rightRegularExpression);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008997 if (!found) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008998 var expandedEnd = startPosition + lib.wc.strWidth(found[0]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008999 if (expandedEnd == -1 || expandedEnd < endPosition) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009000
9001 this.setRange_(row, expandedStart, expandedEnd, range);
9002 selection.addRange(range);
9003};
9004// SOURCE FILE: hterm/js/hterm_scrollport.js
9005// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
9006// Use of this source code is governed by a BSD-style license that can be
9007// found in the LICENSE file.
9008
9009'use strict';
9010
9011lib.rtdep('lib.f', 'hterm.PubSub', 'hterm.Size');
9012
9013/**
9014 * A 'viewport' view of fixed-height rows with support for selection and
9015 * copy-to-clipboard.
9016 *
9017 * 'Viewport' in this case means that only the visible rows are in the DOM.
9018 * If the rowProvider has 100,000 rows, but the ScrollPort is only 25 rows
9019 * tall, then only 25 dom nodes are created. The ScrollPort will ask the
9020 * RowProvider to create new visible rows on demand as they are scrolled in
9021 * to the visible area.
9022 *
9023 * This viewport is designed so that select and copy-to-clipboard still works,
9024 * even when all or part of the selection is scrolled off screen.
9025 *
9026 * Note that the X11 mouse clipboard does not work properly when all or part
9027 * of the selection is off screen. It would be difficult to fix this without
9028 * adding significant overhead to pathologically large selection cases.
9029 *
9030 * The RowProvider should return rows rooted by the custom tag name 'x-row'.
9031 * This ensures that we can quickly assign the correct display height
9032 * to the rows with css.
9033 *
9034 * @param {RowProvider} rowProvider An object capable of providing rows as
9035 * raw text or row nodes.
9036 */
9037hterm.ScrollPort = function(rowProvider) {
9038 hterm.PubSub.addBehavior(this);
9039
9040 this.rowProvider_ = rowProvider;
9041
9042 // SWAG the character size until we can measure it.
9043 this.characterSize = new hterm.Size(10, 10);
9044
9045 // DOM node used for character measurement.
9046 this.ruler_ = null;
9047
9048 this.selection = new hterm.ScrollPort.Selection(this);
9049
9050 // A map of rowIndex => rowNode for each row that is drawn as part of a
9051 // pending redraw_() call. Null if there is no pending redraw_ call.
9052 this.currentRowNodeCache_ = null;
9053
9054 // A map of rowIndex => rowNode for each row that was drawn as part of the
9055 // previous redraw_() call.
9056 this.previousRowNodeCache_ = {};
9057
9058 // Used during scroll events to detect when the underlying cause is a resize.
9059 this.lastScreenWidth_ = null;
9060 this.lastScreenHeight_ = null;
9061
9062 // True if the user should be allowed to select text in the terminal.
9063 // This is disabled when the host requests mouse drag events so that we don't
9064 // end up with two notions of selection.
9065 this.selectionEnabled_ = true;
9066
9067 // The last row count returned by the row provider, re-populated during
9068 // syncScrollHeight().
9069 this.lastRowCount_ = 0;
9070
9071 // The scroll wheel pixel delta multiplier to increase/decrease
9072 // the scroll speed of mouse wheel events. See: https://goo.gl/sXelnq
9073 this.scrollWheelMultiplier_ = 1;
9074
9075 /**
9076 * True if the last scroll caused the scrollport to show the final row.
9077 */
9078 this.isScrolledEnd = true;
9079
9080 // The css rule that we use to control the height of a row.
9081 this.xrowCssRule_ = null;
9082
9083 /**
9084 * A guess at the current scrollbar width, fixed in resize().
9085 */
9086 this.currentScrollbarWidthPx = 16;
9087
9088 /**
9089 * Whether the ctrl-v key on the screen should paste.
9090 */
9091 this.ctrlVPaste = false;
9092
9093 this.div_ = null;
9094 this.document_ = null;
9095
9096 // Collection of active timeout handles.
9097 this.timeouts_ = {};
9098
9099 this.observers_ = {};
9100
9101 this.DEBUG_ = false;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009102};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009103
9104/**
9105 * Proxy for the native selection object which understands how to walk up the
9106 * DOM to find the containing row node and sort out which comes first.
9107 *
9108 * @param {hterm.ScrollPort} scrollPort The parent hterm.ScrollPort instance.
9109 */
9110hterm.ScrollPort.Selection = function(scrollPort) {
9111 this.scrollPort_ = scrollPort;
9112
9113 /**
9114 * The row containing the start of the selection.
9115 *
9116 * This may be partially or fully selected. It may be the selection anchor
9117 * or the focus, but its rowIndex is guaranteed to be less-than-or-equal-to
9118 * that of the endRow.
9119 *
9120 * If only one row is selected then startRow == endRow. If there is no
9121 * selection or the selection is collapsed then startRow == null.
9122 */
9123 this.startRow = null;
9124
9125 /**
9126 * The row containing the end of the selection.
9127 *
9128 * This may be partially or fully selected. It may be the selection anchor
9129 * or the focus, but its rowIndex is guaranteed to be greater-than-or-equal-to
9130 * that of the startRow.
9131 *
9132 * If only one row is selected then startRow == endRow. If there is no
9133 * selection or the selection is collapsed then startRow == null.
9134 */
9135 this.endRow = null;
9136
9137 /**
9138 * True if startRow != endRow.
9139 */
9140 this.isMultiline = null;
9141
9142 /**
9143 * True if the selection is just a point rather than a range.
9144 */
9145 this.isCollapsed = null;
9146};
9147
9148/**
9149 * Given a list of DOM nodes and a container, return the DOM node that
9150 * is first according to a depth-first search.
9151 *
9152 * Returns null if none of the children are found.
9153 */
9154hterm.ScrollPort.Selection.prototype.findFirstChild = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009155 parent, childAry) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009156 var node = parent.firstChild;
9157
9158 while (node) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009159 if (childAry.indexOf(node) != -1) return node;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009160
9161 if (node.childNodes.length) {
9162 var rv = this.findFirstChild(node, childAry);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009163 if (rv) return rv;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009164 }
9165
9166 node = node.nextSibling;
9167 }
9168
9169 return null;
9170};
9171
9172/**
9173 * Synchronize this object with the current DOM selection.
9174 *
9175 * This is a one-way synchronization, the DOM selection is copied to this
9176 * object, not the other way around.
9177 */
9178hterm.ScrollPort.Selection.prototype.sync = function() {
9179 var self = this;
9180
9181 // The dom selection object has no way to tell which nodes come first in
9182 // the document, so we have to figure that out.
9183 //
9184 // This function is used when we detect that the "anchor" node is first.
9185 function anchorFirst() {
9186 self.startRow = anchorRow;
9187 self.startNode = selection.anchorNode;
9188 self.startOffset = selection.anchorOffset;
9189 self.endRow = focusRow;
9190 self.endNode = selection.focusNode;
9191 self.endOffset = selection.focusOffset;
9192 }
9193
9194 // This function is used when we detect that the "focus" node is first.
9195 function focusFirst() {
9196 self.startRow = focusRow;
9197 self.startNode = selection.focusNode;
9198 self.startOffset = selection.focusOffset;
9199 self.endRow = anchorRow;
9200 self.endNode = selection.anchorNode;
9201 self.endOffset = selection.anchorOffset;
9202 }
9203
9204 var selection = this.scrollPort_.getDocument().getSelection();
9205
9206 this.startRow = null;
9207 this.endRow = null;
9208 this.isMultiline = null;
9209 this.isCollapsed = !selection || selection.isCollapsed;
9210
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009211 if (this.isCollapsed) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009212
9213 var anchorRow = selection.anchorNode;
9214 while (anchorRow && !('rowIndex' in anchorRow)) {
9215 anchorRow = anchorRow.parentNode;
9216 }
9217
9218 if (!anchorRow) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009219 console.error(
9220 'Selection anchor is not rooted in a row node: ' +
9221 selection.anchorNode.nodeName);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009222 return;
9223 }
9224
9225 var focusRow = selection.focusNode;
9226 while (focusRow && !('rowIndex' in focusRow)) {
9227 focusRow = focusRow.parentNode;
9228 }
9229
9230 if (!focusRow) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009231 console.error(
9232 'Selection focus is not rooted in a row node: ' +
9233 selection.focusNode.nodeName);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009234 return;
9235 }
9236
9237 if (anchorRow.rowIndex < focusRow.rowIndex) {
9238 anchorFirst();
9239
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009240 } else if (anchorRow.rowIndex > focusRow.rowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009241 focusFirst();
9242
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009243 } else if (selection.focusNode == selection.anchorNode) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009244 if (selection.anchorOffset < selection.focusOffset) {
9245 anchorFirst();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009246 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009247 focusFirst();
9248 }
9249
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009250 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009251 // The selection starts and ends in the same row, but isn't contained all
9252 // in a single node.
9253 var firstNode = this.findFirstChild(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009254 anchorRow, [selection.anchorNode, selection.focusNode]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009255
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009256 if (!firstNode) throw new Error('Unexpected error syncing selection.');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009257
9258 if (firstNode == selection.anchorNode) {
9259 anchorFirst();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009260 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009261 focusFirst();
9262 }
9263 }
9264
9265 this.isMultiline = anchorRow.rowIndex != focusRow.rowIndex;
9266};
9267
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009268/**
9269 * Turn a div into this hterm.ScrollPort.
9270 */
9271hterm.ScrollPort.prototype.decorate = function(div) {
9272 this.div_ = div;
9273
9274 this.iframe_ = div.ownerDocument.createElement('iframe');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009275 this.iframe_.style.cssText =
9276 ('border: 0;' +
9277 'height: 100%;' +
9278 'position: absolute;' +
9279 'width: 100%');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009280
9281 // Set the iframe src to # in FF. Otherwise when the frame's
9282 // load event fires in FF it clears out the content of the iframe.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009283 if ('mozInnerScreenX' in window) // detect a FF only property
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009284 this.iframe_.src = '#';
9285
9286 div.appendChild(this.iframe_);
9287
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009288 this.iframe_.contentWindow.addEventListener(
9289 'resize', this.onResize_.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009290
9291 var doc = this.document_ = this.iframe_.contentDocument;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009292 doc.body.style.cssText =
9293 ('margin: 0px;' +
9294 'padding: 0px;' +
9295 'height: 100%;' +
9296 'width: 100%;' +
9297 'overflow: hidden;' +
9298 'cursor: text;' +
9299 '-webkit-user-select: none;' +
9300 '-moz-user-select: none;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009301
9302 var style = doc.createElement('style');
9303 style.textContent = 'x-row {}';
9304 doc.head.appendChild(style);
9305
9306 this.xrowCssRule_ = doc.styleSheets[0].cssRules[0];
9307 this.xrowCssRule_.style.display = 'block';
9308
9309 this.userCssLink_ = doc.createElement('link');
9310 this.userCssLink_.setAttribute('rel', 'stylesheet');
9311
9312 // TODO(rginda): Sorry, this 'screen_' isn't the same thing as hterm.Screen
9313 // from screen.js. I need to pick a better name for one of them to avoid
9314 // the collision.
9315 this.screen_ = doc.createElement('x-screen');
9316 this.screen_.setAttribute('role', 'textbox');
9317 this.screen_.setAttribute('tabindex', '-1');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009318 this.screen_.style.cssText =
9319 ('display: block;' +
9320 'font-family: monospace;' +
9321 'font-size: 15px;' +
9322 'font-variant-ligatures: none;' +
9323 'height: 100%;' +
9324 'overflow-y: scroll; overflow-x: hidden;' +
9325 'white-space: pre;' +
9326 'width: 100%;' +
9327 'outline: none !important');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009328
9329 doc.body.appendChild(this.screen_);
9330
9331 this.screen_.addEventListener('scroll', this.onScroll_.bind(this));
9332 this.screen_.addEventListener('mousewheel', this.onScrollWheel_.bind(this));
9333 this.screen_.addEventListener(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009334 'DOMMouseScroll', this.onScrollWheel_.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009335 this.screen_.addEventListener('copy', this.onCopy_.bind(this));
9336 this.screen_.addEventListener('paste', this.onPaste_.bind(this));
9337
9338 doc.body.addEventListener('keydown', this.onBodyKeyDown_.bind(this));
9339
9340 // This is the main container for the fixed rows.
9341 this.rowNodes_ = doc.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009342 this.rowNodes_.style.cssText =
9343 ('display: block;' +
9344 'position: fixed;' +
9345 'overflow: hidden;' +
9346 '-webkit-user-select: text;' +
9347 '-moz-user-select: text;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009348 this.screen_.appendChild(this.rowNodes_);
9349
9350 // Two nodes to hold offscreen text during the copy event.
9351 this.topSelectBag_ = doc.createElement('x-select-bag');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009352 this.topSelectBag_.style.cssText =
9353 ('display: block;' +
9354 'overflow: hidden;' +
9355 'white-space: pre;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009356
9357 this.bottomSelectBag_ = this.topSelectBag_.cloneNode();
9358
9359 // Nodes above the top fold and below the bottom fold are hidden. They are
9360 // only used to hold rows that are part of the selection but are currently
9361 // scrolled off the top or bottom of the visible range.
9362 this.topFold_ = doc.createElement('x-fold');
9363 this.topFold_.style.cssText = 'display: block;';
9364 this.rowNodes_.appendChild(this.topFold_);
9365
9366 this.bottomFold_ = this.topFold_.cloneNode();
9367 this.rowNodes_.appendChild(this.bottomFold_);
9368
9369 // This hidden div accounts for the vertical space that would be consumed by
9370 // all the rows in the buffer if they were visible. It's what causes the
9371 // scrollbar to appear on the 'x-screen', and it moves within the screen when
9372 // the scrollbar is moved.
9373 //
9374 // It is set 'visibility: hidden' to keep the browser from trying to include
9375 // it in the selection when a user 'drag selects' upwards (drag the mouse to
9376 // select and scroll at the same time). Without this, the selection gets
9377 // out of whack.
9378 this.scrollArea_ = doc.createElement('div');
9379 this.scrollArea_.style.cssText = 'visibility: hidden';
9380 this.screen_.appendChild(this.scrollArea_);
9381
9382 // This svg element is used to detect when the browser is zoomed. It must be
9383 // placed in the outermost document for currentScale to be correct.
9384 // TODO(rginda): This means that hterm nested in an iframe will not correctly
9385 // detect browser zoom level. We should come up with a better solution.
9386 // Note: This must be http:// else Chrome cannot create the element correctly.
9387 var xmlns = 'http://www.w3.org/2000/svg';
9388 this.svg_ = this.div_.ownerDocument.createElementNS(xmlns, 'svg');
9389 this.svg_.setAttribute('xmlns', xmlns);
9390 this.svg_.setAttribute('version', '1.1');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009391 this.svg_.style.cssText =
9392 ('position: absolute;' +
9393 'top: 0;' +
9394 'left: 0;' +
9395 'visibility: hidden');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009396
9397 // We send focus to this element just before a paste happens, so we can
9398 // capture the pasted text and forward it on to someone who cares.
9399 this.pasteTarget_ = doc.createElement('textarea');
9400 this.pasteTarget_.setAttribute('tabindex', '-1');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009401 this.pasteTarget_.style.cssText =
9402 ('position: absolute;' +
9403 'height: 1px;' +
9404 'width: 1px;' +
9405 'left: 0px; ' +
9406 'bottom: 0px;' +
9407 'opacity: 0');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009408 this.pasteTarget_.contentEditable = true;
9409
9410 this.screen_.appendChild(this.pasteTarget_);
9411 this.pasteTarget_.addEventListener(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009412 'textInput', this.handlePasteTargetTextInput_.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009413
9414 this.resize();
9415};
9416
9417/**
9418 * Select the font-family and font-smoothing for this scrollport.
9419 *
9420 * @param {string} fontFamily Value of the CSS 'font-family' to use for this
9421 * scrollport. Should be a monospace font.
9422 * @param {string} opt_smoothing Optional value for '-webkit-font-smoothing'.
9423 * Defaults to an empty string if not specified.
9424 */
9425hterm.ScrollPort.prototype.setFontFamily = function(fontFamily, opt_smoothing) {
9426 this.screen_.style.fontFamily = fontFamily;
9427 if (opt_smoothing) {
9428 this.screen_.style.webkitFontSmoothing = opt_smoothing;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009429 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009430 this.screen_.style.webkitFontSmoothing = '';
9431 }
9432
9433 this.syncCharacterSize();
9434};
9435
9436hterm.ScrollPort.prototype.getFontFamily = function() {
9437 return this.screen_.style.fontFamily;
9438};
9439
9440/**
9441 * Set a custom stylesheet to include in the scrollport.
9442 *
9443 * Defaults to null, meaning no custom css is loaded. Set it back to null or
9444 * the empty string to remove a previously applied custom css.
9445 */
9446hterm.ScrollPort.prototype.setUserCss = function(url) {
9447 if (url) {
9448 this.userCssLink_.setAttribute('href', url);
9449
9450 if (!this.userCssLink_.parentNode)
9451 this.document_.head.appendChild(this.userCssLink_);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009452 } else if (this.userCssLink_.parentNode) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009453 this.document_.head.removeChild(this.userCssLink_);
9454 }
9455};
9456
9457hterm.ScrollPort.prototype.focus = function() {
9458 this.iframe_.focus();
9459 this.screen_.focus();
9460};
9461
9462hterm.ScrollPort.prototype.getForegroundColor = function() {
9463 return this.screen_.style.color;
9464};
9465
9466hterm.ScrollPort.prototype.setForegroundColor = function(color) {
9467 this.screen_.style.color = color;
9468};
9469
9470hterm.ScrollPort.prototype.getBackgroundColor = function() {
9471 return this.screen_.style.backgroundColor;
9472};
9473
9474hterm.ScrollPort.prototype.setBackgroundColor = function(color) {
9475 this.screen_.style.backgroundColor = color;
9476};
9477
9478hterm.ScrollPort.prototype.setBackgroundImage = function(image) {
9479 this.screen_.style.backgroundImage = image;
9480};
9481
9482hterm.ScrollPort.prototype.setBackgroundSize = function(size) {
9483 this.screen_.style.backgroundSize = size;
9484};
9485
9486hterm.ScrollPort.prototype.setBackgroundPosition = function(position) {
9487 this.screen_.style.backgroundPosition = position;
9488};
9489
9490hterm.ScrollPort.prototype.setCtrlVPaste = function(ctrlVPaste) {
9491 this.ctrlVPaste = ctrlVPaste;
9492};
9493
9494/**
9495 * Get the usable size of the scrollport screen.
9496 *
9497 * The width will not include the scrollbar width.
9498 */
9499hterm.ScrollPort.prototype.getScreenSize = function() {
9500 var size = hterm.getClientSize(this.screen_);
9501 return {
9502 height: size.height,
9503 width: size.width - this.currentScrollbarWidthPx
9504 };
9505};
9506
9507/**
9508 * Get the usable width of the scrollport screen.
9509 *
9510 * This the widget width minus scrollbar width.
9511 */
9512hterm.ScrollPort.prototype.getScreenWidth = function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009513 return this.getScreenSize().width;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009514};
9515
9516/**
9517 * Get the usable height of the scrollport screen.
9518 */
9519hterm.ScrollPort.prototype.getScreenHeight = function() {
9520 return this.getScreenSize().height;
9521};
9522
9523/**
9524 * Return the document that holds the visible rows of this hterm.ScrollPort.
9525 */
9526hterm.ScrollPort.prototype.getDocument = function() {
9527 return this.document_;
9528};
9529
9530/**
9531 * Returns the x-screen element that holds the rows of this hterm.ScrollPort.
9532 */
9533hterm.ScrollPort.prototype.getScreenNode = function() {
9534 return this.screen_;
9535};
9536
9537/**
9538 * Clear out any cached rowNodes.
9539 */
9540hterm.ScrollPort.prototype.resetCache = function() {
9541 this.currentRowNodeCache_ = null;
9542 this.previousRowNodeCache_ = {};
9543};
9544
9545/**
9546 * Change the current rowProvider.
9547 *
9548 * This will clear the row cache and cause a redraw.
9549 *
9550 * @param {Object} rowProvider An object capable of providing the rows
9551 * in this hterm.ScrollPort.
9552 */
9553hterm.ScrollPort.prototype.setRowProvider = function(rowProvider) {
9554 this.resetCache();
9555 this.rowProvider_ = rowProvider;
9556 this.scheduleRedraw();
9557};
9558
9559/**
9560 * Inform the ScrollPort that the root DOM nodes for some or all of the visible
9561 * rows are no longer valid.
9562 *
9563 * Specifically, this should be called if this.rowProvider_.getRowNode() now
9564 * returns an entirely different node than it did before. It does not
9565 * need to be called if the content of a row node is the only thing that
9566 * changed.
9567 *
9568 * This skips some of the overhead of a full redraw, but should not be used
9569 * in cases where the scrollport has been scrolled, or when the row count has
9570 * changed.
9571 */
9572hterm.ScrollPort.prototype.invalidate = function() {
9573 var node = this.topFold_.nextSibling;
9574 while (node != this.bottomFold_) {
9575 var nextSibling = node.nextSibling;
9576 node.parentElement.removeChild(node);
9577 node = nextSibling;
9578 }
9579
9580 this.previousRowNodeCache_ = null;
9581 var topRowIndex = this.getTopRowIndex();
9582 var bottomRowIndex = this.getBottomRowIndex(topRowIndex);
9583
9584 this.drawVisibleRows_(topRowIndex, bottomRowIndex);
9585};
9586
9587hterm.ScrollPort.prototype.scheduleInvalidate = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009588 if (this.timeouts_.invalidate) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009589
9590 var self = this;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009591 this.timeouts_.invalidate = setTimeout(function() {
9592 delete self.timeouts_.invalidate;
9593 self.invalidate();
9594 }, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009595};
9596
9597/**
9598 * Set the font size of the ScrollPort.
9599 */
9600hterm.ScrollPort.prototype.setFontSize = function(px) {
9601 this.screen_.style.fontSize = px + 'px';
9602 this.syncCharacterSize();
9603};
9604
9605/**
9606 * Return the current font size of the ScrollPort.
9607 */
9608hterm.ScrollPort.prototype.getFontSize = function() {
9609 return parseInt(this.screen_.style.fontSize);
9610};
9611
9612/**
9613 * Measure the size of a single character in pixels.
9614 *
9615 * @param {string} opt_weight The font weight to measure, or 'normal' if
9616 * omitted.
9617 * @return {hterm.Size} A new hterm.Size object.
9618 */
9619hterm.ScrollPort.prototype.measureCharacterSize = function(opt_weight) {
9620 // Number of lines used to average the height of a single character.
9621 var numberOfLines = 100;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009622 var rulerSingleLineContents =
9623 ('XXXXXXXXXXXXXXXXXXXX' +
9624 'XXXXXXXXXXXXXXXXXXXX' +
9625 'XXXXXXXXXXXXXXXXXXXX' +
9626 'XXXXXXXXXXXXXXXXXXXX' +
9627 'XXXXXXXXXXXXXXXXXXXX');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009628 if (!this.ruler_) {
9629 this.ruler_ = this.document_.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009630 this.ruler_.style.cssText =
9631 ('position: absolute;' +
9632 'top: 0;' +
9633 'left: 0;' +
9634 'visibility: hidden;' +
9635 'height: auto !important;' +
9636 'width: auto !important;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009637
9638 // We need to put the text in a span to make the size calculation
9639 // work properly in Firefox
9640 this.rulerSpan_ = this.document_.createElement('span');
9641 var rulerContents = '' + rulerSingleLineContents;
9642 for (var i = 0; i < numberOfLines - 1; ++i)
9643 rulerContents += String.fromCharCode(13) + rulerSingleLineContents;
9644
9645 this.rulerSpan_.innerHTML = rulerContents;
9646 this.ruler_.appendChild(this.rulerSpan_);
9647
9648 this.rulerBaseline_ = this.document_.createElement('span');
9649 // We want to collapse it on the baseline
9650 this.rulerBaseline_.style.fontSize = '0px';
9651 this.rulerBaseline_.textContent = 'X';
9652 }
9653
9654 this.rulerSpan_.style.fontWeight = opt_weight || '';
9655
9656 this.rowNodes_.appendChild(this.ruler_);
9657 var rulerSize = hterm.getClientSize(this.rulerSpan_);
9658
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009659 var size = new hterm.Size(
9660 rulerSize.width / rulerSingleLineContents.length,
9661 rulerSize.height / numberOfLines);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009662
9663 this.ruler_.appendChild(this.rulerBaseline_);
9664 size.baseline = this.rulerBaseline_.offsetTop;
9665 this.ruler_.removeChild(this.rulerBaseline_);
9666
9667 this.rowNodes_.removeChild(this.ruler_);
9668
9669 this.div_.ownerDocument.body.appendChild(this.svg_);
9670 size.zoomFactor = this.svg_.currentScale;
9671 this.div_.ownerDocument.body.removeChild(this.svg_);
9672
9673 return size;
9674};
9675
9676/**
9677 * Synchronize the character size.
9678 *
9679 * This will re-measure the current character size and adjust the height
9680 * of an x-row to match.
9681 */
9682hterm.ScrollPort.prototype.syncCharacterSize = function() {
9683 this.characterSize = this.measureCharacterSize();
9684
9685 var lineHeight = this.characterSize.height + 'px';
9686 this.xrowCssRule_.style.height = lineHeight;
9687 this.topSelectBag_.style.height = lineHeight;
9688 this.bottomSelectBag_.style.height = lineHeight;
9689
9690 this.resize();
9691
9692 if (this.DEBUG_) {
9693 // When we're debugging we add padding to the body so that the offscreen
9694 // elements are visible.
9695 this.document_.body.style.paddingTop =
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009696 this.document_.body.style.paddingBottom =
9697 3 * this.characterSize.height + 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009698 }
9699};
9700
9701/**
9702 * Reset dimensions and visible row count to account for a change in the
9703 * dimensions of the 'x-screen'.
9704 */
9705hterm.ScrollPort.prototype.resize = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009706 this.currentScrollbarWidthPx =
9707 hterm.getClientWidth(this.screen_) - this.screen_.clientWidth;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009708
9709 this.syncScrollHeight();
9710 this.syncRowNodesDimensions_();
9711
9712 var self = this;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009713 this.publish('resize', {scrollPort: this}, function() {
9714 self.scrollRowToBottom(self.rowProvider_.getRowCount());
9715 self.scheduleRedraw();
9716 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009717};
9718
9719/**
9720 * Set the position and size of the row nodes element.
9721 */
9722hterm.ScrollPort.prototype.syncRowNodesDimensions_ = function() {
9723 var screenSize = this.getScreenSize();
9724
9725 this.lastScreenWidth_ = screenSize.width;
9726 this.lastScreenHeight_ = screenSize.height;
9727
9728 // We don't want to show a partial row because it would be distracting
9729 // in a terminal, so we floor any fractional row count.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009730 this.visibleRowCount =
9731 lib.f.smartFloorDivide(screenSize.height, this.characterSize.height);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009732
9733 // Then compute the height of our integral number of rows.
9734 var visibleRowsHeight = this.visibleRowCount * this.characterSize.height;
9735
9736 // Then the difference between the screen height and total row height needs to
9737 // be made up for as top margin. We need to record this value so it
9738 // can be used later to determine the topRowIndex.
9739 this.visibleRowTopMargin = 0;
9740 this.visibleRowBottomMargin = screenSize.height - visibleRowsHeight;
9741
9742 this.topFold_.style.marginBottom = this.visibleRowTopMargin + 'px';
9743
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009744 var topFoldOffset = 0;
9745 var node = this.topFold_.previousSibling;
9746 while (node) {
9747 topFoldOffset += hterm.getClientHeight(node);
9748 node = node.previousSibling;
9749 }
9750
9751 // Set the dimensions of the visible rows container.
9752 this.rowNodes_.style.width = screenSize.width + 'px';
9753 this.rowNodes_.style.height = visibleRowsHeight + topFoldOffset + 'px';
9754 this.rowNodes_.style.left = this.screen_.offsetLeft + 'px';
9755 this.rowNodes_.style.top = this.screen_.offsetTop - topFoldOffset + 'px';
9756};
9757
9758hterm.ScrollPort.prototype.syncScrollHeight = function() {
9759 // Resize the scroll area to appear as though it contains every row.
9760 this.lastRowCount_ = this.rowProvider_.getRowCount();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009761 this.scrollArea_.style.height =
9762 (this.characterSize.height * this.lastRowCount_ +
9763 this.visibleRowTopMargin + this.visibleRowBottomMargin + 'px');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009764};
9765
9766/**
9767 * Schedule a redraw to happen asynchronously.
9768 *
9769 * If this method is called multiple times before the redraw has a chance to
9770 * run only one redraw occurs.
9771 */
9772hterm.ScrollPort.prototype.scheduleRedraw = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009773 if (this.timeouts_.redraw) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009774
9775 var self = this;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009776 this.timeouts_.redraw = setTimeout(function() {
9777 delete self.timeouts_.redraw;
9778 self.redraw_();
9779 }, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009780};
9781
9782/**
9783 * Redraw the current hterm.ScrollPort based on the current scrollbar position.
9784 *
9785 * When redrawing, we are careful to make sure that the rows that start or end
9786 * the current selection are not touched in any way. Doing so would disturb
9787 * the selection, and cleaning up after that would cause flashes at best and
9788 * incorrect selection at worst. Instead, we modify the DOM around these nodes.
9789 * We even stash the selection start/end outside of the visible area if
9790 * they are not supposed to be visible in the hterm.ScrollPort.
9791 */
9792hterm.ScrollPort.prototype.redraw_ = function() {
9793 this.resetSelectBags_();
9794 this.selection.sync();
9795
9796 this.syncScrollHeight();
9797
9798 this.currentRowNodeCache_ = {};
9799
9800 var topRowIndex = this.getTopRowIndex();
9801 var bottomRowIndex = this.getBottomRowIndex(topRowIndex);
9802
9803 this.drawTopFold_(topRowIndex);
9804 this.drawBottomFold_(bottomRowIndex);
9805 this.drawVisibleRows_(topRowIndex, bottomRowIndex);
9806
9807 this.syncRowNodesDimensions_();
9808
9809 this.previousRowNodeCache_ = this.currentRowNodeCache_;
9810 this.currentRowNodeCache_ = null;
9811
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009812 this.isScrolledEnd =
9813 (this.getTopRowIndex() + this.visibleRowCount >= this.lastRowCount_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009814};
9815
9816/**
9817 * Ensure that the nodes above the top fold are as they should be.
9818 *
9819 * If the selection start and/or end nodes are above the visible range
9820 * of this hterm.ScrollPort then the dom will be adjusted so that they appear
9821 * before the top fold (the first x-fold element, aka this.topFold).
9822 *
9823 * If not, the top fold will be the first element.
9824 *
9825 * It is critical that this method does not move the selection nodes. Doing
9826 * so would clear the current selection. Instead, the rest of the DOM is
9827 * adjusted around them.
9828 */
9829hterm.ScrollPort.prototype.drawTopFold_ = function(topRowIndex) {
9830 if (!this.selection.startRow ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009831 this.selection.startRow.rowIndex >= topRowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009832 // Selection is entirely below the top fold, just make sure the fold is
9833 // the first child.
9834 if (this.rowNodes_.firstChild != this.topFold_)
9835 this.rowNodes_.insertBefore(this.topFold_, this.rowNodes_.firstChild);
9836
9837 return;
9838 }
9839
9840 if (!this.selection.isMultiline ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009841 this.selection.endRow.rowIndex >= topRowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009842 // Only the startRow is above the fold.
9843 if (this.selection.startRow.nextSibling != this.topFold_)
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009844 this.rowNodes_.insertBefore(
9845 this.topFold_, this.selection.startRow.nextSibling);
9846 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009847 // Both rows are above the fold.
9848 if (this.selection.endRow.nextSibling != this.topFold_) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009849 this.rowNodes_.insertBefore(
9850 this.topFold_, this.selection.endRow.nextSibling);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009851 }
9852
9853 // Trim any intermediate lines.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009854 while (this.selection.startRow.nextSibling != this.selection.endRow) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009855 this.rowNodes_.removeChild(this.selection.startRow.nextSibling);
9856 }
9857 }
9858
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009859 while (this.rowNodes_.firstChild != this.selection.startRow) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009860 this.rowNodes_.removeChild(this.rowNodes_.firstChild);
9861 }
9862};
9863
9864/**
9865 * Ensure that the nodes below the bottom fold are as they should be.
9866 *
9867 * If the selection start and/or end nodes are below the visible range
9868 * of this hterm.ScrollPort then the dom will be adjusted so that they appear
9869 * after the bottom fold (the second x-fold element, aka this.bottomFold).
9870 *
9871 * If not, the bottom fold will be the last element.
9872 *
9873 * It is critical that this method does not move the selection nodes. Doing
9874 * so would clear the current selection. Instead, the rest of the DOM is
9875 * adjusted around them.
9876 */
9877hterm.ScrollPort.prototype.drawBottomFold_ = function(bottomRowIndex) {
9878 if (!this.selection.endRow ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009879 this.selection.endRow.rowIndex <= bottomRowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009880 // Selection is entirely above the bottom fold, just make sure the fold is
9881 // the last child.
9882 if (this.rowNodes_.lastChild != this.bottomFold_)
9883 this.rowNodes_.appendChild(this.bottomFold_);
9884
9885 return;
9886 }
9887
9888 if (!this.selection.isMultiline ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009889 this.selection.startRow.rowIndex <= bottomRowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009890 // Only the endRow is below the fold.
9891 if (this.bottomFold_.nextSibling != this.selection.endRow)
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009892 this.rowNodes_.insertBefore(this.bottomFold_, this.selection.endRow);
9893 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009894 // Both rows are below the fold.
9895 if (this.bottomFold_.nextSibling != this.selection.startRow) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009896 this.rowNodes_.insertBefore(this.bottomFold_, this.selection.startRow);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009897 }
9898
9899 // Trim any intermediate lines.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009900 while (this.selection.startRow.nextSibling != this.selection.endRow) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009901 this.rowNodes_.removeChild(this.selection.startRow.nextSibling);
9902 }
9903 }
9904
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009905 while (this.rowNodes_.lastChild != this.selection.endRow) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009906 this.rowNodes_.removeChild(this.rowNodes_.lastChild);
9907 }
9908};
9909
9910/**
9911 * Ensure that the rows between the top and bottom folds are as they should be.
9912 *
9913 * This method assumes that drawTopFold_() and drawBottomFold_() have already
9914 * run, and that they have left any visible selection row (selection start
9915 * or selection end) between the folds.
9916 *
9917 * It recycles DOM nodes from the previous redraw where possible, but will ask
9918 * the rowSource to make new nodes if necessary.
9919 *
9920 * It is critical that this method does not move the selection nodes. Doing
9921 * so would clear the current selection. Instead, the rest of the DOM is
9922 * adjusted around them.
9923 */
9924hterm.ScrollPort.prototype.drawVisibleRows_ = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009925 topRowIndex, bottomRowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009926 var self = this;
9927
9928 // Keep removing nodes, starting with currentNode, until we encounter
9929 // targetNode. Throws on failure.
9930 function removeUntilNode(currentNode, targetNode) {
9931 while (currentNode != targetNode) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009932 if (!currentNode) throw 'Did not encounter target node';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009933
9934 if (currentNode == self.bottomFold_)
9935 throw 'Encountered bottom fold before target node';
9936
9937 var deadNode = currentNode;
9938 currentNode = currentNode.nextSibling;
9939 deadNode.parentNode.removeChild(deadNode);
9940 }
9941 }
9942
9943 // Shorthand for things we're going to use a lot.
9944 var selectionStartRow = this.selection.startRow;
9945 var selectionEndRow = this.selection.endRow;
9946 var bottomFold = this.bottomFold_;
9947
9948 // The node we're examining during the current iteration.
9949 var node = this.topFold_.nextSibling;
9950
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009951 var targetDrawCount =
9952 Math.min(this.visibleRowCount, this.rowProvider_.getRowCount());
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009953
9954 for (var drawCount = 0; drawCount < targetDrawCount; drawCount++) {
9955 var rowIndex = topRowIndex + drawCount;
9956
9957 if (node == bottomFold) {
9958 // We've hit the bottom fold, we need to insert a new row.
9959 var newNode = this.fetchRowNode_(rowIndex);
9960 if (!newNode) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009961 console.log('Couldn\'t fetch row index: ' + rowIndex);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009962 break;
9963 }
9964
9965 this.rowNodes_.insertBefore(newNode, node);
9966 continue;
9967 }
9968
9969 if (node.rowIndex == rowIndex) {
9970 // This node is in the right place, move along.
9971 node = node.nextSibling;
9972 continue;
9973 }
9974
9975 if (selectionStartRow && selectionStartRow.rowIndex == rowIndex) {
9976 // The selection start row is supposed to be here, remove nodes until
9977 // we find it.
9978 removeUntilNode(node, selectionStartRow);
9979 node = selectionStartRow.nextSibling;
9980 continue;
9981 }
9982
9983 if (selectionEndRow && selectionEndRow.rowIndex == rowIndex) {
9984 // The selection end row is supposed to be here, remove nodes until
9985 // we find it.
9986 removeUntilNode(node, selectionEndRow);
9987 node = selectionEndRow.nextSibling;
9988 continue;
9989 }
9990
9991 if (node == selectionStartRow || node == selectionEndRow) {
9992 // We encountered the start/end of the selection, but we don't want it
9993 // yet. Insert a new row instead.
9994 var newNode = this.fetchRowNode_(rowIndex);
9995 if (!newNode) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009996 console.log('Couldn\'t fetch row index: ' + rowIndex);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009997 break;
9998 }
9999
10000 this.rowNodes_.insertBefore(newNode, node);
10001 continue;
10002 }
10003
10004 // There is nothing special about this node, but it's in our way. Replace
10005 // it with the node that should be here.
10006 var newNode = this.fetchRowNode_(rowIndex);
10007 if (!newNode) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010008 console.log('Couldn\'t fetch row index: ' + rowIndex);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010009 break;
10010 }
10011
10012 if (node == newNode) {
10013 node = node.nextSibling;
10014 continue;
10015 }
10016
10017 this.rowNodes_.insertBefore(newNode, node);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010018 if (!newNode.nextSibling) debugger;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010019 this.rowNodes_.removeChild(node);
10020 node = newNode.nextSibling;
10021 }
10022
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010023 if (node != this.bottomFold_) removeUntilNode(node, bottomFold);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010024};
10025
10026/**
10027 * Empty out both select bags and remove them from the document.
10028 *
10029 * These nodes hold the text between the start and end of the selection
10030 * when that text is otherwise off screen. They are filled out in the
10031 * onCopy_ event.
10032 */
10033hterm.ScrollPort.prototype.resetSelectBags_ = function() {
10034 if (this.topSelectBag_.parentNode) {
10035 this.topSelectBag_.textContent = '';
10036 this.topSelectBag_.parentNode.removeChild(this.topSelectBag_);
10037 }
10038
10039 if (this.bottomSelectBag_.parentNode) {
10040 this.bottomSelectBag_.textContent = '';
10041 this.bottomSelectBag_.parentNode.removeChild(this.bottomSelectBag_);
10042 }
10043};
10044
10045/**
10046 * Place a row node in the cache of visible nodes.
10047 *
10048 * This method may only be used during a redraw_.
10049 */
10050hterm.ScrollPort.prototype.cacheRowNode_ = function(rowNode) {
10051 this.currentRowNodeCache_[rowNode.rowIndex] = rowNode;
10052};
10053
10054/**
10055 * Fetch the row node for the given index.
10056 *
10057 * This will return a node from the cache if possible, or will request one
10058 * from the RowProvider if not.
10059 *
10060 * If a redraw_ is in progress the row will be added to the current cache.
10061 */
10062hterm.ScrollPort.prototype.fetchRowNode_ = function(rowIndex) {
10063 var node;
10064
10065 if (this.previousRowNodeCache_ && rowIndex in this.previousRowNodeCache_) {
10066 node = this.previousRowNodeCache_[rowIndex];
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010067 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010068 node = this.rowProvider_.getRowNode(rowIndex);
10069 }
10070
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010071 if (this.currentRowNodeCache_) this.cacheRowNode_(node);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010072
10073 return node;
10074};
10075
10076/**
10077 * Select all rows in the viewport.
10078 */
10079hterm.ScrollPort.prototype.selectAll = function() {
10080 var firstRow;
10081
10082 if (this.topFold_.nextSibling.rowIndex != 0) {
10083 while (this.topFold_.previousSibling) {
10084 this.rowNodes_.removeChild(this.topFold_.previousSibling);
10085 }
10086
10087 firstRow = this.fetchRowNode_(0);
10088 this.rowNodes_.insertBefore(firstRow, this.topFold_);
10089 this.syncRowNodesDimensions_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010090 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010091 firstRow = this.topFold_.nextSibling;
10092 }
10093
10094 var lastRowIndex = this.rowProvider_.getRowCount() - 1;
10095 var lastRow;
10096
10097 if (this.bottomFold_.previousSibling.rowIndex != lastRowIndex) {
10098 while (this.bottomFold_.nextSibling) {
10099 this.rowNodes_.removeChild(this.bottomFold_.nextSibling);
10100 }
10101
10102 lastRow = this.fetchRowNode_(lastRowIndex);
10103 this.rowNodes_.appendChild(lastRow);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010104 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010105 lastRow = this.bottomFold_.previousSibling.rowIndex;
10106 }
10107
10108 var selection = this.document_.getSelection();
10109 selection.collapse(firstRow, 0);
10110 selection.extend(lastRow, lastRow.childNodes.length);
10111
10112 this.selection.sync();
10113};
10114
10115/**
10116 * Return the maximum scroll position in pixels.
10117 */
10118hterm.ScrollPort.prototype.getScrollMax_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010119 return (
10120 hterm.getClientHeight(this.scrollArea_) + this.visibleRowTopMargin +
10121 this.visibleRowBottomMargin - hterm.getClientHeight(this.screen_));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010122};
10123
10124/**
10125 * Scroll the given rowIndex to the top of the hterm.ScrollPort.
10126 *
10127 * @param {integer} rowIndex Index of the target row.
10128 */
10129hterm.ScrollPort.prototype.scrollRowToTop = function(rowIndex) {
10130 this.syncScrollHeight();
10131
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010132 this.isScrolledEnd = (rowIndex + this.visibleRowCount >= this.lastRowCount_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010133
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010134 var scrollTop =
10135 rowIndex * this.characterSize.height + this.visibleRowTopMargin;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010136
10137 var scrollMax = this.getScrollMax_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010138 if (scrollTop > scrollMax) scrollTop = scrollMax;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010139
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010140 if (this.screen_.scrollTop == scrollTop) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010141
10142 this.screen_.scrollTop = scrollTop;
10143 this.scheduleRedraw();
10144};
10145
10146/**
10147 * Scroll the given rowIndex to the bottom of the hterm.ScrollPort.
10148 *
10149 * @param {integer} rowIndex Index of the target row.
10150 */
10151hterm.ScrollPort.prototype.scrollRowToBottom = function(rowIndex) {
10152 this.syncScrollHeight();
10153
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010154 this.isScrolledEnd = (rowIndex + this.visibleRowCount >= this.lastRowCount_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010155
10156 var scrollTop = rowIndex * this.characterSize.height +
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010157 this.visibleRowTopMargin + this.visibleRowBottomMargin;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010158 scrollTop -= this.visibleRowCount * this.characterSize.height;
10159
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010160 if (scrollTop < 0) scrollTop = 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010161
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010162 if (this.screen_.scrollTop == scrollTop) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010163
10164 this.screen_.scrollTop = scrollTop;
10165};
10166
10167/**
10168 * Return the row index of the first visible row.
10169 *
10170 * This is based on the scroll position. If a redraw_ is in progress this
10171 * returns the row that *should* be at the top.
10172 */
10173hterm.ScrollPort.prototype.getTopRowIndex = function() {
10174 return Math.round(this.screen_.scrollTop / this.characterSize.height);
10175};
10176
10177/**
10178 * Return the row index of the last visible row.
10179 *
10180 * This is based on the scroll position. If a redraw_ is in progress this
10181 * returns the row that *should* be at the bottom.
10182 */
10183hterm.ScrollPort.prototype.getBottomRowIndex = function(topRowIndex) {
10184 return topRowIndex + this.visibleRowCount - 1;
10185};
10186
10187/**
10188 * Handler for scroll events.
10189 *
10190 * The onScroll event fires when scrollArea's scrollTop property changes. This
10191 * may be due to the user manually move the scrollbar, or a programmatic change.
10192 */
10193hterm.ScrollPort.prototype.onScroll_ = function(e) {
10194 var screenSize = this.getScreenSize();
10195 if (screenSize.width != this.lastScreenWidth_ ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010196 screenSize.height != this.lastScreenHeight_) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010197 // This event may also fire during a resize (but before the resize event!).
10198 // This happens when the browser moves the scrollbar as part of the resize.
10199 // In these cases, we want to ignore the scroll event and let onResize
10200 // handle things. If we don't, then we end up scrolling to the wrong
10201 // position after a resize.
10202 this.resize();
10203 return;
10204 }
10205
10206 this.redraw_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010207 this.publish('scroll', {scrollPort: this});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010208};
10209
10210/**
10211 * Clients can override this if they want to hear scrollwheel events.
10212 *
10213 * Clients may call event.preventDefault() if they want to keep the scrollport
10214 * from also handling the events.
10215 */
10216hterm.ScrollPort.prototype.onScrollWheel = function(e) {};
10217
10218/**
10219 * Handler for scroll-wheel events.
10220 *
10221 * The onScrollWheel event fires when the user moves their scrollwheel over this
10222 * hterm.ScrollPort. Because the frontmost element in the hterm.ScrollPort is
10223 * a fixed position DIV, the scroll wheel does nothing by default. Instead, we
10224 * have to handle it manually.
10225 */
10226hterm.ScrollPort.prototype.onScrollWheel_ = function(e) {
10227 this.onScrollWheel(e);
10228
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010229 if (e.defaultPrevented) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010230
10231 // In FF, the event is DOMMouseScroll and puts the scroll pixel delta in the
10232 // 'detail' field of the event. It also flips the mapping of which direction
10233 // a negative number means in the scroll.
10234 var delta = e.type == 'DOMMouseScroll' ? (-1 * e.detail) : e.wheelDeltaY;
10235 delta *= this.scrollWheelMultiplier_;
10236
10237 var top = this.screen_.scrollTop - delta;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010238 if (top < 0) top = 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010239
10240 var scrollMax = this.getScrollMax_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010241 if (top > scrollMax) top = scrollMax;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010242
10243 if (top != this.screen_.scrollTop) {
10244 // Moving scrollTop causes a scroll event, which triggers the redraw.
10245 this.screen_.scrollTop = top;
10246
10247 // Only preventDefault when we've actually scrolled. If there's nothing
10248 // to scroll we want to pass the event through so Chrome can detect the
10249 // overscroll.
10250 e.preventDefault();
10251 }
10252};
10253
10254/**
10255 * Handler for resize events.
10256 *
10257 * The browser will resize us such that the top row stays at the top, but we
10258 * prefer to the bottom row to stay at the bottom.
10259 */
10260hterm.ScrollPort.prototype.onResize_ = function(e) {
10261 // Re-measure, since onResize also happens for browser zoom changes.
10262 this.syncCharacterSize();
10263 this.resize();
10264};
10265
10266/**
10267 * Clients can override this if they want to hear copy events.
10268 *
10269 * Clients may call event.preventDefault() if they want to keep the scrollport
10270 * from also handling the events.
10271 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010272hterm.ScrollPort.prototype.onCopy = function(e) {};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010273
10274/**
10275 * Handler for copy-to-clipboard events.
10276 *
10277 * If some or all of the selected rows are off screen we may need to fill in
10278 * the rows between selection start and selection end. This handler determines
10279 * if we're missing some of the selected text, and if so populates one or both
10280 * of the "select bags" with the missing text.
10281 */
10282hterm.ScrollPort.prototype.onCopy_ = function(e) {
10283 this.onCopy(e);
10284
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010285 if (e.defaultPrevented) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010286
10287 this.resetSelectBags_();
10288 this.selection.sync();
10289
10290 if (!this.selection.startRow ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010291 this.selection.endRow.rowIndex - this.selection.startRow.rowIndex < 2) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010292 return;
10293 }
10294
10295 var topRowIndex = this.getTopRowIndex();
10296 var bottomRowIndex = this.getBottomRowIndex(topRowIndex);
10297
10298 if (this.selection.startRow.rowIndex < topRowIndex) {
10299 // Start of selection is above the top fold.
10300 var endBackfillIndex;
10301
10302 if (this.selection.endRow.rowIndex < topRowIndex) {
10303 // Entire selection is above the top fold.
10304 endBackfillIndex = this.selection.endRow.rowIndex;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010305 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010306 // Selection extends below the top fold.
10307 endBackfillIndex = this.topFold_.nextSibling.rowIndex;
10308 }
10309
10310 this.topSelectBag_.textContent = this.rowProvider_.getRowsText(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010311 this.selection.startRow.rowIndex + 1, endBackfillIndex);
10312 this.rowNodes_.insertBefore(
10313 this.topSelectBag_, this.selection.startRow.nextSibling);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010314 this.syncRowNodesDimensions_();
10315 }
10316
10317 if (this.selection.endRow.rowIndex > bottomRowIndex) {
10318 // Selection ends below the bottom fold.
10319 var startBackfillIndex;
10320
10321 if (this.selection.startRow.rowIndex > bottomRowIndex) {
10322 // Entire selection is below the bottom fold.
10323 startBackfillIndex = this.selection.startRow.rowIndex + 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010324 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010325 // Selection starts above the bottom fold.
10326 startBackfillIndex = this.bottomFold_.previousSibling.rowIndex + 1;
10327 }
10328
10329 this.bottomSelectBag_.textContent = this.rowProvider_.getRowsText(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010330 startBackfillIndex, this.selection.endRow.rowIndex);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010331 this.rowNodes_.insertBefore(this.bottomSelectBag_, this.selection.endRow);
10332 }
10333};
10334
10335/**
10336 * Focuses on the paste target on a ctrl-v keydown event, as in
10337 * FF a content editable element must be focused before the paste event.
10338 */
10339hterm.ScrollPort.prototype.onBodyKeyDown_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010340 if (!this.ctrlVPaste) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010341
10342 var key = String.fromCharCode(e.which);
10343 var lowerKey = key.toLowerCase();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010344 if ((e.ctrlKey || e.metaKey) && lowerKey == 'v') this.pasteTarget_.focus();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010345};
10346
10347/**
10348 * Handle a paste event on the the ScrollPort's screen element.
10349 */
10350hterm.ScrollPort.prototype.onPaste_ = function(e) {
10351 this.pasteTarget_.focus();
10352
10353 var self = this;
10354 setTimeout(function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010355 self.publish('paste', {text: self.pasteTarget_.value});
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010356 self.pasteTarget_.value = '';
10357 self.screen_.focus();
10358 }, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010359};
10360
10361/**
10362 * Handles a textInput event on the paste target. Stops this from
10363 * propagating as we want this to be handled in the onPaste_ method.
10364 */
10365hterm.ScrollPort.prototype.handlePasteTargetTextInput_ = function(e) {
10366 e.stopPropagation();
10367};
10368
10369/**
10370 * Set the vertical scrollbar mode of the ScrollPort.
10371 */
10372hterm.ScrollPort.prototype.setScrollbarVisible = function(state) {
10373 this.screen_.style.overflowY = state ? 'scroll' : 'hidden';
10374};
10375
10376/**
10377 * Set scroll wheel multiplier. This alters how much the screen scrolls on
10378 * mouse wheel events.
10379 */
10380hterm.ScrollPort.prototype.setScrollWheelMoveMultipler = function(multiplier) {
10381 this.scrollWheelMultiplier_ = multiplier;
10382};
10383// SOURCE FILE: hterm/js/hterm_terminal.js
10384// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
10385// Use of this source code is governed by a BSD-style license that can be
10386// found in the LICENSE file.
10387
10388'use strict';
10389
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010390lib.rtdep(
10391 'lib.colors', 'lib.PreferenceManager', 'lib.resource', 'lib.wc', 'lib.f',
10392 'hterm.Keyboard', 'hterm.Options', 'hterm.PreferenceManager',
10393 'hterm.Screen', 'hterm.ScrollPort', 'hterm.Size', 'hterm.TextAttributes',
10394 'hterm.VT');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010395
10396/**
10397 * Constructor for the Terminal class.
10398 *
10399 * A Terminal pulls together the hterm.ScrollPort, hterm.Screen and hterm.VT100
10400 * classes to provide the complete terminal functionality.
10401 *
10402 * There are a number of lower-level Terminal methods that can be called
10403 * directly to manipulate the cursor, text, scroll region, and other terminal
10404 * attributes. However, the primary method is interpret(), which parses VT
10405 * escape sequences and invokes the appropriate Terminal methods.
10406 *
10407 * This class was heavily influenced by Cory Maccarrone's Framebuffer class.
10408 *
10409 * TODO(rginda): Eventually we're going to need to support characters which are
10410 * displayed twice as wide as standard latin characters. This is to support
10411 * CJK (and possibly other character sets).
10412 *
10413 * @param {string} opt_profileId Optional preference profile name. If not
10414 * provided, defaults to 'default'.
10415 */
10416hterm.Terminal = function(opt_profileId) {
10417 this.profileId_ = null;
10418
10419 // Two screen instances.
10420 this.primaryScreen_ = new hterm.Screen();
10421 this.alternateScreen_ = new hterm.Screen();
10422
10423 // The "current" screen.
10424 this.screen_ = this.primaryScreen_;
10425
10426 // The local notion of the screen size. ScreenBuffers also have a size which
10427 // indicates their present size. During size changes, the two may disagree.
10428 // Also, the inactive screen's size is not altered until it is made the active
10429 // screen.
10430 this.screenSize = new hterm.Size(0, 0);
10431
10432 // The scroll port we'll be using to display the visible rows.
10433 this.scrollPort_ = new hterm.ScrollPort(this);
10434 this.scrollPort_.subscribe('resize', this.onResize_.bind(this));
10435 this.scrollPort_.subscribe('scroll', this.onScroll_.bind(this));
10436 this.scrollPort_.subscribe('paste', this.onPaste_.bind(this));
10437 this.scrollPort_.onCopy = this.onCopy_.bind(this);
10438
10439 // The div that contains this terminal.
10440 this.div_ = null;
10441
10442 // The document that contains the scrollPort. Defaulted to the global
10443 // document here so that the terminal is functional even if it hasn't been
10444 // inserted into a document yet, but re-set in decorate().
10445 this.document_ = window.document;
10446
10447 // The rows that have scrolled off screen and are no longer addressable.
10448 this.scrollbackRows_ = [];
10449
10450 // Saved tab stops.
10451 this.tabStops_ = [];
10452
10453 // Keep track of whether default tab stops have been erased; after a TBC
10454 // clears all tab stops, defaults aren't restored on resize until a reset.
10455 this.defaultTabStops = true;
10456
10457 // The VT's notion of the top and bottom rows. Used during some VT
10458 // cursor positioning and scrolling commands.
10459 this.vtScrollTop_ = null;
10460 this.vtScrollBottom_ = null;
10461
10462 // The DIV element for the visible cursor.
10463 this.cursorNode_ = null;
10464
10465 // The current cursor shape of the terminal.
10466 this.cursorShape_ = hterm.Terminal.cursorShape.BLOCK;
10467
10468 // The current color of the cursor.
10469 this.cursorColor_ = null;
10470
10471 // Cursor blink on/off cycle in ms, overwritten by prefs once they're loaded.
10472 this.cursorBlinkCycle_ = [100, 100];
10473
10474 // Pre-bound onCursorBlink_ handler, so we don't have to do this for each
10475 // cursor on/off servicing.
10476 this.myOnCursorBlink_ = this.onCursorBlink_.bind(this);
10477
10478 // These prefs are cached so we don't have to read from local storage with
10479 // each output and keystroke. They are initialized by the preference manager.
10480 this.backgroundColor_ = null;
10481 this.foregroundColor_ = null;
10482 this.scrollOnOutput_ = null;
10483 this.scrollOnKeystroke_ = null;
10484
10485 // True if we should override mouse event reporting to allow local selection.
10486 this.defeatMouseReports_ = false;
10487
10488 // Terminal bell sound.
10489 this.bellAudio_ = this.document_.createElement('audio');
10490 this.bellAudio_.setAttribute('preload', 'auto');
10491
10492 // All terminal bell notifications that have been generated (not necessarily
10493 // shown).
10494 this.bellNotificationList_ = [];
10495
10496 // Whether we have permission to display notifications.
10497 this.desktopNotificationBell_ = false;
10498
10499 // Cursor position and attributes saved with DECSC.
10500 this.savedOptions_ = {};
10501
10502 // The current mode bits for the terminal.
10503 this.options_ = new hterm.Options();
10504
10505 // Timeouts we might need to clear.
10506 this.timeouts_ = {};
10507
10508 // The VT escape sequence interpreter.
10509 this.vt = new hterm.VT(this);
10510
10511 // The keyboard handler.
10512 this.keyboard = new hterm.Keyboard(this);
10513
10514 // General IO interface that can be given to third parties without exposing
10515 // the entire terminal object.
10516 this.io = new hterm.Terminal.IO(this);
10517
10518 // True if mouse-click-drag should scroll the terminal.
10519 this.enableMouseDragScroll = true;
10520
10521 this.copyOnSelect = null;
10522 this.mousePasteButton = null;
10523
10524 // Whether to use the default window copy behavior.
10525 this.useDefaultWindowCopy = false;
10526
10527 this.clearSelectionAfterCopy = true;
10528
10529 this.realizeSize_(80, 24);
10530 this.setDefaultTabStops();
10531
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010532 this.setProfile(opt_profileId || 'default', function() {
10533 this.onTerminalReady();
10534 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010535};
10536
10537/**
10538 * Possible cursor shapes.
10539 */
10540hterm.Terminal.cursorShape = {
10541 BLOCK: 'BLOCK',
10542 BEAM: 'BEAM',
10543 UNDERLINE: 'UNDERLINE'
10544};
10545
10546/**
10547 * Clients should override this to be notified when the terminal is ready
10548 * for use.
10549 *
10550 * The terminal initialization is asynchronous, and shouldn't be used before
10551 * this method is called.
10552 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010553hterm.Terminal.prototype.onTerminalReady = function() {};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010554
10555/**
10556 * Default tab with of 8 to match xterm.
10557 */
10558hterm.Terminal.prototype.tabWidth = 8;
10559
10560/**
10561 * Select a preference profile.
10562 *
10563 * This will load the terminal preferences for the given profile name and
10564 * associate subsequent preference changes with the new preference profile.
10565 *
10566 * @param {string} profileId The name of the preference profile. Forward slash
10567 * characters will be removed from the name.
10568 * @param {function} opt_callback Optional callback to invoke when the profile
10569 * transition is complete.
10570 */
10571hterm.Terminal.prototype.setProfile = function(profileId, opt_callback) {
10572 this.profileId_ = profileId.replace(/\//g, '');
10573
10574 var terminal = this;
10575
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010576 if (this.prefs_) this.prefs_.deactivate();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010577
10578 this.prefs_ = new hterm.PreferenceManager(this.profileId_);
10579 this.prefs_.addObservers(null, {
10580 'alt-gr-mode': function(v) {
10581 if (v == null) {
10582 if (navigator.language.toLowerCase() == 'en-us') {
10583 v = 'none';
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010584 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010585 v = 'right-alt';
10586 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010587 } else if (typeof v == 'string') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010588 v = v.toLowerCase();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010589 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010590 v = 'none';
10591 }
10592
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010593 if (!/^(none|ctrl-alt|left-alt|right-alt)$/.test(v)) v = 'none';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010594
10595 terminal.keyboard.altGrMode = v;
10596 },
10597
10598 'alt-backspace-is-meta-backspace': function(v) {
10599 terminal.keyboard.altBackspaceIsMetaBackspace = v;
10600 },
10601
10602 'alt-is-meta': function(v) {
10603 terminal.keyboard.altIsMeta = v;
10604 },
10605
10606 'alt-sends-what': function(v) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010607 if (!/^(escape|8-bit|browser-key)$/.test(v)) v = 'escape';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010608
10609 terminal.keyboard.altSendsWhat = v;
10610 },
10611
10612 'audible-bell-sound': function(v) {
10613 var ary = v.match(/^lib-resource:(\S+)/);
10614 if (ary) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010615 terminal.bellAudio_.setAttribute(
10616 'src', lib.resource.getDataUrl(ary[1]));
10617 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010618 terminal.bellAudio_.setAttribute('src', v);
10619 }
10620 },
10621
10622 'desktop-notification-bell': function(v) {
10623 if (v && Notification) {
10624 terminal.desktopNotificationBell_ =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010625 Notification.permission === 'granted';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010626 if (!terminal.desktopNotificationBell_) {
10627 // Note: We don't call Notification.requestPermission here because
10628 // Chrome requires the call be the result of a user action (such as an
10629 // onclick handler), and pref listeners are run asynchronously.
10630 //
10631 // A way of working around this would be to display a dialog in the
10632 // terminal with a "click-to-request-permission" button.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010633 console.warn(
10634 'desktop-notification-bell is true but we do not have ' +
10635 'permission to display notifications.');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010636 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010637 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010638 terminal.desktopNotificationBell_ = false;
10639 }
10640 },
10641
10642 'background-color': function(v) {
10643 terminal.setBackgroundColor(v);
10644 },
10645
10646 'background-image': function(v) {
10647 terminal.scrollPort_.setBackgroundImage(v);
10648 },
10649
10650 'background-size': function(v) {
10651 terminal.scrollPort_.setBackgroundSize(v);
10652 },
10653
10654 'background-position': function(v) {
10655 terminal.scrollPort_.setBackgroundPosition(v);
10656 },
10657
10658 'backspace-sends-backspace': function(v) {
10659 terminal.keyboard.backspaceSendsBackspace = v;
10660 },
10661
10662 'character-map-overrides': function(v) {
10663 if (!(v == null || v instanceof Object)) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010664 console.warn(
10665 'Preference character-map-modifications is not an ' +
10666 'object: ' + v);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010667 return;
10668 }
10669
10670 for (var code in v) {
10671 var glmap = hterm.VT.CharacterMap.maps[code].glmap;
10672 for (var received in v[code]) {
10673 glmap[received] = v[code][received];
10674 }
10675 hterm.VT.CharacterMap.maps[code].reset(glmap);
10676 }
10677 },
10678
10679 'cursor-blink': function(v) {
10680 terminal.setCursorBlink(!!v);
10681 },
10682
10683 'cursor-blink-cycle': function(v) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010684 if (v instanceof Array && typeof v[0] == 'number' &&
10685 typeof v[1] == 'number') {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010686 terminal.cursorBlinkCycle_ = v;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010687 } else if (typeof v == 'number') {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010688 terminal.cursorBlinkCycle_ = [v, v];
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010689 } else {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010690 // Fast blink indicates an error.
10691 terminal.cursorBlinkCycle_ = [100, 100];
10692 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010693 },
10694
10695 'cursor-color': function(v) {
10696 terminal.setCursorColor(v);
10697 },
10698
10699 'color-palette-overrides': function(v) {
10700 if (!(v == null || v instanceof Object || v instanceof Array)) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010701 console.warn(
10702 'Preference color-palette-overrides is not an array or ' +
10703 'object: ' + v);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010704 return;
10705 }
10706
10707 lib.colors.colorPalette = lib.colors.stockColorPalette.concat();
10708
10709 if (v) {
10710 for (var key in v) {
10711 var i = parseInt(key);
10712 if (isNaN(i) || i < 0 || i > 255) {
10713 console.log('Invalid value in palette: ' + key + ': ' + v[key]);
10714 continue;
10715 }
10716
10717 if (v[i]) {
10718 var rgb = lib.colors.normalizeCSS(v[i]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010719 if (rgb) lib.colors.colorPalette[i] = rgb;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010720 }
10721 }
10722 }
10723
10724 terminal.primaryScreen_.textAttributes.resetColorPalette();
10725 terminal.alternateScreen_.textAttributes.resetColorPalette();
10726 },
10727
10728 'copy-on-select': function(v) {
10729 terminal.copyOnSelect = !!v;
10730 },
10731
10732 'use-default-window-copy': function(v) {
10733 terminal.useDefaultWindowCopy = !!v;
10734 },
10735
10736 'clear-selection-after-copy': function(v) {
10737 terminal.clearSelectionAfterCopy = !!v;
10738 },
10739
10740 'ctrl-plus-minus-zero-zoom': function(v) {
10741 terminal.keyboard.ctrlPlusMinusZeroZoom = v;
10742 },
10743
10744 'ctrl-c-copy': function(v) {
10745 terminal.keyboard.ctrlCCopy = v;
10746 },
10747
10748 'ctrl-v-paste': function(v) {
10749 terminal.keyboard.ctrlVPaste = v;
10750 terminal.scrollPort_.setCtrlVPaste(v);
10751 },
10752
10753 'east-asian-ambiguous-as-two-column': function(v) {
10754 lib.wc.regardCjkAmbiguous = v;
10755 },
10756
10757 'enable-8-bit-control': function(v) {
10758 terminal.vt.enable8BitControl = !!v;
10759 },
10760
10761 'enable-bold': function(v) {
10762 terminal.syncBoldSafeState();
10763 },
10764
10765 'enable-bold-as-bright': function(v) {
10766 terminal.primaryScreen_.textAttributes.enableBoldAsBright = !!v;
10767 terminal.alternateScreen_.textAttributes.enableBoldAsBright = !!v;
10768 },
10769
10770 'enable-blink': function(v) {
10771 terminal.syncBlinkState();
10772 },
10773
10774 'enable-clipboard-write': function(v) {
10775 terminal.vt.enableClipboardWrite = !!v;
10776 },
10777
10778 'enable-dec12': function(v) {
10779 terminal.vt.enableDec12 = !!v;
10780 },
10781
10782 'font-family': function(v) {
10783 terminal.syncFontFamily();
10784 },
10785
10786 'font-size': function(v) {
10787 terminal.setFontSize(v);
10788 },
10789
10790 'font-smoothing': function(v) {
10791 terminal.syncFontFamily();
10792 },
10793
10794 'foreground-color': function(v) {
10795 terminal.setForegroundColor(v);
10796 },
10797
10798 'home-keys-scroll': function(v) {
10799 terminal.keyboard.homeKeysScroll = v;
10800 },
10801
10802 'keybindings': function(v) {
10803 terminal.keyboard.bindings.clear();
10804
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010805 if (!v) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010806
10807 if (!(v instanceof Object)) {
10808 console.error('Error in keybindings preference: Expected object');
10809 return;
10810 }
10811
10812 try {
10813 terminal.keyboard.bindings.addBindings(v);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010814 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010815 console.error('Error in keybindings preference: ' + ex);
10816 }
10817 },
10818
10819 'max-string-sequence': function(v) {
10820 terminal.vt.maxStringSequence = v;
10821 },
10822
10823 'media-keys-are-fkeys': function(v) {
10824 terminal.keyboard.mediaKeysAreFKeys = v;
10825 },
10826
10827 'meta-sends-escape': function(v) {
10828 terminal.keyboard.metaSendsEscape = v;
10829 },
10830
10831 'mouse-paste-button': function(v) {
10832 terminal.syncMousePasteButton();
10833 },
10834
10835 'page-keys-scroll': function(v) {
10836 terminal.keyboard.pageKeysScroll = v;
10837 },
10838
10839 'pass-alt-number': function(v) {
10840 if (v == null) {
10841 var osx = window.navigator.userAgent.match(/Mac OS X/);
10842
10843 // Let Alt-1..9 pass to the browser (to control tab switching) on
10844 // non-OS X systems, or if hterm is not opened in an app window.
10845 v = (!osx && hterm.windowType != 'popup');
10846 }
10847
10848 terminal.passAltNumber = v;
10849 },
10850
10851 'pass-ctrl-number': function(v) {
10852 if (v == null) {
10853 var osx = window.navigator.userAgent.match(/Mac OS X/);
10854
10855 // Let Ctrl-1..9 pass to the browser (to control tab switching) on
10856 // non-OS X systems, or if hterm is not opened in an app window.
10857 v = (!osx && hterm.windowType != 'popup');
10858 }
10859
10860 terminal.passCtrlNumber = v;
10861 },
10862
10863 'pass-meta-number': function(v) {
10864 if (v == null) {
10865 var osx = window.navigator.userAgent.match(/Mac OS X/);
10866
10867 // Let Meta-1..9 pass to the browser (to control tab switching) on
10868 // OS X systems, or if hterm is not opened in an app window.
10869 v = (osx && hterm.windowType != 'popup');
10870 }
10871
10872 terminal.passMetaNumber = v;
10873 },
10874
10875 'pass-meta-v': function(v) {
10876 terminal.keyboard.passMetaV = v;
10877 },
10878
10879 'receive-encoding': function(v) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010880 if (!(/^(utf-8|raw)$/).test(v)) {
10881 console.warn('Invalid value for "receive-encoding": ' + v);
10882 v = 'utf-8';
10883 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010884
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010885 terminal.vt.characterEncoding = v;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010886 },
10887
10888 'scroll-on-keystroke': function(v) {
10889 terminal.scrollOnKeystroke_ = v;
10890 },
10891
10892 'scroll-on-output': function(v) {
10893 terminal.scrollOnOutput_ = v;
10894 },
10895
10896 'scrollbar-visible': function(v) {
10897 terminal.setScrollbarVisible(v);
10898 },
10899
10900 'scroll-wheel-move-multiplier': function(v) {
10901 terminal.setScrollWheelMoveMultipler(v);
10902 },
10903
10904 'send-encoding': function(v) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010905 if (!(/^(utf-8|raw)$/).test(v)) {
10906 console.warn('Invalid value for "send-encoding": ' + v);
10907 v = 'utf-8';
10908 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010909
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010910 terminal.keyboard.characterEncoding = v;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010911 },
10912
10913 'shift-insert-paste': function(v) {
10914 terminal.keyboard.shiftInsertPaste = v;
10915 },
10916
10917 'user-css': function(v) {
10918 terminal.scrollPort_.setUserCss(v);
10919 }
10920 });
10921
10922 this.prefs_.readStorage(function() {
10923 this.prefs_.notifyAll();
10924
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010925 if (opt_callback) opt_callback();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010926 }.bind(this));
10927};
10928
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010929/**
10930 * Returns the preferences manager used for configuring this terminal.
10931 *
10932 * @return {hterm.PreferenceManager}
10933 */
10934hterm.Terminal.prototype.getPrefs = function() {
10935 return this.prefs_;
10936};
10937
10938/**
10939 * Enable or disable bracketed paste mode.
10940 *
10941 * @param {boolean} state The value to set.
10942 */
10943hterm.Terminal.prototype.setBracketedPaste = function(state) {
10944 this.options_.bracketedPaste = state;
10945};
10946
10947/**
10948 * Set the color for the cursor.
10949 *
10950 * If you want this setting to persist, set it through prefs_, rather than
10951 * with this method.
10952 *
10953 * @param {string} color The color to set.
10954 */
10955hterm.Terminal.prototype.setCursorColor = function(color) {
10956 this.cursorColor_ = color;
10957 this.cursorNode_.style.backgroundColor = color;
10958 this.cursorNode_.style.borderColor = color;
10959};
10960
10961/**
10962 * Return the current cursor color as a string.
10963 * @return {string}
10964 */
10965hterm.Terminal.prototype.getCursorColor = function() {
10966 return this.cursorColor_;
10967};
10968
10969/**
10970 * Enable or disable mouse based text selection in the terminal.
10971 *
10972 * @param {boolean} state The value to set.
10973 */
10974hterm.Terminal.prototype.setSelectionEnabled = function(state) {
10975 this.enableMouseDragScroll = state;
10976};
10977
10978/**
10979 * Set the background color.
10980 *
10981 * If you want this setting to persist, set it through prefs_, rather than
10982 * with this method.
10983 *
10984 * @param {string} color The color to set.
10985 */
10986hterm.Terminal.prototype.setBackgroundColor = function(color) {
10987 this.backgroundColor_ = lib.colors.normalizeCSS(color);
10988 this.primaryScreen_.textAttributes.setDefaults(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010989 this.foregroundColor_, this.backgroundColor_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010990 this.alternateScreen_.textAttributes.setDefaults(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010991 this.foregroundColor_, this.backgroundColor_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010992 this.scrollPort_.setBackgroundColor(color);
10993};
10994
10995/**
10996 * Return the current terminal background color.
10997 *
10998 * Intended for use by other classes, so we don't have to expose the entire
10999 * prefs_ object.
11000 *
11001 * @return {string}
11002 */
11003hterm.Terminal.prototype.getBackgroundColor = function() {
11004 return this.backgroundColor_;
11005};
11006
11007/**
11008 * Set the foreground color.
11009 *
11010 * If you want this setting to persist, set it through prefs_, rather than
11011 * with this method.
11012 *
11013 * @param {string} color The color to set.
11014 */
11015hterm.Terminal.prototype.setForegroundColor = function(color) {
11016 this.foregroundColor_ = lib.colors.normalizeCSS(color);
11017 this.primaryScreen_.textAttributes.setDefaults(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011018 this.foregroundColor_, this.backgroundColor_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011019 this.alternateScreen_.textAttributes.setDefaults(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011020 this.foregroundColor_, this.backgroundColor_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011021 this.scrollPort_.setForegroundColor(color);
11022};
11023
11024/**
11025 * Return the current terminal foreground color.
11026 *
11027 * Intended for use by other classes, so we don't have to expose the entire
11028 * prefs_ object.
11029 *
11030 * @return {string}
11031 */
11032hterm.Terminal.prototype.getForegroundColor = function() {
11033 return this.foregroundColor_;
11034};
11035
11036/**
11037 * Create a new instance of a terminal command and run it with a given
11038 * argument string.
11039 *
11040 * @param {function} commandClass The constructor for a terminal command.
11041 * @param {string} argString The argument string to pass to the command.
11042 */
11043hterm.Terminal.prototype.runCommandClass = function(commandClass, argString) {
11044 var environment = this.prefs_.get('environment');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011045 if (typeof environment != 'object' || environment == null) environment = {};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011046
11047 var self = this;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011048 this.command = new commandClass({
11049 argString: argString || '',
11050 io: this.io.push(),
11051 environment: environment,
11052 onExit: function(code) {
11053 self.io.pop();
11054 self.uninstallKeyboard();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011055 if (self.prefs_.get('close-on-exit')) window.close();
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011056 }
11057 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011058
11059 this.installKeyboard();
11060 this.command.run();
11061};
11062
11063/**
11064 * Returns true if the current screen is the primary screen, false otherwise.
11065 *
11066 * @return {boolean}
11067 */
11068hterm.Terminal.prototype.isPrimaryScreen = function() {
11069 return this.screen_ == this.primaryScreen_;
11070};
11071
11072/**
11073 * Install the keyboard handler for this terminal.
11074 *
11075 * This will prevent the browser from seeing any keystrokes sent to the
11076 * terminal.
11077 */
11078hterm.Terminal.prototype.installKeyboard = function() {
11079 this.keyboard.installKeyboard(this.scrollPort_.getDocument().body);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011080};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011081
11082/**
11083 * Uninstall the keyboard handler for this terminal.
11084 */
11085hterm.Terminal.prototype.uninstallKeyboard = function() {
11086 this.keyboard.installKeyboard(null);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011087};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011088
11089/**
11090 * Set the font size for this terminal.
11091 *
11092 * Call setFontSize(0) to reset to the default font size.
11093 *
11094 * This function does not modify the font-size preference.
11095 *
11096 * @param {number} px The desired font size, in pixels.
11097 */
11098hterm.Terminal.prototype.setFontSize = function(px) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011099 if (px === 0) px = this.prefs_.get('font-size');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011100
11101 this.scrollPort_.setFontSize(px);
11102 if (this.wcCssRule_) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011103 this.wcCssRule_.style.width =
11104 this.scrollPort_.characterSize.width * 2 + 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011105 }
11106};
11107
11108/**
11109 * Get the current font size.
11110 *
11111 * @return {number}
11112 */
11113hterm.Terminal.prototype.getFontSize = function() {
11114 return this.scrollPort_.getFontSize();
11115};
11116
11117/**
11118 * Get the current font family.
11119 *
11120 * @return {string}
11121 */
11122hterm.Terminal.prototype.getFontFamily = function() {
11123 return this.scrollPort_.getFontFamily();
11124};
11125
11126/**
11127 * Set the CSS "font-family" for this terminal.
11128 */
11129hterm.Terminal.prototype.syncFontFamily = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011130 this.scrollPort_.setFontFamily(
11131 this.prefs_.get('font-family'), this.prefs_.get('font-smoothing'));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011132 this.syncBoldSafeState();
11133};
11134
11135/**
11136 * Set this.mousePasteButton based on the mouse-paste-button pref,
11137 * autodetecting if necessary.
11138 */
11139hterm.Terminal.prototype.syncMousePasteButton = function() {
11140 var button = this.prefs_.get('mouse-paste-button');
11141 if (typeof button == 'number') {
11142 this.mousePasteButton = button;
11143 return;
11144 }
11145
11146 var ary = navigator.userAgent.match(/\(X11;\s+(\S+)/);
11147 if (!ary || ary[2] == 'CrOS') {
11148 this.mousePasteButton = 2;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011149 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011150 this.mousePasteButton = 3;
11151 }
11152};
11153
11154/**
11155 * Enable or disable bold based on the enable-bold pref, autodetecting if
11156 * necessary.
11157 */
11158hterm.Terminal.prototype.syncBoldSafeState = function() {
11159 var enableBold = this.prefs_.get('enable-bold');
11160 if (enableBold !== null) {
11161 this.primaryScreen_.textAttributes.enableBold = enableBold;
11162 this.alternateScreen_.textAttributes.enableBold = enableBold;
11163 return;
11164 }
11165
11166 var normalSize = this.scrollPort_.measureCharacterSize();
11167 var boldSize = this.scrollPort_.measureCharacterSize('bold');
11168
11169 var isBoldSafe = normalSize.equals(boldSize);
11170 if (!isBoldSafe) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011171 console.warn(
11172 'Bold characters disabled: Size of bold weight differs ' +
11173 'from normal. Font family is: ' + this.scrollPort_.getFontFamily());
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011174 }
11175
11176 this.primaryScreen_.textAttributes.enableBold = isBoldSafe;
11177 this.alternateScreen_.textAttributes.enableBold = isBoldSafe;
11178};
11179
11180/**
11181 * Enable or disable blink based on the enable-blink pref.
11182 */
11183hterm.Terminal.prototype.syncBlinkState = function() {
11184 this.document_.documentElement.style.setProperty(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011185 '--hterm-blink-node-duration',
11186 this.prefs_.get('enable-blink') ? '0.7s' : '0');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011187};
11188
11189/**
11190 * Return a copy of the current cursor position.
11191 *
11192 * @return {hterm.RowCol} The RowCol object representing the current position.
11193 */
11194hterm.Terminal.prototype.saveCursor = function() {
11195 return this.screen_.cursorPosition.clone();
11196};
11197
11198/**
11199 * Return the current text attributes.
11200 *
11201 * @return {string}
11202 */
11203hterm.Terminal.prototype.getTextAttributes = function() {
11204 return this.screen_.textAttributes;
11205};
11206
11207/**
11208 * Set the text attributes.
11209 *
11210 * @param {string} textAttributes The attributes to set.
11211 */
11212hterm.Terminal.prototype.setTextAttributes = function(textAttributes) {
11213 this.screen_.textAttributes = textAttributes;
11214};
11215
11216/**
11217 * Return the current browser zoom factor applied to the terminal.
11218 *
11219 * @return {number} The current browser zoom factor.
11220 */
11221hterm.Terminal.prototype.getZoomFactor = function() {
11222 return this.scrollPort_.characterSize.zoomFactor;
11223};
11224
11225/**
11226 * Change the title of this terminal's window.
11227 *
11228 * @param {string} title The title to set.
11229 */
11230hterm.Terminal.prototype.setWindowTitle = function(title) {
11231 window.document.title = title;
11232};
11233
11234/**
11235 * Restore a previously saved cursor position.
11236 *
11237 * @param {hterm.RowCol} cursor The position to restore.
11238 */
11239hterm.Terminal.prototype.restoreCursor = function(cursor) {
11240 var row = lib.f.clamp(cursor.row, 0, this.screenSize.height - 1);
11241 var column = lib.f.clamp(cursor.column, 0, this.screenSize.width - 1);
11242 this.screen_.setCursorPosition(row, column);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011243 if (cursor.column > column || cursor.column == column && cursor.overflow) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011244 this.screen_.cursorPosition.overflow = true;
11245 }
11246};
11247
11248/**
11249 * Clear the cursor's overflow flag.
11250 */
11251hterm.Terminal.prototype.clearCursorOverflow = function() {
11252 this.screen_.cursorPosition.overflow = false;
11253};
11254
11255/**
11256 * Sets the cursor shape
11257 *
11258 * @param {string} shape The shape to set.
11259 */
11260hterm.Terminal.prototype.setCursorShape = function(shape) {
11261 this.cursorShape_ = shape;
11262 this.restyleCursor_();
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011263};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011264
11265/**
11266 * Get the cursor shape
11267 *
11268 * @return {string}
11269 */
11270hterm.Terminal.prototype.getCursorShape = function() {
11271 return this.cursorShape_;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011272};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011273
11274/**
11275 * Set the width of the terminal, resizing the UI to match.
11276 *
11277 * @param {number} columnCount
11278 */
11279hterm.Terminal.prototype.setWidth = function(columnCount) {
11280 if (columnCount == null) {
11281 this.div_.style.width = '100%';
11282 return;
11283 }
11284
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011285 this.div_.style.width =
11286 Math.ceil(
11287 this.scrollPort_.characterSize.width * columnCount +
11288 this.scrollPort_.currentScrollbarWidthPx) +
11289 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011290 this.realizeSize_(columnCount, this.screenSize.height);
11291 this.scheduleSyncCursorPosition_();
11292};
11293
11294/**
11295 * Set the height of the terminal, resizing the UI to match.
11296 *
11297 * @param {number} rowCount The height in rows.
11298 */
11299hterm.Terminal.prototype.setHeight = function(rowCount) {
11300 if (rowCount == null) {
11301 this.div_.style.height = '100%';
11302 return;
11303 }
11304
11305 this.div_.style.height =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011306 this.scrollPort_.characterSize.height * rowCount + 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011307 this.realizeSize_(this.screenSize.width, rowCount);
11308 this.scheduleSyncCursorPosition_();
11309};
11310
11311/**
11312 * Deal with terminal size changes.
11313 *
11314 * @param {number} columnCount The number of columns.
11315 * @param {number} rowCount The number of rows.
11316 */
11317hterm.Terminal.prototype.realizeSize_ = function(columnCount, rowCount) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011318 if (columnCount != this.screenSize.width) this.realizeWidth_(columnCount);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011319
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011320 if (rowCount != this.screenSize.height) this.realizeHeight_(rowCount);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011321
11322 // Send new terminal size to plugin.
11323 this.io.onTerminalResize_(columnCount, rowCount);
11324};
11325
11326/**
11327 * Deal with terminal width changes.
11328 *
11329 * This function does what needs to be done when the terminal width changes
11330 * out from under us. It happens here rather than in onResize_() because this
11331 * code may need to run synchronously to handle programmatic changes of
11332 * terminal width.
11333 *
11334 * Relying on the browser to send us an async resize event means we may not be
11335 * in the correct state yet when the next escape sequence hits.
11336 *
11337 * @param {number} columnCount The number of columns.
11338 */
11339hterm.Terminal.prototype.realizeWidth_ = function(columnCount) {
11340 if (columnCount <= 0)
11341 throw new Error('Attempt to realize bad width: ' + columnCount);
11342
11343 var deltaColumns = columnCount - this.screen_.getWidth();
11344
11345 this.screenSize.width = columnCount;
11346 this.screen_.setColumnCount(columnCount);
11347
11348 if (deltaColumns > 0) {
11349 if (this.defaultTabStops)
11350 this.setDefaultTabStops(this.screenSize.width - deltaColumns);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011351 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011352 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011353 if (this.tabStops_[i] < columnCount) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011354
11355 this.tabStops_.pop();
11356 }
11357 }
11358
11359 this.screen_.setColumnCount(this.screenSize.width);
11360};
11361
11362/**
11363 * Deal with terminal height changes.
11364 *
11365 * This function does what needs to be done when the terminal height changes
11366 * out from under us. It happens here rather than in onResize_() because this
11367 * code may need to run synchronously to handle programmatic changes of
11368 * terminal height.
11369 *
11370 * Relying on the browser to send us an async resize event means we may not be
11371 * in the correct state yet when the next escape sequence hits.
11372 *
11373 * @param {number} rowCount The number of rows.
11374 */
11375hterm.Terminal.prototype.realizeHeight_ = function(rowCount) {
11376 if (rowCount <= 0)
11377 throw new Error('Attempt to realize bad height: ' + rowCount);
11378
11379 var deltaRows = rowCount - this.screen_.getHeight();
11380
11381 this.screenSize.height = rowCount;
11382
11383 var cursor = this.saveCursor();
11384
11385 if (deltaRows < 0) {
11386 // Screen got smaller.
11387 deltaRows *= -1;
11388 while (deltaRows) {
11389 var lastRow = this.getRowCount() - 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011390 if (lastRow - this.scrollbackRows_.length == cursor.row) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011391
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011392 if (this.getRowText(lastRow)) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011393
11394 this.screen_.popRow();
11395 deltaRows--;
11396 }
11397
11398 var ary = this.screen_.shiftRows(deltaRows);
11399 this.scrollbackRows_.push.apply(this.scrollbackRows_, ary);
11400
11401 // We just removed rows from the top of the screen, we need to update
11402 // the cursor to match.
11403 cursor.row = Math.max(cursor.row - deltaRows, 0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011404 } else if (deltaRows > 0) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011405 // Screen got larger.
11406
11407 if (deltaRows <= this.scrollbackRows_.length) {
11408 var scrollbackCount = Math.min(deltaRows, this.scrollbackRows_.length);
11409 var rows = this.scrollbackRows_.splice(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011410 this.scrollbackRows_.length - scrollbackCount, scrollbackCount);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011411 this.screen_.unshiftRows(rows);
11412 deltaRows -= scrollbackCount;
11413 cursor.row += scrollbackCount;
11414 }
11415
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011416 if (deltaRows) this.appendRows_(deltaRows);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011417 }
11418
11419 this.setVTScrollRegion(null, null);
11420 this.restoreCursor(cursor);
11421};
11422
11423/**
11424 * Scroll the terminal to the top of the scrollback buffer.
11425 */
11426hterm.Terminal.prototype.scrollHome = function() {
11427 this.scrollPort_.scrollRowToTop(0);
11428};
11429
11430/**
11431 * Scroll the terminal to the end.
11432 */
11433hterm.Terminal.prototype.scrollEnd = function() {
11434 this.scrollPort_.scrollRowToBottom(this.getRowCount());
11435};
11436
11437/**
11438 * Scroll the terminal one page up (minus one line) relative to the current
11439 * position.
11440 */
11441hterm.Terminal.prototype.scrollPageUp = function() {
11442 var i = this.scrollPort_.getTopRowIndex();
11443 this.scrollPort_.scrollRowToTop(i - this.screenSize.height + 1);
11444};
11445
11446/**
11447 * Scroll the terminal one page down (minus one line) relative to the current
11448 * position.
11449 */
11450hterm.Terminal.prototype.scrollPageDown = function() {
11451 var i = this.scrollPort_.getTopRowIndex();
11452 this.scrollPort_.scrollRowToTop(i + this.screenSize.height - 1);
11453};
11454
11455/**
11456 * Clear primary screen, secondary screen, and the scrollback buffer.
11457 */
11458hterm.Terminal.prototype.wipeContents = function() {
11459 this.scrollbackRows_.length = 0;
11460 this.scrollPort_.resetCache();
11461
11462 [this.primaryScreen_, this.alternateScreen_].forEach(function(screen) {
11463 var bottom = screen.getHeight();
11464 if (bottom > 0) {
11465 this.renumberRows_(0, bottom);
11466 this.clearHome(screen);
11467 }
11468 }.bind(this));
11469
11470 this.syncCursorPosition_();
11471 this.scrollPort_.invalidate();
11472};
11473
11474/**
11475 * Full terminal reset.
11476 */
11477hterm.Terminal.prototype.reset = function() {
11478 this.clearAllTabStops();
11479 this.setDefaultTabStops();
11480
11481 this.clearHome(this.primaryScreen_);
11482 this.primaryScreen_.textAttributes.reset();
11483
11484 this.clearHome(this.alternateScreen_);
11485 this.alternateScreen_.textAttributes.reset();
11486
11487 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
11488
11489 this.vt.reset();
11490
11491 this.softReset();
11492};
11493
11494/**
11495 * Soft terminal reset.
11496 *
11497 * Perform a soft reset to the default values listed in
11498 * http://www.vt100.net/docs/vt510-rm/DECSTR#T5-9
11499 */
11500hterm.Terminal.prototype.softReset = function() {
11501 // Reset terminal options to their default values.
11502 this.options_ = new hterm.Options();
11503
11504 // We show the cursor on soft reset but do not alter the blink state.
11505 this.options_.cursorBlink = !!this.timeouts_.cursorBlink;
11506
11507 // Xterm also resets the color palette on soft reset, even though it doesn't
11508 // seem to be documented anywhere.
11509 this.primaryScreen_.textAttributes.resetColorPalette();
11510 this.alternateScreen_.textAttributes.resetColorPalette();
11511
11512 // The xterm man page explicitly says this will happen on soft reset.
11513 this.setVTScrollRegion(null, null);
11514
11515 // Xterm also shows the cursor on soft reset, but does not alter the blink
11516 // state.
11517 this.setCursorVisible(true);
11518};
11519
11520/**
11521 * Move the cursor forward to the next tab stop, or to the last column
11522 * if no more tab stops are set.
11523 */
11524hterm.Terminal.prototype.forwardTabStop = function() {
11525 var column = this.screen_.cursorPosition.column;
11526
11527 for (var i = 0; i < this.tabStops_.length; i++) {
11528 if (this.tabStops_[i] > column) {
11529 this.setCursorColumn(this.tabStops_[i]);
11530 return;
11531 }
11532 }
11533
11534 // xterm does not clear the overflow flag on HT or CHT.
11535 var overflow = this.screen_.cursorPosition.overflow;
11536 this.setCursorColumn(this.screenSize.width - 1);
11537 this.screen_.cursorPosition.overflow = overflow;
11538};
11539
11540/**
11541 * Move the cursor backward to the previous tab stop, or to the first column
11542 * if no previous tab stops are set.
11543 */
11544hterm.Terminal.prototype.backwardTabStop = function() {
11545 var column = this.screen_.cursorPosition.column;
11546
11547 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
11548 if (this.tabStops_[i] < column) {
11549 this.setCursorColumn(this.tabStops_[i]);
11550 return;
11551 }
11552 }
11553
11554 this.setCursorColumn(1);
11555};
11556
11557/**
11558 * Set a tab stop at the given column.
11559 *
11560 * @param {integer} column Zero based column.
11561 */
11562hterm.Terminal.prototype.setTabStop = function(column) {
11563 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011564 if (this.tabStops_[i] == column) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011565
11566 if (this.tabStops_[i] < column) {
11567 this.tabStops_.splice(i + 1, 0, column);
11568 return;
11569 }
11570 }
11571
11572 this.tabStops_.splice(0, 0, column);
11573};
11574
11575/**
11576 * Clear the tab stop at the current cursor position.
11577 *
11578 * No effect if there is no tab stop at the current cursor position.
11579 */
11580hterm.Terminal.prototype.clearTabStopAtCursor = function() {
11581 var column = this.screen_.cursorPosition.column;
11582
11583 var i = this.tabStops_.indexOf(column);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011584 if (i == -1) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011585
11586 this.tabStops_.splice(i, 1);
11587};
11588
11589/**
11590 * Clear all tab stops.
11591 */
11592hterm.Terminal.prototype.clearAllTabStops = function() {
11593 this.tabStops_.length = 0;
11594 this.defaultTabStops = false;
11595};
11596
11597/**
11598 * Set up the default tab stops, starting from a given column.
11599 *
11600 * This sets a tabstop every (column % this.tabWidth) column, starting
11601 * from the specified column, or 0 if no column is provided. It also flags
11602 * future resizes to set them up.
11603 *
11604 * This does not clear the existing tab stops first, use clearAllTabStops
11605 * for that.
11606 *
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011607 * @param {integer} opt_start Optional starting zero based starting column,
11608 * useful for filling out missing tab stops when the terminal is resized.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011609 */
11610hterm.Terminal.prototype.setDefaultTabStops = function(opt_start) {
11611 var start = opt_start || 0;
11612 var w = this.tabWidth;
11613 // Round start up to a default tab stop.
11614 start = start - 1 - ((start - 1) % w) + w;
11615 for (var i = start; i < this.screenSize.width; i += w) {
11616 this.setTabStop(i);
11617 }
11618
11619 this.defaultTabStops = true;
11620};
11621
11622/**
11623 * Interpret a sequence of characters.
11624 *
11625 * Incomplete escape sequences are buffered until the next call.
11626 *
11627 * @param {string} str Sequence of characters to interpret or pass through.
11628 */
11629hterm.Terminal.prototype.interpret = function(str) {
11630 this.vt.interpret(str);
11631 this.scheduleSyncCursorPosition_();
11632};
11633
11634/**
11635 * Take over the given DIV for use as the terminal display.
11636 *
11637 * @param {HTMLDivElement} div The div to use as the terminal display.
11638 */
11639hterm.Terminal.prototype.decorate = function(div) {
11640 this.div_ = div;
11641
11642 this.scrollPort_.decorate(div);
11643 this.scrollPort_.setBackgroundImage(this.prefs_.get('background-image'));
11644 this.scrollPort_.setBackgroundSize(this.prefs_.get('background-size'));
11645 this.scrollPort_.setBackgroundPosition(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011646 this.prefs_.get('background-position'));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011647 this.scrollPort_.setUserCss(this.prefs_.get('user-css'));
11648
11649 this.div_.focus = this.focus.bind(this);
11650
11651 this.setFontSize(this.prefs_.get('font-size'));
11652 this.syncFontFamily();
11653
11654 this.setScrollbarVisible(this.prefs_.get('scrollbar-visible'));
11655 this.setScrollWheelMoveMultipler(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011656 this.prefs_.get('scroll-wheel-move-multiplier'));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011657
11658 this.document_ = this.scrollPort_.getDocument();
11659
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011660 this.document_.body.oncontextmenu = function() {
11661 return false;
11662 };
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011663
11664 var onMouse = this.onMouse_.bind(this);
11665 var screenNode = this.scrollPort_.getScreenNode();
11666 screenNode.addEventListener('mousedown', onMouse);
11667 screenNode.addEventListener('mouseup', onMouse);
11668 screenNode.addEventListener('mousemove', onMouse);
11669 this.scrollPort_.onScrollWheel = onMouse;
11670
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011671 screenNode.addEventListener('focus', this.onFocusChange_.bind(this, true));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011672 // Listen for mousedown events on the screenNode as in FF the focus
11673 // events don't bubble.
11674 screenNode.addEventListener('mousedown', function() {
11675 setTimeout(this.onFocusChange_.bind(this, true));
11676 }.bind(this));
11677
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011678 screenNode.addEventListener('blur', this.onFocusChange_.bind(this, false));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011679
11680 var style = this.document_.createElement('style');
11681 style.textContent =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011682 ('.cursor-node[focus="false"] {' +
11683 ' box-sizing: border-box;' +
11684 ' background-color: transparent !important;' +
11685 ' border-width: 2px;' +
11686 ' border-style: solid;' +
11687 '}' +
11688 '.wc-node {' +
11689 ' display: inline-block;' +
11690 ' text-align: center;' +
11691 ' width: ' + this.scrollPort_.characterSize.width * 2 + 'px;' +
11692 '}' +
11693 ':root {' +
11694 ' --hterm-blink-node-duration: 0.7s;' +
11695 '}' +
11696 '@keyframes blink {' +
11697 ' from { opacity: 1.0; }' +
11698 ' to { opacity: 0.0; }' +
11699 '}' +
11700 '.blink-node {' +
11701 ' animation-name: blink;' +
11702 ' animation-duration: var(--hterm-blink-node-duration);' +
11703 ' animation-iteration-count: infinite;' +
11704 ' animation-timing-function: ease-in-out;' +
11705 ' animation-direction: alternate;' +
11706 '}');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011707 this.document_.head.appendChild(style);
11708
11709 var styleSheets = this.document_.styleSheets;
11710 var cssRules = styleSheets[styleSheets.length - 1].cssRules;
11711 this.wcCssRule_ = cssRules[cssRules.length - 1];
11712
11713 this.cursorNode_ = this.document_.createElement('div');
11714 this.cursorNode_.className = 'cursor-node';
11715 this.cursorNode_.style.cssText =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011716 ('position: absolute;' +
11717 'top: -99px;' +
11718 'display: block;' +
11719 'width: ' + this.scrollPort_.characterSize.width + 'px;' +
11720 'height: ' + this.scrollPort_.characterSize.height + 'px;' +
11721 '-webkit-transition: opacity, background-color 100ms linear;' +
11722 '-moz-transition: opacity, background-color 100ms linear;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011723
11724 this.setCursorColor(this.prefs_.get('cursor-color'));
11725 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
11726 this.restyleCursor_();
11727
11728 this.document_.body.appendChild(this.cursorNode_);
11729
11730 // When 'enableMouseDragScroll' is off we reposition this element directly
11731 // under the mouse cursor after a click. This makes Chrome associate
11732 // subsequent mousemove events with the scroll-blocker. Since the
11733 // scroll-blocker is a peer (not a child) of the scrollport, the mousemove
11734 // events do not cause the scrollport to scroll.
11735 //
11736 // It's a hack, but it's the cleanest way I could find.
11737 this.scrollBlockerNode_ = this.document_.createElement('div');
11738 this.scrollBlockerNode_.style.cssText =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011739 ('position: absolute;' +
11740 'top: -99px;' +
11741 'display: block;' +
11742 'width: 10px;' +
11743 'height: 10px;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011744 this.document_.body.appendChild(this.scrollBlockerNode_);
11745
11746 this.scrollPort_.onScrollWheel = onMouse;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011747 ['mousedown',
11748 'mouseup',
11749 'mousemove',
11750 'click',
11751 'dblclick',
11752 ].forEach(function(event) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011753 this.scrollBlockerNode_.addEventListener(event, onMouse);
11754 this.cursorNode_.addEventListener(event, onMouse);
11755 this.document_.addEventListener(event, onMouse);
11756 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011757
11758 this.cursorNode_.addEventListener('mousedown', function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011759 setTimeout(this.focus.bind(this));
11760 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011761
11762 this.setReverseVideo(false);
11763
11764 this.scrollPort_.focus();
11765 this.scrollPort_.scheduleRedraw();
11766};
11767
11768/**
11769 * Return the HTML document that contains the terminal DOM nodes.
11770 *
11771 * @return {HTMLDocument}
11772 */
11773hterm.Terminal.prototype.getDocument = function() {
11774 return this.document_;
11775};
11776
11777/**
11778 * Focus the terminal.
11779 */
11780hterm.Terminal.prototype.focus = function() {
11781 this.scrollPort_.focus();
11782};
11783
11784/**
11785 * Return the HTML Element for a given row index.
11786 *
11787 * This is a method from the RowProvider interface. The ScrollPort uses
11788 * it to fetch rows on demand as they are scrolled into view.
11789 *
11790 * TODO(rginda): Consider saving scrollback rows as (HTML source, text content)
11791 * pairs to conserve memory.
11792 *
11793 * @param {integer} index The zero-based row index, measured relative to the
11794 * start of the scrollback buffer. On-screen rows will always have the
11795 * largest indices.
11796 * @return {HTMLElement} The 'x-row' element containing for the requested row.
11797 */
11798hterm.Terminal.prototype.getRowNode = function(index) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011799 if (index < this.scrollbackRows_.length) return this.scrollbackRows_[index];
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011800
11801 var screenIndex = index - this.scrollbackRows_.length;
11802 return this.screen_.rowsArray[screenIndex];
11803};
11804
11805/**
11806 * Return the text content for a given range of rows.
11807 *
11808 * This is a method from the RowProvider interface. The ScrollPort uses
11809 * it to fetch text content on demand when the user attempts to copy their
11810 * selection to the clipboard.
11811 *
11812 * @param {integer} start The zero-based row index to start from, measured
11813 * relative to the start of the scrollback buffer. On-screen rows will
11814 * always have the largest indices.
11815 * @param {integer} end The zero-based row index to end on, measured
11816 * relative to the start of the scrollback buffer.
11817 * @return {string} A single string containing the text value of the range of
11818 * rows. Lines will be newline delimited, with no trailing newline.
11819 */
11820hterm.Terminal.prototype.getRowsText = function(start, end) {
11821 var ary = [];
11822 for (var i = start; i < end; i++) {
11823 var node = this.getRowNode(i);
11824 ary.push(node.textContent);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011825 if (i < end - 1 && !node.getAttribute('line-overflow')) ary.push('\n');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011826 }
11827
11828 return ary.join('');
11829};
11830
11831/**
11832 * Return the text content for a given row.
11833 *
11834 * This is a method from the RowProvider interface. The ScrollPort uses
11835 * it to fetch text content on demand when the user attempts to copy their
11836 * selection to the clipboard.
11837 *
11838 * @param {integer} index The zero-based row index to return, measured
11839 * relative to the start of the scrollback buffer. On-screen rows will
11840 * always have the largest indices.
11841 * @return {string} A string containing the text value of the selected row.
11842 */
11843hterm.Terminal.prototype.getRowText = function(index) {
11844 var node = this.getRowNode(index);
11845 return node.textContent;
11846};
11847
11848/**
11849 * Return the total number of rows in the addressable screen and in the
11850 * scrollback buffer of this terminal.
11851 *
11852 * This is a method from the RowProvider interface. The ScrollPort uses
11853 * it to compute the size of the scrollbar.
11854 *
11855 * @return {integer} The number of rows in this terminal.
11856 */
11857hterm.Terminal.prototype.getRowCount = function() {
11858 return this.scrollbackRows_.length + this.screen_.rowsArray.length;
11859};
11860
11861/**
11862 * Create DOM nodes for new rows and append them to the end of the terminal.
11863 *
11864 * This is the only correct way to add a new DOM node for a row. Notice that
11865 * the new row is appended to the bottom of the list of rows, and does not
11866 * require renumbering (of the rowIndex property) of previous rows.
11867 *
11868 * If you think you want a new blank row somewhere in the middle of the
11869 * terminal, look into moveRows_().
11870 *
11871 * This method does not pay attention to vtScrollTop/Bottom, since you should
11872 * be using moveRows() in cases where they would matter.
11873 *
11874 * The cursor will be positioned at column 0 of the first inserted line.
11875 *
11876 * @param {number} count The number of rows to created.
11877 */
11878hterm.Terminal.prototype.appendRows_ = function(count) {
11879 var cursorRow = this.screen_.rowsArray.length;
11880 var offset = this.scrollbackRows_.length + cursorRow;
11881 for (var i = 0; i < count; i++) {
11882 var row = this.document_.createElement('x-row');
11883 row.appendChild(this.document_.createTextNode(''));
11884 row.rowIndex = offset + i;
11885 this.screen_.pushRow(row);
11886 }
11887
11888 var extraRows = this.screen_.rowsArray.length - this.screenSize.height;
11889 if (extraRows > 0) {
11890 var ary = this.screen_.shiftRows(extraRows);
11891 Array.prototype.push.apply(this.scrollbackRows_, ary);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011892 if (this.scrollPort_.isScrolledEnd) this.scheduleScrollDown_();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011893 }
11894
11895 if (cursorRow >= this.screen_.rowsArray.length)
11896 cursorRow = this.screen_.rowsArray.length - 1;
11897
11898 this.setAbsoluteCursorPosition(cursorRow, 0);
11899};
11900
11901/**
11902 * Relocate rows from one part of the addressable screen to another.
11903 *
11904 * This is used to recycle rows during VT scrolls (those which are driven
11905 * by VT commands, rather than by the user manipulating the scrollbar.)
11906 *
11907 * In this case, the blank lines scrolled into the scroll region are made of
11908 * the nodes we scrolled off. These have their rowIndex properties carefully
11909 * renumbered so as not to confuse the ScrollPort.
11910 *
11911 * @param {number} fromIndex The start index.
11912 * @param {number} count The number of rows to move.
11913 * @param {number} toIndex The destination index.
11914 */
11915hterm.Terminal.prototype.moveRows_ = function(fromIndex, count, toIndex) {
11916 var ary = this.screen_.removeRows(fromIndex, count);
11917 this.screen_.insertRows(toIndex, ary);
11918
11919 var start, end;
11920 if (fromIndex < toIndex) {
11921 start = fromIndex;
11922 end = toIndex + count;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011923 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011924 start = toIndex;
11925 end = fromIndex + count;
11926 }
11927
11928 this.renumberRows_(start, end);
11929 this.scrollPort_.scheduleInvalidate();
11930};
11931
11932/**
11933 * Renumber the rowIndex property of the given range of rows.
11934 *
11935 * The start and end indices are relative to the screen, not the scrollback.
11936 * Rows in the scrollback buffer cannot be renumbered. Since they are not
11937 * addressable (you can't delete them, scroll them, etc), you should have
11938 * no need to renumber scrollback rows.
11939 *
11940 * @param {number} start The start index.
11941 * @param {number} end The end index.
11942 * @param {hterm.Screen} opt_screen The screen to renumber.
11943 */
11944hterm.Terminal.prototype.renumberRows_ = function(start, end, opt_screen) {
11945 var screen = opt_screen || this.screen_;
11946
11947 var offset = this.scrollbackRows_.length;
11948 for (var i = start; i < end; i++) {
11949 screen.rowsArray[i].rowIndex = offset + i;
11950 }
11951};
11952
11953/**
11954 * Print a string to the terminal.
11955 *
11956 * This respects the current insert and wraparound modes. It will add new lines
11957 * to the end of the terminal, scrolling off the top into the scrollback buffer
11958 * if necessary.
11959 *
11960 * The string is *not* parsed for escape codes. Use the interpret() method if
11961 * that's what you're after.
11962 *
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011963 * @param {string} str The string to print.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011964 */
11965hterm.Terminal.prototype.print = function(str) {
11966 var startOffset = 0;
11967
11968 var strWidth = lib.wc.strWidth(str);
11969
11970 while (startOffset < strWidth) {
11971 if (this.options_.wraparound && this.screen_.cursorPosition.overflow) {
11972 this.screen_.commitLineOverflow();
11973 this.newLine();
11974 }
11975
11976 var count = strWidth - startOffset;
11977 var didOverflow = false;
11978 var substr;
11979
11980 if (this.screen_.cursorPosition.column + count >= this.screenSize.width) {
11981 didOverflow = true;
11982 count = this.screenSize.width - this.screen_.cursorPosition.column;
11983 }
11984
11985 if (didOverflow && !this.options_.wraparound) {
11986 // If the string overflowed the line but wraparound is off, then the
11987 // last printed character should be the last of the string.
11988 // TODO: This will add to our problems with multibyte UTF-16 characters.
11989 substr = lib.wc.substr(str, startOffset, count - 1) +
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011990 lib.wc.substr(str, strWidth - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011991 count = strWidth;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011992 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011993 substr = lib.wc.substr(str, startOffset, count);
11994 }
11995
11996 var tokens = hterm.TextAttributes.splitWidecharString(substr);
11997 for (var i = 0; i < tokens.length; i++) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011998 if (tokens[i].wcNode) this.screen_.textAttributes.wcNode = true;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011999
12000 if (this.options_.insertMode) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012001 this.screen_.insertString(tokens[i].str);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012002 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012003 this.screen_.overwriteString(tokens[i].str);
12004 }
12005 this.screen_.textAttributes.wcNode = false;
12006 }
12007
12008 this.screen_.maybeClipCurrentRow();
12009 startOffset += count;
12010 }
12011
12012 this.scheduleSyncCursorPosition_();
12013
12014 if (this.scrollOnOutput_)
12015 this.scrollPort_.scrollRowToBottom(this.getRowCount());
12016};
12017
12018/**
12019 * Set the VT scroll region.
12020 *
12021 * This also resets the cursor position to the absolute (0, 0) position, since
12022 * that's what xterm appears to do.
12023 *
12024 * Setting the scroll region to the full height of the terminal will clear
12025 * the scroll region. This is *NOT* what most terminals do. We're explicitly
12026 * going "off-spec" here because it makes `screen` and `tmux` overflow into the
12027 * local scrollback buffer, which means the scrollbars and shift-pgup/pgdn
12028 * continue to work as most users would expect.
12029 *
12030 * @param {integer} scrollTop The zero-based top of the scroll region.
12031 * @param {integer} scrollBottom The zero-based bottom of the scroll region,
12032 * inclusive.
12033 */
12034hterm.Terminal.prototype.setVTScrollRegion = function(scrollTop, scrollBottom) {
12035 if (scrollTop == 0 && scrollBottom == this.screenSize.height - 1) {
12036 this.vtScrollTop_ = null;
12037 this.vtScrollBottom_ = null;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012038 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012039 this.vtScrollTop_ = scrollTop;
12040 this.vtScrollBottom_ = scrollBottom;
12041 }
12042};
12043
12044/**
12045 * Return the top row index according to the VT.
12046 *
12047 * This will return 0 unless the terminal has been told to restrict scrolling
12048 * to some lower row. It is used for some VT cursor positioning and scrolling
12049 * commands.
12050 *
12051 * @return {integer} The topmost row in the terminal's scroll region.
12052 */
12053hterm.Terminal.prototype.getVTScrollTop = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012054 if (this.vtScrollTop_ != null) return this.vtScrollTop_;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012055
12056 return 0;
12057};
12058
12059/**
12060 * Return the bottom row index according to the VT.
12061 *
12062 * This will return the height of the terminal unless the it has been told to
12063 * restrict scrolling to some higher row. It is used for some VT cursor
12064 * positioning and scrolling commands.
12065 *
12066 * @return {integer} The bottom most row in the terminal's scroll region.
12067 */
12068hterm.Terminal.prototype.getVTScrollBottom = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012069 if (this.vtScrollBottom_ != null) return this.vtScrollBottom_;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012070
12071 return this.screenSize.height - 1;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012072};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012073
12074/**
12075 * Process a '\n' character.
12076 *
12077 * If the cursor is on the final row of the terminal this will append a new
12078 * blank row to the screen and scroll the topmost row into the scrollback
12079 * buffer.
12080 *
12081 * Otherwise, this moves the cursor to column zero of the next row.
12082 */
12083hterm.Terminal.prototype.newLine = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012084 var cursorAtEndOfScreen =
12085 (this.screen_.cursorPosition.row == this.screen_.rowsArray.length - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012086
12087 if (this.vtScrollBottom_ != null) {
12088 // A VT Scroll region is active, we never append new rows.
12089 if (this.screen_.cursorPosition.row == this.vtScrollBottom_) {
12090 // We're at the end of the VT Scroll Region, perform a VT scroll.
12091 this.vtScrollUp(1);
12092 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012093 } else if (cursorAtEndOfScreen) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012094 // We're at the end of the screen, the only thing to do is put the
12095 // cursor to column 0.
12096 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012097 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012098 // Anywhere else, advance the cursor row, and reset the column.
12099 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
12100 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012101 } else if (cursorAtEndOfScreen) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012102 // We're at the end of the screen. Append a new row to the terminal,
12103 // shifting the top row into the scrollback.
12104 this.appendRows_(1);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012105 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012106 // Anywhere else in the screen just moves the cursor.
12107 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
12108 }
12109};
12110
12111/**
12112 * Like newLine(), except maintain the cursor column.
12113 */
12114hterm.Terminal.prototype.lineFeed = function() {
12115 var column = this.screen_.cursorPosition.column;
12116 this.newLine();
12117 this.setCursorColumn(column);
12118};
12119
12120/**
12121 * If autoCarriageReturn is set then newLine(), else lineFeed().
12122 */
12123hterm.Terminal.prototype.formFeed = function() {
12124 if (this.options_.autoCarriageReturn) {
12125 this.newLine();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012126 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012127 this.lineFeed();
12128 }
12129};
12130
12131/**
12132 * Move the cursor up one row, possibly inserting a blank line.
12133 *
12134 * The cursor column is not changed.
12135 */
12136hterm.Terminal.prototype.reverseLineFeed = function() {
12137 var scrollTop = this.getVTScrollTop();
12138 var currentRow = this.screen_.cursorPosition.row;
12139
12140 if (currentRow == scrollTop) {
12141 this.insertLines(1);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012142 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012143 this.setAbsoluteCursorRow(currentRow - 1);
12144 }
12145};
12146
12147/**
12148 * Replace all characters to the left of the current cursor with the space
12149 * character.
12150 *
12151 * TODO(rginda): This should probably *remove* the characters (not just replace
12152 * with a space) if there are no characters at or beyond the current cursor
12153 * position.
12154 */
12155hterm.Terminal.prototype.eraseToLeft = function() {
12156 var cursor = this.saveCursor();
12157 this.setCursorColumn(0);
12158 this.screen_.overwriteString(lib.f.getWhitespace(cursor.column + 1));
12159 this.restoreCursor(cursor);
12160};
12161
12162/**
12163 * Erase a given number of characters to the right of the cursor.
12164 *
12165 * The cursor position is unchanged.
12166 *
12167 * If the current background color is not the default background color this
12168 * will insert spaces rather than delete. This is unfortunate because the
12169 * trailing space will affect text selection, but it's difficult to come up
12170 * with a way to style empty space that wouldn't trip up the hterm.Screen
12171 * code.
12172 *
12173 * eraseToRight is ignored in the presence of a cursor overflow. This deviates
12174 * from xterm, but agrees with gnome-terminal and konsole, xfce4-terminal. See
12175 * crbug.com/232390 for details.
12176 *
12177 * @param {number} opt_count The number of characters to erase.
12178 */
12179hterm.Terminal.prototype.eraseToRight = function(opt_count) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012180 if (this.screen_.cursorPosition.overflow) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012181
12182 var maxCount = this.screenSize.width - this.screen_.cursorPosition.column;
12183 var count = opt_count ? Math.min(opt_count, maxCount) : maxCount;
12184
12185 if (this.screen_.textAttributes.background ===
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012186 this.screen_.textAttributes.DEFAULT_COLOR) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012187 var cursorRow = this.screen_.rowsArray[this.screen_.cursorPosition.row];
12188 if (hterm.TextAttributes.nodeWidth(cursorRow) <=
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012189 this.screen_.cursorPosition.column + count) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012190 this.screen_.deleteChars(count);
12191 this.clearCursorOverflow();
12192 return;
12193 }
12194 }
12195
12196 var cursor = this.saveCursor();
12197 this.screen_.overwriteString(lib.f.getWhitespace(count));
12198 this.restoreCursor(cursor);
12199 this.clearCursorOverflow();
12200};
12201
12202/**
12203 * Erase the current line.
12204 *
12205 * The cursor position is unchanged.
12206 */
12207hterm.Terminal.prototype.eraseLine = function() {
12208 var cursor = this.saveCursor();
12209 this.screen_.clearCursorRow();
12210 this.restoreCursor(cursor);
12211 this.clearCursorOverflow();
12212};
12213
12214/**
12215 * Erase all characters from the start of the screen to the current cursor
12216 * position, regardless of scroll region.
12217 *
12218 * The cursor position is unchanged.
12219 */
12220hterm.Terminal.prototype.eraseAbove = function() {
12221 var cursor = this.saveCursor();
12222
12223 this.eraseToLeft();
12224
12225 for (var i = 0; i < cursor.row; i++) {
12226 this.setAbsoluteCursorPosition(i, 0);
12227 this.screen_.clearCursorRow();
12228 }
12229
12230 this.restoreCursor(cursor);
12231 this.clearCursorOverflow();
12232};
12233
12234/**
12235 * Erase all characters from the current cursor position to the end of the
12236 * screen, regardless of scroll region.
12237 *
12238 * The cursor position is unchanged.
12239 */
12240hterm.Terminal.prototype.eraseBelow = function() {
12241 var cursor = this.saveCursor();
12242
12243 this.eraseToRight();
12244
12245 var bottom = this.screenSize.height - 1;
12246 for (var i = cursor.row + 1; i <= bottom; i++) {
12247 this.setAbsoluteCursorPosition(i, 0);
12248 this.screen_.clearCursorRow();
12249 }
12250
12251 this.restoreCursor(cursor);
12252 this.clearCursorOverflow();
12253};
12254
12255/**
12256 * Fill the terminal with a given character.
12257 *
12258 * This methods does not respect the VT scroll region.
12259 *
12260 * @param {string} ch The character to use for the fill.
12261 */
12262hterm.Terminal.prototype.fill = function(ch) {
12263 var cursor = this.saveCursor();
12264
12265 this.setAbsoluteCursorPosition(0, 0);
12266 for (var row = 0; row < this.screenSize.height; row++) {
12267 for (var col = 0; col < this.screenSize.width; col++) {
12268 this.setAbsoluteCursorPosition(row, col);
12269 this.screen_.overwriteString(ch);
12270 }
12271 }
12272
12273 this.restoreCursor(cursor);
12274};
12275
12276/**
12277 * Erase the entire display and leave the cursor at (0, 0).
12278 *
12279 * This does not respect the scroll region.
12280 *
12281 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
12282 * to the current screen.
12283 */
12284hterm.Terminal.prototype.clearHome = function(opt_screen) {
12285 var screen = opt_screen || this.screen_;
12286 var bottom = screen.getHeight();
12287
12288 if (bottom == 0) {
12289 // Empty screen, nothing to do.
12290 return;
12291 }
12292
12293 for (var i = 0; i < bottom; i++) {
12294 screen.setCursorPosition(i, 0);
12295 screen.clearCursorRow();
12296 }
12297
12298 screen.setCursorPosition(0, 0);
12299};
12300
12301/**
12302 * Erase the entire display without changing the cursor position.
12303 *
12304 * The cursor position is unchanged. This does not respect the scroll
12305 * region.
12306 *
12307 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
12308 * to the current screen.
12309 */
12310hterm.Terminal.prototype.clear = function(opt_screen) {
12311 var screen = opt_screen || this.screen_;
12312 var cursor = screen.cursorPosition.clone();
12313 this.clearHome(screen);
12314 screen.setCursorPosition(cursor.row, cursor.column);
12315};
12316
12317/**
12318 * VT command to insert lines at the current cursor row.
12319 *
12320 * This respects the current scroll region. Rows pushed off the bottom are
12321 * lost (they won't show up in the scrollback buffer).
12322 *
12323 * @param {integer} count The number of lines to insert.
12324 */
12325hterm.Terminal.prototype.insertLines = function(count) {
12326 var cursorRow = this.screen_.cursorPosition.row;
12327
12328 var bottom = this.getVTScrollBottom();
12329 count = Math.min(count, bottom - cursorRow);
12330
12331 // The moveCount is the number of rows we need to relocate to make room for
12332 // the new row(s). The count is the distance to move them.
12333 var moveCount = bottom - cursorRow - count + 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012334 if (moveCount) this.moveRows_(cursorRow, moveCount, cursorRow + count);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012335
12336 for (var i = count - 1; i >= 0; i--) {
12337 this.setAbsoluteCursorPosition(cursorRow + i, 0);
12338 this.screen_.clearCursorRow();
12339 }
12340};
12341
12342/**
12343 * VT command to delete lines at the current cursor row.
12344 *
12345 * New rows are added to the bottom of scroll region to take their place. New
12346 * rows are strictly there to take up space and have no content or style.
12347 *
12348 * @param {number} count The number of lines to delete.
12349 */
12350hterm.Terminal.prototype.deleteLines = function(count) {
12351 var cursor = this.saveCursor();
12352
12353 var top = cursor.row;
12354 var bottom = this.getVTScrollBottom();
12355
12356 var maxCount = bottom - top + 1;
12357 count = Math.min(count, maxCount);
12358
12359 var moveStart = bottom - count + 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012360 if (count != maxCount) this.moveRows_(top, count, moveStart);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012361
12362 for (var i = 0; i < count; i++) {
12363 this.setAbsoluteCursorPosition(moveStart + i, 0);
12364 this.screen_.clearCursorRow();
12365 }
12366
12367 this.restoreCursor(cursor);
12368 this.clearCursorOverflow();
12369};
12370
12371/**
12372 * Inserts the given number of spaces at the current cursor position.
12373 *
12374 * The cursor position is not changed.
12375 *
12376 * @param {number} count The number of spaces to insert.
12377 */
12378hterm.Terminal.prototype.insertSpace = function(count) {
12379 var cursor = this.saveCursor();
12380
12381 var ws = lib.f.getWhitespace(count || 1);
12382 this.screen_.insertString(ws);
12383 this.screen_.maybeClipCurrentRow();
12384
12385 this.restoreCursor(cursor);
12386 this.clearCursorOverflow();
12387};
12388
12389/**
12390 * Forward-delete the specified number of characters starting at the cursor
12391 * position.
12392 *
12393 * @param {integer} count The number of characters to delete.
12394 */
12395hterm.Terminal.prototype.deleteChars = function(count) {
12396 var deleted = this.screen_.deleteChars(count);
12397 if (deleted && !this.screen_.textAttributes.isDefault()) {
12398 var cursor = this.saveCursor();
12399 this.setCursorColumn(this.screenSize.width - deleted);
12400 this.screen_.insertString(lib.f.getWhitespace(deleted));
12401 this.restoreCursor(cursor);
12402 }
12403
12404 this.clearCursorOverflow();
12405};
12406
12407/**
12408 * Shift rows in the scroll region upwards by a given number of lines.
12409 *
12410 * New rows are inserted at the bottom of the scroll region to fill the
12411 * vacated rows. The new rows not filled out with the current text attributes.
12412 *
12413 * This function does not affect the scrollback rows at all. Rows shifted
12414 * off the top are lost.
12415 *
12416 * The cursor position is not altered.
12417 *
12418 * @param {integer} count The number of rows to scroll.
12419 */
12420hterm.Terminal.prototype.vtScrollUp = function(count) {
12421 var cursor = this.saveCursor();
12422
12423 this.setAbsoluteCursorRow(this.getVTScrollTop());
12424 this.deleteLines(count);
12425
12426 this.restoreCursor(cursor);
12427};
12428
12429/**
12430 * Shift rows below the cursor down by a given number of lines.
12431 *
12432 * This function respects the current scroll region.
12433 *
12434 * New rows are inserted at the top of the scroll region to fill the
12435 * vacated rows. The new rows not filled out with the current text attributes.
12436 *
12437 * This function does not affect the scrollback rows at all. Rows shifted
12438 * off the bottom are lost.
12439 *
12440 * @param {integer} count The number of rows to scroll.
12441 */
12442hterm.Terminal.prototype.vtScrollDown = function(opt_count) {
12443 var cursor = this.saveCursor();
12444
12445 this.setAbsoluteCursorPosition(this.getVTScrollTop(), 0);
12446 this.insertLines(opt_count);
12447
12448 this.restoreCursor(cursor);
12449};
12450
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012451/**
12452 * Set the cursor position.
12453 *
12454 * The cursor row is relative to the scroll region if the terminal has
12455 * 'origin mode' enabled, or relative to the addressable screen otherwise.
12456 *
12457 * @param {integer} row The new zero-based cursor row.
12458 * @param {integer} row The new zero-based cursor column.
12459 */
12460hterm.Terminal.prototype.setCursorPosition = function(row, column) {
12461 if (this.options_.originMode) {
12462 this.setRelativeCursorPosition(row, column);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012463 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012464 this.setAbsoluteCursorPosition(row, column);
12465 }
12466};
12467
12468/**
12469 * Move the cursor relative to its current position.
12470 *
12471 * @param {number} row
12472 * @param {number} column
12473 */
12474hterm.Terminal.prototype.setRelativeCursorPosition = function(row, column) {
12475 var scrollTop = this.getVTScrollTop();
12476 row = lib.f.clamp(row + scrollTop, scrollTop, this.getVTScrollBottom());
12477 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
12478 this.screen_.setCursorPosition(row, column);
12479};
12480
12481/**
12482 * Move the cursor to the specified position.
12483 *
12484 * @param {number} row
12485 * @param {number} column
12486 */
12487hterm.Terminal.prototype.setAbsoluteCursorPosition = function(row, column) {
12488 row = lib.f.clamp(row, 0, this.screenSize.height - 1);
12489 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
12490 this.screen_.setCursorPosition(row, column);
12491};
12492
12493/**
12494 * Set the cursor column.
12495 *
12496 * @param {integer} column The new zero-based cursor column.
12497 */
12498hterm.Terminal.prototype.setCursorColumn = function(column) {
12499 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, column);
12500};
12501
12502/**
12503 * Return the cursor column.
12504 *
12505 * @return {integer} The zero-based cursor column.
12506 */
12507hterm.Terminal.prototype.getCursorColumn = function() {
12508 return this.screen_.cursorPosition.column;
12509};
12510
12511/**
12512 * Set the cursor row.
12513 *
12514 * The cursor row is relative to the scroll region if the terminal has
12515 * 'origin mode' enabled, or relative to the addressable screen otherwise.
12516 *
12517 * @param {integer} row The new cursor row.
12518 */
12519hterm.Terminal.prototype.setAbsoluteCursorRow = function(row) {
12520 this.setAbsoluteCursorPosition(row, this.screen_.cursorPosition.column);
12521};
12522
12523/**
12524 * Return the cursor row.
12525 *
12526 * @return {integer} The zero-based cursor row.
12527 */
12528hterm.Terminal.prototype.getCursorRow = function() {
12529 return this.screen_.cursorPosition.row;
12530};
12531
12532/**
12533 * Request that the ScrollPort redraw itself soon.
12534 *
12535 * The redraw will happen asynchronously, soon after the call stack winds down.
12536 * Multiple calls will be coalesced into a single redraw.
12537 */
12538hterm.Terminal.prototype.scheduleRedraw_ = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012539 if (this.timeouts_.redraw) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012540
12541 var self = this;
12542 this.timeouts_.redraw = setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012543 delete self.timeouts_.redraw;
12544 self.scrollPort_.redraw_();
12545 }, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012546};
12547
12548/**
12549 * Request that the ScrollPort be scrolled to the bottom.
12550 *
12551 * The scroll will happen asynchronously, soon after the call stack winds down.
12552 * Multiple calls will be coalesced into a single scroll.
12553 *
12554 * This affects the scrollbar position of the ScrollPort, and has nothing to
12555 * do with the VT scroll commands.
12556 */
12557hterm.Terminal.prototype.scheduleScrollDown_ = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012558 if (this.timeouts_.scrollDown) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012559
12560 var self = this;
12561 this.timeouts_.scrollDown = setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012562 delete self.timeouts_.scrollDown;
12563 self.scrollPort_.scrollRowToBottom(self.getRowCount());
12564 }, 10);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012565};
12566
12567/**
12568 * Move the cursor up a specified number of rows.
12569 *
12570 * @param {integer} count The number of rows to move the cursor.
12571 */
12572hterm.Terminal.prototype.cursorUp = function(count) {
12573 return this.cursorDown(-(count || 1));
12574};
12575
12576/**
12577 * Move the cursor down a specified number of rows.
12578 *
12579 * @param {integer} count The number of rows to move the cursor.
12580 */
12581hterm.Terminal.prototype.cursorDown = function(count) {
12582 count = count || 1;
12583 var minHeight = (this.options_.originMode ? this.getVTScrollTop() : 0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012584 var maxHeight =
12585 (this.options_.originMode ? this.getVTScrollBottom() :
12586 this.screenSize.height - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012587
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012588 var row = lib.f.clamp(
12589 this.screen_.cursorPosition.row + count, minHeight, maxHeight);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012590 this.setAbsoluteCursorRow(row);
12591};
12592
12593/**
12594 * Move the cursor left a specified number of columns.
12595 *
12596 * If reverse wraparound mode is enabled and the previous row wrapped into
12597 * the current row then we back up through the wraparound as well.
12598 *
12599 * @param {integer} count The number of columns to move the cursor.
12600 */
12601hterm.Terminal.prototype.cursorLeft = function(count) {
12602 count = count || 1;
12603
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012604 if (count < 1) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012605
12606 var currentColumn = this.screen_.cursorPosition.column;
12607 if (this.options_.reverseWraparound) {
12608 if (this.screen_.cursorPosition.overflow) {
12609 // If this cursor is in the right margin, consume one count to get it
12610 // back to the last column. This only applies when we're in reverse
12611 // wraparound mode.
12612 count--;
12613 this.clearCursorOverflow();
12614
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012615 if (!count) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012616 }
12617
12618 var newRow = this.screen_.cursorPosition.row;
12619 var newColumn = currentColumn - count;
12620 if (newColumn < 0) {
12621 newRow = newRow - Math.floor(count / this.screenSize.width) - 1;
12622 if (newRow < 0) {
12623 // xterm also wraps from row 0 to the last row.
12624 newRow = this.screenSize.height + newRow % this.screenSize.height;
12625 }
12626 newColumn = this.screenSize.width + newColumn % this.screenSize.width;
12627 }
12628
12629 this.setCursorPosition(Math.max(newRow, 0), newColumn);
12630
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012631 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012632 var newColumn = Math.max(currentColumn - count, 0);
12633 this.setCursorColumn(newColumn);
12634 }
12635};
12636
12637/**
12638 * Move the cursor right a specified number of columns.
12639 *
12640 * @param {integer} count The number of columns to move the cursor.
12641 */
12642hterm.Terminal.prototype.cursorRight = function(count) {
12643 count = count || 1;
12644
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012645 if (count < 1) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012646
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012647 var column = lib.f.clamp(
12648 this.screen_.cursorPosition.column + count, 0, this.screenSize.width - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012649 this.setCursorColumn(column);
12650};
12651
12652/**
12653 * Reverse the foreground and background colors of the terminal.
12654 *
12655 * This only affects text that was drawn with no attributes.
12656 *
12657 * TODO(rginda): Test xterm to see if reverse is respected for text that has
12658 * been drawn with attributes that happen to coincide with the default
12659 * 'no-attribute' colors. My guess is probably not.
12660 *
12661 * @param {boolean} state The state to set.
12662 */
12663hterm.Terminal.prototype.setReverseVideo = function(state) {
12664 this.options_.reverseVideo = state;
12665 if (state) {
12666 this.scrollPort_.setForegroundColor(this.prefs_.get('background-color'));
12667 this.scrollPort_.setBackgroundColor(this.prefs_.get('foreground-color'));
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012668 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012669 this.scrollPort_.setForegroundColor(this.prefs_.get('foreground-color'));
12670 this.scrollPort_.setBackgroundColor(this.prefs_.get('background-color'));
12671 }
12672};
12673
12674/**
12675 * Ring the terminal bell.
12676 *
12677 * This will not play the bell audio more than once per second.
12678 */
12679hterm.Terminal.prototype.ringBell = function() {
12680 this.cursorNode_.style.backgroundColor =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012681 this.scrollPort_.getForegroundColor();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012682
12683 var self = this;
12684 setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012685 self.cursorNode_.style.backgroundColor = self.prefs_.get('cursor-color');
12686 }, 200);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012687
12688 // bellSquelchTimeout_ affects both audio and notification bells.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012689 if (this.bellSquelchTimeout_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012690
12691 if (this.bellAudio_.getAttribute('src')) {
12692 this.bellAudio_.play();
12693 this.bellSequelchTimeout_ = setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012694 delete this.bellSquelchTimeout_;
12695 }.bind(this), 500);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012696 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012697 delete this.bellSquelchTimeout_;
12698 }
12699
12700 if (this.desktopNotificationBell_ && !this.document_.hasFocus()) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012701 var n = new Notification(lib.f.replaceVars(
12702 hterm.desktopNotificationTitle,
12703 {'title': window.document.title || 'hterm'}));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012704 this.bellNotificationList_.push(n);
12705 // TODO: Should we try to raise the window here?
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012706 n.onclick = function() {
12707 self.closeBellNotifications_();
12708 };
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012709 }
12710};
12711
12712/**
12713 * Set the origin mode bit.
12714 *
12715 * If origin mode is on, certain VT cursor and scrolling commands measure their
12716 * row parameter relative to the VT scroll region. Otherwise, row 0 corresponds
12717 * to the top of the addressable screen.
12718 *
12719 * Defaults to off.
12720 *
12721 * @param {boolean} state True to set origin mode, false to unset.
12722 */
12723hterm.Terminal.prototype.setOriginMode = function(state) {
12724 this.options_.originMode = state;
12725 this.setCursorPosition(0, 0);
12726};
12727
12728/**
12729 * Set the insert mode bit.
12730 *
12731 * If insert mode is on, existing text beyond the cursor position will be
12732 * shifted right to make room for new text. Otherwise, new text overwrites
12733 * any existing text.
12734 *
12735 * Defaults to off.
12736 *
12737 * @param {boolean} state True to set insert mode, false to unset.
12738 */
12739hterm.Terminal.prototype.setInsertMode = function(state) {
12740 this.options_.insertMode = state;
12741};
12742
12743/**
12744 * Set the auto carriage return bit.
12745 *
12746 * If auto carriage return is on then a formfeed character is interpreted
12747 * as a newline, otherwise it's the same as a linefeed. The difference boils
12748 * down to whether or not the cursor column is reset.
12749 *
12750 * @param {boolean} state The state to set.
12751 */
12752hterm.Terminal.prototype.setAutoCarriageReturn = function(state) {
12753 this.options_.autoCarriageReturn = state;
12754};
12755
12756/**
12757 * Set the wraparound mode bit.
12758 *
12759 * If wraparound mode is on, certain VT commands will allow the cursor to wrap
12760 * to the start of the following row. Otherwise, the cursor is clamped to the
12761 * end of the screen and attempts to write past it are ignored.
12762 *
12763 * Defaults to on.
12764 *
12765 * @param {boolean} state True to set wraparound mode, false to unset.
12766 */
12767hterm.Terminal.prototype.setWraparound = function(state) {
12768 this.options_.wraparound = state;
12769};
12770
12771/**
12772 * Set the reverse-wraparound mode bit.
12773 *
12774 * If wraparound mode is off, certain VT commands will allow the cursor to wrap
12775 * to the end of the previous row. Otherwise, the cursor is clamped to column
12776 * 0.
12777 *
12778 * Defaults to off.
12779 *
12780 * @param {boolean} state True to set reverse-wraparound mode, false to unset.
12781 */
12782hterm.Terminal.prototype.setReverseWraparound = function(state) {
12783 this.options_.reverseWraparound = state;
12784};
12785
12786/**
12787 * Selects between the primary and alternate screens.
12788 *
12789 * If alternate mode is on, the alternate screen is active. Otherwise the
12790 * primary screen is active.
12791 *
12792 * Swapping screens has no effect on the scrollback buffer.
12793 *
12794 * Each screen maintains its own cursor position.
12795 *
12796 * Defaults to off.
12797 *
12798 * @param {boolean} state True to set alternate mode, false to unset.
12799 */
12800hterm.Terminal.prototype.setAlternateMode = function(state) {
12801 var cursor = this.saveCursor();
12802 this.screen_ = state ? this.alternateScreen_ : this.primaryScreen_;
12803
12804 if (this.screen_.rowsArray.length &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012805 this.screen_.rowsArray[0].rowIndex != this.scrollbackRows_.length) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012806 // If the screen changed sizes while we were away, our rowIndexes may
12807 // be incorrect.
12808 var offset = this.scrollbackRows_.length;
12809 var ary = this.screen_.rowsArray;
12810 for (var i = 0; i < ary.length; i++) {
12811 ary[i].rowIndex = offset + i;
12812 }
12813 }
12814
12815 this.realizeWidth_(this.screenSize.width);
12816 this.realizeHeight_(this.screenSize.height);
12817 this.scrollPort_.syncScrollHeight();
12818 this.scrollPort_.invalidate();
12819
12820 this.restoreCursor(cursor);
12821 this.scrollPort_.resize();
12822};
12823
12824/**
12825 * Set the cursor-blink mode bit.
12826 *
12827 * If cursor-blink is on, the cursor will blink when it is visible. Otherwise
12828 * a visible cursor does not blink.
12829 *
12830 * You should make sure to turn blinking off if you're going to dispose of a
12831 * terminal, otherwise you'll leak a timeout.
12832 *
12833 * Defaults to on.
12834 *
12835 * @param {boolean} state True to set cursor-blink mode, false to unset.
12836 */
12837hterm.Terminal.prototype.setCursorBlink = function(state) {
12838 this.options_.cursorBlink = state;
12839
12840 if (!state && this.timeouts_.cursorBlink) {
12841 clearTimeout(this.timeouts_.cursorBlink);
12842 delete this.timeouts_.cursorBlink;
12843 }
12844
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012845 if (this.options_.cursorVisible) this.setCursorVisible(true);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012846};
12847
12848/**
12849 * Set the cursor-visible mode bit.
12850 *
12851 * If cursor-visible is on, the cursor will be visible. Otherwise it will not.
12852 *
12853 * Defaults to on.
12854 *
12855 * @param {boolean} state True to set cursor-visible mode, false to unset.
12856 */
12857hterm.Terminal.prototype.setCursorVisible = function(state) {
12858 this.options_.cursorVisible = state;
12859
12860 if (!state) {
12861 if (this.timeouts_.cursorBlink) {
12862 clearTimeout(this.timeouts_.cursorBlink);
12863 delete this.timeouts_.cursorBlink;
12864 }
12865 this.cursorNode_.style.opacity = '0';
12866 return;
12867 }
12868
12869 this.syncCursorPosition_();
12870
12871 this.cursorNode_.style.opacity = '1';
12872
12873 if (this.options_.cursorBlink) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012874 if (this.timeouts_.cursorBlink) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012875
12876 this.onCursorBlink_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012877 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012878 if (this.timeouts_.cursorBlink) {
12879 clearTimeout(this.timeouts_.cursorBlink);
12880 delete this.timeouts_.cursorBlink;
12881 }
12882 }
12883};
12884
12885/**
12886 * Synchronizes the visible cursor and document selection with the current
12887 * cursor coordinates.
12888 */
12889hterm.Terminal.prototype.syncCursorPosition_ = function() {
12890 var topRowIndex = this.scrollPort_.getTopRowIndex();
12891 var bottomRowIndex = this.scrollPort_.getBottomRowIndex(topRowIndex);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012892 var cursorRowIndex =
12893 this.scrollbackRows_.length + this.screen_.cursorPosition.row;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012894
12895 if (cursorRowIndex > bottomRowIndex) {
12896 // Cursor is scrolled off screen, move it outside of the visible area.
12897 this.cursorNode_.style.top = -this.scrollPort_.characterSize.height + 'px';
12898 return;
12899 }
12900
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012901 if (this.options_.cursorVisible && this.cursorNode_.style.display == 'none') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012902 // Re-display the terminal cursor if it was hidden by the mouse cursor.
12903 this.cursorNode_.style.display = '';
12904 }
12905
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012906 this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin +
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012907 this.scrollPort_.characterSize.height * (cursorRowIndex - topRowIndex) +
12908 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012909 this.cursorNode_.style.left = this.scrollPort_.characterSize.width *
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012910 this.screen_.cursorPosition.column +
12911 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012912
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012913 this.cursorNode_.setAttribute(
12914 'title',
12915 '(' + this.screen_.cursorPosition.row + ', ' +
12916 this.screen_.cursorPosition.column + ')');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012917
12918 // Update the caret for a11y purposes.
12919 var selection = this.document_.getSelection();
12920 if (selection && selection.isCollapsed)
12921 this.screen_.syncSelectionCaret(selection);
12922};
12923
12924/**
12925 * Adjusts the style of this.cursorNode_ according to the current cursor shape
12926 * and character cell dimensions.
12927 */
12928hterm.Terminal.prototype.restyleCursor_ = function() {
12929 var shape = this.cursorShape_;
12930
12931 if (this.cursorNode_.getAttribute('focus') == 'false') {
12932 // Always show a block cursor when unfocused.
12933 shape = hterm.Terminal.cursorShape.BLOCK;
12934 }
12935
12936 var style = this.cursorNode_.style;
12937
12938 style.width = this.scrollPort_.characterSize.width + 'px';
12939
12940 switch (shape) {
12941 case hterm.Terminal.cursorShape.BEAM:
12942 style.height = this.scrollPort_.characterSize.height + 'px';
12943 style.backgroundColor = 'transparent';
12944 style.borderBottomStyle = null;
12945 style.borderLeftStyle = 'solid';
12946 break;
12947
12948 case hterm.Terminal.cursorShape.UNDERLINE:
12949 style.height = this.scrollPort_.characterSize.baseline + 'px';
12950 style.backgroundColor = 'transparent';
12951 style.borderBottomStyle = 'solid';
12952 // correct the size to put it exactly at the baseline
12953 style.borderLeftStyle = null;
12954 break;
12955
12956 default:
12957 style.height = this.scrollPort_.characterSize.height + 'px';
12958 style.backgroundColor = this.cursorColor_;
12959 style.borderBottomStyle = null;
12960 style.borderLeftStyle = null;
12961 break;
12962 }
12963};
12964
12965/**
12966 * Synchronizes the visible cursor with the current cursor coordinates.
12967 *
12968 * The sync will happen asynchronously, soon after the call stack winds down.
12969 * Multiple calls will be coalesced into a single sync.
12970 */
12971hterm.Terminal.prototype.scheduleSyncCursorPosition_ = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012972 if (this.timeouts_.syncCursor) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012973
12974 var self = this;
12975 this.timeouts_.syncCursor = setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012976 self.syncCursorPosition_();
12977 delete self.timeouts_.syncCursor;
12978 }, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012979};
12980
12981/**
12982 * Show or hide the zoom warning.
12983 *
12984 * The zoom warning is a message warning the user that their browser zoom must
12985 * be set to 100% in order for hterm to function properly.
12986 *
12987 * @param {boolean} state True to show the message, false to hide it.
12988 */
12989hterm.Terminal.prototype.showZoomWarning_ = function(state) {
12990 if (!this.zoomWarningNode_) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012991 if (!state) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012992
12993 this.zoomWarningNode_ = this.document_.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012994 this.zoomWarningNode_.style.cssText =
12995 ('color: black;' +
12996 'background-color: #ff2222;' +
12997 'font-size: large;' +
12998 'border-radius: 8px;' +
12999 'opacity: 0.75;' +
13000 'padding: 0.2em 0.5em 0.2em 0.5em;' +
13001 'top: 0.5em;' +
13002 'right: 1.2em;' +
13003 'position: absolute;' +
13004 '-webkit-text-size-adjust: none;' +
13005 '-webkit-user-select: none;' +
13006 '-moz-text-size-adjust: none;' +
13007 '-moz-user-select: none;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013008
13009 this.zoomWarningNode_.addEventListener('click', function(e) {
13010 this.parentNode.removeChild(this);
13011 });
13012 }
13013
13014 this.zoomWarningNode_.textContent = lib.MessageManager.replaceReferences(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013015 hterm.zoomWarningMessage,
13016 [parseInt(this.scrollPort_.characterSize.zoomFactor * 100)]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013017
13018 this.zoomWarningNode_.style.fontFamily = this.prefs_.get('font-family');
13019
13020 if (state) {
13021 if (!this.zoomWarningNode_.parentNode)
13022 this.div_.parentNode.appendChild(this.zoomWarningNode_);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013023 } else if (this.zoomWarningNode_.parentNode) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013024 this.zoomWarningNode_.parentNode.removeChild(this.zoomWarningNode_);
13025 }
13026};
13027
13028/**
13029 * Show the terminal overlay for a given amount of time.
13030 *
13031 * The terminal overlay appears in inverse video in a large font, centered
13032 * over the terminal. You should probably keep the overlay message brief,
13033 * since it's in a large font and you probably aren't going to check the size
13034 * of the terminal first.
13035 *
13036 * @param {string} msg The text (not HTML) message to display in the overlay.
13037 * @param {number} opt_timeout The amount of time to wait before fading out
13038 * the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
13039 * stay up forever (or until the next overlay).
13040 */
13041hterm.Terminal.prototype.showOverlay = function(msg, opt_timeout) {
13042 if (!this.overlayNode_) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013043 if (!this.div_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013044
13045 this.overlayNode_ = this.document_.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013046 this.overlayNode_.style.cssText =
13047 ('border-radius: 15px;' +
13048 'font-size: xx-large;' +
13049 'opacity: 0.75;' +
13050 'padding: 0.2em 0.5em 0.2em 0.5em;' +
13051 'position: absolute;' +
13052 '-webkit-user-select: none;' +
13053 '-webkit-transition: opacity 180ms ease-in;' +
13054 '-moz-user-select: none;' +
13055 '-moz-transition: opacity 180ms ease-in;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013056
13057 this.overlayNode_.addEventListener('mousedown', function(e) {
13058 e.preventDefault();
13059 e.stopPropagation();
13060 }, true);
13061 }
13062
13063 this.overlayNode_.style.color = this.prefs_.get('background-color');
13064 this.overlayNode_.style.backgroundColor = this.prefs_.get('foreground-color');
13065 this.overlayNode_.style.fontFamily = this.prefs_.get('font-family');
13066
13067 this.overlayNode_.textContent = msg;
13068 this.overlayNode_.style.opacity = '0.75';
13069
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013070 if (!this.overlayNode_.parentNode) this.div_.appendChild(this.overlayNode_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013071
13072 var divSize = hterm.getClientSize(this.div_);
13073 var overlaySize = hterm.getClientSize(this.overlayNode_);
13074
13075 this.overlayNode_.style.top =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013076 (divSize.height - overlaySize.height) / 2 + 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013077 this.overlayNode_.style.left = (divSize.width - overlaySize.width -
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013078 this.scrollPort_.currentScrollbarWidthPx) /
13079 2 +
13080 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013081
13082 var self = this;
13083
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013084 if (this.overlayTimeout_) clearTimeout(this.overlayTimeout_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013085
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013086 if (opt_timeout === null) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013087
13088 this.overlayTimeout_ = setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013089 self.overlayNode_.style.opacity = '0';
13090 self.overlayTimeout_ = setTimeout(function() {
13091 if (self.overlayNode_.parentNode)
13092 self.overlayNode_.parentNode.removeChild(self.overlayNode_);
13093 self.overlayTimeout_ = null;
13094 self.overlayNode_.style.opacity = '0.75';
13095 }, 200);
13096 }, opt_timeout || 1500);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013097};
13098
13099/**
13100 * Paste from the system clipboard to the terminal.
13101 */
13102hterm.Terminal.prototype.paste = function() {
13103 hterm.pasteFromClipboard(this.document_);
13104};
13105
13106/**
13107 * Copy a string to the system clipboard.
13108 *
13109 * Note: If there is a selected range in the terminal, it'll be cleared.
13110 *
13111 * @param {string} str The string to copy.
13112 */
13113hterm.Terminal.prototype.copyStringToClipboard = function(str) {
13114 if (this.prefs_.get('enable-clipboard-notice'))
13115 setTimeout(this.showOverlay.bind(this, hterm.notifyCopyMessage, 500), 200);
13116
13117 var copySource = this.document_.createElement('pre');
13118 copySource.textContent = str;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013119 copySource.style.cssText =
13120 ('-webkit-user-select: text;' +
13121 '-moz-user-select: text;' +
13122 'position: absolute;' +
13123 'top: -99px');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013124
13125 this.document_.body.appendChild(copySource);
13126
13127 var selection = this.document_.getSelection();
13128 var anchorNode = selection.anchorNode;
13129 var anchorOffset = selection.anchorOffset;
13130 var focusNode = selection.focusNode;
13131 var focusOffset = selection.focusOffset;
13132
13133 selection.selectAllChildren(copySource);
13134
13135 hterm.copySelectionToClipboard(this.document_);
13136
13137 // IE doesn't support selection.extend. This means that the selection
13138 // won't return on IE.
13139 if (selection.extend) {
13140 selection.collapse(anchorNode, anchorOffset);
13141 selection.extend(focusNode, focusOffset);
13142 }
13143
13144 copySource.parentNode.removeChild(copySource);
13145};
13146
13147/**
13148 * Returns the selected text, or null if no text is selected.
13149 *
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013150 * @return {?string}
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013151 */
13152hterm.Terminal.prototype.getSelectionText = function() {
13153 var selection = this.scrollPort_.selection;
13154 selection.sync();
13155
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013156 if (selection.isCollapsed) return null;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013157
13158 // Start offset measures from the beginning of the line.
13159 var startOffset = selection.startOffset;
13160 var node = selection.startNode;
13161
13162 if (node.nodeName != 'X-ROW') {
13163 // If the selection doesn't start on an x-row node, then it must be
13164 // somewhere inside the x-row. Add any characters from previous siblings
13165 // into the start offset.
13166
13167 if (node.nodeName == '#text' && node.parentNode.nodeName == 'SPAN') {
13168 // If node is the text node in a styled span, move up to the span node.
13169 node = node.parentNode;
13170 }
13171
13172 while (node.previousSibling) {
13173 node = node.previousSibling;
13174 startOffset += hterm.TextAttributes.nodeWidth(node);
13175 }
13176 }
13177
13178 // End offset measures from the end of the line.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013179 var endOffset =
13180 (hterm.TextAttributes.nodeWidth(selection.endNode) - selection.endOffset);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013181 node = selection.endNode;
13182
13183 if (node.nodeName != 'X-ROW') {
13184 // If the selection doesn't end on an x-row node, then it must be
13185 // somewhere inside the x-row. Add any characters from following siblings
13186 // into the end offset.
13187
13188 if (node.nodeName == '#text' && node.parentNode.nodeName == 'SPAN') {
13189 // If node is the text node in a styled span, move up to the span node.
13190 node = node.parentNode;
13191 }
13192
13193 while (node.nextSibling) {
13194 node = node.nextSibling;
13195 endOffset += hterm.TextAttributes.nodeWidth(node);
13196 }
13197 }
13198
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013199 var rv = this.getRowsText(
13200 selection.startRow.rowIndex, selection.endRow.rowIndex + 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013201 return lib.wc.substring(rv, startOffset, lib.wc.strWidth(rv) - endOffset);
13202};
13203
13204/**
13205 * Copy the current selection to the system clipboard, then clear it after a
13206 * short delay.
13207 */
13208hterm.Terminal.prototype.copySelectionToClipboard = function() {
13209 var text = this.getSelectionText();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013210 if (text != null) this.copyStringToClipboard(text);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013211};
13212
13213hterm.Terminal.prototype.overlaySize = function() {
13214 this.showOverlay(this.screenSize.width + 'x' + this.screenSize.height);
13215};
13216
13217/**
13218 * Invoked by hterm.Terminal.Keyboard when a VT keystroke is detected.
13219 *
13220 * @param {string} string The VT string representing the keystroke, in UTF-16.
13221 */
13222hterm.Terminal.prototype.onVTKeystroke = function(string) {
13223 if (this.scrollOnKeystroke_)
13224 this.scrollPort_.scrollRowToBottom(this.getRowCount());
13225
13226 this.io.onVTKeystroke(this.keyboard.encode(string));
13227};
13228
13229/**
13230 * Launches url in a new tab.
13231 *
13232 * @param {string} url URL to launch in a new tab.
13233 */
13234hterm.Terminal.prototype.openUrl = function(url) {
13235 var win = window.open(url, '_blank');
13236 win.focus();
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013237};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013238
13239/**
13240 * Open the selected url.
13241 */
13242hterm.Terminal.prototype.openSelectedUrl_ = function() {
13243 var str = this.getSelectionText();
13244
13245 // If there is no selection, try and expand wherever they clicked.
13246 if (str == null) {
13247 this.screen_.expandSelection(this.document_.getSelection());
13248 str = this.getSelectionText();
13249 }
13250
13251 // Make sure URL is valid before opening.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013252 if (str.length > 2048 || str.search(/[\s\[\](){}<>"'\\^`]/) >= 0) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013253 // If the URL isn't anchored, it'll open relative to the extension.
13254 // We have no way of knowing the correct schema, so assume http.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013255 if (str.search('^[a-zA-Z][a-zA-Z0-9+.-]*://') < 0) str = 'http://' + str;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013256
13257 this.openUrl(str);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013258};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013259
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013260/**
13261 * Add the terminalRow and terminalColumn properties to mouse events and
13262 * then forward on to onMouse().
13263 *
13264 * The terminalRow and terminalColumn properties contain the (row, column)
13265 * coordinates for the mouse event.
13266 *
13267 * @param {Event} e The mouse event to handle.
13268 */
13269hterm.Terminal.prototype.onMouse_ = function(e) {
13270 if (e.processedByTerminalHandler_) {
13271 // We register our event handlers on the document, as well as the cursor
13272 // and the scroll blocker. Mouse events that occur on the cursor or
13273 // scroll blocker will also appear on the document, but we don't want to
13274 // process them twice.
13275 //
13276 // We can't just prevent bubbling because that has other side effects, so
13277 // we decorate the event object with this property instead.
13278 return;
13279 }
13280
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013281 var reportMouseEvents =
13282 (!this.defeatMouseReports_ &&
13283 this.vt.mouseReport != this.vt.MOUSE_REPORT_DISABLED);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013284
13285 e.processedByTerminalHandler_ = true;
13286
13287 // One based row/column stored on the mouse event.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013288 e.terminalRow = parseInt(
13289 (e.clientY - this.scrollPort_.visibleRowTopMargin) /
13290 this.scrollPort_.characterSize.height) +
13291 1;
13292 e.terminalColumn =
13293 parseInt(e.clientX / this.scrollPort_.characterSize.width) + 1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013294
13295 if (e.type == 'mousedown' && e.terminalColumn > this.screenSize.width) {
13296 // Mousedown in the scrollbar area.
13297 return;
13298 }
13299
13300 if (this.options_.cursorVisible && !reportMouseEvents) {
13301 // If the cursor is visible and we're not sending mouse events to the
13302 // host app, then we want to hide the terminal cursor when the mouse
13303 // cursor is over top. This keeps the terminal cursor from interfering
13304 // with local text selection.
13305 if (e.terminalRow - 1 == this.screen_.cursorPosition.row &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013306 e.terminalColumn - 1 == this.screen_.cursorPosition.column) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013307 this.cursorNode_.style.display = 'none';
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013308 } else if (this.cursorNode_.style.display == 'none') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013309 this.cursorNode_.style.display = '';
13310 }
13311 }
13312
13313 if (e.type == 'mousedown') {
13314 if (e.altKey || !reportMouseEvents) {
13315 // If VT mouse reporting is disabled, or has been defeated with
13316 // alt-mousedown, then the mouse will act on the local selection.
13317 this.defeatMouseReports_ = true;
13318 this.setSelectionEnabled(true);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013319 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013320 // Otherwise we defer ownership of the mouse to the VT.
13321 this.defeatMouseReports_ = false;
13322 this.document_.getSelection().collapseToEnd();
13323 this.setSelectionEnabled(false);
13324 e.preventDefault();
13325 }
13326 }
13327
13328 if (!reportMouseEvents) {
13329 if (e.type == 'dblclick' && this.copyOnSelect) {
13330 this.screen_.expandSelection(this.document_.getSelection());
13331 this.copySelectionToClipboard(this.document_);
13332 }
13333
13334 if (e.type == 'click' && !e.shiftKey && e.ctrlKey) {
13335 // Debounce this event with the dblclick event. If you try to doubleclick
13336 // a URL to open it, Chrome will fire click then dblclick, but we won't
13337 // have expanded the selection text at the first click event.
13338 clearTimeout(this.timeouts_.openUrl);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013339 this.timeouts_.openUrl =
13340 setTimeout(this.openSelectedUrl_.bind(this), 500);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013341 return;
13342 }
13343
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013344 if (e.type == 'mousedown' && e.which == this.mousePasteButton) this.paste();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013345
13346 if (e.type == 'mouseup' && e.which == 1 && this.copyOnSelect &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013347 !this.document_.getSelection().isCollapsed) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013348 this.copySelectionToClipboard(this.document_);
13349 }
13350
13351 if ((e.type == 'mousemove' || e.type == 'mouseup') &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013352 this.scrollBlockerNode_.engaged) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013353 // Disengage the scroll-blocker after one of these events.
13354 this.scrollBlockerNode_.engaged = false;
13355 this.scrollBlockerNode_.style.top = '-99px';
13356 }
13357
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013358 } else /* if (this.reportMouseEvents) */ {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013359 if (!this.scrollBlockerNode_.engaged) {
13360 if (e.type == 'mousedown') {
13361 // Move the scroll-blocker into place if we want to keep the scrollport
13362 // from scrolling.
13363 this.scrollBlockerNode_.engaged = true;
13364 this.scrollBlockerNode_.style.top = (e.clientY - 5) + 'px';
13365 this.scrollBlockerNode_.style.left = (e.clientX - 5) + 'px';
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013366 } else if (e.type == 'mousemove') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013367 // Oh. This means that drag-scroll was disabled AFTER the mouse down,
13368 // in which case it's too late to engage the scroll-blocker.
13369 this.document_.getSelection().collapseToEnd();
13370 e.preventDefault();
13371 }
13372 }
13373
13374 this.onMouse(e);
13375 }
13376
13377 if (e.type == 'mouseup' && this.document_.getSelection().isCollapsed) {
13378 // Restore this on mouseup in case it was temporarily defeated with a
13379 // alt-mousedown. Only do this when the selection is empty so that
13380 // we don't immediately kill the users selection.
13381 this.defeatMouseReports_ = false;
13382 }
13383};
13384
13385/**
13386 * Clients should override this if they care to know about mouse events.
13387 *
13388 * The event parameter will be a normal DOM mouse click event with additional
13389 * 'terminalRow' and 'terminalColumn' properties.
13390 *
13391 * @param {Event} e The mouse event to handle.
13392 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013393hterm.Terminal.prototype.onMouse = function(e) {};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013394
13395/**
13396 * React when focus changes.
13397 *
13398 * @param {boolean} focused True if focused, false otherwise.
13399 */
13400hterm.Terminal.prototype.onFocusChange_ = function(focused) {
13401 this.cursorNode_.setAttribute('focus', focused);
13402 this.restyleCursor_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013403 if (focused === true) this.closeBellNotifications_();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013404};
13405
13406/**
13407 * React when the ScrollPort is scrolled.
13408 */
13409hterm.Terminal.prototype.onScroll_ = function() {
13410 this.scheduleSyncCursorPosition_();
13411};
13412
13413/**
13414 * React when text is pasted into the scrollPort.
13415 *
13416 * @param {Event} e The DOM paste event to handle.
13417 */
13418hterm.Terminal.prototype.onPaste_ = function(e) {
13419 var data = e.text.replace(/\n/mg, '\r');
13420 data = this.keyboard.encode(data);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013421 if (this.options_.bracketedPaste) data = '\x1b[200~' + data + '\x1b[201~';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013422
13423 this.io.sendString(data);
13424};
13425
13426/**
13427 * React when the user tries to copy from the scrollPort.
13428 *
13429 * @param {Event} e The DOM copy event.
13430 */
13431hterm.Terminal.prototype.onCopy_ = function(e) {
13432 if (!this.useDefaultWindowCopy) {
13433 e.preventDefault();
13434 setTimeout(this.copySelectionToClipboard.bind(this), 0);
13435 }
13436};
13437
13438/**
13439 * React when the ScrollPort is resized.
13440 *
13441 * Note: This function should not directly contain code that alters the internal
13442 * state of the terminal. That kind of code belongs in realizeWidth or
13443 * realizeHeight, so that it can be executed synchronously in the case of a
13444 * programmatic width change.
13445 */
13446hterm.Terminal.prototype.onResize_ = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013447 var columnCount = Math.floor(
13448 this.scrollPort_.getScreenWidth() /
13449 this.scrollPort_.characterSize.width) ||
13450 0;
13451 var rowCount = lib.f.smartFloorDivide(
13452 this.scrollPort_.getScreenHeight(),
13453 this.scrollPort_.characterSize.height) ||
13454 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013455
13456 if (columnCount <= 0 || rowCount <= 0) {
13457 // We avoid these situations since they happen sometimes when the terminal
13458 // gets removed from the document or during the initial load, and we can't
13459 // deal with that.
13460 // This can also happen if called before the scrollPort calculates the
13461 // character size, meaning we dived by 0 above and default to 0 values.
13462 return;
13463 }
13464
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013465 var isNewSize =
13466 (columnCount != this.screenSize.width ||
13467 rowCount != this.screenSize.height);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013468
13469 // We do this even if the size didn't change, just to be sure everything is
13470 // in sync.
13471 this.realizeSize_(columnCount, rowCount);
13472 this.showZoomWarning_(this.scrollPort_.characterSize.zoomFactor != 1);
13473
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013474 if (isNewSize) this.overlaySize();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013475
13476 this.restyleCursor_();
13477 this.scheduleSyncCursorPosition_();
13478};
13479
13480/**
13481 * Service the cursor blink timeout.
13482 */
13483hterm.Terminal.prototype.onCursorBlink_ = function() {
13484 if (!this.options_.cursorBlink) {
13485 delete this.timeouts_.cursorBlink;
13486 return;
13487 }
13488
13489 if (this.cursorNode_.getAttribute('focus') == 'false' ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013490 this.cursorNode_.style.opacity == '0') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013491 this.cursorNode_.style.opacity = '1';
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013492 this.timeouts_.cursorBlink =
13493 setTimeout(this.myOnCursorBlink_, this.cursorBlinkCycle_[0]);
13494 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013495 this.cursorNode_.style.opacity = '0';
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013496 this.timeouts_.cursorBlink =
13497 setTimeout(this.myOnCursorBlink_, this.cursorBlinkCycle_[1]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013498 }
13499};
13500
13501/**
13502 * Set the scrollbar-visible mode bit.
13503 *
13504 * If scrollbar-visible is on, the vertical scrollbar will be visible.
13505 * Otherwise it will not.
13506 *
13507 * Defaults to on.
13508 *
13509 * @param {boolean} state True to set scrollbar-visible mode, false to unset.
13510 */
13511hterm.Terminal.prototype.setScrollbarVisible = function(state) {
13512 this.scrollPort_.setScrollbarVisible(state);
13513};
13514
13515/**
13516 * Set the scroll wheel move multiplier. This will affect how fast the page
13517 * scrolls on mousewheel events.
13518 *
13519 * Defaults to 1.
13520 *
13521 * @param {number} multiplier The multiplier to set.
13522 */
13523hterm.Terminal.prototype.setScrollWheelMoveMultipler = function(multiplier) {
13524 this.scrollPort_.setScrollWheelMoveMultipler(multiplier);
13525};
13526
13527/**
13528 * Close all web notifications created by terminal bells.
13529 */
13530hterm.Terminal.prototype.closeBellNotifications_ = function() {
13531 this.bellNotificationList_.forEach(function(n) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013532 n.close();
13533 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013534 this.bellNotificationList_.length = 0;
13535};
13536// SOURCE FILE: hterm/js/hterm_terminal_io.js
13537// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
13538// Use of this source code is governed by a BSD-style license that can be
13539// found in the LICENSE file.
13540
13541'use strict';
13542
13543lib.rtdep('lib.encodeUTF8');
13544
13545/**
13546 * Input/Output interface used by commands to communicate with the terminal.
13547 *
13548 * Commands like `nassh` and `crosh` receive an instance of this class as
13549 * part of their argv object. This allows them to write to and read from the
13550 * terminal without exposing them to an entire hterm.Terminal instance.
13551 *
13552 * The active command must override the onVTKeystroke() and sendString() methods
13553 * of this class in order to receive keystrokes and send output to the correct
13554 * destination.
13555 *
13556 * Isolating commands from the terminal provides the following benefits:
13557 * - Provides a mechanism to save and restore onVTKeystroke and sendString
13558 * handlers when invoking subcommands (see the push() and pop() methods).
13559 * - The isolation makes it easier to make changes in Terminal and supporting
13560 * classes without affecting commands.
13561 * - In The Future commands may run in web workers where they would only be able
13562 * to talk to a Terminal instance through an IPC mechanism.
13563 *
13564 * @param {hterm.Terminal}
13565 */
13566hterm.Terminal.IO = function(terminal) {
13567 this.terminal_ = terminal;
13568
13569 // The IO object to restore on IO.pop().
13570 this.previousIO_ = null;
13571};
13572
13573/**
13574 * Show the terminal overlay for a given amount of time.
13575 *
13576 * The terminal overlay appears in inverse video in a large font, centered
13577 * over the terminal. You should probably keep the overlay message brief,
13578 * since it's in a large font and you probably aren't going to check the size
13579 * of the terminal first.
13580 *
13581 * @param {string} msg The text (not HTML) message to display in the overlay.
13582 * @param {number} opt_timeout The amount of time to wait before fading out
13583 * the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
13584 * stay up forever (or until the next overlay).
13585 */
13586hterm.Terminal.IO.prototype.showOverlay = function(message, opt_timeout) {
13587 this.terminal_.showOverlay(message, opt_timeout);
13588};
13589
13590/**
13591 * Open an frame in the current terminal window, pointed to the specified
13592 * url.
13593 *
13594 * Eventually we'll probably need size/position/decoration options.
13595 * The user should also be able to move/resize the frame.
13596 *
13597 * @param {string} url The URL to load in the frame.
13598 * @param {Object} opt_options Optional frame options. Not implemented.
13599 */
13600hterm.Terminal.IO.prototype.createFrame = function(url, opt_options) {
13601 return new hterm.Frame(this.terminal_, url, opt_options);
13602};
13603
13604/**
13605 * Change the preference profile for the terminal.
13606 *
13607 * @param profileName {string} The name of the preference profile to activate.
13608 */
13609hterm.Terminal.IO.prototype.setTerminalProfile = function(profileName) {
13610 this.terminal_.setProfile(profileName);
13611};
13612
13613/**
13614 * Create a new hterm.Terminal.IO instance and make it active on the Terminal
13615 * object associated with this instance.
13616 *
13617 * This is used to pass control of the terminal IO off to a subcommand. The
13618 * IO.pop() method can be used to restore control when the subcommand completes.
13619 */
13620hterm.Terminal.IO.prototype.push = function() {
13621 var io = new hterm.Terminal.IO(this.terminal_);
13622 io.keyboardCaptured_ = this.keyboardCaptured_;
13623
13624 io.columnCount = this.columnCount;
13625 io.rowCount = this.rowCount;
13626
13627 io.previousIO_ = this.terminal_.io;
13628 this.terminal_.io = io;
13629
13630 return io;
13631};
13632
13633/**
13634 * Restore the Terminal's previous IO object.
13635 */
13636hterm.Terminal.IO.prototype.pop = function() {
13637 this.terminal_.io = this.previousIO_;
13638};
13639
13640/**
13641 * Called when data needs to be sent to the current command.
13642 *
13643 * Clients should override this to receive notification of pending data.
13644 *
13645 * @param {string} string The data to send.
13646 */
13647hterm.Terminal.IO.prototype.sendString = function(string) {
13648 // Override this.
13649 console.log('Unhandled sendString: ' + string);
13650};
13651
13652/**
13653 * Called when a terminal keystroke is detected.
13654 *
13655 * Clients should override this to receive notification of keystrokes.
13656 *
13657 * The keystroke data will be encoded according to the 'send-encoding'
13658 * preference.
13659 *
13660 * @param {string} string The VT key sequence.
13661 */
13662hterm.Terminal.IO.prototype.onVTKeystroke = function(string) {
13663 // Override this.
13664 console.log('Unobserverd VT keystroke: ' + JSON.stringify(string));
13665};
13666
13667hterm.Terminal.IO.prototype.onTerminalResize_ = function(width, height) {
13668 var obj = this;
13669 while (obj) {
13670 obj.columnCount = width;
13671 obj.rowCount = height;
13672 obj = obj.previousIO_;
13673 }
13674
13675 this.onTerminalResize(width, height);
13676};
13677
13678/**
13679 * Called when terminal size is changed.
13680 *
13681 * Clients should override this to receive notification of resize.
13682 *
13683 * @param {string|integer} terminal width.
13684 * @param {string|integer} terminal height.
13685 */
13686hterm.Terminal.IO.prototype.onTerminalResize = function(width, height) {
13687 // Override this.
13688};
13689
13690/**
13691 * Write a UTF-8 encoded byte string to the terminal.
13692 *
13693 * @param {string} string The UTF-8 encoded string to print.
13694 */
13695hterm.Terminal.IO.prototype.writeUTF8 = function(string) {
13696 if (this.terminal_.io != this)
13697 throw 'Attempt to print from inactive IO object.';
13698
13699 this.terminal_.interpret(string);
13700};
13701
13702/**
13703 * Write a UTF-8 encoded byte string to the terminal followed by crlf.
13704 *
13705 * @param {string} string The UTF-8 encoded string to print.
13706 */
13707hterm.Terminal.IO.prototype.writelnUTF8 = function(string) {
13708 if (this.terminal_.io != this)
13709 throw 'Attempt to print from inactive IO object.';
13710
13711 this.terminal_.interpret(string + '\r\n');
13712};
13713
13714/**
13715 * Write a UTF-16 JavaScript string to the terminal.
13716 *
13717 * @param {string} string The string to print.
13718 */
13719hterm.Terminal.IO.prototype.print =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013720 hterm.Terminal.IO.prototype.writeUTF16 = function(string) {
13721 this.writeUTF8(lib.encodeUTF8(string));
13722 };
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013723
13724/**
13725 * Print a UTF-16 JavaScript string to the terminal followed by a newline.
13726 *
13727 * @param {string} string The string to print.
13728 */
13729hterm.Terminal.IO.prototype.println =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013730 hterm.Terminal.IO.prototype.writelnUTF16 = function(string) {
13731 this.writelnUTF8(lib.encodeUTF8(string));
13732 };
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013733// SOURCE FILE: hterm/js/hterm_text_attributes.js
13734// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
13735// Use of this source code is governed by a BSD-style license that can be
13736// found in the LICENSE file.
13737
13738'use strict';
13739
13740lib.rtdep('lib.colors');
13741
13742/**
13743 * Constructor for TextAttribute objects.
13744 *
13745 * These objects manage a set of text attributes such as foreground/
13746 * background color, bold, faint, italic, blink, underline, and strikethrough.
13747 *
13748 * TextAttribute instances can be used to construct a DOM container implementing
13749 * the current attributes, or to test an existing DOM container for
13750 * compatibility with the current attributes.
13751 *
13752 * @constructor
13753 * @param {HTMLDocument} document The parent document to use when creating
13754 * new DOM containers.
13755 */
13756hterm.TextAttributes = function(document) {
13757 this.document_ = document;
13758 // These variables contain the source of the color as either:
13759 // SRC_DEFAULT (use context default)
13760 // SRC_RGB (specified in 'rgb( r, g, b)' form)
13761 // number (representing the index from color palette to use)
13762 this.foregroundSource = this.SRC_DEFAULT;
13763 this.backgroundSource = this.SRC_DEFAULT;
13764
13765 // These properties cache the value in the color table, but foregroundSource
13766 // and backgroundSource contain the canonical values.
13767 this.foreground = this.DEFAULT_COLOR;
13768 this.background = this.DEFAULT_COLOR;
13769
13770 this.defaultForeground = 'rgb(255, 255, 255)';
13771 this.defaultBackground = 'rgb(0, 0, 0)';
13772
13773 this.bold = false;
13774 this.faint = false;
13775 this.italic = false;
13776 this.blink = false;
13777 this.underline = false;
13778 this.strikethrough = false;
13779 this.inverse = false;
13780 this.invisible = false;
13781 this.wcNode = false;
13782 this.tileData = null;
13783
13784 this.colorPalette = null;
13785 this.resetColorPalette();
13786};
13787
13788/**
13789 * If false, we ignore the bold attribute.
13790 *
13791 * This is used for fonts that have a bold version that is a different size
13792 * than the normal weight version.
13793 */
13794hterm.TextAttributes.prototype.enableBold = true;
13795
13796/**
13797 * If true, use bright colors (if available) for bold text.
13798 *
13799 * This setting is independent of the enableBold setting.
13800 */
13801hterm.TextAttributes.prototype.enableBoldAsBright = true;
13802
13803/**
13804 * A sentinel constant meaning "whatever the default color is in this context".
13805 */
13806hterm.TextAttributes.prototype.DEFAULT_COLOR = new String('');
13807
13808/**
13809 * A constant string used to specify that source color is context default.
13810 */
13811hterm.TextAttributes.prototype.SRC_DEFAULT = 'default';
13812
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013813/**
13814 * A constant string used to specify that the source of a color is a valid
13815 * rgb( r, g, b) specifier.
13816 */
13817hterm.TextAttributes.prototype.SRC_RGB = 'rgb';
13818
13819/**
13820 * The document object which should own the DOM nodes created by this instance.
13821 *
13822 * @param {HTMLDocument} document The parent document.
13823 */
13824hterm.TextAttributes.prototype.setDocument = function(document) {
13825 this.document_ = document;
13826};
13827
13828/**
13829 * Create a deep copy of this object.
13830 *
13831 * @return {hterm.TextAttributes} A deep copy of this object.
13832 */
13833hterm.TextAttributes.prototype.clone = function() {
13834 var rv = new hterm.TextAttributes(null);
13835
13836 for (var key in this) {
13837 rv[key] = this[key];
13838 }
13839
13840 rv.colorPalette = this.colorPalette.concat();
13841 return rv;
13842};
13843
13844/**
13845 * Reset the current set of attributes.
13846 *
13847 * This does not affect the palette. Use resetColorPalette() for that.
13848 * It also doesn't affect the tile data, it's not meant to.
13849 */
13850hterm.TextAttributes.prototype.reset = function() {
13851 this.foregroundSource = this.SRC_DEFAULT;
13852 this.backgroundSource = this.SRC_DEFAULT;
13853 this.foreground = this.DEFAULT_COLOR;
13854 this.background = this.DEFAULT_COLOR;
13855 this.bold = false;
13856 this.faint = false;
13857 this.italic = false;
13858 this.blink = false;
13859 this.underline = false;
13860 this.strikethrough = false;
13861 this.inverse = false;
13862 this.invisible = false;
13863 this.wcNode = false;
13864};
13865
13866/**
13867 * Reset the color palette to the default state.
13868 */
13869hterm.TextAttributes.prototype.resetColorPalette = function() {
13870 this.colorPalette = lib.colors.colorPalette.concat();
13871 this.syncColors();
13872};
13873
13874/**
13875 * Test if the current attributes describe unstyled text.
13876 *
13877 * @return {boolean} True if the current attributes describe unstyled text.
13878 */
13879hterm.TextAttributes.prototype.isDefault = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013880 return (
13881 this.foregroundSource == this.SRC_DEFAULT &&
13882 this.backgroundSource == this.SRC_DEFAULT && !this.bold && !this.faint &&
13883 !this.italic && !this.blink && !this.underline && !this.strikethrough &&
13884 !this.inverse && !this.invisible && !this.wcNode &&
13885 this.tileData == null);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013886};
13887
13888/**
13889 * Create a DOM container (a span or a text node) with a style to match the
13890 * current set of attributes.
13891 *
13892 * This method will create a plain text node if the text is unstyled, or
13893 * an HTML span if the text is styled. Due to lack of monospace wide character
13894 * fonts on certain systems (e.g. Chrome OS), we need to put each wide character
13895 * in a span of CSS class '.wc-node' which has double column width.
13896 * Each vt_tiledata tile is also represented by a span with a single
13897 * character, with CSS classes '.tile' and '.tile_<glyph number>'.
13898 *
13899 * @param {string} opt_textContent Optional text content for the new container.
13900 * @return {HTMLNode} An HTML span or text nodes styled to match the current
13901 * attributes.
13902 */
13903hterm.TextAttributes.prototype.createContainer = function(opt_textContent) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013904 if (this.isDefault()) return this.document_.createTextNode(opt_textContent);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013905
13906 var span = this.document_.createElement('span');
13907 var style = span.style;
13908 var classes = [];
13909
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013910 if (this.foreground != this.DEFAULT_COLOR) style.color = this.foreground;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013911
13912 if (this.background != this.DEFAULT_COLOR)
13913 style.backgroundColor = this.background;
13914
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013915 if (this.enableBold && this.bold) style.fontWeight = 'bold';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013916
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013917 if (this.faint) span.faint = true;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013918
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013919 if (this.italic) style.fontStyle = 'italic';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013920
13921 if (this.blink) {
13922 classes.push('blink-node');
13923 span.blinkNode = true;
13924 }
13925
13926 var textDecoration = '';
13927 if (this.underline) {
13928 textDecoration += ' underline';
13929 span.underline = true;
13930 }
13931 if (this.strikethrough) {
13932 textDecoration += ' line-through';
13933 span.strikethrough = true;
13934 }
13935 if (textDecoration) {
13936 style.textDecoration = textDecoration;
13937 }
13938
13939 if (this.wcNode) {
13940 classes.push('wc-node');
13941 span.wcNode = true;
13942 }
13943
13944 if (this.tileData != null) {
13945 classes.push('tile');
13946 classes.push('tile_' + this.tileData);
13947 span.tileNode = true;
13948 }
13949
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013950 if (opt_textContent) span.textContent = opt_textContent;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013951
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013952 if (classes.length) span.className = classes.join(' ');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013953
13954 return span;
13955};
13956
13957/**
13958 * Tests if the provided object (string, span or text node) has the same
13959 * style as this TextAttributes instance.
13960 *
13961 * This indicates that text with these attributes could be inserted directly
13962 * into the target DOM node.
13963 *
13964 * For the purposes of this method, a string is considered a text node.
13965 *
13966 * @param {string|HTMLNode} obj The object to test.
13967 * @return {boolean} True if the provided container has the same style as
13968 * this attributes instance.
13969 */
13970hterm.TextAttributes.prototype.matchesContainer = function(obj) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013971 if (typeof obj == 'string' || obj.nodeType == 3) return this.isDefault();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013972
13973 var style = obj.style;
13974
13975 // We don't want to put multiple characters in a wcNode or a tile.
13976 // See the comments in createContainer.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013977 return (
13978 !(this.wcNode || obj.wcNode) &&
13979 !(this.tileData != null || obj.tileNode) &&
13980 this.foreground == style.color &&
13981 this.background == style.backgroundColor &&
13982 (this.enableBold && this.bold) == !!style.fontWeight &&
13983 this.blink == obj.blinkNode && this.italic == !!style.fontStyle &&
13984 !!this.underline == !!obj.underline &&
13985 !!this.strikethrough == !!obj.strikethrough);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013986};
13987
13988hterm.TextAttributes.prototype.setDefaults = function(foreground, background) {
13989 this.defaultForeground = foreground;
13990 this.defaultBackground = background;
13991
13992 this.syncColors();
13993};
13994
13995/**
13996 * Updates foreground and background properties based on current indices and
13997 * other state.
13998 *
13999 * @param {string} terminalForeground The terminal foreground color for use as
14000 * inverse text background.
14001 * @param {string} terminalBackground The terminal background color for use as
14002 * inverse text foreground.
14003 *
14004 */
14005hterm.TextAttributes.prototype.syncColors = function() {
14006 function getBrightIndex(i) {
14007 if (i < 8) {
14008 // If the color is from the lower half of the ANSI 16, add 8.
14009 return i + 8;
14010 }
14011
14012 // If it's not from the 16 color palette, ignore bold requests. This
14013 // matches the behavior of gnome-terminal.
14014 return i;
14015 }
14016
14017 var foregroundSource = this.foregroundSource;
14018 var backgroundSource = this.backgroundSource;
14019 var defaultForeground = this.DEFAULT_COLOR;
14020 var defaultBackground = this.DEFAULT_COLOR;
14021
14022 if (this.inverse) {
14023 foregroundSource = this.backgroundSource;
14024 backgroundSource = this.foregroundSource;
14025 // We can't inherit the container's color anymore.
14026 defaultForeground = this.defaultBackground;
14027 defaultBackground = this.defaultForeground;
14028 }
14029
14030 if (this.enableBoldAsBright && this.bold) {
14031 if (foregroundSource != this.SRC_DEFAULT &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014032 foregroundSource != this.SRC_RGB) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014033 foregroundSource = getBrightIndex(foregroundSource);
14034 }
14035 }
14036
14037 if (this.invisible) {
14038 foregroundSource = backgroundSource;
14039 defaultForeground = this.defaultBackground;
14040 }
14041
14042 // Set fore/background colors unless already specified in rgb(r, g, b) form.
14043 if (foregroundSource != this.SRC_RGB) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014044 this.foreground =
14045 ((foregroundSource == this.SRC_DEFAULT) ?
14046 defaultForeground :
14047 this.colorPalette[foregroundSource]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014048 }
14049
14050 if (this.faint && !this.invisible) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014051 var colorToMakeFaint =
14052 ((this.foreground == this.DEFAULT_COLOR) ? this.defaultForeground :
14053 this.foreground);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014054 this.foreground = lib.colors.mix(colorToMakeFaint, 'rgb(0, 0, 0)', 0.3333);
14055 }
14056
14057 if (backgroundSource != this.SRC_RGB) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014058 this.background =
14059 ((backgroundSource == this.SRC_DEFAULT) ?
14060 defaultBackground :
14061 this.colorPalette[backgroundSource]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014062 }
14063};
14064
14065/**
14066 * Static method used to test if the provided objects (strings, spans or
14067 * text nodes) have the same style.
14068 *
14069 * For the purposes of this method, a string is considered a text node.
14070 *
14071 * @param {string|HTMLNode} obj1 An object to test.
14072 * @param {string|HTMLNode} obj2 Another object to test.
14073 * @return {boolean} True if the containers have the same style.
14074 */
14075hterm.TextAttributes.containersMatch = function(obj1, obj2) {
14076 if (typeof obj1 == 'string')
14077 return hterm.TextAttributes.containerIsDefault(obj2);
14078
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014079 if (obj1.nodeType != obj2.nodeType) return false;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014080
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014081 if (obj1.nodeType == 3) return true;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014082
14083 var style1 = obj1.style;
14084 var style2 = obj2.style;
14085
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014086 return (
14087 style1.color == style2.color &&
14088 style1.backgroundColor == style2.backgroundColor &&
14089 style1.fontWeight == style2.fontWeight &&
14090 style1.fontStyle == style2.fontStyle &&
14091 style1.textDecoration == style2.textDecoration);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014092};
14093
14094/**
14095 * Static method to test if a given DOM container represents unstyled text.
14096 *
14097 * For the purposes of this method, a string is considered a text node.
14098 *
14099 * @param {string|HTMLNode} obj1 An object to test.
14100 * @return {boolean} True if the object is unstyled.
14101 */
14102hterm.TextAttributes.containerIsDefault = function(obj) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070014103 return typeof obj == 'string' || obj.nodeType == 3;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014104};
14105
14106/**
14107 * Static method to get the column width of a node's textContent.
14108 *
14109 * @param {HTMLElement} node The HTML element to get the width of textContent
14110 * from.
14111 * @return {integer} The column width of the node's textContent.
14112 */
14113hterm.TextAttributes.nodeWidth = function(node) {
14114 if (node.wcNode) {
14115 return lib.wc.strWidth(node.textContent);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014116 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014117 return node.textContent.length;
14118 }
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070014119};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014120
14121/**
14122 * Static method to get the substr of a node's textContent. The start index
14123 * and substr width are computed in column width.
14124 *
14125 * @param {HTMLElement} node The HTML element to get the substr of textContent
14126 * from.
14127 * @param {integer} start The starting offset in column width.
14128 * @param {integer} width The width to capture in column width.
14129 * @return {integer} The extracted substr of the node's textContent.
14130 */
14131hterm.TextAttributes.nodeSubstr = function(node, start, width) {
14132 if (node.wcNode) {
14133 return lib.wc.substr(node.textContent, start, width);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014134 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014135 return node.textContent.substr(start, width);
14136 }
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070014137};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014138
14139/**
14140 * Static method to get the substring based of a node's textContent. The
14141 * start index of end index are computed in column width.
14142 *
14143 * @param {HTMLElement} node The HTML element to get the substr of textContent
14144 * from.
14145 * @param {integer} start The starting offset in column width.
14146 * @param {integer} end The ending offset in column width.
14147 * @return {integer} The extracted substring of the node's textContent.
14148 */
14149hterm.TextAttributes.nodeSubstring = function(node, start, end) {
14150 if (node.wcNode) {
14151 return lib.wc.substring(node.textContent, start, end);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014152 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014153 return node.textContent.substring(start, end);
14154 }
14155};
14156
14157/**
14158 * Static method to split a string into contiguous runs of single-width
14159 * characters and runs of double-width characters.
14160 *
14161 * @param {string} str The string to split.
14162 * @return {Array} An array of objects that contain substrings of str, where
14163 * each substring is either a contiguous runs of single-width characters
14164 * or a double-width character. For object that contains a double-width
14165 * character, its wcNode property is set to true.
14166 */
14167hterm.TextAttributes.splitWidecharString = function(str) {
14168 var rv = [];
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014169 var base = 0, length = 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014170
14171 for (var i = 0; i < str.length;) {
14172 var c = str.codePointAt(i);
14173 var increment = (c <= 0xffff) ? 1 : 2;
14174 if (c < 128 || lib.wc.charWidth(c) == 1) {
14175 length += increment;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014176 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014177 if (length) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014178 rv.push({str: str.substr(base, length)});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014179 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014180 rv.push({str: str.substr(i, increment), wcNode: true});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014181 base = i + increment;
14182 length = 0;
14183 }
14184 i += increment;
14185 }
14186
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014187 if (length) rv.push({str: str.substr(base, length)});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014188
14189 return rv;
14190};
14191// SOURCE FILE: hterm/js/hterm_vt.js
14192// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
14193// Use of this source code is governed by a BSD-style license that can be
14194// found in the LICENSE file.
14195
14196'use strict';
14197
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014198lib.rtdep('lib.colors', 'lib.f', 'lib.UTF8Decoder', 'hterm.VT.CharacterMap');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014199
14200/**
14201 * Constructor for the VT escape sequence interpreter.
14202 *
14203 * The interpreter operates on a terminal object capable of performing cursor
14204 * move operations, painting characters, etc.
14205 *
14206 * This interpreter is intended to be compatible with xterm, though it
14207 * ignores some of the more esoteric escape sequences.
14208 *
14209 * Some sequences are marked "Will not implement", meaning that they aren't
14210 * considered relevant to hterm and will probably never be implemented.
14211 *
14212 * Others are marked "Not currently implemented", meaning that they are lower
14213 * priority items that may be useful to implement at some point.
14214 *
14215 * See also:
14216 * [VT100] VT100 User Guide
14217 * http://vt100.net/docs/vt100-ug/chapter3.html
14218 * [VT510] VT510 Video Terminal Programmer Information
14219 * http://vt100.net/docs/vt510-rm/contents
14220 * [XTERM] Xterm Control Sequences
14221 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
14222 * [CTRL] Wikipedia: C0 and C1 Control Codes
14223 * https://en.wikipedia.org/wiki/C0_and_C1_control_codes
14224 * [CSI] Wikipedia: ANSI Escape Code
14225 * https://en.wikipedia.org/wiki/Control_Sequence_Introducer
14226 * man 5 terminfo, man infocmp, infocmp -L xterm-new
14227 *
14228 * @param {hterm.Terminal} terminal Terminal to use with the interpreter.
14229 */
14230hterm.VT = function(terminal) {
14231 /**
14232 * The display terminal object associated with this virtual terminal.
14233 */
14234 this.terminal = terminal;
14235
14236 terminal.onMouse = this.onTerminalMouse_.bind(this);
14237 this.mouseReport = this.MOUSE_REPORT_DISABLED;
14238
14239 // Parse state left over from the last parse. You should use the parseState
14240 // instance passed into your parse routine, rather than reading
14241 // this.parseState_ directly.
14242 this.parseState_ = new hterm.VT.ParseState(this.parseUnknown_);
14243
14244 // Any "leading modifiers" for the escape sequence, such as '?', ' ', or the
14245 // other modifiers handled in this.parseCSI_.
14246 this.leadingModifier_ = '';
14247
14248 // Any "trailing modifiers". Same character set as a leading modifier,
14249 // except these are found after the numeric arguments.
14250 this.trailingModifier_ = '';
14251
14252 // Whether or not to respect the escape codes for setting terminal width.
14253 this.allowColumnWidthChanges_ = false;
14254
14255 // The amount of time we're willing to wait for the end of an OSC sequence.
14256 this.oscTimeLimit_ = 20000;
14257
14258 // Construct a regular expression to match the known one-byte control chars.
14259 // This is used in parseUnknown_ to quickly scan a string for the next
14260 // control character.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014261 var cc1 = Object.keys(hterm.VT.CC1)
14262 .map(function(e) {
14263 return '\\x' + lib.f.zpad(e.charCodeAt().toString(16), 2);
14264 })
14265 .join('');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014266 this.cc1Pattern_ = new RegExp('[' + cc1 + ']');
14267
14268 // Decoder to maintain UTF-8 decode state.
14269 this.utf8Decoder_ = new lib.UTF8Decoder();
14270
14271 /**
14272 * Whether to accept the 8-bit control characters.
14273 *
14274 * An 8-bit control character is one with the eighth bit set. These
14275 * didn't work on 7-bit terminals so they all have two byte equivalents.
14276 * Most hosts still only use the two-byte versions.
14277 *
14278 * We ignore 8-bit control codes by default. This is in order to avoid
14279 * issues with "accidental" usage of codes that need to be terminated.
14280 * The "accident" usually involves cat'ing binary data.
14281 */
14282 this.enable8BitControl = false;
14283
14284 /**
14285 * Whether to allow the OSC 52 sequence to write to the system clipboard.
14286 */
14287 this.enableClipboardWrite = true;
14288
14289 /**
14290 * Respect the host's attempt to change the cursor blink status using
14291 * the DEC Private mode 12.
14292 */
14293 this.enableDec12 = false;
14294
14295 /**
14296 * The expected encoding method for data received from the host.
14297 */
14298 this.characterEncoding = 'utf-8';
14299
14300 /**
14301 * Max length of an unterminated DCS, OSC, PM or APC sequence before we give
14302 * up and ignore the code.
14303 *
14304 * These all end with a String Terminator (ST, '\x9c', ESC '\\') or
14305 * (BEL, '\x07') character, hence the "string sequence" moniker.
14306 */
14307 this.maxStringSequence = 1024;
14308
14309 /**
14310 * If true, emit warnings when we encounter a control character or escape
14311 * sequence that we don't recognize or explicitly ignore.
14312 */
14313 this.warnUnimplemented = true;
14314
14315 /**
14316 * The default G0...G3 character maps.
14317 */
14318 this.G0 = hterm.VT.CharacterMap.maps['B'];
14319 this.G1 = hterm.VT.CharacterMap.maps['0'];
14320 this.G2 = hterm.VT.CharacterMap.maps['B'];
14321 this.G3 = hterm.VT.CharacterMap.maps['B'];
14322
14323 /**
14324 * The 7-bit visible character set.
14325 *
14326 * This is a mapping from inbound data to display glyph. The GL set
14327 * contains the 94 bytes from 0x21 to 0x7e.
14328 *
14329 * The default GL set is 'B', US ASCII.
14330 */
14331 this.GL = 'G0';
14332
14333 /**
14334 * The 8-bit visible character set.
14335 *
14336 * This is a mapping from inbound data to display glyph. The GR set
14337 * contains the 94 bytes from 0xa1 to 0xfe.
14338 */
14339 this.GR = 'G0';
14340
14341 // Saved state used in DECSC.
14342 //
14343 // This is a place to store a copy VT state, it is *not* the active state.
14344 this.savedState_ = new hterm.VT.CursorState(this);
14345};
14346
14347/**
14348 * No mouse events.
14349 */
14350hterm.VT.prototype.MOUSE_REPORT_DISABLED = 0;
14351
14352/**
14353 * DECSET mode 1000.
14354 *
14355 * Report mouse down/up events only.
14356 */
14357hterm.VT.prototype.MOUSE_REPORT_CLICK = 1;
14358
14359/**
14360 * DECSET mode 1002.
14361 *
14362 * Report mouse down/up and movement while a button is down.
14363 */
14364hterm.VT.prototype.MOUSE_REPORT_DRAG = 3;
14365
14366/**
14367 * ParseState constructor.
14368 *
14369 * This object tracks the current state of the parse. It has fields for the
14370 * current buffer, position in the buffer, and the parse function.
14371 *
14372 * @param {function} defaultFunc The default parser function.
14373 * @param {string} opt_buf Optional string to use as the current buffer.
14374 */
14375hterm.VT.ParseState = function(defaultFunction, opt_buf) {
14376 this.defaultFunction = defaultFunction;
14377 this.buf = opt_buf || null;
14378 this.pos = 0;
14379 this.func = defaultFunction;
14380 this.args = [];
14381};
14382
14383/**
14384 * Reset the parser function, buffer, and position.
14385 */
14386hterm.VT.ParseState.prototype.reset = function(opt_buf) {
14387 this.resetParseFunction();
14388 this.resetBuf(opt_buf || '');
14389 this.resetArguments();
14390};
14391
14392/**
14393 * Reset the parser function only.
14394 */
14395hterm.VT.ParseState.prototype.resetParseFunction = function() {
14396 this.func = this.defaultFunction;
14397};
14398
14399/**
14400 * Reset the buffer and position only.
14401 *
14402 * @param {string} buf Optional new value for buf, defaults to null.
14403 */
14404hterm.VT.ParseState.prototype.resetBuf = function(opt_buf) {
14405 this.buf = (typeof opt_buf == 'string') ? opt_buf : null;
14406 this.pos = 0;
14407};
14408
14409/**
14410 * Reset the arguments list only.
14411 *
14412 * @param {string} opt_arg_zero Optional initial value for args[0].
14413 */
14414hterm.VT.ParseState.prototype.resetArguments = function(opt_arg_zero) {
14415 this.args.length = 0;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014416 if (typeof opt_arg_zero != 'undefined') this.args[0] = opt_arg_zero;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014417};
14418
14419/**
14420 * Get an argument as an integer.
14421 *
14422 * @param {number} argnum The argument number to retrieve.
14423 */
14424hterm.VT.ParseState.prototype.iarg = function(argnum, defaultValue) {
14425 var str = this.args[argnum];
14426 if (str) {
14427 var ret = parseInt(str, 10);
14428 // An argument of zero is treated as the default value.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014429 if (ret == 0) ret = defaultValue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014430 return ret;
14431 }
14432 return defaultValue;
14433};
14434
14435/**
14436 * Advance the parse position.
14437 *
14438 * @param {integer} count The number of bytes to advance.
14439 */
14440hterm.VT.ParseState.prototype.advance = function(count) {
14441 this.pos += count;
14442};
14443
14444/**
14445 * Return the remaining portion of the buffer without affecting the parse
14446 * position.
14447 *
14448 * @return {string} The remaining portion of the buffer.
14449 */
14450hterm.VT.ParseState.prototype.peekRemainingBuf = function() {
14451 return this.buf.substr(this.pos);
14452};
14453
14454/**
14455 * Return the next single character in the buffer without affecting the parse
14456 * position.
14457 *
14458 * @return {string} The next character in the buffer.
14459 */
14460hterm.VT.ParseState.prototype.peekChar = function() {
14461 return this.buf.substr(this.pos, 1);
14462};
14463
14464/**
14465 * Return the next single character in the buffer and advance the parse
14466 * position one byte.
14467 *
14468 * @return {string} The next character in the buffer.
14469 */
14470hterm.VT.ParseState.prototype.consumeChar = function() {
14471 return this.buf.substr(this.pos++, 1);
14472};
14473
14474/**
14475 * Return true if the buffer is empty, or the position is past the end.
14476 */
14477hterm.VT.ParseState.prototype.isComplete = function() {
14478 return this.buf == null || this.buf.length <= this.pos;
14479};
14480
14481hterm.VT.CursorState = function(vt) {
14482 this.vt_ = vt;
14483 this.save();
14484};
14485
14486hterm.VT.CursorState.prototype.save = function() {
14487 this.cursor = this.vt_.terminal.saveCursor();
14488
14489 this.textAttributes = this.vt_.terminal.getTextAttributes().clone();
14490
14491 this.GL = this.vt_.GL;
14492 this.GR = this.vt_.GR;
14493
14494 this.G0 = this.vt_.G0;
14495 this.G1 = this.vt_.G1;
14496 this.G2 = this.vt_.G2;
14497 this.G3 = this.vt_.G3;
14498};
14499
14500hterm.VT.CursorState.prototype.restore = function() {
14501 this.vt_.terminal.restoreCursor(this.cursor);
14502
14503 this.vt_.terminal.setTextAttributes(this.textAttributes.clone());
14504
14505 this.vt_.GL = this.GL;
14506 this.vt_.GR = this.GR;
14507
14508 this.vt_.G0 = this.G0;
14509 this.vt_.G1 = this.G1;
14510 this.vt_.G2 = this.G2;
14511 this.vt_.G3 = this.G3;
14512};
14513
14514hterm.VT.prototype.reset = function() {
14515 this.G0 = hterm.VT.CharacterMap.maps['B'];
14516 this.G1 = hterm.VT.CharacterMap.maps['0'];
14517 this.G2 = hterm.VT.CharacterMap.maps['B'];
14518 this.G3 = hterm.VT.CharacterMap.maps['B'];
14519
14520 this.GL = 'G0';
14521 this.GR = 'G0';
14522
14523 this.savedState_ = new hterm.VT.CursorState(this);
14524
14525 this.mouseReport = this.MOUSE_REPORT_DISABLED;
14526};
14527
14528/**
14529 * Handle terminal mouse events.
14530 *
14531 * See the "Mouse Tracking" section of [xterm].
14532 */
14533hterm.VT.prototype.onTerminalMouse_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014534 if (this.mouseReport == this.MOUSE_REPORT_DISABLED) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014535
14536 // Temporary storage for our response.
14537 var response;
14538
14539 // Modifier key state.
14540 var mod = 0;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014541 if (e.shiftKey) mod |= 4;
14542 if (e.metaKey || (this.terminal.keyboard.altIsMeta && e.altKey)) mod |= 8;
14543 if (e.ctrlKey) mod |= 16;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014544
14545 // TODO(rginda): We should also support mode 1005 and/or 1006 to extend the
14546 // coordinate space. Though, after poking around just a little, I wasn't
14547 // able to get vi or emacs to use either of these modes.
14548 var x = String.fromCharCode(lib.f.clamp(e.terminalColumn + 32, 32, 255));
14549 var y = String.fromCharCode(lib.f.clamp(e.terminalRow + 32, 32, 255));
14550
14551 switch (e.type) {
14552 case 'mousewheel':
14553 // Mouse wheel is treated as button 1 or 2 plus an additional 64.
14554 b = ((e.wheelDeltaY > 0) ? 0 : 1) + 96;
14555 b |= mod;
14556 response = '\x1b[M' + String.fromCharCode(b) + x + y;
14557
14558 // Keep the terminal from scrolling.
14559 e.preventDefault();
14560 break;
14561
14562 case 'mousedown':
14563 // Buttons are encoded as button number plus 32.
14564 var b = Math.min(e.which - 1, 2) + 32;
14565
14566 // And mix in the modifier keys.
14567 b |= mod;
14568
14569 response = '\x1b[M' + String.fromCharCode(b) + x + y;
14570 break;
14571
14572 case 'mouseup':
14573 // Mouse up has no indication of which button was released.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070014574 response = '\x1b[M#' + x + y;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014575 break;
14576
14577 case 'mousemove':
14578 if (this.mouseReport == this.MOUSE_REPORT_DRAG && e.which) {
14579 // Standard button bits.
14580 b = 32 + Math.min(e.which - 1, 2);
14581
14582 // Add 32 to indicate mouse motion.
14583 b += 32;
14584
14585 // And mix in the modifier keys.
14586 b |= mod;
14587
14588 response = '\x1b[M' + String.fromCharCode(b) + x + y;
14589 }
14590
14591 break;
14592
14593 case 'click':
14594 case 'dblclick':
14595 break;
14596
14597 default:
14598 console.error('Unknown mouse event: ' + e.type, e);
14599 break;
14600 }
14601
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014602 if (response) this.terminal.io.sendString(response);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014603};
14604
14605/**
14606 * Interpret a string of characters, displaying the results on the associated
14607 * terminal object.
14608 *
14609 * The buffer will be decoded according to the 'receive-encoding' preference.
14610 */
14611hterm.VT.prototype.interpret = function(buf) {
14612 this.parseState_.resetBuf(this.decode(buf));
14613
14614 while (!this.parseState_.isComplete()) {
14615 var func = this.parseState_.func;
14616 var pos = this.parseState_.pos;
14617 var buf = this.parseState_.buf;
14618
14619 this.parseState_.func.call(this, this.parseState_);
14620
14621 if (this.parseState_.func == func && this.parseState_.pos == pos &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014622 this.parseState_.buf == buf) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014623 throw 'Parser did not alter the state!';
14624 }
14625 }
14626};
14627
14628/**
14629 * Decode a string according to the 'receive-encoding' preference.
14630 */
14631hterm.VT.prototype.decode = function(str) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014632 if (this.characterEncoding == 'utf-8') return this.decodeUTF8(str);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014633
14634 return str;
14635};
14636
14637/**
14638 * Encode a UTF-16 string as UTF-8.
14639 *
14640 * See also: https://en.wikipedia.org/wiki/UTF-16
14641 */
14642hterm.VT.prototype.encodeUTF8 = function(str) {
14643 return lib.encodeUTF8(str);
14644};
14645
14646/**
14647 * Decode a UTF-8 string into UTF-16.
14648 */
14649hterm.VT.prototype.decodeUTF8 = function(str) {
14650 return this.utf8Decoder_.decode(str);
14651};
14652
14653/**
14654 * The default parse function.
14655 *
14656 * This will scan the string for the first 1-byte control character (C0/C1
14657 * characters from [CTRL]). Any plain text coming before the code will be
14658 * printed to the terminal, then the control character will be dispatched.
14659 */
14660hterm.VT.prototype.parseUnknown_ = function(parseState) {
14661 var self = this;
14662
14663 function print(str) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014664 if (self[self.GL].GL) str = self[self.GL].GL(str);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014665
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014666 if (self[self.GR].GR) str = self[self.GR].GR(str);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014667
14668 self.terminal.print(str);
14669 };
14670
14671 // Search for the next contiguous block of plain text.
14672 var buf = parseState.peekRemainingBuf();
14673 var nextControl = buf.search(this.cc1Pattern_);
14674
14675 if (nextControl == 0) {
14676 // We've stumbled right into a control character.
14677 this.dispatch('CC1', buf.substr(0, 1), parseState);
14678 parseState.advance(1);
14679 return;
14680 }
14681
14682 if (nextControl == -1) {
14683 // There are no control characters in this string.
14684 print(buf);
14685 parseState.reset();
14686 return;
14687 }
14688
14689 print(buf.substr(0, nextControl));
14690 this.dispatch('CC1', buf.substr(nextControl, 1), parseState);
14691 parseState.advance(nextControl + 1);
14692};
14693
14694/**
14695 * Parse a Control Sequence Introducer code and dispatch it.
14696 *
14697 * See [CSI] for some useful information about these codes.
14698 */
14699hterm.VT.prototype.parseCSI_ = function(parseState) {
14700 var ch = parseState.peekChar();
14701 var args = parseState.args;
14702
14703 if (ch >= '@' && ch <= '~') {
14704 // This is the final character.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014705 this.dispatch(
14706 'CSI', this.leadingModifier_ + this.trailingModifier_ + ch, parseState);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014707 parseState.resetParseFunction();
14708
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014709 } else if (ch == ';') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014710 // Parameter delimiter.
14711 if (this.trailingModifier_) {
14712 // Parameter delimiter after the trailing modifier. That's a paddlin'.
14713 parseState.resetParseFunction();
14714
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014715 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014716 if (!args.length) {
14717 // They omitted the first param, we need to supply it.
14718 args.push('');
14719 }
14720
14721 args.push('');
14722 }
14723
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014724 } else if (ch >= '0' && ch <= '9') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014725 // Next byte in the current parameter.
14726
14727 if (this.trailingModifier_) {
14728 // Numeric parameter after the trailing modifier. That's a paddlin'.
14729 parseState.resetParseFunction();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014730 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014731 if (!args.length) {
14732 args[0] = ch;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014733 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014734 args[args.length - 1] += ch;
14735 }
14736 }
14737
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014738 } else if (ch >= ' ' && ch <= '?' && ch != ':') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014739 // Modifier character.
14740 if (!args.length) {
14741 this.leadingModifier_ += ch;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014742 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014743 this.trailingModifier_ += ch;
14744 }
14745
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014746 } else if (this.cc1Pattern_.test(ch)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014747 // Control character.
14748 this.dispatch('CC1', ch, parseState);
14749
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014750 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014751 // Unexpected character in sequence, bail out.
14752 parseState.resetParseFunction();
14753 }
14754
14755 parseState.advance(1);
14756};
14757
14758/**
14759 * Skip over the string until the next String Terminator (ST, 'ESC \') or
14760 * Bell (BEL, '\x07').
14761 *
14762 * The string is accumulated in parseState.args[0]. Make sure to reset the
14763 * arguments (with parseState.resetArguments) before starting the parse.
14764 *
14765 * You can detect that parsing in complete by checking that the parse
14766 * function has changed back to the default parse function.
14767 *
14768 * If we encounter more than maxStringSequence characters, we send back
14769 * the unterminated sequence to be re-parsed with the default parser function.
14770 *
14771 * @return {boolean} If true, parsing is ongoing or complete. If false, we've
14772 * exceeded the max string sequence.
14773 */
14774hterm.VT.prototype.parseUntilStringTerminator_ = function(parseState) {
14775 var buf = parseState.peekRemainingBuf();
14776 var nextTerminator = buf.search(/(\x1b\\|\x07)/);
14777 var args = parseState.args;
14778
14779 if (!args.length) {
14780 args[0] = '';
14781 args[1] = new Date();
14782 }
14783
14784 if (nextTerminator == -1) {
14785 // No terminator here, have to wait for the next string.
14786
14787 args[0] += buf;
14788
14789 var abortReason;
14790
14791 if (args[0].length > this.maxStringSequence)
14792 abortReason = 'too long: ' + args[0].length;
14793
14794 if (args[0].indexOf('\x1b') != -1)
14795 abortReason = 'embedded escape: ' + args[0].indexOf('\x1b');
14796
14797 if (new Date() - args[1] > this.oscTimeLimit_)
14798 abortReason = 'timeout expired: ' + new Date() - args[1];
14799
14800 if (abortReason) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014801 console.log(
14802 'parseUntilStringTerminator_: aborting: ' + abortReason, args[0]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014803 parseState.reset(args[0]);
14804 return false;
14805 }
14806
14807 parseState.advance(buf.length);
14808 return true;
14809 }
14810
14811 if (args[0].length + nextTerminator > this.maxStringSequence) {
14812 // We found the end of the sequence, but we still think it's too long.
14813 parseState.reset(args[0] + buf);
14814 return false;
14815 }
14816
14817 args[0] += buf.substr(0, nextTerminator);
14818
14819 parseState.resetParseFunction();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014820 parseState.advance(
14821 nextTerminator + (buf.substr(nextTerminator, 1) == '\x1b' ? 2 : 1));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014822
14823 return true;
14824};
14825
14826/**
14827 * Dispatch to the function that handles a given CC1, ESC, or CSI or VT52 code.
14828 */
14829hterm.VT.prototype.dispatch = function(type, code, parseState) {
14830 var handler = hterm.VT[type][code];
14831 if (!handler) {
14832 if (this.warnUnimplemented)
14833 console.warn('Unknown ' + type + ' code: ' + JSON.stringify(code));
14834 return;
14835 }
14836
14837 if (handler == hterm.VT.ignore) {
14838 if (this.warnUnimplemented)
14839 console.warn('Ignored ' + type + ' code: ' + JSON.stringify(code));
14840 return;
14841 }
14842
14843 if (type == 'CC1' && code > '\x7f' && !this.enable8BitControl) {
14844 // It's kind of a hack to put this here, but...
14845 //
14846 // If we're dispatching a 'CC1' code, and it's got the eighth bit set,
14847 // but we're not supposed to handle 8-bit codes? Just ignore it.
14848 //
14849 // This prevents an errant (DCS, '\x90'), (OSC, '\x9d'), (PM, '\x9e') or
14850 // (APC, '\x9f') from locking up the terminal waiting for its expected
14851 // (ST, '\x9c') or (BEL, '\x07').
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014852 console.warn(
14853 'Ignoring 8-bit control code: 0x' + code.charCodeAt(0).toString(16));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014854 return;
14855 }
14856
14857 handler.apply(this, [parseState, code]);
14858};
14859
14860/**
14861 * Set one of the ANSI defined terminal mode bits.
14862 *
14863 * Invoked in response to SM/RM.
14864 *
14865 * Expected values for code:
14866 * 2 - Keyboard Action Mode (AM). Will not implement.
14867 * 4 - Insert Mode (IRM).
14868 * 12 - Send/receive (SRM). Will not implement.
14869 * 20 - Automatic Newline (LNM).
14870 *
14871 * Unexpected and unimplemented values are silently ignored.
14872 */
14873hterm.VT.prototype.setANSIMode = function(code, state) {
14874 if (code == '4') {
14875 this.terminal.setInsertMode(state);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014876 } else if (code == '20') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014877 this.terminal.setAutoCarriageReturn(state);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014878 } else if (this.warnUnimplemented) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014879 console.warn('Unimplemented ANSI Mode: ' + code);
14880 }
14881};
14882
14883/**
14884 * Set or reset one of the DEC Private modes.
14885 *
14886 * Invoked in response to DECSET/DECRST.
14887 *
14888 * Expected values for code:
14889 * 1 - Application Cursor Keys (DECCKM).
14890 * 2 - [!] Designate USASCII for character sets G0-G3 (DECANM), and set
14891 * VT100 mode.
14892 * 3 - 132 Column Mode (DECCOLM).
14893 * 4 - [x] Smooth (Slow) Scroll (DECSCLM).
14894 * 5 - Reverse Video (DECSCNM).
14895 * 6 - Origin Mode (DECOM).
14896 * 7 - Wraparound Mode (DECAWM).
14897 * 8 - [x] Auto-repeat Keys (DECARM).
14898 * 9 - [!] Send Mouse X & Y on button press.
14899 * 10 - [x] Show toolbar (rxvt).
14900 * 12 - Start Blinking Cursor (att610).
14901 * 18 - [!] Print form feed (DECPFF).
14902 * 19 - [x] Set print extent to full screen (DECPEX).
14903 * 25 - Show Cursor (DECTCEM).
14904 * 30 - [!] Show scrollbar (rxvt).
14905 * 35 - [x] Enable font-shifting functions (rxvt).
14906 * 38 - [x] Enter Tektronix Mode (DECTEK).
14907 * 40 - Allow 80 - 132 Mode.
14908 * 41 - [!] more(1) fix (see curses resource).
14909 * 42 - [!] Enable Nation Replacement Character sets (DECNRCM).
14910 * 44 - [!] Turn On Margin Bell.
14911 * 45 - Reverse-wraparound Mode.
14912 * 46 - [x] Start Logging.
14913 * 47 - [!] Use Alternate Screen Buffer.
14914 * 66 - [!] Application keypad (DECNKM).
14915 * 67 - Backarrow key sends backspace (DECBKM).
14916 * 1000 - Send Mouse X & Y on button press and release. (MOUSE_REPORT_CLICK)
14917 * 1001 - [!] Use Hilite Mouse Tracking.
14918 * 1002 - Use Cell Motion Mouse Tracking. (MOUSE_REPORT_DRAG)
14919 * 1003 - [!] Use All Motion Mouse Tracking.
14920 * 1004 - [!] Send FocusIn/FocusOut events.
14921 * 1005 - [!] Enable Extended Mouse Mode.
14922 * 1010 - Scroll to bottom on tty output (rxvt).
14923 * 1011 - Scroll to bottom on key press (rxvt).
14924 * 1034 - [x] Interpret "meta" key, sets eighth bit.
14925 * 1035 - [x] Enable special modifiers for Alt and NumLock keys.
14926 * 1036 - Send ESC when Meta modifies a key.
14927 * 1037 - [!] Send DEL from the editing-keypad Delete key.
14928 * 1039 - Send ESC when Alt modifies a key.
14929 * 1040 - [x] Keep selection even if not highlighted.
14930 * 1041 - [x] Use the CLIPBOARD selection.
14931 * 1042 - [!] Enable Urgency window manager hint when Control-G is received.
14932 * 1043 - [!] Enable raising of the window when Control-G is received.
14933 * 1047 - [!] Use Alternate Screen Buffer.
14934 * 1048 - Save cursor as in DECSC.
14935 * 1049 - Save cursor as in DECSC and use Alternate Screen Buffer, clearing
14936 * it first. (This may be disabled by the titeInhibit resource). This
14937 * combines the effects of the 1047 and 1048 modes. Use this with
14938 * terminfo-based applications rather than the 47 mode.
14939 * 1050 - [!] Set terminfo/termcap function-key mode.
14940 * 1051 - [x] Set Sun function-key mode.
14941 * 1052 - [x] Set HP function-key mode.
14942 * 1053 - [x] Set SCO function-key mode.
14943 * 1060 - [x] Set legacy keyboard emulation (X11R6).
14944 * 1061 - [!] Set VT220 keyboard emulation.
14945 * 2004 - Set bracketed paste mode.
14946 *
14947 * [!] - Not currently implemented, may be in the future.
14948 * [x] - Will not implement.
14949 */
14950hterm.VT.prototype.setDECMode = function(code, state) {
14951 switch (code) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014952 case '1': // DECCKM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014953 this.terminal.keyboard.applicationCursor = state;
14954 break;
14955
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014956 case '3': // DECCOLM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014957 if (this.allowColumnWidthChanges_) {
14958 this.terminal.setWidth(state ? 132 : 80);
14959
14960 this.terminal.clearHome();
14961 this.terminal.setVTScrollRegion(null, null);
14962 }
14963 break;
14964
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014965 case '5': // DECSCNM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014966 this.terminal.setReverseVideo(state);
14967 break;
14968
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014969 case '6': // DECOM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014970 this.terminal.setOriginMode(state);
14971 break;
14972
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014973 case '7': // DECAWM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014974 this.terminal.setWraparound(state);
14975 break;
14976
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014977 case '12': // att610
14978 if (this.enableDec12) this.terminal.setCursorBlink(state);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014979 break;
14980
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014981 case '25': // DECTCEM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014982 this.terminal.setCursorVisible(state);
14983 break;
14984
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014985 case '40': // no-spec
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014986 this.terminal.allowColumnWidthChanges_ = state;
14987 break;
14988
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014989 case '45': // no-spec
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014990 this.terminal.setReverseWraparound(state);
14991 break;
14992
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014993 case '67': // DECBKM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014994 this.terminal.keyboard.backspaceSendsBackspace = state;
14995 break;
14996
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014997 case '1000': // Report on mouse clicks only.
14998 this.mouseReport =
14999 (state ? this.MOUSE_REPORT_CLICK : this.MOUSE_REPORT_DISABLED);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015000 break;
15001
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015002 case '1002': // Report on mouse clicks and drags
15003 this.mouseReport =
15004 (state ? this.MOUSE_REPORT_DRAG : this.MOUSE_REPORT_DISABLED);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015005 break;
15006
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015007 case '1010': // rxvt
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015008 this.terminal.scrollOnOutput = state;
15009 break;
15010
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015011 case '1011': // rxvt
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015012 this.terminal.scrollOnKeystroke = state;
15013 break;
15014
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015015 case '1036': // no-spec
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015016 this.terminal.keyboard.metaSendsEscape = state;
15017 break;
15018
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015019 case '1039': // no-spec
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015020 if (state) {
15021 if (!this.terminal.keyboard.previousAltSendsWhat_) {
15022 this.terminal.keyboard.previousAltSendsWhat_ =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015023 this.terminal.keyboard.altSendsWhat;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015024 this.terminal.keyboard.altSendsWhat = 'escape';
15025 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015026 } else if (this.terminal.keyboard.previousAltSendsWhat_) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015027 this.terminal.keyboard.altSendsWhat =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015028 this.terminal.keyboard.previousAltSendsWhat_;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015029 this.terminal.keyboard.previousAltSendsWhat_ = null;
15030 }
15031 break;
15032
15033 case '47':
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015034 case '1047': // no-spec
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015035 this.terminal.setAlternateMode(state);
15036 break;
15037
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015038 case '1048': // Save cursor as in DECSC.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015039 this.savedState_.save();
15040
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015041 case '1049': // 1047 + 1048 + clear.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015042 if (state) {
15043 this.savedState_.save();
15044 this.terminal.setAlternateMode(state);
15045 this.terminal.clear();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015046 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015047 this.terminal.setAlternateMode(state);
15048 this.savedState_.restore();
15049 }
15050
15051 break;
15052
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015053 case '2004': // Bracketed paste mode.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015054 this.terminal.setBracketedPaste(state);
15055 break;
15056
15057 default:
15058 if (this.warnUnimplemented)
15059 console.warn('Unimplemented DEC Private Mode: ' + code);
15060 break;
15061 }
15062};
15063
15064/**
15065 * Function shared by control characters and escape sequences that are
15066 * ignored.
15067 */
15068hterm.VT.ignore = function() {};
15069
15070/**
15071 * Collection of control characters expressed in a single byte.
15072 *
15073 * This includes the characters from the C0 and C1 sets (see [CTRL]) that we
15074 * care about. Two byte versions of the C1 codes are defined in the
15075 * hterm.VT.ESC collection.
15076 *
15077 * The 'CC1' mnemonic here refers to the fact that these are one-byte Control
15078 * Codes. It's only used in this source file and not defined in any of the
15079 * referenced documents.
15080 */
15081hterm.VT.CC1 = {};
15082
15083/**
15084 * Collection of two-byte and three-byte sequences starting with ESC.
15085 */
15086hterm.VT.ESC = {};
15087
15088/**
15089 * Collection of CSI (Control Sequence Introducer) sequences.
15090 *
15091 * These sequences begin with 'ESC [', and may take zero or more arguments.
15092 */
15093hterm.VT.CSI = {};
15094
15095/**
15096 * Collection of OSC (Operating System Control) sequences.
15097 *
15098 * These sequences begin with 'ESC ]', followed by a function number and a
15099 * string terminated by either ST or BEL.
15100 */
15101hterm.VT.OSC = {};
15102
15103/**
15104 * Collection of VT52 sequences.
15105 *
15106 * When in VT52 mode, other sequences are disabled.
15107 */
15108hterm.VT.VT52 = {};
15109
15110/**
15111 * Null (NUL).
15112 *
15113 * Silently ignored.
15114 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070015115hterm.VT.CC1['\x00'] = function() {};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015116
15117/**
15118 * Enquiry (ENQ).
15119 *
15120 * Transmit answerback message.
15121 *
15122 * The default answerback message in xterm is an empty string, so we just
15123 * ignore this.
15124 */
15125hterm.VT.CC1['\x05'] = hterm.VT.ignore;
15126
15127/**
15128 * Ring Bell (BEL).
15129 */
15130hterm.VT.CC1['\x07'] = function() {
15131 this.terminal.ringBell();
15132};
15133
15134/**
15135 * Backspace (BS).
15136 *
15137 * Move the cursor to the left one character position, unless it is at the
15138 * left margin, in which case no action occurs.
15139 */
15140hterm.VT.CC1['\x08'] = function() {
15141 this.terminal.cursorLeft(1);
15142};
15143
15144/**
15145 * Horizontal Tab (HT).
15146 *
15147 * Move the cursor to the next tab stop, or to the right margin if no further
15148 * tab stops are present on the line.
15149 */
15150hterm.VT.CC1['\x09'] = function() {
15151 this.terminal.forwardTabStop();
15152};
15153
15154/**
15155 * Line Feed (LF).
15156 *
15157 * This code causes a line feed or a new line operation. See Automatic
15158 * Newline (LNM).
15159 */
15160hterm.VT.CC1['\x0a'] = function() {
15161 this.terminal.formFeed();
15162};
15163
15164/**
15165 * Vertical Tab (VT).
15166 *
15167 * Interpreted as LF.
15168 */
15169hterm.VT.CC1['\x0b'] = hterm.VT.CC1['\x0a'];
15170
15171/**
15172 * Form Feed (FF).
15173 *
15174 * Interpreted as LF.
15175 */
15176hterm.VT.CC1['\x0c'] = function() {
15177 this.terminal.formFeed();
15178};
15179
15180/**
15181 * Carriage Return (CR).
15182 *
15183 * Move cursor to the left margin on the current line.
15184 */
15185hterm.VT.CC1['\x0d'] = function() {
15186 this.terminal.setCursorColumn(0);
15187};
15188
15189/**
15190 * Shift Out (SO), aka Lock Shift 0 (LS1).
15191 *
15192 * Invoke G1 character set in GL.
15193 */
15194hterm.VT.CC1['\x0e'] = function() {
15195 this.GL = 'G1';
15196};
15197
15198/**
15199 * Shift In (SI), aka Lock Shift 0 (LS0).
15200 *
15201 * Invoke G0 character set in GL.
15202 */
15203hterm.VT.CC1['\x0f'] = function() {
15204 this.GL = 'G0';
15205};
15206
15207/**
15208 * Transmit On (XON).
15209 *
15210 * Not currently implemented.
15211 *
15212 * TODO(rginda): Implement?
15213 */
15214hterm.VT.CC1['\x11'] = hterm.VT.ignore;
15215
15216/**
15217 * Transmit Off (XOFF).
15218 *
15219 * Not currently implemented.
15220 *
15221 * TODO(rginda): Implement?
15222 */
15223hterm.VT.CC1['\x13'] = hterm.VT.ignore;
15224
15225/**
15226 * Cancel (CAN).
15227 *
15228 * If sent during a control sequence, the sequence is immediately terminated
15229 * and not executed.
15230 *
15231 * It also causes the error character to be displayed.
15232 */
15233hterm.VT.CC1['\x18'] = function(parseState) {
15234 // If we've shifted in the G1 character set, shift it back out to
15235 // the default character set.
15236 if (this.GL == 'G1') {
15237 this.GL = 'G0';
15238 }
15239 parseState.resetParseFunction();
15240 this.terminal.print('?');
15241};
15242
15243/**
15244 * Substitute (SUB).
15245 *
15246 * Interpreted as CAN.
15247 */
15248hterm.VT.CC1['\x1a'] = hterm.VT.CC1['\x18'];
15249
15250/**
15251 * Escape (ESC).
15252 */
15253hterm.VT.CC1['\x1b'] = function(parseState) {
15254 function parseESC(parseState) {
15255 var ch = parseState.consumeChar();
15256
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015257 if (ch == '\x1b') return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015258
15259 this.dispatch('ESC', ch, parseState);
15260
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015261 if (parseState.func == parseESC) parseState.resetParseFunction();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015262 };
15263
15264 parseState.func = parseESC;
15265};
15266
15267/**
15268 * Delete (DEL).
15269 */
15270hterm.VT.CC1['\x7f'] = hterm.VT.ignore;
15271
15272// 8 bit control characters and their two byte equivalents, below...
15273
15274/**
15275 * Index (IND).
15276 *
15277 * Like newline, only keep the X position
15278 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015279hterm.VT.CC1['\x84'] = hterm.VT.ESC['D'] = function() {
15280 this.terminal.lineFeed();
15281};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015282
15283/**
15284 * Next Line (NEL).
15285 *
15286 * Like newline, but doesn't add lines.
15287 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015288hterm.VT.CC1['\x85'] = hterm.VT.ESC['E'] = function() {
15289 this.terminal.setCursorColumn(0);
15290 this.terminal.cursorDown(1);
15291};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015292
15293/**
15294 * Horizontal Tabulation Set (HTS).
15295 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015296hterm.VT.CC1['\x88'] = hterm.VT.ESC['H'] = function() {
15297 this.terminal.setTabStop(this.terminal.getCursorColumn());
15298};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015299
15300/**
15301 * Reverse Index (RI).
15302 *
15303 * Move up one line.
15304 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015305hterm.VT.CC1['\x8d'] = hterm.VT.ESC['M'] = function() {
15306 this.terminal.reverseLineFeed();
15307};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015308
15309/**
15310 * Single Shift 2 (SS2).
15311 *
15312 * Select of G2 Character Set for the next character only.
15313 *
15314 * Not currently implemented.
15315 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015316hterm.VT.CC1['\x8e'] = hterm.VT.ESC['N'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015317
15318/**
15319 * Single Shift 3 (SS3).
15320 *
15321 * Select of G3 Character Set for the next character only.
15322 *
15323 * Not currently implemented.
15324 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015325hterm.VT.CC1['\x8f'] = hterm.VT.ESC['O'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015326
15327/**
15328 * Device Control String (DCS).
15329 *
15330 * Indicate a DCS sequence. See Device-Control functions in [XTERM].
15331 * Not currently implemented.
15332 *
15333 * TODO(rginda): Consider implementing DECRQSS, the rest don't seem applicable.
15334 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015335hterm.VT.CC1['\x90'] = hterm.VT.ESC['P'] = function(parseState) {
15336 parseState.resetArguments();
15337 parseState.func = this.parseUntilStringTerminator_;
15338};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015339
15340/**
15341 * Start of Protected Area (SPA).
15342 *
15343 * Will not implement.
15344 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015345hterm.VT.CC1['\x96'] = hterm.VT.ESC['V'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015346
15347/**
15348 * End of Protected Area (EPA).
15349 *
15350 * Will not implement.
15351 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015352hterm.VT.CC1['\x97'] = hterm.VT.ESC['W'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015353
15354/**
15355 * Start of String (SOS).
15356 *
15357 * Will not implement.
15358 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015359hterm.VT.CC1['\x98'] = hterm.VT.ESC['X'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015360
15361/**
15362 * Single Character Introducer (SCI, also DECID).
15363 *
15364 * Return Terminal ID. Obsolete form of 'ESC [ c' (DA).
15365 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015366hterm.VT.CC1['\x9a'] = hterm.VT.ESC['Z'] = function() {
15367 this.terminal.io.sendString('\x1b[?1;2c');
15368};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015369
15370/**
15371 * Control Sequence Introducer (CSI).
15372 *
15373 * The lead into most escape sequences. See [CSI].
15374 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015375hterm.VT.CC1['\x9b'] = hterm.VT.ESC['['] = function(parseState) {
15376 parseState.resetArguments();
15377 this.leadingModifier_ = '';
15378 this.trailingModifier_ = '';
15379 parseState.func = this.parseCSI_;
15380};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015381
15382/**
15383 * String Terminator (ST).
15384 *
15385 * Used to terminate DCS/OSC/PM/APC commands which may take string arguments.
15386 *
15387 * We don't directly handle it here, as it's only used to terminate other
15388 * sequences. See the 'parseUntilStringTerminator_' method.
15389 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015390hterm.VT.CC1['\x9c'] = hterm.VT.ESC['\\'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015391
15392/**
15393 * Operating System Command (OSC).
15394 *
15395 * Commands relating to the operating system.
15396 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015397hterm.VT.CC1['\x9d'] = hterm.VT.ESC[']'] = function(parseState) {
15398 parseState.resetArguments();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015399
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015400 function parseOSC(parseState) {
15401 if (!this.parseUntilStringTerminator_(parseState)) {
15402 // The string sequence was too long.
15403 return;
15404 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015405
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015406 if (parseState.func == parseOSC) {
15407 // We're not done parsing the string yet.
15408 return;
15409 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015410
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015411 // We're done.
15412 var ary = parseState.args[0].match(/^(\d+);(.*)$/);
15413 if (ary) {
15414 parseState.args[0] = ary[2];
15415 this.dispatch('OSC', ary[1], parseState);
15416 } else {
15417 console.warn('Invalid OSC: ' + JSON.stringify(parseState.args[0]));
15418 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015419 };
15420
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015421 parseState.func = parseOSC;
15422};
15423
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015424/**
15425 * Privacy Message (PM).
15426 *
15427 * Will not implement.
15428 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015429hterm.VT.CC1['\x9e'] = hterm.VT.ESC['^'] = function(parseState) {
15430 parseState.resetArguments();
15431 parseState.func = this.parseUntilStringTerminator_;
15432};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015433
15434/**
15435 * Application Program Control (APC).
15436 *
15437 * Will not implement.
15438 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015439hterm.VT.CC1['\x9f'] = hterm.VT.ESC['_'] = function(parseState) {
15440 parseState.resetArguments();
15441 parseState.func = this.parseUntilStringTerminator_;
15442};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015443
15444/**
15445 * ESC \x20 - Unclear to me where these originated, possibly in xterm.
15446 *
15447 * Not currently implemented:
15448 * ESC \x20 F - Select 7 bit escape codes in responses (S7C1T).
15449 * ESC \x20 G - Select 8 bit escape codes in responses (S8C1T).
15450 * NB: We currently assume S7C1T always.
15451 *
15452 * Will not implement:
15453 * ESC \x20 L - Set ANSI conformance level 1.
15454 * ESC \x20 M - Set ANSI conformance level 2.
15455 * ESC \x20 N - Set ANSI conformance level 3.
15456 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070015457hterm.VT.ESC[' '] = function(parseState) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015458 parseState.func = function(parseState) {
15459 var ch = parseState.consumeChar();
15460 if (this.warnUnimplemented)
15461 console.warn('Unimplemented sequence: ESC 0x20 ' + ch);
15462 parseState.resetParseFunction();
15463 };
15464};
15465
15466/**
15467 * DEC 'ESC #' sequences.
15468 *
15469 * Handled:
15470 * ESC # 8 - DEC Screen Alignment Test (DECALN).
15471 * Fills the terminal with 'E's. Used liberally by vttest.
15472 *
15473 * Ignored:
15474 * ESC # 3 - DEC double-height line, top half (DECDHL).
15475 * ESC # 4 - DEC double-height line, bottom half (DECDHL).
15476 * ESC # 5 - DEC single-width line (DECSWL).
15477 * ESC # 6 - DEC double-width line (DECDWL).
15478 */
15479hterm.VT.ESC['#'] = function(parseState) {
15480 parseState.func = function(parseState) {
15481 var ch = parseState.consumeChar();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015482 if (ch == '8') this.terminal.fill('E');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015483
15484 parseState.resetParseFunction();
15485 };
15486};
15487
15488/**
15489 * 'ESC %' sequences, character set control. Not currently implemented.
15490 *
15491 * To be implemented (currently ignored):
15492 * ESC % @ - Set ISO 8859-1 character set.
15493 * ESC % G - Set UTF-8 character set.
15494 *
15495 * All other ESC # sequences are echoed to the terminal.
15496 *
15497 * TODO(rginda): Implement.
15498 */
15499hterm.VT.ESC['%'] = function(parseState) {
15500 parseState.func = function(parseState) {
15501 var ch = parseState.consumeChar();
15502 if (ch != '@' && ch != 'G' && this.warnUnimplemented)
15503 console.warn('Unknown ESC % argument: ' + JSON.stringify(ch));
15504 parseState.resetParseFunction();
15505 };
15506};
15507
15508/**
15509 * Character Set Selection (SCS).
15510 *
15511 * ESC ( Ps - Set G0 character set (VT100).
15512 * ESC ) Ps - Set G1 character set (VT220).
15513 * ESC * Ps - Set G2 character set (VT220).
15514 * ESC + Ps - Set G3 character set (VT220).
15515 * ESC - Ps - Set G1 character set (VT300).
15516 * ESC . Ps - Set G2 character set (VT300).
15517 * ESC / Ps - Set G3 character set (VT300).
15518 *
15519 * Values for Ps are:
15520 * 0 - DEC Special Character and Line Drawing Set.
15521 * A - United Kingdom (UK).
15522 * B - United States (USASCII).
15523 * 4 - Dutch.
15524 * C or 5 - Finnish.
15525 * R - French.
15526 * Q - French Canadian.
15527 * K - German.
15528 * Y - Italian.
15529 * E or 6 - Norwegian/Danish.
15530 * Z - Spanish.
15531 * H or 7 - Swedish.
15532 * = - Swiss.
15533 *
15534 * All other sequences are echoed to the terminal.
15535 *
15536 * TODO(rginda): Implement.
15537 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015538hterm.VT.ESC['('] = hterm.VT.ESC[')'] = hterm.VT.ESC['*'] = hterm.VT.ESC['+'] =
15539 hterm.VT.ESC['-'] = hterm.VT.ESC['.'] =
15540 hterm.VT.ESC['/'] = function(parseState, code) {
15541 parseState.func = function(parseState) {
15542 var ch = parseState.consumeChar();
15543 if (ch == '\x1b') {
15544 parseState.resetParseFunction();
15545 parseState.func();
15546 return;
15547 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015548
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015549 if (ch in hterm.VT.CharacterMap.maps) {
15550 if (code == '(') {
15551 this.G0 = hterm.VT.CharacterMap.maps[ch];
15552 } else if (code == ')' || code == '-') {
15553 this.G1 = hterm.VT.CharacterMap.maps[ch];
15554 } else if (code == '*' || code == '.') {
15555 this.G2 = hterm.VT.CharacterMap.maps[ch];
15556 } else if (code == '+' || code == '/') {
15557 this.G3 = hterm.VT.CharacterMap.maps[ch];
15558 }
15559 } else if (this.warnUnimplemented) {
15560 console.log('Invalid character set for "' + code + '": ' + ch);
15561 }
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070015562
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015563 parseState.resetParseFunction();
15564 };
15565 };
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015566
15567/**
15568 * Back Index (DECBI).
15569 *
15570 * VT420 and up. Not currently implemented.
15571 */
15572hterm.VT.ESC['6'] = hterm.VT.ignore;
15573
15574/**
15575 * Save Cursor (DECSC).
15576 */
15577hterm.VT.ESC['7'] = function() {
15578 this.savedState_.save();
15579};
15580
15581/**
15582 * Restore Cursor (DECSC).
15583 */
15584hterm.VT.ESC['8'] = function() {
15585 this.savedState_.restore();
15586};
15587
15588/**
15589 * Forward Index (DECFI).
15590 *
15591 * VT210 and up. Not currently implemented.
15592 */
15593hterm.VT.ESC['9'] = hterm.VT.ignore;
15594
15595/**
15596 * Application keypad (DECPAM).
15597 */
15598hterm.VT.ESC['='] = function() {
15599 this.terminal.keyboard.applicationKeypad = true;
15600};
15601
15602/**
15603 * Normal keypad (DECPNM).
15604 */
15605hterm.VT.ESC['>'] = function() {
15606 this.terminal.keyboard.applicationKeypad = false;
15607};
15608
15609/**
15610 * Cursor to lower left corner of screen.
15611 *
15612 * Will not implement.
15613 *
15614 * This is only recognized by xterm when the hpLowerleftBugCompat resource is
15615 * set.
15616 */
15617hterm.VT.ESC['F'] = hterm.VT.ignore;
15618
15619/**
15620 * Full Reset (RIS).
15621 */
15622hterm.VT.ESC['c'] = function() {
15623 this.reset();
15624 this.terminal.reset();
15625};
15626
15627/**
15628 * Memory lock/unlock.
15629 *
15630 * Will not implement.
15631 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015632hterm.VT.ESC['l'] = hterm.VT.ESC['m'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015633
15634/**
15635 * Lock Shift 2 (LS2)
15636 *
15637 * Invoke the G2 Character Set as GL.
15638 */
15639hterm.VT.ESC['n'] = function() {
15640 this.GL = 'G2';
15641};
15642
15643/**
15644 * Lock Shift 3 (LS3)
15645 *
15646 * Invoke the G3 Character Set as GL.
15647 */
15648hterm.VT.ESC['o'] = function() {
15649 this.GL = 'G3';
15650};
15651
15652/**
15653 * Lock Shift 2, Right (LS3R)
15654 *
15655 * Invoke the G3 Character Set as GR.
15656 */
15657hterm.VT.ESC['|'] = function() {
15658 this.GR = 'G3';
15659};
15660
15661/**
15662 * Lock Shift 2, Right (LS2R)
15663 *
15664 * Invoke the G2 Character Set as GR.
15665 */
15666hterm.VT.ESC['}'] = function() {
15667 this.GR = 'G2';
15668};
15669
15670/**
15671 * Lock Shift 1, Right (LS1R)
15672 *
15673 * Invoke the G1 Character Set as GR.
15674 */
15675hterm.VT.ESC['~'] = function() {
15676 this.GR = 'G1';
15677};
15678
15679/**
15680 * Change icon name and window title.
15681 *
15682 * We only change the window title.
15683 */
15684hterm.VT.OSC['0'] = function(parseState) {
15685 this.terminal.setWindowTitle(parseState.args[0]);
15686};
15687
15688/**
15689 * Change window title.
15690 */
15691hterm.VT.OSC['2'] = hterm.VT.OSC['0'];
15692
15693/**
15694 * Set/read color palette.
15695 */
15696hterm.VT.OSC['4'] = function(parseState) {
15697 // Args come in as a single 'index1;rgb1 ... ;indexN;rgbN' string.
15698 // We split on the semicolon and iterate through the pairs.
15699 var args = parseState.args[0].split(';');
15700
15701 var pairCount = parseInt(args.length / 2);
15702 var colorPalette = this.terminal.getTextAttributes().colorPalette;
15703 var responseArray = [];
15704
15705 for (var pairNumber = 0; pairNumber < pairCount; ++pairNumber) {
15706 var colorIndex = parseInt(args[pairNumber * 2]);
15707 var colorValue = args[pairNumber * 2 + 1];
15708
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015709 if (colorIndex >= colorPalette.length) continue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015710
15711 if (colorValue == '?') {
15712 // '?' means we should report back the current color value.
15713 colorValue = lib.colors.rgbToX11(colorPalette[colorIndex]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015714 if (colorValue) responseArray.push(colorIndex + ';' + colorValue);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015715
15716 continue;
15717 }
15718
15719 colorValue = lib.colors.x11ToCSS(colorValue);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015720 if (colorValue) colorPalette[colorIndex] = colorValue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015721 }
15722
15723 if (responseArray.length)
15724 this.terminal.io.sendString('\x1b]4;' + responseArray.join(';') + '\x07');
15725};
15726
15727/**
15728 * Set the cursor shape.
15729 *
15730 * Parameter is expected to be in the form "CursorShape=number", where number is
15731 * one of:
15732 *
15733 * 0 - Block
15734 * 1 - I-Beam
15735 * 2 - Underline
15736 *
15737 * This is a bit of a de-facto standard supported by iTerm 2 and Konsole. See
15738 * also: DECSCUSR.
15739 *
15740 * Invalid numbers will restore the cursor to the block shape.
15741 */
15742hterm.VT.OSC['50'] = function(parseState) {
15743 var args = parseState.args[0].match(/CursorShape=(.)/i);
15744 if (!args) {
15745 console.warn('Could not parse OSC 50 args: ' + parseState.args[0]);
15746 return;
15747 }
15748
15749 switch (args[1]) {
15750 case '1':
15751 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
15752 break;
15753
15754 case '2':
15755 this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
15756 break;
15757
15758 default:
15759 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
15760 }
15761};
15762
15763/**
15764 * Set/read system clipboard.
15765 *
15766 * Read is not implemented due to security considerations. A remote app
15767 * that is able to both write and read to the clipboard could essentially
15768 * take over your session.
15769 *
15770 * The clipboard data will be decoded according to the 'receive-encoding'
15771 * preference.
15772 */
15773hterm.VT.OSC['52'] = function(parseState) {
15774 // Args come in as a single 'clipboard;b64-data' string. The clipboard
15775 // parameter is used to select which of the X clipboards to address. Since
15776 // we're not integrating with X, we treat them all the same.
15777 var args = parseState.args[0].match(/^[cps01234567]*;(.*)/);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015778 if (!args) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015779
15780 var data = window.atob(args[1]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015781 if (data) this.terminal.copyStringToClipboard(this.decode(data));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015782};
15783
15784/**
15785 * Insert (blank) characters (ICH).
15786 */
15787hterm.VT.CSI['@'] = function(parseState) {
15788 this.terminal.insertSpace(parseState.iarg(0, 1));
15789};
15790
15791/**
15792 * Cursor Up (CUU).
15793 */
15794hterm.VT.CSI['A'] = function(parseState) {
15795 this.terminal.cursorUp(parseState.iarg(0, 1));
15796};
15797
15798/**
15799 * Cursor Down (CUD).
15800 */
15801hterm.VT.CSI['B'] = function(parseState) {
15802 this.terminal.cursorDown(parseState.iarg(0, 1));
15803};
15804
15805/**
15806 * Cursor Forward (CUF).
15807 */
15808hterm.VT.CSI['C'] = function(parseState) {
15809 this.terminal.cursorRight(parseState.iarg(0, 1));
15810};
15811
15812/**
15813 * Cursor Backward (CUB).
15814 */
15815hterm.VT.CSI['D'] = function(parseState) {
15816 this.terminal.cursorLeft(parseState.iarg(0, 1));
15817};
15818
15819/**
15820 * Cursor Next Line (CNL).
15821 *
15822 * This is like Cursor Down, except the cursor moves to the beginning of the
15823 * line as well.
15824 */
15825hterm.VT.CSI['E'] = function(parseState) {
15826 this.terminal.cursorDown(parseState.iarg(0, 1));
15827 this.terminal.setCursorColumn(0);
15828};
15829
15830/**
15831 * Cursor Preceding Line (CPL).
15832 *
15833 * This is like Cursor Up, except the cursor moves to the beginning of the
15834 * line as well.
15835 */
15836hterm.VT.CSI['F'] = function(parseState) {
15837 this.terminal.cursorUp(parseState.iarg(0, 1));
15838 this.terminal.setCursorColumn(0);
15839};
15840
15841/**
15842 * Cursor Character Absolute (CHA).
15843 */
15844hterm.VT.CSI['G'] = function(parseState) {
15845 this.terminal.setCursorColumn(parseState.iarg(0, 1) - 1);
15846};
15847
15848/**
15849 * Cursor Position (CUP).
15850 */
15851hterm.VT.CSI['H'] = function(parseState) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015852 this.terminal.setCursorPosition(
15853 parseState.iarg(0, 1) - 1, parseState.iarg(1, 1) - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015854};
15855
15856/**
15857 * Cursor Forward Tabulation (CHT).
15858 */
15859hterm.VT.CSI['I'] = function(parseState) {
15860 var count = parseState.iarg(0, 1);
15861 count = lib.f.clamp(count, 1, this.terminal.screenSize.width);
15862 for (var i = 0; i < count; i++) {
15863 this.terminal.forwardTabStop();
15864 }
15865};
15866
15867/**
15868 * Erase in Display (ED, DECSED).
15869 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015870hterm.VT.CSI['J'] = hterm.VT.CSI['?J'] = function(parseState, code) {
15871 var arg = parseState.args[0];
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015872
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015873 if (!arg || arg == '0') {
15874 this.terminal.eraseBelow();
15875 } else if (arg == '1') {
15876 this.terminal.eraseAbove();
15877 } else if (arg == '2') {
15878 this.terminal.clear();
15879 } else if (arg == '3') {
15880 // The xterm docs say this means "Erase saved lines", but we'll just clear
15881 // the display since killing the scrollback seems rude.
15882 this.terminal.clear();
15883 }
15884};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015885
15886/**
15887 * Erase in line (EL, DECSEL).
15888 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015889hterm.VT.CSI['K'] = hterm.VT.CSI['?K'] = function(parseState, code) {
15890 var arg = parseState.args[0];
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015891
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015892 if (!arg || arg == '0') {
15893 this.terminal.eraseToRight();
15894 } else if (arg == '1') {
15895 this.terminal.eraseToLeft();
15896 } else if (arg == '2') {
15897 this.terminal.eraseLine();
15898 }
15899};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015900
15901/**
15902 * Insert Lines (IL).
15903 */
15904hterm.VT.CSI['L'] = function(parseState) {
15905 this.terminal.insertLines(parseState.iarg(0, 1));
15906};
15907
15908/**
15909 * Delete Lines (DL).
15910 */
15911hterm.VT.CSI['M'] = function(parseState) {
15912 this.terminal.deleteLines(parseState.iarg(0, 1));
15913};
15914
15915/**
15916 * Delete Characters (DCH).
15917 *
15918 * This command shifts the line contents left, starting at the cursor position.
15919 */
15920hterm.VT.CSI['P'] = function(parseState) {
15921 this.terminal.deleteChars(parseState.iarg(0, 1));
15922};
15923
15924/**
15925 * Scroll Up (SU).
15926 */
15927hterm.VT.CSI['S'] = function(parseState) {
15928 this.terminal.vtScrollUp(parseState.iarg(0, 1));
15929};
15930
15931/**
15932 * Scroll Down (SD).
15933 * Also 'Initiate highlight mouse tracking'. Will not implement this part.
15934 */
15935hterm.VT.CSI['T'] = function(parseState) {
15936 if (parseState.args.length <= 1)
15937 this.terminal.vtScrollDown(parseState.iarg(0, 1));
15938};
15939
15940/**
15941 * Reset one or more features of the title modes to the default value.
15942 *
15943 * ESC [ > Ps T
15944 *
15945 * Normally, "reset" disables the feature. It is possible to disable the
15946 * ability to reset features by compiling a different default for the title
15947 * modes into xterm.
15948 *
15949 * Ps values:
15950 * 0 - Do not set window/icon labels using hexadecimal.
15951 * 1 - Do not query window/icon labels using hexadecimal.
15952 * 2 - Do not set window/icon labels using UTF-8.
15953 * 3 - Do not query window/icon labels using UTF-8.
15954 *
15955 * Will not implement.
15956 */
15957hterm.VT.CSI['>T'] = hterm.VT.ignore;
15958
15959/**
15960 * Erase Characters (ECH).
15961 */
15962hterm.VT.CSI['X'] = function(parseState) {
15963 this.terminal.eraseToRight(parseState.iarg(0, 1));
15964};
15965
15966/**
15967 * Cursor Backward Tabulation (CBT).
15968 */
15969hterm.VT.CSI['Z'] = function(parseState) {
15970 var count = parseState.iarg(0, 1);
15971 count = lib.f.clamp(count, 1, this.terminal.screenSize.width);
15972 for (var i = 0; i < count; i++) {
15973 this.terminal.backwardTabStop();
15974 }
15975};
15976
15977/**
15978 * Character Position Absolute (HPA).
15979 */
15980hterm.VT.CSI['`'] = function(parseState) {
15981 this.terminal.setCursorColumn(parseState.iarg(0, 1) - 1);
15982};
15983
15984/**
15985 * Repeat the preceding graphic character.
15986 *
15987 * Not currently implemented.
15988 */
15989hterm.VT.CSI['b'] = hterm.VT.ignore;
15990
15991/**
15992 * Send Device Attributes (Primary DA).
15993 *
15994 * TODO(rginda): This is hardcoded to send back 'VT100 with Advanced Video
15995 * Option', but it may be more correct to send a VT220 response once
15996 * we fill out the 'Not currently implemented' parts.
15997 */
15998hterm.VT.CSI['c'] = function(parseState) {
15999 if (!parseState.args[0] || parseState.args[0] == '0') {
16000 this.terminal.io.sendString('\x1b[?1;2c');
16001 }
16002};
16003
16004/**
16005 * Send Device Attributes (Secondary DA).
16006 *
16007 * TODO(rginda): This is hardcoded to send back 'VT100' but it may be more
16008 * correct to send a VT220 response once we fill out more 'Not currently
16009 * implemented' parts.
16010 */
16011hterm.VT.CSI['>c'] = function(parseState) {
16012 this.terminal.io.sendString('\x1b[>0;256;0c');
16013};
16014
16015/**
16016 * Line Position Absolute (VPA).
16017 */
16018hterm.VT.CSI['d'] = function(parseState) {
16019 this.terminal.setAbsoluteCursorRow(parseState.iarg(0, 1) - 1);
16020};
16021
16022/**
16023 * Horizontal and Vertical Position (HVP).
16024 *
16025 * Same as Cursor Position (CUP).
16026 */
16027hterm.VT.CSI['f'] = hterm.VT.CSI['H'];
16028
16029/**
16030 * Tab Clear (TBC).
16031 */
16032hterm.VT.CSI['g'] = function(parseState) {
16033 if (!parseState.args[0] || parseState.args[0] == '0') {
16034 // Clear tab stop at cursor.
16035 this.terminal.clearTabStopAtCursor(false);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016036 } else if (parseState.args[0] == '3') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016037 // Clear all tab stops.
16038 this.terminal.clearAllTabStops();
16039 }
16040};
16041
16042/**
16043 * Set Mode (SM).
16044 */
16045hterm.VT.CSI['h'] = function(parseState) {
16046 for (var i = 0; i < parseState.args.length; i++) {
16047 this.setANSIMode(parseState.args[i], true);
16048 }
16049};
16050
16051/**
16052 * DEC Private Mode Set (DECSET).
16053 */
16054hterm.VT.CSI['?h'] = function(parseState) {
16055 for (var i = 0; i < parseState.args.length; i++) {
16056 this.setDECMode(parseState.args[i], true);
16057 }
16058};
16059
16060/**
16061 * Media Copy (MC).
16062 * Media Copy (MC, DEC Specific).
16063 *
16064 * These commands control the printer. Will not implement.
16065 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016066hterm.VT.CSI['i'] = hterm.VT.CSI['?i'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016067
16068/**
16069 * Reset Mode (RM).
16070 */
16071hterm.VT.CSI['l'] = function(parseState) {
16072 for (var i = 0; i < parseState.args.length; i++) {
16073 this.setANSIMode(parseState.args[i], false);
16074 }
16075};
16076
16077/**
16078 * DEC Private Mode Reset (DECRST).
16079 */
16080hterm.VT.CSI['?l'] = function(parseState) {
16081 for (var i = 0; i < parseState.args.length; i++) {
16082 this.setDECMode(parseState.args[i], false);
16083 }
16084};
16085
16086/**
16087 * Character Attributes (SGR).
16088 *
16089 * Iterate through the list of arguments, applying the following attribute
16090 * changes based on the argument value...
16091 *
16092 * 0 Normal (default).
16093 * 1 Bold.
16094 * 2 Faint.
16095 * 3 Italic (non-xterm).
16096 * 4 Underlined.
16097 * 5 Blink (appears as Bold).
16098 * 7 Inverse.
16099 * 8 Invisible, i.e., hidden (VT300).
16100 * 9 Crossed out (ECMA-48).
16101 * 22 Normal (neither bold nor faint).
16102 * 23 Not italic (non-xterm).
16103 * 24 Not underlined.
16104 * 25 Steady (not blinking).
16105 * 27 Positive (not inverse).
16106 * 28 Visible, i.e., not hidden (VT300).
16107 * 29 Not crossed out (ECMA-48).
16108 * 30 Set foreground color to Black.
16109 * 31 Set foreground color to Red.
16110 * 32 Set foreground color to Green.
16111 * 33 Set foreground color to Yellow.
16112 * 34 Set foreground color to Blue.
16113 * 35 Set foreground color to Magenta.
16114 * 36 Set foreground color to Cyan.
16115 * 37 Set foreground color to White.
16116 * 39 Set foreground color to default (original).
16117 * 40 Set background color to Black.
16118 * 41 Set background color to Red.
16119 * 42 Set background color to Green.
16120 * 43 Set background color to Yellow.
16121 * 44 Set background color to Blue.
16122 * 45 Set background color to Magenta.
16123 * 46 Set background color to Cyan.
16124 * 47 Set background color to White.
16125 * 49 Set background color to default (original)
16126 *
16127 * Non-xterm (italic) codes have mixed support, but are supported by both
16128 * gnome-terminal and rxvt and are recognized as CSI codes on Wikipedia
16129 * (https://en.wikipedia.org/wiki/ANSI_escape_code).
16130 *
16131 * For 16-color support, the following apply.
16132 *
16133 * 90 Set foreground color to Bright Black.
16134 * 91 Set foreground color to Bright Red.
16135 * 92 Set foreground color to Bright Green.
16136 * 93 Set foreground color to Bright Yellow.
16137 * 94 Set foreground color to Bright Blue.
16138 * 95 Set foreground color to Bright Magenta.
16139 * 96 Set foreground color to Bright Cyan.
16140 * 97 Set foreground color to Bright White.
16141 * 100 Set background color to Bright Black.
16142 * 101 Set background color to Bright Red.
16143 * 102 Set background color to Bright Green.
16144 * 103 Set background color to Bright Yellow.
16145 * 104 Set background color to Bright Blue.
16146 * 105 Set background color to Bright Magenta.
16147 * 106 Set background color to Bright Cyan.
16148 * 107 Set background color to Bright White.
16149 *
16150 * For 88- or 256-color support, the following apply.
16151 * 38 ; 5 ; P Set foreground color to P.
16152 * 48 ; 5 ; P Set background color to P.
16153 *
16154 * For true color (24-bit) support, the following apply.
16155 * 38 ; 2 ; R ; G ; B Set foreground color to rgb(R, G, B)
16156 * 48 ; 2 ; R ; G ; B Set background color to rgb(R, G, B)
16157 *
16158 * Note that most terminals consider "bold" to be "bold and bright". In
16159 * some documents the bold state is even referred to as bright. We interpret
16160 * bold as bold-bright here too, but only when the "bold" setting comes before
16161 * the color selection.
16162 */
16163hterm.VT.CSI['m'] = function(parseState) {
16164 function get256(i) {
16165 if (parseState.args.length < i + 2 || parseState.args[i + 1] != '5')
16166 return null;
16167
16168 return parseState.iarg(i + 2, 0);
16169 }
16170
16171 function getTrueColor(i) {
16172 if (parseState.args.length < i + 5 || parseState.args[i + 1] != '2')
16173 return null;
16174 var r = parseState.iarg(i + 2, 0);
16175 var g = parseState.iarg(i + 3, 0);
16176 var b = parseState.iarg(i + 4, 0);
16177
16178 return 'rgb(' + r + ' ,' + g + ' ,' + b + ')';
16179 }
16180
16181 var attrs = this.terminal.getTextAttributes();
16182
16183 if (!parseState.args.length) {
16184 attrs.reset();
16185 return;
16186 }
16187
16188 for (var i = 0; i < parseState.args.length; i++) {
16189 var arg = parseState.iarg(i, 0);
16190
16191 if (arg < 30) {
16192 if (arg == 0) {
16193 attrs.reset();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016194 } else if (arg == 1) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016195 attrs.bold = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016196 } else if (arg == 2) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016197 attrs.faint = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016198 } else if (arg == 3) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016199 attrs.italic = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016200 } else if (arg == 4) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016201 attrs.underline = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016202 } else if (arg == 5) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016203 attrs.blink = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016204 } else if (arg == 7) { // Inverse.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016205 attrs.inverse = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016206 } else if (arg == 8) { // Invisible.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016207 attrs.invisible = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016208 } else if (arg == 9) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016209 attrs.strikethrough = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016210 } else if (arg == 22) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016211 attrs.bold = false;
16212 attrs.faint = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016213 } else if (arg == 23) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016214 attrs.italic = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016215 } else if (arg == 24) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016216 attrs.underline = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016217 } else if (arg == 25) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016218 attrs.blink = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016219 } else if (arg == 27) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016220 attrs.inverse = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016221 } else if (arg == 28) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016222 attrs.invisible = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016223 } else if (arg == 29) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016224 attrs.strikethrough = false;
16225 }
16226
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016227 } else if (arg < 50) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016228 // Select fore/background color from bottom half of 16 color palette
16229 // or from the 256 color palette or alternative specify color in fully
16230 // qualified rgb(r, g, b) form.
16231 if (arg < 38) {
16232 attrs.foregroundSource = arg - 30;
16233
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016234 } else if (arg == 38) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016235 // First check for true color definition
16236 var trueColor = getTrueColor(i);
16237 if (trueColor != null) {
16238 attrs.foregroundSource = attrs.SRC_RGB;
16239 attrs.foreground = trueColor;
16240
16241 i += 5;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016242 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016243 // Check for 256 color
16244 var c = get256(i);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016245 if (c == null) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016246
16247 i += 2;
16248
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016249 if (c >= attrs.colorPalette.length) continue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016250
16251 attrs.foregroundSource = c;
16252 }
16253
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016254 } else if (arg == 39) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016255 attrs.foregroundSource = attrs.SRC_DEFAULT;
16256
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016257 } else if (arg < 48) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016258 attrs.backgroundSource = arg - 40;
16259
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016260 } else if (arg == 48) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016261 // First check for true color definition
16262 var trueColor = getTrueColor(i);
16263 if (trueColor != null) {
16264 attrs.backgroundSource = attrs.SRC_RGB;
16265 attrs.background = trueColor;
16266
16267 i += 5;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016268 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016269 // Check for 256 color
16270 var c = get256(i);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016271 if (c == null) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016272
16273 i += 2;
16274
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016275 if (c >= attrs.colorPalette.length) continue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016276
16277 attrs.backgroundSource = c;
16278 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016279 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016280 attrs.backgroundSource = attrs.SRC_DEFAULT;
16281 }
16282
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016283 } else if (arg >= 90 && arg <= 97) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016284 attrs.foregroundSource = arg - 90 + 8;
16285
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016286 } else if (arg >= 100 && arg <= 107) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016287 attrs.backgroundSource = arg - 100 + 8;
16288 }
16289 }
16290
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016291 attrs.setDefaults(
16292 this.terminal.getForegroundColor(), this.terminal.getBackgroundColor());
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016293};
16294
16295/**
16296 * Set xterm-specific keyboard modes.
16297 *
16298 * Will not implement.
16299 */
16300hterm.VT.CSI['>m'] = hterm.VT.ignore;
16301
16302/**
16303 * Device Status Report (DSR, DEC Specific).
16304 *
16305 * 5 - Status Report. Result (OK) is CSI 0 n
16306 * 6 - Report Cursor Position (CPR) [row;column]. Result is CSI r ; c R
16307 */
16308hterm.VT.CSI['n'] = function(parseState) {
16309 if (parseState.args[0] == '5') {
16310 this.terminal.io.sendString('\x1b0n');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016311 } else if (parseState.args[0] == '6') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016312 var row = this.terminal.getCursorRow() + 1;
16313 var col = this.terminal.getCursorColumn() + 1;
16314 this.terminal.io.sendString('\x1b[' + row + ';' + col + 'R');
16315 }
16316};
16317
16318/**
16319 * Disable modifiers which may be enabled via CSI['>m'].
16320 *
16321 * Will not implement.
16322 */
16323hterm.VT.CSI['>n'] = hterm.VT.ignore;
16324
16325/**
16326 * Device Status Report (DSR, DEC Specific).
16327 *
16328 * 6 - Report Cursor Position (CPR) [row;column] as CSI ? r ; c R
16329 * 15 - Report Printer status as CSI ? 1 0 n (ready) or
16330 * CSI ? 1 1 n (not ready).
16331 * 25 - Report UDK status as CSI ? 2 0 n (unlocked) or CSI ? 2 1 n (locked).
16332 * 26 - Report Keyboard status as CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).
16333 * The last two parameters apply to VT400 & up, and denote keyboard ready
16334 * and LK01 respectively.
16335 * 53 - Report Locator status as CSI ? 5 3 n Locator available, if compiled-in,
16336 * or CSI ? 5 0 n No Locator, if not.
16337 */
16338hterm.VT.CSI['?n'] = function(parseState) {
16339 if (parseState.args[0] == '6') {
16340 var row = this.terminal.getCursorRow() + 1;
16341 var col = this.terminal.getCursorColumn() + 1;
16342 this.terminal.io.sendString('\x1b[' + row + ';' + col + 'R');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016343 } else if (parseState.args[0] == '15') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016344 this.terminal.io.sendString('\x1b[?11n');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016345 } else if (parseState.args[0] == '25') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016346 this.terminal.io.sendString('\x1b[?21n');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016347 } else if (parseState.args[0] == '26') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016348 this.terminal.io.sendString('\x1b[?12;1;0;0n');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016349 } else if (parseState.args[0] == '53') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016350 this.terminal.io.sendString('\x1b[?50n');
16351 }
16352};
16353
16354/**
16355 * This is used by xterm to decide whether to hide the pointer cursor as the
16356 * user types.
16357 *
16358 * Valid values for the parameter:
16359 * 0 - Never hide the pointer.
16360 * 1 - Hide if the mouse tracking mode is not enabled.
16361 * 2 - Always hide the pointer.
16362 *
16363 * If no parameter is given, xterm uses the default, which is 1.
16364 *
16365 * Not currently implemented.
16366 */
16367hterm.VT.CSI['>p'] = hterm.VT.ignore;
16368
16369/**
16370 * Soft terminal reset (DECSTR).
16371 */
16372hterm.VT.CSI['!p'] = function() {
16373 this.reset();
16374 this.terminal.softReset();
16375};
16376
16377/**
16378 * Request ANSI Mode (DECRQM).
16379 *
16380 * Not currently implemented.
16381 */
16382hterm.VT.CSI['$p'] = hterm.VT.ignore;
16383hterm.VT.CSI['?$p'] = hterm.VT.ignore;
16384
16385/**
16386 * Set conformance level (DECSCL).
16387 *
16388 * Not currently implemented.
16389 */
16390hterm.VT.CSI['"p'] = hterm.VT.ignore;
16391
16392/**
16393 * Load LEDs (DECLL).
16394 *
16395 * Not currently implemented. Could be implemented as virtual LEDs overlaying
16396 * the terminal if anyone cares.
16397 */
16398hterm.VT.CSI['q'] = hterm.VT.ignore;
16399
16400/**
16401 * Set cursor style (DECSCUSR, VT520).
16402 *
16403 * 0 - Blinking block.
16404 * 1 - Blinking block (default).
16405 * 2 - Steady block.
16406 * 3 - Blinking underline.
16407 * 4 - Steady underline.
16408 */
16409hterm.VT.CSI[' q'] = function(parseState) {
16410 var arg = parseState.args[0];
16411
16412 if (arg == '0' || arg == '1') {
16413 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
16414 this.terminal.setCursorBlink(true);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016415 } else if (arg == '2') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016416 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
16417 this.terminal.setCursorBlink(false);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016418 } else if (arg == '3') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016419 this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
16420 this.terminal.setCursorBlink(true);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016421 } else if (arg == '4') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016422 this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
16423 this.terminal.setCursorBlink(false);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016424 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016425 console.warn('Unknown cursor style: ' + arg);
16426 }
16427};
16428
16429/**
16430 * Select character protection attribute (DECSCA).
16431 *
16432 * Will not implement.
16433 */
16434hterm.VT.CSI['"q'] = hterm.VT.ignore;
16435
16436/**
16437 * Set Scrolling Region (DECSTBM).
16438 */
16439hterm.VT.CSI['r'] = function(parseState) {
16440 var args = parseState.args;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070016441 var scrollTop = args[0] ? parseInt(args[0], 10) - 1 : null;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016442 var scrollBottom = args[1] ? parseInt(args[1], 10) - 1 : null;
16443 this.terminal.setVTScrollRegion(scrollTop, scrollBottom);
16444 this.terminal.setCursorPosition(0, 0);
16445};
16446
16447/**
16448 * Restore DEC Private Mode Values.
16449 *
16450 * Will not implement.
16451 */
16452hterm.VT.CSI['?r'] = hterm.VT.ignore;
16453
16454/**
16455 * Change Attributes in Rectangular Area (DECCARA)
16456 *
16457 * Will not implement.
16458 */
16459hterm.VT.CSI['$r'] = hterm.VT.ignore;
16460
16461/**
16462 * Save cursor (ANSI.SYS)
16463 */
16464hterm.VT.CSI['s'] = function() {
16465 this.savedState_.save();
16466};
16467
16468/**
16469 * Save DEC Private Mode Values.
16470 *
16471 * Will not implement.
16472 */
16473hterm.VT.CSI['?s'] = hterm.VT.ignore;
16474
16475/**
16476 * Window manipulation (from dtterm, as well as extensions).
16477 *
16478 * Will not implement.
16479 */
16480hterm.VT.CSI['t'] = hterm.VT.ignore;
16481
16482/**
16483 * Reverse Attributes in Rectangular Area (DECRARA).
16484 *
16485 * Will not implement.
16486 */
16487hterm.VT.CSI['$t'] = hterm.VT.ignore;
16488
16489/**
16490 * Set one or more features of the title modes.
16491 *
16492 * Will not implement.
16493 */
16494hterm.VT.CSI['>t'] = hterm.VT.ignore;
16495
16496/**
16497 * Set warning-bell volume (DECSWBV, VT520).
16498 *
16499 * Will not implement.
16500 */
16501hterm.VT.CSI[' t'] = hterm.VT.ignore;
16502
16503/**
16504 * Restore cursor (ANSI.SYS).
16505 */
16506hterm.VT.CSI['u'] = function() {
16507 this.savedState_.restore();
16508};
16509
16510/**
16511 * Set margin-bell volume (DECSMBV, VT520).
16512 *
16513 * Will not implement.
16514 */
16515hterm.VT.CSI[' u'] = hterm.VT.ignore;
16516
16517/**
16518 * Copy Rectangular Area (DECCRA, VT400 and up).
16519 *
16520 * Will not implement.
16521 */
16522hterm.VT.CSI['$v'] = hterm.VT.ignore;
16523
16524/**
16525 * Enable Filter Rectangle (DECEFR).
16526 *
16527 * Will not implement.
16528 */
16529hterm.VT.CSI['\'w'] = hterm.VT.ignore;
16530
16531/**
16532 * Request Terminal Parameters (DECREQTPARM).
16533 *
16534 * Not currently implemented.
16535 */
16536hterm.VT.CSI['x'] = hterm.VT.ignore;
16537
16538/**
16539 * Select Attribute Change Extent (DECSACE).
16540 *
16541 * Will not implement.
16542 */
16543hterm.VT.CSI['*x'] = hterm.VT.ignore;
16544
16545/**
16546 * Fill Rectangular Area (DECFRA), VT420 and up.
16547 *
16548 * Will not implement.
16549 */
16550hterm.VT.CSI['$x'] = hterm.VT.ignore;
16551
16552/**
16553 * vt_tiledata (as used by NAOhack and UnNetHack)
16554 * (see https://nethackwiki.com/wiki/Vt_tiledata for more info)
16555 *
16556 * Implemented as far as we care (start a glyph and end a glyph).
16557 */
16558hterm.VT.CSI['z'] = function(parseState) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016559 if (parseState.args.length < 1) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016560 var arg = parseState.args[0];
16561 if (arg == '0') {
16562 // Start a glyph (one parameter, the glyph number).
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016563 if (parseState.args.length < 2) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016564 this.terminal.getTextAttributes().tileData = parseState.args[1];
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016565 } else if (arg == '1') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016566 // End a glyph.
16567 this.terminal.getTextAttributes().tileData = null;
16568 }
16569};
16570
16571/**
16572 * Enable Locator Reporting (DECELR).
16573 *
16574 * Not currently implemented.
16575 */
16576hterm.VT.CSI['\'z'] = hterm.VT.ignore;
16577
16578/**
16579 * Erase Rectangular Area (DECERA), VT400 and up.
16580 *
16581 * Will not implement.
16582 */
16583hterm.VT.CSI['$z'] = hterm.VT.ignore;
16584
16585/**
16586 * Select Locator Events (DECSLE).
16587 *
16588 * Not currently implemented.
16589 */
16590hterm.VT.CSI['\'{'] = hterm.VT.ignore;
16591
16592/**
16593 * Request Locator Position (DECRQLP).
16594 *
16595 * Not currently implemented.
16596 */
16597hterm.VT.CSI['\'|'] = hterm.VT.ignore;
16598
16599/**
16600 * Insert Columns (DECIC), VT420 and up.
16601 *
16602 * Will not implement.
16603 */
16604hterm.VT.CSI[' }'] = hterm.VT.ignore;
16605
16606/**
16607 * Delete P s Columns (DECDC), VT420 and up.
16608 *
16609 * Will not implement.
16610 */
16611hterm.VT.CSI[' ~'] = hterm.VT.ignore;
16612// SOURCE FILE: hterm/js/hterm_vt_character_map.js
16613// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
16614// Use of this source code is governed by a BSD-style license that can be
16615// found in the LICENSE file.
16616
16617'use strict';
16618
16619lib.rtdep('lib.f');
16620
16621/**
16622 * Character map object.
16623 *
16624 * @param {object} The GL mapping from input characters to output characters.
16625 * The GR mapping will be automatically created.
16626 */
16627hterm.VT.CharacterMap = function(name, glmap) {
16628 /**
16629 * Short name for this character set, useful for debugging.
16630 */
16631 this.name = name;
16632
16633 /**
16634 * The function to call to when this map is installed in GL.
16635 */
16636 this.GL = null;
16637
16638 /**
16639 * The function to call to when this map is installed in GR.
16640 */
16641 this.GR = null;
16642
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016643 if (glmap) this.reset(glmap);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016644};
16645
16646/**
16647 * @param {object} The GL mapping from input characters to output characters.
16648 * The GR mapping will be automatically created.
16649 */
16650hterm.VT.CharacterMap.prototype.reset = function(glmap) {
16651 // Set the the GL mapping.
16652 this.glmap = glmap;
16653
16654 var glkeys = Object.keys(this.glmap).map(function(key) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070016655 return '\\x' + lib.f.zpad(key.charCodeAt(0).toString(16));
16656 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016657
16658 this.glre = new RegExp('[' + glkeys.join('') + ']', 'g');
16659
16660 // Compute the GR mapping.
16661 // This is the same as GL except all keys have their MSB set.
16662 this.grmap = {};
16663
16664 glkeys.forEach(function(glkey) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070016665 var grkey = String.fromCharCode(glkey.charCodeAt(0) & 0x80);
16666 this.grmap[grkey] = this.glmap[glkey];
16667 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016668
16669 var grkeys = Object.keys(this.grmap).map(function(key) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070016670 return '\\x' + lib.f.zpad(key.charCodeAt(0).toString(16), 2);
16671 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016672
16673 this.grre = new RegExp('[' + grkeys.join('') + ']', 'g');
16674
16675 this.GL = function(str) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016676 return str.replace(this.glre, function(ch) {
16677 return this.glmap[ch];
16678 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016679 }.bind(this);
16680
16681 this.GR = function(str) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016682 return str.replace(this.grre, function(ch) {
16683 return this.grmap[ch];
16684 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016685 }.bind(this);
16686};
16687
16688/**
16689 * Mapping from received to display character, used depending on the active
16690 * VT character set.
16691 */
16692hterm.VT.CharacterMap.maps = {};
16693
16694/**
16695 * VT100 Graphic character map.
16696 * http://vt100.net/docs/vt220-rm/table2-4.html
16697 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016698hterm.VT.CharacterMap.maps['0'] = new hterm.VT.CharacterMap('graphic', {
16699 '`': '◆', // ` -> diamond
16700 'a': '▒', // a -> grey-box
16701 'b': '␉', // b -> h/t
16702 'c': '␌', // c -> f/f
16703 'd': '␍', // d -> c/r
16704 'e': '␊', // e -> l/f
16705 'f': '°', // f -> degree
16706 'g': '±', // g -> +/-
16707 'h': '␤', // h -> n/l
16708 'i': '␋', // i -> v/t
16709 'j': '┘', // j -> bottom-right
16710 'k': '┐', // k -> top-right
16711 'l': '┌', // l -> top-left
16712 'm': '└', // m -> bottom-left
16713 'n': '┼', // n -> line-cross
16714 'o': '⎺', // o -> scan1
16715 'p': '⎻', // p -> scan3
16716 'q': '─', // q -> scan5
16717 'r': '⎼', // r -> scan7
16718 's': '⎽', // s -> scan9
16719 't': '├', // t -> left-tee
16720 'u': '┤', // u -> right-tee
16721 'v': '┴', // v -> bottom-tee
16722 'w': '┬', // w -> top-tee
16723 'x': '│', // x -> vertical-line
16724 'y': '≤', // y -> less-equal
16725 'z': '≥', // z -> greater-equal
16726 '{': 'π', // { -> pi
16727 '|': '≠', // | -> not-equal
16728 '}': '£', // } -> british-pound
16729 '~': '·', // ~ -> dot
16730});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016731
16732/**
16733 * British character map.
16734 * http://vt100.net/docs/vt220-rm/table2-5.html
16735 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016736hterm.VT.CharacterMap.maps['A'] = new hterm.VT.CharacterMap('british', {
16737 '#': '£', // # -> british-pound
16738});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016739
16740/**
16741 * US ASCII map, no changes.
16742 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016743hterm.VT.CharacterMap.maps['B'] = new hterm.VT.CharacterMap('us', null);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016744
16745/**
16746 * Dutch character map.
16747 * http://vt100.net/docs/vt220-rm/table2-6.html
16748 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016749hterm.VT.CharacterMap.maps['4'] = new hterm.VT.CharacterMap('dutch', {
16750 '#': '£', // # -> british-pound
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016751
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016752 '@': '¾', // @ -> 3/4
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016753
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016754 '[': 'IJ', // [ -> 'ij' ligature (xterm goes with \u00ff?)
16755 '\\': '½', // \ -> 1/2
16756 ']': '|', // ] -> vertical bar
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016757
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016758 '{': '¨', // { -> two dots
16759 '|': 'f', // | -> f
16760 '}': '¼', // } -> 1/4
16761 '~': '´', // ~ -> acute
16762});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016763
16764/**
16765 * Finnish character map.
16766 * http://vt100.net/docs/vt220-rm/table2-7.html
16767 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016768hterm.VT.CharacterMap.maps['C'] = hterm.VT.CharacterMap.maps['5'] =
16769 new hterm.VT.CharacterMap('finnish', {
16770 '[': 'Ä', // [ -> 'A' umlaut
16771 '\\': 'Ö', // \ -> 'O' umlaut
16772 ']': 'Å', // ] -> 'A' ring
16773 '^': 'Ü', // ~ -> 'u' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016774
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016775 '`': 'é', // ` -> 'e' acute
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016776
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016777 '{': 'ä', // { -> 'a' umlaut
16778 '|': 'ö', // | -> 'o' umlaut
16779 '}': 'å', // } -> 'a' ring
16780 '~': 'ü', // ~ -> 'u' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016781 });
16782
16783/**
16784 * French character map.
16785 * http://vt100.net/docs/vt220-rm/table2-8.html
16786 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016787hterm.VT.CharacterMap.maps['R'] = new hterm.VT.CharacterMap('french', {
16788 '#': '£', // # -> british-pound
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016789
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016790 '@': 'à', // @ -> 'a' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016791
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016792 '[': '°', // [ -> ring
16793 '\\': 'ç', // \ -> 'c' cedilla
16794 ']': '§', // ] -> section symbol (double s)
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016795
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016796 '{': 'é', // { -> 'e' acute
16797 '|': 'ù', // | -> 'u' grave
16798 '}': 'è', // } -> 'e' grave
16799 '~': '¨', // ~ -> umlaut
16800});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016801
16802/**
16803 * French Canadian character map.
16804 * http://vt100.net/docs/vt220-rm/table2-9.html
16805 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016806hterm.VT.CharacterMap.maps['Q'] = new hterm.VT.CharacterMap('french canadian', {
16807 '@': 'à', // @ -> 'a' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016808
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016809 '[': 'â', // [ -> 'a' circumflex
16810 '\\': 'ç', // \ -> 'c' cedilla
16811 ']': 'ê', // ] -> 'e' circumflex
16812 '^': 'î', // ^ -> 'i' circumflex
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016813
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016814 '`': 'ô', // ` -> 'o' circumflex
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016815
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016816 '{': 'é', // { -> 'e' acute
16817 '|': 'ù', // | -> 'u' grave
16818 '}': 'è', // } -> 'e' grave
16819 '~': 'û', // ~ -> 'u' circumflex
16820});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016821
16822/**
16823 * German character map.
16824 * http://vt100.net/docs/vt220-rm/table2-10.html
16825 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016826hterm.VT.CharacterMap.maps['K'] = new hterm.VT.CharacterMap('german', {
16827 '@': '§', // @ -> section symbol (double s)
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016828
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016829 '[': 'Ä', // [ -> 'A' umlaut
16830 '\\': 'Ö', // \ -> 'O' umlaut
16831 ']': 'Ü', // ] -> 'U' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016832
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016833 '{': 'ä', // { -> 'a' umlaut
16834 '|': 'ö', // | -> 'o' umlaut
16835 '}': 'ü', // } -> 'u' umlaut
16836 '~': 'ß', // ~ -> eszett
16837});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016838
16839/**
16840 * Italian character map.
16841 * http://vt100.net/docs/vt220-rm/table2-11.html
16842 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016843hterm.VT.CharacterMap.maps['Y'] = new hterm.VT.CharacterMap('italian', {
16844 '#': '£', // # -> british-pound
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016845
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016846 '@': '§', // @ -> section symbol (double s)
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016847
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016848 '[': '°', // [ -> ring
16849 '\\': 'ç', // \ -> 'c' cedilla
16850 ']': 'é', // ] -> 'e' acute
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016851
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016852 '`': 'ù', // ` -> 'u' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016853
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016854 '{': 'à', // { -> 'a' grave
16855 '|': 'ò', // | -> 'o' grave
16856 '}': 'è', // } -> 'e' grave
16857 '~': 'ì', // ~ -> 'i' grave
16858});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016859
16860/**
16861 * Norwegian/Danish character map.
16862 * http://vt100.net/docs/vt220-rm/table2-12.html
16863 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016864hterm.VT.CharacterMap.maps['E'] = hterm.VT.CharacterMap.maps['6'] =
16865 new hterm.VT.CharacterMap('norwegian/danish', {
16866 '@': 'Ä', // @ -> 'A' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016867
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016868 '[': 'Æ', // [ -> 'AE' ligature
16869 '\\': 'Ø', // \ -> 'O' stroke
16870 ']': 'Å', // ] -> 'A' ring
16871 '^': 'Ü', // ^ -> 'U' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016872
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016873 '`': 'ä', // ` -> 'a' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016874
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016875 '{': 'æ', // { -> 'ae' ligature
16876 '|': 'ø', // | -> 'o' stroke
16877 '}': 'å', // } -> 'a' ring
16878 '~': 'ü', // ~ -> 'u' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016879 });
16880
16881/**
16882 * Spanish character map.
16883 * http://vt100.net/docs/vt220-rm/table2-13.html
16884 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016885hterm.VT.CharacterMap.maps['Z'] = new hterm.VT.CharacterMap('spanish', {
16886 '#': '£', // # -> british-pound
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016887
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016888 '@': '§', // @ -> section symbol (double s)
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016889
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016890 '[': '¡', // [ -> '!' inverted
16891 '\\': 'Ñ', // \ -> 'N' tilde
16892 ']': '¿', // ] -> '?' inverted
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016893
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016894 '{': '°', // { -> ring
16895 '|': 'ñ', // | -> 'n' tilde
16896 '}': 'ç', // } -> 'c' cedilla
16897});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016898
16899/**
16900 * Swedish character map.
16901 * http://vt100.net/docs/vt220-rm/table2-14.html
16902 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016903hterm.VT.CharacterMap.maps['7'] = hterm.VT.CharacterMap.maps['H'] =
16904 new hterm.VT.CharacterMap('swedish', {
16905 '@': 'É', // @ -> 'E' acute
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016906
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016907 '[': 'Ä', // [ -> 'A' umlaut
16908 '\\': 'Ö', // \ -> 'O' umlaut
16909 ']': 'Å', // ] -> 'A' ring
16910 '^': 'Ü', // ^ -> 'U' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016911
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016912 '`': 'é', // ` -> 'e' acute
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016913
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016914 '{': 'ä', // { -> 'a' umlaut
16915 '|': 'ö', // | -> 'o' umlaut
16916 '}': 'å', // } -> 'a' ring
16917 '~': 'ü', // ~ -> 'u' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016918 });
16919
16920/**
16921 * Swiss character map.
16922 * http://vt100.net/docs/vt220-rm/table2-15.html
16923 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016924hterm.VT.CharacterMap.maps['='] = new hterm.VT.CharacterMap('swiss', {
16925 '#': 'ù', // # -> 'u' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016926
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016927 '@': 'à', // @ -> 'a' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016928
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016929 '[': 'é', // [ -> 'e' acute
16930 '\\': 'ç', // \ -> 'c' cedilla
16931 ']': 'ê', // ] -> 'e' circumflex
16932 '^': 'î', // ^ -> 'i' circumflex
16933 '_': 'è', // _ -> 'e' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016934
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016935 '`': 'ô', // ` -> 'o' circumflex
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016936
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016937 '{': 'ä', // { -> 'a' umlaut
16938 '|': 'ö', // | -> 'o' umlaut
16939 '}': 'ü', // } -> 'u' umlaut
16940 '~': 'û', // ~ -> 'u' circumflex
16941});
16942lib.resource.add(
16943 'hterm/audio/bell', 'audio/ogg;base64',
16944 'T2dnUwACAAAAAAAAAADhqW5KAAAAAMFvEjYBHgF2b3JiaXMAAAAAAYC7AAAAAAAAAHcBAAAAAAC4' +
16945 'AU9nZ1MAAAAAAAAAAAAA4aluSgEAAAAAesI3EC3//////////////////8kDdm9yYmlzHQAAAFhp' +
16946 'cGguT3JnIGxpYlZvcmJpcyBJIDIwMDkwNzA5AAAAAAEFdm9yYmlzKUJDVgEACAAAADFMIMWA0JBV' +
16947 'AAAQAABgJCkOk2ZJKaWUoSh5mJRISSmllMUwiZiUicUYY4wxxhhjjDHGGGOMIDRkFQAABACAKAmO' +
16948 'o+ZJas45ZxgnjnKgOWlOOKcgB4pR4DkJwvUmY26mtKZrbs4pJQgNWQUAAAIAQEghhRRSSCGFFGKI' +
16949 'IYYYYoghhxxyyCGnnHIKKqigggoyyCCDTDLppJNOOumoo4466ii00EILLbTSSkwx1VZjrr0GXXxz' +
16950 'zjnnnHPOOeecc84JQkNWAQAgAAAEQgYZZBBCCCGFFFKIKaaYcgoyyIDQkFUAACAAgAAAAABHkRRJ' +
16951 'sRTLsRzN0SRP8ixREzXRM0VTVE1VVVVVdV1XdmXXdnXXdn1ZmIVbuH1ZuIVb2IVd94VhGIZhGIZh' +
16952 'GIZh+H3f933f930gNGQVACABAKAjOZbjKaIiGqLiOaIDhIasAgBkAAAEACAJkiIpkqNJpmZqrmmb' +
16953 'tmirtm3LsizLsgyEhqwCAAABAAQAAAAAAKBpmqZpmqZpmqZpmqZpmqZpmqZpmmZZlmVZlmVZlmVZ' +
16954 'lmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZQGjIKgBAAgBAx3Ecx3EkRVIkx3IsBwgNWQUAyAAA' +
16955 'CABAUizFcjRHczTHczzHczxHdETJlEzN9EwPCA1ZBQAAAgAIAAAAAABAMRzFcRzJ0SRPUi3TcjVX' +
16956 'cz3Xc03XdV1XVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYHQkFUAAAQAACGdZpZq' +
16957 'gAgzkGEgNGQVAIAAAAAYoQhDDAgNWQUAAAQAAIih5CCa0JrzzTkOmuWgqRSb08GJVJsnuamYm3PO' +
16958 'OeecbM4Z45xzzinKmcWgmdCac85JDJqloJnQmnPOeRKbB62p0ppzzhnnnA7GGWGcc85p0poHqdlY' +
16959 'm3POWdCa5qi5FJtzzomUmye1uVSbc84555xzzjnnnHPOqV6czsE54Zxzzonam2u5CV2cc875ZJzu' +
16960 'zQnhnHPOOeecc84555xzzglCQ1YBAEAAAARh2BjGnYIgfY4GYhQhpiGTHnSPDpOgMcgppB6NjkZK' +
16961 'qYNQUhknpXSC0JBVAAAgAACEEFJIIYUUUkghhRRSSCGGGGKIIaeccgoqqKSSiirKKLPMMssss8wy' +
16962 'y6zDzjrrsMMQQwwxtNJKLDXVVmONteaec645SGultdZaK6WUUkoppSA0ZBUAAAIAQCBkkEEGGYUU' +
16963 'UkghhphyyimnoIIKCA1ZBQAAAgAIAAAA8CTPER3RER3RER3RER3RER3P8RxREiVREiXRMi1TMz1V' +
16964 'VFVXdm1Zl3Xbt4Vd2HXf133f141fF4ZlWZZlWZZlWZZlWZZlWZZlCUJDVgEAIAAAAEIIIYQUUkgh' +
16965 'hZRijDHHnINOQgmB0JBVAAAgAIAAAAAAR3EUx5EcyZEkS7IkTdIszfI0T/M00RNFUTRNUxVd0RV1' +
16966 '0xZlUzZd0zVl01Vl1XZl2bZlW7d9WbZ93/d93/d93/d93/d939d1IDRkFQAgAQCgIzmSIimSIjmO' +
16967 '40iSBISGrAIAZAAABACgKI7iOI4jSZIkWZImeZZniZqpmZ7pqaIKhIasAgAAAQAEAAAAAACgaIqn' +
16968 'mIqniIrniI4oiZZpiZqquaJsyq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7rukBo' +
16969 'yCoAQAIAQEdyJEdyJEVSJEVyJAcIDVkFAMgAAAgAwDEcQ1Ikx7IsTfM0T/M00RM90TM9VXRFFwgN' +
16970 'WQUAAAIACAAAAAAAwJAMS7EczdEkUVIt1VI11VItVVQ9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV' +
16971 'VVVVVVVVVVVV1TRN0zSB0JCVAAAZAAAjQQYZhBCKcpBCbj1YCDHmJAWhOQahxBiEpxAzDDkNInSQ' +
16972 'QSc9uJI5wwzz4FIoFURMg40lN44gDcKmXEnlOAhCQ1YEAFEAAIAxyDHEGHLOScmgRM4xCZ2UyDkn' +
16973 'pZPSSSktlhgzKSWmEmPjnKPSScmklBhLip2kEmOJrQAAgAAHAIAAC6HQkBUBQBQAAGIMUgophZRS' +
16974 'zinmkFLKMeUcUko5p5xTzjkIHYTKMQadgxAppRxTzinHHITMQeWcg9BBKAAAIMABACDAQig0ZEUA' +
16975 'ECcA4HAkz5M0SxQlSxNFzxRl1xNN15U0zTQ1UVRVyxNV1VRV2xZNVbYlTRNNTfRUVRNFVRVV05ZN' +
16976 'VbVtzzRl2VRV3RZV1bZl2xZ+V5Z13zNNWRZV1dZNVbV115Z9X9ZtXZg0zTQ1UVRVTRRV1VRV2zZV' +
16977 '17Y1UXRVUVVlWVRVWXZlWfdVV9Z9SxRV1VNN2RVVVbZV2fVtVZZ94XRVXVdl2fdVWRZ+W9eF4fZ9' +
16978 '4RhV1dZN19V1VZZ9YdZlYbd13yhpmmlqoqiqmiiqqqmqtm2qrq1bouiqoqrKsmeqrqzKsq+rrmzr' +
16979 'miiqrqiqsiyqqiyrsqz7qizrtqiquq3KsrCbrqvrtu8LwyzrunCqrq6rsuz7qizruq3rxnHrujB8' +
16980 'pinLpqvquqm6um7runHMtm0co6rqvirLwrDKsu/rui+0dSFRVXXdlF3jV2VZ921fd55b94WybTu/' +
16981 'rfvKceu60vg5z28cubZtHLNuG7+t+8bzKz9hOI6lZ5q2baqqrZuqq+uybivDrOtCUVV9XZVl3zdd' +
16982 'WRdu3zeOW9eNoqrquirLvrDKsjHcxm8cuzAcXds2jlvXnbKtC31jyPcJz2vbxnH7OuP2daOvDAnH' +
16983 'jwAAgAEHAIAAE8pAoSErAoA4AQAGIecUUxAqxSB0EFLqIKRUMQYhc05KxRyUUEpqIZTUKsYgVI5J' +
16984 'yJyTEkpoKZTSUgehpVBKa6GU1lJrsabUYu0gpBZKaS2U0lpqqcbUWowRYxAy56RkzkkJpbQWSmkt' +
16985 'c05K56CkDkJKpaQUS0otVsxJyaCj0kFIqaQSU0mptVBKa6WkFktKMbYUW24x1hxKaS2kEltJKcYU' +
16986 'U20txpojxiBkzknJnJMSSmktlNJa5ZiUDkJKmYOSSkqtlZJSzJyT0kFIqYOOSkkptpJKTKGU1kpK' +
16987 'sYVSWmwx1pxSbDWU0lpJKcaSSmwtxlpbTLV1EFoLpbQWSmmttVZraq3GUEprJaUYS0qxtRZrbjHm' +
16988 'GkppraQSW0mpxRZbji3GmlNrNabWam4x5hpbbT3WmnNKrdbUUo0txppjbb3VmnvvIKQWSmktlNJi' +
16989 'ai3G1mKtoZTWSiqxlZJabDHm2lqMOZTSYkmpxZJSjC3GmltsuaaWamwx5ppSi7Xm2nNsNfbUWqwt' +
16990 'xppTS7XWWnOPufVWAADAgAMAQIAJZaDQkJUAQBQAAEGIUs5JaRByzDkqCULMOSepckxCKSlVzEEI' +
16991 'JbXOOSkpxdY5CCWlFksqLcVWaykptRZrLQAAoMABACDABk2JxQEKDVkJAEQBACDGIMQYhAYZpRiD' +
16992 '0BikFGMQIqUYc05KpRRjzknJGHMOQioZY85BKCmEUEoqKYUQSkklpQIAAAocAAACbNCUWByg0JAV' +
16993 'AUAUAABgDGIMMYYgdFQyKhGETEonqYEQWgutddZSa6XFzFpqrbTYQAithdYySyXG1FpmrcSYWisA' +
16994 'AOzAAQDswEIoNGQlAJAHAEAYoxRjzjlnEGLMOegcNAgx5hyEDirGnIMOQggVY85BCCGEzDkIIYQQ' +
16995 'QuYchBBCCKGDEEIIpZTSQQghhFJK6SCEEEIppXQQQgihlFIKAAAqcAAACLBRZHOCkaBCQ1YCAHkA' +
16996 'AIAxSjkHoZRGKcYglJJSoxRjEEpJqXIMQikpxVY5B6GUlFrsIJTSWmw1dhBKaS3GWkNKrcVYa64h' +
16997 'pdZirDXX1FqMteaaa0otxlprzbkAANwFBwCwAxtFNicYCSo0ZCUAkAcAgCCkFGOMMYYUYoox55xD' +
16998 'CCnFmHPOKaYYc84555RijDnnnHOMMeecc845xphzzjnnHHPOOeecc44555xzzjnnnHPOOeecc845' +
16999 '55xzzgkAACpwAAAIsFFkc4KRoEJDVgIAqQAAABFWYowxxhgbCDHGGGOMMUYSYowxxhhjbDHGGGOM' +
17000 'McaYYowxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHG' +
17001 'GFtrrbXWWmuttdZaa6211lprrQBAvwoHAP8HG1ZHOCkaCyw0ZCUAEA4AABjDmHOOOQYdhIYp6KSE' +
17002 'DkIIoUNKOSglhFBKKSlzTkpKpaSUWkqZc1JSKiWlllLqIKTUWkottdZaByWl1lJqrbXWOgiltNRa' +
17003 'a6212EFIKaXWWostxlBKSq212GKMNYZSUmqtxdhirDGk0lJsLcYYY6yhlNZaazHGGGstKbXWYoy1' +
17004 'xlprSam11mKLNdZaCwDgbnAAgEiwcYaVpLPC0eBCQ1YCACEBAARCjDnnnHMQQgghUoox56CDEEII' +
17005 'IURKMeYcdBBCCCGEjDHnoIMQQgghhJAx5hx0EEIIIYQQOucchBBCCKGEUkrnHHQQQgghlFBC6SCE' +
17006 'EEIIoYRSSikdhBBCKKGEUkopJYQQQgmllFJKKaWEEEIIoYQSSimllBBCCKWUUkoppZQSQgghlFJK' +
17007 'KaWUUkIIoZRQSimllFJKCCGEUkoppZRSSgkhhFBKKaWUUkopIYQSSimllFJKKaUAAIADBwCAACPo' +
17008 'JKPKImw04cIDUGjISgCADAAAcdhq6ynWyCDFnISWS4SQchBiLhFSijlHsWVIGcUY1ZQxpRRTUmvo' +
17009 'nGKMUU+dY0oxw6yUVkookYLScqy1dswBAAAgCAAwECEzgUABFBjIAIADhAQpAKCwwNAxXAQE5BIy' +
17010 'CgwKx4Rz0mkDABCEyAyRiFgMEhOqgaJiOgBYXGDIB4AMjY20iwvoMsAFXdx1IIQgBCGIxQEUkICD' +
17011 'E2544g1PuMEJOkWlDgIAAAAA4AAAHgAAkg0gIiKaOY4Ojw+QEJERkhKTE5QAAAAAALABgA8AgCQF' +
17012 'iIiIZo6jw+MDJERkhKTE5AQlAAAAAAAAAAAACAgIAAAAAAAEAAAACAhPZ2dTAAQYOwAAAAAAAOGp' +
17013 'bkoCAAAAmc74DRgyNjM69TAzOTk74dnLubewsbagmZiNp4d0KbsExSY/I3XUTwJgkeZdn1HY4zoj' +
17014 '33/q9DFtv3Ui1/jmx7lCUtPt18/sYf9MkgAsAGRBd3gMGP4sU+qCPYBy9VrA3YqJosW3W2/ef1iO' +
17015 '/u3cg8ZG/57jU+pPmbGEJUgkfnaI39DbPqxddZphbMRmCc5rKlkUMkyx8iIoug5dJv1OYH9a59c+' +
17016 '3Gevqc7Z2XFdDjL/qHztRfjWEWxJ/aiGezjohu9HsCZdQBKbiH0VtU/3m85lDG2T/+xkZcYnX+E+' +
17017 'aqzv/xTgOoTFG+x7SNqQ4N+oAABSxuVXw77Jd5bmmTmuJakX7509HH0kGYKvARPpwfOSAPySPAc2' +
17018 'EkneDwB2HwAAJlQDYK5586N79GJCjx4+p6aDUd27XSvRyXLJkIC5YZ1jLv5lpOhZTz0s+DmnF1di' +
17019 'ptrnM6UDgIW11Xh8cHTd0/SmbgOAdxcyWwMAAGIrZ3fNSfZbzKiYrK4+tPqtnMVLOeWOG2kVvUY+' +
17020 'p2PJ/hkCl5aFRO4TLGYPZcIU3vYM1hohS4jHFlnyW/2T5J7kGsShXWT8N05V+3C/GPqJ1QdWisGP' +
17021 'xEzHqXISBPIinWDUt7IeJv/f5OtzBxpTzZZQ+CYEhHXfqG4aABQli72GJhN4oJv+hXcApAJSErAW' +
17022 '8G2raAX4NUcABnVt77CzZAB+LsHcVe+Q4h+QB1wh/ZrJTPxSBdI8mgTeAdTsQOoFUEng9BHcVPhx' +
17023 'SRRYkKWZJXOFYP6V4AEripJoEjXgA2wJRZHSExmJDm8F0A6gEXsg5a4ZsALItrMB7+fh7UKLvYWS' +
17024 'dtsDwFf1mzYzS1F82N1h2Oyt2e76B1QdS0SAsQigLPMOgJS9JRC7hFXA6kUsLFNKD5cA5cTRvgSq' +
17025 'Pc3Fl99xW3QTi/MHR8DEm6WnvaVQATwRqRKjywQ9BrrhugR2AKTsPQeQckrAOgDOhbTESyrXQ50C' +
17026 'kNpXdtWjW7W2/3UjeX3U95gIdalfRAoAmqUEiwp53hCdcCwlg47fcbfzlmQMAgaBkh7c+fcDgF+i' +
17027 'fwDXfzegLPcLYJsAAJQArTXjnh/uXGy3v1Hk3pV6/3t5ruW81f6prfbM2Q3WNVy98BwUtbCwhFhA' +
17028 'WuPev6Oe/4ZaFQUcgKrVs4defzh1TADA1DEh5b3VlDaECw5b+bPfkKos3tIAue3vJZOih3ga3l6O' +
17029 '3PSfIkrLv0PAS86PPdL7g8oc2KteNFKKzKRehOv2gJoFLBPXmaXvPBQILgJon0bbWBszrYZYYwE7' +
17030 'jl2j+vTdU7Vpk21LiU0QajPkywAAHqbUC0/YsYOdb4e6BOp7E0cCi04Ao/TgD8ZVAMid6h/A8IeB' +
17031 'Nkp6/xsAACZELEYIk+yvI6Qz1NN6lIftB/6IMWjWJNOqPTMedAmyaj6Es0QBklJpiSWWHnQ2CoYb' +
17032 'GWAmt+0gLQBFKCBnp2QUUQZ/1thtZDBJUpFWY82z34ocorB62oX7qB5y0oPAv/foxH25wVmgIHf2' +
17033 'xFOr8leZcBq1Kx3ZvCq9Bga639AxuHuPNL/71YCF4EywJpqHFAX6XF0sjVbuANnvvdLcrufYwOM/' +
17034 'iDa6iA468AYAAB6mNBMXcgTD8HSRqJ4vw8CjAlCEPACASlX/APwPOJKl9xQAAAPmnev2eWp33Xgy' +
17035 'w3Dvfz6myGk3oyP8YTKsCOvzAgALQi0o1c6Nzs2O2Pg2h4ACIJAgAGP0aNn5x0BDgVfH7u2TtyfD' +
17036 'cRIuYAyQhBF/lvSRAttgA6TPbWZA9gaUrZWAUEAA+Dx47Q3/r87HxUUqZmB0BmUuMlojFjHt1gDu' +
17037 'nnvuX8MImsjSq5WkzSzGS62OEIlOufWWezxWpv6FBgDgJVltfXFYtNAAnqU0xQoD0YLiXo5cF5QV' +
17038 '4CnY1tBLAkZCOABAhbk/AM+/AwSCCdlWAAAMcFjS7owb8GVDzveDiZvznbt2tF4bL5odN1YKl88T' +
17039 'AEABCZvufq9YCTBtMwVAQUEAwGtNltzSaHvADYC3TxLVjqiRA+OZAMhzcqEgRcAOwoCgvdTxsTHL' +
17040 'QEF6+oOb2+PAI8ciPQcXg7pOY+LjxQSv2fjmFuj34gGwz310/bGK6z3xgT887eomWULEaDd04wHe' +
17041 'tYxdjcgV2SxvSwn0VoZXJRqkRC5ASQ/muVoAUsX7AgAQMBNaVwAAlABRxT/1PmfqLqSRNDbhXb07' +
17042 'berpB3b94jpuWEZjBCD2OcdXFpCKEgCDfcFPMw8AAADUwT4lnUm50lmwrpMMhPQIKj6u0E8fr2vG' +
17043 'BngMNdIlrZsigjahljud6AFVg+tzXwUnXL3TJLpajaWKA4VAAAAMiFfqJgKAZ08XrtS3dxtQNYcp' +
17044 'PvYEG8ClvrQRJgBephwnNWJjtGqmp6VEPSvBe7EBiU3qgJbQAwD4Le8LAMDMhHbNAAAlgK+tFs5O' +
17045 '+YyJc9yCnJa3rxLPulGnxwsXV9Fsk2k4PisCAHC8FkwbGE9gJQAAoMnyksj0CdFMZLLgoz8M+Fxz' +
17046 'iwYBgIx+zHiCBAKAlBKNpF1sO9JpVcyEi9ar15YlHgrut5fPJnkdJ6vEwZPyAHQBIEDUrlMcBAAd' +
17047 '2KAS0Qq+JwRsE4AJZtMnAD6GnOYwYlOIZvtzUNdjreB7fiMkWI0CmBB6AIAKc38A9osEFlTSGECB' +
17048 '+cbeRDC0aRpLHqNPplcK/76Lxn2rpmqyXsYJWRi/FQAAAKBQk9MCAOibrQBQADCDsqpooPutd+05' +
17049 'Ce9g6iEdiYXgVmQAI4+4wskEBEiBloNQ6Ki0/KTQ0QjWfjxzi+AeuXKoMjEVfQOZzr0y941qLgM2' +
17050 'AExvbZOqcxZ6J6krlrj4y2j9AdgKDx6GnJsVLhbc42uq584+ouSdNBpoCiCVHrz+WzUA/DDtD8AT' +
17051 'gA3h0lMCAAzcFv+S+fSSNkeYWlTpb34mf2RfmqqJeMeklhHAfu7VoAEACgAApKRktL+KkQDWMwYC' +
17052 'UAAAAHCKsp80xhp91UjqQBw3x45cetqkjQEyu3G9B6N+R650Uq8OVig7wOm6Wun0ea4lKDPoabJs' +
17053 '6aLqgbhPzpv4KR4iODilw88ZpY7q1IOMcbASAOAVtmcCnobcrkG4KGS7/ZnskVWRNF9J0RUHKOnB' +
17054 'yy9WA8Dv6L4AAARMCQUA4GritfVM2lcZfH3Q3T/vZ47J2YHhcmBazjfdyuV25gLAzrc0cwAAAAAY' +
17055 'Ch6PdwAAAGyWjFW4yScjaWa2mGcofHxWxewKALglWBpLUvwwk+UOh5eNGyUOs1/EF+pZr+ud5Ozo' +
17056 'GwYdAABg2p52LiSgAY/ZVlOmilEgHn6G3OcwYjzI7vOj1t6xsx4S3lBY96EUQBF6AIBAmPYH4PoG' +
17057 'YCoJAADWe+OZJZi7/x76/yH7Lzf9M5XzRKnFPmveMsilQHwVAAAAAKB3LQD8PCIAAADga0QujBLy' +
17058 'wzeJ4a6Z/ERVBAUlAEDqvoM7BQBAuAguzFqILtmjH3Kd4wfKobnOhA3z85qWoRPm9hwoOHoDAAlC' +
17059 'bwDAA56FHAuXflHo3fe2ttG9XUDeA9YmYCBQ0oPr/1QC8IvuCwAAApbUAQCK22MmE3O78VAbHQT9' +
17060 'PIPNoT9zNc3l2Oe7TAVLANBufT8MAQAAAGzT4PS8AQAAoELGHb2uaCwwEv1EWhFriUkbAaAZ27/f' +
17061 'VZnTZXbWz3BwWpjUaMZKRj7dZ0J//gUeTdpVEwAAZOFsNxKAjQSgA+ABPoY8Jj5y2wje81jsXc/1' +
17062 'TOQWTDYZBmAkNDiqVwuA2NJ9AQAAEBKAt9Vrsfs/2N19MO91S9rd8EHTZHnzC5MYmfQEACy/FBcA' +
17063 'AADA5c4gi4z8RANs/m6FNXVo9DV46JG1BBDukqlw/Va5G7QbuGVSI+2aZaoLXJrdVj2zlC9Z5QEA' +
17064 'EFz/5QzgVZwAAAAA/oXcxyC6WfTu+09Ve/c766J4VTAGUFmA51+VANKi/QPoPwYgYAkA715OH4S0' +
17065 's5KDHvj99MMq8TPFc3roKZnGOoT1bmIhVgc7XAMBAAAAAMAW1VbQw3gapzOpJd+Kd2fc4iSO62fJ' +
17066 'v9+movui1wUNPAj059N3OVxzk4gV73PmE8FIA2F5mRq37Evc76vLXfF4rD5UJJAw46hW6LZCb5sN' +
17067 'Ldx+kzMCAAB+hfy95+965ZCLP7B3/VlTHCvDEKtQhTm4KiCgAEAbrfbWTPssAAAAXpee1tVrozYY' +
17068 'n41wD1aeYtkKfswN5/SXPO0JDnhO/4laUortv/s412fybe/nONdncoCHnBVliu0CQGBWlPY/5Kwo' +
17069 'm2L/kruPM6Q7oz4tvDQy+bZ3HzOi+gNHA4DZEgA=' +
17070 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050017071
Andrew Geisslerd27bb132018-05-24 11:07:27 -070017072lib.resource.add(
17073 'hterm/concat/date', 'text/plain',
17074 'Tue, 25 Apr 2017 15:12:45 +0000' +
17075 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050017076
Andrew Geisslerd27bb132018-05-24 11:07:27 -070017077lib.resource.add(
17078 'hterm/changelog/version', 'text/plain',
17079 '1.62' +
17080 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050017081
Andrew Geisslerd27bb132018-05-24 11:07:27 -070017082lib.resource.add(
17083 'hterm/changelog/date', 'text/plain',
17084 '2017-04-17' +
17085 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050017086
Andrew Geisslerd27bb132018-05-24 11:07:27 -070017087lib.resource.add(
17088 'hterm/git/HEAD', 'text/plain',
17089 'git rev-parse HEAD' +
17090 '');