blob: 4c4deb80e02d88280fe60605e61b52201d0fb21c [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 var record = this.prefRecords_[name];
2031 if (record) {
2032 this.changeDefault(name, value);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002033 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002034 record = this.prefRecords_[name] =
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002035 new lib.PreferenceManager.Record(name, value);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002036 }
2037
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002038 if (opt_onChange) record.addObserver(opt_onChange);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002039};
2040
2041/**
2042 * Define multiple preferences with a single function call.
2043 *
2044 * @param {Array} defaults An array of 3-element arrays. Each three element
2045 * array should contain the [key, value, onChange] parameters for a
2046 * preference.
2047 */
2048lib.PreferenceManager.prototype.definePreferences = function(defaults) {
2049 for (var i = 0; i < defaults.length; i++) {
2050 this.definePreference(defaults[i][0], defaults[i][1], defaults[i][2]);
2051 }
2052};
2053
2054/**
2055 * Define an ordered list of child preferences.
2056 *
2057 * Child preferences are different from just storing an array of JSON objects
2058 * in that each child is an instance of a preference manager. This means you
2059 * can observe changes to individual child preferences, and get some validation
2060 * that you're not reading or writing to an undefined child preference value.
2061 *
2062 * @param {string} listName A name for the list of children. This must be
2063 * unique in this preference manager. The listName will become a
2064 * preference on this PreferenceManager used to store the ordered list of
2065 * child ids. It is also used in get/add/remove operations to identify the
2066 * list of children to operate on.
2067 * @param {function} childFactory A function that will be used to generate
2068 * instances of these children. The factory function will receive the
2069 * parent lib.PreferenceManager object and a unique id for the new child
2070 * preferences.
2071 */
2072lib.PreferenceManager.prototype.defineChildren = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002073 listName, childFactory) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002074 // Define a preference to hold the ordered list of child ids.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002075 this.definePreference(
2076 listName, [], this.onChildListChange_.bind(this, listName));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002077 this.childFactories_[listName] = childFactory;
2078 this.childLists_[listName] = {};
2079};
2080
2081/**
2082 * Register to observe preference changes.
2083 *
2084 * @param {Function} global A callback that will happen for every preference.
2085 * Pass null if you don't need one.
2086 * @param {Object} map A map of preference specific callbacks. Pass null if
2087 * you don't need any.
2088 */
2089lib.PreferenceManager.prototype.addObservers = function(global, map) {
2090 if (global && typeof global != 'function')
2091 throw new Error('Invalid param: globals');
2092
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002093 if (global) this.globalObservers_.push(global);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002094
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002095 if (!map) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002096
2097 for (var name in map) {
2098 if (!(name in this.prefRecords_))
2099 throw new Error('Unknown preference: ' + name);
2100
2101 this.prefRecords_[name].addObserver(map[name]);
2102 }
2103};
2104
2105/**
2106 * Dispatch the change observers for all known preferences.
2107 *
2108 * It may be useful to call this after readStorage completes, in order to
2109 * get application state in sync with user preferences.
2110 *
2111 * This can be used if you've changed a preference manager out from under
2112 * a live object, for example when switching to a different prefix.
2113 */
2114lib.PreferenceManager.prototype.notifyAll = function() {
2115 for (var name in this.prefRecords_) {
2116 this.notifyChange_(name);
2117 }
2118};
2119
2120/**
2121 * Notify the change observers for a given preference.
2122 *
2123 * @param {string} name The name of the preference that changed.
2124 */
2125lib.PreferenceManager.prototype.notifyChange_ = function(name) {
2126 var record = this.prefRecords_[name];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002127 if (!record) throw new Error('Unknown preference: ' + name);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002128
2129 var currentValue = record.get();
2130
2131 for (var i = 0; i < this.globalObservers_.length; i++)
2132 this.globalObservers_[i](name, currentValue);
2133
2134 for (var i = 0; i < record.observers.length; i++) {
2135 record.observers[i](currentValue, name, this);
2136 }
2137};
2138
2139/**
2140 * Create a new child PreferenceManager for the given child list.
2141 *
2142 * The optional hint parameter is an opaque prefix added to the auto-generated
2143 * unique id for this child. Your child factory can parse out the prefix
2144 * and use it.
2145 *
2146 * @param {string} listName The child list to create the new instance from.
2147 * @param {string} opt_hint Optional hint to include in the child id.
2148 * @param {string} opt_id Optional id to override the generated id.
2149 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002150lib.PreferenceManager.prototype.createChild = function(
2151 listName, opt_hint, opt_id) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002152 var ids = this.get(listName);
2153 var id;
2154
2155 if (opt_id) {
2156 id = opt_id;
2157 if (ids.indexOf(id) != -1)
2158 throw new Error('Duplicate child: ' + listName + ': ' + id);
2159
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002160 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002161 // Pick a random, unique 4-digit hex identifier for the new profile.
2162 while (!id || ids.indexOf(id) != -1) {
2163 id = Math.floor(Math.random() * 0xffff + 1).toString(16);
2164 id = lib.f.zpad(id, 4);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002165 if (opt_hint) id = opt_hint + ':' + id;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002166 }
2167 }
2168
2169 var childManager = this.childFactories_[listName](this, id);
2170 childManager.trace = this.trace;
2171 childManager.resetAll();
2172
2173 this.childLists_[listName][id] = childManager;
2174
2175 ids.push(id);
2176 this.set(listName, ids);
2177
2178 return childManager;
2179};
2180
2181/**
2182 * Remove a child preferences instance.
2183 *
2184 * Removes a child preference manager and clears any preferences stored in it.
2185 *
2186 * @param {string} listName The name of the child list containing the child to
2187 * remove.
2188 * @param {string} id The child ID.
2189 */
2190lib.PreferenceManager.prototype.removeChild = function(listName, id) {
2191 var prefs = this.getChild(listName, id);
2192 prefs.resetAll();
2193
2194 var ids = this.get(listName);
2195 var i = ids.indexOf(id);
2196 if (i != -1) {
2197 ids.splice(i, 1);
2198 this.set(listName, ids);
2199 }
2200
2201 delete this.childLists_[listName][id];
2202};
2203
2204/**
2205 * Return a child PreferenceManager instance for a given id.
2206 *
2207 * If the child list or child id is not known this will return the specified
2208 * default value or throw an exception if no default value is provided.
2209 *
2210 * @param {string} listName The child list to look in.
2211 * @param {string} id The child ID.
2212 * @param {*} opt_default The optional default value to return if the child
2213 * is not found.
2214 */
2215lib.PreferenceManager.prototype.getChild = function(listName, id, opt_default) {
2216 if (!(listName in this.childLists_))
2217 throw new Error('Unknown child list: ' + listName);
2218
2219 var childList = this.childLists_[listName];
2220 if (!(id in childList)) {
2221 if (typeof opt_default == 'undefined')
2222 throw new Error('Unknown "' + listName + '" child: ' + id);
2223
2224 return opt_default;
2225 }
2226
2227 return childList[id];
2228};
2229
2230/**
2231 * Calculate the difference between two lists of child ids.
2232 *
2233 * Given two arrays of child ids, this function will return an object
2234 * with "added", "removed", and "common" properties. Each property is
2235 * a map of child-id to `true`. For example, given...
2236 *
2237 * a = ['child-x', 'child-y']
2238 * b = ['child-y']
2239 *
2240 * diffChildLists(a, b) =>
2241 * { added: { 'child-x': true }, removed: {}, common: { 'child-y': true } }
2242 *
2243 * The added/removed properties assume that `a` is the current list.
2244 *
2245 * @param {Array[string]} a The most recent list of child ids.
2246 * @param {Array[string]} b An older list of child ids.
2247 * @return {Object} An object with added/removed/common properties.
2248 */
2249lib.PreferenceManager.diffChildLists = function(a, b) {
2250 var rv = {
2251 added: {},
2252 removed: {},
2253 common: {},
2254 };
2255
2256 for (var i = 0; i < a.length; i++) {
2257 if (b.indexOf(a[i]) != -1) {
2258 rv.common[a[i]] = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002259 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002260 rv.added[a[i]] = true;
2261 }
2262 }
2263
2264 for (var i = 0; i < b.length; i++) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002265 if ((b[i] in rv.added) || (b[i] in rv.common)) continue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002266
2267 rv.removed[b[i]] = true;
2268 }
2269
2270 return rv;
2271};
2272
2273/**
2274 * Synchronize a list of child PreferenceManagers instances with the current
2275 * list stored in prefs.
2276 *
2277 * This will instantiate any missing managers and read current preference values
2278 * from storage. Any active managers that no longer appear in preferences will
2279 * be deleted.
2280 *
2281 * @param {string} listName The child list to synchronize.
2282 * @param {function()} opt_callback Optional function to invoke when the sync
2283 * is complete.
2284 */
2285lib.PreferenceManager.prototype.syncChildList = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002286 listName, opt_callback) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002287 var pendingChildren = 0;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07002288
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002289 function onChildStorage() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002290 if (--pendingChildren == 0 && opt_callback) opt_callback();
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002291 }
2292
2293 // The list of child ids that we *should* have a manager for.
2294 var currentIds = this.get(listName);
2295
2296 // The known managers at the start of the sync. Any manager still in this
2297 // list at the end should be discarded.
2298 var oldIds = Object.keys(this.childLists_[listName]);
2299
2300 var rv = lib.PreferenceManager.diffChildLists(currentIds, oldIds);
2301
2302 for (var i = 0; i < currentIds.length; i++) {
2303 var id = currentIds[i];
2304
2305 var managerIndex = oldIds.indexOf(id);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002306 if (managerIndex >= 0) oldIds.splice(managerIndex, 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002307
2308 if (!this.childLists_[listName][id]) {
2309 var childManager = this.childFactories_[listName](this, id);
2310 if (!childManager) {
2311 console.warn('Unable to restore child: ' + listName + ': ' + id);
2312 continue;
2313 }
2314
2315 childManager.trace = this.trace;
2316 this.childLists_[listName][id] = childManager;
2317 pendingChildren++;
2318 childManager.readStorage(onChildStorage);
2319 }
2320 }
2321
2322 for (var i = 0; i < oldIds.length; i++) {
2323 delete this.childLists_[listName][oldIds[i]];
2324 }
2325
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002326 if (!pendingChildren && opt_callback) setTimeout(opt_callback);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002327};
2328
2329/**
2330 * Reset a preference to its default state.
2331 *
2332 * This will dispatch the onChange handler if the preference value actually
2333 * changes.
2334 *
2335 * @param {string} name The preference to reset.
2336 */
2337lib.PreferenceManager.prototype.reset = function(name) {
2338 var record = this.prefRecords_[name];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002339 if (!record) throw new Error('Unknown preference: ' + name);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002340
2341 this.storage.removeItem(this.prefix + name);
2342
2343 if (record.currentValue !== this.DEFAULT_VALUE) {
2344 record.currentValue = this.DEFAULT_VALUE;
2345 this.notifyChange_(name);
2346 }
2347};
2348
2349/**
2350 * Reset all preferences back to their default state.
2351 */
2352lib.PreferenceManager.prototype.resetAll = function() {
2353 var changed = [];
2354
2355 for (var listName in this.childLists_) {
2356 var childList = this.childLists_[listName];
2357 for (var id in childList) {
2358 childList[id].resetAll();
2359 }
2360 }
2361
2362 for (var name in this.prefRecords_) {
2363 if (this.prefRecords_[name].currentValue !== this.DEFAULT_VALUE) {
2364 this.prefRecords_[name].currentValue = this.DEFAULT_VALUE;
2365 changed.push(name);
2366 }
2367 }
2368
2369 var keys = Object.keys(this.prefRecords_).map(function(el) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07002370 return this.prefix + el;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002371 }.bind(this));
2372
2373 this.storage.removeItems(keys);
2374
2375 changed.forEach(this.notifyChange_.bind(this));
2376};
2377
2378/**
2379 * Return true if two values should be considered not-equal.
2380 *
2381 * If both values are the same scalar type and compare equal this function
2382 * returns false (no difference), otherwise return true.
2383 *
2384 * This is used in places where we want to check if a preference has changed.
2385 * Rather than take the time to compare complex values we just consider them
2386 * to always be different.
2387 *
2388 * @param {*} a A value to compare.
2389 * @param {*} b A value to compare.
2390 */
2391lib.PreferenceManager.prototype.diff = function(a, b) {
2392 // If the types are different, or the type is not a simple primitive one.
2393 if ((typeof a) !== (typeof b) ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002394 !(/^(undefined|boolean|number|string)$/.test(typeof a))) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002395 return true;
2396 }
2397
2398 return a !== b;
2399};
2400
2401/**
2402 * Change the default value of a preference.
2403 *
2404 * This is useful when subclassing preference managers.
2405 *
2406 * The function does not alter the current value of the preference, unless
2407 * it has the old default value. When that happens, the change observers
2408 * will be notified.
2409 *
2410 * @param {string} name The name of the parameter to change.
2411 * @param {*} newValue The new default value for the preference.
2412 */
2413lib.PreferenceManager.prototype.changeDefault = function(name, newValue) {
2414 var record = this.prefRecords_[name];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002415 if (!record) throw new Error('Unknown preference: ' + name);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002416
2417 if (!this.diff(record.defaultValue, newValue)) {
2418 // Default value hasn't changed.
2419 return;
2420 }
2421
2422 if (record.currentValue !== this.DEFAULT_VALUE) {
2423 // This pref has a specific value, just change the default and we're done.
2424 record.defaultValue = newValue;
2425 return;
2426 }
2427
2428 record.defaultValue = newValue;
2429
2430 this.notifyChange_(name);
2431};
2432
2433/**
2434 * Change the default value of multiple preferences.
2435 *
2436 * @param {Object} map A map of name -> value pairs specifying the new default
2437 * values.
2438 */
2439lib.PreferenceManager.prototype.changeDefaults = function(map) {
2440 for (var key in map) {
2441 this.changeDefault(key, map[key]);
2442 }
2443};
2444
2445/**
2446 * Set a preference to a specific value.
2447 *
2448 * This will dispatch the onChange handler if the preference value actually
2449 * changes.
2450 *
2451 * @param {string} key The preference to set.
2452 * @param {*} value The value to set. Anything that can be represented in
2453 * JSON is a valid value.
2454 */
2455lib.PreferenceManager.prototype.set = function(name, newValue) {
2456 var record = this.prefRecords_[name];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002457 if (!record) throw new Error('Unknown preference: ' + name);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002458
2459 var oldValue = record.get();
2460
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002461 if (!this.diff(oldValue, newValue)) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002462
2463 if (this.diff(record.defaultValue, newValue)) {
2464 record.currentValue = newValue;
2465 this.storage.setItem(this.prefix + name, newValue);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002466 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002467 record.currentValue = this.DEFAULT_VALUE;
2468 this.storage.removeItem(this.prefix + name);
2469 }
2470
2471 // We need to manually send out the notification on this instance. If we
2472 // The storage event won't fire a notification because we've already changed
2473 // the currentValue, so it won't see a difference. If we delayed changing
2474 // currentValue until the storage event, a pref read immediately after a write
2475 // would return the previous value.
2476 //
2477 // The notification is in a timeout so clients don't accidentally depend on
2478 // a synchronous notification.
2479 setTimeout(this.notifyChange_.bind(this, name), 0);
2480};
2481
2482/**
2483 * Get the value of a preference.
2484 *
2485 * @param {string} key The preference to get.
2486 */
2487lib.PreferenceManager.prototype.get = function(name) {
2488 var record = this.prefRecords_[name];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002489 if (!record) throw new Error('Unknown preference: ' + name);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002490
2491 return record.get();
2492};
2493
2494/**
2495 * Return all non-default preferences as a JSON object.
2496 *
2497 * This includes any nested preference managers as well.
2498 */
2499lib.PreferenceManager.prototype.exportAsJson = function() {
2500 var rv = {};
2501
2502 for (var name in this.prefRecords_) {
2503 if (name in this.childLists_) {
2504 rv[name] = [];
2505 var childIds = this.get(name);
2506 for (var i = 0; i < childIds.length; i++) {
2507 var id = childIds[i];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002508 rv[name].push({id: id, json: this.getChild(name, id).exportAsJson()});
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002509 }
2510
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002511 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002512 var record = this.prefRecords_[name];
2513 if (record.currentValue != this.DEFAULT_VALUE)
2514 rv[name] = record.currentValue;
2515 }
2516 }
2517
2518 return rv;
2519};
2520
2521/**
2522 * Import a JSON blob of preferences previously generated with exportAsJson.
2523 *
2524 * This will create nested preference managers as well.
2525 */
2526lib.PreferenceManager.prototype.importFromJson = function(json) {
2527 for (var name in json) {
2528 if (name in this.childLists_) {
2529 var childList = json[name];
2530 for (var i = 0; i < childList.length; i++) {
2531 var id = childList[i].id;
2532
2533 var childPrefManager = this.childLists_[name][id];
2534 if (!childPrefManager)
2535 childPrefManager = this.createChild(name, null, id);
2536
2537 childPrefManager.importFromJson(childList[i].json);
2538 }
2539
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002540 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002541 this.set(name, json[name]);
2542 }
2543 }
2544};
2545
2546/**
2547 * Called when one of the child list preferences changes.
2548 */
2549lib.PreferenceManager.prototype.onChildListChange_ = function(listName) {
2550 this.syncChildList(listName);
2551};
2552
2553/**
2554 * Called when a key in the storage changes.
2555 */
2556lib.PreferenceManager.prototype.onStorageChange_ = function(map) {
2557 for (var key in map) {
2558 if (this.prefix) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002559 if (key.lastIndexOf(this.prefix, 0) != 0) continue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002560 }
2561
2562 var name = key.substr(this.prefix.length);
2563
2564 if (!(name in this.prefRecords_)) {
2565 // Sometimes we'll get notified about prefs that are no longer defined.
2566 continue;
2567 }
2568
2569 var record = this.prefRecords_[name];
2570
2571 var newValue = map[key].newValue;
2572 var currentValue = record.currentValue;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002573 if (currentValue === record.DEFAULT_VALUE) currentValue = (void 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002574
2575 if (this.diff(currentValue, newValue)) {
2576 if (typeof newValue == 'undefined') {
2577 record.currentValue = record.DEFAULT_VALUE;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002578 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002579 record.currentValue = newValue;
2580 }
2581
2582 this.notifyChange_(name);
2583 }
2584 }
2585};
2586// SOURCE FILE: libdot/js/lib_resource.js
2587// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2588// Use of this source code is governed by a BSD-style license that can be
2589// found in the LICENSE file.
2590
2591'use strict';
2592
2593/**
2594 * Storage for canned resources.
2595 *
2596 * These are usually non-JavaScript things that are collected during a build
2597 * step and converted into a series of 'lib.resource.add(...)' calls. See
2598 * the "@resource" directive from libdot/bin/concat.sh for the canonical use
2599 * case.
2600 *
2601 * This is global storage, so you should prefix your resource names to avoid
2602 * collisions.
2603 */
2604lib.resource = {
2605 resources_: {}
2606};
2607
2608/**
2609 * Add a resource.
2610 *
2611 * @param {string} name A name for the resource. You should prefix this to
2612 * avoid collisions with resources from a shared library.
2613 * @param {string} type A mime type for the resource, or "raw" if not
2614 * applicable.
2615 * @param {*} data The value of the resource.
2616 */
2617lib.resource.add = function(name, type, data) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002618 lib.resource.resources_[name] = {type: type, name: name, data: data};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002619};
2620
2621/**
2622 * Retrieve a resource record.
2623 *
2624 * The resource data is stored on the "data" property of the returned object.
2625 *
2626 * @param {string} name The name of the resource to get.
2627 * @param {*} opt_defaultValue The optional value to return if the resource is
2628 * not defined.
2629 * @return {object} An object with "type", "name", and "data" properties.
2630 */
2631lib.resource.get = function(name, opt_defaultValue) {
2632 if (!(name in lib.resource.resources_)) {
2633 if (typeof opt_defaultValue == 'undefined')
2634 throw 'Unknown resource: ' + name;
2635
2636 return opt_defaultValue;
2637 }
2638
2639 return lib.resource.resources_[name];
2640};
2641
2642/**
2643 * Retrieve resource data.
2644 *
2645 * @param {string} name The name of the resource to get.
2646 * @param {*} opt_defaultValue The optional value to return if the resource is
2647 * not defined.
2648 * @return {*} The resource data.
2649 */
2650lib.resource.getData = function(name, opt_defaultValue) {
2651 if (!(name in lib.resource.resources_)) {
2652 if (typeof opt_defaultValue == 'undefined')
2653 throw 'Unknown resource: ' + name;
2654
2655 return opt_defaultValue;
2656 }
2657
2658 return lib.resource.resources_[name].data;
2659};
2660
2661/**
2662 * Retrieve resource as a data: url.
2663 *
2664 * @param {string} name The name of the resource to get.
2665 * @param {*} opt_defaultValue The optional value to return if the resource is
2666 * not defined.
2667 * @return {*} A data: url encoded version of the resource.
2668 */
2669lib.resource.getDataUrl = function(name, opt_defaultValue) {
2670 var resource = lib.resource.get(name, opt_defaultValue);
2671 return 'data:' + resource.type + ',' + resource.data;
2672};
2673// SOURCE FILE: libdot/js/lib_storage.js
2674// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2675// Use of this source code is governed by a BSD-style license that can be
2676// found in the LICENSE file.
2677
2678'use strict';
2679
2680/**
2681 * Namespace for implementations of persistent, possibly cloud-backed
2682 * storage.
2683 */
2684lib.Storage = new Object();
2685// SOURCE FILE: libdot/js/lib_storage_chrome.js
2686// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2687// Use of this source code is governed by a BSD-style license that can be
2688// found in the LICENSE file.
2689
2690'use strict';
2691
2692/**
2693 * chrome.storage based class with an async interface that is interchangeable
2694 * with other lib.Storage.* implementations.
2695 */
2696lib.Storage.Chrome = function(storage) {
2697 this.storage_ = storage;
2698 this.observers_ = [];
2699
2700 chrome.storage.onChanged.addListener(this.onChanged_.bind(this));
2701};
2702
2703/**
2704 * Called by the storage implementation when the storage is modified.
2705 */
2706lib.Storage.Chrome.prototype.onChanged_ = function(changes, areaname) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002707 if (chrome.storage[areaname] != this.storage_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002708
2709 for (var i = 0; i < this.observers_.length; i++) {
2710 this.observers_[i](changes);
2711 }
2712};
2713
2714/**
2715 * Register a function to observe storage changes.
2716 *
2717 * @param {function(map)} callback The function to invoke when the storage
2718 * changes.
2719 */
2720lib.Storage.Chrome.prototype.addObserver = function(callback) {
2721 this.observers_.push(callback);
2722};
2723
2724/**
2725 * Unregister a change observer.
2726 *
2727 * @param {function} observer A previously registered callback.
2728 */
2729lib.Storage.Chrome.prototype.removeObserver = function(callback) {
2730 var i = this.observers_.indexOf(callback);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002731 if (i != -1) this.observers_.splice(i, 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002732};
2733
2734/**
2735 * Delete everything in this storage.
2736 *
2737 * @param {function(map)} callback The function to invoke when the delete
2738 * has completed.
2739 */
2740lib.Storage.Chrome.prototype.clear = function(opt_callback) {
2741 this.storage_.clear();
2742
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002743 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002744};
2745
2746/**
2747 * Return the current value of a storage item.
2748 *
2749 * @param {string} key The key to look up.
2750 * @param {function(value) callback The function to invoke when the value has
2751 * been retrieved.
2752 */
2753lib.Storage.Chrome.prototype.getItem = function(key, callback) {
2754 this.storage_.get(key, callback);
2755};
2756/**
2757 * Fetch the values of multiple storage items.
2758 *
2759 * @param {Array} keys The keys to look up.
2760 * @param {function(map) callback The function to invoke when the values have
2761 * been retrieved.
2762 */
2763
2764lib.Storage.Chrome.prototype.getItems = function(keys, callback) {
2765 this.storage_.get(keys, callback);
2766};
2767
2768/**
2769 * Set a value in storage.
2770 *
2771 * @param {string} key The key for the value to be stored.
2772 * @param {*} value The value to be stored. Anything that can be serialized
2773 * with JSON is acceptable.
2774 * @param {function()} opt_callback Optional function to invoke when the
2775 * set is complete. You don't have to wait for the set to complete in order
2776 * to read the value, since the local cache is updated synchronously.
2777 */
2778lib.Storage.Chrome.prototype.setItem = function(key, value, opt_callback) {
2779 var obj = {};
2780 obj[key] = value;
2781 this.storage_.set(obj, opt_callback);
2782};
2783
2784/**
2785 * Set multiple values in storage.
2786 *
2787 * @param {Object} map A map of key/values to set in storage.
2788 * @param {function()} opt_callback Optional function to invoke when the
2789 * set is complete. You don't have to wait for the set to complete in order
2790 * to read the value, since the local cache is updated synchronously.
2791 */
2792lib.Storage.Chrome.prototype.setItems = function(obj, opt_callback) {
2793 this.storage_.set(obj, opt_callback);
2794};
2795
2796/**
2797 * Remove an item from storage.
2798 *
2799 * @param {string} key The key to be removed.
2800 * @param {function()} opt_callback Optional function to invoke when the
2801 * remove is complete. You don't have to wait for the set to complete in
2802 * order to read the value, since the local cache is updated synchronously.
2803 */
2804lib.Storage.Chrome.prototype.removeItem = function(key, opt_callback) {
2805 this.storage_.remove(key, opt_callback);
2806};
2807
2808/**
2809 * Remove multiple items from storage.
2810 *
2811 * @param {Array} keys The keys to be removed.
2812 * @param {function()} opt_callback Optional function to invoke when the
2813 * remove is complete. You don't have to wait for the set to complete in
2814 * order to read the value, since the local cache is updated synchronously.
2815 */
2816lib.Storage.Chrome.prototype.removeItems = function(keys, opt_callback) {
2817 this.storage_.remove(keys, opt_callback);
2818};
2819// SOURCE FILE: libdot/js/lib_storage_local.js
2820// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2821// Use of this source code is governed by a BSD-style license that can be
2822// found in the LICENSE file.
2823
2824'use strict';
2825
2826/**
2827 * window.localStorage based class with an async interface that is
2828 * interchangeable with other lib.Storage.* implementations.
2829 */
2830lib.Storage.Local = function() {
2831 this.observers_ = [];
2832 this.storage_ = window.localStorage;
2833 window.addEventListener('storage', this.onStorage_.bind(this));
2834};
2835
2836/**
2837 * Called by the storage implementation when the storage is modified.
2838 */
2839lib.Storage.Local.prototype.onStorage_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002840 if (e.storageArea != this.storage_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002841
2842 // IE throws an exception if JSON.parse is given an empty string.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07002843 var prevValue = e.oldValue ? JSON.parse(e.oldValue) : '';
2844 var curValue = e.newValue ? JSON.parse(e.newValue) : '';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002845 var o = {};
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002846 o[e.key] = {oldValue: prevValue, newValue: curValue};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002847
2848 for (var i = 0; i < this.observers_.length; i++) {
2849 this.observers_[i](o);
2850 }
2851};
2852
2853/**
2854 * Register a function to observe storage changes.
2855 *
2856 * @param {function(map)} callback The function to invoke when the storage
2857 * changes.
2858 */
2859lib.Storage.Local.prototype.addObserver = function(callback) {
2860 this.observers_.push(callback);
2861};
2862
2863/**
2864 * Unregister a change observer.
2865 *
2866 * @param {function} observer A previously registered callback.
2867 */
2868lib.Storage.Local.prototype.removeObserver = function(callback) {
2869 var i = this.observers_.indexOf(callback);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002870 if (i != -1) this.observers_.splice(i, 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002871};
2872
2873/**
2874 * Delete everything in this storage.
2875 *
2876 * @param {function(map)} callback The function to invoke when the delete
2877 * has completed.
2878 */
2879lib.Storage.Local.prototype.clear = function(opt_callback) {
2880 this.storage_.clear();
2881
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002882 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002883};
2884
2885/**
2886 * Return the current value of a storage item.
2887 *
2888 * @param {string} key The key to look up.
2889 * @param {function(value) callback The function to invoke when the value has
2890 * been retrieved.
2891 */
2892lib.Storage.Local.prototype.getItem = function(key, callback) {
2893 var value = this.storage_.getItem(key);
2894
2895 if (typeof value == 'string') {
2896 try {
2897 value = JSON.parse(value);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002898 } catch (e) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002899 // If we can't parse the value, just return it unparsed.
2900 }
2901 }
2902
2903 setTimeout(callback.bind(null, value), 0);
2904};
2905
2906/**
2907 * Fetch the values of multiple storage items.
2908 *
2909 * @param {Array} keys The keys to look up.
2910 * @param {function(map) callback The function to invoke when the values have
2911 * been retrieved.
2912 */
2913lib.Storage.Local.prototype.getItems = function(keys, callback) {
2914 var rv = {};
2915
2916 for (var i = keys.length - 1; i >= 0; i--) {
2917 var key = keys[i];
2918 var value = this.storage_.getItem(key);
2919 if (typeof value == 'string') {
2920 try {
2921 rv[key] = JSON.parse(value);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002922 } catch (e) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002923 // If we can't parse the value, just return it unparsed.
2924 rv[key] = value;
2925 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002926 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002927 keys.splice(i, 1);
2928 }
2929 }
2930
2931 setTimeout(callback.bind(null, rv), 0);
2932};
2933
2934/**
2935 * Set a value in storage.
2936 *
2937 * @param {string} key The key for the value to be stored.
2938 * @param {*} value The value to be stored. Anything that can be serialized
2939 * with JSON is acceptable.
2940 * @param {function()} opt_callback Optional function to invoke when the
2941 * set is complete. You don't have to wait for the set to complete in order
2942 * to read the value, since the local cache is updated synchronously.
2943 */
2944lib.Storage.Local.prototype.setItem = function(key, value, opt_callback) {
2945 this.storage_.setItem(key, JSON.stringify(value));
2946
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002947 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002948};
2949
2950/**
2951 * Set multiple values in storage.
2952 *
2953 * @param {Object} map A map of key/values to set in storage.
2954 * @param {function()} opt_callback Optional function to invoke when the
2955 * set is complete. You don't have to wait for the set to complete in order
2956 * to read the value, since the local cache is updated synchronously.
2957 */
2958lib.Storage.Local.prototype.setItems = function(obj, opt_callback) {
2959 for (var key in obj) {
2960 this.storage_.setItem(key, JSON.stringify(obj[key]));
2961 }
2962
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002963 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002964};
2965
2966/**
2967 * Remove an item from storage.
2968 *
2969 * @param {string} key The key to be removed.
2970 * @param {function()} opt_callback Optional function to invoke when the
2971 * remove is complete. You don't have to wait for the set to complete in
2972 * order to read the value, since the local cache is updated synchronously.
2973 */
2974lib.Storage.Local.prototype.removeItem = function(key, opt_callback) {
2975 this.storage_.removeItem(key);
2976
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002977 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002978};
2979
2980/**
2981 * Remove multiple items from storage.
2982 *
2983 * @param {Array} keys The keys to be removed.
2984 * @param {function()} opt_callback Optional function to invoke when the
2985 * remove is complete. You don't have to wait for the set to complete in
2986 * order to read the value, since the local cache is updated synchronously.
2987 */
2988lib.Storage.Local.prototype.removeItems = function(ary, opt_callback) {
2989 for (var i = 0; i < ary.length; i++) {
2990 this.storage_.removeItem(ary[i]);
2991 }
2992
Andrew Geisslerd27bb132018-05-24 11:07:27 -07002993 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05002994};
2995// SOURCE FILE: libdot/js/lib_storage_memory.js
2996// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2997// Use of this source code is governed by a BSD-style license that can be
2998// found in the LICENSE file.
2999
3000'use strict';
3001
3002/**
3003 * In-memory storage class with an async interface that is interchangeable with
3004 * other lib.Storage.* implementations.
3005 */
3006lib.Storage.Memory = function() {
3007 this.observers_ = [];
3008 this.storage_ = {};
3009};
3010
3011/**
3012 * Register a function to observe storage changes.
3013 *
3014 * @param {function(map)} callback The function to invoke when the storage
3015 * changes.
3016 */
3017lib.Storage.Memory.prototype.addObserver = function(callback) {
3018 this.observers_.push(callback);
3019};
3020
3021/**
3022 * Unregister a change observer.
3023 *
3024 * @param {function} observer A previously registered callback.
3025 */
3026lib.Storage.Memory.prototype.removeObserver = function(callback) {
3027 var i = this.observers_.indexOf(callback);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003028 if (i != -1) this.observers_.splice(i, 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003029};
3030
3031/**
3032 * Delete everything in this storage.
3033 *
3034 * @param {function(map)} callback The function to invoke when the delete
3035 * has completed.
3036 */
3037lib.Storage.Memory.prototype.clear = function(opt_callback) {
3038 var e = {};
3039 for (var key in this.storage_) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003040 e[key] = {oldValue: this.storage_[key], newValue: (void 0)};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003041 }
3042
3043 this.storage_ = {};
3044
3045 setTimeout(function() {
3046 for (var i = 0; i < this.observers_.length; i++) {
3047 this.observers_[i](e);
3048 }
3049 }.bind(this), 0);
3050
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003051 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003052};
3053
3054/**
3055 * Return the current value of a storage item.
3056 *
3057 * @param {string} key The key to look up.
3058 * @param {function(value) callback The function to invoke when the value has
3059 * been retrieved.
3060 */
3061lib.Storage.Memory.prototype.getItem = function(key, callback) {
3062 var value = this.storage_[key];
3063
3064 if (typeof value == 'string') {
3065 try {
3066 value = JSON.parse(value);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003067 } catch (e) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003068 // If we can't parse the value, just return it unparsed.
3069 }
3070 }
3071
3072 setTimeout(callback.bind(null, value), 0);
3073};
3074
3075/**
3076 * Fetch the values of multiple storage items.
3077 *
3078 * @param {Array} keys The keys to look up.
3079 * @param {function(map) callback The function to invoke when the values have
3080 * been retrieved.
3081 */
3082lib.Storage.Memory.prototype.getItems = function(keys, callback) {
3083 var rv = {};
3084
3085 for (var i = keys.length - 1; i >= 0; i--) {
3086 var key = keys[i];
3087 var value = this.storage_[key];
3088 if (typeof value == 'string') {
3089 try {
3090 rv[key] = JSON.parse(value);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003091 } catch (e) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003092 // If we can't parse the value, just return it unparsed.
3093 rv[key] = value;
3094 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003095 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003096 keys.splice(i, 1);
3097 }
3098 }
3099
3100 setTimeout(callback.bind(null, rv), 0);
3101};
3102
3103/**
3104 * Set a value in storage.
3105 *
3106 * @param {string} key The key for the value to be stored.
3107 * @param {*} value The value to be stored. Anything that can be serialized
3108 * with JSON is acceptable.
3109 * @param {function()} opt_callback Optional function to invoke when the
3110 * set is complete. You don't have to wait for the set to complete in order
3111 * to read the value, since the local cache is updated synchronously.
3112 */
3113lib.Storage.Memory.prototype.setItem = function(key, value, opt_callback) {
3114 var oldValue = this.storage_[key];
3115 this.storage_[key] = JSON.stringify(value);
3116
3117 var e = {};
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003118 e[key] = {oldValue: oldValue, newValue: value};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003119
3120 setTimeout(function() {
3121 for (var i = 0; i < this.observers_.length; i++) {
3122 this.observers_[i](e);
3123 }
3124 }.bind(this), 0);
3125
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003126 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003127};
3128
3129/**
3130 * Set multiple values in storage.
3131 *
3132 * @param {Object} map A map of key/values to set in storage.
3133 * @param {function()} opt_callback Optional function to invoke when the
3134 * set is complete. You don't have to wait for the set to complete in order
3135 * to read the value, since the local cache is updated synchronously.
3136 */
3137lib.Storage.Memory.prototype.setItems = function(obj, opt_callback) {
3138 var e = {};
3139
3140 for (var key in obj) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003141 e[key] = {oldValue: this.storage_[key], newValue: obj[key]};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003142 this.storage_[key] = JSON.stringify(obj[key]);
3143 }
3144
3145 setTimeout(function() {
3146 for (var i = 0; i < this.observers_.length; i++) {
3147 this.observers_[i](e);
3148 }
3149 }.bind(this));
3150
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003151 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003152};
3153
3154/**
3155 * Remove an item from storage.
3156 *
3157 * @param {string} key The key to be removed.
3158 * @param {function()} opt_callback Optional function to invoke when the
3159 * remove is complete. You don't have to wait for the set to complete in
3160 * order to read the value, since the local cache is updated synchronously.
3161 */
3162lib.Storage.Memory.prototype.removeItem = function(key, opt_callback) {
3163 delete this.storage_[key];
3164
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003165 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003166};
3167
3168/**
3169 * Remove multiple items from storage.
3170 *
3171 * @param {Array} keys The keys to be removed.
3172 * @param {function()} opt_callback Optional function to invoke when the
3173 * remove is complete. You don't have to wait for the set to complete in
3174 * order to read the value, since the local cache is updated synchronously.
3175 */
3176lib.Storage.Memory.prototype.removeItems = function(ary, opt_callback) {
3177 for (var i = 0; i < ary.length; i++) {
3178 delete this.storage_[ary[i]];
3179 }
3180
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003181 if (opt_callback) setTimeout(opt_callback, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003182};
3183// SOURCE FILE: libdot/js/lib_test_manager.js
3184// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3185// Use of this source code is governed by a BSD-style license that can be
3186// found in the LICENSE file.
3187
3188'use strict';
3189
3190/**
3191 * @fileoverview JavaScript unit testing framework for synchronous and
3192 * asynchronous tests.
3193 *
3194 * This file contains the lib.TestManager and related classes. At the moment
3195 * it's all collected in a single file since it's reasonably small
3196 * (=~1k lines), and it's a lot easier to include one file into your test
3197 * harness than it is to include seven.
3198 *
3199 * The following classes are defined...
3200 *
3201 * lib.TestManager - The root class and entrypoint for creating test runs.
3202 * lib.TestManager.Log - Logging service.
3203 * lib.TestManager.Suite - A collection of tests.
3204 * lib.TestManager.Test - A single test.
3205 * lib.TestManager.TestRun - Manages the execution of a set of tests.
3206 * lib.TestManager.Result - A single test result.
3207 */
3208
3209/**
3210 * Root object in the unit test hierarchy, and keeper of the log object.
3211 *
3212 * @param {lib.TestManager.Log} opt_log Optional lib.TestManager.Log object.
3213 * Logs to the JavaScript console if omitted.
3214 */
3215lib.TestManager = function(opt_log) {
3216 this.log = opt_log || new lib.TestManager.Log();
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07003217};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003218
3219/**
3220 * Create a new test run object for this test manager.
3221 *
3222 * @param {Object} opt_cx An object to be passed to test suite setup(),
3223 * preamble(), and test cases during this test run. This object is opaque
3224 * to lib.TestManager.* code. It's entirely up to the test suite what it's
3225 * used for.
3226 */
3227lib.TestManager.prototype.createTestRun = function(opt_cx) {
3228 return new lib.TestManager.TestRun(this, opt_cx);
3229};
3230
3231/**
3232 * Called when a test run associated with this test manager completes.
3233 *
3234 * Clients may override this to call an appropriate function.
3235 */
3236lib.TestManager.prototype.onTestRunComplete = function(testRun) {};
3237
3238/**
3239 * Called before a test associated with this test manager is run.
3240 *
3241 * @param {lib.TestManager.Result} result The result object for the upcoming
3242 * test.
3243 * @param {Object} cx The context object for a test run.
3244 */
3245lib.TestManager.prototype.testPreamble = function(result, cx) {};
3246
3247/**
3248 * Called after a test associated with this test manager finishes.
3249 *
3250 * @param {lib.TestManager.Result} result The result object for the finished
3251 * test.
3252 * @param {Object} cx The context object for a test run.
3253 */
3254lib.TestManager.prototype.testPostamble = function(result, cx) {};
3255
3256/**
3257 * Destination for test case output.
3258 *
3259 * @param {function(string)} opt_logFunction Optional function to call to
3260 * write a string to the log. If omitted, console.log is used.
3261 */
3262lib.TestManager.Log = function(opt_logFunction) {
3263 this.save = false;
3264 this.data = '';
3265 this.logFunction_ = opt_logFunction || function(s) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003266 if (this.save) this.data += s + '\n';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003267 console.log(s);
3268 };
3269 this.pending_ = '';
3270 this.prefix_ = '';
3271 this.prefixStack_ = [];
3272};
3273
3274/**
3275 * Add a prefix to log messages.
3276 *
3277 * This only affects log messages that are added after the prefix is pushed.
3278 *
3279 * @param {string} str The prefix to prepend to future log messages.
3280 */
3281lib.TestManager.Log.prototype.pushPrefix = function(str) {
3282 this.prefixStack_.push(str);
3283 this.prefix_ = this.prefixStack_.join('');
3284};
3285
3286/**
3287 * Remove the most recently added message prefix.
3288 */
3289lib.TestManager.Log.prototype.popPrefix = function() {
3290 this.prefixStack_.pop();
3291 this.prefix_ = this.prefixStack_.join('');
3292};
3293
3294/**
3295 * Queue up a string to print to the log.
3296 *
3297 * If a line is already pending, this string is added to it.
3298 *
3299 * The string is not actually printed to the log until flush() or println()
3300 * is called. The following call sequence will result in TWO lines in the
3301 * log...
3302 *
3303 * log.print('hello');
3304 * log.print(' ');
3305 * log.println('world');
3306 *
3307 * While a typical stream-like thing would result in 'hello world\n', this one
3308 * results in 'hello \nworld\n'.
3309 *
3310 * @param {string} str The string to add to the log.
3311 */
3312lib.TestManager.Log.prototype.print = function(str) {
3313 if (this.pending_) {
3314 this.pending_ += str;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003315 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003316 this.pending_ = this.prefix_ + str;
3317 }
3318};
3319
3320/**
3321 * Print a line to the log and flush it immediately.
3322 *
3323 * @param {string} str The string to add to the log.
3324 */
3325lib.TestManager.Log.prototype.println = function(str) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003326 if (this.pending_) this.flush();
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003327
3328 this.logFunction_(this.prefix_ + str);
3329};
3330
3331/**
3332 * Flush any pending log message.
3333 */
3334lib.TestManager.Log.prototype.flush = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003335 if (!this.pending_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003336
3337 this.logFunction_(this.pending_);
3338 this.pending_ = '';
3339};
3340
3341/**
3342 * Returns a new constructor function that will inherit from
3343 * lib.TestManager.Suite.
3344 *
3345 * Use this function to create a new test suite subclass. It will return a
3346 * properly initialized constructor function for the subclass. You can then
3347 * override the setup() and preamble() methods if necessary and add test cases
3348 * to the subclass.
3349 *
3350 * var MyTests = new lib.TestManager.Suite('MyTests');
3351 *
3352 * MyTests.prototype.setup = function(cx) {
3353 * // Sets this.size to cx.size if it exists, or the default value of 10
3354 * // if not.
3355 * this.setDefault(cx, {size: 10});
3356 * };
3357 *
3358 * MyTests.prototype.preamble = function(result, cx) {
3359 * // Some tests (even successful ones) may side-effect this list, so
3360 * // recreate it before every test.
3361 * this.list = [];
3362 * for (var i = 0; i < this.size; i++) {
3363 * this.list[i] = i;
3364 * }
3365 * };
3366 *
3367 * // Basic synchronous test case.
3368 * MyTests.addTest('pop-length', function(result, cx) {
3369 * this.list.pop();
3370 *
3371 * // If this assertion fails, the testcase will stop here.
3372 * result.assertEQ(this.list.length, this.size - 1);
3373 *
3374 * // A test must indicate it has passed by calling this method.
3375 * result.pass();
3376 * });
3377 *
3378 * // Sample asynchronous test case.
3379 * MyTests.addTest('async-pop-length', function(result, cx) {
3380 * var self = this;
3381 *
3382 * var callback = function() {
3383 * result.assertEQ(self.list.length, self.size - 1);
3384 * result.pass();
3385 * };
3386 *
3387 * // Wait 100ms to check the array length for the sake of this example.
3388 * setTimeout(callback, 100);
3389 *
3390 * this.list.pop();
3391 *
3392 * // Indicate that this test needs another 200ms to complete.
3393 * // If the test does not report pass/fail by then, it is considered to
3394 * // have timed out.
3395 * result.requestTime(200);
3396 * });
3397 *
3398 * ...
3399 *
3400 * @param {string} suiteName The name of the test suite.
3401 */
3402lib.TestManager.Suite = function(suiteName) {
3403 function ctor(testManager, cx) {
3404 this.testManager_ = testManager;
3405 this.suiteName = suiteName;
3406
3407 this.setup(cx);
3408 }
3409
3410 ctor.suiteName = suiteName;
3411 ctor.addTest = lib.TestManager.Suite.addTest;
3412 ctor.disableTest = lib.TestManager.Suite.disableTest;
3413 ctor.getTest = lib.TestManager.Suite.getTest;
3414 ctor.getTestList = lib.TestManager.Suite.getTestList;
3415 ctor.testList_ = [];
3416 ctor.testMap_ = {};
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003417 ctor.prototype = {__proto__: lib.TestManager.Suite.prototype};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003418
3419 lib.TestManager.Suite.subclasses.push(ctor);
3420
3421 return ctor;
3422};
3423
3424/**
3425 * List of lib.TestManager.Suite subclasses, in the order they were defined.
3426 */
3427lib.TestManager.Suite.subclasses = [];
3428
3429/**
3430 * Add a test to a lib.TestManager.Suite.
3431 *
3432 * This method is copied to new subclasses when they are created.
3433 */
3434lib.TestManager.Suite.addTest = function(testName, testFunction) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003435 if (testName in this.testMap_) throw 'Duplicate test name: ' + testName;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003436
3437 var test = new lib.TestManager.Test(this, testName, testFunction);
3438 this.testMap_[testName] = test;
3439 this.testList_.push(test);
3440};
3441
3442/**
3443 * Defines a disabled test.
3444 */
3445lib.TestManager.Suite.disableTest = function(testName, testFunction) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003446 if (testName in this.testMap_) throw 'Duplicate test name: ' + testName;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003447
3448 var test = new lib.TestManager.Test(this, testName, testFunction);
3449 console.log('Disabled test: ' + test.fullName);
3450};
3451
3452/**
3453 * Get a lib.TestManager.Test instance by name.
3454 *
3455 * This method is copied to new subclasses when they are created.
3456 *
3457 * @param {string} testName The name of the desired test.
3458 * @return {lib.TestManager.Test} The requested test, or undefined if it was not
3459 * found.
3460 */
3461lib.TestManager.Suite.getTest = function(testName) {
3462 return this.testMap_[testName];
3463};
3464
3465/**
3466 * Get an array of lib.TestManager.Tests associated with this Suite.
3467 *
3468 * This method is copied to new subclasses when they are created.
3469 */
3470lib.TestManager.Suite.getTestList = function() {
3471 return this.testList_;
3472};
3473
3474/**
3475 * Set properties on a test suite instance, pulling the property value from
3476 * the context if it exists and from the defaults dictionary if not.
3477 *
3478 * This is intended to be used in your test suite's setup() method to
3479 * define parameters for the test suite which may be overridden through the
3480 * context object. For example...
3481 *
3482 * MySuite.prototype.setup = function(cx) {
3483 * this.setDefaults(cx, {size: 10});
3484 * };
3485 *
3486 * If the context object has a 'size' property then this.size will be set to
3487 * the value of cx.size, otherwise this.size will get a default value of 10.
3488 *
3489 * @param {Object} cx The context object for a test run.
3490 * @param {Object} defaults An object containing name/value pairs to set on
3491 * this test suite instance. The value listed here will be used if the
3492 * name is not defined on the context object.
3493 */
3494lib.TestManager.Suite.prototype.setDefaults = function(cx, defaults) {
3495 for (var k in defaults) {
3496 this[k] = (k in cx) ? cx[k] : defaults[k];
3497 }
3498};
3499
3500/**
3501 * Subclassable method called to set up the test suite.
3502 *
3503 * The default implementation of this method is a no-op. If your test suite
3504 * requires some kind of suite-wide setup, this is the place to do it.
3505 *
3506 * It's fine to store state on the test suite instance, that state will be
3507 * accessible to all tests in the suite. If any test case fails, the entire
3508 * test suite object will be discarded and a new one will be created for
3509 * the remaining tests.
3510 *
3511 * Any side effects outside of this test suite instance must be idempotent.
3512 * For example, if you're adding DOM nodes to a document, make sure to first
3513 * test that they're not already there. If they are, remove them rather than
3514 * reuse them. You should not count on their state, since they were probably
3515 * left behind by a failed testcase.
3516 *
3517 * Any exception here will abort the remainder of the test run.
3518 *
3519 * @param {Object} cx The context object for a test run.
3520 */
3521lib.TestManager.Suite.prototype.setup = function(cx) {};
3522
3523/**
3524 * Subclassable method called to do pre-test set up.
3525 *
3526 * The default implementation of this method is a no-op. If your test suite
3527 * requires some kind of pre-test setup, this is the place to do it.
3528 *
3529 * This can be used to avoid a bunch of boilerplate setup/teardown code in
3530 * this suite's testcases.
3531 *
3532 * Any exception here will abort the remainder of the test run.
3533 *
3534 * @param {lib.TestManager.Result} result The result object for the upcoming
3535 * test.
3536 * @param {Object} cx The context object for a test run.
3537 */
3538lib.TestManager.Suite.prototype.preamble = function(result, cx) {};
3539
3540/**
3541 * Subclassable method called to do post-test tear-down.
3542 *
3543 * The default implementation of this method is a no-op. If your test suite
3544 * requires some kind of pre-test setup, this is the place to do it.
3545 *
3546 * This can be used to avoid a bunch of boilerplate setup/teardown code in
3547 * this suite's testcases.
3548 *
3549 * Any exception here will abort the remainder of the test run.
3550 *
3551 * @param {lib.TestManager.Result} result The result object for the finished
3552 * test.
3553 * @param {Object} cx The context object for a test run.
3554 */
3555lib.TestManager.Suite.prototype.postamble = function(result, cx) {};
3556
3557/**
3558 * Object representing a single test in a test suite.
3559 *
3560 * These are created as part of the lib.TestManager.Suite.addTest() method.
3561 * You should never have to construct one by hand.
3562 *
3563 * @param {lib.TestManager.Suite} suiteClass The test suite class containing
3564 * this test.
3565 * @param {string} testName The local name of this test case, not including the
3566 * test suite name.
3567 * @param {function(lib.TestManager.Result, Object)} testFunction The function
3568 * to invoke for this test case. This is passed a Result instance and the
3569 * context object associated with the test run.
3570 *
3571 */
3572lib.TestManager.Test = function(suiteClass, testName, testFunction) {
3573 /**
3574 * The test suite class containing this function.
3575 */
3576 this.suiteClass = suiteClass;
3577
3578 /**
3579 * The local name of this test, not including the test suite name.
3580 */
3581 this.testName = testName;
3582
3583 /**
3584 * The global name of this test, including the test suite name.
3585 */
3586 this.fullName = suiteClass.suiteName + '[' + testName + ']';
3587
3588 // The function to call for this test.
3589 this.testFunction_ = testFunction;
3590};
3591
3592/**
3593 * Execute this test.
3594 *
3595 * This is called by a lib.TestManager.Result instance, as part of a
3596 * lib.TestManager.TestRun. You should not call it by hand.
3597 *
3598 * @param {lib.TestManager.Result} result The result object for the test.
3599 */
3600lib.TestManager.Test.prototype.run = function(result) {
3601 try {
3602 // Tests are applied to the parent lib.TestManager.Suite subclass.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07003603 this.testFunction_.apply(result.suite, [result, result.testRun.cx]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003604 } catch (ex) {
3605 if (ex instanceof lib.TestManager.Result.TestComplete) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003606
3607 result.println('Test raised an exception: ' + ex);
3608
3609 if (ex.stack) {
3610 if (ex.stack instanceof Array) {
3611 result.println(ex.stack.join('\n'));
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003612 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003613 result.println(ex.stack);
3614 }
3615 }
3616
3617 result.completeTest_(result.FAILED, false);
3618 }
3619};
3620
3621/**
3622 * Used to choose a set of tests and run them.
3623 *
3624 * It's slightly more convenient to construct one of these from
3625 * lib.TestManager.prototype.createTestRun().
3626 *
3627 * @param {lib.TestManager} testManager The testManager associated with this
3628 * TestRun.
3629 * @param {Object} cx A context to be passed into the tests. This can be used
3630 * to set parameters for the test suite or individual test cases.
3631 */
3632lib.TestManager.TestRun = function(testManager, cx) {
3633 /**
3634 * The associated lib.TestManager instance.
3635 */
3636 this.testManager = testManager;
3637
3638 /**
3639 * Shortcut to the lib.TestManager's log.
3640 */
3641 this.log = testManager.log;
3642
3643 /**
3644 * The test run context. It's entirely up to the test suite and test cases
3645 * how this is used. It is opaque to lib.TestManager.* classes.
3646 */
3647 this.cx = cx || {};
3648
3649 /**
3650 * The list of test cases that encountered failures.
3651 */
3652 this.failures = [];
3653
3654 /**
3655 * The list of test cases that passed.
3656 */
3657 this.passes = [];
3658
3659 /**
3660 * The time the test run started, or null if it hasn't been started yet.
3661 */
3662 this.startDate = null;
3663
3664 /**
3665 * The time in milliseconds that the test run took to complete, or null if
3666 * it hasn't completed yet.
3667 */
3668 this.duration = null;
3669
3670 /**
3671 * The most recent result object, or null if the test run hasn't started
3672 * yet. In order to detect late failures, this is not cleared when the test
3673 * completes.
3674 */
3675 this.currentResult = null;
3676
3677 /**
3678 * Number of maximum failures. The test run will stop when this number is
3679 * reached. If 0 or omitted, the entire set of selected tests is run, even
3680 * if some fail.
3681 */
3682 this.maxFailures = 0;
3683
3684 /**
3685 * True if this test run ended early because of an unexpected condition.
3686 */
3687 this.panic = false;
3688
3689 // List of pending test cases.
3690 this.testQueue_ = [];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003691};
3692
3693/**
3694 * This value can be passed to select() to indicate that all tests should
3695 * be selected.
3696 */
3697lib.TestManager.TestRun.prototype.ALL_TESTS = new String('<all-tests>');
3698
3699/**
3700 * Add a single test to the test run.
3701 */
3702lib.TestManager.TestRun.prototype.selectTest = function(test) {
3703 this.testQueue_.push(test);
3704};
3705
3706lib.TestManager.TestRun.prototype.selectSuite = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003707 suiteClass, opt_pattern) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003708 var pattern = opt_pattern || this.ALL_TESTS;
3709 var selectCount = 0;
3710 var testList = suiteClass.getTestList();
3711
3712 for (var j = 0; j < testList.length; j++) {
3713 var test = testList[j];
3714 // Note that we're using "!==" rather than "!=" so that we're matching
3715 // the ALL_TESTS String object, rather than the contents of the string.
3716 if (pattern !== this.ALL_TESTS) {
3717 if (pattern instanceof RegExp) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003718 if (!pattern.test(test.testName)) continue;
3719 } else if (test.testName != pattern) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003720 continue;
3721 }
3722 }
3723
3724 this.selectTest(test);
3725 selectCount++;
3726 }
3727
3728 return selectCount;
3729};
3730
3731/**
3732 * Selects one or more tests to gather results for.
3733 *
3734 * Selecting the same test more than once is allowed.
3735 *
3736 * @param {string|RegExp} pattern Pattern used to select tests.
3737 * If TestRun.prototype.ALL_TESTS, all tests are selected.
3738 * If a string, only the test that exactly matches is selected.
3739 * If a RegExp, only tests matching the RegExp are added.
3740 *
3741 * @return {int} The number of additional tests that have been selected into
3742 * this TestRun.
3743 */
3744lib.TestManager.TestRun.prototype.selectPattern = function(pattern) {
3745 var selectCount = 0;
3746
3747 for (var i = 0; i < lib.TestManager.Suite.subclasses.length; i++) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003748 selectCount +=
3749 this.selectSuite(lib.TestManager.Suite.subclasses[i], pattern);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003750 }
3751
3752 if (!selectCount) {
3753 this.log.println('No tests matched selection criteria: ' + pattern);
3754 }
3755
3756 return selectCount;
3757};
3758
3759/**
3760 * Hooked up to window.onerror during a test run in order to catch exceptions
3761 * that would otherwise go uncaught.
3762 */
3763lib.TestManager.TestRun.prototype.onUncaughtException_ = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003764 message, file, line) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003765 if (message.indexOf('Uncaught lib.TestManager.Result.TestComplete') == 0 ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003766 message.indexOf('status: passed') != -1) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003767 // This is a result.pass() or result.fail() call from a callback. We're
3768 // already going to deal with it as part of the completeTest_() call
3769 // that raised it. We can safely squelch this error message.
3770 return true;
3771 }
3772
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003773 if (!this.currentResult) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003774
3775 if (message == 'Uncaught ' + this.currentResult.expectedErrorMessage_) {
3776 // Test cases may need to raise an unhandled exception as part of the test.
3777 return;
3778 }
3779
3780 var when = 'during';
3781
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003782 if (this.currentResult.status != this.currentResult.PENDING) when = 'after';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003783
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003784 this.log.println(
3785 'Uncaught exception ' + when +
3786 ' test case: ' + this.currentResult.test.fullName);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003787 this.log.println(message + ', ' + file + ':' + line);
3788
3789 this.currentResult.completeTest_(this.currentResult.FAILED, false);
3790
3791 return false;
3792};
3793
3794/**
3795 * Called to when this test run has completed.
3796 *
3797 * This method typically re-runs itself asynchronously, in order to let the
3798 * DOM stabilize and short-term timeouts to complete before declaring the
3799 * test run complete.
3800 *
3801 * @param {boolean} opt_skipTimeout If true, the timeout is skipped and the
3802 * test run is completed immediately. This should only be used from within
3803 * this function.
3804 */
3805lib.TestManager.TestRun.prototype.onTestRunComplete_ = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003806 opt_skipTimeout) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003807 if (!opt_skipTimeout) {
3808 // The final test may have left a lingering setTimeout(..., 0), or maybe
3809 // poked at the DOM in a way that will trigger a event to fire at the end
3810 // of this stack, so we give things a chance to settle down before our
3811 // final cleanup...
3812 setTimeout(this.onTestRunComplete_.bind(this), 0, true);
3813 return;
3814 }
3815
3816 this.duration = (new Date()) - this.startDate;
3817
3818 this.log.popPrefix();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003819 this.log.println(
3820 '} ' + this.passes.length + ' passed, ' + this.failures.length +
3821 ' failed, ' + this.msToSeconds_(this.duration));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003822 this.log.println('');
3823
3824 this.summarize();
3825
3826 window.onerror = null;
3827
3828 this.testManager.onTestRunComplete(this);
3829};
3830
3831/**
3832 * Called by the lib.TestManager.Result object when a test completes.
3833 *
3834 * @param {lib.TestManager.Result} result The result object which has just
3835 * completed.
3836 */
3837lib.TestManager.TestRun.prototype.onResultComplete = function(result) {
3838 try {
3839 this.testManager.testPostamble(result, this.cx);
3840 result.suite.postamble(result, this.ctx);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003841 } catch (ex) {
3842 this.log.println(
3843 'Unexpected exception in postamble: ' + (ex.stack ? ex.stack : ex));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003844 this.panic = true;
3845 }
3846
3847 this.log.popPrefix();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003848 this.log.print(
3849 '} ' + result.status + ', ' + this.msToSeconds_(result.duration));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003850 this.log.flush();
3851
3852 if (result.status == result.FAILED) {
3853 this.failures.push(result);
3854 this.currentSuite = null;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003855 } else if (result.status == result.PASSED) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003856 this.passes.push(result);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003857 } else {
3858 this.log.println(
3859 'Unknown result status: ' + result.test.fullName + ': ' +
3860 result.status);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003861 return this.panic = true;
3862 }
3863
3864 this.runNextTest_();
3865};
3866
3867/**
3868 * Called by the lib.TestManager.Result object when a test which has already
3869 * completed reports another completion.
3870 *
3871 * This is usually indicative of a buggy testcase. It is probably reporting a
3872 * result on exit and then again from an asynchronous callback.
3873 *
3874 * It may also be the case that the last act of the testcase causes a DOM change
3875 * which triggers some event to run after the test returns. If the event
3876 * handler reports a failure or raises an uncaught exception, the test will
3877 * fail even though it has already completed.
3878 *
3879 * In any case, re-completing a test ALWAYS moves it into the failure pile.
3880 *
3881 * @param {lib.TestManager.Result} result The result object which has just
3882 * completed.
3883 * @param {string} lateStatus The status that the test attempted to record this
3884 * time around.
3885 */
3886lib.TestManager.TestRun.prototype.onResultReComplete = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003887 result, lateStatus) {
3888 this.log.println(
3889 'Late complete for test: ' + result.test.fullName + ': ' + lateStatus);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003890
3891 // Consider any late completion a failure, even if it's a double-pass, since
3892 // it's a misuse of the testing API.
3893 var index = this.passes.indexOf(result);
3894 if (index >= 0) {
3895 this.passes.splice(index, 1);
3896 this.failures.push(result);
3897 }
3898};
3899
3900/**
3901 * Run the next test in the queue.
3902 */
3903lib.TestManager.TestRun.prototype.runNextTest_ = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003904 if (this.panic || !this.testQueue_.length) return this.onTestRunComplete_();
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003905
3906 if (this.maxFailures && this.failures.length >= this.maxFailures) {
3907 this.log.println('Maximum failure count reached, aborting test run.');
3908 return this.onTestRunComplete_();
3909 }
3910
3911 // Peek at the top test first. We remove it later just before it's about
3912 // to run, so that we don't disturb the incomplete test count in the
3913 // event that we fail before running it.
3914 var test = this.testQueue_[0];
3915 var suite = this.currentResult ? this.currentResult.suite : null;
3916
3917 try {
3918 if (!suite || !(suite instanceof test.suiteClass)) {
3919 this.log.println('Initializing suite: ' + test.suiteClass.suiteName);
3920 suite = new test.suiteClass(this.testManager, this.cx);
3921 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003922 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003923 // If test suite setup fails we're not even going to try to run the tests.
3924 this.log.println('Exception during setup: ' + (ex.stack ? ex.stack : ex));
3925 this.panic = true;
3926 this.onTestRunComplete_();
3927 return;
3928 }
3929
3930 try {
3931 this.log.print('Test: ' + test.fullName + ' {');
3932 this.log.pushPrefix(' ');
3933
3934 this.currentResult = new lib.TestManager.Result(this, suite, test);
3935 this.testManager.testPreamble(this.currentResult, this.cx);
3936 suite.preamble(this.currentResult, this.cx);
3937
3938 this.testQueue_.shift();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003939 } catch (ex) {
3940 this.log.println(
3941 'Unexpected exception during test preamble: ' +
3942 (ex.stack ? ex.stack : ex));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003943 this.log.popPrefix();
3944 this.log.println('}');
3945
3946 this.panic = true;
3947 this.onTestRunComplete_();
3948 return;
3949 }
3950
3951 try {
3952 this.currentResult.run();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003953 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003954 // Result.run() should catch test exceptions and turn them into failures.
3955 // If we got here, it means there is trouble in the testing framework.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07003956 this.log.println(
3957 'Unexpected exception during test run: ' + (ex.stack ? ex.stack : ex));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05003958 this.panic = true;
3959 }
3960};
3961
3962/**
3963 * Run the selected list of tests.
3964 *
3965 * Some tests may need to run asynchronously, so you cannot assume the run is
3966 * complete when this function returns. Instead, pass in a function to be
3967 * called back when the run has completed.
3968 *
3969 * This function will log the results of the test run as they happen into the
3970 * log defined by the associated lib.TestManager. By default this is
3971 * console.log, which can be viewed in the JavaScript console of most browsers.
3972 *
3973 * The browser state is determined by the last test to run. We intentionally
3974 * don't do any cleanup so that you can inspect the state of a failed test, or
3975 * leave the browser ready for manual testing.
3976 *
3977 * Any failures in lib.TestManager.* code or test suite setup or test case
3978 * preamble will cause the test run to abort.
3979 */
3980lib.TestManager.TestRun.prototype.run = function() {
3981 this.log.println('Running ' + this.testQueue_.length + ' test(s) {');
3982 this.log.pushPrefix(' ');
3983
3984 window.onerror = this.onUncaughtException_.bind(this);
3985 this.startDate = new Date();
3986 this.runNextTest_();
3987};
3988
3989/**
3990 * Format milliseconds as fractional seconds.
3991 */
3992lib.TestManager.TestRun.prototype.msToSeconds_ = function(ms) {
3993 var secs = (ms / 1000).toFixed(2);
3994 return secs + 's';
3995};
3996
3997/**
3998 * Log the current result summary.
3999 */
4000lib.TestManager.TestRun.prototype.summarize = function() {
4001 if (this.failures.length) {
4002 for (var i = 0; i < this.failures.length; i++) {
4003 this.log.println('FAILED: ' + this.failures[i].test.fullName);
4004 }
4005 }
4006
4007 if (this.testQueue_.length) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004008 this.log.println(
4009 'Test run incomplete: ' + this.testQueue_.length +
4010 ' test(s) were not run.');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004011 }
4012};
4013
4014/**
4015 * Record of the result of a single test.
4016 *
4017 * These are constructed during a test run, you shouldn't have to make one
4018 * on your own.
4019 *
4020 * An instance of this class is passed in to each test function. It can be
4021 * used to add messages to the test log, to record a test pass/fail state, to
4022 * test assertions, or to create exception-proof wrappers for callback
4023 * functions.
4024 *
4025 * @param {lib.TestManager.TestRun} testRun The TestRun instance associated with
4026 * this result.
4027 * @param {lib.TestManager.Suit} suite The Suite containing the test we're
4028 * collecting this result for.
4029 * @param {lib.TestManager.Test} test The test we're collecting this result for.
4030 */
4031lib.TestManager.Result = function(testRun, suite, test) {
4032 /**
4033 * The TestRun instance associated with this result.
4034 */
4035 this.testRun = testRun;
4036
4037 /**
4038 * The Suite containing the test we're collecting this result for.
4039 */
4040 this.suite = suite;
4041
4042 /**
4043 * The test we're collecting this result for.
4044 */
4045 this.test = test;
4046
4047 /**
4048 * The time we started to collect this result, or null if we haven't started.
4049 */
4050 this.startDate = null;
4051
4052 /**
4053 * The time in milliseconds that the test took to complete, or null if
4054 * it hasn't completed yet.
4055 */
4056 this.duration = null;
4057
4058 /**
4059 * The current status of this test result.
4060 */
4061 this.status = this.PENDING;
4062
4063 // An error message that the test case is expected to generate.
4064 this.expectedErrorMessage_ = null;
4065};
4066
4067/**
4068 * Possible values for this.status.
4069 */
4070lib.TestManager.Result.prototype.PENDING = 'pending';
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004071lib.TestManager.Result.prototype.FAILED = 'FAILED';
4072lib.TestManager.Result.prototype.PASSED = 'passed';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004073
4074/**
4075 * Exception thrown when a test completes (pass or fail), to ensure no more of
4076 * the test is run.
4077 */
4078lib.TestManager.Result.TestComplete = function(result) {
4079 this.result = result;
4080};
4081
4082lib.TestManager.Result.TestComplete.prototype.toString = function() {
4083 return 'lib.TestManager.Result.TestComplete: ' + this.result.test.fullName +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004084 ', status: ' + this.result.status;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004085};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004086
4087/**
4088 * Start the test associated with this result.
4089 */
4090lib.TestManager.Result.prototype.run = function() {
4091 var self = this;
4092
4093 this.startDate = new Date();
4094 this.test.run(this);
4095
4096 if (this.status == this.PENDING && !this.timeout_) {
4097 this.println('Test did not return a value and did not request more time.');
4098 this.completeTest_(this.FAILED, false);
4099 }
4100};
4101
4102/**
4103 * Unhandled error message this test expects to generate.
4104 *
4105 * This must be the exact string that would appear in the JavaScript console,
4106 * minus the 'Uncaught ' prefix.
4107 *
4108 * The test case does *not* automatically fail if the error message is not
4109 * encountered.
4110 */
4111lib.TestManager.Result.prototype.expectErrorMessage = function(str) {
4112 this.expectedErrorMessage_ = str;
4113};
4114
4115/**
4116 * Function called when a test times out.
4117 */
4118lib.TestManager.Result.prototype.onTimeout_ = function() {
4119 this.timeout_ = null;
4120
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004121 if (this.status != this.PENDING) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004122
4123 this.println('Test timed out.');
4124 this.completeTest_(this.FAILED, false);
4125};
4126
4127/**
4128 * Indicate that a test case needs more time to complete.
4129 *
4130 * Before a test case returns it must report a pass/fail result, or request more
4131 * time to do so.
4132 *
4133 * If a test does not report pass/fail before the time expires it will
4134 * be reported as a timeout failure. Any late pass/fails will be noted in the
4135 * test log, but will not affect the final result of the test.
4136 *
4137 * Test cases may call requestTime more than once. If you have a few layers
4138 * of asynchronous API to go through, you should call this once per layer with
4139 * an estimate of how long each callback will take to complete.
4140 *
4141 * @param {int} ms Number of milliseconds requested.
4142 */
4143lib.TestManager.Result.prototype.requestTime = function(ms) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004144 if (this.timeout_) clearTimeout(this.timeout_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004145
4146 this.timeout_ = setTimeout(this.onTimeout_.bind(this), ms);
4147};
4148
4149/**
4150 * Report the completion of a test.
4151 *
4152 * @param {string} status The status of the test case.
4153 * @param {boolean} opt_throw Optional boolean indicating whether or not
4154 * to throw the TestComplete exception.
4155 */
4156lib.TestManager.Result.prototype.completeTest_ = function(status, opt_throw) {
4157 if (this.status == this.PENDING) {
4158 this.duration = (new Date()) - this.startDate;
4159 this.status = status;
4160
4161 this.testRun.onResultComplete(this);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004162 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004163 this.testRun.onResultReComplete(this, status);
4164 }
4165
4166 if (arguments.length < 2 || opt_throw)
4167 throw new lib.TestManager.Result.TestComplete(this);
4168};
4169
4170/**
4171 * Check that two arrays are equal.
4172 */
4173lib.TestManager.Result.prototype.arrayEQ_ = function(actual, expected) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004174 if (!actual || !expected) return (!actual && !expected);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004175
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004176 if (actual.length != expected.length) return false;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004177
4178 for (var i = 0; i < actual.length; ++i)
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004179 if (actual[i] != expected[i]) return false;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004180
4181 return true;
4182};
4183
4184/**
4185 * Assert that an actual value is exactly equal to the expected value.
4186 *
4187 * This uses the JavaScript '===' operator in order to avoid type coercion.
4188 *
4189 * If the assertion fails, the test is marked as a failure and a TestCompleted
4190 * exception is thrown.
4191 *
4192 * @param {*} actual The actual measured value.
4193 * @param {*} expected The value expected.
4194 * @param {string} opt_name An optional name used to identify this
4195 * assertion in the test log. If omitted it will be the file:line
4196 * of the caller.
4197 */
4198lib.TestManager.Result.prototype.assertEQ = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004199 actual, expected, opt_name) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004200 // Utility function to pretty up the log.
4201 function format(value) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004202 if (typeof value == 'number') return value;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004203
4204 var str = String(value);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004205 var ary = str.split('\n').map(function(e) {
4206 return JSON.stringify(e);
4207 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004208 if (ary.length > 1) {
4209 // If the string has newlines, start it off on its own line so that
4210 // it's easier to compare against another string with newlines.
4211 return '\n' + ary.join('\n');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004212 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004213 return ary.join('\n');
4214 }
4215 }
4216
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004217 if (actual === expected) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004218
4219 // Deal with common object types since JavaScript can't.
4220 if (expected instanceof Array)
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004221 if (this.arrayEQ_(actual, expected)) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004222
4223 var name = opt_name ? '[' + opt_name + ']' : '';
4224
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004225 this.fail(
4226 'assertEQ' + name + ': ' + this.getCallerLocation_(1) + ': ' +
4227 format(actual) + ' !== ' + format(expected));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004228};
4229
4230/**
4231 * Assert that a value is true.
4232 *
4233 * This uses the JavaScript '===' operator in order to avoid type coercion.
4234 * The must be the boolean value `true`, not just some "truish" value.
4235 *
4236 * If the assertion fails, the test is marked as a failure and a TestCompleted
4237 * exception is thrown.
4238 *
4239 * @param {boolean} actual The actual measured value.
4240 * @param {string} opt_name An optional name used to identify this
4241 * assertion in the test log. If omitted it will be the file:line
4242 * of the caller.
4243 */
4244lib.TestManager.Result.prototype.assert = function(actual, opt_name) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004245 if (actual === true) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004246
4247 var name = opt_name ? '[' + opt_name + ']' : '';
4248
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004249 this.fail(
4250 'assert' + name + ': ' + this.getCallerLocation_(1) + ': ' +
4251 String(actual));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004252};
4253
4254/**
4255 * Return the filename:line of a calling stack frame.
4256 *
4257 * This uses a dirty hack. It throws an exception, catches it, and examines
4258 * the stack property of the caught exception.
4259 *
4260 * @param {int} frameIndex The stack frame to return. 0 is the frame that
4261 * called this method, 1 is its caller, and so on.
4262 * @return {string} A string of the format "filename:linenumber".
4263 */
4264lib.TestManager.Result.prototype.getCallerLocation_ = function(frameIndex) {
4265 try {
4266 throw new Error();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004267 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004268 var frame = ex.stack.split('\n')[frameIndex + 2];
4269 var ary = frame.match(/([^/]+:\d+):\d+\)?$/);
4270 return ary ? ary[1] : '???';
4271 }
4272};
4273
4274/**
4275 * Write a message to the result log.
4276 */
4277lib.TestManager.Result.prototype.println = function(message) {
4278 this.testRun.log.println(message);
4279};
4280
4281/**
4282 * Mark a failed test and exit out of the rest of the test.
4283 *
4284 * This will throw a TestCompleted exception, causing the current test to stop.
4285 *
4286 * @param {string} opt_message Optional message to add to the log.
4287 */
4288lib.TestManager.Result.prototype.fail = function(opt_message) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004289 if (arguments.length) this.println(opt_message);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004290
4291 this.completeTest_(this.FAILED, true);
4292};
4293
4294/**
4295 * Mark a passed test and exit out of the rest of the test.
4296 *
4297 * This will throw a TestCompleted exception, causing the current test to stop.
4298 */
4299lib.TestManager.Result.prototype.pass = function() {
4300 this.completeTest_(this.PASSED, true);
4301};
4302// SOURCE FILE: libdot/js/lib_utf8.js
4303// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4304// Use of this source code is governed by a BSD-style license that can be
4305// found in the LICENSE file.
4306
4307'use strict';
4308
4309// TODO(davidben): When the string encoding API is implemented,
4310// replace this with the native in-browser implementation.
4311//
4312// https://wiki.whatwg.org/wiki/StringEncoding
4313// https://encoding.spec.whatwg.org/
4314
4315/**
4316 * A stateful UTF-8 decoder.
4317 */
4318lib.UTF8Decoder = function() {
4319 // The number of bytes left in the current sequence.
4320 this.bytesLeft = 0;
4321 // The in-progress code point being decoded, if bytesLeft > 0.
4322 this.codePoint = 0;
4323 // The lower bound on the final code point, if bytesLeft > 0.
4324 this.lowerBound = 0;
4325};
4326
4327/**
4328 * Decodes a some UTF-8 data, taking into account state from previous
4329 * data streamed through the encoder.
4330 *
4331 * @param {String} str data to decode, represented as a JavaScript
4332 * String with each code unit representing a byte between 0x00 to
4333 * 0xFF.
4334 * @return {String} The data decoded into a JavaScript UTF-16 string.
4335 */
4336lib.UTF8Decoder.prototype.decode = function(str) {
4337 var ret = '';
4338 for (var i = 0; i < str.length; i++) {
4339 var c = str.charCodeAt(i);
4340 if (this.bytesLeft == 0) {
4341 if (c <= 0x7F) {
4342 ret += str.charAt(i);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004343 } else if (0xC0 <= c && c <= 0xDF) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004344 this.codePoint = c - 0xC0;
4345 this.bytesLeft = 1;
4346 this.lowerBound = 0x80;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004347 } else if (0xE0 <= c && c <= 0xEF) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004348 this.codePoint = c - 0xE0;
4349 this.bytesLeft = 2;
4350 this.lowerBound = 0x800;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004351 } else if (0xF0 <= c && c <= 0xF7) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004352 this.codePoint = c - 0xF0;
4353 this.bytesLeft = 3;
4354 this.lowerBound = 0x10000;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004355 } else if (0xF8 <= c && c <= 0xFB) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004356 this.codePoint = c - 0xF8;
4357 this.bytesLeft = 4;
4358 this.lowerBound = 0x200000;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004359 } else if (0xFC <= c && c <= 0xFD) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004360 this.codePoint = c - 0xFC;
4361 this.bytesLeft = 5;
4362 this.lowerBound = 0x4000000;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004363 } else {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004364 ret += '�';
4365 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004366 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004367 if (0x80 <= c && c <= 0xBF) {
4368 this.bytesLeft--;
4369 this.codePoint = (this.codePoint << 6) + (c - 0x80);
4370 if (this.bytesLeft == 0) {
4371 // Got a full sequence. Check if it's within bounds and
4372 // filter out surrogate pairs.
4373 var codePoint = this.codePoint;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004374 if (codePoint < this.lowerBound ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004375 (0xD800 <= codePoint && codePoint <= 0xDFFF) ||
4376 codePoint > 0x10FFFF) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004377 ret += '�';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004378 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004379 // Encode as UTF-16 in the output.
4380 if (codePoint < 0x10000) {
4381 ret += String.fromCharCode(codePoint);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004382 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004383 // Surrogate pair.
4384 codePoint -= 0x10000;
4385 ret += String.fromCharCode(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004386 0xD800 + ((codePoint >>> 10) & 0x3FF),
4387 0xDC00 + (codePoint & 0x3FF));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004388 }
4389 }
4390 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004391 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004392 // Too few bytes in multi-byte sequence. Rewind stream so we
4393 // don't lose the next byte.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004394 ret += '�';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004395 this.bytesLeft = 0;
4396 i--;
4397 }
4398 }
4399 }
4400 return ret;
4401};
4402
4403/**
4404 * Decodes UTF-8 data. This is a convenience function for when all the
4405 * data is already known.
4406 *
4407 * @param {String} str data to decode, represented as a JavaScript
4408 * String with each code unit representing a byte between 0x00 to
4409 * 0xFF.
4410 * @return {String} The data decoded into a JavaScript UTF-16 string.
4411 */
4412lib.decodeUTF8 = function(utf8) {
4413 return (new lib.UTF8Decoder()).decode(utf8);
4414};
4415
4416/**
4417 * Encodes a UTF-16 string into UTF-8.
4418 *
4419 * TODO(davidben): Do we need a stateful version of this that can
4420 * handle a surrogate pair split in two calls? What happens if a
4421 * keypress event would have contained a character outside the BMP?
4422 *
4423 * @param {String} str The string to encode.
4424 * @return {String} The string encoded as UTF-8, as a JavaScript
4425 * string with bytes represented as code units from 0x00 to 0xFF.
4426 */
4427lib.encodeUTF8 = function(str) {
4428 var ret = '';
4429 for (var i = 0; i < str.length; i++) {
4430 // Get a unicode code point out of str.
4431 var c = str.charCodeAt(i);
4432 if (0xDC00 <= c && c <= 0xDFFF) {
4433 c = 0xFFFD;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004434 } else if (0xD800 <= c && c <= 0xDBFF) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004435 if (i + 1 < str.length) {
4436 var d = str.charCodeAt(i + 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004437 if (0xDC00 <= d && d <= 0xDFFF) {
4438 // Swallow a surrogate pair.
4439 c = 0x10000 + ((c & 0x3FF) << 10) + (d & 0x3FF);
4440 i++;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004441 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004442 c = 0xFFFD;
4443 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004444 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004445 c = 0xFFFD;
4446 }
4447 }
4448
4449 // Encode c in UTF-8.
4450 var bytesLeft;
4451 if (c <= 0x7F) {
4452 ret += str.charAt(i);
4453 continue;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004454 } else if (c <= 0x7FF) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004455 ret += String.fromCharCode(0xC0 | (c >>> 6));
4456 bytesLeft = 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004457 } else if (c <= 0xFFFF) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004458 ret += String.fromCharCode(0xE0 | (c >>> 12));
4459 bytesLeft = 2;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004460 } else /* if (c <= 0x10FFFF) */ {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004461 ret += String.fromCharCode(0xF0 | (c >>> 18));
4462 bytesLeft = 3;
4463 }
4464
4465 while (bytesLeft > 0) {
4466 bytesLeft--;
4467 ret += String.fromCharCode(0x80 | ((c >>> (6 * bytesLeft)) & 0x3F));
4468 }
4469 }
4470 return ret;
4471};
4472// SOURCE FILE: libdot/js/lib_wc.js
4473// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
4474// Use of lib.wc source code is governed by a BSD-style license that can be
4475// found in the LICENSE file.
4476
4477'use strict';
4478
4479/**
4480 * This JavaScript library is ported from the wcwidth.js module of node.js.
4481 * The original implementation can be found at:
4482 * https://npmjs.org/package/wcwidth.js
4483 */
4484
4485/**
4486 * JavaScript porting of Markus Kuhn's wcwidth() implementation
4487 *
4488 * The following explanation comes from the original C implementation:
4489 *
4490 * This is an implementation of wcwidth() and wcswidth() (defined in
4491 * IEEE Std 1002.1-2001) for Unicode.
4492 *
4493 * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
4494 * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
4495 *
4496 * In fixed-width output devices, Latin characters all occupy a single
4497 * "cell" position of equal width, whereas ideographic CJK characters
4498 * occupy two such cells. Interoperability between terminal-line
4499 * applications and (teletype-style) character terminals using the
4500 * UTF-8 encoding requires agreement on which character should advance
4501 * the cursor by how many cell positions. No established formal
4502 * standards exist at present on which Unicode character shall occupy
4503 * how many cell positions on character terminals. These routines are
4504 * a first attempt of defining such behavior based on simple rules
4505 * applied to data provided by the Unicode Consortium.
4506 *
4507 * For some graphical characters, the Unicode standard explicitly
4508 * defines a character-cell width via the definition of the East Asian
4509 * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
4510 * In all these cases, there is no ambiguity about which width a
4511 * terminal shall use. For characters in the East Asian Ambiguous (A)
4512 * class, the width choice depends purely on a preference of backward
4513 * compatibility with either historic CJK or Western practice.
4514 * Choosing single-width for these characters is easy to justify as
4515 * the appropriate long-term solution, as the CJK practice of
4516 * displaying these characters as double-width comes from historic
4517 * implementation simplicity (8-bit encoded characters were displayed
4518 * single-width and 16-bit ones double-width, even for Greek,
4519 * Cyrillic, etc.) and not any typographic considerations.
4520 *
4521 * Much less clear is the choice of width for the Not East Asian
4522 * (Neutral) class. Existing practice does not dictate a width for any
4523 * of these characters. It would nevertheless make sense
4524 * typographically to allocate two character cells to characters such
4525 * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
4526 * represented adequately with a single-width glyph. The following
4527 * routines at present merely assign a single-cell width to all
4528 * neutral characters, in the interest of simplicity. This is not
4529 * entirely satisfactory and should be reconsidered before
4530 * establishing a formal standard in lib.wc area. At the moment, the
4531 * decision which Not East Asian (Neutral) characters should be
4532 * represented by double-width glyphs cannot yet be answered by
4533 * applying a simple rule from the Unicode database content. Setting
4534 * up a proper standard for the behavior of UTF-8 character terminals
4535 * will require a careful analysis not only of each Unicode character,
4536 * but also of each presentation form, something the author of these
4537 * routines has avoided to do so far.
4538 *
4539 * http://www.unicode.org/unicode/reports/tr11/
4540 *
4541 * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
4542 *
4543 * Permission to use, copy, modify, and distribute lib.wc software
4544 * for any purpose and without fee is hereby granted. The author
4545 * disclaims all warranties with regard to lib.wc software.
4546 *
4547 * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
4548 */
4549
4550/**
4551 * The following function defines the column width of an ISO 10646 character
4552 * as follows:
4553 *
4554 * - The null character (U+0000) has a column width of 0.
4555 * - Other C0/C1 control characters and DEL will lead to a return value of -1.
4556 * - Non-spacing and enclosing combining characters (general category code Mn
4557 * or Me in the Unicode database) have a column width of 0.
4558 * - SOFT HYPHEN (U+00AD) has a column width of 1.
4559 * - Other format characters (general category code Cf in the Unicode database)
4560 * and ZERO WIDTH SPACE (U+200B) have a column width of 0.
4561 * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) have a
4562 * column width of 0.
4563 * - Spacing characters in the East Asian Wide (W) or East Asian Full-width (F)
4564 * category as defined in Unicode Technical Report #11 have a column width of
4565 * 2.
4566 * - East Asian Ambiguous characters are taken into account if
4567 * regardCjkAmbiguous flag is enabled. They have a column width of 2.
4568 * - All remaining characters (including all printable ISO 8859-1 and WGL4
4569 * characters, Unicode control characters, etc.) have a column width of 1.
4570 *
4571 * This implementation assumes that characters are encoded in ISO 10646.
4572 */
4573
4574/**
4575 * This library relies on the use of codePointAt, which is not supported in
4576 * all browsers. Polyfill if not. See
4577 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt#Polyfill
4578 */
4579if (!String.prototype.codePointAt) {
4580 (function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004581 'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004582 var codePointAt = function(position) {
4583 if (this == null) {
4584 throw TypeError();
4585 }
4586 var string = String(this);
4587 var size = string.length;
4588 // `ToInteger`
4589 var index = position ? Number(position) : 0;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004590 if (index != index) { // better `isNaN`
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004591 index = 0;
4592 }
4593 // Account for out-of-bounds indices:
4594 if (index < 0 || index >= size) {
4595 return undefined;
4596 }
4597 // Get the first code unit
4598 var first = string.charCodeAt(index);
4599 var second;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004600 if (
4601 // check if it’s the start of a surrogate pair
4602 first >= 0xD800 && first <= 0xDBFF && // high surrogate
4603 size > index + 1 // there is a next code unit
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004604 ) {
4605 second = string.charCodeAt(index + 1);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004606 if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004607 // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
4608 return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
4609 }
4610 }
4611 return first;
4612 };
4613 if (Object.defineProperty) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004614 Object.defineProperty(
4615 String.prototype, 'codePointAt',
4616 {'value': codePointAt, 'configurable': true, 'writable': true});
4617 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004618 String.prototype.codePointAt = codePointAt;
4619 }
4620 }());
4621}
4622
4623lib.wc = {};
4624
4625// Width of a nul character.
4626lib.wc.nulWidth = 0;
4627
4628// Width of a control character.
4629lib.wc.controlWidth = 0;
4630
4631// Flag whether to consider East Asian Ambiguous characters.
4632lib.wc.regardCjkAmbiguous = false;
4633
4634// Width of an East Asian Ambiguous character.
4635lib.wc.cjkAmbiguousWidth = 2;
4636
4637// Sorted list of non-overlapping intervals of non-spacing characters
4638// generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c"
4639lib.wc.combining = [
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004640 [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
4641 [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
4642 [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
4643 [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
4644 [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
4645 [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
4646 [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
4647 [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
4648 [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
4649 [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
4650 [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
4651 [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
4652 [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
4653 [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
4654 [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
4655 [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
4656 [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
4657 [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
4658 [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
4659 [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
4660 [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
4661 [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
4662 [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
4663 [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
4664 [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
4665 [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
4666 [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
4667 [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
4668 [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
4669 [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
4670 [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
4671 [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
4672 [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
4673 [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
4674 [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
4675 [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
4676 [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
4677 [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
4678 [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
4679 [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
4680 [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
4681 [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
4682 [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
4683 [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
4684 [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
4685 [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
4686 [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004687 [0xE0100, 0xE01EF]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004688];
4689
4690// Sorted list of non-overlapping intervals of East Asian Ambiguous characters
4691// generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c"
4692lib.wc.ambiguous = [
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004693 [0x00A1, 0x00A1], [0x00A4, 0x00A4], [0x00A7, 0x00A8], [0x00AA, 0x00AA],
4694 [0x00AE, 0x00AE], [0x00B0, 0x00B4], [0x00B6, 0x00BA], [0x00BC, 0x00BF],
4695 [0x00C6, 0x00C6], [0x00D0, 0x00D0], [0x00D7, 0x00D8], [0x00DE, 0x00E1],
4696 [0x00E6, 0x00E6], [0x00E8, 0x00EA], [0x00EC, 0x00ED], [0x00F0, 0x00F0],
4697 [0x00F2, 0x00F3], [0x00F7, 0x00FA], [0x00FC, 0x00FC], [0x00FE, 0x00FE],
4698 [0x0101, 0x0101], [0x0111, 0x0111], [0x0113, 0x0113], [0x011B, 0x011B],
4699 [0x0126, 0x0127], [0x012B, 0x012B], [0x0131, 0x0133], [0x0138, 0x0138],
4700 [0x013F, 0x0142], [0x0144, 0x0144], [0x0148, 0x014B], [0x014D, 0x014D],
4701 [0x0152, 0x0153], [0x0166, 0x0167], [0x016B, 0x016B], [0x01CE, 0x01CE],
4702 [0x01D0, 0x01D0], [0x01D2, 0x01D2], [0x01D4, 0x01D4], [0x01D6, 0x01D6],
4703 [0x01D8, 0x01D8], [0x01DA, 0x01DA], [0x01DC, 0x01DC], [0x0251, 0x0251],
4704 [0x0261, 0x0261], [0x02C4, 0x02C4], [0x02C7, 0x02C7], [0x02C9, 0x02CB],
4705 [0x02CD, 0x02CD], [0x02D0, 0x02D0], [0x02D8, 0x02DB], [0x02DD, 0x02DD],
4706 [0x02DF, 0x02DF], [0x0391, 0x03A1], [0x03A3, 0x03A9], [0x03B1, 0x03C1],
4707 [0x03C3, 0x03C9], [0x0401, 0x0401], [0x0410, 0x044F], [0x0451, 0x0451],
4708 [0x2010, 0x2010], [0x2013, 0x2016], [0x2018, 0x2019], [0x201C, 0x201D],
4709 [0x2020, 0x2022], [0x2024, 0x2027], [0x2030, 0x2030], [0x2032, 0x2033],
4710 [0x2035, 0x2035], [0x203B, 0x203B], [0x203E, 0x203E], [0x2074, 0x2074],
4711 [0x207F, 0x207F], [0x2081, 0x2084], [0x20AC, 0x20AC], [0x2103, 0x2103],
4712 [0x2105, 0x2105], [0x2109, 0x2109], [0x2113, 0x2113], [0x2116, 0x2116],
4713 [0x2121, 0x2122], [0x2126, 0x2126], [0x212B, 0x212B], [0x2153, 0x2154],
4714 [0x215B, 0x215E], [0x2160, 0x216B], [0x2170, 0x2179], [0x2190, 0x2199],
4715 [0x21B8, 0x21B9], [0x21D2, 0x21D2], [0x21D4, 0x21D4], [0x21E7, 0x21E7],
4716 [0x2200, 0x2200], [0x2202, 0x2203], [0x2207, 0x2208], [0x220B, 0x220B],
4717 [0x220F, 0x220F], [0x2211, 0x2211], [0x2215, 0x2215], [0x221A, 0x221A],
4718 [0x221D, 0x2220], [0x2223, 0x2223], [0x2225, 0x2225], [0x2227, 0x222C],
4719 [0x222E, 0x222E], [0x2234, 0x2237], [0x223C, 0x223D], [0x2248, 0x2248],
4720 [0x224C, 0x224C], [0x2252, 0x2252], [0x2260, 0x2261], [0x2264, 0x2267],
4721 [0x226A, 0x226B], [0x226E, 0x226F], [0x2282, 0x2283], [0x2286, 0x2287],
4722 [0x2295, 0x2295], [0x2299, 0x2299], [0x22A5, 0x22A5], [0x22BF, 0x22BF],
4723 [0x2312, 0x2312], [0x2460, 0x24E9], [0x24EB, 0x254B], [0x2550, 0x2573],
4724 [0x2580, 0x258F], [0x2592, 0x2595], [0x25A0, 0x25A1], [0x25A3, 0x25A9],
4725 [0x25B2, 0x25B3], [0x25B6, 0x25B7], [0x25BC, 0x25BD], [0x25C0, 0x25C1],
4726 [0x25C6, 0x25C8], [0x25CB, 0x25CB], [0x25CE, 0x25D1], [0x25E2, 0x25E5],
4727 [0x25EF, 0x25EF], [0x2605, 0x2606], [0x2609, 0x2609], [0x260E, 0x260F],
4728 [0x2614, 0x2615], [0x261C, 0x261C], [0x261E, 0x261E], [0x2640, 0x2640],
4729 [0x2642, 0x2642], [0x2660, 0x2661], [0x2663, 0x2665], [0x2667, 0x266A],
4730 [0x266C, 0x266D], [0x266F, 0x266F], [0x273D, 0x273D], [0x2776, 0x277F],
4731 [0xE000, 0xF8FF], [0xFFFD, 0xFFFD], [0xF0000, 0xFFFFD], [0x100000, 0x10FFFD]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004732];
4733
4734/**
4735 * Binary search to check if the given unicode character is a space character.
4736 *
4737 * @param {integer} ucs A unicode character code.
4738 *
4739 * @return {boolean} True if the given character is a space character; false
4740 * otherwise.
4741 */
4742lib.wc.isSpace = function(ucs) {
4743 // Auxiliary function for binary search in interval table.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004744 var min = 0, max = lib.wc.combining.length - 1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004745 var mid;
4746
4747 if (ucs < lib.wc.combining[0][0] || ucs > lib.wc.combining[max][1])
4748 return false;
4749 while (max >= min) {
4750 mid = Math.floor((min + max) / 2);
4751 if (ucs > lib.wc.combining[mid][1]) {
4752 min = mid + 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004753 } else if (ucs < lib.wc.combining[mid][0]) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004754 max = mid - 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004755 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004756 return true;
4757 }
4758 }
4759
4760 return false;
4761};
4762
4763/**
4764 * Auxiliary function for checking if the given unicode character is a East
4765 * Asian Ambiguous character.
4766 *
4767 * @param {integer} ucs A unicode character code.
4768 *
4769 * @return {boolean} True if the given character is a East Asian Ambiguous
4770 * character.
4771 */
4772lib.wc.isCjkAmbiguous = function(ucs) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004773 var min = 0, max = lib.wc.ambiguous.length - 1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004774 var mid;
4775
4776 if (ucs < lib.wc.ambiguous[0][0] || ucs > lib.wc.ambiguous[max][1])
4777 return false;
4778 while (max >= min) {
4779 mid = Math.floor((min + max) / 2);
4780 if (ucs > lib.wc.ambiguous[mid][1]) {
4781 min = mid + 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004782 } else if (ucs < lib.wc.ambiguous[mid][0]) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004783 max = mid - 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004784 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004785 return true;
4786 }
4787 }
4788
4789 return false;
4790};
4791
4792/**
4793 * Determine the column width of the given character.
4794 *
4795 * @param {integer} ucs A unicode character code.
4796 *
4797 * @return {integer} The column width of the given character.
4798 */
4799lib.wc.charWidth = function(ucs) {
4800 if (lib.wc.regardCjkAmbiguous) {
4801 return lib.wc.charWidthRegardAmbiguous(ucs);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004802 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004803 return lib.wc.charWidthDisregardAmbiguous(ucs);
4804 }
4805};
4806
4807/**
4808 * Determine the column width of the given character without considering East
4809 * Asian Ambiguous characters.
4810 *
4811 * @param {integer} ucs A unicode character code.
4812 *
4813 * @return {integer} The column width of the given character.
4814 */
4815lib.wc.charWidthDisregardAmbiguous = function(ucs) {
4816 // Test for 8-bit control characters.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004817 if (ucs === 0) return lib.wc.nulWidth;
4818 if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) return lib.wc.controlWidth;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004819
4820 // Optimize for ASCII characters.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004821 if (ucs < 0x7f) return 1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004822
4823 // Binary search in table of non-spacing characters.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004824 if (lib.wc.isSpace(ucs)) return 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004825
4826 // If we arrive here, ucs is not a combining or C0/C1 control character.
4827 return 1 +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004828 (ucs >= 0x1100 &&
4829 (ucs <= 0x115f || // Hangul Jamo init. consonants
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004830 ucs == 0x2329 || ucs == 0x232a ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004831 (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || // CJK ... Yi
4832 (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
4833 (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compatibility Ideographs
4834 (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
4835 (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compatibility Forms
4836 (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004837 (ucs >= 0xffe0 && ucs <= 0xffe6) ||
4838 (ucs >= 0x20000 && ucs <= 0x2fffd) ||
4839 (ucs >= 0x30000 && ucs <= 0x3fffd)));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004840 // TODO: emoji characters usually require space for wide characters although
4841 // East Asian width spec says nothing. Should we add special cases for them?
4842};
4843
4844/**
4845 * Determine the column width of the given character considering East Asian
4846 * Ambiguous characters.
4847 *
4848 * @param {integer} ucs A unicode character code.
4849 *
4850 * @return {integer} The column width of the given character.
4851 */
4852lib.wc.charWidthRegardAmbiguous = function(ucs) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004853 if (lib.wc.isCjkAmbiguous(ucs)) return lib.wc.cjkAmbiguousWidth;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004854
4855 return lib.wc.charWidthDisregardAmbiguous(ucs);
4856};
4857
4858/**
4859 * Determine the column width of the given string.
4860 *
4861 * @param {string} str A string.
4862 *
4863 * @return {integer} The column width of the given string.
4864 */
4865lib.wc.strWidth = function(str) {
4866 var width, rv = 0;
4867
4868 for (var i = 0; i < str.length;) {
4869 var codePoint = str.codePointAt(i);
4870 width = lib.wc.charWidth(codePoint);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004871 if (width < 0) return -1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004872 rv += width;
4873 i += (codePoint <= 0xffff) ? 1 : 2;
4874 }
4875
4876 return rv;
4877};
4878
4879/**
4880 * Get the substring at the given column offset of the given column width.
4881 *
4882 * @param {string} str The string to get substring from.
4883 * @param {integer} start The starting column offset to get substring.
4884 * @param {integer} opt_width The column width of the substring.
4885 *
4886 * @return {string} The substring.
4887 */
4888lib.wc.substr = function(str, start, opt_width) {
4889 var startIndex, endIndex, width;
4890
4891 for (startIndex = 0, width = 0; startIndex < str.length; startIndex++) {
4892 width += lib.wc.charWidth(str.charCodeAt(startIndex));
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004893 if (width > start) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004894 }
4895
4896 if (opt_width != undefined) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004897 for (endIndex = startIndex, width = 0;
4898 endIndex < str.length && width < opt_width;
4899 width += lib.wc.charWidth(str.charCodeAt(endIndex)), endIndex++)
4900 ;
4901 if (width > opt_width) endIndex--;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004902 return str.substring(startIndex, endIndex);
4903 }
4904
4905 return str.substr(startIndex);
4906};
4907
4908/**
4909 * Get substring at the given start and end column offset.
4910 *
4911 * @param {string} str The string to get substring from.
4912 * @param {integer} start The starting column offset.
4913 * @param {integer} end The ending column offset.
4914 *
4915 * @return {string} The substring.
4916 */
4917lib.wc.substring = function(str, start, end) {
4918 return lib.wc.substr(str, start, end - start);
4919};
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004920lib.resource.add(
4921 'libdot/changelog/version', 'text/plain',
4922 '1.11' +
4923 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004924
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004925lib.resource.add(
4926 'libdot/changelog/date', 'text/plain',
4927 '2017-04-17' +
4928 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004929
4930// SOURCE FILE: hterm/js/hterm.js
4931// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4932// Use of this source code is governed by a BSD-style license that can be
4933// found in the LICENSE file.
4934
4935'use strict';
4936
4937lib.rtdep('lib.Storage');
4938
4939/**
4940 * @fileoverview Declares the hterm.* namespace and some basic shared utilities
4941 * that are too small to deserve dedicated files.
4942 */
4943var hterm = {};
4944
4945/**
4946 * The type of window hosting hterm.
4947 *
4948 * This is set as part of hterm.init(). The value is invalid until
4949 * initialization completes.
4950 */
4951hterm.windowType = null;
4952
4953/**
4954 * Warning message to display in the terminal when browser zoom is enabled.
4955 *
4956 * You can replace it with your own localized message.
4957 */
4958hterm.zoomWarningMessage = 'ZOOM != 100%';
4959
4960/**
4961 * Brief overlay message displayed when text is copied to the clipboard.
4962 *
4963 * By default it is the unicode BLACK SCISSORS character, but you can
4964 * replace it with your own localized message.
4965 *
4966 * This is only displayed when the 'enable-clipboard-notice' preference
4967 * is enabled.
4968 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004969hterm.notifyCopyMessage = '✂';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004970
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004971/**
4972 * Text shown in a desktop notification for the terminal
4973 * bell. \u226a is a unicode EIGHTH NOTE, %(title) will
4974 * be replaced by the terminal title.
4975 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004976hterm.desktopNotificationTitle = '♪ %(title) ♪';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004977
4978/**
4979 * List of known hterm test suites.
4980 *
4981 * A test harness should ensure that they all exist before running.
4982 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -07004983hterm.testDeps = [
4984 'hterm.ScrollPort.Tests', 'hterm.Screen.Tests', 'hterm.Terminal.Tests',
4985 'hterm.VT.Tests', 'hterm.VT.CannedTests'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07004986];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05004987
4988/**
4989 * The hterm init function, registered with lib.registerInit().
4990 *
4991 * This is called during lib.init().
4992 *
4993 * @param {function} onInit The function lib.init() wants us to invoke when
4994 * initialization is complete.
4995 */
4996lib.registerInit('hterm', function(onInit) {
4997 function onWindow(window) {
4998 hterm.windowType = window.type;
4999 setTimeout(onInit, 0);
5000 }
5001
5002 function onTab(tab) {
5003 if (tab && window.chrome) {
5004 chrome.windows.get(tab.windowId, null, onWindow);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005005 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005006 // TODO(rginda): This is where we end up for a v1 app's background page.
5007 // Maybe windowType = 'none' would be more appropriate, or something.
5008 hterm.windowType = 'normal';
5009 setTimeout(onInit, 0);
5010 }
5011 }
5012
5013 if (!hterm.defaultStorage) {
5014 var ary = navigator.userAgent.match(/\sChrome\/(\d\d)/);
5015 var version = ary ? parseInt(ary[1]) : -1;
5016 if (window.chrome && chrome.storage && chrome.storage.sync &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005017 version > 21) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005018 hterm.defaultStorage = new lib.Storage.Chrome(chrome.storage.sync);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005019 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005020 hterm.defaultStorage = new lib.Storage.Local();
5021 }
5022 }
5023
5024 // The chrome.tabs API is not supported in packaged apps, and detecting if
5025 // you're a packaged app is a little awkward.
5026 var isPackagedApp = false;
5027 if (window.chrome && chrome.runtime && chrome.runtime.getManifest) {
5028 var manifest = chrome.runtime.getManifest();
5029 isPackagedApp = manifest.app && manifest.app.background;
5030 }
5031
5032 if (isPackagedApp) {
5033 // Packaged apps are never displayed in browser tabs.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005034 setTimeout(onWindow.bind(null, {type: 'popup'}), 0);
5035 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005036 if (window.chrome && chrome.tabs) {
5037 // The getCurrent method gets the tab that is "currently running", not the
5038 // topmost or focused tab.
5039 chrome.tabs.getCurrent(onTab);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005040 } else {
5041 setTimeout(onWindow.bind(null, {type: 'normal'}), 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005042 }
5043 }
5044});
5045
5046/**
5047 * Return decimal { width, height } for a given dom node.
5048 */
5049hterm.getClientSize = function(dom) {
5050 return dom.getBoundingClientRect();
5051};
5052
5053/**
5054 * Return decimal width for a given dom node.
5055 */
5056hterm.getClientWidth = function(dom) {
5057 return dom.getBoundingClientRect().width;
5058};
5059
5060/**
5061 * Return decimal height for a given dom node.
5062 */
5063hterm.getClientHeight = function(dom) {
5064 return dom.getBoundingClientRect().height;
5065};
5066
5067/**
5068 * Copy the current selection to the system clipboard.
5069 *
5070 * @param {HTMLDocument} The document with the selection to copy.
5071 */
5072hterm.copySelectionToClipboard = function(document) {
5073 try {
5074 document.execCommand('copy');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005075 } catch (firefoxException) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005076 // Ignore this. FF throws an exception if there was an error, even though
5077 // the spec says just return false.
5078 }
5079};
5080
5081/**
5082 * Paste the system clipboard into the element with focus.
5083 *
5084 * @param {HTMLDocument} The document to paste into.
5085 */
5086hterm.pasteFromClipboard = function(document) {
5087 try {
5088 document.execCommand('paste');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005089 } catch (firefoxException) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005090 // Ignore this. FF throws an exception if there was an error, even though
5091 // the spec says just return false.
5092 }
5093};
5094
5095/**
5096 * Constructor for a hterm.Size record.
5097 *
5098 * Instances of this class have public read/write members for width and height.
5099 *
5100 * @param {integer} width The width of this record.
5101 * @param {integer} height The height of this record.
5102 */
5103hterm.Size = function(width, height) {
5104 this.width = width;
5105 this.height = height;
5106};
5107
5108/**
5109 * Adjust the width and height of this record.
5110 *
5111 * @param {integer} width The new width of this record.
5112 * @param {integer} height The new height of this record.
5113 */
5114hterm.Size.prototype.resize = function(width, height) {
5115 this.width = width;
5116 this.height = height;
5117};
5118
5119/**
5120 * Return a copy of this record.
5121 *
5122 * @return {hterm.Size} A new hterm.Size instance with the same width and
5123 * height.
5124 */
5125hterm.Size.prototype.clone = function() {
5126 return new hterm.Size(this.width, this.height);
5127};
5128
5129/**
5130 * Set the height and width of this instance based on another hterm.Size.
5131 *
5132 * @param {hterm.Size} that The object to copy from.
5133 */
5134hterm.Size.prototype.setTo = function(that) {
5135 this.width = that.width;
5136 this.height = that.height;
5137};
5138
5139/**
5140 * Test if another hterm.Size instance is equal to this one.
5141 *
5142 * @param {hterm.Size} that The other hterm.Size instance.
5143 * @return {boolean} True if both instances have the same width/height, false
5144 * otherwise.
5145 */
5146hterm.Size.prototype.equals = function(that) {
5147 return this.width == that.width && this.height == that.height;
5148};
5149
5150/**
5151 * Return a string representation of this instance.
5152 *
5153 * @return {string} A string that identifies the width and height of this
5154 * instance.
5155 */
5156hterm.Size.prototype.toString = function() {
5157 return '[hterm.Size: ' + this.width + ', ' + this.height + ']';
5158};
5159
5160/**
5161 * Constructor for a hterm.RowCol record.
5162 *
5163 * Instances of this class have public read/write members for row and column.
5164 *
5165 * This class includes an 'overflow' bit which is use to indicate that an
5166 * attempt has been made to move the cursor column passed the end of the
5167 * screen. When this happens we leave the cursor column set to the last column
5168 * of the screen but set the overflow bit. In this state cursor movement
5169 * happens normally, but any attempt to print new characters causes a cr/lf
5170 * first.
5171 *
5172 * @param {integer} row The row of this record.
5173 * @param {integer} column The column of this record.
5174 * @param {boolean} opt_overflow Optional boolean indicating that the RowCol
5175 * has overflowed.
5176 */
5177hterm.RowCol = function(row, column, opt_overflow) {
5178 this.row = row;
5179 this.column = column;
5180 this.overflow = !!opt_overflow;
5181};
5182
5183/**
5184 * Adjust the row and column of this record.
5185 *
5186 * @param {integer} row The new row of this record.
5187 * @param {integer} column The new column of this record.
5188 * @param {boolean} opt_overflow Optional boolean indicating that the RowCol
5189 * has overflowed.
5190 */
5191hterm.RowCol.prototype.move = function(row, column, opt_overflow) {
5192 this.row = row;
5193 this.column = column;
5194 this.overflow = !!opt_overflow;
5195};
5196
5197/**
5198 * Return a copy of this record.
5199 *
5200 * @return {hterm.RowCol} A new hterm.RowCol instance with the same row and
5201 * column.
5202 */
5203hterm.RowCol.prototype.clone = function() {
5204 return new hterm.RowCol(this.row, this.column, this.overflow);
5205};
5206
5207/**
5208 * Set the row and column of this instance based on another hterm.RowCol.
5209 *
5210 * @param {hterm.RowCol} that The object to copy from.
5211 */
5212hterm.RowCol.prototype.setTo = function(that) {
5213 this.row = that.row;
5214 this.column = that.column;
5215 this.overflow = that.overflow;
5216};
5217
5218/**
5219 * Test if another hterm.RowCol instance is equal to this one.
5220 *
5221 * @param {hterm.RowCol} that The other hterm.RowCol instance.
5222 * @return {boolean} True if both instances have the same row/column, false
5223 * otherwise.
5224 */
5225hterm.RowCol.prototype.equals = function(that) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005226 return (
5227 this.row == that.row && this.column == that.column &&
5228 this.overflow == that.overflow);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005229};
5230
5231/**
5232 * Return a string representation of this instance.
5233 *
5234 * @return {string} A string that identifies the row and column of this
5235 * instance.
5236 */
5237hterm.RowCol.prototype.toString = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005238 return (
5239 '[hterm.RowCol: ' + this.row + ', ' + this.column + ', ' + this.overflow +
5240 ']');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005241};
5242// SOURCE FILE: hterm/js/hterm_frame.js
5243// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
5244// Use of this source code is governed by a BSD-style license that can be
5245// found in the LICENSE file.
5246
5247'use strict';
5248
5249lib.rtdep('lib.f');
5250
5251/**
5252 * First draft of the interface between the terminal and a third party dialog.
5253 *
5254 * This is rough. It's just the terminal->dialog layer. To complete things
5255 * we'll also need a command->terminal layer. That will have to facilitate
5256 * command->terminal->dialog or direct command->dialog communication.
5257 *
5258 * I imagine this class will change significantly when that happens.
5259 */
5260
5261/**
5262 * Construct a new frame for the given terminal.
5263 *
5264 * @param terminal {hterm.Terminal} The parent terminal object.
5265 * @param url {String} The url to load in the frame.
5266 * @param opt_options {Object} Optional options for the frame. Not implemented.
5267 */
5268hterm.Frame = function(terminal, url, opt_options) {
5269 this.terminal_ = terminal;
5270 this.div_ = terminal.div_;
5271 this.url = url;
5272 this.options = opt_options || {};
5273 this.iframe_ = null;
5274 this.container_ = null;
5275 this.messageChannel_ = null;
5276};
5277
5278/**
5279 * Handle messages from the iframe.
5280 */
5281hterm.Frame.prototype.onMessage_ = function(e) {
5282 if (e.data.name != 'ipc-init-ok') {
5283 console.log('Unknown message from frame:', e.data);
5284 return;
5285 }
5286
5287 this.sendTerminalInfo_();
5288 this.messageChannel_.port1.onmessage = this.onMessage.bind(this);
5289 this.onLoad();
5290};
5291
5292/**
5293 * Clients could override this, I guess.
5294 *
5295 * It doesn't support multiple listeners, but I'm not sure that would make sense
5296 * here. It's probably better to speak directly to our parents.
5297 */
5298hterm.Frame.prototype.onMessage = function() {};
5299
5300/**
5301 * Handle iframe onLoad event.
5302 */
5303hterm.Frame.prototype.onLoad_ = function() {
5304 this.messageChannel_ = new MessageChannel();
5305 this.messageChannel_.port1.onmessage = this.onMessage_.bind(this);
5306 this.messageChannel_.port1.start();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005307 this.iframe_.contentWindow.postMessage(
5308 {name: 'ipc-init', argv: [{messagePort: this.messageChannel_.port2}]},
5309 this.url, [this.messageChannel_.port2]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005310};
5311
5312/**
5313 * Clients may override this.
5314 */
5315hterm.Frame.prototype.onLoad = function() {};
5316
5317/**
5318 * Sends the terminal-info message to the iframe.
5319 */
5320hterm.Frame.prototype.sendTerminalInfo_ = function() {
5321 lib.f.getAcceptLanguages(function(languages) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005322 this.postMessage('terminal-info', [{
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005323 acceptLanguages: languages,
5324 foregroundColor: this.terminal_.getForegroundColor(),
5325 backgroundColor: this.terminal_.getBackgroundColor(),
5326 cursorColor: this.terminal_.getCursorColor(),
5327 fontSize: this.terminal_.getFontSize(),
5328 fontFamily: this.terminal_.getFontFamily(),
5329 baseURL: lib.f.getURL('/')
5330 }]);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005331 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005332};
5333
5334/**
5335 * User clicked the close button on the frame decoration.
5336 */
5337hterm.Frame.prototype.onCloseClicked_ = function() {
5338 this.close();
5339};
5340
5341/**
5342 * Close this frame.
5343 */
5344hterm.Frame.prototype.close = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005345 if (!this.container_ || !this.container_.parentNode) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005346
5347 this.container_.parentNode.removeChild(this.container_);
5348 this.onClose();
5349};
5350
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005351/**
5352 * Clients may override this.
5353 */
5354hterm.Frame.prototype.onClose = function() {};
5355
5356/**
5357 * Send a message to the iframe.
5358 */
5359hterm.Frame.prototype.postMessage = function(name, argv) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005360 if (!this.messageChannel_) throw new Error('Message channel is not set up.');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005361
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005362 this.messageChannel_.port1.postMessage({name: name, argv: argv});
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005363};
5364
5365/**
5366 * Show the UI for this frame.
5367 *
5368 * The iframe src is not loaded until this method is called.
5369 */
5370hterm.Frame.prototype.show = function() {
5371 var self = this;
5372
5373 function opt(name, defaultValue) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005374 if (name in self.options) return self.options[name];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005375
5376 return defaultValue;
5377 }
5378
5379 var self = this;
5380
5381 if (this.container_ && this.container_.parentNode) {
5382 console.error('Frame already visible');
5383 return;
5384 }
5385
5386 var headerHeight = '16px';
5387
5388 var divSize = hterm.getClientSize(this.div_);
5389
5390 var width = opt('width', 640);
5391 var height = opt('height', 480);
5392 var left = (divSize.width - width) / 2;
5393 var top = (divSize.height - height) / 2;
5394
5395 var document = this.terminal_.document_;
5396
5397 var container = this.container_ = document.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005398 container.style.cssText =
5399 ('position: absolute;' +
5400 'display: -webkit-flex;' +
5401 '-webkit-flex-direction: column;' +
5402 'top: 10%;' +
5403 'left: 4%;' +
5404 'width: 90%;' +
5405 'height: 80%;' +
5406 'box-shadow: 0 0 2px ' + this.terminal_.getForegroundColor() + ';' +
5407 'border: 2px ' + this.terminal_.getForegroundColor() + ' solid;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005408
5409 var header = document.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005410 header.style.cssText =
5411 ('display: -webkit-flex;' +
5412 '-webkit-justify-content: flex-end;' +
5413 'height: ' + headerHeight + ';' +
5414 'background-color: ' + this.terminal_.getForegroundColor() + ';' +
5415 'color: ' + this.terminal_.getBackgroundColor() + ';' +
5416 'font-size: 16px;' +
5417 'font-family: ' + this.terminal_.getFontFamily());
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005418 container.appendChild(header);
5419
5420 if (false) {
5421 // No use for the close button.
5422 var button = document.createElement('div');
5423 button.setAttribute('role', 'button');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005424 button.style.cssText =
5425 ('margin-top: -3px;' +
5426 'margin-right: 3px;' +
5427 'cursor: pointer;');
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005428 button.textContent = '⨯';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005429 button.addEventListener('click', this.onCloseClicked_.bind(this));
5430 header.appendChild(button);
5431 }
5432
5433 var iframe = this.iframe_ = document.createElement('iframe');
5434 iframe.onload = this.onLoad_.bind(this);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005435 iframe.style.cssText =
5436 ('display: -webkit-flex;' +
5437 '-webkit-flex: 1;' +
5438 'width: 100%');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005439 iframe.setAttribute('src', this.url);
5440 iframe.setAttribute('seamless', true);
5441 container.appendChild(iframe);
5442
5443 this.div_.appendChild(container);
5444};
5445// SOURCE FILE: hterm/js/hterm_keyboard.js
5446// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
5447// Use of this source code is governed by a BSD-style license that can be
5448// found in the LICENSE file.
5449
5450'use strict';
5451
5452lib.rtdep('hterm.Keyboard.KeyMap');
5453
5454/**
5455 * Keyboard handler.
5456 *
5457 * Consumes onKey* events and invokes onVTKeystroke on the associated
5458 * hterm.Terminal object.
5459 *
5460 * See also: [XTERM] as referenced in vt.js.
5461 *
5462 * @param {hterm.Terminal} The Terminal object associated with this keyboard.
5463 */
5464hterm.Keyboard = function(terminal) {
5465 // The parent vt interpreter.
5466 this.terminal = terminal;
5467
5468 // The element we're currently capturing keyboard events for.
5469 this.keyboardElement_ = null;
5470
5471 // The event handlers we are interested in, and their bound callbacks, saved
5472 // so they can be uninstalled with removeEventListener, when required.
5473 this.handlers_ = [
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005474 ['focusout', this.onFocusOut_.bind(this)],
5475 ['keydown', this.onKeyDown_.bind(this)],
5476 ['keypress', this.onKeyPress_.bind(this)],
5477 ['keyup', this.onKeyUp_.bind(this)],
5478 ['textInput', this.onTextInput_.bind(this)]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005479 ];
5480
5481 /**
5482 * The current key map.
5483 */
5484 this.keyMap = new hterm.Keyboard.KeyMap(this);
5485
5486 this.bindings = new hterm.Keyboard.Bindings(this);
5487
5488 /**
5489 * none: Disable any AltGr related munging.
5490 * ctrl-alt: Assume Ctrl+Alt means AltGr.
5491 * left-alt: Assume left Alt means AltGr.
5492 * right-alt: Assume right Alt means AltGr.
5493 */
5494 this.altGrMode = 'none';
5495
5496 /**
5497 * If true, Shift-Insert will fall through to the browser as a paste.
5498 * If false, the keystroke will be sent to the host.
5499 */
5500 this.shiftInsertPaste = true;
5501
5502 /**
5503 * If true, home/end will control the terminal scrollbar and shift home/end
5504 * will send the VT keycodes. If false then home/end sends VT codes and
5505 * shift home/end scrolls.
5506 */
5507 this.homeKeysScroll = false;
5508
5509 /**
5510 * Same as above, except for page up/page down.
5511 */
5512 this.pageKeysScroll = false;
5513
5514 /**
5515 * If true, Ctrl-Plus/Minus/Zero controls zoom.
5516 * If false, Ctrl-Shift-Plus/Minus/Zero controls zoom, Ctrl-Minus sends ^_,
5517 * Ctrl-Plus/Zero do nothing.
5518 */
5519 this.ctrlPlusMinusZeroZoom = true;
5520
5521 /**
5522 * Ctrl+C copies if true, sends ^C to host if false.
5523 * Ctrl+Shift+C sends ^C to host if true, copies if false.
5524 */
5525 this.ctrlCCopy = false;
5526
5527 /**
5528 * Ctrl+V pastes if true, sends ^V to host if false.
5529 * Ctrl+Shift+V sends ^V to host if true, pastes if false.
5530 */
5531 this.ctrlVPaste = false;
5532
5533 /**
5534 * Enable/disable application keypad.
5535 *
5536 * This changes the way numeric keys are sent from the keyboard.
5537 */
5538 this.applicationKeypad = false;
5539
5540 /**
5541 * Enable/disable the application cursor mode.
5542 *
5543 * This changes the way cursor keys are sent from the keyboard.
5544 */
5545 this.applicationCursor = false;
5546
5547 /**
5548 * If true, the backspace should send BS ('\x08', aka ^H). Otherwise
5549 * the backspace key should send '\x7f'.
5550 */
5551 this.backspaceSendsBackspace = false;
5552
5553 /**
5554 * The encoding method for data sent to the host.
5555 */
5556 this.characterEncoding = 'utf-8';
5557
5558 /**
5559 * Set whether the meta key sends a leading escape or not.
5560 */
5561 this.metaSendsEscape = true;
5562
5563 /**
5564 * Set whether meta-V gets passed to host.
5565 */
5566 this.passMetaV = true;
5567
5568 /**
5569 * Controls how the alt key is handled.
5570 *
5571 * escape....... Send an ESC prefix.
5572 * 8-bit........ Add 128 to the unshifted character as in xterm.
5573 * browser-key.. Wait for the keypress event and see what the browser says.
5574 * (This won't work well on platforms where the browser
5575 * performs a default action for some alt sequences.)
5576 *
5577 * This setting only matters when alt is distinct from meta (altIsMeta is
5578 * false.)
5579 */
5580 this.altSendsWhat = 'escape';
5581
5582 /**
5583 * Set whether the alt key acts as a meta key, instead of producing 8-bit
5584 * characters.
5585 *
5586 * True to enable, false to disable, null to autodetect based on platform.
5587 */
5588 this.altIsMeta = false;
5589
5590 /**
5591 * If true, tries to detect DEL key events that are from alt-backspace on
5592 * Chrome OS vs from a true DEL key press.
5593 *
5594 * Background: At the time of writing, on Chrome OS, alt-backspace is mapped
5595 * to DEL. Some users may be happy with this, but others may be frustrated
5596 * that it's impossible to do meta-backspace. If the user enables this pref,
5597 * we use a trick to tell a true DEL keypress from alt-backspace: on
5598 * alt-backspace, we will see the alt key go down, then get a DEL keystroke
5599 * that indicates that alt is not pressed. See https://crbug.com/174410 .
5600 */
5601 this.altBackspaceIsMetaBackspace = false;
5602
5603 /**
5604 * Used to keep track of the current alt-key state, which is necessary for
5605 * the altBackspaceIsMetaBackspace preference above and for the altGrMode
5606 * preference. This is a bitmap with where bit positions correspond to the
5607 * "location" property of the key event.
5608 */
5609 this.altKeyPressed = 0;
5610
5611 /**
5612 * If true, Chrome OS media keys will be mapped to their F-key equivalent.
5613 * E.g. "Back" will be mapped to F1. If false, Chrome will handle the keys.
5614 */
5615 this.mediaKeysAreFKeys = false;
5616
5617 /**
5618 * Holds the previous setting of altSendsWhat when DECSET 1039 is used. When
5619 * DECRST 1039 is used, altSendsWhat is changed back to this and this is
5620 * nulled out.
5621 */
5622 this.previousAltSendsWhat_ = null;
5623};
5624
5625/**
5626 * Special handling for keyCodes in a keyboard layout.
5627 */
5628hterm.Keyboard.KeyActions = {
5629 /**
5630 * Call preventDefault and stopPropagation for this key event and nothing
5631 * else.
5632 */
5633 CANCEL: new String('CANCEL'),
5634
5635 /**
5636 * This performs the default terminal action for the key. If used in the
5637 * 'normal' action and the the keystroke represents a printable key, the
5638 * character will be sent to the host. If used in one of the modifier
5639 * actions, the terminal will perform the normal action after (possibly)
5640 * altering it.
5641 *
5642 * - If the normal sequence starts with CSI, the sequence will be adjusted
5643 * to include the modifier parameter as described in [XTERM] in the final
5644 * table of the "PC-Style Function Keys" section.
5645 *
5646 * - If the control key is down and the key represents a printable character,
5647 * and the uppercase version of the unshifted keycap is between
5648 * 64 (ASCII '@') and 95 (ASCII '_'), then the uppercase version of the
5649 * unshifted keycap minus 64 is sent. This makes '^@' send '\x00' and
5650 * '^_' send '\x1f'. (Note that one higher that 0x1f is 0x20, which is
5651 * the first printable ASCII value.)
5652 *
5653 * - If the alt key is down and the key represents a printable character then
5654 * the value of the character is shifted up by 128.
5655 *
5656 * - If meta is down and configured to send an escape, '\x1b' will be sent
5657 * before the normal action is performed.
5658 */
5659 DEFAULT: new String('DEFAULT'),
5660
5661 /**
5662 * Causes the terminal to opt out of handling the key event, instead letting
5663 * the browser deal with it.
5664 */
5665 PASS: new String('PASS'),
5666
5667 /**
5668 * Insert the first or second character of the keyCap, based on e.shiftKey.
5669 * The key will be handled in onKeyDown, and e.preventDefault() will be
5670 * called.
5671 *
5672 * It is useful for a modified key action, where it essentially strips the
5673 * modifier while preventing the browser from reacting to the key.
5674 */
5675 STRIP: new String('STRIP')
5676};
5677
5678/**
5679 * Encode a string according to the 'send-encoding' preference.
5680 */
5681hterm.Keyboard.prototype.encode = function(str) {
5682 if (this.characterEncoding == 'utf-8')
5683 return this.terminal.vt.encodeUTF8(str);
5684
5685 return str;
5686};
5687
5688/**
5689 * Capture keyboard events sent to the associated element.
5690 *
5691 * This enables the keyboard. Captured events are consumed by this class
5692 * and will not perform their default action or bubble to other elements.
5693 *
5694 * Passing a null element will uninstall the keyboard handlers.
5695 *
5696 * @param {HTMLElement} element The element whose events should be captured, or
5697 * null to disable the keyboard.
5698 */
5699hterm.Keyboard.prototype.installKeyboard = function(element) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005700 if (element == this.keyboardElement_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005701
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005702 if (element && this.keyboardElement_) this.installKeyboard(null);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005703
5704 for (var i = 0; i < this.handlers_.length; i++) {
5705 var handler = this.handlers_[i];
5706 if (element) {
5707 element.addEventListener(handler[0], handler[1]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005708 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005709 this.keyboardElement_.removeEventListener(handler[0], handler[1]);
5710 }
5711 }
5712
5713 this.keyboardElement_ = element;
5714};
5715
5716/**
5717 * Disable keyboard event capture.
5718 *
5719 * This will allow the browser to process key events normally.
5720 */
5721hterm.Keyboard.prototype.uninstallKeyboard = function() {
5722 this.installKeyboard(null);
5723};
5724
5725/**
5726 * Handle onTextInput events.
5727 *
5728 * We're not actually supposed to get these, but we do on the Mac in the case
5729 * where a third party app sends synthetic keystrokes to Chrome.
5730 */
5731hterm.Keyboard.prototype.onTextInput_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005732 if (!e.data) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005733
5734 e.data.split('').forEach(this.terminal.onVTKeystroke.bind(this.terminal));
5735};
5736
5737/**
5738 * Handle onKeyPress events.
5739 */
5740hterm.Keyboard.prototype.onKeyPress_ = function(e) {
5741 var code;
5742
5743 var key = String.fromCharCode(e.which);
5744 var lowerKey = key.toLowerCase();
5745 if ((e.ctrlKey || e.metaKey) && (lowerKey == 'c' || lowerKey == 'v')) {
5746 // On FF the key press (not key down) event gets fired for copy/paste.
5747 // Let it fall through for the default browser behavior.
5748 return;
5749 }
5750
5751 if (e.altKey && this.altSendsWhat == 'browser-key' && e.charCode == 0) {
5752 // If we got here because we were expecting the browser to handle an
5753 // alt sequence but it didn't do it, then we might be on an OS without
5754 // an enabled IME system. In that case we fall back to xterm-like
5755 // behavior.
5756 //
5757 // This happens here only as a fallback. Typically these platforms should
5758 // set altSendsWhat to either 'escape' or '8-bit'.
5759 var ch = String.fromCharCode(e.keyCode);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005760 if (!e.shiftKey) ch = ch.toLowerCase();
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005761 code = ch.charCodeAt(0) + 128;
5762
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005763 } else if (e.charCode >= 32) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005764 ch = e.charCode;
5765 }
5766
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005767 if (ch) this.terminal.onVTKeystroke(String.fromCharCode(ch));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005768
5769 e.preventDefault();
5770 e.stopPropagation();
5771};
5772
5773/**
5774 * Prevent default handling for non-ctrl-shifted event.
5775 *
5776 * When combined with Chrome permission 'app.window.fullscreen.overrideEsc',
5777 * and called for both key down and key up events,
5778 * the ESC key remains usable within fullscreen Chrome app windows.
5779 */
5780hterm.Keyboard.prototype.preventChromeAppNonCtrlShiftDefault_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005781 if (!window.chrome || !window.chrome.app || !window.chrome.app.window) return;
5782 if (!e.ctrlKey || !e.shiftKey) e.preventDefault();
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005783};
5784
5785hterm.Keyboard.prototype.onFocusOut_ = function(e) {
5786 this.altKeyPressed = 0;
5787};
5788
5789hterm.Keyboard.prototype.onKeyUp_ = function(e) {
5790 if (e.keyCode == 18)
5791 this.altKeyPressed = this.altKeyPressed & ~(1 << (e.location - 1));
5792
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005793 if (e.keyCode == 27) this.preventChromeAppNonCtrlShiftDefault_(e);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005794};
5795
5796/**
5797 * Handle onKeyDown events.
5798 */
5799hterm.Keyboard.prototype.onKeyDown_ = function(e) {
5800 if (e.keyCode == 18)
5801 this.altKeyPressed = this.altKeyPressed | (1 << (e.location - 1));
5802
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005803 if (e.keyCode == 27) this.preventChromeAppNonCtrlShiftDefault_(e);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005804
5805 var keyDef = this.keyMap.keyDefs[e.keyCode];
5806 if (!keyDef) {
5807 console.warn('No definition for keyCode: ' + e.keyCode);
5808 return;
5809 }
5810
5811 // The type of action we're going to use.
5812 var resolvedActionType = null;
5813
5814 var self = this;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005815
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005816 function getAction(name) {
5817 // Get the key action for the given action name. If the action is a
5818 // function, dispatch it. If the action defers to the normal action,
5819 // resolve that instead.
5820
5821 resolvedActionType = name;
5822
5823 var action = keyDef[name];
5824 if (typeof action == 'function')
5825 action = action.apply(self.keyMap, [e, keyDef]);
5826
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005827 if (action === DEFAULT && name != 'normal') action = getAction('normal');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005828
5829 return action;
5830 }
5831
5832 // Note that we use the triple-equals ('===') operator to test equality for
5833 // these constants, in order to distinguish usage of the constant from usage
5834 // of a literal string that happens to contain the same bytes.
5835 var CANCEL = hterm.Keyboard.KeyActions.CANCEL;
5836 var DEFAULT = hterm.Keyboard.KeyActions.DEFAULT;
5837 var PASS = hterm.Keyboard.KeyActions.PASS;
5838 var STRIP = hterm.Keyboard.KeyActions.STRIP;
5839
5840 var control = e.ctrlKey;
5841 var alt = this.altIsMeta ? false : e.altKey;
5842 var meta = this.altIsMeta ? (e.altKey || e.metaKey) : e.metaKey;
5843
5844 // In the key-map, we surround the keyCap for non-printables in "[...]"
5845 var isPrintable = !(/^\[\w+\]$/.test(keyDef.keyCap));
5846
5847 switch (this.altGrMode) {
5848 case 'ctrl-alt':
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005849 if (isPrintable && control && alt) {
5850 // ctrl-alt-printable means altGr. We clear out the control and
5851 // alt modifiers and wait to see the charCode in the keydown event.
5852 control = false;
5853 alt = false;
5854 }
5855 break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005856
5857 case 'right-alt':
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005858 if (isPrintable && (this.terminal.keyboard.altKeyPressed & 2)) {
5859 control = false;
5860 alt = false;
5861 }
5862 break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005863
5864 case 'left-alt':
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07005865 if (isPrintable && (this.terminal.keyboard.altKeyPressed & 1)) {
5866 control = false;
5867 alt = false;
5868 }
5869 break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005870 }
5871
5872 var action;
5873
5874 if (control) {
5875 action = getAction('control');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005876 } else if (alt) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005877 action = getAction('alt');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005878 } else if (meta) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005879 action = getAction('meta');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005880 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005881 action = getAction('normal');
5882 }
5883
5884 // If e.maskShiftKey was set (during getAction) it means the shift key is
5885 // already accounted for in the action, and we should not act on it any
5886 // further. This is currently only used for Ctrl-Shift-Tab, which should send
5887 // "CSI Z", not "CSI 1 ; 2 Z".
5888 var shift = !e.maskShiftKey && e.shiftKey;
5889
5890 var keyDown = {
5891 keyCode: e.keyCode,
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005892 shift: e.shiftKey, // not `var shift` from above.
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005893 ctrl: control,
5894 alt: alt,
5895 meta: meta
5896 };
5897
5898 var binding = this.bindings.getBinding(keyDown);
5899
5900 if (binding) {
5901 // Clear out the modifier bits so we don't try to munge the sequence
5902 // further.
5903 shift = control = alt = meta = false;
5904 resolvedActionType = 'normal';
5905 action = binding.action;
5906
5907 if (typeof action == 'function')
5908 action = action.call(this, this.terminal, keyDown);
5909 }
5910
5911 if (alt && this.altSendsWhat == 'browser-key' && action == DEFAULT) {
5912 // When altSendsWhat is 'browser-key', we wait for the keypress event.
5913 // In keypress, the browser should have set the event.charCode to the
5914 // appropriate character.
5915 // TODO(rginda): Character compositions will need some black magic.
5916 action = PASS;
5917 }
5918
5919 if (action === PASS || (action === DEFAULT && !(control || alt || meta))) {
5920 // If this key is supposed to be handled by the browser, or it is an
5921 // unmodified key with the default action, then exit this event handler.
5922 // If it's an unmodified key, it'll be handled in onKeyPress where we
5923 // can tell for sure which ASCII code to insert.
5924 //
5925 // This block needs to come before the STRIP test, otherwise we'll strip
5926 // the modifier and think it's ok to let the browser handle the keypress.
5927 // The browser won't know we're trying to ignore the modifiers and might
5928 // perform some default action.
5929 return;
5930 }
5931
5932 if (action === STRIP) {
5933 alt = control = false;
5934 action = keyDef.normal;
5935 if (typeof action == 'function')
5936 action = action.apply(this.keyMap, [e, keyDef]);
5937
5938 if (action == DEFAULT && keyDef.keyCap.length == 2)
5939 action = keyDef.keyCap.substr((shift ? 1 : 0), 1);
5940 }
5941
5942 e.preventDefault();
5943 e.stopPropagation();
5944
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005945 if (action === CANCEL) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005946
5947 if (action !== DEFAULT && typeof action != 'string') {
5948 console.warn('Invalid action: ' + JSON.stringify(action));
5949 return;
5950 }
5951
5952 // Strip the modifier that is associated with the action, since we assume that
5953 // modifier has already been accounted for in the action.
5954 if (resolvedActionType == 'control') {
5955 control = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005956 } else if (resolvedActionType == 'alt') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005957 alt = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005958 } else if (resolvedActionType == 'meta') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005959 meta = false;
5960 }
5961
5962 if (action.substr(0, 2) == '\x1b[' && (alt || control || shift)) {
5963 // The action is an escape sequence that and it was triggered in the
5964 // presence of a keyboard modifier, we may need to alter the action to
5965 // include the modifier before sending it.
5966
5967 var mod;
5968
5969 if (shift && !(alt || control)) {
5970 mod = ';2';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005971 } else if (alt && !(shift || control)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005972 mod = ';3';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005973 } else if (shift && alt && !control) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005974 mod = ';4';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005975 } else if (control && !(shift || alt)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005976 mod = ';5';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005977 } else if (shift && control && !alt) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005978 mod = ';6';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005979 } else if (alt && control && !shift) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005980 mod = ';7';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005981 } else if (shift && alt && control) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005982 mod = ';8';
5983 }
5984
5985 if (action.length == 3) {
5986 // Some of the CSI sequences have zero parameters unless modified.
5987 action = '\x1b[1' + mod + action.substr(2, 1);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005988 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005989 // Others always have at least one parameter.
5990 action = action.substr(0, action.length - 1) + mod +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005991 action.substr(action.length - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005992 }
5993
Andrew Geisslerd27bb132018-05-24 11:07:27 -07005994 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05005995 if (action === DEFAULT) {
5996 action = keyDef.keyCap.substr((shift ? 1 : 0), 1);
5997
5998 if (control) {
5999 var unshifted = keyDef.keyCap.substr(0, 1);
6000 var code = unshifted.charCodeAt(0);
6001 if (code >= 64 && code <= 95) {
6002 action = String.fromCharCode(code - 64);
6003 }
6004 }
6005 }
6006
6007 if (alt && this.altSendsWhat == '8-bit' && action.length == 1) {
6008 var code = action.charCodeAt(0) + 128;
6009 action = String.fromCharCode(code);
6010 }
6011
6012 // We respect alt/metaSendsEscape even if the keymap action was a literal
6013 // string. Otherwise, every overridden alt/meta action would have to
6014 // check alt/metaSendsEscape.
6015 if ((alt && this.altSendsWhat == 'escape') ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006016 (meta && this.metaSendsEscape)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006017 action = '\x1b' + action;
6018 }
6019 }
6020
6021 this.terminal.onVTKeystroke(action);
6022};
6023// SOURCE FILE: hterm/js/hterm_keyboard_bindings.js
6024// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
6025// Use of this source code is governed by a BSD-style license that can be
6026// found in the LICENSE file.
6027
6028'use strict';
6029
6030/**
6031 * A mapping from hterm.Keyboard.KeyPattern to an action.
6032 *
6033 * TODO(rginda): For now this bindings code is only used for user overrides.
6034 * hterm.Keyboard.KeyMap still handles all of the built-in key mappings.
6035 * It'd be nice if we migrated that over to be hterm.Keyboard.Bindings based.
6036 */
6037hterm.Keyboard.Bindings = function() {
6038 this.bindings_ = {};
6039};
6040
6041/**
6042 * Remove all bindings.
6043 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006044hterm.Keyboard.Bindings.prototype.clear = function() {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006045 this.bindings_ = {};
6046};
6047
6048/**
6049 * Add a new binding.
6050 *
6051 * Internal API that assumes parsed objects as inputs.
6052 * See the public addBinding for more details.
6053 *
6054 * @param {hterm.Keyboard.KeyPattern} keyPattern
6055 * @param {string|function|hterm.Keyboard.KeyAction} action
6056 */
6057hterm.Keyboard.Bindings.prototype.addBinding_ = function(keyPattern, action) {
6058 var binding = null;
6059 var list = this.bindings_[keyPattern.keyCode];
6060 if (list) {
6061 for (var i = 0; i < list.length; i++) {
6062 if (list[i].keyPattern.matchKeyPattern(keyPattern)) {
6063 binding = list[i];
6064 break;
6065 }
6066 }
6067 }
6068
6069 if (binding) {
6070 binding.action = action;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006071 } else {
6072 binding = {keyPattern: keyPattern, action: action};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006073
6074 if (!list) {
6075 this.bindings_[keyPattern.keyCode] = [binding];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006076 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006077 this.bindings_[keyPattern.keyCode].push(binding);
6078
6079 list.sort(function(a, b) {
6080 return hterm.Keyboard.KeyPattern.sortCompare(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006081 a.keyPattern, b.keyPattern);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006082 });
6083 }
6084 }
6085};
6086
6087/**
6088 * Add a new binding.
6089 *
6090 * If a binding for the keyPattern already exists it will be overridden.
6091 *
6092 * More specific keyPatterns take precedence over those with wildcards. Given
6093 * bindings for "Ctrl-A" and "Ctrl-*-A", and a "Ctrl-A" keydown, the "Ctrl-A"
6094 * binding will match even if "Ctrl-*-A" was created last.
6095 *
6096 * If action is a string, it will be passed through hterm.Parser.parseKeyAction.
6097 *
6098 * For example:
6099 * // Will replace Ctrl-P keystrokes with the string "hiya!".
6100 * addBinding('Ctrl-P', "'hiya!'");
6101 * // Will cancel the keystroke entirely (make it do nothing).
6102 * addBinding('Alt-D', hterm.Keyboard.KeyActions.CANCEL);
6103 * // Will execute the code and return the action.
6104 * addBinding('Ctrl-T', function() {
6105 * console.log('Got a T!');
6106 * return hterm.Keyboard.KeyActions.PASS;
6107 * });
6108 *
6109 * @param {string|hterm.Keyboard.KeyPattern} keyPattern
6110 * @param {string|function|hterm.Keyboard.KeyAction} action
6111 */
6112hterm.Keyboard.Bindings.prototype.addBinding = function(key, action) {
6113 // If we're given a hterm.Keyboard.KeyPattern object, pass it down.
6114 if (typeof key != 'string') {
6115 this.addBinding_(key, action);
6116 return;
6117 }
6118
6119 // Here we treat key as a string.
6120 var p = new hterm.Parser();
6121
6122 p.reset(key);
6123 var sequence;
6124
6125 try {
6126 sequence = p.parseKeySequence();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006127 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006128 console.error(ex);
6129 return;
6130 }
6131
6132 if (!p.isComplete()) {
6133 console.error(p.error('Expected end of sequence: ' + sequence));
6134 return;
6135 }
6136
6137 // If action is a string, parse it. Otherwise assume it's callable.
6138 if (typeof action == 'string') {
6139 p.reset(action);
6140 try {
6141 action = p.parseKeyAction();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006142 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006143 console.error(ex);
6144 return;
6145 }
6146 }
6147
6148 if (!p.isComplete()) {
6149 console.error(p.error('Expected end of sequence: ' + sequence));
6150 return;
6151 }
6152
6153 this.addBinding_(new hterm.Keyboard.KeyPattern(sequence), action);
6154};
6155
6156/**
6157 * Add multiple bindings at a time using a map of {string: string, ...}
6158 *
6159 * This uses hterm.Parser to parse the maps key into KeyPatterns, and the
6160 * map values into {string|function|KeyAction}.
6161 *
6162 * For example:
6163 * {
6164 * // Will replace Ctrl-P keystrokes with the string "hiya!".
6165 * 'Ctrl-P': "'hiya!'",
6166 * // Will cancel the keystroke entirely (make it do nothing).
6167 * 'Alt-D': hterm.Keyboard.KeyActions.CANCEL,
6168 * }
6169 *
6170 * @param {Object} map
6171 */
6172hterm.Keyboard.Bindings.prototype.addBindings = function(map) {
6173 for (var key in map) {
6174 this.addBinding(key, map[key]);
6175 }
6176};
6177
6178/**
6179 * Return the binding that is the best match for the given keyDown record,
6180 * or null if there is no match.
6181 *
6182 * @param {Object} keyDown An object with a keyCode property and zero or
6183 * more boolean properties representing key modifiers. These property names
6184 * must match those defined in hterm.Keyboard.KeyPattern.modifiers.
6185 */
6186hterm.Keyboard.Bindings.prototype.getBinding = function(keyDown) {
6187 var list = this.bindings_[keyDown.keyCode];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006188 if (!list) return null;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006189
6190 for (var i = 0; i < list.length; i++) {
6191 var binding = list[i];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006192 if (binding.keyPattern.matchKeyDown(keyDown)) return binding;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006193 }
6194
6195 return null;
6196};
6197// SOURCE FILE: hterm/js/hterm_keyboard_keymap.js
6198// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
6199// Use of this source code is governed by a BSD-style license that can be
6200// found in the LICENSE file.
6201
6202'use strict';
6203
6204lib.rtdep('hterm.Keyboard.KeyActions');
6205
6206/**
6207 * The default key map for hterm.
6208 *
6209 * Contains a mapping of keyCodes to keyDefs (aka key definitions). The key
6210 * definition tells the hterm.Keyboard class how to handle keycodes.
6211 *
6212 * This should work for most cases, as the printable characters get handled
6213 * in the keypress event. In that case, even if the keycap is wrong in the
6214 * key map, the correct character should be sent.
6215 *
6216 * Different layouts, such as Dvorak should work with this keymap, as those
6217 * layouts typically move keycodes around on the keyboard without disturbing
6218 * the actual keycaps.
6219 *
6220 * There may be issues with control keys on non-US keyboards or with keyboards
6221 * that very significantly from the expectations here, in which case we may
6222 * have to invent new key maps.
6223 *
6224 * The sequences defined in this key map come from [XTERM] as referenced in
6225 * vt.js, starting with the section titled "Alt and Meta Keys".
6226 */
6227hterm.Keyboard.KeyMap = function(keyboard) {
6228 this.keyboard = keyboard;
6229 this.keyDefs = {};
6230 this.reset();
6231};
6232
6233/**
6234 * Add a single key definition.
6235 *
6236 * The definition is a hash containing the following keys: 'keyCap', 'normal',
6237 * 'control', and 'alt'.
6238 *
6239 * - keyCap is a string identifying the key. For printable
6240 * keys, the key cap should be exactly two characters, starting with the
6241 * unshifted version. For example, 'aA', 'bB', '1!' and '=+'. For
6242 * non-printable the key cap should be surrounded in square braces, as in
6243 * '[INS]', '[LEFT]'. By convention, non-printable keycaps are in uppercase
6244 * but this is not a strict requirement.
6245 *
6246 * - Normal is the action that should be performed when they key is pressed
6247 * in the absence of any modifier. See below for the supported actions.
6248 *
6249 * - Control is the action that should be performed when they key is pressed
6250 * along with the control modifier. See below for the supported actions.
6251 *
6252 * - Alt is the action that should be performed when they key is pressed
6253 * along with the alt modifier. See below for the supported actions.
6254 *
6255 * - Meta is the action that should be performed when they key is pressed
6256 * along with the meta modifier. See below for the supported actions.
6257 *
6258 * Actions can be one of the hterm.Keyboard.KeyActions as documented below,
6259 * a literal string, or an array. If the action is a literal string then
6260 * the string is sent directly to the host. If the action is an array it
6261 * is taken to be an escape sequence that may be altered by modifier keys.
6262 * The second-to-last element of the array will be overwritten with the
6263 * state of the modifier keys, as specified in the final table of "PC-Style
6264 * Function Keys" from [XTERM].
6265 */
6266hterm.Keyboard.KeyMap.prototype.addKeyDef = function(keyCode, def) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006267 if (keyCode in this.keyDefs) console.warn('Duplicate keyCode: ' + keyCode);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006268
6269 this.keyDefs[keyCode] = def;
6270};
6271
6272/**
6273 * Add multiple key definitions in a single call.
6274 *
6275 * This function takes the key definitions as variable argument list. Each
6276 * argument is the key definition specified as an array.
6277 *
6278 * (If the function took everything as one big hash we couldn't detect
6279 * duplicates, and there would be a lot more typing involved.)
6280 *
6281 * Each key definition should have 6 elements: (keyCode, keyCap, normal action,
6282 * control action, alt action and meta action). See KeyMap.addKeyDef for the
6283 * meaning of these elements.
6284 */
6285hterm.Keyboard.KeyMap.prototype.addKeyDefs = function(var_args) {
6286 for (var i = 0; i < arguments.length; i++) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006287 this.addKeyDef(arguments[i][0], {
6288 keyCap: arguments[i][1],
6289 normal: arguments[i][2],
6290 control: arguments[i][3],
6291 alt: arguments[i][4],
6292 meta: arguments[i][5]
6293 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006294 }
6295};
6296
6297/**
6298 * Set up the default state for this keymap.
6299 */
6300hterm.Keyboard.KeyMap.prototype.reset = function() {
6301 this.keyDefs = {};
6302
6303 var self = this;
6304
6305 // This function is used by the "macro" functions below. It makes it
6306 // possible to use the call() macro as an argument to any other macro.
6307 function resolve(action, e, k) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006308 if (typeof action == 'function') return action.apply(self, [e, k]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006309
6310 return action;
6311 }
6312
6313 // If not application keypad a, else b. The keys that care about
6314 // application keypad ignore it when the key is modified.
6315 function ak(a, b) {
6316 return function(e, k) {
6317 var action = (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006318 !self.keyboard.applicationKeypad) ?
6319 a :
6320 b;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006321 return resolve(action, e, k);
6322 };
6323 }
6324
6325 // If mod or not application cursor a, else b. The keys that care about
6326 // application cursor ignore it when the key is modified.
6327 function ac(a, b) {
6328 return function(e, k) {
6329 var action = (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006330 !self.keyboard.applicationCursor) ?
6331 a :
6332 b;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006333 return resolve(action, e, k);
6334 };
6335 }
6336
6337 // If not backspace-sends-backspace keypad a, else b.
6338 function bs(a, b) {
6339 return function(e, k) {
6340 var action = !self.keyboard.backspaceSendsBackspace ? a : b;
6341 return resolve(action, e, k);
6342 };
6343 }
6344
6345 // If not e.shiftKey a, else b.
6346 function sh(a, b) {
6347 return function(e, k) {
6348 var action = !e.shiftKey ? a : b;
6349 e.maskShiftKey = true;
6350 return resolve(action, e, k);
6351 };
6352 }
6353
6354 // If not e.altKey a, else b.
6355 function alt(a, b) {
6356 return function(e, k) {
6357 var action = !e.altKey ? a : b;
6358 return resolve(action, e, k);
6359 };
6360 }
6361
6362 // If no modifiers a, else b.
6363 function mod(a, b) {
6364 return function(e, k) {
6365 var action = !(e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) ? a : b;
6366 return resolve(action, e, k);
6367 };
6368 }
6369
6370 // Compute a control character for a given character.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006371 function ctl(ch) {
6372 return String.fromCharCode(ch.charCodeAt(0) - 64);
6373 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006374
6375 // Call a method on the keymap instance.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006376 function c(m) {
6377 return function(e, k) {
6378 return this[m](e, k);
6379 }
6380 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006381
6382 // Ignore if not trapping media keys.
6383 function med(fn) {
6384 return function(e, k) {
6385 if (!self.keyboard.mediaKeysAreFKeys) {
6386 // Block Back, Forward, and Reload keys to avoid navigating away from
6387 // the current page.
6388 return (e.keyCode == 166 || e.keyCode == 167 || e.keyCode == 168) ?
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006389 hterm.Keyboard.KeyActions.CANCEL :
6390 hterm.Keyboard.KeyActions.PASS;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006391 }
6392 return resolve(fn, e, k);
6393 };
6394 }
6395
6396 var ESC = '\x1b';
6397 var CSI = '\x1b[';
6398 var SS3 = '\x1bO';
6399
6400 var CANCEL = hterm.Keyboard.KeyActions.CANCEL;
6401 var DEFAULT = hterm.Keyboard.KeyActions.DEFAULT;
6402 var PASS = hterm.Keyboard.KeyActions.PASS;
6403 var STRIP = hterm.Keyboard.KeyActions.STRIP;
6404
6405 this.addKeyDefs(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006406 // These fields are: [keycode, keycap, normal, control, alt, meta]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006407
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006408 // The browser sends the keycode 0 for some keys. We'll just assume it's
6409 // going to do the right thing by default for those keys.
6410 [0, '[UNKNOWN]', PASS, PASS, PASS, PASS],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006411
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006412 // First row.
6413 [27, '[ESC]', ESC, DEFAULT, DEFAULT, DEFAULT],
6414 [112, '[F1]', mod(SS3 + 'P', CSI + 'P'), DEFAULT, CSI + '23~', DEFAULT],
6415 [113, '[F2]', mod(SS3 + 'Q', CSI + 'Q'), DEFAULT, CSI + '24~', DEFAULT],
6416 [114, '[F3]', mod(SS3 + 'R', CSI + 'R'), DEFAULT, CSI + '25~', DEFAULT],
6417 [115, '[F4]', mod(SS3 + 'S', CSI + 'S'), DEFAULT, CSI + '26~', DEFAULT],
6418 [116, '[F5]', CSI + '15~', DEFAULT, CSI + '28~', DEFAULT],
6419 [117, '[F6]', CSI + '17~', DEFAULT, CSI + '29~', DEFAULT],
6420 [118, '[F7]', CSI + '18~', DEFAULT, CSI + '31~', DEFAULT],
6421 [119, '[F8]', CSI + '19~', DEFAULT, CSI + '32~', DEFAULT],
6422 [120, '[F9]', CSI + '20~', DEFAULT, CSI + '33~', DEFAULT],
6423 [121, '[F10]', CSI + '21~', DEFAULT, CSI + '34~', DEFAULT],
6424 [122, '[F11]', CSI + '23~', DEFAULT, CSI + '42~', DEFAULT],
6425 [123, '[F12]', CSI + '24~', DEFAULT, CSI + '43~', DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006426
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006427 // Second row.
6428 [192, '`~', DEFAULT, sh(ctl('@'), ctl('^')), DEFAULT, PASS],
6429 [49, '1!', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6430 [50, '2@', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6431 [51, '3#', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6432 [52, '4$', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6433 [53, '5%', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6434 [54, '6^', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6435 [55, '7&', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6436 [56, '8*', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6437 [57, '9(', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')],
6438 [
6439 48, '0)', DEFAULT, c('onPlusMinusZero_'), c('onAltNum_'),
6440 c('onPlusMinusZero_')
6441 ],
6442 [
6443 189, '-_', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6444 c('onPlusMinusZero_')
6445 ],
6446 [
6447 187, '=+', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6448 c('onPlusMinusZero_')
6449 ],
6450 // Firefox -_ and =+
6451 [
6452 173, '-_', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6453 c('onPlusMinusZero_')
6454 ],
6455 [
6456 61, '=+', DEFAULT, c('onPlusMinusZero_'), DEFAULT, c('onPlusMinusZero_')
6457 ],
6458 // Firefox Italian +*
6459 [
6460 171, '+*', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6461 c('onPlusMinusZero_')
6462 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006463
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006464 [8, '[BKSP]', bs('\x7f', '\b'), bs('\b', '\x7f'), DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006465
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006466 // Third row.
6467 [9, '[TAB]', sh('\t', CSI + 'Z'), STRIP, PASS, DEFAULT],
6468 [81, 'qQ', DEFAULT, ctl('Q'), DEFAULT, DEFAULT],
6469 [87, 'wW', DEFAULT, ctl('W'), DEFAULT, DEFAULT],
6470 [69, 'eE', DEFAULT, ctl('E'), DEFAULT, DEFAULT],
6471 [82, 'rR', DEFAULT, ctl('R'), DEFAULT, DEFAULT],
6472 [84, 'tT', DEFAULT, ctl('T'), DEFAULT, DEFAULT],
6473 [89, 'yY', DEFAULT, ctl('Y'), DEFAULT, DEFAULT],
6474 [85, 'uU', DEFAULT, ctl('U'), DEFAULT, DEFAULT],
6475 [73, 'iI', DEFAULT, ctl('I'), DEFAULT, DEFAULT],
6476 [79, 'oO', DEFAULT, ctl('O'), DEFAULT, DEFAULT],
6477 [80, 'pP', DEFAULT, ctl('P'), DEFAULT, DEFAULT],
6478 [219, '[{', DEFAULT, ctl('['), DEFAULT, DEFAULT],
6479 [221, ']}', DEFAULT, ctl(']'), DEFAULT, DEFAULT],
6480 [220, '\\|', DEFAULT, ctl('\\'), DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006481
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006482 // Fourth row. (We let Ctrl-Shift-J pass for Chrome DevTools.)
6483 [20, '[CAPS]', PASS, PASS, PASS, DEFAULT],
6484 [65, 'aA', DEFAULT, ctl('A'), DEFAULT, DEFAULT],
6485 [83, 'sS', DEFAULT, ctl('S'), DEFAULT, DEFAULT],
6486 [68, 'dD', DEFAULT, ctl('D'), DEFAULT, DEFAULT],
6487 [70, 'fF', DEFAULT, ctl('F'), DEFAULT, DEFAULT],
6488 [71, 'gG', DEFAULT, ctl('G'), DEFAULT, DEFAULT],
6489 [72, 'hH', DEFAULT, ctl('H'), DEFAULT, DEFAULT],
6490 [74, 'jJ', DEFAULT, sh(ctl('J'), PASS), DEFAULT, DEFAULT],
6491 [75, 'kK', DEFAULT, sh(ctl('K'), c('onClear_')), DEFAULT, DEFAULT],
6492 [76, 'lL', DEFAULT, sh(ctl('L'), PASS), DEFAULT, DEFAULT],
6493 [186, ';:', DEFAULT, STRIP, DEFAULT, DEFAULT],
6494 [222, '\'"', DEFAULT, STRIP, DEFAULT, DEFAULT],
6495 [13, '[ENTER]', '\r', CANCEL, CANCEL, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006496
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006497 // Fifth row. This includes the copy/paste shortcuts. On some
6498 // platforms it's Ctrl-C/V, on others it's Meta-C/V. We assume either
6499 // Ctrl-C/Meta-C should pass to the browser when there is a selection,
6500 // and Ctrl-Shift-V/Meta-*-V should always pass to the browser (since
6501 // these seem to be recognized as paste too).
6502 [16, '[SHIFT]', PASS, PASS, PASS, DEFAULT],
6503 [90, 'zZ', DEFAULT, ctl('Z'), DEFAULT, DEFAULT],
6504 [88, 'xX', DEFAULT, ctl('X'), DEFAULT, DEFAULT],
6505 [67, 'cC', DEFAULT, c('onCtrlC_'), DEFAULT, c('onMetaC_')],
6506 [86, 'vV', DEFAULT, c('onCtrlV_'), DEFAULT, c('onMetaV_')],
6507 [66, 'bB', DEFAULT, sh(ctl('B'), PASS), DEFAULT, sh(DEFAULT, PASS)],
6508 [78, 'nN', DEFAULT, c('onCtrlN_'), DEFAULT, c('onMetaN_')],
6509 [77, 'mM', DEFAULT, ctl('M'), DEFAULT, DEFAULT],
6510 [188, ',<', DEFAULT, alt(STRIP, PASS), DEFAULT, DEFAULT],
6511 [190, '.>', DEFAULT, alt(STRIP, PASS), DEFAULT, DEFAULT],
6512 [191, '/?', DEFAULT, sh(ctl('_'), ctl('?')), DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006513
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006514 // Sixth and final row.
6515 [17, '[CTRL]', PASS, PASS, PASS, PASS],
6516 [18, '[ALT]', PASS, PASS, PASS, PASS],
6517 [91, '[LAPL]', PASS, PASS, PASS, PASS],
6518 [32, ' ', DEFAULT, ctl('@'), DEFAULT, DEFAULT],
6519 [92, '[RAPL]', PASS, PASS, PASS, PASS],
6520 [93, '[RMENU]', PASS, PASS, PASS, PASS],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006521
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006522 // These things.
6523 [42, '[PRTSCR]', PASS, PASS, PASS, PASS],
6524 [145, '[SCRLK]', PASS, PASS, PASS, PASS],
6525 [19, '[BREAK]', PASS, PASS, PASS, PASS],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006526
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006527 // The block of six keys above the arrows.
6528 [45, '[INSERT]', c('onKeyInsert_'), DEFAULT, DEFAULT, DEFAULT],
6529 [36, '[HOME]', c('onKeyHome_'), DEFAULT, DEFAULT, DEFAULT],
6530 [33, '[PGUP]', c('onKeyPageUp_'), DEFAULT, DEFAULT, DEFAULT],
6531 [46, '[DEL]', c('onKeyDel_'), DEFAULT, DEFAULT, DEFAULT],
6532 [35, '[END]', c('onKeyEnd_'), DEFAULT, DEFAULT, DEFAULT],
6533 [34, '[PGDOWN]', c('onKeyPageDown_'), DEFAULT, DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006534
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006535 // Arrow keys. When unmodified they respect the application cursor state,
6536 // otherwise they always send the CSI codes.
6537 [38, '[UP]', ac(CSI + 'A', SS3 + 'A'), DEFAULT, DEFAULT, DEFAULT],
6538 [40, '[DOWN]', ac(CSI + 'B', SS3 + 'B'), DEFAULT, DEFAULT, DEFAULT],
6539 [39, '[RIGHT]', ac(CSI + 'C', SS3 + 'C'), DEFAULT, DEFAULT, DEFAULT],
6540 [37, '[LEFT]', ac(CSI + 'D', SS3 + 'D'), DEFAULT, DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006541
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006542 [144, '[NUMLOCK]', PASS, PASS, PASS, PASS],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006543
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006544 // With numlock off, the keypad generates the same key codes as the arrows
6545 // and 'block of six' for some keys, and null key codes for the rest.
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006546
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006547 // Keypad with numlock on generates unique key codes...
6548 [96, '[KP0]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6549 [97, '[KP1]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6550 [98, '[KP2]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6551 [99, '[KP3]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6552 [100, '[KP4]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6553 [101, '[KP5]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6554 [102, '[KP6]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6555 [103, '[KP7]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6556 [104, '[KP8]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6557 [105, '[KP9]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6558 [
6559 107, '[KP+]', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6560 c('onPlusMinusZero_')
6561 ],
6562 [
6563 109, '[KP-]', DEFAULT, c('onPlusMinusZero_'), DEFAULT,
6564 c('onPlusMinusZero_')
6565 ],
6566 [106, '[KP*]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6567 [111, '[KP/]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
6568 [110, '[KP.]', DEFAULT, DEFAULT, DEFAULT, DEFAULT],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006569
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006570 // Chrome OS keyboard top row.
6571 [
6572 166, '[BACK]', med(mod(SS3 + 'P', CSI + 'P')), DEFAULT, CSI + '23~',
6573 DEFAULT
6574 ],
6575 [
6576 167, '[FWD]', med(mod(SS3 + 'Q', CSI + 'Q')), DEFAULT, CSI + '24~',
6577 DEFAULT
6578 ],
6579 [
6580 168, '[RELOAD]', med(mod(SS3 + 'R', CSI + 'R')), DEFAULT, CSI + '25~',
6581 DEFAULT
6582 ],
6583 [
6584 183, '[FSCR]', med(mod(SS3 + 'S', CSI + 'S')), DEFAULT, CSI + '26~',
6585 DEFAULT
6586 ],
6587 [182, '[WINS]', med(CSI + '15~'), DEFAULT, CSI + '28~', DEFAULT],
6588 [216, '[BRIT-]', med(CSI + '17~'), DEFAULT, CSI + '29~', DEFAULT],
6589 [217, '[BRIT+]', med(CSI + '18~'), DEFAULT, CSI + '31~', DEFAULT]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006590
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006591 // 173 [MUTE], 174 [VOL-] and 175 [VOL+] are trapped by the Chrome OS
6592 // window manager, so we'll never see them. Note that 173 is also
6593 // Firefox's -_ keycode.
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006594 );
6595};
6596
6597/**
6598 * Either allow the paste or send a key sequence.
6599 */
6600hterm.Keyboard.KeyMap.prototype.onKeyInsert_ = function(e) {
6601 if (this.keyboard.shiftInsertPaste && e.shiftKey)
6602 return hterm.Keyboard.KeyActions.PASS;
6603
6604 return '\x1b[2~';
6605};
6606
6607/**
6608 * Either scroll the scrollback buffer or send a key sequence.
6609 */
6610hterm.Keyboard.KeyMap.prototype.onKeyHome_ = function(e) {
6611 if (!this.keyboard.homeKeysScroll ^ e.shiftKey) {
6612 if ((e.altey || e.ctrlKey || e.shiftKey) ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006613 !this.keyboard.applicationCursor) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006614 return '\x1b[H';
6615 }
6616
6617 return '\x1bOH';
6618 }
6619
6620 this.keyboard.terminal.scrollHome();
6621 return hterm.Keyboard.KeyActions.CANCEL;
6622};
6623
6624/**
6625 * Either scroll the scrollback buffer or send a key sequence.
6626 */
6627hterm.Keyboard.KeyMap.prototype.onKeyEnd_ = function(e) {
6628 if (!this.keyboard.homeKeysScroll ^ e.shiftKey) {
6629 if ((e.altKey || e.ctrlKey || e.shiftKey) ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006630 !this.keyboard.applicationCursor) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006631 return '\x1b[F';
6632 }
6633
6634 return '\x1bOF';
6635 }
6636
6637 this.keyboard.terminal.scrollEnd();
6638 return hterm.Keyboard.KeyActions.CANCEL;
6639};
6640
6641/**
6642 * Either scroll the scrollback buffer or send a key sequence.
6643 */
6644hterm.Keyboard.KeyMap.prototype.onKeyPageUp_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006645 if (!this.keyboard.pageKeysScroll ^ e.shiftKey) return '\x1b[5~';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006646
6647 this.keyboard.terminal.scrollPageUp();
6648 return hterm.Keyboard.KeyActions.CANCEL;
6649};
6650
6651/**
6652 * Either send a true DEL, or sub in meta-backspace.
6653 *
6654 * On Chrome OS, if we know the alt key is down, but we get a DEL event that
6655 * claims that the alt key is not pressed, we know the DEL was a synthetic
6656 * one from a user that hit alt-backspace. Based on a user pref, we can sub
6657 * in meta-backspace in this case.
6658 */
6659hterm.Keyboard.KeyMap.prototype.onKeyDel_ = function(e) {
6660 if (this.keyboard.altBackspaceIsMetaBackspace &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006661 this.keyboard.altKeyPressed && !e.altKey)
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006662 return '\x1b\x7f';
6663 return '\x1b[3~';
6664};
6665
6666/**
6667 * Either scroll the scrollback buffer or send a key sequence.
6668 */
6669hterm.Keyboard.KeyMap.prototype.onKeyPageDown_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006670 if (!this.keyboard.pageKeysScroll ^ e.shiftKey) return '\x1b[6~';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006671
6672 this.keyboard.terminal.scrollPageDown();
6673 return hterm.Keyboard.KeyActions.CANCEL;
6674};
6675
6676/**
6677 * Clear the primary/alternate screens and the scrollback buffer.
6678 */
6679hterm.Keyboard.KeyMap.prototype.onClear_ = function(e, keyDef) {
6680 this.keyboard.terminal.wipeContents();
6681 return hterm.Keyboard.KeyActions.CANCEL;
6682};
6683
6684/**
6685 * Either pass Ctrl-1..9 to the browser or send them to the host.
6686 *
6687 * Note that Ctrl-1 and Ctrl-9 don't actually have special sequences mapped
6688 * to them in xterm or gnome-terminal. The range is really Ctrl-2..8, but
6689 * we handle 1..9 since Chrome treats the whole range special.
6690 */
6691hterm.Keyboard.KeyMap.prototype.onCtrlNum_ = function(e, keyDef) {
6692 // Compute a control character for a given character.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006693 function ctl(ch) {
6694 return String.fromCharCode(ch.charCodeAt(0) - 64);
6695 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006696
6697 if (this.keyboard.terminal.passCtrlNumber && !e.shiftKey)
6698 return hterm.Keyboard.KeyActions.PASS;
6699
6700 switch (keyDef.keyCap.substr(0, 1)) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006701 case '1':
6702 return '1';
6703 case '2':
6704 return ctl('@');
6705 case '3':
6706 return ctl('[');
6707 case '4':
6708 return ctl('\\');
6709 case '5':
6710 return ctl(']');
6711 case '6':
6712 return ctl('^');
6713 case '7':
6714 return ctl('_');
6715 case '8':
6716 return '\x7f';
6717 case '9':
6718 return '9';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006719 }
6720};
6721
6722/**
6723 * Either pass Alt-1..9 to the browser or send them to the host.
6724 */
6725hterm.Keyboard.KeyMap.prototype.onAltNum_ = function(e, keyDef) {
6726 if (this.keyboard.terminal.passAltNumber && !e.shiftKey)
6727 return hterm.Keyboard.KeyActions.PASS;
6728
6729 return hterm.Keyboard.KeyActions.DEFAULT;
6730};
6731
6732/**
6733 * Either pass Meta-1..9 to the browser or send them to the host.
6734 */
6735hterm.Keyboard.KeyMap.prototype.onMetaNum_ = function(e, keyDef) {
6736 if (this.keyboard.terminal.passMetaNumber && !e.shiftKey)
6737 return hterm.Keyboard.KeyActions.PASS;
6738
6739 return hterm.Keyboard.KeyActions.DEFAULT;
6740};
6741
6742/**
6743 * Either send a ^C or interpret the keystroke as a copy command.
6744 */
6745hterm.Keyboard.KeyMap.prototype.onCtrlC_ = function(e, keyDef) {
6746 var selection = this.keyboard.terminal.getDocument().getSelection();
6747
6748 if (!selection.isCollapsed) {
6749 if (this.keyboard.ctrlCCopy && !e.shiftKey) {
6750 // Ctrl-C should copy if there is a selection, send ^C otherwise.
6751 // Perform the copy by letting the browser handle Ctrl-C. On most
6752 // browsers, this is the *only* way to place text on the clipboard from
6753 // the 'drive-by' web.
6754 if (this.keyboard.terminal.clearSelectionAfterCopy) {
6755 setTimeout(selection.collapseToEnd.bind(selection), 50);
6756 }
6757 return hterm.Keyboard.KeyActions.PASS;
6758 }
6759
6760 if (!this.keyboard.ctrlCCopy && e.shiftKey) {
6761 // Ctrl-Shift-C should copy if there is a selection, send ^C otherwise.
6762 // Perform the copy manually. This only works in situations where
6763 // document.execCommand('copy') is allowed.
6764 if (this.keyboard.terminal.clearSelectionAfterCopy) {
6765 setTimeout(selection.collapseToEnd.bind(selection), 50);
6766 }
6767 this.keyboard.terminal.copySelectionToClipboard();
6768 return hterm.Keyboard.KeyActions.CANCEL;
6769 }
6770 }
6771
6772 return '\x03';
6773};
6774
6775/**
6776 * Either send a ^N or open a new window to the same location.
6777 */
6778hterm.Keyboard.KeyMap.prototype.onCtrlN_ = function(e, keyDef) {
6779 if (e.shiftKey) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006780 window.open(
6781 document.location.href, '',
6782 'chrome=no,close=yes,resize=yes,scrollbars=yes,' +
6783 'minimizable=yes,width=' + window.innerWidth +
6784 ',height=' + window.innerHeight);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006785 return hterm.Keyboard.KeyActions.CANCEL;
6786 }
6787
6788 return '\x0e';
6789};
6790
6791/**
6792 * Either send a ^V or allow the browser to interpret the keystroke as a paste
6793 * command.
6794 *
6795 * The default behavior is to paste if the user presses Ctrl-Shift-V, and send
6796 * a ^V if the user presses Ctrl-V. This can be flipped with the
6797 * 'ctrl-v-paste' preference.
6798 */
6799hterm.Keyboard.KeyMap.prototype.onCtrlV_ = function(e, keyDef) {
6800 if ((!e.shiftKey && this.keyboard.ctrlVPaste) ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006801 (e.shiftKey && !this.keyboard.ctrlVPaste)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006802 return hterm.Keyboard.KeyActions.PASS;
6803 }
6804
6805 return '\x16';
6806};
6807
6808/**
6809 * Either the default action or open a new window to the same location.
6810 */
6811hterm.Keyboard.KeyMap.prototype.onMetaN_ = function(e, keyDef) {
6812 if (e.shiftKey) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006813 window.open(
6814 document.location.href, '',
6815 'chrome=no,close=yes,resize=yes,scrollbars=yes,' +
6816 'minimizable=yes,width=' + window.outerWidth +
6817 ',height=' + window.outerHeight);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006818 return hterm.Keyboard.KeyActions.CANCEL;
6819 }
6820
6821 return hterm.Keyboard.KeyActions.DEFAULT;
6822};
6823
6824/**
6825 * Either send a Meta-C or allow the browser to interpret the keystroke as a
6826 * copy command.
6827 *
6828 * If there is no selection, or if the user presses Meta-Shift-C, then we'll
6829 * transmit an '\x1b' (if metaSendsEscape is on) followed by 'c' or 'C'.
6830 *
6831 * If there is a selection, we defer to the browser. In this case we clear out
6832 * the selection so the user knows we heard them, and also to give them a
6833 * chance to send a Meta-C by just hitting the key again.
6834 */
6835hterm.Keyboard.KeyMap.prototype.onMetaC_ = function(e, keyDef) {
6836 var document = this.keyboard.terminal.getDocument();
6837 if (e.shiftKey || document.getSelection().isCollapsed) {
6838 // If the shift key is being held, or there is no document selection, send
6839 // a Meta-C. The keyboard code will add the ESC if metaSendsEscape is true,
6840 // we just have to decide between 'c' and 'C'.
6841 return keyDef.keyCap.substr(e.shiftKey ? 1 : 0, 1);
6842 }
6843
6844 // Otherwise let the browser handle it as a copy command.
6845 if (this.keyboard.terminal.clearSelectionAfterCopy) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006846 setTimeout(function() {
6847 document.getSelection().collapseToEnd();
6848 }, 50);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006849 }
6850 return hterm.Keyboard.KeyActions.PASS;
6851};
6852
6853/**
6854 * Either PASS or DEFAULT Meta-V, depending on preference.
6855 *
6856 * Always PASS Meta-Shift-V to allow browser to interpret the keystroke as
6857 * a paste command.
6858 */
6859hterm.Keyboard.KeyMap.prototype.onMetaV_ = function(e, keyDef) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006860 if (e.shiftKey) return hterm.Keyboard.KeyActions.PASS;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006861
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006862 return this.keyboard.passMetaV ? hterm.Keyboard.KeyActions.PASS :
6863 hterm.Keyboard.KeyActions.DEFAULT;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006864};
6865
6866/**
6867 * Handle font zooming.
6868 *
6869 * The browser's built-in zoom has a bit of an issue at certain zoom levels.
6870 * At some magnifications, the measured height of a row of text differs from
6871 * the height that was explicitly set.
6872 *
6873 * We override the browser zoom keys to change the ScrollPort's font size to
6874 * avoid the issue.
6875 */
6876hterm.Keyboard.KeyMap.prototype.onPlusMinusZero_ = function(e, keyDef) {
6877 if (!(this.keyboard.ctrlPlusMinusZeroZoom ^ e.shiftKey)) {
6878 // If ctrl-PMZ controls zoom and the shift key is pressed, or
6879 // ctrl-shift-PMZ controls zoom and this shift key is not pressed,
6880 // then we want to send the control code instead of affecting zoom.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006881 if (keyDef.keyCap == '-_') return '\x1f'; // ^_
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006882
6883 // Only ^_ is valid, the other sequences have no meaning.
6884 return hterm.Keyboard.KeyActions.CANCEL;
6885 }
6886
6887 if (this.keyboard.terminal.getZoomFactor() != 1) {
6888 // If we're not at 1:1 zoom factor, let the Ctrl +/-/0 keys control the
6889 // browser zoom, so it's easier to for the user to get back to 100%.
6890 return hterm.Keyboard.KeyActions.PASS;
6891 }
6892
6893 var cap = keyDef.keyCap.substr(0, 1);
6894 if (cap == '0') {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07006895 this.keyboard.terminal.setFontSize(0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006896 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006897 var size = this.keyboard.terminal.getFontSize();
6898
6899 if (cap == '-' || keyDef.keyCap == '[KP-]') {
6900 size -= 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006901 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006902 size += 1;
6903 }
6904
6905 this.keyboard.terminal.setFontSize(size);
6906 }
6907
6908 return hterm.Keyboard.KeyActions.CANCEL;
6909};
6910// SOURCE FILE: hterm/js/hterm_keyboard_keypattern.js
6911// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
6912// Use of this source code is governed by a BSD-style license that can be
6913// found in the LICENSE file.
6914
6915'use strict';
6916
6917/**
6918 * A record of modifier bits and keycode used to define a key binding.
6919 *
6920 * The modifier names are enumerated in the static KeyPattern.modifiers
6921 * property below. Each modifier can be true, false, or "*". True means
6922 * the modifier key must be present, false means it must not, and "*" means
6923 * it doesn't matter.
6924 */
6925hterm.Keyboard.KeyPattern = function(spec) {
6926 this.wildcardCount = 0;
6927 this.keyCode = spec.keyCode;
6928
6929 hterm.Keyboard.KeyPattern.modifiers.forEach(function(mod) {
6930 this[mod] = spec[mod] || false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006931 if (this[mod] == '*') this.wildcardCount++;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006932 }.bind(this));
6933};
6934
6935/**
6936 * Valid modifier names.
6937 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006938hterm.Keyboard.KeyPattern.modifiers = ['shift', 'ctrl', 'alt', 'meta'];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006939
6940/**
6941 * A compare callback for Array.prototype.sort().
6942 *
6943 * The bindings code wants to be sure to search through the strictest key
6944 * patterns first, so that loosely defined patterns have a lower priority than
6945 * exact patterns.
6946 *
6947 * @param {hterm.Keyboard.KeyPattern} a
6948 * @param {hterm.Keyboard.KeyPattern} b
6949 */
6950hterm.Keyboard.KeyPattern.sortCompare = function(a, b) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006951 if (a.wildcardCount < b.wildcardCount) return -1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006952
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006953 if (a.wildcardCount > b.wildcardCount) return 1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006954
6955 return 0;
6956};
6957
6958/**
6959 * Private method used to match this key pattern against other key patterns
6960 * or key down events.
6961 *
6962 * @param {Object} The object to match.
6963 * @param {boolean} True if we should ignore wildcards. Useful when you want
6964 * to perform and exact match against another key pattern.
6965 */
6966hterm.Keyboard.KeyPattern.prototype.match_ = function(obj, exactMatch) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07006967 if (this.keyCode != obj.keyCode) return false;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05006968
6969 var rv = true;
6970
6971 hterm.Keyboard.KeyPattern.modifiers.forEach(function(mod) {
6972 var modValue = (mod in obj) ? obj[mod] : false;
6973 if (!rv || (!exactMatch && this[mod] == '*') || this[mod] == modValue)
6974 return;
6975
6976 rv = false;
6977 }.bind(this));
6978
6979 return rv;
6980};
6981
6982/**
6983 * Return true if the given keyDown object is a match for this key pattern.
6984 *
6985 * @param {Object} keyDown An object with a keyCode property and zero or
6986 * more boolean properties representing key modifiers. These property names
6987 * must match those defined in hterm.Keyboard.KeyPattern.modifiers.
6988 */
6989hterm.Keyboard.KeyPattern.prototype.matchKeyDown = function(keyDown) {
6990 return this.match_(keyDown, false);
6991};
6992
6993/**
6994 * Return true if the given hterm.Keyboard.KeyPattern is exactly the same as
6995 * this one.
6996 *
6997 * @param {hterm.Keyboard.KeyPattern}
6998 */
6999hterm.Keyboard.KeyPattern.prototype.matchKeyPattern = function(keyPattern) {
7000 return this.match_(keyPattern, true);
7001};
7002// SOURCE FILE: hterm/js/hterm_options.js
7003// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
7004// Use of this source code is governed by a BSD-style license that can be
7005// found in the LICENSE file.
7006
7007'use strict';
7008
7009/**
7010 * @fileoverview This file implements the hterm.Options class,
7011 * which stores current operating conditions for the terminal. This object is
7012 * used instead of a series of parameters to allow saving/restoring of cursor
7013 * conditions easily, and to provide an easy place for common configuration
7014 * options.
7015 *
7016 * Original code by Cory Maccarrone.
7017 */
7018
7019/**
7020 * Constructor for the hterm.Options class, optionally acting as a copy
7021 * constructor.
7022 *
7023 * The defaults are as defined in http://www.vt100.net/docs/vt510-rm/DECSTR
7024 * except that we enable autowrap (wraparound) by default since that seems to
7025 * be what xterm does.
7026 *
7027 * @param {hterm.Options=} opt_copy Optional instance to copy.
7028 * @constructor
7029 */
7030hterm.Options = function(opt_copy) {
7031 // All attributes in this class are public to allow easy access by the
7032 // terminal.
7033
7034 this.wraparound = opt_copy ? opt_copy.wraparound : true;
7035 this.reverseWraparound = opt_copy ? opt_copy.reverseWraparound : false;
7036 this.originMode = opt_copy ? opt_copy.originMode : false;
7037 this.autoCarriageReturn = opt_copy ? opt_copy.autoCarriageReturn : false;
7038 this.cursorVisible = opt_copy ? opt_copy.cursorVisible : false;
7039 this.cursorBlink = opt_copy ? opt_copy.cursorBlink : false;
7040 this.insertMode = opt_copy ? opt_copy.insertMode : false;
7041 this.reverseVideo = opt_copy ? opt_copy.reverseVideo : false;
7042 this.bracketedPaste = opt_copy ? opt_copy.bracketedPaste : false;
7043};
7044// SOURCE FILE: hterm/js/hterm_parser.js
7045// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
7046// Use of this source code is governed by a BSD-style license that can be
7047// found in the LICENSE file.
7048
7049'use strict';
7050
7051lib.rtdep('hterm.Keyboard.KeyActions');
7052
7053/**
7054 * @constructor
7055 * Parses the key definition syntax used for user keyboard customizations.
7056 */
7057hterm.Parser = function() {
7058 /**
7059 * @type {string} The source string.
7060 */
7061 this.source = '';
7062
7063 /**
7064 * @type {number} The current position.
7065 */
7066 this.pos = 0;
7067
7068 /**
7069 * @type {string?} The character at the current position.
7070 */
7071 this.ch = null;
7072};
7073
7074hterm.Parser.prototype.error = function(message) {
7075 return new Error('Parse error at ' + this.pos + ': ' + message);
7076};
7077
7078hterm.Parser.prototype.isComplete = function() {
7079 return this.pos == this.source.length;
7080};
7081
7082hterm.Parser.prototype.reset = function(source, opt_pos) {
7083 this.source = source;
7084 this.pos = opt_pos || 0;
7085 this.ch = source.substr(0, 1);
7086};
7087
7088/**
7089 * Parse a key sequence.
7090 *
7091 * A key sequence is zero or more of the key modifiers defined in
7092 * hterm.Parser.identifiers.modifierKeys followed by a key code. Key
7093 * codes can be an integer or an identifier from
7094 * hterm.Parser.identifiers.keyCodes. Modifiers and keyCodes should be joined
7095 * by the dash character.
7096 *
7097 * An asterisk "*" can be used to indicate that the unspecified modifiers
7098 * are optional.
7099 *
7100 * For example:
7101 * A: Matches only an unmodified "A" character.
7102 * 65: Same as above.
7103 * 0x41: Same as above.
7104 * Ctrl-A: Matches only Ctrl-A.
7105 * Ctrl-65: Same as above.
7106 * Ctrl-0x41: Same as above.
7107 * Ctrl-Shift-A: Matches only Ctrl-Shift-A.
7108 * Ctrl-*-A: Matches Ctrl-A, as well as any other key sequence that includes
7109 * at least the Ctrl and A keys.
7110 *
7111 * @return {Object} An object with shift, ctrl, alt, meta, keyCode
7112 * properties.
7113 */
7114hterm.Parser.prototype.parseKeySequence = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007115 var rv = {keyCode: null};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007116
7117 for (var k in hterm.Parser.identifiers.modifierKeys) {
7118 rv[hterm.Parser.identifiers.modifierKeys[k]] = false;
7119 }
7120
7121 while (this.pos < this.source.length) {
7122 this.skipSpace();
7123
7124 var token = this.parseToken();
7125 if (token.type == 'integer') {
7126 rv.keyCode = token.value;
7127
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007128 } else if (token.type == 'identifier') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007129 if (token.value in hterm.Parser.identifiers.modifierKeys) {
7130 var mod = hterm.Parser.identifiers.modifierKeys[token.value];
7131 if (rv[mod] && rv[mod] != '*')
7132 throw this.error('Duplicate modifier: ' + token.value);
7133 rv[mod] = true;
7134
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007135 } else if (token.value in hterm.Parser.identifiers.keyCodes) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007136 rv.keyCode = hterm.Parser.identifiers.keyCodes[token.value];
7137
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007138 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007139 throw this.error('Unknown key: ' + token.value);
7140 }
7141
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007142 } else if (token.type == 'symbol') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007143 if (token.value == '*') {
7144 for (var id in hterm.Parser.identifiers.modifierKeys) {
7145 var p = hterm.Parser.identifiers.modifierKeys[id];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007146 if (!rv[p]) rv[p] = '*';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007147 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007148 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007149 throw this.error('Unexpected symbol: ' + token.value);
7150 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007151 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007152 throw this.error('Expected integer or identifier');
7153 }
7154
7155 this.skipSpace();
7156
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007157 if (this.ch != '-') break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007158
7159 if (rv.keyCode != null)
7160 throw this.error('Extra definition after target key');
7161
7162 this.advance(1);
7163 }
7164
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007165 if (rv.keyCode == null) throw this.error('Missing target key');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007166
7167 return rv;
7168};
7169
7170hterm.Parser.prototype.parseKeyAction = function() {
7171 this.skipSpace();
7172
7173 var token = this.parseToken();
7174
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007175 if (token.type == 'string') return token.value;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007176
7177 if (token.type == 'identifier') {
7178 if (token.value in hterm.Parser.identifiers.actions)
7179 return hterm.Parser.identifiers.actions[token.value];
7180
7181 throw this.error('Unknown key action: ' + token.value);
7182 }
7183
7184 throw this.error('Expected string or identifier');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007185};
7186
7187hterm.Parser.prototype.peekString = function() {
7188 return this.ch == '\'' || this.ch == '"';
7189};
7190
7191hterm.Parser.prototype.peekIdentifier = function() {
7192 return this.ch.match(/[a-z_]/i);
7193};
7194
7195hterm.Parser.prototype.peekInteger = function() {
7196 return this.ch.match(/[0-9]/);
7197};
7198
7199hterm.Parser.prototype.parseToken = function() {
7200 if (this.ch == '*') {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007201 var rv = {type: 'symbol', value: this.ch};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007202 this.advance(1);
7203 return rv;
7204 }
7205
7206 if (this.peekIdentifier())
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007207 return {type: 'identifier', value: this.parseIdentifier()};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007208
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007209 if (this.peekString()) return {type: 'string', value: this.parseString()};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007210
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007211 if (this.peekInteger()) return {type: 'integer', value: this.parseInteger()};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007212
7213 throw this.error('Unexpected token');
7214};
7215
7216hterm.Parser.prototype.parseIdentifier = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007217 if (!this.peekIdentifier()) throw this.error('Expected identifier');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007218
7219 return this.parsePattern(/[a-z0-9_]+/ig);
7220};
7221
7222hterm.Parser.prototype.parseInteger = function() {
7223 var base = 10;
7224
7225 if (this.ch == '0' && this.pos < this.source.length - 1 &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007226 this.source.substr(this.pos + 1, 1) == 'x') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007227 return parseInt(this.parsePattern(/0x[0-9a-f]+/gi));
7228 }
7229
7230 return parseInt(this.parsePattern(/\d+/g));
7231};
7232
7233/**
7234 * Parse a single or double quoted string.
7235 *
7236 * The current position should point at the initial quote character. Single
7237 * quoted strings will be treated literally, double quoted will process escapes.
7238 *
7239 * TODO(rginda): Variable interpolation.
7240 *
7241 * @param {ParseState} parseState
7242 * @param {string} quote A single or double-quote character.
7243 * @return {string}
7244 */
7245hterm.Parser.prototype.parseString = function() {
7246 var result = '';
7247
7248 var quote = this.ch;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007249 if (quote != '"' && quote != '\'') throw this.error('String expected');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007250
7251 this.advance(1);
7252
7253 var re = new RegExp('[\\\\' + quote + ']', 'g');
7254
7255 while (this.pos < this.source.length) {
7256 re.lastIndex = this.pos;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007257 if (!re.exec(this.source)) throw this.error('Unterminated string literal');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007258
7259 result += this.source.substring(this.pos, re.lastIndex - 1);
7260
7261 this.advance(re.lastIndex - this.pos - 1);
7262
7263 if (quote == '"' && this.ch == '\\') {
7264 this.advance(1);
7265 result += this.parseEscape();
7266 continue;
7267 }
7268
7269 if (quote == '\'' && this.ch == '\\') {
7270 result += this.ch;
7271 this.advance(1);
7272 continue;
7273 }
7274
7275 if (this.ch == quote) {
7276 this.advance(1);
7277 return result;
7278 }
7279 }
7280
7281 throw this.error('Unterminated string literal');
7282};
7283
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007284/**
7285 * Parse an escape code from the current position (which should point to
7286 * the first character AFTER the leading backslash.)
7287 *
7288 * @return {string}
7289 */
7290hterm.Parser.prototype.parseEscape = function() {
7291 var map = {
7292 '"': '"',
7293 '\'': '\'',
7294 '\\': '\\',
7295 'a': '\x07',
7296 'b': '\x08',
7297 'e': '\x1b',
7298 'f': '\x0c',
7299 'n': '\x0a',
7300 'r': '\x0d',
7301 't': '\x09',
7302 'v': '\x0b',
7303 'x': function() {
7304 var value = this.parsePattern(/[a-z0-9]{2}/ig);
7305 return String.fromCharCode(parseInt(value, 16));
7306 },
7307 'u': function() {
7308 var value = this.parsePattern(/[a-z0-9]{4}/ig);
7309 return String.fromCharCode(parseInt(value, 16));
7310 }
7311 };
7312
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007313 if (!(this.ch in map)) throw this.error('Unknown escape: ' + this.ch);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007314
7315 var value = map[this.ch];
7316 this.advance(1);
7317
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007318 if (typeof value == 'function') value = value.call(this);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007319
7320 return value;
7321};
7322
7323/**
7324 * Parse the given pattern starting from the current position.
7325 *
7326 * @param {RegExp} pattern A pattern representing the characters to span. MUST
7327 * include the "global" RegExp flag.
7328 * @return {string}
7329 */
7330hterm.Parser.prototype.parsePattern = function(pattern) {
7331 if (!pattern.global)
7332 throw this.error('Internal error: Span patterns must be global');
7333
7334 pattern.lastIndex = this.pos;
7335 var ary = pattern.exec(this.source);
7336
7337 if (!ary || pattern.lastIndex - ary[0].length != this.pos)
7338 throw this.error('Expected match for: ' + pattern);
7339
7340 this.pos = pattern.lastIndex - 1;
7341 this.advance(1);
7342
7343 return ary[0];
7344};
7345
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007346/**
7347 * Advance the current position.
7348 *
7349 * @param {number} count
7350 */
7351hterm.Parser.prototype.advance = function(count) {
7352 this.pos += count;
7353 this.ch = this.source.substr(this.pos, 1);
7354};
7355
7356/**
7357 * @param {string=} opt_expect A list of valid non-whitespace characters to
7358 * terminate on.
7359 * @return {void}
7360 */
7361hterm.Parser.prototype.skipSpace = function(opt_expect) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007362 if (!/\s/.test(this.ch)) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007363
7364 var re = /\s+/gm;
7365 re.lastIndex = this.pos;
7366
7367 var source = this.source;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007368 if (re.exec(source)) this.pos = re.lastIndex;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007369
7370 this.ch = this.source.substr(this.pos, 1);
7371
7372 if (opt_expect) {
7373 if (this.ch.indexOf(opt_expect) == -1) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007374 throw this.error('Expected one of ' + opt_expect + ', found: ' + this.ch);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007375 }
7376 }
7377};
7378// SOURCE FILE: hterm/js/hterm_parser_identifiers.js
7379// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
7380// Use of this source code is governed by a BSD-style license that can be
7381// found in the LICENSE file.
7382
7383'use strict';
7384
7385/**
7386 * Collections of identifier for hterm.Parser.
7387 */
7388hterm.Parser.identifiers = {};
7389
7390hterm.Parser.identifiers.modifierKeys = {
7391 Shift: 'shift',
7392 Ctrl: 'ctrl',
7393 Alt: 'alt',
7394 Meta: 'meta'
7395};
7396
7397/**
7398 * Key codes useful when defining key sequences.
7399 *
7400 * Punctuation is mostly left out of this list because they can move around
7401 * based on keyboard locale and browser.
7402 *
7403 * In a key sequence like "Ctrl-ESC", the ESC comes from this list of
7404 * identifiers. It is equivalent to "Ctrl-27" and "Ctrl-0x1b".
7405 */
7406hterm.Parser.identifiers.keyCodes = {
7407 // Top row.
7408 ESC: 27,
7409 F1: 112,
7410 F2: 113,
7411 F3: 114,
7412 F4: 115,
7413 F5: 116,
7414 F6: 117,
7415 F7: 118,
7416 F8: 119,
7417 F9: 120,
7418 F10: 121,
7419 F11: 122,
7420 F12: 123,
7421
7422 // Row two.
7423 ONE: 49,
7424 TWO: 50,
7425 THREE: 51,
7426 FOUR: 52,
7427 FIVE: 53,
7428 SIX: 54,
7429 SEVEN: 55,
7430 EIGHT: 56,
7431 NINE: 57,
7432 ZERO: 48,
7433 BACKSPACE: 8,
7434
7435 // Row three.
7436 TAB: 9,
7437 Q: 81,
7438 W: 87,
7439 E: 69,
7440 R: 82,
7441 T: 84,
7442 Y: 89,
7443 U: 85,
7444 I: 73,
7445 O: 79,
7446 P: 80,
7447
7448 // Row four.
7449 CAPSLOCK: 20,
7450 A: 65,
7451 S: 83,
7452 D: 68,
7453 F: 70,
7454 G: 71,
7455 H: 72,
7456 J: 74,
7457 K: 75,
7458 L: 76,
7459 ENTER: 13,
7460
7461 // Row five.
7462 Z: 90,
7463 X: 88,
7464 C: 67,
7465 V: 86,
7466 B: 66,
7467 N: 78,
7468 M: 77,
7469
7470 // Etc.
7471 SPACE: 32,
7472 PRINT_SCREEN: 42,
7473 SCROLL_LOCK: 145,
7474 BREAK: 19,
7475 INSERT: 45,
7476 HOME: 36,
7477 PGUP: 33,
7478 DEL: 46,
7479 END: 35,
7480 PGDOWN: 34,
7481 UP: 38,
7482 DOWN: 40,
7483 RIGHT: 39,
7484 LEFT: 37,
7485 NUMLOCK: 144,
7486
7487 // Keypad
7488 KP0: 96,
7489 KP1: 97,
7490 KP2: 98,
7491 KP3: 99,
7492 KP4: 100,
7493 KP5: 101,
7494 KP6: 102,
7495 KP7: 103,
7496 KP8: 104,
7497 KP9: 105,
7498 KP_PLUS: 107,
7499 KP_MINUS: 109,
7500 KP_STAR: 106,
7501 KP_DIVIDE: 111,
7502 KP_DECIMAL: 110,
7503
7504 // Chrome OS media keys
7505 NAVIGATE_BACK: 166,
7506 NAVIGATE_FORWARD: 167,
7507 RELOAD: 168,
7508 FULL_SCREEN: 183,
7509 WINDOW_OVERVIEW: 182,
7510 BRIGHTNESS_UP: 216,
7511 BRIGHTNESS_DOWN: 217
7512};
7513
7514/**
7515 * Identifiers for use in key actions.
7516 */
7517hterm.Parser.identifiers.actions = {
7518 /**
7519 * Prevent the browser and operating system from handling the event.
7520 */
7521 CANCEL: hterm.Keyboard.KeyActions.CANCEL,
7522
7523 /**
7524 * Wait for a "keypress" event, send the keypress charCode to the host.
7525 */
7526 DEFAULT: hterm.Keyboard.KeyActions.DEFAULT,
7527
7528 /**
7529 * Let the browser or operating system handle the key.
7530 */
7531 PASS: hterm.Keyboard.KeyActions.PASS,
7532
7533 /**
7534 * Scroll the terminal one page up.
7535 */
7536 scrollPageUp: function(terminal) {
7537 terminal.scrollPageUp();
7538 return hterm.Keyboard.KeyActions.CANCEL;
7539 },
7540
7541 /**
7542 * Scroll the terminal one page down.
7543 */
7544 scrollPageDown: function(terminal) {
7545 terminal.scrollPageDown();
7546 return hterm.Keyboard.KeyActions.CANCEL;
7547 },
7548
7549 /**
7550 * Scroll the terminal to the top.
7551 */
7552 scrollToTop: function(terminal) {
7553 terminal.scrollEnd();
7554 return hterm.Keyboard.KeyActions.CANCEL;
7555 },
7556
7557 /**
7558 * Scroll the terminal to the bottom.
7559 */
7560 scrollToBottom: function(terminal) {
7561 terminal.scrollEnd();
7562 return hterm.Keyboard.KeyActions.CANCEL;
7563 },
7564
7565 /**
7566 * Clear the terminal and scrollback buffer.
7567 */
7568 clearScrollback: function(terminal) {
7569 terminal.wipeContents();
7570 return hterm.Keyboard.KeyActions.CANCEL;
7571 }
7572};
7573// SOURCE FILE: hterm/js/hterm_preference_manager.js
7574// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
7575// Use of this source code is governed by a BSD-style license that can be
7576// found in the LICENSE file.
7577
7578'use strict';
7579
7580lib.rtdep('lib.f', 'lib.Storage');
7581
7582/**
7583 * PreferenceManager subclass managing global NaSSH preferences.
7584 *
7585 * This is currently just an ordered list of known connection profiles.
7586 */
7587hterm.PreferenceManager = function(profileId) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007588 lib.PreferenceManager.call(
7589 this, hterm.defaultStorage, '/hterm/profiles/' + profileId);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007590 var defs = hterm.PreferenceManager.defaultPreferences;
7591 Object.keys(defs).forEach(function(key) {
7592 this.definePreference(key, defs[key][1]);
7593 }.bind(this));
7594};
7595
7596hterm.PreferenceManager.categories = {};
7597hterm.PreferenceManager.categories.Keyboard = 'Keyboard';
7598hterm.PreferenceManager.categories.Appearance = 'Appearance';
7599hterm.PreferenceManager.categories.CopyPaste = 'CopyPaste';
7600hterm.PreferenceManager.categories.Sounds = 'Sounds';
7601hterm.PreferenceManager.categories.Scrolling = 'Scrolling';
7602hterm.PreferenceManager.categories.Encoding = 'Encoding';
7603hterm.PreferenceManager.categories.Miscellaneous = 'Miscellaneous';
7604
7605/**
7606 * List of categories, ordered by display order (top to bottom)
7607 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007608hterm.PreferenceManager.categoryDefinitions = [
7609 {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007610 id: hterm.PreferenceManager.categories.Appearance,
7611 text: 'Appearance (fonts, colors, images)'
7612 },
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007613 {id: hterm.PreferenceManager.categories.CopyPaste, text: 'Copy & Paste'},
7614 {id: hterm.PreferenceManager.categories.Encoding, text: 'Encoding'},
7615 {id: hterm.PreferenceManager.categories.Keyboard, text: 'Keyboard'},
7616 {id: hterm.PreferenceManager.categories.Scrolling, text: 'Scrolling'},
7617 {id: hterm.PreferenceManager.categories.Sounds, text: 'Sounds'},
7618 {id: hterm.PreferenceManager.categories.Miscellaneous, text: 'Misc.'}
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007619];
7620
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007621hterm.PreferenceManager.defaultPreferences = {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007622 'alt-gr-mode': [
7623 hterm.PreferenceManager.categories.Keyboard, null,
7624 [null, 'none', 'ctrl-alt', 'left-alt', 'right-alt'],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007625 'Select an AltGr detection hack^Wheuristic.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007626 '\n' +
7627 '\'null\': Autodetect based on navigator.language:\n' +
7628 ' \'en-us\' => \'none\', else => \'right-alt\'\n' +
7629 '\'none\': Disable any AltGr related munging.\n' +
7630 '\'ctrl-alt\': Assume Ctrl+Alt means AltGr.\n' +
7631 '\'left-alt\': Assume left Alt means AltGr.\n' +
7632 '\'right-alt\': Assume right Alt means AltGr.\n'
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007633 ],
7634
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007635 'alt-backspace-is-meta-backspace': [
7636 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007637 'If set, undoes the Chrome OS Alt-Backspace->DEL remap, so that ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007638 'alt-backspace indeed is alt-backspace.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007639 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007640
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007641 'alt-is-meta': [
7642 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007643 'Set whether the alt key acts as a meta key or as a distinct alt key.'
7644 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007645
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007646 'alt-sends-what': [
7647 hterm.PreferenceManager.categories.Keyboard, 'escape',
7648 ['escape', '8-bit', 'browser-key'],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007649 'Controls how the alt key is handled.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007650 '\n' +
7651 ' escape....... Send an ESC prefix.\n' +
7652 ' 8-bit........ Add 128 to the unshifted character as in xterm.\n' +
7653 ' browser-key.. Wait for the keypress event and see what the browser \n' +
7654 ' says. (This won\'t work well on platforms where the \n' +
7655 ' browser performs a default action for some alt sequences.)'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007656 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007657
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007658 'audible-bell-sound': [
7659 hterm.PreferenceManager.categories.Sounds, 'lib-resource:hterm/audio/bell',
7660 'url', 'URL of the terminal bell sound. Empty string for no audible bell.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007661 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007662
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007663 'desktop-notification-bell': [
7664 hterm.PreferenceManager.categories.Sounds, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007665 'If true, terminal bells in the background will create a Web ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007666 'Notification. https://www.w3.org/TR/notifications/\n' +
7667 '\n' +
7668 'Displaying notifications requires permission from the user. When this ' +
7669 'option is set to true, hterm will attempt to ask the user for permission ' +
7670 'if necessary. Note browsers may not show this permission request if it ' +
7671 'did not originate from a user action.\n' +
7672 '\n' +
7673 'Chrome extensions with the "notifications" permission have permission to ' +
7674 'display notifications.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007675 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007676
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007677 'background-color': [
7678 hterm.PreferenceManager.categories.Appearance, 'rgb(16, 16, 16)', 'color',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007679 'The background color for text with no other color attributes.'
7680 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007681
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007682 'background-image': [
7683 hterm.PreferenceManager.categories.Appearance, '', 'string',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007684 'CSS value of the background image. Empty string for no image.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007685 '\n' +
7686 'For example:\n' +
7687 ' url(https://goo.gl/anedTK)\n' +
7688 ' linear-gradient(top bottom, blue, red)'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007689 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007690
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007691 'background-size': [
7692 hterm.PreferenceManager.categories.Appearance, '', 'string',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007693 'CSS value of the background image size. Defaults to none.'
7694 ],
7695
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007696 'background-position': [
7697 hterm.PreferenceManager.categories.Appearance, '', 'string',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007698 'CSS value of the background image position.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007699 '\n' +
7700 'For example:\n' +
7701 ' 10% 10%\n' +
7702 ' center'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007703 ],
7704
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007705 'backspace-sends-backspace': [
7706 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007707 'If true, the backspace should send BS (\'\\x08\', aka ^H). Otherwise ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007708 'the backspace key should send \'\\x7f\'.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007709 ],
7710
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007711 'character-map-overrides': [
7712 hterm.PreferenceManager.categories.Appearance, null, 'value',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007713 'This is specified as an object. It is a sparse array, where each ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007714 'property is the character set code and the value is an object that is ' +
7715 'a sparse array itself. In that sparse array, each property is the ' +
7716 'received character and the value is the displayed character.\n' +
7717 '\n' +
7718 'For example:\n' +
7719 ' {"0":{"+":"\\u2192",",":"\\u2190","-":"\\u2191",".":"\\u2193", ' +
7720 '"0":"\\u2588"}}'
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007721 ],
7722
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007723 'close-on-exit': [
7724 hterm.PreferenceManager.categories.Miscellaneous, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007725 'Whether or not to close the window when the command exits.'
7726 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007727
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007728 'cursor-blink': [
7729 hterm.PreferenceManager.categories.Appearance, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007730 'Whether or not to blink the cursor by default.'
7731 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007732
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007733 'cursor-blink-cycle': [
7734 hterm.PreferenceManager.categories.Appearance, [1000, 500], 'value',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007735 'The cursor blink rate in milliseconds.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007736 '\n' +
7737 'A two element array, the first of which is how long the cursor should be ' +
7738 'on, second is how long it should be off.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007739 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007740
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007741 'cursor-color': [
7742 hterm.PreferenceManager.categories.Appearance, 'rgba(255, 0, 0, 0.5)',
7743 'color', 'The color of the visible cursor.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007744 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007745
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007746 'color-palette-overrides': [
7747 hterm.PreferenceManager.categories.Appearance, null, 'value',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007748 'Override colors in the default palette.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007749 '\n' +
7750 'This can be specified as an array or an object. If specified as an ' +
7751 'object it is assumed to be a sparse array, where each property ' +
7752 'is a numeric index into the color palette.\n' +
7753 '\n' +
7754 'Values can be specified as almost any css color value. This ' +
7755 'includes #RGB, #RRGGBB, rgb(...), rgba(...), and any color names ' +
7756 'that are also part of the stock X11 rgb.txt file.\n' +
7757 '\n' +
7758 'You can use \'null\' to specify that the default value should be not ' +
7759 'be changed. This is useful for skipping a small number of indices ' +
7760 'when the value is specified as an array.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007761 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007762
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007763 'copy-on-select': [
7764 hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007765 'Automatically copy mouse selection to the clipboard.'
7766 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007767
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007768 'use-default-window-copy': [
7769 hterm.PreferenceManager.categories.CopyPaste, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007770 'Whether to use the default window copy behavior'
7771 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007772
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007773 'clear-selection-after-copy': [
7774 hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007775 'Whether to clear the selection after copying.'
7776 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007777
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007778 'ctrl-plus-minus-zero-zoom': [
7779 hterm.PreferenceManager.categories.Keyboard, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007780 'If true, Ctrl-Plus/Minus/Zero controls zoom.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007781 'If false, Ctrl-Shift-Plus/Minus/Zero controls zoom, Ctrl-Minus sends ^_, ' +
7782 'Ctrl-Plus/Zero do nothing.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007783 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007784
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007785 'ctrl-c-copy': [
7786 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007787 'Ctrl+C copies if true, send ^C to host if false.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007788 'Ctrl+Shift+C sends ^C to host if true, copies if false.'
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-v-paste': [
7792 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007793 'Ctrl+V pastes if true, send ^V to host if false.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007794 'Ctrl+Shift+V sends ^V to host if true, pastes 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 'east-asian-ambiguous-as-two-column': [
7798 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007799 'Set whether East Asian Ambiguous characters have two column width.'
7800 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007801
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007802 'enable-8-bit-control': [
7803 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007804 'True to enable 8-bit control characters, false to ignore them.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007805 '\n' +
7806 'We\'ll respect the two-byte versions of these control characters ' +
7807 'regardless of this setting.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007808 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007809
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007810 'enable-bold': [
7811 hterm.PreferenceManager.categories.Appearance, null, 'tristate',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007812 'True if we should use bold weight font for text with the bold/bright ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007813 'attribute. False to use the normal weight font. Null to autodetect.'
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-as-bright': [
7817 hterm.PreferenceManager.categories.Appearance, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007818 'True if we should use bright colors (8-15 on a 16 color palette) ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007819 'for any text with the bold attribute. False otherwise.'
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-blink': [
7823 hterm.PreferenceManager.categories.Appearance, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007824 'True if we should respect the blink attribute. False to ignore it. '
7825 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007826
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007827 'enable-clipboard-notice': [
7828 hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007829 'Show a message in the terminal when the host writes to the clipboard.'
7830 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007831
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007832 'enable-clipboard-write': [
7833 hterm.PreferenceManager.categories.CopyPaste, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007834 'Allow the host to write directly to the system clipboard.'
7835 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007836
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007837 'enable-dec12': [
7838 hterm.PreferenceManager.categories.Miscellaneous, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007839 'Respect the host\'s attempt to change the cursor blink status using ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007840 'DEC Private Mode 12.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007841 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007842
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007843 'environment': [
7844 hterm.PreferenceManager.categories.Miscellaneous,
7845 {'TERM': 'xterm-256color'}, 'value',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007846 'The default environment variables, as an object.'
7847 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007848
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007849 'font-family': [
7850 hterm.PreferenceManager.categories.Appearance,
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007851 '"DejaVu Sans Mono", "Everson Mono", FreeMono, "Menlo", "Terminal", ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007852 'monospace',
7853 'string', 'Default font family for the terminal text.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007854 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007855
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007856 'font-size': [
7857 hterm.PreferenceManager.categories.Appearance, 15, 'int',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007858 'The default font size in pixels.'
7859 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007860
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007861 'font-smoothing': [
7862 hterm.PreferenceManager.categories.Appearance, 'antialiased', 'string',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007863 'CSS font-smoothing property.'
7864 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007865
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007866 'foreground-color': [
7867 hterm.PreferenceManager.categories.Appearance, 'rgb(240, 240, 240)',
7868 'color', 'The foreground color for text with no other color attributes.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007869 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007870
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007871 'home-keys-scroll': [
7872 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007873 'If true, home/end will control the terminal scrollbar and shift home/end ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007874 'will send the VT keycodes. If false then home/end sends VT codes and ' +
7875 'shift home/end scrolls.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007876 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007877
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007878 'keybindings': [
7879 hterm.PreferenceManager.categories.Keyboard, null, 'value',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007880 'A map of key sequence to key actions. Key sequences include zero or ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007881 'more modifier keys followed by a key code. Key codes can be decimal or ' +
7882 'hexadecimal numbers, or a key identifier. Key actions can be specified ' +
7883 'a string to send to the host, or an action identifier. For a full ' +
7884 'list of key code and action identifiers, see https://goo.gl/8AoD09.' +
7885 '\n' +
7886 '\n' +
7887 'Sample keybindings:\n' +
7888 '{ "Ctrl-Alt-K": "clearScrollback",\n' +
7889 ' "Ctrl-Shift-L": "PASS",\n' +
7890 ' "Ctrl-H": "\'HELLO\\n\'"\n' +
7891 '}'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007892 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007893
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007894 'max-string-sequence': [
7895 hterm.PreferenceManager.categories.Encoding, 100000, 'int',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007896 'Max length of a DCS, OSC, PM, or APS sequence before we give up and ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007897 'ignore the code.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007898 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007899
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007900 'media-keys-are-fkeys': [
7901 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007902 'If true, convert media keys to their Fkey equivalent. If false, let ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007903 'the browser handle the keys.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007904 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007905
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007906 'meta-sends-escape': [
7907 hterm.PreferenceManager.categories.Keyboard, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007908 'Set whether the meta key sends a leading escape or not.'
7909 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007910
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007911 'mouse-paste-button': [
7912 hterm.PreferenceManager.categories.CopyPaste, null,
7913 [null, 0, 1, 2, 3, 4, 5, 6],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007914 'Mouse paste button, or null to autodetect.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007915 '\n' +
7916 'For autodetect, we\'ll try to enable middle button paste for non-X11 ' +
7917 'platforms. On X11 we move it to button 3.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007918 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007919
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007920 'page-keys-scroll': [
7921 hterm.PreferenceManager.categories.Keyboard, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007922 'If true, page up/down will control the terminal scrollbar and shift ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007923 'page up/down will send the VT keycodes. If false then page up/down ' +
7924 'sends VT codes and shift page up/down scrolls.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007925 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007926
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007927 'pass-alt-number': [
7928 hterm.PreferenceManager.categories.Keyboard, null, 'tristate',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007929 'Set whether we should pass Alt-1..9 to the browser.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007930 '\n' +
7931 'This is handy when running hterm in a browser tab, so that you don\'t ' +
7932 'lose Chrome\'s "switch to tab" keyboard accelerators. When not running ' +
7933 'in a tab it\'s better to send these keys to the host so they can be ' +
7934 'used in vim or emacs.\n' +
7935 '\n' +
7936 'If true, Alt-1..9 will be handled by the browser. If false, Alt-1..9 ' +
7937 'will be sent to the host. If null, autodetect based on browser platform ' +
7938 'and window type.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007939 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007940
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007941 'pass-ctrl-number': [
7942 hterm.PreferenceManager.categories.Keyboard, null, 'tristate',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007943 'Set whether we should pass Ctrl-1..9 to the browser.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007944 '\n' +
7945 'This is handy when running hterm in a browser tab, so that you don\'t ' +
7946 'lose Chrome\'s "switch to tab" keyboard accelerators. When not running ' +
7947 'in a tab it\'s better to send these keys to the host so they can be ' +
7948 'used in vim or emacs.\n' +
7949 '\n' +
7950 'If true, Ctrl-1..9 will be handled by the browser. If false, Ctrl-1..9 ' +
7951 'will be sent to the host. If null, autodetect based on browser platform ' +
7952 'and window type.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007953 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007954
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007955 'pass-meta-number': [
7956 hterm.PreferenceManager.categories.Keyboard, null, 'tristate',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007957 'Set whether we should pass Meta-1..9 to the browser.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007958 '\n' +
7959 'This is handy when running hterm in a browser tab, so that you don\'t ' +
7960 'lose Chrome\'s "switch to tab" keyboard accelerators. When not running ' +
7961 'in a tab it\'s better to send these keys to the host so they can be ' +
7962 'used in vim or emacs.\n' +
7963 '\n' +
7964 'If true, Meta-1..9 will be handled by the browser. If false, Meta-1..9 ' +
7965 'will be sent to the host. If null, autodetect based on browser platform ' +
7966 'and window type.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007967 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007968
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007969 'pass-meta-v': [
7970 hterm.PreferenceManager.categories.Keyboard, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007971 'Set whether meta-V gets passed to host.'
7972 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007973
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007974 'receive-encoding': [
7975 hterm.PreferenceManager.categories.Encoding, 'utf-8', ['utf-8', 'raw'],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007976 'Set the expected encoding for data received from the host.\n' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007977 '\n' +
7978 'Valid values are \'utf-8\' and \'raw\'.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007979 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007980
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007981 'scroll-on-keystroke': [
7982 hterm.PreferenceManager.categories.Scrolling, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007983 'If true, scroll to the bottom on any keystroke.'
7984 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007985
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007986 'scroll-on-output': [
7987 hterm.PreferenceManager.categories.Scrolling, false, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007988 'If true, scroll to the bottom on terminal output.'
7989 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007990
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007991 'scrollbar-visible': [
7992 hterm.PreferenceManager.categories.Scrolling, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007993 'The vertical scrollbar mode.'
7994 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05007995
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007996 'scroll-wheel-move-multiplier': [
7997 hterm.PreferenceManager.categories.Scrolling, 1, 'int',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07007998 'The multiplier for the pixel delta in mousewheel event caused by the ' +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07007999 'scroll wheel. Alters how fast the page scrolls.'
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008000 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008001
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008002 'send-encoding': [
8003 hterm.PreferenceManager.categories.Encoding, 'utf-8', ['utf-8', 'raw'],
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008004 'Set the encoding for data sent to host.'
8005 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008006
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008007 'shift-insert-paste': [
8008 hterm.PreferenceManager.categories.Keyboard, true, 'bool',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008009 'Shift + Insert pastes if true, sent to host if false.'
8010 ],
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008011
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008012 'user-css': [
8013 hterm.PreferenceManager.categories.Appearance, '', 'url',
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008014 'URL of user stylesheet to include in the terminal document.'
8015 ]
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008016};
8017
8018hterm.PreferenceManager.prototype = {
8019 __proto__: lib.PreferenceManager.prototype
8020};
8021// SOURCE FILE: hterm/js/hterm_pubsub.js
8022// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
8023// Use of this source code is governed by a BSD-style license that can be
8024// found in the LICENSE file.
8025
8026'use strict';
8027
8028/**
8029 * Utility class used to add publish/subscribe/unsubscribe functionality to
8030 * an existing object.
8031 */
8032hterm.PubSub = function() {
8033 this.observers_ = {};
8034};
8035
8036/**
8037 * Add publish, subscribe, and unsubscribe methods to an existing object.
8038 *
8039 * No other properties of the object are touched, so there is no need to
8040 * worry about clashing private properties.
8041 *
8042 * @param {Object} obj The object to add this behavior to.
8043 */
8044hterm.PubSub.addBehavior = function(obj) {
8045 var pubsub = new hterm.PubSub();
8046 for (var m in hterm.PubSub.prototype) {
8047 obj[m] = hterm.PubSub.prototype[m].bind(pubsub);
8048 }
8049};
8050
8051/**
8052 * Subscribe to be notified of messages about a subject.
8053 *
8054 * @param {string} subject The subject to subscribe to.
8055 * @param {function(Object)} callback The function to invoke for notifications.
8056 */
8057hterm.PubSub.prototype.subscribe = function(subject, callback) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008058 if (!(subject in this.observers_)) this.observers_[subject] = [];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008059
8060 this.observers_[subject].push(callback);
8061};
8062
8063/**
8064 * Unsubscribe from a subject.
8065 *
8066 * @param {string} subject The subject to unsubscribe from.
8067 * @param {function(Object)} callback A callback previously registered via
8068 * subscribe().
8069 */
8070hterm.PubSub.prototype.unsubscribe = function(subject, callback) {
8071 var list = this.observers_[subject];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008072 if (!list) throw 'Invalid subject: ' + subject;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008073
8074 var i = list.indexOf(callback);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008075 if (i < 0) throw 'Not subscribed: ' + subject;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008076
8077 list.splice(i, 1);
8078};
8079
8080/**
8081 * Publish a message about a subject.
8082 *
8083 * Subscribers (and the optional final callback) are invoked asynchronously.
8084 * This method will return before anyone is actually notified.
8085 *
8086 * @param {string} subject The subject to publish about.
8087 * @param {Object} e An arbitrary object associated with this notification.
8088 * @param {function(Object)} opt_lastCallback An optional function to call after
8089 * all subscribers have been notified.
8090 */
8091hterm.PubSub.prototype.publish = function(subject, e, opt_lastCallback) {
8092 function notifyList(i) {
8093 // Set this timeout before invoking the callback, so we don't have to
8094 // concern ourselves with exceptions.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008095 if (i < list.length - 1) setTimeout(notifyList, 0, i + 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008096
8097 list[i](e);
8098 }
8099
8100 var list = this.observers_[subject];
8101 if (list) {
8102 // Copy the list, in case it changes while we're notifying.
8103 list = [].concat(list);
8104 }
8105
8106 if (opt_lastCallback) {
8107 if (list) {
8108 list.push(opt_lastCallback);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008109 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008110 list = [opt_lastCallback];
8111 }
8112 }
8113
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008114 if (list) setTimeout(notifyList, 0, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008115};
8116// SOURCE FILE: hterm/js/hterm_screen.js
8117// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
8118// Use of this source code is governed by a BSD-style license that can be
8119// found in the LICENSE file.
8120
8121'use strict';
8122
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008123lib.rtdep(
8124 'lib.f', 'lib.wc', 'hterm.RowCol', 'hterm.Size', 'hterm.TextAttributes');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008125
8126/**
8127 * @fileoverview This class represents a single terminal screen full of text.
8128 *
8129 * It maintains the current cursor position and has basic methods for text
8130 * insert and overwrite, and adding or removing rows from the screen.
8131 *
8132 * This class has no knowledge of the scrollback buffer.
8133 *
8134 * The number of rows on the screen is determined only by the number of rows
8135 * that the caller inserts into the screen. If a caller wants to ensure a
8136 * constant number of rows on the screen, it's their responsibility to remove a
8137 * row for each row inserted.
8138 *
8139 * The screen width, in contrast, is enforced locally.
8140 *
8141 *
8142 * In practice...
8143 * - The hterm.Terminal class holds two hterm.Screen instances. One for the
8144 * primary screen and one for the alternate screen.
8145 *
8146 * - The html.Screen class only cares that rows are HTMLElements. In the
8147 * larger context of hterm, however, the rows happen to be displayed by an
8148 * hterm.ScrollPort and have to follow a few rules as a result. Each
8149 * row must be rooted by the custom HTML tag 'x-row', and each must have a
8150 * rowIndex property that corresponds to the index of the row in the context
8151 * of the scrollback buffer. These invariants are enforced by hterm.Terminal
8152 * because that is the class using the hterm.Screen in the context of an
8153 * hterm.ScrollPort.
8154 */
8155
8156/**
8157 * Create a new screen instance.
8158 *
8159 * The screen initially has no rows and a maximum column count of 0.
8160 *
8161 * @param {integer} opt_columnCount The maximum number of columns for this
8162 * screen. See insertString() and overwriteString() for information about
8163 * what happens when too many characters are added too a row. Defaults to
8164 * 0 if not provided.
8165 */
8166hterm.Screen = function(opt_columnCount) {
8167 /**
8168 * Public, read-only access to the rows in this screen.
8169 */
8170 this.rowsArray = [];
8171
8172 // The max column width for this screen.
8173 this.columnCount_ = opt_columnCount || 80;
8174
8175 // The current color, bold, underline and blink attributes.
8176 this.textAttributes = new hterm.TextAttributes(window.document);
8177
8178 // Current zero-based cursor coordinates.
8179 this.cursorPosition = new hterm.RowCol(0, 0);
8180
8181 // The node containing the row that the cursor is positioned on.
8182 this.cursorRowNode_ = null;
8183
8184 // The node containing the span of text that the cursor is positioned on.
8185 this.cursorNode_ = null;
8186
8187 // The offset in column width into cursorNode_ where the cursor is positioned.
8188 this.cursorOffset_ = null;
8189};
8190
8191/**
8192 * Return the screen size as an hterm.Size object.
8193 *
8194 * @return {hterm.Size} hterm.Size object representing the current number
8195 * of rows and columns in this screen.
8196 */
8197hterm.Screen.prototype.getSize = function() {
8198 return new hterm.Size(this.columnCount_, this.rowsArray.length);
8199};
8200
8201/**
8202 * Return the current number of rows in this screen.
8203 *
8204 * @return {integer} The number of rows in this screen.
8205 */
8206hterm.Screen.prototype.getHeight = function() {
8207 return this.rowsArray.length;
8208};
8209
8210/**
8211 * Return the current number of columns in this screen.
8212 *
8213 * @return {integer} The number of columns in this screen.
8214 */
8215hterm.Screen.prototype.getWidth = function() {
8216 return this.columnCount_;
8217};
8218
8219/**
8220 * Set the maximum number of columns per row.
8221 *
8222 * @param {integer} count The maximum number of columns per row.
8223 */
8224hterm.Screen.prototype.setColumnCount = function(count) {
8225 this.columnCount_ = count;
8226
8227 if (this.cursorPosition.column >= count)
8228 this.setCursorPosition(this.cursorPosition.row, count - 1);
8229};
8230
8231/**
8232 * Remove the first row from the screen and return it.
8233 *
8234 * @return {HTMLElement} The first row in this screen.
8235 */
8236hterm.Screen.prototype.shiftRow = function() {
8237 return this.shiftRows(1)[0];
8238};
8239
8240/**
8241 * Remove rows from the top of the screen and return them as an array.
8242 *
8243 * @param {integer} count The number of rows to remove.
8244 * @return {Array.<HTMLElement>} The selected rows.
8245 */
8246hterm.Screen.prototype.shiftRows = function(count) {
8247 return this.rowsArray.splice(0, count);
8248};
8249
8250/**
8251 * Insert a row at the top of the screen.
8252 *
8253 * @param {HTMLElement} row The row to insert.
8254 */
8255hterm.Screen.prototype.unshiftRow = function(row) {
8256 this.rowsArray.splice(0, 0, row);
8257};
8258
8259/**
8260 * Insert rows at the top of the screen.
8261 *
8262 * @param {Array.<HTMLElement>} rows The rows to insert.
8263 */
8264hterm.Screen.prototype.unshiftRows = function(rows) {
8265 this.rowsArray.unshift.apply(this.rowsArray, rows);
8266};
8267
8268/**
8269 * Remove the last row from the screen and return it.
8270 *
8271 * @return {HTMLElement} The last row in this screen.
8272 */
8273hterm.Screen.prototype.popRow = function() {
8274 return this.popRows(1)[0];
8275};
8276
8277/**
8278 * Remove rows from the bottom of the screen and return them as an array.
8279 *
8280 * @param {integer} count The number of rows to remove.
8281 * @return {Array.<HTMLElement>} The selected rows.
8282 */
8283hterm.Screen.prototype.popRows = function(count) {
8284 return this.rowsArray.splice(this.rowsArray.length - count, count);
8285};
8286
8287/**
8288 * Insert a row at the bottom of the screen.
8289 *
8290 * @param {HTMLElement} row The row to insert.
8291 */
8292hterm.Screen.prototype.pushRow = function(row) {
8293 this.rowsArray.push(row);
8294};
8295
8296/**
8297 * Insert rows at the bottom of the screen.
8298 *
8299 * @param {Array.<HTMLElement>} rows The rows to insert.
8300 */
8301hterm.Screen.prototype.pushRows = function(rows) {
8302 rows.push.apply(this.rowsArray, rows);
8303};
8304
8305/**
8306 * Insert a row at the specified row of the screen.
8307 *
8308 * @param {integer} index The index to insert the row.
8309 * @param {HTMLElement} row The row to insert.
8310 */
8311hterm.Screen.prototype.insertRow = function(index, row) {
8312 this.rowsArray.splice(index, 0, row);
8313};
8314
8315/**
8316 * Insert rows at the specified row of the screen.
8317 *
8318 * @param {integer} index The index to insert the rows.
8319 * @param {Array.<HTMLElement>} rows The rows to insert.
8320 */
8321hterm.Screen.prototype.insertRows = function(index, rows) {
8322 for (var i = 0; i < rows.length; i++) {
8323 this.rowsArray.splice(index + i, 0, rows[i]);
8324 }
8325};
8326
8327/**
8328 * Remove a row from the screen and return it.
8329 *
8330 * @param {integer} index The index of the row to remove.
8331 * @return {HTMLElement} The selected row.
8332 */
8333hterm.Screen.prototype.removeRow = function(index) {
8334 return this.rowsArray.splice(index, 1)[0];
8335};
8336
8337/**
8338 * Remove rows from the bottom of the screen and return them as an array.
8339 *
8340 * @param {integer} index The index to start removing rows.
8341 * @param {integer} count The number of rows to remove.
8342 * @return {Array.<HTMLElement>} The selected rows.
8343 */
8344hterm.Screen.prototype.removeRows = function(index, count) {
8345 return this.rowsArray.splice(index, count);
8346};
8347
8348/**
8349 * Invalidate the current cursor position.
8350 *
8351 * This sets this.cursorPosition to (0, 0) and clears out some internal
8352 * data.
8353 *
8354 * Attempting to insert or overwrite text while the cursor position is invalid
8355 * will raise an obscure exception.
8356 */
8357hterm.Screen.prototype.invalidateCursorPosition = function() {
8358 this.cursorPosition.move(0, 0);
8359 this.cursorRowNode_ = null;
8360 this.cursorNode_ = null;
8361 this.cursorOffset_ = null;
8362};
8363
8364/**
8365 * Clear the contents of the cursor row.
8366 */
8367hterm.Screen.prototype.clearCursorRow = function() {
8368 this.cursorRowNode_.innerHTML = '';
8369 this.cursorRowNode_.removeAttribute('line-overflow');
8370 this.cursorOffset_ = 0;
8371 this.cursorPosition.column = 0;
8372 this.cursorPosition.overflow = false;
8373
8374 var text;
8375 if (this.textAttributes.isDefault()) {
8376 text = '';
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008377 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008378 text = lib.f.getWhitespace(this.columnCount_);
8379 }
8380
8381 // We shouldn't honor inverse colors when clearing an area, to match
8382 // xterm's back color erase behavior.
8383 var inverse = this.textAttributes.inverse;
8384 this.textAttributes.inverse = false;
8385 this.textAttributes.syncColors();
8386
8387 var node = this.textAttributes.createContainer(text);
8388 this.cursorRowNode_.appendChild(node);
8389 this.cursorNode_ = node;
8390
8391 this.textAttributes.inverse = inverse;
8392 this.textAttributes.syncColors();
8393};
8394
8395/**
8396 * Mark the current row as having overflowed to the next line.
8397 *
8398 * The line overflow state is used when converting a range of rows into text.
8399 * It makes it possible to recombine two or more overflow terminal rows into
8400 * a single line.
8401 *
8402 * This is distinct from the cursor being in the overflow state. Cursor
8403 * overflow indicates that printing at the cursor position will commit a
8404 * line overflow, unless it is preceded by a repositioning of the cursor
8405 * to a non-overflow state.
8406 */
8407hterm.Screen.prototype.commitLineOverflow = function() {
8408 this.cursorRowNode_.setAttribute('line-overflow', true);
8409};
8410
8411/**
8412 * Relocate the cursor to a give row and column.
8413 *
8414 * @param {integer} row The zero based row.
8415 * @param {integer} column The zero based column.
8416 */
8417hterm.Screen.prototype.setCursorPosition = function(row, column) {
8418 if (!this.rowsArray.length) {
8419 console.warn('Attempt to set cursor position on empty screen.');
8420 return;
8421 }
8422
8423 if (row >= this.rowsArray.length) {
8424 console.error('Row out of bounds: ' + row);
8425 row = this.rowsArray.length - 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008426 } else if (row < 0) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008427 console.error('Row out of bounds: ' + row);
8428 row = 0;
8429 }
8430
8431 if (column >= this.columnCount_) {
8432 console.error('Column out of bounds: ' + column);
8433 column = this.columnCount_ - 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008434 } else if (column < 0) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008435 console.error('Column out of bounds: ' + column);
8436 column = 0;
8437 }
8438
8439 this.cursorPosition.overflow = false;
8440
8441 var rowNode = this.rowsArray[row];
8442 var node = rowNode.firstChild;
8443
8444 if (!node) {
8445 node = rowNode.ownerDocument.createTextNode('');
8446 rowNode.appendChild(node);
8447 }
8448
8449 var currentColumn = 0;
8450
8451 if (rowNode == this.cursorRowNode_) {
8452 if (column >= this.cursorPosition.column - this.cursorOffset_) {
8453 node = this.cursorNode_;
8454 currentColumn = this.cursorPosition.column - this.cursorOffset_;
8455 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008456 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008457 this.cursorRowNode_ = rowNode;
8458 }
8459
8460 this.cursorPosition.move(row, column);
8461
8462 while (node) {
8463 var offset = column - currentColumn;
8464 var width = hterm.TextAttributes.nodeWidth(node);
8465 if (!node.nextSibling || width > offset) {
8466 this.cursorNode_ = node;
8467 this.cursorOffset_ = offset;
8468 return;
8469 }
8470
8471 currentColumn += width;
8472 node = node.nextSibling;
8473 }
8474};
8475
8476/**
8477 * Set the provided selection object to be a caret selection at the current
8478 * cursor position.
8479 */
8480hterm.Screen.prototype.syncSelectionCaret = function(selection) {
8481 try {
8482 selection.collapse(this.cursorNode_, this.cursorOffset_);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008483 } catch (firefoxIgnoredException) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008484 // FF can throw an exception if the range is off, rather than just not
8485 // performing the collapse.
8486 }
8487};
8488
8489/**
8490 * Split a single node into two nodes at the given offset.
8491 *
8492 * For example:
8493 * Given the DOM fragment '<div><span>Hello World</span></div>', call splitNode_
8494 * passing the span and an offset of 6. This would modify the fragment to
8495 * become: '<div><span>Hello </span><span>World</span></div>'. If the span
8496 * had any attributes they would have been copied to the new span as well.
8497 *
8498 * The to-be-split node must have a container, so that the new node can be
8499 * placed next to it.
8500 *
8501 * @param {HTMLNode} node The node to split.
8502 * @param {integer} offset The offset into the node where the split should
8503 * occur.
8504 */
8505hterm.Screen.prototype.splitNode_ = function(node, offset) {
8506 var afterNode = node.cloneNode(false);
8507
8508 var textContent = node.textContent;
8509 node.textContent = hterm.TextAttributes.nodeSubstr(node, 0, offset);
8510 afterNode.textContent = lib.wc.substr(textContent, offset);
8511
8512 if (afterNode.textContent)
8513 node.parentNode.insertBefore(afterNode, node.nextSibling);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008514 if (!node.textContent) node.parentNode.removeChild(node);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008515};
8516
8517/**
8518 * Ensure that text is clipped and the cursor is clamped to the column count.
8519 */
8520hterm.Screen.prototype.maybeClipCurrentRow = function() {
8521 var width = hterm.TextAttributes.nodeWidth(this.cursorRowNode_);
8522
8523 if (width <= this.columnCount_) {
8524 // Current row does not need clipping, but may need clamping.
8525 if (this.cursorPosition.column >= this.columnCount_) {
8526 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
8527 this.cursorPosition.overflow = true;
8528 }
8529
8530 return;
8531 }
8532
8533 // Save off the current column so we can maybe restore it later.
8534 var currentColumn = this.cursorPosition.column;
8535
8536 // Move the cursor to the final column.
8537 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
8538
8539 // Remove any text that partially overflows.
8540 width = hterm.TextAttributes.nodeWidth(this.cursorNode_);
8541
8542 if (this.cursorOffset_ < width - 1) {
8543 this.cursorNode_.textContent = hterm.TextAttributes.nodeSubstr(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008544 this.cursorNode_, 0, this.cursorOffset_ + 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008545 }
8546
8547 // Remove all nodes after the cursor.
8548 var rowNode = this.cursorRowNode_;
8549 var node = this.cursorNode_.nextSibling;
8550
8551 while (node) {
8552 rowNode.removeChild(node);
8553 node = this.cursorNode_.nextSibling;
8554 }
8555
8556 if (currentColumn < this.columnCount_) {
8557 // If the cursor was within the screen before we started then restore its
8558 // position.
8559 this.setCursorPosition(this.cursorPosition.row, currentColumn);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008560 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008561 // Otherwise leave it at the the last column in the overflow state.
8562 this.cursorPosition.overflow = true;
8563 }
8564};
8565
8566/**
8567 * Insert a string at the current character position using the current
8568 * text attributes.
8569 *
8570 * You must call maybeClipCurrentRow() after in order to clip overflowed
8571 * text and clamp the cursor.
8572 *
8573 * It is also up to the caller to properly maintain the line overflow state
8574 * using hterm.Screen..commitLineOverflow().
8575 */
8576hterm.Screen.prototype.insertString = function(str) {
8577 var cursorNode = this.cursorNode_;
8578 var cursorNodeText = cursorNode.textContent;
8579
8580 this.cursorRowNode_.removeAttribute('line-overflow');
8581
8582 // We may alter the width of the string by prepending some missing
8583 // whitespaces, so we need to record the string width ahead of time.
8584 var strWidth = lib.wc.strWidth(str);
8585
8586 // No matter what, before this function exits the cursor column will have
8587 // moved this much.
8588 this.cursorPosition.column += strWidth;
8589
8590 // Local cache of the cursor offset.
8591 var offset = this.cursorOffset_;
8592
8593 // Reverse offset is the offset measured from the end of the string.
8594 // Zero implies that the cursor is at the end of the cursor node.
8595 var reverseOffset = hterm.TextAttributes.nodeWidth(cursorNode) - offset;
8596
8597 if (reverseOffset < 0) {
8598 // A negative reverse offset means the cursor is positioned past the end
8599 // of the characters on this line. We'll need to insert the missing
8600 // whitespace.
8601 var ws = lib.f.getWhitespace(-reverseOffset);
8602
8603 // This whitespace should be completely unstyled. Underline, background
8604 // color, and strikethrough would be visible on whitespace, so we can't use
8605 // one of those spans to hold the text.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008606 if (!(this.textAttributes.underline || this.textAttributes.strikethrough ||
8607 this.textAttributes.background || this.textAttributes.wcNode ||
8608 this.textAttributes.tileData != null)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008609 // Best case scenario, we can just pretend the spaces were part of the
8610 // original string.
8611 str = ws + str;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008612 } else if (
8613 cursorNode.nodeType == 3 ||
8614 !(cursorNode.wcNode || cursorNode.tileNode ||
8615 cursorNode.style.textDecoration ||
8616 cursorNode.style.backgroundColor)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008617 // Second best case, the current node is able to hold the whitespace.
8618 cursorNode.textContent = (cursorNodeText += ws);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008619 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008620 // Worst case, we have to create a new node to hold the whitespace.
8621 var wsNode = cursorNode.ownerDocument.createTextNode(ws);
8622 this.cursorRowNode_.insertBefore(wsNode, cursorNode.nextSibling);
8623 this.cursorNode_ = cursorNode = wsNode;
8624 this.cursorOffset_ = offset = -reverseOffset;
8625 cursorNodeText = ws;
8626 }
8627
8628 // We now know for sure that we're at the last character of the cursor node.
8629 reverseOffset = 0;
8630 }
8631
8632 if (this.textAttributes.matchesContainer(cursorNode)) {
8633 // The new text can be placed directly in the cursor node.
8634 if (reverseOffset == 0) {
8635 cursorNode.textContent = cursorNodeText + str;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008636 } else if (offset == 0) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008637 cursorNode.textContent = str + cursorNodeText;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008638 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008639 cursorNode.textContent =
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008640 hterm.TextAttributes.nodeSubstr(cursorNode, 0, offset) + str +
8641 hterm.TextAttributes.nodeSubstr(cursorNode, offset);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008642 }
8643
8644 this.cursorOffset_ += strWidth;
8645 return;
8646 }
8647
8648 // The cursor node is the wrong style for the new text. If we're at the
8649 // beginning or end of the cursor node, then the adjacent node is also a
8650 // potential candidate.
8651
8652 if (offset == 0) {
8653 // At the beginning of the cursor node, the check the previous sibling.
8654 var previousSibling = cursorNode.previousSibling;
8655 if (previousSibling &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008656 this.textAttributes.matchesContainer(previousSibling)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008657 previousSibling.textContent += str;
8658 this.cursorNode_ = previousSibling;
8659 this.cursorOffset_ = lib.wc.strWidth(previousSibling.textContent);
8660 return;
8661 }
8662
8663 var newNode = this.textAttributes.createContainer(str);
8664 this.cursorRowNode_.insertBefore(newNode, cursorNode);
8665 this.cursorNode_ = newNode;
8666 this.cursorOffset_ = strWidth;
8667 return;
8668 }
8669
8670 if (reverseOffset == 0) {
8671 // At the end of the cursor node, the check the next sibling.
8672 var nextSibling = cursorNode.nextSibling;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008673 if (nextSibling && this.textAttributes.matchesContainer(nextSibling)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008674 nextSibling.textContent = str + nextSibling.textContent;
8675 this.cursorNode_ = nextSibling;
8676 this.cursorOffset_ = lib.wc.strWidth(str);
8677 return;
8678 }
8679
8680 var newNode = this.textAttributes.createContainer(str);
8681 this.cursorRowNode_.insertBefore(newNode, nextSibling);
8682 this.cursorNode_ = newNode;
8683 // We specifically need to include any missing whitespace here, since it's
8684 // going in a new node.
8685 this.cursorOffset_ = hterm.TextAttributes.nodeWidth(newNode);
8686 return;
8687 }
8688
8689 // Worst case, we're somewhere in the middle of the cursor node. We'll
8690 // have to split it into two nodes and insert our new container in between.
8691 this.splitNode_(cursorNode, offset);
8692 var newNode = this.textAttributes.createContainer(str);
8693 this.cursorRowNode_.insertBefore(newNode, cursorNode.nextSibling);
8694 this.cursorNode_ = newNode;
8695 this.cursorOffset_ = strWidth;
8696};
8697
8698/**
8699 * Overwrite the text at the current cursor position.
8700 *
8701 * You must call maybeClipCurrentRow() after in order to clip overflowed
8702 * text and clamp the cursor.
8703 *
8704 * It is also up to the caller to properly maintain the line overflow state
8705 * using hterm.Screen..commitLineOverflow().
8706 */
8707hterm.Screen.prototype.overwriteString = function(str) {
8708 var maxLength = this.columnCount_ - this.cursorPosition.column;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008709 if (!maxLength) return [str];
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008710
8711 var width = lib.wc.strWidth(str);
8712 if (this.textAttributes.matchesContainer(this.cursorNode_) &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008713 this.cursorNode_.textContent.substr(this.cursorOffset_) == str) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008714 // This overwrite would be a no-op, just move the cursor and return.
8715 this.cursorOffset_ += width;
8716 this.cursorPosition.column += width;
8717 return;
8718 }
8719
8720 this.deleteChars(Math.min(width, maxLength));
8721 this.insertString(str);
8722};
8723
8724/**
8725 * Forward-delete one or more characters at the current cursor position.
8726 *
8727 * Text to the right of the deleted characters is shifted left. Only affects
8728 * characters on the same row as the cursor.
8729 *
8730 * @param {integer} count The column width of characters to delete. This is
8731 * clamped to the column width minus the cursor column.
8732 * @return {integer} The column width of the characters actually deleted.
8733 */
8734hterm.Screen.prototype.deleteChars = function(count) {
8735 var node = this.cursorNode_;
8736 var offset = this.cursorOffset_;
8737
8738 var currentCursorColumn = this.cursorPosition.column;
8739 count = Math.min(count, this.columnCount_ - currentCursorColumn);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008740 if (!count) return 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008741
8742 var rv = count;
8743 var startLength, endLength;
8744
8745 while (node && count) {
8746 startLength = hterm.TextAttributes.nodeWidth(node);
8747 node.textContent = hterm.TextAttributes.nodeSubstr(node, 0, offset) +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008748 hterm.TextAttributes.nodeSubstr(node, offset + count);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008749 endLength = hterm.TextAttributes.nodeWidth(node);
8750 count -= startLength - endLength;
8751 if (offset < startLength && endLength && startLength == endLength) {
8752 // No characters were deleted when there should be. We're probably trying
8753 // to delete one column width from a wide character node. We remove the
8754 // wide character node here and replace it with a single space.
8755 var spaceNode = this.textAttributes.createContainer(' ');
8756 node.parentNode.insertBefore(spaceNode, node.nextSibling);
8757 node.textContent = '';
8758 endLength = 0;
8759 count -= 1;
8760 }
8761
8762 var nextNode = node.nextSibling;
8763 if (endLength == 0 && node != this.cursorNode_) {
8764 node.parentNode.removeChild(node);
8765 }
8766 node = nextNode;
8767 offset = 0;
8768 }
8769
8770 // Remove this.cursorNode_ if it is an empty non-text node.
8771 if (this.cursorNode_.nodeType != 3 && !this.cursorNode_.textContent) {
8772 var cursorNode = this.cursorNode_;
8773 if (cursorNode.previousSibling) {
8774 this.cursorNode_ = cursorNode.previousSibling;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008775 this.cursorOffset_ =
8776 hterm.TextAttributes.nodeWidth(cursorNode.previousSibling);
8777 } else if (cursorNode.nextSibling) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008778 this.cursorNode_ = cursorNode.nextSibling;
8779 this.cursorOffset_ = 0;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008780 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008781 var emptyNode = this.cursorRowNode_.ownerDocument.createTextNode('');
8782 this.cursorRowNode_.appendChild(emptyNode);
8783 this.cursorNode_ = emptyNode;
8784 this.cursorOffset_ = 0;
8785 }
8786 this.cursorRowNode_.removeChild(cursorNode);
8787 }
8788
8789 return rv;
8790};
8791
8792/**
8793 * Finds first X-ROW of a line containing specified X-ROW.
8794 * Used to support line overflow.
8795 *
8796 * @param {Node} row X-ROW to begin search for first row of line.
8797 * @return {Node} The X-ROW that is at the beginning of the line.
8798 **/
8799hterm.Screen.prototype.getLineStartRow_ = function(row) {
8800 while (row.previousSibling &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008801 row.previousSibling.hasAttribute('line-overflow')) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008802 row = row.previousSibling;
8803 }
8804 return row;
8805};
8806
8807/**
8808 * Gets text of a line beginning with row.
8809 * Supports line overflow.
8810 *
8811 * @param {Node} row First X-ROW of line.
8812 * @return {string} Text content of line.
8813 **/
8814hterm.Screen.prototype.getLineText_ = function(row) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008815 var rowText = '';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008816 while (row) {
8817 rowText += row.textContent;
8818 if (row.hasAttribute('line-overflow')) {
8819 row = row.nextSibling;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008820 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008821 break;
8822 }
8823 }
8824 return rowText;
8825};
8826
8827/**
8828 * Returns X-ROW that is ancestor of the node.
8829 *
8830 * @param {Node} node Node to get X-ROW ancestor for.
8831 * @return {Node} X-ROW ancestor of node, or null if not found.
8832 **/
8833hterm.Screen.prototype.getXRowAncestor_ = function(node) {
8834 while (node) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008835 if (node.nodeName === 'X-ROW') break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008836 node = node.parentNode;
8837 }
8838 return node;
8839};
8840
8841/**
8842 * Returns position within line of character at offset within node.
8843 * Supports line overflow.
8844 *
8845 * @param {Node} row X-ROW at beginning of line.
8846 * @param {Node} node Node to get position of.
8847 * @param {integer} offset Offset into node.
8848 *
8849 * @return {integer} Position within line of character at offset within node.
8850 **/
8851hterm.Screen.prototype.getPositionWithOverflow_ = function(row, node, offset) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008852 if (!node) return -1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008853 var ancestorRow = this.getXRowAncestor_(node);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008854 if (!ancestorRow) return -1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008855 var position = 0;
8856 while (ancestorRow != row) {
8857 position += hterm.TextAttributes.nodeWidth(row);
8858 if (row.hasAttribute('line-overflow') && row.nextSibling) {
8859 row = row.nextSibling;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008860 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008861 return -1;
8862 }
8863 }
8864 return position + this.getPositionWithinRow_(row, node, offset);
8865};
8866
8867/**
8868 * Returns position within row of character at offset within node.
8869 * Does not support line overflow.
8870 *
8871 * @param {Node} row X-ROW to get position within.
8872 * @param {Node} node Node to get position for.
8873 * @param {integer} offset Offset within node to get position for.
8874 * @return {integer} Position within row of character at offset within node.
8875 **/
8876hterm.Screen.prototype.getPositionWithinRow_ = function(row, node, offset) {
8877 if (node.parentNode != row) {
8878 return this.getPositionWithinRow_(node.parentNode, node, offset) +
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008879 this.getPositionWithinRow_(row, node.parentNode, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008880 }
8881 var position = 0;
8882 for (var i = 0; i < row.childNodes.length; i++) {
8883 var currentNode = row.childNodes[i];
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008884 if (currentNode == node) return position + offset;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008885 position += hterm.TextAttributes.nodeWidth(currentNode);
8886 }
8887 return -1;
8888};
8889
8890/**
8891 * Returns the node and offset corresponding to position within line.
8892 * Supports line overflow.
8893 *
8894 * @param {Node} row X-ROW at beginning of line.
8895 * @param {integer} position Position within line to retrieve node and offset.
8896 * @return {Array} Two element array containing node and offset respectively.
8897 **/
8898hterm.Screen.prototype.getNodeAndOffsetWithOverflow_ = function(row, position) {
8899 while (row && position > hterm.TextAttributes.nodeWidth(row)) {
8900 if (row.hasAttribute('line-overflow') && row.nextSibling) {
8901 position -= hterm.TextAttributes.nodeWidth(row);
8902 row = row.nextSibling;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008903 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008904 return -1;
8905 }
8906 }
8907 return this.getNodeAndOffsetWithinRow_(row, position);
8908};
8909
8910/**
8911 * Returns the node and offset corresponding to position within row.
8912 * Does not support line overflow.
8913 *
8914 * @param {Node} row X-ROW to get position within.
8915 * @param {integer} position Position within row to retrieve node and offset.
8916 * @return {Array} Two element array containing node and offset respectively.
8917 **/
8918hterm.Screen.prototype.getNodeAndOffsetWithinRow_ = function(row, position) {
8919 for (var i = 0; i < row.childNodes.length; i++) {
8920 var node = row.childNodes[i];
8921 var nodeTextWidth = hterm.TextAttributes.nodeWidth(node);
8922 if (position <= nodeTextWidth) {
8923 if (node.nodeName === 'SPAN') {
8924 /** Drill down to node contained by SPAN. **/
8925 return this.getNodeAndOffsetWithinRow_(node, position);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008926 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008927 return [node, position];
8928 }
8929 }
8930 position -= nodeTextWidth;
8931 }
8932 return null;
8933};
8934
8935/**
8936 * Returns the node and offset corresponding to position within line.
8937 * Supports line overflow.
8938 *
8939 * @param {Node} row X-ROW at beginning of line.
8940 * @param {integer} start Start position of range within line.
8941 * @param {integer} end End position of range within line.
8942 * @param {Range} range Range to modify.
8943 **/
8944hterm.Screen.prototype.setRange_ = function(row, start, end, range) {
8945 var startNodeAndOffset = this.getNodeAndOffsetWithOverflow_(row, start);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008946 if (startNodeAndOffset == null) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008947 var endNodeAndOffset = this.getNodeAndOffsetWithOverflow_(row, end);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008948 if (endNodeAndOffset == null) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008949 range.setStart(startNodeAndOffset[0], startNodeAndOffset[1]);
8950 range.setEnd(endNodeAndOffset[0], endNodeAndOffset[1]);
8951};
8952
8953/**
8954 * Expands selection to surround URLs.
8955 *
8956 * @param {Selection} selection Selection to expand.
8957 **/
8958hterm.Screen.prototype.expandSelection = function(selection) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008959 if (!selection) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008960
8961 var range = selection.getRangeAt(0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008962 if (!range || range.toString().match(/\s/)) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008963
8964 var row = this.getLineStartRow_(this.getXRowAncestor_(range.startContainer));
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008965 if (!row) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008966
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008967 var startPosition = this.getPositionWithOverflow_(
8968 row, range.startContainer, range.startOffset);
8969 if (startPosition == -1) return;
8970 var endPosition =
8971 this.getPositionWithOverflow_(row, range.endContainer, range.endOffset);
8972 if (endPosition == -1) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008973
8974 // Matches can start with '~' or '.', since paths frequently do.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008975 var leftMatch = '[^\\s\\[\\](){}<>"\'\\^!@#$%&*,;:`]';
8976 var rightMatch = '[^\\s\\[\\](){}<>"\'\\^!@#$%&*,;:~.`]';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008977 var insideMatch = '[^\\s\\[\\](){}<>"\'\\^]*';
8978
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008979 // Move start to the left.
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008980 var rowText = this.getLineText_(row);
8981 var lineUpToRange = lib.wc.substring(rowText, 0, endPosition);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008982 var leftRegularExpression = new RegExp(leftMatch + insideMatch + '$');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008983 var expandedStart = lineUpToRange.search(leftRegularExpression);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008984 if (expandedStart == -1 || expandedStart > startPosition) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008985
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008986 // Move end to the right.
8987 var lineFromRange =
8988 lib.wc.substring(rowText, startPosition, lib.wc.strWidth(rowText));
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07008989 var rightRegularExpression = new RegExp('^' + insideMatch + rightMatch);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008990 var found = lineFromRange.match(rightRegularExpression);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008991 if (!found) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008992 var expandedEnd = startPosition + lib.wc.strWidth(found[0]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07008993 if (expandedEnd == -1 || expandedEnd < endPosition) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05008994
8995 this.setRange_(row, expandedStart, expandedEnd, range);
8996 selection.addRange(range);
8997};
8998// SOURCE FILE: hterm/js/hterm_scrollport.js
8999// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
9000// Use of this source code is governed by a BSD-style license that can be
9001// found in the LICENSE file.
9002
9003'use strict';
9004
9005lib.rtdep('lib.f', 'hterm.PubSub', 'hterm.Size');
9006
9007/**
9008 * A 'viewport' view of fixed-height rows with support for selection and
9009 * copy-to-clipboard.
9010 *
9011 * 'Viewport' in this case means that only the visible rows are in the DOM.
9012 * If the rowProvider has 100,000 rows, but the ScrollPort is only 25 rows
9013 * tall, then only 25 dom nodes are created. The ScrollPort will ask the
9014 * RowProvider to create new visible rows on demand as they are scrolled in
9015 * to the visible area.
9016 *
9017 * This viewport is designed so that select and copy-to-clipboard still works,
9018 * even when all or part of the selection is scrolled off screen.
9019 *
9020 * Note that the X11 mouse clipboard does not work properly when all or part
9021 * of the selection is off screen. It would be difficult to fix this without
9022 * adding significant overhead to pathologically large selection cases.
9023 *
9024 * The RowProvider should return rows rooted by the custom tag name 'x-row'.
9025 * This ensures that we can quickly assign the correct display height
9026 * to the rows with css.
9027 *
9028 * @param {RowProvider} rowProvider An object capable of providing rows as
9029 * raw text or row nodes.
9030 */
9031hterm.ScrollPort = function(rowProvider) {
9032 hterm.PubSub.addBehavior(this);
9033
9034 this.rowProvider_ = rowProvider;
9035
9036 // SWAG the character size until we can measure it.
9037 this.characterSize = new hterm.Size(10, 10);
9038
9039 // DOM node used for character measurement.
9040 this.ruler_ = null;
9041
9042 this.selection = new hterm.ScrollPort.Selection(this);
9043
9044 // A map of rowIndex => rowNode for each row that is drawn as part of a
9045 // pending redraw_() call. Null if there is no pending redraw_ call.
9046 this.currentRowNodeCache_ = null;
9047
9048 // A map of rowIndex => rowNode for each row that was drawn as part of the
9049 // previous redraw_() call.
9050 this.previousRowNodeCache_ = {};
9051
9052 // Used during scroll events to detect when the underlying cause is a resize.
9053 this.lastScreenWidth_ = null;
9054 this.lastScreenHeight_ = null;
9055
9056 // True if the user should be allowed to select text in the terminal.
9057 // This is disabled when the host requests mouse drag events so that we don't
9058 // end up with two notions of selection.
9059 this.selectionEnabled_ = true;
9060
9061 // The last row count returned by the row provider, re-populated during
9062 // syncScrollHeight().
9063 this.lastRowCount_ = 0;
9064
9065 // The scroll wheel pixel delta multiplier to increase/decrease
9066 // the scroll speed of mouse wheel events. See: https://goo.gl/sXelnq
9067 this.scrollWheelMultiplier_ = 1;
9068
9069 /**
9070 * True if the last scroll caused the scrollport to show the final row.
9071 */
9072 this.isScrolledEnd = true;
9073
9074 // The css rule that we use to control the height of a row.
9075 this.xrowCssRule_ = null;
9076
9077 /**
9078 * A guess at the current scrollbar width, fixed in resize().
9079 */
9080 this.currentScrollbarWidthPx = 16;
9081
9082 /**
9083 * Whether the ctrl-v key on the screen should paste.
9084 */
9085 this.ctrlVPaste = false;
9086
9087 this.div_ = null;
9088 this.document_ = null;
9089
9090 // Collection of active timeout handles.
9091 this.timeouts_ = {};
9092
9093 this.observers_ = {};
9094
9095 this.DEBUG_ = false;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009096};
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009097
9098/**
9099 * Proxy for the native selection object which understands how to walk up the
9100 * DOM to find the containing row node and sort out which comes first.
9101 *
9102 * @param {hterm.ScrollPort} scrollPort The parent hterm.ScrollPort instance.
9103 */
9104hterm.ScrollPort.Selection = function(scrollPort) {
9105 this.scrollPort_ = scrollPort;
9106
9107 /**
9108 * The row containing the start of the selection.
9109 *
9110 * This may be partially or fully selected. It may be the selection anchor
9111 * or the focus, but its rowIndex is guaranteed to be less-than-or-equal-to
9112 * that of the endRow.
9113 *
9114 * If only one row is selected then startRow == endRow. If there is no
9115 * selection or the selection is collapsed then startRow == null.
9116 */
9117 this.startRow = null;
9118
9119 /**
9120 * The row containing the end of the selection.
9121 *
9122 * This may be partially or fully selected. It may be the selection anchor
9123 * or the focus, but its rowIndex is guaranteed to be greater-than-or-equal-to
9124 * that of the startRow.
9125 *
9126 * If only one row is selected then startRow == endRow. If there is no
9127 * selection or the selection is collapsed then startRow == null.
9128 */
9129 this.endRow = null;
9130
9131 /**
9132 * True if startRow != endRow.
9133 */
9134 this.isMultiline = null;
9135
9136 /**
9137 * True if the selection is just a point rather than a range.
9138 */
9139 this.isCollapsed = null;
9140};
9141
9142/**
9143 * Given a list of DOM nodes and a container, return the DOM node that
9144 * is first according to a depth-first search.
9145 *
9146 * Returns null if none of the children are found.
9147 */
9148hterm.ScrollPort.Selection.prototype.findFirstChild = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009149 parent, childAry) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009150 var node = parent.firstChild;
9151
9152 while (node) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009153 if (childAry.indexOf(node) != -1) return node;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009154
9155 if (node.childNodes.length) {
9156 var rv = this.findFirstChild(node, childAry);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009157 if (rv) return rv;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009158 }
9159
9160 node = node.nextSibling;
9161 }
9162
9163 return null;
9164};
9165
9166/**
9167 * Synchronize this object with the current DOM selection.
9168 *
9169 * This is a one-way synchronization, the DOM selection is copied to this
9170 * object, not the other way around.
9171 */
9172hterm.ScrollPort.Selection.prototype.sync = function() {
9173 var self = this;
9174
9175 // The dom selection object has no way to tell which nodes come first in
9176 // the document, so we have to figure that out.
9177 //
9178 // This function is used when we detect that the "anchor" node is first.
9179 function anchorFirst() {
9180 self.startRow = anchorRow;
9181 self.startNode = selection.anchorNode;
9182 self.startOffset = selection.anchorOffset;
9183 self.endRow = focusRow;
9184 self.endNode = selection.focusNode;
9185 self.endOffset = selection.focusOffset;
9186 }
9187
9188 // This function is used when we detect that the "focus" node is first.
9189 function focusFirst() {
9190 self.startRow = focusRow;
9191 self.startNode = selection.focusNode;
9192 self.startOffset = selection.focusOffset;
9193 self.endRow = anchorRow;
9194 self.endNode = selection.anchorNode;
9195 self.endOffset = selection.anchorOffset;
9196 }
9197
9198 var selection = this.scrollPort_.getDocument().getSelection();
9199
9200 this.startRow = null;
9201 this.endRow = null;
9202 this.isMultiline = null;
9203 this.isCollapsed = !selection || selection.isCollapsed;
9204
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009205 if (this.isCollapsed) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009206
9207 var anchorRow = selection.anchorNode;
9208 while (anchorRow && !('rowIndex' in anchorRow)) {
9209 anchorRow = anchorRow.parentNode;
9210 }
9211
9212 if (!anchorRow) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009213 console.error(
9214 'Selection anchor is not rooted in a row node: ' +
9215 selection.anchorNode.nodeName);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009216 return;
9217 }
9218
9219 var focusRow = selection.focusNode;
9220 while (focusRow && !('rowIndex' in focusRow)) {
9221 focusRow = focusRow.parentNode;
9222 }
9223
9224 if (!focusRow) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009225 console.error(
9226 'Selection focus is not rooted in a row node: ' +
9227 selection.focusNode.nodeName);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009228 return;
9229 }
9230
9231 if (anchorRow.rowIndex < focusRow.rowIndex) {
9232 anchorFirst();
9233
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009234 } else if (anchorRow.rowIndex > focusRow.rowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009235 focusFirst();
9236
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009237 } else if (selection.focusNode == selection.anchorNode) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009238 if (selection.anchorOffset < selection.focusOffset) {
9239 anchorFirst();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009240 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009241 focusFirst();
9242 }
9243
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009244 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009245 // The selection starts and ends in the same row, but isn't contained all
9246 // in a single node.
9247 var firstNode = this.findFirstChild(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009248 anchorRow, [selection.anchorNode, selection.focusNode]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009249
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009250 if (!firstNode) throw new Error('Unexpected error syncing selection.');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009251
9252 if (firstNode == selection.anchorNode) {
9253 anchorFirst();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009254 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009255 focusFirst();
9256 }
9257 }
9258
9259 this.isMultiline = anchorRow.rowIndex != focusRow.rowIndex;
9260};
9261
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009262/**
9263 * Turn a div into this hterm.ScrollPort.
9264 */
9265hterm.ScrollPort.prototype.decorate = function(div) {
9266 this.div_ = div;
9267
9268 this.iframe_ = div.ownerDocument.createElement('iframe');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009269 this.iframe_.style.cssText =
9270 ('border: 0;' +
9271 'height: 100%;' +
9272 'position: absolute;' +
9273 'width: 100%');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009274
9275 // Set the iframe src to # in FF. Otherwise when the frame's
9276 // load event fires in FF it clears out the content of the iframe.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009277 if ('mozInnerScreenX' in window) // detect a FF only property
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009278 this.iframe_.src = '#';
9279
9280 div.appendChild(this.iframe_);
9281
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009282 this.iframe_.contentWindow.addEventListener(
9283 'resize', this.onResize_.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009284
9285 var doc = this.document_ = this.iframe_.contentDocument;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009286 doc.body.style.cssText =
9287 ('margin: 0px;' +
9288 'padding: 0px;' +
9289 'height: 100%;' +
9290 'width: 100%;' +
9291 'overflow: hidden;' +
9292 'cursor: text;' +
9293 '-webkit-user-select: none;' +
9294 '-moz-user-select: none;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009295
9296 var style = doc.createElement('style');
9297 style.textContent = 'x-row {}';
9298 doc.head.appendChild(style);
9299
9300 this.xrowCssRule_ = doc.styleSheets[0].cssRules[0];
9301 this.xrowCssRule_.style.display = 'block';
9302
9303 this.userCssLink_ = doc.createElement('link');
9304 this.userCssLink_.setAttribute('rel', 'stylesheet');
9305
9306 // TODO(rginda): Sorry, this 'screen_' isn't the same thing as hterm.Screen
9307 // from screen.js. I need to pick a better name for one of them to avoid
9308 // the collision.
9309 this.screen_ = doc.createElement('x-screen');
9310 this.screen_.setAttribute('role', 'textbox');
9311 this.screen_.setAttribute('tabindex', '-1');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009312 this.screen_.style.cssText =
9313 ('display: block;' +
9314 'font-family: monospace;' +
9315 'font-size: 15px;' +
9316 'font-variant-ligatures: none;' +
9317 'height: 100%;' +
9318 'overflow-y: scroll; overflow-x: hidden;' +
9319 'white-space: pre;' +
9320 'width: 100%;' +
9321 'outline: none !important');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009322
9323 doc.body.appendChild(this.screen_);
9324
9325 this.screen_.addEventListener('scroll', this.onScroll_.bind(this));
9326 this.screen_.addEventListener('mousewheel', this.onScrollWheel_.bind(this));
9327 this.screen_.addEventListener(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009328 'DOMMouseScroll', this.onScrollWheel_.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009329 this.screen_.addEventListener('copy', this.onCopy_.bind(this));
9330 this.screen_.addEventListener('paste', this.onPaste_.bind(this));
9331
9332 doc.body.addEventListener('keydown', this.onBodyKeyDown_.bind(this));
9333
9334 // This is the main container for the fixed rows.
9335 this.rowNodes_ = doc.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009336 this.rowNodes_.style.cssText =
9337 ('display: block;' +
9338 'position: fixed;' +
9339 'overflow: hidden;' +
9340 '-webkit-user-select: text;' +
9341 '-moz-user-select: text;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009342 this.screen_.appendChild(this.rowNodes_);
9343
9344 // Two nodes to hold offscreen text during the copy event.
9345 this.topSelectBag_ = doc.createElement('x-select-bag');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009346 this.topSelectBag_.style.cssText =
9347 ('display: block;' +
9348 'overflow: hidden;' +
9349 'white-space: pre;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009350
9351 this.bottomSelectBag_ = this.topSelectBag_.cloneNode();
9352
9353 // Nodes above the top fold and below the bottom fold are hidden. They are
9354 // only used to hold rows that are part of the selection but are currently
9355 // scrolled off the top or bottom of the visible range.
9356 this.topFold_ = doc.createElement('x-fold');
9357 this.topFold_.style.cssText = 'display: block;';
9358 this.rowNodes_.appendChild(this.topFold_);
9359
9360 this.bottomFold_ = this.topFold_.cloneNode();
9361 this.rowNodes_.appendChild(this.bottomFold_);
9362
9363 // This hidden div accounts for the vertical space that would be consumed by
9364 // all the rows in the buffer if they were visible. It's what causes the
9365 // scrollbar to appear on the 'x-screen', and it moves within the screen when
9366 // the scrollbar is moved.
9367 //
9368 // It is set 'visibility: hidden' to keep the browser from trying to include
9369 // it in the selection when a user 'drag selects' upwards (drag the mouse to
9370 // select and scroll at the same time). Without this, the selection gets
9371 // out of whack.
9372 this.scrollArea_ = doc.createElement('div');
9373 this.scrollArea_.style.cssText = 'visibility: hidden';
9374 this.screen_.appendChild(this.scrollArea_);
9375
9376 // This svg element is used to detect when the browser is zoomed. It must be
9377 // placed in the outermost document for currentScale to be correct.
9378 // TODO(rginda): This means that hterm nested in an iframe will not correctly
9379 // detect browser zoom level. We should come up with a better solution.
9380 // Note: This must be http:// else Chrome cannot create the element correctly.
9381 var xmlns = 'http://www.w3.org/2000/svg';
9382 this.svg_ = this.div_.ownerDocument.createElementNS(xmlns, 'svg');
9383 this.svg_.setAttribute('xmlns', xmlns);
9384 this.svg_.setAttribute('version', '1.1');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009385 this.svg_.style.cssText =
9386 ('position: absolute;' +
9387 'top: 0;' +
9388 'left: 0;' +
9389 'visibility: hidden');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009390
9391 // We send focus to this element just before a paste happens, so we can
9392 // capture the pasted text and forward it on to someone who cares.
9393 this.pasteTarget_ = doc.createElement('textarea');
9394 this.pasteTarget_.setAttribute('tabindex', '-1');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009395 this.pasteTarget_.style.cssText =
9396 ('position: absolute;' +
9397 'height: 1px;' +
9398 'width: 1px;' +
9399 'left: 0px; ' +
9400 'bottom: 0px;' +
9401 'opacity: 0');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009402 this.pasteTarget_.contentEditable = true;
9403
9404 this.screen_.appendChild(this.pasteTarget_);
9405 this.pasteTarget_.addEventListener(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009406 'textInput', this.handlePasteTargetTextInput_.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009407
9408 this.resize();
9409};
9410
9411/**
9412 * Select the font-family and font-smoothing for this scrollport.
9413 *
9414 * @param {string} fontFamily Value of the CSS 'font-family' to use for this
9415 * scrollport. Should be a monospace font.
9416 * @param {string} opt_smoothing Optional value for '-webkit-font-smoothing'.
9417 * Defaults to an empty string if not specified.
9418 */
9419hterm.ScrollPort.prototype.setFontFamily = function(fontFamily, opt_smoothing) {
9420 this.screen_.style.fontFamily = fontFamily;
9421 if (opt_smoothing) {
9422 this.screen_.style.webkitFontSmoothing = opt_smoothing;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009423 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009424 this.screen_.style.webkitFontSmoothing = '';
9425 }
9426
9427 this.syncCharacterSize();
9428};
9429
9430hterm.ScrollPort.prototype.getFontFamily = function() {
9431 return this.screen_.style.fontFamily;
9432};
9433
9434/**
9435 * Set a custom stylesheet to include in the scrollport.
9436 *
9437 * Defaults to null, meaning no custom css is loaded. Set it back to null or
9438 * the empty string to remove a previously applied custom css.
9439 */
9440hterm.ScrollPort.prototype.setUserCss = function(url) {
9441 if (url) {
9442 this.userCssLink_.setAttribute('href', url);
9443
9444 if (!this.userCssLink_.parentNode)
9445 this.document_.head.appendChild(this.userCssLink_);
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009446 } else if (this.userCssLink_.parentNode) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009447 this.document_.head.removeChild(this.userCssLink_);
9448 }
9449};
9450
9451hterm.ScrollPort.prototype.focus = function() {
9452 this.iframe_.focus();
9453 this.screen_.focus();
9454};
9455
9456hterm.ScrollPort.prototype.getForegroundColor = function() {
9457 return this.screen_.style.color;
9458};
9459
9460hterm.ScrollPort.prototype.setForegroundColor = function(color) {
9461 this.screen_.style.color = color;
9462};
9463
9464hterm.ScrollPort.prototype.getBackgroundColor = function() {
9465 return this.screen_.style.backgroundColor;
9466};
9467
9468hterm.ScrollPort.prototype.setBackgroundColor = function(color) {
9469 this.screen_.style.backgroundColor = color;
9470};
9471
9472hterm.ScrollPort.prototype.setBackgroundImage = function(image) {
9473 this.screen_.style.backgroundImage = image;
9474};
9475
9476hterm.ScrollPort.prototype.setBackgroundSize = function(size) {
9477 this.screen_.style.backgroundSize = size;
9478};
9479
9480hterm.ScrollPort.prototype.setBackgroundPosition = function(position) {
9481 this.screen_.style.backgroundPosition = position;
9482};
9483
9484hterm.ScrollPort.prototype.setCtrlVPaste = function(ctrlVPaste) {
9485 this.ctrlVPaste = ctrlVPaste;
9486};
9487
9488/**
9489 * Get the usable size of the scrollport screen.
9490 *
9491 * The width will not include the scrollbar width.
9492 */
9493hterm.ScrollPort.prototype.getScreenSize = function() {
9494 var size = hterm.getClientSize(this.screen_);
9495 return {
9496 height: size.height,
9497 width: size.width - this.currentScrollbarWidthPx
9498 };
9499};
9500
9501/**
9502 * Get the usable width of the scrollport screen.
9503 *
9504 * This the widget width minus scrollbar width.
9505 */
9506hterm.ScrollPort.prototype.getScreenWidth = function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009507 return this.getScreenSize().width;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009508};
9509
9510/**
9511 * Get the usable height of the scrollport screen.
9512 */
9513hterm.ScrollPort.prototype.getScreenHeight = function() {
9514 return this.getScreenSize().height;
9515};
9516
9517/**
9518 * Return the document that holds the visible rows of this hterm.ScrollPort.
9519 */
9520hterm.ScrollPort.prototype.getDocument = function() {
9521 return this.document_;
9522};
9523
9524/**
9525 * Returns the x-screen element that holds the rows of this hterm.ScrollPort.
9526 */
9527hterm.ScrollPort.prototype.getScreenNode = function() {
9528 return this.screen_;
9529};
9530
9531/**
9532 * Clear out any cached rowNodes.
9533 */
9534hterm.ScrollPort.prototype.resetCache = function() {
9535 this.currentRowNodeCache_ = null;
9536 this.previousRowNodeCache_ = {};
9537};
9538
9539/**
9540 * Change the current rowProvider.
9541 *
9542 * This will clear the row cache and cause a redraw.
9543 *
9544 * @param {Object} rowProvider An object capable of providing the rows
9545 * in this hterm.ScrollPort.
9546 */
9547hterm.ScrollPort.prototype.setRowProvider = function(rowProvider) {
9548 this.resetCache();
9549 this.rowProvider_ = rowProvider;
9550 this.scheduleRedraw();
9551};
9552
9553/**
9554 * Inform the ScrollPort that the root DOM nodes for some or all of the visible
9555 * rows are no longer valid.
9556 *
9557 * Specifically, this should be called if this.rowProvider_.getRowNode() now
9558 * returns an entirely different node than it did before. It does not
9559 * need to be called if the content of a row node is the only thing that
9560 * changed.
9561 *
9562 * This skips some of the overhead of a full redraw, but should not be used
9563 * in cases where the scrollport has been scrolled, or when the row count has
9564 * changed.
9565 */
9566hterm.ScrollPort.prototype.invalidate = function() {
9567 var node = this.topFold_.nextSibling;
9568 while (node != this.bottomFold_) {
9569 var nextSibling = node.nextSibling;
9570 node.parentElement.removeChild(node);
9571 node = nextSibling;
9572 }
9573
9574 this.previousRowNodeCache_ = null;
9575 var topRowIndex = this.getTopRowIndex();
9576 var bottomRowIndex = this.getBottomRowIndex(topRowIndex);
9577
9578 this.drawVisibleRows_(topRowIndex, bottomRowIndex);
9579};
9580
9581hterm.ScrollPort.prototype.scheduleInvalidate = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009582 if (this.timeouts_.invalidate) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009583
9584 var self = this;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009585 this.timeouts_.invalidate = setTimeout(function() {
9586 delete self.timeouts_.invalidate;
9587 self.invalidate();
9588 }, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009589};
9590
9591/**
9592 * Set the font size of the ScrollPort.
9593 */
9594hterm.ScrollPort.prototype.setFontSize = function(px) {
9595 this.screen_.style.fontSize = px + 'px';
9596 this.syncCharacterSize();
9597};
9598
9599/**
9600 * Return the current font size of the ScrollPort.
9601 */
9602hterm.ScrollPort.prototype.getFontSize = function() {
9603 return parseInt(this.screen_.style.fontSize);
9604};
9605
9606/**
9607 * Measure the size of a single character in pixels.
9608 *
9609 * @param {string} opt_weight The font weight to measure, or 'normal' if
9610 * omitted.
9611 * @return {hterm.Size} A new hterm.Size object.
9612 */
9613hterm.ScrollPort.prototype.measureCharacterSize = function(opt_weight) {
9614 // Number of lines used to average the height of a single character.
9615 var numberOfLines = 100;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009616 var rulerSingleLineContents =
9617 ('XXXXXXXXXXXXXXXXXXXX' +
9618 'XXXXXXXXXXXXXXXXXXXX' +
9619 'XXXXXXXXXXXXXXXXXXXX' +
9620 'XXXXXXXXXXXXXXXXXXXX' +
9621 'XXXXXXXXXXXXXXXXXXXX');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009622 if (!this.ruler_) {
9623 this.ruler_ = this.document_.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009624 this.ruler_.style.cssText =
9625 ('position: absolute;' +
9626 'top: 0;' +
9627 'left: 0;' +
9628 'visibility: hidden;' +
9629 'height: auto !important;' +
9630 'width: auto !important;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009631
9632 // We need to put the text in a span to make the size calculation
9633 // work properly in Firefox
9634 this.rulerSpan_ = this.document_.createElement('span');
9635 var rulerContents = '' + rulerSingleLineContents;
9636 for (var i = 0; i < numberOfLines - 1; ++i)
9637 rulerContents += String.fromCharCode(13) + rulerSingleLineContents;
9638
9639 this.rulerSpan_.innerHTML = rulerContents;
9640 this.ruler_.appendChild(this.rulerSpan_);
9641
9642 this.rulerBaseline_ = this.document_.createElement('span');
9643 // We want to collapse it on the baseline
9644 this.rulerBaseline_.style.fontSize = '0px';
9645 this.rulerBaseline_.textContent = 'X';
9646 }
9647
9648 this.rulerSpan_.style.fontWeight = opt_weight || '';
9649
9650 this.rowNodes_.appendChild(this.ruler_);
9651 var rulerSize = hterm.getClientSize(this.rulerSpan_);
9652
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009653 var size = new hterm.Size(
9654 rulerSize.width / rulerSingleLineContents.length,
9655 rulerSize.height / numberOfLines);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009656
9657 this.ruler_.appendChild(this.rulerBaseline_);
9658 size.baseline = this.rulerBaseline_.offsetTop;
9659 this.ruler_.removeChild(this.rulerBaseline_);
9660
9661 this.rowNodes_.removeChild(this.ruler_);
9662
9663 this.div_.ownerDocument.body.appendChild(this.svg_);
9664 size.zoomFactor = this.svg_.currentScale;
9665 this.div_.ownerDocument.body.removeChild(this.svg_);
9666
9667 return size;
9668};
9669
9670/**
9671 * Synchronize the character size.
9672 *
9673 * This will re-measure the current character size and adjust the height
9674 * of an x-row to match.
9675 */
9676hterm.ScrollPort.prototype.syncCharacterSize = function() {
9677 this.characterSize = this.measureCharacterSize();
9678
9679 var lineHeight = this.characterSize.height + 'px';
9680 this.xrowCssRule_.style.height = lineHeight;
9681 this.topSelectBag_.style.height = lineHeight;
9682 this.bottomSelectBag_.style.height = lineHeight;
9683
9684 this.resize();
9685
9686 if (this.DEBUG_) {
9687 // When we're debugging we add padding to the body so that the offscreen
9688 // elements are visible.
9689 this.document_.body.style.paddingTop =
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009690 this.document_.body.style.paddingBottom =
9691 3 * this.characterSize.height + 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009692 }
9693};
9694
9695/**
9696 * Reset dimensions and visible row count to account for a change in the
9697 * dimensions of the 'x-screen'.
9698 */
9699hterm.ScrollPort.prototype.resize = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009700 this.currentScrollbarWidthPx =
9701 hterm.getClientWidth(this.screen_) - this.screen_.clientWidth;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009702
9703 this.syncScrollHeight();
9704 this.syncRowNodesDimensions_();
9705
9706 var self = this;
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009707 this.publish('resize', {scrollPort: this}, function() {
9708 self.scrollRowToBottom(self.rowProvider_.getRowCount());
9709 self.scheduleRedraw();
9710 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009711};
9712
9713/**
9714 * Set the position and size of the row nodes element.
9715 */
9716hterm.ScrollPort.prototype.syncRowNodesDimensions_ = function() {
9717 var screenSize = this.getScreenSize();
9718
9719 this.lastScreenWidth_ = screenSize.width;
9720 this.lastScreenHeight_ = screenSize.height;
9721
9722 // We don't want to show a partial row because it would be distracting
9723 // in a terminal, so we floor any fractional row count.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009724 this.visibleRowCount =
9725 lib.f.smartFloorDivide(screenSize.height, this.characterSize.height);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009726
9727 // Then compute the height of our integral number of rows.
9728 var visibleRowsHeight = this.visibleRowCount * this.characterSize.height;
9729
9730 // Then the difference between the screen height and total row height needs to
9731 // be made up for as top margin. We need to record this value so it
9732 // can be used later to determine the topRowIndex.
9733 this.visibleRowTopMargin = 0;
9734 this.visibleRowBottomMargin = screenSize.height - visibleRowsHeight;
9735
9736 this.topFold_.style.marginBottom = this.visibleRowTopMargin + 'px';
9737
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009738 var topFoldOffset = 0;
9739 var node = this.topFold_.previousSibling;
9740 while (node) {
9741 topFoldOffset += hterm.getClientHeight(node);
9742 node = node.previousSibling;
9743 }
9744
9745 // Set the dimensions of the visible rows container.
9746 this.rowNodes_.style.width = screenSize.width + 'px';
9747 this.rowNodes_.style.height = visibleRowsHeight + topFoldOffset + 'px';
9748 this.rowNodes_.style.left = this.screen_.offsetLeft + 'px';
9749 this.rowNodes_.style.top = this.screen_.offsetTop - topFoldOffset + 'px';
9750};
9751
9752hterm.ScrollPort.prototype.syncScrollHeight = function() {
9753 // Resize the scroll area to appear as though it contains every row.
9754 this.lastRowCount_ = this.rowProvider_.getRowCount();
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009755 this.scrollArea_.style.height =
9756 (this.characterSize.height * this.lastRowCount_ +
9757 this.visibleRowTopMargin + this.visibleRowBottomMargin + 'px');
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009758};
9759
9760/**
9761 * Schedule a redraw to happen asynchronously.
9762 *
9763 * If this method is called multiple times before the redraw has a chance to
9764 * run only one redraw occurs.
9765 */
9766hterm.ScrollPort.prototype.scheduleRedraw = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009767 if (this.timeouts_.redraw) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009768
9769 var self = this;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009770 this.timeouts_.redraw = setTimeout(function() {
9771 delete self.timeouts_.redraw;
9772 self.redraw_();
9773 }, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009774};
9775
9776/**
9777 * Redraw the current hterm.ScrollPort based on the current scrollbar position.
9778 *
9779 * When redrawing, we are careful to make sure that the rows that start or end
9780 * the current selection are not touched in any way. Doing so would disturb
9781 * the selection, and cleaning up after that would cause flashes at best and
9782 * incorrect selection at worst. Instead, we modify the DOM around these nodes.
9783 * We even stash the selection start/end outside of the visible area if
9784 * they are not supposed to be visible in the hterm.ScrollPort.
9785 */
9786hterm.ScrollPort.prototype.redraw_ = function() {
9787 this.resetSelectBags_();
9788 this.selection.sync();
9789
9790 this.syncScrollHeight();
9791
9792 this.currentRowNodeCache_ = {};
9793
9794 var topRowIndex = this.getTopRowIndex();
9795 var bottomRowIndex = this.getBottomRowIndex(topRowIndex);
9796
9797 this.drawTopFold_(topRowIndex);
9798 this.drawBottomFold_(bottomRowIndex);
9799 this.drawVisibleRows_(topRowIndex, bottomRowIndex);
9800
9801 this.syncRowNodesDimensions_();
9802
9803 this.previousRowNodeCache_ = this.currentRowNodeCache_;
9804 this.currentRowNodeCache_ = null;
9805
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009806 this.isScrolledEnd =
9807 (this.getTopRowIndex() + this.visibleRowCount >= this.lastRowCount_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009808};
9809
9810/**
9811 * Ensure that the nodes above the top fold are as they should be.
9812 *
9813 * If the selection start and/or end nodes are above the visible range
9814 * of this hterm.ScrollPort then the dom will be adjusted so that they appear
9815 * before the top fold (the first x-fold element, aka this.topFold).
9816 *
9817 * If not, the top fold will be the first element.
9818 *
9819 * It is critical that this method does not move the selection nodes. Doing
9820 * so would clear the current selection. Instead, the rest of the DOM is
9821 * adjusted around them.
9822 */
9823hterm.ScrollPort.prototype.drawTopFold_ = function(topRowIndex) {
9824 if (!this.selection.startRow ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009825 this.selection.startRow.rowIndex >= topRowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009826 // Selection is entirely below the top fold, just make sure the fold is
9827 // the first child.
9828 if (this.rowNodes_.firstChild != this.topFold_)
9829 this.rowNodes_.insertBefore(this.topFold_, this.rowNodes_.firstChild);
9830
9831 return;
9832 }
9833
9834 if (!this.selection.isMultiline ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009835 this.selection.endRow.rowIndex >= topRowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009836 // Only the startRow is above the fold.
9837 if (this.selection.startRow.nextSibling != this.topFold_)
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009838 this.rowNodes_.insertBefore(
9839 this.topFold_, this.selection.startRow.nextSibling);
9840 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009841 // Both rows are above the fold.
9842 if (this.selection.endRow.nextSibling != this.topFold_) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009843 this.rowNodes_.insertBefore(
9844 this.topFold_, this.selection.endRow.nextSibling);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009845 }
9846
9847 // Trim any intermediate lines.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009848 while (this.selection.startRow.nextSibling != this.selection.endRow) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009849 this.rowNodes_.removeChild(this.selection.startRow.nextSibling);
9850 }
9851 }
9852
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009853 while (this.rowNodes_.firstChild != this.selection.startRow) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009854 this.rowNodes_.removeChild(this.rowNodes_.firstChild);
9855 }
9856};
9857
9858/**
9859 * Ensure that the nodes below the bottom fold are as they should be.
9860 *
9861 * If the selection start and/or end nodes are below the visible range
9862 * of this hterm.ScrollPort then the dom will be adjusted so that they appear
9863 * after the bottom fold (the second x-fold element, aka this.bottomFold).
9864 *
9865 * If not, the bottom fold will be the last element.
9866 *
9867 * It is critical that this method does not move the selection nodes. Doing
9868 * so would clear the current selection. Instead, the rest of the DOM is
9869 * adjusted around them.
9870 */
9871hterm.ScrollPort.prototype.drawBottomFold_ = function(bottomRowIndex) {
9872 if (!this.selection.endRow ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009873 this.selection.endRow.rowIndex <= bottomRowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009874 // Selection is entirely above the bottom fold, just make sure the fold is
9875 // the last child.
9876 if (this.rowNodes_.lastChild != this.bottomFold_)
9877 this.rowNodes_.appendChild(this.bottomFold_);
9878
9879 return;
9880 }
9881
9882 if (!this.selection.isMultiline ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009883 this.selection.startRow.rowIndex <= bottomRowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009884 // Only the endRow is below the fold.
9885 if (this.bottomFold_.nextSibling != this.selection.endRow)
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009886 this.rowNodes_.insertBefore(this.bottomFold_, this.selection.endRow);
9887 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009888 // Both rows are below the fold.
9889 if (this.bottomFold_.nextSibling != this.selection.startRow) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009890 this.rowNodes_.insertBefore(this.bottomFold_, this.selection.startRow);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009891 }
9892
9893 // Trim any intermediate lines.
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009894 while (this.selection.startRow.nextSibling != this.selection.endRow) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009895 this.rowNodes_.removeChild(this.selection.startRow.nextSibling);
9896 }
9897 }
9898
Andrew Geisslerba5e3f32018-05-24 10:58:00 -07009899 while (this.rowNodes_.lastChild != this.selection.endRow) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009900 this.rowNodes_.removeChild(this.rowNodes_.lastChild);
9901 }
9902};
9903
9904/**
9905 * Ensure that the rows between the top and bottom folds are as they should be.
9906 *
9907 * This method assumes that drawTopFold_() and drawBottomFold_() have already
9908 * run, and that they have left any visible selection row (selection start
9909 * or selection end) between the folds.
9910 *
9911 * It recycles DOM nodes from the previous redraw where possible, but will ask
9912 * the rowSource to make new nodes if necessary.
9913 *
9914 * It is critical that this method does not move the selection nodes. Doing
9915 * so would clear the current selection. Instead, the rest of the DOM is
9916 * adjusted around them.
9917 */
9918hterm.ScrollPort.prototype.drawVisibleRows_ = function(
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009919 topRowIndex, bottomRowIndex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009920 var self = this;
9921
9922 // Keep removing nodes, starting with currentNode, until we encounter
9923 // targetNode. Throws on failure.
9924 function removeUntilNode(currentNode, targetNode) {
9925 while (currentNode != targetNode) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009926 if (!currentNode) throw 'Did not encounter target node';
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009927
9928 if (currentNode == self.bottomFold_)
9929 throw 'Encountered bottom fold before target node';
9930
9931 var deadNode = currentNode;
9932 currentNode = currentNode.nextSibling;
9933 deadNode.parentNode.removeChild(deadNode);
9934 }
9935 }
9936
9937 // Shorthand for things we're going to use a lot.
9938 var selectionStartRow = this.selection.startRow;
9939 var selectionEndRow = this.selection.endRow;
9940 var bottomFold = this.bottomFold_;
9941
9942 // The node we're examining during the current iteration.
9943 var node = this.topFold_.nextSibling;
9944
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009945 var targetDrawCount =
9946 Math.min(this.visibleRowCount, this.rowProvider_.getRowCount());
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009947
9948 for (var drawCount = 0; drawCount < targetDrawCount; drawCount++) {
9949 var rowIndex = topRowIndex + drawCount;
9950
9951 if (node == bottomFold) {
9952 // We've hit the bottom fold, we need to insert a new row.
9953 var newNode = this.fetchRowNode_(rowIndex);
9954 if (!newNode) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009955 console.log('Couldn\'t fetch row index: ' + rowIndex);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009956 break;
9957 }
9958
9959 this.rowNodes_.insertBefore(newNode, node);
9960 continue;
9961 }
9962
9963 if (node.rowIndex == rowIndex) {
9964 // This node is in the right place, move along.
9965 node = node.nextSibling;
9966 continue;
9967 }
9968
9969 if (selectionStartRow && selectionStartRow.rowIndex == rowIndex) {
9970 // The selection start row is supposed to be here, remove nodes until
9971 // we find it.
9972 removeUntilNode(node, selectionStartRow);
9973 node = selectionStartRow.nextSibling;
9974 continue;
9975 }
9976
9977 if (selectionEndRow && selectionEndRow.rowIndex == rowIndex) {
9978 // The selection end row is supposed to be here, remove nodes until
9979 // we find it.
9980 removeUntilNode(node, selectionEndRow);
9981 node = selectionEndRow.nextSibling;
9982 continue;
9983 }
9984
9985 if (node == selectionStartRow || node == selectionEndRow) {
9986 // We encountered the start/end of the selection, but we don't want it
9987 // yet. Insert a new row instead.
9988 var newNode = this.fetchRowNode_(rowIndex);
9989 if (!newNode) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -07009990 console.log('Couldn\'t fetch row index: ' + rowIndex);
Iftekharul Islamdb28a382017-11-02 13:16:17 -05009991 break;
9992 }
9993
9994 this.rowNodes_.insertBefore(newNode, node);
9995 continue;
9996 }
9997
9998 // There is nothing special about this node, but it's in our way. Replace
9999 // it with the node that should be here.
10000 var newNode = this.fetchRowNode_(rowIndex);
10001 if (!newNode) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010002 console.log('Couldn\'t fetch row index: ' + rowIndex);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010003 break;
10004 }
10005
10006 if (node == newNode) {
10007 node = node.nextSibling;
10008 continue;
10009 }
10010
10011 this.rowNodes_.insertBefore(newNode, node);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010012 if (!newNode.nextSibling) debugger;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010013 this.rowNodes_.removeChild(node);
10014 node = newNode.nextSibling;
10015 }
10016
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010017 if (node != this.bottomFold_) removeUntilNode(node, bottomFold);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010018};
10019
10020/**
10021 * Empty out both select bags and remove them from the document.
10022 *
10023 * These nodes hold the text between the start and end of the selection
10024 * when that text is otherwise off screen. They are filled out in the
10025 * onCopy_ event.
10026 */
10027hterm.ScrollPort.prototype.resetSelectBags_ = function() {
10028 if (this.topSelectBag_.parentNode) {
10029 this.topSelectBag_.textContent = '';
10030 this.topSelectBag_.parentNode.removeChild(this.topSelectBag_);
10031 }
10032
10033 if (this.bottomSelectBag_.parentNode) {
10034 this.bottomSelectBag_.textContent = '';
10035 this.bottomSelectBag_.parentNode.removeChild(this.bottomSelectBag_);
10036 }
10037};
10038
10039/**
10040 * Place a row node in the cache of visible nodes.
10041 *
10042 * This method may only be used during a redraw_.
10043 */
10044hterm.ScrollPort.prototype.cacheRowNode_ = function(rowNode) {
10045 this.currentRowNodeCache_[rowNode.rowIndex] = rowNode;
10046};
10047
10048/**
10049 * Fetch the row node for the given index.
10050 *
10051 * This will return a node from the cache if possible, or will request one
10052 * from the RowProvider if not.
10053 *
10054 * If a redraw_ is in progress the row will be added to the current cache.
10055 */
10056hterm.ScrollPort.prototype.fetchRowNode_ = function(rowIndex) {
10057 var node;
10058
10059 if (this.previousRowNodeCache_ && rowIndex in this.previousRowNodeCache_) {
10060 node = this.previousRowNodeCache_[rowIndex];
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010061 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010062 node = this.rowProvider_.getRowNode(rowIndex);
10063 }
10064
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010065 if (this.currentRowNodeCache_) this.cacheRowNode_(node);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010066
10067 return node;
10068};
10069
10070/**
10071 * Select all rows in the viewport.
10072 */
10073hterm.ScrollPort.prototype.selectAll = function() {
10074 var firstRow;
10075
10076 if (this.topFold_.nextSibling.rowIndex != 0) {
10077 while (this.topFold_.previousSibling) {
10078 this.rowNodes_.removeChild(this.topFold_.previousSibling);
10079 }
10080
10081 firstRow = this.fetchRowNode_(0);
10082 this.rowNodes_.insertBefore(firstRow, this.topFold_);
10083 this.syncRowNodesDimensions_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010084 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010085 firstRow = this.topFold_.nextSibling;
10086 }
10087
10088 var lastRowIndex = this.rowProvider_.getRowCount() - 1;
10089 var lastRow;
10090
10091 if (this.bottomFold_.previousSibling.rowIndex != lastRowIndex) {
10092 while (this.bottomFold_.nextSibling) {
10093 this.rowNodes_.removeChild(this.bottomFold_.nextSibling);
10094 }
10095
10096 lastRow = this.fetchRowNode_(lastRowIndex);
10097 this.rowNodes_.appendChild(lastRow);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010098 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010099 lastRow = this.bottomFold_.previousSibling.rowIndex;
10100 }
10101
10102 var selection = this.document_.getSelection();
10103 selection.collapse(firstRow, 0);
10104 selection.extend(lastRow, lastRow.childNodes.length);
10105
10106 this.selection.sync();
10107};
10108
10109/**
10110 * Return the maximum scroll position in pixels.
10111 */
10112hterm.ScrollPort.prototype.getScrollMax_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010113 return (
10114 hterm.getClientHeight(this.scrollArea_) + this.visibleRowTopMargin +
10115 this.visibleRowBottomMargin - hterm.getClientHeight(this.screen_));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010116};
10117
10118/**
10119 * Scroll the given rowIndex to the top of the hterm.ScrollPort.
10120 *
10121 * @param {integer} rowIndex Index of the target row.
10122 */
10123hterm.ScrollPort.prototype.scrollRowToTop = function(rowIndex) {
10124 this.syncScrollHeight();
10125
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010126 this.isScrolledEnd = (rowIndex + this.visibleRowCount >= this.lastRowCount_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010127
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010128 var scrollTop =
10129 rowIndex * this.characterSize.height + this.visibleRowTopMargin;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010130
10131 var scrollMax = this.getScrollMax_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010132 if (scrollTop > scrollMax) scrollTop = scrollMax;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010133
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010134 if (this.screen_.scrollTop == scrollTop) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010135
10136 this.screen_.scrollTop = scrollTop;
10137 this.scheduleRedraw();
10138};
10139
10140/**
10141 * Scroll the given rowIndex to the bottom of the hterm.ScrollPort.
10142 *
10143 * @param {integer} rowIndex Index of the target row.
10144 */
10145hterm.ScrollPort.prototype.scrollRowToBottom = function(rowIndex) {
10146 this.syncScrollHeight();
10147
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010148 this.isScrolledEnd = (rowIndex + this.visibleRowCount >= this.lastRowCount_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010149
10150 var scrollTop = rowIndex * this.characterSize.height +
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010151 this.visibleRowTopMargin + this.visibleRowBottomMargin;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010152 scrollTop -= this.visibleRowCount * this.characterSize.height;
10153
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010154 if (scrollTop < 0) scrollTop = 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010155
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010156 if (this.screen_.scrollTop == scrollTop) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010157
10158 this.screen_.scrollTop = scrollTop;
10159};
10160
10161/**
10162 * Return the row index of the first visible row.
10163 *
10164 * This is based on the scroll position. If a redraw_ is in progress this
10165 * returns the row that *should* be at the top.
10166 */
10167hterm.ScrollPort.prototype.getTopRowIndex = function() {
10168 return Math.round(this.screen_.scrollTop / this.characterSize.height);
10169};
10170
10171/**
10172 * Return the row index of the last visible row.
10173 *
10174 * This is based on the scroll position. If a redraw_ is in progress this
10175 * returns the row that *should* be at the bottom.
10176 */
10177hterm.ScrollPort.prototype.getBottomRowIndex = function(topRowIndex) {
10178 return topRowIndex + this.visibleRowCount - 1;
10179};
10180
10181/**
10182 * Handler for scroll events.
10183 *
10184 * The onScroll event fires when scrollArea's scrollTop property changes. This
10185 * may be due to the user manually move the scrollbar, or a programmatic change.
10186 */
10187hterm.ScrollPort.prototype.onScroll_ = function(e) {
10188 var screenSize = this.getScreenSize();
10189 if (screenSize.width != this.lastScreenWidth_ ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010190 screenSize.height != this.lastScreenHeight_) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010191 // This event may also fire during a resize (but before the resize event!).
10192 // This happens when the browser moves the scrollbar as part of the resize.
10193 // In these cases, we want to ignore the scroll event and let onResize
10194 // handle things. If we don't, then we end up scrolling to the wrong
10195 // position after a resize.
10196 this.resize();
10197 return;
10198 }
10199
10200 this.redraw_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010201 this.publish('scroll', {scrollPort: this});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010202};
10203
10204/**
10205 * Clients can override this if they want to hear scrollwheel events.
10206 *
10207 * Clients may call event.preventDefault() if they want to keep the scrollport
10208 * from also handling the events.
10209 */
10210hterm.ScrollPort.prototype.onScrollWheel = function(e) {};
10211
10212/**
10213 * Handler for scroll-wheel events.
10214 *
10215 * The onScrollWheel event fires when the user moves their scrollwheel over this
10216 * hterm.ScrollPort. Because the frontmost element in the hterm.ScrollPort is
10217 * a fixed position DIV, the scroll wheel does nothing by default. Instead, we
10218 * have to handle it manually.
10219 */
10220hterm.ScrollPort.prototype.onScrollWheel_ = function(e) {
10221 this.onScrollWheel(e);
10222
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010223 if (e.defaultPrevented) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010224
10225 // In FF, the event is DOMMouseScroll and puts the scroll pixel delta in the
10226 // 'detail' field of the event. It also flips the mapping of which direction
10227 // a negative number means in the scroll.
10228 var delta = e.type == 'DOMMouseScroll' ? (-1 * e.detail) : e.wheelDeltaY;
10229 delta *= this.scrollWheelMultiplier_;
10230
10231 var top = this.screen_.scrollTop - delta;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010232 if (top < 0) top = 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010233
10234 var scrollMax = this.getScrollMax_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010235 if (top > scrollMax) top = scrollMax;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010236
10237 if (top != this.screen_.scrollTop) {
10238 // Moving scrollTop causes a scroll event, which triggers the redraw.
10239 this.screen_.scrollTop = top;
10240
10241 // Only preventDefault when we've actually scrolled. If there's nothing
10242 // to scroll we want to pass the event through so Chrome can detect the
10243 // overscroll.
10244 e.preventDefault();
10245 }
10246};
10247
10248/**
10249 * Handler for resize events.
10250 *
10251 * The browser will resize us such that the top row stays at the top, but we
10252 * prefer to the bottom row to stay at the bottom.
10253 */
10254hterm.ScrollPort.prototype.onResize_ = function(e) {
10255 // Re-measure, since onResize also happens for browser zoom changes.
10256 this.syncCharacterSize();
10257 this.resize();
10258};
10259
10260/**
10261 * Clients can override this if they want to hear copy events.
10262 *
10263 * Clients may call event.preventDefault() if they want to keep the scrollport
10264 * from also handling the events.
10265 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010266hterm.ScrollPort.prototype.onCopy = function(e) {};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010267
10268/**
10269 * Handler for copy-to-clipboard events.
10270 *
10271 * If some or all of the selected rows are off screen we may need to fill in
10272 * the rows between selection start and selection end. This handler determines
10273 * if we're missing some of the selected text, and if so populates one or both
10274 * of the "select bags" with the missing text.
10275 */
10276hterm.ScrollPort.prototype.onCopy_ = function(e) {
10277 this.onCopy(e);
10278
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010279 if (e.defaultPrevented) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010280
10281 this.resetSelectBags_();
10282 this.selection.sync();
10283
10284 if (!this.selection.startRow ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010285 this.selection.endRow.rowIndex - this.selection.startRow.rowIndex < 2) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010286 return;
10287 }
10288
10289 var topRowIndex = this.getTopRowIndex();
10290 var bottomRowIndex = this.getBottomRowIndex(topRowIndex);
10291
10292 if (this.selection.startRow.rowIndex < topRowIndex) {
10293 // Start of selection is above the top fold.
10294 var endBackfillIndex;
10295
10296 if (this.selection.endRow.rowIndex < topRowIndex) {
10297 // Entire selection is above the top fold.
10298 endBackfillIndex = this.selection.endRow.rowIndex;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010299 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010300 // Selection extends below the top fold.
10301 endBackfillIndex = this.topFold_.nextSibling.rowIndex;
10302 }
10303
10304 this.topSelectBag_.textContent = this.rowProvider_.getRowsText(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010305 this.selection.startRow.rowIndex + 1, endBackfillIndex);
10306 this.rowNodes_.insertBefore(
10307 this.topSelectBag_, this.selection.startRow.nextSibling);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010308 this.syncRowNodesDimensions_();
10309 }
10310
10311 if (this.selection.endRow.rowIndex > bottomRowIndex) {
10312 // Selection ends below the bottom fold.
10313 var startBackfillIndex;
10314
10315 if (this.selection.startRow.rowIndex > bottomRowIndex) {
10316 // Entire selection is below the bottom fold.
10317 startBackfillIndex = this.selection.startRow.rowIndex + 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010318 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010319 // Selection starts above the bottom fold.
10320 startBackfillIndex = this.bottomFold_.previousSibling.rowIndex + 1;
10321 }
10322
10323 this.bottomSelectBag_.textContent = this.rowProvider_.getRowsText(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010324 startBackfillIndex, this.selection.endRow.rowIndex);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010325 this.rowNodes_.insertBefore(this.bottomSelectBag_, this.selection.endRow);
10326 }
10327};
10328
10329/**
10330 * Focuses on the paste target on a ctrl-v keydown event, as in
10331 * FF a content editable element must be focused before the paste event.
10332 */
10333hterm.ScrollPort.prototype.onBodyKeyDown_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010334 if (!this.ctrlVPaste) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010335
10336 var key = String.fromCharCode(e.which);
10337 var lowerKey = key.toLowerCase();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010338 if ((e.ctrlKey || e.metaKey) && lowerKey == 'v') this.pasteTarget_.focus();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010339};
10340
10341/**
10342 * Handle a paste event on the the ScrollPort's screen element.
10343 */
10344hterm.ScrollPort.prototype.onPaste_ = function(e) {
10345 this.pasteTarget_.focus();
10346
10347 var self = this;
10348 setTimeout(function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010349 self.publish('paste', {text: self.pasteTarget_.value});
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010350 self.pasteTarget_.value = '';
10351 self.screen_.focus();
10352 }, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010353};
10354
10355/**
10356 * Handles a textInput event on the paste target. Stops this from
10357 * propagating as we want this to be handled in the onPaste_ method.
10358 */
10359hterm.ScrollPort.prototype.handlePasteTargetTextInput_ = function(e) {
10360 e.stopPropagation();
10361};
10362
10363/**
10364 * Set the vertical scrollbar mode of the ScrollPort.
10365 */
10366hterm.ScrollPort.prototype.setScrollbarVisible = function(state) {
10367 this.screen_.style.overflowY = state ? 'scroll' : 'hidden';
10368};
10369
10370/**
10371 * Set scroll wheel multiplier. This alters how much the screen scrolls on
10372 * mouse wheel events.
10373 */
10374hterm.ScrollPort.prototype.setScrollWheelMoveMultipler = function(multiplier) {
10375 this.scrollWheelMultiplier_ = multiplier;
10376};
10377// SOURCE FILE: hterm/js/hterm_terminal.js
10378// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
10379// Use of this source code is governed by a BSD-style license that can be
10380// found in the LICENSE file.
10381
10382'use strict';
10383
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010384lib.rtdep(
10385 'lib.colors', 'lib.PreferenceManager', 'lib.resource', 'lib.wc', 'lib.f',
10386 'hterm.Keyboard', 'hterm.Options', 'hterm.PreferenceManager',
10387 'hterm.Screen', 'hterm.ScrollPort', 'hterm.Size', 'hterm.TextAttributes',
10388 'hterm.VT');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010389
10390/**
10391 * Constructor for the Terminal class.
10392 *
10393 * A Terminal pulls together the hterm.ScrollPort, hterm.Screen and hterm.VT100
10394 * classes to provide the complete terminal functionality.
10395 *
10396 * There are a number of lower-level Terminal methods that can be called
10397 * directly to manipulate the cursor, text, scroll region, and other terminal
10398 * attributes. However, the primary method is interpret(), which parses VT
10399 * escape sequences and invokes the appropriate Terminal methods.
10400 *
10401 * This class was heavily influenced by Cory Maccarrone's Framebuffer class.
10402 *
10403 * TODO(rginda): Eventually we're going to need to support characters which are
10404 * displayed twice as wide as standard latin characters. This is to support
10405 * CJK (and possibly other character sets).
10406 *
10407 * @param {string} opt_profileId Optional preference profile name. If not
10408 * provided, defaults to 'default'.
10409 */
10410hterm.Terminal = function(opt_profileId) {
10411 this.profileId_ = null;
10412
10413 // Two screen instances.
10414 this.primaryScreen_ = new hterm.Screen();
10415 this.alternateScreen_ = new hterm.Screen();
10416
10417 // The "current" screen.
10418 this.screen_ = this.primaryScreen_;
10419
10420 // The local notion of the screen size. ScreenBuffers also have a size which
10421 // indicates their present size. During size changes, the two may disagree.
10422 // Also, the inactive screen's size is not altered until it is made the active
10423 // screen.
10424 this.screenSize = new hterm.Size(0, 0);
10425
10426 // The scroll port we'll be using to display the visible rows.
10427 this.scrollPort_ = new hterm.ScrollPort(this);
10428 this.scrollPort_.subscribe('resize', this.onResize_.bind(this));
10429 this.scrollPort_.subscribe('scroll', this.onScroll_.bind(this));
10430 this.scrollPort_.subscribe('paste', this.onPaste_.bind(this));
10431 this.scrollPort_.onCopy = this.onCopy_.bind(this);
10432
10433 // The div that contains this terminal.
10434 this.div_ = null;
10435
10436 // The document that contains the scrollPort. Defaulted to the global
10437 // document here so that the terminal is functional even if it hasn't been
10438 // inserted into a document yet, but re-set in decorate().
10439 this.document_ = window.document;
10440
10441 // The rows that have scrolled off screen and are no longer addressable.
10442 this.scrollbackRows_ = [];
10443
10444 // Saved tab stops.
10445 this.tabStops_ = [];
10446
10447 // Keep track of whether default tab stops have been erased; after a TBC
10448 // clears all tab stops, defaults aren't restored on resize until a reset.
10449 this.defaultTabStops = true;
10450
10451 // The VT's notion of the top and bottom rows. Used during some VT
10452 // cursor positioning and scrolling commands.
10453 this.vtScrollTop_ = null;
10454 this.vtScrollBottom_ = null;
10455
10456 // The DIV element for the visible cursor.
10457 this.cursorNode_ = null;
10458
10459 // The current cursor shape of the terminal.
10460 this.cursorShape_ = hterm.Terminal.cursorShape.BLOCK;
10461
10462 // The current color of the cursor.
10463 this.cursorColor_ = null;
10464
10465 // Cursor blink on/off cycle in ms, overwritten by prefs once they're loaded.
10466 this.cursorBlinkCycle_ = [100, 100];
10467
10468 // Pre-bound onCursorBlink_ handler, so we don't have to do this for each
10469 // cursor on/off servicing.
10470 this.myOnCursorBlink_ = this.onCursorBlink_.bind(this);
10471
10472 // These prefs are cached so we don't have to read from local storage with
10473 // each output and keystroke. They are initialized by the preference manager.
10474 this.backgroundColor_ = null;
10475 this.foregroundColor_ = null;
10476 this.scrollOnOutput_ = null;
10477 this.scrollOnKeystroke_ = null;
10478
10479 // True if we should override mouse event reporting to allow local selection.
10480 this.defeatMouseReports_ = false;
10481
10482 // Terminal bell sound.
10483 this.bellAudio_ = this.document_.createElement('audio');
10484 this.bellAudio_.setAttribute('preload', 'auto');
10485
10486 // All terminal bell notifications that have been generated (not necessarily
10487 // shown).
10488 this.bellNotificationList_ = [];
10489
10490 // Whether we have permission to display notifications.
10491 this.desktopNotificationBell_ = false;
10492
10493 // Cursor position and attributes saved with DECSC.
10494 this.savedOptions_ = {};
10495
10496 // The current mode bits for the terminal.
10497 this.options_ = new hterm.Options();
10498
10499 // Timeouts we might need to clear.
10500 this.timeouts_ = {};
10501
10502 // The VT escape sequence interpreter.
10503 this.vt = new hterm.VT(this);
10504
10505 // The keyboard handler.
10506 this.keyboard = new hterm.Keyboard(this);
10507
10508 // General IO interface that can be given to third parties without exposing
10509 // the entire terminal object.
10510 this.io = new hterm.Terminal.IO(this);
10511
10512 // True if mouse-click-drag should scroll the terminal.
10513 this.enableMouseDragScroll = true;
10514
10515 this.copyOnSelect = null;
10516 this.mousePasteButton = null;
10517
10518 // Whether to use the default window copy behavior.
10519 this.useDefaultWindowCopy = false;
10520
10521 this.clearSelectionAfterCopy = true;
10522
10523 this.realizeSize_(80, 24);
10524 this.setDefaultTabStops();
10525
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010526 this.setProfile(opt_profileId || 'default', function() {
10527 this.onTerminalReady();
10528 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010529};
10530
10531/**
10532 * Possible cursor shapes.
10533 */
10534hterm.Terminal.cursorShape = {
10535 BLOCK: 'BLOCK',
10536 BEAM: 'BEAM',
10537 UNDERLINE: 'UNDERLINE'
10538};
10539
10540/**
10541 * Clients should override this to be notified when the terminal is ready
10542 * for use.
10543 *
10544 * The terminal initialization is asynchronous, and shouldn't be used before
10545 * this method is called.
10546 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010547hterm.Terminal.prototype.onTerminalReady = function() {};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010548
10549/**
10550 * Default tab with of 8 to match xterm.
10551 */
10552hterm.Terminal.prototype.tabWidth = 8;
10553
10554/**
10555 * Select a preference profile.
10556 *
10557 * This will load the terminal preferences for the given profile name and
10558 * associate subsequent preference changes with the new preference profile.
10559 *
10560 * @param {string} profileId The name of the preference profile. Forward slash
10561 * characters will be removed from the name.
10562 * @param {function} opt_callback Optional callback to invoke when the profile
10563 * transition is complete.
10564 */
10565hterm.Terminal.prototype.setProfile = function(profileId, opt_callback) {
10566 this.profileId_ = profileId.replace(/\//g, '');
10567
10568 var terminal = this;
10569
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010570 if (this.prefs_) this.prefs_.deactivate();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010571
10572 this.prefs_ = new hterm.PreferenceManager(this.profileId_);
10573 this.prefs_.addObservers(null, {
10574 'alt-gr-mode': function(v) {
10575 if (v == null) {
10576 if (navigator.language.toLowerCase() == 'en-us') {
10577 v = 'none';
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010578 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010579 v = 'right-alt';
10580 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010581 } else if (typeof v == 'string') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010582 v = v.toLowerCase();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010583 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010584 v = 'none';
10585 }
10586
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010587 if (!/^(none|ctrl-alt|left-alt|right-alt)$/.test(v)) v = 'none';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010588
10589 terminal.keyboard.altGrMode = v;
10590 },
10591
10592 'alt-backspace-is-meta-backspace': function(v) {
10593 terminal.keyboard.altBackspaceIsMetaBackspace = v;
10594 },
10595
10596 'alt-is-meta': function(v) {
10597 terminal.keyboard.altIsMeta = v;
10598 },
10599
10600 'alt-sends-what': function(v) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010601 if (!/^(escape|8-bit|browser-key)$/.test(v)) v = 'escape';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010602
10603 terminal.keyboard.altSendsWhat = v;
10604 },
10605
10606 'audible-bell-sound': function(v) {
10607 var ary = v.match(/^lib-resource:(\S+)/);
10608 if (ary) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010609 terminal.bellAudio_.setAttribute(
10610 'src', lib.resource.getDataUrl(ary[1]));
10611 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010612 terminal.bellAudio_.setAttribute('src', v);
10613 }
10614 },
10615
10616 'desktop-notification-bell': function(v) {
10617 if (v && Notification) {
10618 terminal.desktopNotificationBell_ =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010619 Notification.permission === 'granted';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010620 if (!terminal.desktopNotificationBell_) {
10621 // Note: We don't call Notification.requestPermission here because
10622 // Chrome requires the call be the result of a user action (such as an
10623 // onclick handler), and pref listeners are run asynchronously.
10624 //
10625 // A way of working around this would be to display a dialog in the
10626 // terminal with a "click-to-request-permission" button.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010627 console.warn(
10628 'desktop-notification-bell is true but we do not have ' +
10629 'permission to display notifications.');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010630 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010631 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010632 terminal.desktopNotificationBell_ = false;
10633 }
10634 },
10635
10636 'background-color': function(v) {
10637 terminal.setBackgroundColor(v);
10638 },
10639
10640 'background-image': function(v) {
10641 terminal.scrollPort_.setBackgroundImage(v);
10642 },
10643
10644 'background-size': function(v) {
10645 terminal.scrollPort_.setBackgroundSize(v);
10646 },
10647
10648 'background-position': function(v) {
10649 terminal.scrollPort_.setBackgroundPosition(v);
10650 },
10651
10652 'backspace-sends-backspace': function(v) {
10653 terminal.keyboard.backspaceSendsBackspace = v;
10654 },
10655
10656 'character-map-overrides': function(v) {
10657 if (!(v == null || v instanceof Object)) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010658 console.warn(
10659 'Preference character-map-modifications is not an ' +
10660 'object: ' + v);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010661 return;
10662 }
10663
10664 for (var code in v) {
10665 var glmap = hterm.VT.CharacterMap.maps[code].glmap;
10666 for (var received in v[code]) {
10667 glmap[received] = v[code][received];
10668 }
10669 hterm.VT.CharacterMap.maps[code].reset(glmap);
10670 }
10671 },
10672
10673 'cursor-blink': function(v) {
10674 terminal.setCursorBlink(!!v);
10675 },
10676
10677 'cursor-blink-cycle': function(v) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010678 if (v instanceof Array && typeof v[0] == 'number' &&
10679 typeof v[1] == 'number') {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010680 terminal.cursorBlinkCycle_ = v;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010681 } else if (typeof v == 'number') {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010682 terminal.cursorBlinkCycle_ = [v, v];
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010683 } else {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010684 // Fast blink indicates an error.
10685 terminal.cursorBlinkCycle_ = [100, 100];
10686 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010687 },
10688
10689 'cursor-color': function(v) {
10690 terminal.setCursorColor(v);
10691 },
10692
10693 'color-palette-overrides': function(v) {
10694 if (!(v == null || v instanceof Object || v instanceof Array)) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010695 console.warn(
10696 'Preference color-palette-overrides is not an array or ' +
10697 'object: ' + v);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010698 return;
10699 }
10700
10701 lib.colors.colorPalette = lib.colors.stockColorPalette.concat();
10702
10703 if (v) {
10704 for (var key in v) {
10705 var i = parseInt(key);
10706 if (isNaN(i) || i < 0 || i > 255) {
10707 console.log('Invalid value in palette: ' + key + ': ' + v[key]);
10708 continue;
10709 }
10710
10711 if (v[i]) {
10712 var rgb = lib.colors.normalizeCSS(v[i]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010713 if (rgb) lib.colors.colorPalette[i] = rgb;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010714 }
10715 }
10716 }
10717
10718 terminal.primaryScreen_.textAttributes.resetColorPalette();
10719 terminal.alternateScreen_.textAttributes.resetColorPalette();
10720 },
10721
10722 'copy-on-select': function(v) {
10723 terminal.copyOnSelect = !!v;
10724 },
10725
10726 'use-default-window-copy': function(v) {
10727 terminal.useDefaultWindowCopy = !!v;
10728 },
10729
10730 'clear-selection-after-copy': function(v) {
10731 terminal.clearSelectionAfterCopy = !!v;
10732 },
10733
10734 'ctrl-plus-minus-zero-zoom': function(v) {
10735 terminal.keyboard.ctrlPlusMinusZeroZoom = v;
10736 },
10737
10738 'ctrl-c-copy': function(v) {
10739 terminal.keyboard.ctrlCCopy = v;
10740 },
10741
10742 'ctrl-v-paste': function(v) {
10743 terminal.keyboard.ctrlVPaste = v;
10744 terminal.scrollPort_.setCtrlVPaste(v);
10745 },
10746
10747 'east-asian-ambiguous-as-two-column': function(v) {
10748 lib.wc.regardCjkAmbiguous = v;
10749 },
10750
10751 'enable-8-bit-control': function(v) {
10752 terminal.vt.enable8BitControl = !!v;
10753 },
10754
10755 'enable-bold': function(v) {
10756 terminal.syncBoldSafeState();
10757 },
10758
10759 'enable-bold-as-bright': function(v) {
10760 terminal.primaryScreen_.textAttributes.enableBoldAsBright = !!v;
10761 terminal.alternateScreen_.textAttributes.enableBoldAsBright = !!v;
10762 },
10763
10764 'enable-blink': function(v) {
10765 terminal.syncBlinkState();
10766 },
10767
10768 'enable-clipboard-write': function(v) {
10769 terminal.vt.enableClipboardWrite = !!v;
10770 },
10771
10772 'enable-dec12': function(v) {
10773 terminal.vt.enableDec12 = !!v;
10774 },
10775
10776 'font-family': function(v) {
10777 terminal.syncFontFamily();
10778 },
10779
10780 'font-size': function(v) {
10781 terminal.setFontSize(v);
10782 },
10783
10784 'font-smoothing': function(v) {
10785 terminal.syncFontFamily();
10786 },
10787
10788 'foreground-color': function(v) {
10789 terminal.setForegroundColor(v);
10790 },
10791
10792 'home-keys-scroll': function(v) {
10793 terminal.keyboard.homeKeysScroll = v;
10794 },
10795
10796 'keybindings': function(v) {
10797 terminal.keyboard.bindings.clear();
10798
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010799 if (!v) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010800
10801 if (!(v instanceof Object)) {
10802 console.error('Error in keybindings preference: Expected object');
10803 return;
10804 }
10805
10806 try {
10807 terminal.keyboard.bindings.addBindings(v);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010808 } catch (ex) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010809 console.error('Error in keybindings preference: ' + ex);
10810 }
10811 },
10812
10813 'max-string-sequence': function(v) {
10814 terminal.vt.maxStringSequence = v;
10815 },
10816
10817 'media-keys-are-fkeys': function(v) {
10818 terminal.keyboard.mediaKeysAreFKeys = v;
10819 },
10820
10821 'meta-sends-escape': function(v) {
10822 terminal.keyboard.metaSendsEscape = v;
10823 },
10824
10825 'mouse-paste-button': function(v) {
10826 terminal.syncMousePasteButton();
10827 },
10828
10829 'page-keys-scroll': function(v) {
10830 terminal.keyboard.pageKeysScroll = v;
10831 },
10832
10833 'pass-alt-number': function(v) {
10834 if (v == null) {
10835 var osx = window.navigator.userAgent.match(/Mac OS X/);
10836
10837 // Let Alt-1..9 pass to the browser (to control tab switching) on
10838 // non-OS X systems, or if hterm is not opened in an app window.
10839 v = (!osx && hterm.windowType != 'popup');
10840 }
10841
10842 terminal.passAltNumber = v;
10843 },
10844
10845 'pass-ctrl-number': function(v) {
10846 if (v == null) {
10847 var osx = window.navigator.userAgent.match(/Mac OS X/);
10848
10849 // Let Ctrl-1..9 pass to the browser (to control tab switching) on
10850 // non-OS X systems, or if hterm is not opened in an app window.
10851 v = (!osx && hterm.windowType != 'popup');
10852 }
10853
10854 terminal.passCtrlNumber = v;
10855 },
10856
10857 'pass-meta-number': function(v) {
10858 if (v == null) {
10859 var osx = window.navigator.userAgent.match(/Mac OS X/);
10860
10861 // Let Meta-1..9 pass to the browser (to control tab switching) on
10862 // OS X systems, or if hterm is not opened in an app window.
10863 v = (osx && hterm.windowType != 'popup');
10864 }
10865
10866 terminal.passMetaNumber = v;
10867 },
10868
10869 'pass-meta-v': function(v) {
10870 terminal.keyboard.passMetaV = v;
10871 },
10872
10873 'receive-encoding': function(v) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010874 if (!(/^(utf-8|raw)$/).test(v)) {
10875 console.warn('Invalid value for "receive-encoding": ' + v);
10876 v = 'utf-8';
10877 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010878
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010879 terminal.vt.characterEncoding = v;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010880 },
10881
10882 'scroll-on-keystroke': function(v) {
10883 terminal.scrollOnKeystroke_ = v;
10884 },
10885
10886 'scroll-on-output': function(v) {
10887 terminal.scrollOnOutput_ = v;
10888 },
10889
10890 'scrollbar-visible': function(v) {
10891 terminal.setScrollbarVisible(v);
10892 },
10893
10894 'scroll-wheel-move-multiplier': function(v) {
10895 terminal.setScrollWheelMoveMultipler(v);
10896 },
10897
10898 'send-encoding': function(v) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010899 if (!(/^(utf-8|raw)$/).test(v)) {
10900 console.warn('Invalid value for "send-encoding": ' + v);
10901 v = 'utf-8';
10902 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010903
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070010904 terminal.keyboard.characterEncoding = v;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010905 },
10906
10907 'shift-insert-paste': function(v) {
10908 terminal.keyboard.shiftInsertPaste = v;
10909 },
10910
10911 'user-css': function(v) {
10912 terminal.scrollPort_.setUserCss(v);
10913 }
10914 });
10915
10916 this.prefs_.readStorage(function() {
10917 this.prefs_.notifyAll();
10918
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010919 if (opt_callback) opt_callback();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010920 }.bind(this));
10921};
10922
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010923/**
10924 * Returns the preferences manager used for configuring this terminal.
10925 *
10926 * @return {hterm.PreferenceManager}
10927 */
10928hterm.Terminal.prototype.getPrefs = function() {
10929 return this.prefs_;
10930};
10931
10932/**
10933 * Enable or disable bracketed paste mode.
10934 *
10935 * @param {boolean} state The value to set.
10936 */
10937hterm.Terminal.prototype.setBracketedPaste = function(state) {
10938 this.options_.bracketedPaste = state;
10939};
10940
10941/**
10942 * Set the color for the cursor.
10943 *
10944 * If you want this setting to persist, set it through prefs_, rather than
10945 * with this method.
10946 *
10947 * @param {string} color The color to set.
10948 */
10949hterm.Terminal.prototype.setCursorColor = function(color) {
10950 this.cursorColor_ = color;
10951 this.cursorNode_.style.backgroundColor = color;
10952 this.cursorNode_.style.borderColor = color;
10953};
10954
10955/**
10956 * Return the current cursor color as a string.
10957 * @return {string}
10958 */
10959hterm.Terminal.prototype.getCursorColor = function() {
10960 return this.cursorColor_;
10961};
10962
10963/**
10964 * Enable or disable mouse based text selection in the terminal.
10965 *
10966 * @param {boolean} state The value to set.
10967 */
10968hterm.Terminal.prototype.setSelectionEnabled = function(state) {
10969 this.enableMouseDragScroll = state;
10970};
10971
10972/**
10973 * Set the background color.
10974 *
10975 * If you want this setting to persist, set it through prefs_, rather than
10976 * with this method.
10977 *
10978 * @param {string} color The color to set.
10979 */
10980hterm.Terminal.prototype.setBackgroundColor = function(color) {
10981 this.backgroundColor_ = lib.colors.normalizeCSS(color);
10982 this.primaryScreen_.textAttributes.setDefaults(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010983 this.foregroundColor_, this.backgroundColor_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010984 this.alternateScreen_.textAttributes.setDefaults(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070010985 this.foregroundColor_, this.backgroundColor_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050010986 this.scrollPort_.setBackgroundColor(color);
10987};
10988
10989/**
10990 * Return the current terminal background color.
10991 *
10992 * Intended for use by other classes, so we don't have to expose the entire
10993 * prefs_ object.
10994 *
10995 * @return {string}
10996 */
10997hterm.Terminal.prototype.getBackgroundColor = function() {
10998 return this.backgroundColor_;
10999};
11000
11001/**
11002 * Set the foreground color.
11003 *
11004 * If you want this setting to persist, set it through prefs_, rather than
11005 * with this method.
11006 *
11007 * @param {string} color The color to set.
11008 */
11009hterm.Terminal.prototype.setForegroundColor = function(color) {
11010 this.foregroundColor_ = lib.colors.normalizeCSS(color);
11011 this.primaryScreen_.textAttributes.setDefaults(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011012 this.foregroundColor_, this.backgroundColor_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011013 this.alternateScreen_.textAttributes.setDefaults(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011014 this.foregroundColor_, this.backgroundColor_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011015 this.scrollPort_.setForegroundColor(color);
11016};
11017
11018/**
11019 * Return the current terminal foreground color.
11020 *
11021 * Intended for use by other classes, so we don't have to expose the entire
11022 * prefs_ object.
11023 *
11024 * @return {string}
11025 */
11026hterm.Terminal.prototype.getForegroundColor = function() {
11027 return this.foregroundColor_;
11028};
11029
11030/**
11031 * Create a new instance of a terminal command and run it with a given
11032 * argument string.
11033 *
11034 * @param {function} commandClass The constructor for a terminal command.
11035 * @param {string} argString The argument string to pass to the command.
11036 */
11037hterm.Terminal.prototype.runCommandClass = function(commandClass, argString) {
11038 var environment = this.prefs_.get('environment');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011039 if (typeof environment != 'object' || environment == null) environment = {};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011040
11041 var self = this;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011042 this.command = new commandClass({
11043 argString: argString || '',
11044 io: this.io.push(),
11045 environment: environment,
11046 onExit: function(code) {
11047 self.io.pop();
11048 self.uninstallKeyboard();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011049 if (self.prefs_.get('close-on-exit')) window.close();
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011050 }
11051 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011052
11053 this.installKeyboard();
11054 this.command.run();
11055};
11056
11057/**
11058 * Returns true if the current screen is the primary screen, false otherwise.
11059 *
11060 * @return {boolean}
11061 */
11062hterm.Terminal.prototype.isPrimaryScreen = function() {
11063 return this.screen_ == this.primaryScreen_;
11064};
11065
11066/**
11067 * Install the keyboard handler for this terminal.
11068 *
11069 * This will prevent the browser from seeing any keystrokes sent to the
11070 * terminal.
11071 */
11072hterm.Terminal.prototype.installKeyboard = function() {
11073 this.keyboard.installKeyboard(this.scrollPort_.getDocument().body);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011074};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011075
11076/**
11077 * Uninstall the keyboard handler for this terminal.
11078 */
11079hterm.Terminal.prototype.uninstallKeyboard = function() {
11080 this.keyboard.installKeyboard(null);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011081};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011082
11083/**
11084 * Set the font size for this terminal.
11085 *
11086 * Call setFontSize(0) to reset to the default font size.
11087 *
11088 * This function does not modify the font-size preference.
11089 *
11090 * @param {number} px The desired font size, in pixels.
11091 */
11092hterm.Terminal.prototype.setFontSize = function(px) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011093 if (px === 0) px = this.prefs_.get('font-size');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011094
11095 this.scrollPort_.setFontSize(px);
11096 if (this.wcCssRule_) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011097 this.wcCssRule_.style.width =
11098 this.scrollPort_.characterSize.width * 2 + 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011099 }
11100};
11101
11102/**
11103 * Get the current font size.
11104 *
11105 * @return {number}
11106 */
11107hterm.Terminal.prototype.getFontSize = function() {
11108 return this.scrollPort_.getFontSize();
11109};
11110
11111/**
11112 * Get the current font family.
11113 *
11114 * @return {string}
11115 */
11116hterm.Terminal.prototype.getFontFamily = function() {
11117 return this.scrollPort_.getFontFamily();
11118};
11119
11120/**
11121 * Set the CSS "font-family" for this terminal.
11122 */
11123hterm.Terminal.prototype.syncFontFamily = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011124 this.scrollPort_.setFontFamily(
11125 this.prefs_.get('font-family'), this.prefs_.get('font-smoothing'));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011126 this.syncBoldSafeState();
11127};
11128
11129/**
11130 * Set this.mousePasteButton based on the mouse-paste-button pref,
11131 * autodetecting if necessary.
11132 */
11133hterm.Terminal.prototype.syncMousePasteButton = function() {
11134 var button = this.prefs_.get('mouse-paste-button');
11135 if (typeof button == 'number') {
11136 this.mousePasteButton = button;
11137 return;
11138 }
11139
11140 var ary = navigator.userAgent.match(/\(X11;\s+(\S+)/);
11141 if (!ary || ary[2] == 'CrOS') {
11142 this.mousePasteButton = 2;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011143 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011144 this.mousePasteButton = 3;
11145 }
11146};
11147
11148/**
11149 * Enable or disable bold based on the enable-bold pref, autodetecting if
11150 * necessary.
11151 */
11152hterm.Terminal.prototype.syncBoldSafeState = function() {
11153 var enableBold = this.prefs_.get('enable-bold');
11154 if (enableBold !== null) {
11155 this.primaryScreen_.textAttributes.enableBold = enableBold;
11156 this.alternateScreen_.textAttributes.enableBold = enableBold;
11157 return;
11158 }
11159
11160 var normalSize = this.scrollPort_.measureCharacterSize();
11161 var boldSize = this.scrollPort_.measureCharacterSize('bold');
11162
11163 var isBoldSafe = normalSize.equals(boldSize);
11164 if (!isBoldSafe) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011165 console.warn(
11166 'Bold characters disabled: Size of bold weight differs ' +
11167 'from normal. Font family is: ' + this.scrollPort_.getFontFamily());
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011168 }
11169
11170 this.primaryScreen_.textAttributes.enableBold = isBoldSafe;
11171 this.alternateScreen_.textAttributes.enableBold = isBoldSafe;
11172};
11173
11174/**
11175 * Enable or disable blink based on the enable-blink pref.
11176 */
11177hterm.Terminal.prototype.syncBlinkState = function() {
11178 this.document_.documentElement.style.setProperty(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011179 '--hterm-blink-node-duration',
11180 this.prefs_.get('enable-blink') ? '0.7s' : '0');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011181};
11182
11183/**
11184 * Return a copy of the current cursor position.
11185 *
11186 * @return {hterm.RowCol} The RowCol object representing the current position.
11187 */
11188hterm.Terminal.prototype.saveCursor = function() {
11189 return this.screen_.cursorPosition.clone();
11190};
11191
11192/**
11193 * Return the current text attributes.
11194 *
11195 * @return {string}
11196 */
11197hterm.Terminal.prototype.getTextAttributes = function() {
11198 return this.screen_.textAttributes;
11199};
11200
11201/**
11202 * Set the text attributes.
11203 *
11204 * @param {string} textAttributes The attributes to set.
11205 */
11206hterm.Terminal.prototype.setTextAttributes = function(textAttributes) {
11207 this.screen_.textAttributes = textAttributes;
11208};
11209
11210/**
11211 * Return the current browser zoom factor applied to the terminal.
11212 *
11213 * @return {number} The current browser zoom factor.
11214 */
11215hterm.Terminal.prototype.getZoomFactor = function() {
11216 return this.scrollPort_.characterSize.zoomFactor;
11217};
11218
11219/**
11220 * Change the title of this terminal's window.
11221 *
11222 * @param {string} title The title to set.
11223 */
11224hterm.Terminal.prototype.setWindowTitle = function(title) {
11225 window.document.title = title;
11226};
11227
11228/**
11229 * Restore a previously saved cursor position.
11230 *
11231 * @param {hterm.RowCol} cursor The position to restore.
11232 */
11233hterm.Terminal.prototype.restoreCursor = function(cursor) {
11234 var row = lib.f.clamp(cursor.row, 0, this.screenSize.height - 1);
11235 var column = lib.f.clamp(cursor.column, 0, this.screenSize.width - 1);
11236 this.screen_.setCursorPosition(row, column);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011237 if (cursor.column > column || cursor.column == column && cursor.overflow) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011238 this.screen_.cursorPosition.overflow = true;
11239 }
11240};
11241
11242/**
11243 * Clear the cursor's overflow flag.
11244 */
11245hterm.Terminal.prototype.clearCursorOverflow = function() {
11246 this.screen_.cursorPosition.overflow = false;
11247};
11248
11249/**
11250 * Sets the cursor shape
11251 *
11252 * @param {string} shape The shape to set.
11253 */
11254hterm.Terminal.prototype.setCursorShape = function(shape) {
11255 this.cursorShape_ = shape;
11256 this.restyleCursor_();
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011257};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011258
11259/**
11260 * Get the cursor shape
11261 *
11262 * @return {string}
11263 */
11264hterm.Terminal.prototype.getCursorShape = function() {
11265 return this.cursorShape_;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011266};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011267
11268/**
11269 * Set the width of the terminal, resizing the UI to match.
11270 *
11271 * @param {number} columnCount
11272 */
11273hterm.Terminal.prototype.setWidth = function(columnCount) {
11274 if (columnCount == null) {
11275 this.div_.style.width = '100%';
11276 return;
11277 }
11278
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011279 this.div_.style.width =
11280 Math.ceil(
11281 this.scrollPort_.characterSize.width * columnCount +
11282 this.scrollPort_.currentScrollbarWidthPx) +
11283 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011284 this.realizeSize_(columnCount, this.screenSize.height);
11285 this.scheduleSyncCursorPosition_();
11286};
11287
11288/**
11289 * Set the height of the terminal, resizing the UI to match.
11290 *
11291 * @param {number} rowCount The height in rows.
11292 */
11293hterm.Terminal.prototype.setHeight = function(rowCount) {
11294 if (rowCount == null) {
11295 this.div_.style.height = '100%';
11296 return;
11297 }
11298
11299 this.div_.style.height =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011300 this.scrollPort_.characterSize.height * rowCount + 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011301 this.realizeSize_(this.screenSize.width, rowCount);
11302 this.scheduleSyncCursorPosition_();
11303};
11304
11305/**
11306 * Deal with terminal size changes.
11307 *
11308 * @param {number} columnCount The number of columns.
11309 * @param {number} rowCount The number of rows.
11310 */
11311hterm.Terminal.prototype.realizeSize_ = function(columnCount, rowCount) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011312 if (columnCount != this.screenSize.width) this.realizeWidth_(columnCount);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011313
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011314 if (rowCount != this.screenSize.height) this.realizeHeight_(rowCount);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011315
11316 // Send new terminal size to plugin.
11317 this.io.onTerminalResize_(columnCount, rowCount);
11318};
11319
11320/**
11321 * Deal with terminal width changes.
11322 *
11323 * This function does what needs to be done when the terminal width changes
11324 * out from under us. It happens here rather than in onResize_() because this
11325 * code may need to run synchronously to handle programmatic changes of
11326 * terminal width.
11327 *
11328 * Relying on the browser to send us an async resize event means we may not be
11329 * in the correct state yet when the next escape sequence hits.
11330 *
11331 * @param {number} columnCount The number of columns.
11332 */
11333hterm.Terminal.prototype.realizeWidth_ = function(columnCount) {
11334 if (columnCount <= 0)
11335 throw new Error('Attempt to realize bad width: ' + columnCount);
11336
11337 var deltaColumns = columnCount - this.screen_.getWidth();
11338
11339 this.screenSize.width = columnCount;
11340 this.screen_.setColumnCount(columnCount);
11341
11342 if (deltaColumns > 0) {
11343 if (this.defaultTabStops)
11344 this.setDefaultTabStops(this.screenSize.width - deltaColumns);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011345 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011346 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011347 if (this.tabStops_[i] < columnCount) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011348
11349 this.tabStops_.pop();
11350 }
11351 }
11352
11353 this.screen_.setColumnCount(this.screenSize.width);
11354};
11355
11356/**
11357 * Deal with terminal height changes.
11358 *
11359 * This function does what needs to be done when the terminal height changes
11360 * out from under us. It happens here rather than in onResize_() because this
11361 * code may need to run synchronously to handle programmatic changes of
11362 * terminal height.
11363 *
11364 * Relying on the browser to send us an async resize event means we may not be
11365 * in the correct state yet when the next escape sequence hits.
11366 *
11367 * @param {number} rowCount The number of rows.
11368 */
11369hterm.Terminal.prototype.realizeHeight_ = function(rowCount) {
11370 if (rowCount <= 0)
11371 throw new Error('Attempt to realize bad height: ' + rowCount);
11372
11373 var deltaRows = rowCount - this.screen_.getHeight();
11374
11375 this.screenSize.height = rowCount;
11376
11377 var cursor = this.saveCursor();
11378
11379 if (deltaRows < 0) {
11380 // Screen got smaller.
11381 deltaRows *= -1;
11382 while (deltaRows) {
11383 var lastRow = this.getRowCount() - 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011384 if (lastRow - this.scrollbackRows_.length == cursor.row) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011385
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011386 if (this.getRowText(lastRow)) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011387
11388 this.screen_.popRow();
11389 deltaRows--;
11390 }
11391
11392 var ary = this.screen_.shiftRows(deltaRows);
11393 this.scrollbackRows_.push.apply(this.scrollbackRows_, ary);
11394
11395 // We just removed rows from the top of the screen, we need to update
11396 // the cursor to match.
11397 cursor.row = Math.max(cursor.row - deltaRows, 0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011398 } else if (deltaRows > 0) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011399 // Screen got larger.
11400
11401 if (deltaRows <= this.scrollbackRows_.length) {
11402 var scrollbackCount = Math.min(deltaRows, this.scrollbackRows_.length);
11403 var rows = this.scrollbackRows_.splice(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011404 this.scrollbackRows_.length - scrollbackCount, scrollbackCount);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011405 this.screen_.unshiftRows(rows);
11406 deltaRows -= scrollbackCount;
11407 cursor.row += scrollbackCount;
11408 }
11409
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011410 if (deltaRows) this.appendRows_(deltaRows);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011411 }
11412
11413 this.setVTScrollRegion(null, null);
11414 this.restoreCursor(cursor);
11415};
11416
11417/**
11418 * Scroll the terminal to the top of the scrollback buffer.
11419 */
11420hterm.Terminal.prototype.scrollHome = function() {
11421 this.scrollPort_.scrollRowToTop(0);
11422};
11423
11424/**
11425 * Scroll the terminal to the end.
11426 */
11427hterm.Terminal.prototype.scrollEnd = function() {
11428 this.scrollPort_.scrollRowToBottom(this.getRowCount());
11429};
11430
11431/**
11432 * Scroll the terminal one page up (minus one line) relative to the current
11433 * position.
11434 */
11435hterm.Terminal.prototype.scrollPageUp = function() {
11436 var i = this.scrollPort_.getTopRowIndex();
11437 this.scrollPort_.scrollRowToTop(i - this.screenSize.height + 1);
11438};
11439
11440/**
11441 * Scroll the terminal one page down (minus one line) relative to the current
11442 * position.
11443 */
11444hterm.Terminal.prototype.scrollPageDown = function() {
11445 var i = this.scrollPort_.getTopRowIndex();
11446 this.scrollPort_.scrollRowToTop(i + this.screenSize.height - 1);
11447};
11448
11449/**
11450 * Clear primary screen, secondary screen, and the scrollback buffer.
11451 */
11452hterm.Terminal.prototype.wipeContents = function() {
11453 this.scrollbackRows_.length = 0;
11454 this.scrollPort_.resetCache();
11455
11456 [this.primaryScreen_, this.alternateScreen_].forEach(function(screen) {
11457 var bottom = screen.getHeight();
11458 if (bottom > 0) {
11459 this.renumberRows_(0, bottom);
11460 this.clearHome(screen);
11461 }
11462 }.bind(this));
11463
11464 this.syncCursorPosition_();
11465 this.scrollPort_.invalidate();
11466};
11467
11468/**
11469 * Full terminal reset.
11470 */
11471hterm.Terminal.prototype.reset = function() {
11472 this.clearAllTabStops();
11473 this.setDefaultTabStops();
11474
11475 this.clearHome(this.primaryScreen_);
11476 this.primaryScreen_.textAttributes.reset();
11477
11478 this.clearHome(this.alternateScreen_);
11479 this.alternateScreen_.textAttributes.reset();
11480
11481 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
11482
11483 this.vt.reset();
11484
11485 this.softReset();
11486};
11487
11488/**
11489 * Soft terminal reset.
11490 *
11491 * Perform a soft reset to the default values listed in
11492 * http://www.vt100.net/docs/vt510-rm/DECSTR#T5-9
11493 */
11494hterm.Terminal.prototype.softReset = function() {
11495 // Reset terminal options to their default values.
11496 this.options_ = new hterm.Options();
11497
11498 // We show the cursor on soft reset but do not alter the blink state.
11499 this.options_.cursorBlink = !!this.timeouts_.cursorBlink;
11500
11501 // Xterm also resets the color palette on soft reset, even though it doesn't
11502 // seem to be documented anywhere.
11503 this.primaryScreen_.textAttributes.resetColorPalette();
11504 this.alternateScreen_.textAttributes.resetColorPalette();
11505
11506 // The xterm man page explicitly says this will happen on soft reset.
11507 this.setVTScrollRegion(null, null);
11508
11509 // Xterm also shows the cursor on soft reset, but does not alter the blink
11510 // state.
11511 this.setCursorVisible(true);
11512};
11513
11514/**
11515 * Move the cursor forward to the next tab stop, or to the last column
11516 * if no more tab stops are set.
11517 */
11518hterm.Terminal.prototype.forwardTabStop = function() {
11519 var column = this.screen_.cursorPosition.column;
11520
11521 for (var i = 0; i < this.tabStops_.length; i++) {
11522 if (this.tabStops_[i] > column) {
11523 this.setCursorColumn(this.tabStops_[i]);
11524 return;
11525 }
11526 }
11527
11528 // xterm does not clear the overflow flag on HT or CHT.
11529 var overflow = this.screen_.cursorPosition.overflow;
11530 this.setCursorColumn(this.screenSize.width - 1);
11531 this.screen_.cursorPosition.overflow = overflow;
11532};
11533
11534/**
11535 * Move the cursor backward to the previous tab stop, or to the first column
11536 * if no previous tab stops are set.
11537 */
11538hterm.Terminal.prototype.backwardTabStop = function() {
11539 var column = this.screen_.cursorPosition.column;
11540
11541 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
11542 if (this.tabStops_[i] < column) {
11543 this.setCursorColumn(this.tabStops_[i]);
11544 return;
11545 }
11546 }
11547
11548 this.setCursorColumn(1);
11549};
11550
11551/**
11552 * Set a tab stop at the given column.
11553 *
11554 * @param {integer} column Zero based column.
11555 */
11556hterm.Terminal.prototype.setTabStop = function(column) {
11557 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011558 if (this.tabStops_[i] == column) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011559
11560 if (this.tabStops_[i] < column) {
11561 this.tabStops_.splice(i + 1, 0, column);
11562 return;
11563 }
11564 }
11565
11566 this.tabStops_.splice(0, 0, column);
11567};
11568
11569/**
11570 * Clear the tab stop at the current cursor position.
11571 *
11572 * No effect if there is no tab stop at the current cursor position.
11573 */
11574hterm.Terminal.prototype.clearTabStopAtCursor = function() {
11575 var column = this.screen_.cursorPosition.column;
11576
11577 var i = this.tabStops_.indexOf(column);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011578 if (i == -1) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011579
11580 this.tabStops_.splice(i, 1);
11581};
11582
11583/**
11584 * Clear all tab stops.
11585 */
11586hterm.Terminal.prototype.clearAllTabStops = function() {
11587 this.tabStops_.length = 0;
11588 this.defaultTabStops = false;
11589};
11590
11591/**
11592 * Set up the default tab stops, starting from a given column.
11593 *
11594 * This sets a tabstop every (column % this.tabWidth) column, starting
11595 * from the specified column, or 0 if no column is provided. It also flags
11596 * future resizes to set them up.
11597 *
11598 * This does not clear the existing tab stops first, use clearAllTabStops
11599 * for that.
11600 *
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011601 * @param {integer} opt_start Optional starting zero based starting column,
11602 * useful for filling out missing tab stops when the terminal is resized.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011603 */
11604hterm.Terminal.prototype.setDefaultTabStops = function(opt_start) {
11605 var start = opt_start || 0;
11606 var w = this.tabWidth;
11607 // Round start up to a default tab stop.
11608 start = start - 1 - ((start - 1) % w) + w;
11609 for (var i = start; i < this.screenSize.width; i += w) {
11610 this.setTabStop(i);
11611 }
11612
11613 this.defaultTabStops = true;
11614};
11615
11616/**
11617 * Interpret a sequence of characters.
11618 *
11619 * Incomplete escape sequences are buffered until the next call.
11620 *
11621 * @param {string} str Sequence of characters to interpret or pass through.
11622 */
11623hterm.Terminal.prototype.interpret = function(str) {
11624 this.vt.interpret(str);
11625 this.scheduleSyncCursorPosition_();
11626};
11627
11628/**
11629 * Take over the given DIV for use as the terminal display.
11630 *
11631 * @param {HTMLDivElement} div The div to use as the terminal display.
11632 */
11633hterm.Terminal.prototype.decorate = function(div) {
11634 this.div_ = div;
11635
11636 this.scrollPort_.decorate(div);
11637 this.scrollPort_.setBackgroundImage(this.prefs_.get('background-image'));
11638 this.scrollPort_.setBackgroundSize(this.prefs_.get('background-size'));
11639 this.scrollPort_.setBackgroundPosition(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011640 this.prefs_.get('background-position'));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011641 this.scrollPort_.setUserCss(this.prefs_.get('user-css'));
11642
11643 this.div_.focus = this.focus.bind(this);
11644
11645 this.setFontSize(this.prefs_.get('font-size'));
11646 this.syncFontFamily();
11647
11648 this.setScrollbarVisible(this.prefs_.get('scrollbar-visible'));
11649 this.setScrollWheelMoveMultipler(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011650 this.prefs_.get('scroll-wheel-move-multiplier'));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011651
11652 this.document_ = this.scrollPort_.getDocument();
11653
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011654 this.document_.body.oncontextmenu = function() {
11655 return false;
11656 };
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011657
11658 var onMouse = this.onMouse_.bind(this);
11659 var screenNode = this.scrollPort_.getScreenNode();
11660 screenNode.addEventListener('mousedown', onMouse);
11661 screenNode.addEventListener('mouseup', onMouse);
11662 screenNode.addEventListener('mousemove', onMouse);
11663 this.scrollPort_.onScrollWheel = onMouse;
11664
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011665 screenNode.addEventListener('focus', this.onFocusChange_.bind(this, true));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011666 // Listen for mousedown events on the screenNode as in FF the focus
11667 // events don't bubble.
11668 screenNode.addEventListener('mousedown', function() {
11669 setTimeout(this.onFocusChange_.bind(this, true));
11670 }.bind(this));
11671
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011672 screenNode.addEventListener('blur', this.onFocusChange_.bind(this, false));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011673
11674 var style = this.document_.createElement('style');
11675 style.textContent =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011676 ('.cursor-node[focus="false"] {' +
11677 ' box-sizing: border-box;' +
11678 ' background-color: transparent !important;' +
11679 ' border-width: 2px;' +
11680 ' border-style: solid;' +
11681 '}' +
11682 '.wc-node {' +
11683 ' display: inline-block;' +
11684 ' text-align: center;' +
11685 ' width: ' + this.scrollPort_.characterSize.width * 2 + 'px;' +
11686 '}' +
11687 ':root {' +
11688 ' --hterm-blink-node-duration: 0.7s;' +
11689 '}' +
11690 '@keyframes blink {' +
11691 ' from { opacity: 1.0; }' +
11692 ' to { opacity: 0.0; }' +
11693 '}' +
11694 '.blink-node {' +
11695 ' animation-name: blink;' +
11696 ' animation-duration: var(--hterm-blink-node-duration);' +
11697 ' animation-iteration-count: infinite;' +
11698 ' animation-timing-function: ease-in-out;' +
11699 ' animation-direction: alternate;' +
11700 '}');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011701 this.document_.head.appendChild(style);
11702
11703 var styleSheets = this.document_.styleSheets;
11704 var cssRules = styleSheets[styleSheets.length - 1].cssRules;
11705 this.wcCssRule_ = cssRules[cssRules.length - 1];
11706
11707 this.cursorNode_ = this.document_.createElement('div');
11708 this.cursorNode_.className = 'cursor-node';
11709 this.cursorNode_.style.cssText =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011710 ('position: absolute;' +
11711 'top: -99px;' +
11712 'display: block;' +
11713 'width: ' + this.scrollPort_.characterSize.width + 'px;' +
11714 'height: ' + this.scrollPort_.characterSize.height + 'px;' +
11715 '-webkit-transition: opacity, background-color 100ms linear;' +
11716 '-moz-transition: opacity, background-color 100ms linear;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011717
11718 this.setCursorColor(this.prefs_.get('cursor-color'));
11719 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
11720 this.restyleCursor_();
11721
11722 this.document_.body.appendChild(this.cursorNode_);
11723
11724 // When 'enableMouseDragScroll' is off we reposition this element directly
11725 // under the mouse cursor after a click. This makes Chrome associate
11726 // subsequent mousemove events with the scroll-blocker. Since the
11727 // scroll-blocker is a peer (not a child) of the scrollport, the mousemove
11728 // events do not cause the scrollport to scroll.
11729 //
11730 // It's a hack, but it's the cleanest way I could find.
11731 this.scrollBlockerNode_ = this.document_.createElement('div');
11732 this.scrollBlockerNode_.style.cssText =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011733 ('position: absolute;' +
11734 'top: -99px;' +
11735 'display: block;' +
11736 'width: 10px;' +
11737 'height: 10px;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011738 this.document_.body.appendChild(this.scrollBlockerNode_);
11739
11740 this.scrollPort_.onScrollWheel = onMouse;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011741 ['mousedown',
11742 'mouseup',
11743 'mousemove',
11744 'click',
11745 'dblclick',
11746 ].forEach(function(event) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011747 this.scrollBlockerNode_.addEventListener(event, onMouse);
11748 this.cursorNode_.addEventListener(event, onMouse);
11749 this.document_.addEventListener(event, onMouse);
11750 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011751
11752 this.cursorNode_.addEventListener('mousedown', function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011753 setTimeout(this.focus.bind(this));
11754 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011755
11756 this.setReverseVideo(false);
11757
11758 this.scrollPort_.focus();
11759 this.scrollPort_.scheduleRedraw();
11760};
11761
11762/**
11763 * Return the HTML document that contains the terminal DOM nodes.
11764 *
11765 * @return {HTMLDocument}
11766 */
11767hterm.Terminal.prototype.getDocument = function() {
11768 return this.document_;
11769};
11770
11771/**
11772 * Focus the terminal.
11773 */
11774hterm.Terminal.prototype.focus = function() {
11775 this.scrollPort_.focus();
11776};
11777
11778/**
11779 * Return the HTML Element for a given row index.
11780 *
11781 * This is a method from the RowProvider interface. The ScrollPort uses
11782 * it to fetch rows on demand as they are scrolled into view.
11783 *
11784 * TODO(rginda): Consider saving scrollback rows as (HTML source, text content)
11785 * pairs to conserve memory.
11786 *
11787 * @param {integer} index The zero-based row index, measured relative to the
11788 * start of the scrollback buffer. On-screen rows will always have the
11789 * largest indices.
11790 * @return {HTMLElement} The 'x-row' element containing for the requested row.
11791 */
11792hterm.Terminal.prototype.getRowNode = function(index) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011793 if (index < this.scrollbackRows_.length) return this.scrollbackRows_[index];
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011794
11795 var screenIndex = index - this.scrollbackRows_.length;
11796 return this.screen_.rowsArray[screenIndex];
11797};
11798
11799/**
11800 * Return the text content for a given range of rows.
11801 *
11802 * This is a method from the RowProvider interface. The ScrollPort uses
11803 * it to fetch text content on demand when the user attempts to copy their
11804 * selection to the clipboard.
11805 *
11806 * @param {integer} start The zero-based row index to start from, measured
11807 * relative to the start of the scrollback buffer. On-screen rows will
11808 * always have the largest indices.
11809 * @param {integer} end The zero-based row index to end on, measured
11810 * relative to the start of the scrollback buffer.
11811 * @return {string} A single string containing the text value of the range of
11812 * rows. Lines will be newline delimited, with no trailing newline.
11813 */
11814hterm.Terminal.prototype.getRowsText = function(start, end) {
11815 var ary = [];
11816 for (var i = start; i < end; i++) {
11817 var node = this.getRowNode(i);
11818 ary.push(node.textContent);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011819 if (i < end - 1 && !node.getAttribute('line-overflow')) ary.push('\n');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011820 }
11821
11822 return ary.join('');
11823};
11824
11825/**
11826 * Return the text content for a given row.
11827 *
11828 * This is a method from the RowProvider interface. The ScrollPort uses
11829 * it to fetch text content on demand when the user attempts to copy their
11830 * selection to the clipboard.
11831 *
11832 * @param {integer} index The zero-based row index to return, measured
11833 * relative to the start of the scrollback buffer. On-screen rows will
11834 * always have the largest indices.
11835 * @return {string} A string containing the text value of the selected row.
11836 */
11837hterm.Terminal.prototype.getRowText = function(index) {
11838 var node = this.getRowNode(index);
11839 return node.textContent;
11840};
11841
11842/**
11843 * Return the total number of rows in the addressable screen and in the
11844 * scrollback buffer of this terminal.
11845 *
11846 * This is a method from the RowProvider interface. The ScrollPort uses
11847 * it to compute the size of the scrollbar.
11848 *
11849 * @return {integer} The number of rows in this terminal.
11850 */
11851hterm.Terminal.prototype.getRowCount = function() {
11852 return this.scrollbackRows_.length + this.screen_.rowsArray.length;
11853};
11854
11855/**
11856 * Create DOM nodes for new rows and append them to the end of the terminal.
11857 *
11858 * This is the only correct way to add a new DOM node for a row. Notice that
11859 * the new row is appended to the bottom of the list of rows, and does not
11860 * require renumbering (of the rowIndex property) of previous rows.
11861 *
11862 * If you think you want a new blank row somewhere in the middle of the
11863 * terminal, look into moveRows_().
11864 *
11865 * This method does not pay attention to vtScrollTop/Bottom, since you should
11866 * be using moveRows() in cases where they would matter.
11867 *
11868 * The cursor will be positioned at column 0 of the first inserted line.
11869 *
11870 * @param {number} count The number of rows to created.
11871 */
11872hterm.Terminal.prototype.appendRows_ = function(count) {
11873 var cursorRow = this.screen_.rowsArray.length;
11874 var offset = this.scrollbackRows_.length + cursorRow;
11875 for (var i = 0; i < count; i++) {
11876 var row = this.document_.createElement('x-row');
11877 row.appendChild(this.document_.createTextNode(''));
11878 row.rowIndex = offset + i;
11879 this.screen_.pushRow(row);
11880 }
11881
11882 var extraRows = this.screen_.rowsArray.length - this.screenSize.height;
11883 if (extraRows > 0) {
11884 var ary = this.screen_.shiftRows(extraRows);
11885 Array.prototype.push.apply(this.scrollbackRows_, ary);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011886 if (this.scrollPort_.isScrolledEnd) this.scheduleScrollDown_();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011887 }
11888
11889 if (cursorRow >= this.screen_.rowsArray.length)
11890 cursorRow = this.screen_.rowsArray.length - 1;
11891
11892 this.setAbsoluteCursorPosition(cursorRow, 0);
11893};
11894
11895/**
11896 * Relocate rows from one part of the addressable screen to another.
11897 *
11898 * This is used to recycle rows during VT scrolls (those which are driven
11899 * by VT commands, rather than by the user manipulating the scrollbar.)
11900 *
11901 * In this case, the blank lines scrolled into the scroll region are made of
11902 * the nodes we scrolled off. These have their rowIndex properties carefully
11903 * renumbered so as not to confuse the ScrollPort.
11904 *
11905 * @param {number} fromIndex The start index.
11906 * @param {number} count The number of rows to move.
11907 * @param {number} toIndex The destination index.
11908 */
11909hterm.Terminal.prototype.moveRows_ = function(fromIndex, count, toIndex) {
11910 var ary = this.screen_.removeRows(fromIndex, count);
11911 this.screen_.insertRows(toIndex, ary);
11912
11913 var start, end;
11914 if (fromIndex < toIndex) {
11915 start = fromIndex;
11916 end = toIndex + count;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011917 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011918 start = toIndex;
11919 end = fromIndex + count;
11920 }
11921
11922 this.renumberRows_(start, end);
11923 this.scrollPort_.scheduleInvalidate();
11924};
11925
11926/**
11927 * Renumber the rowIndex property of the given range of rows.
11928 *
11929 * The start and end indices are relative to the screen, not the scrollback.
11930 * Rows in the scrollback buffer cannot be renumbered. Since they are not
11931 * addressable (you can't delete them, scroll them, etc), you should have
11932 * no need to renumber scrollback rows.
11933 *
11934 * @param {number} start The start index.
11935 * @param {number} end The end index.
11936 * @param {hterm.Screen} opt_screen The screen to renumber.
11937 */
11938hterm.Terminal.prototype.renumberRows_ = function(start, end, opt_screen) {
11939 var screen = opt_screen || this.screen_;
11940
11941 var offset = this.scrollbackRows_.length;
11942 for (var i = start; i < end; i++) {
11943 screen.rowsArray[i].rowIndex = offset + i;
11944 }
11945};
11946
11947/**
11948 * Print a string to the terminal.
11949 *
11950 * This respects the current insert and wraparound modes. It will add new lines
11951 * to the end of the terminal, scrolling off the top into the scrollback buffer
11952 * if necessary.
11953 *
11954 * The string is *not* parsed for escape codes. Use the interpret() method if
11955 * that's what you're after.
11956 *
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011957 * @param {string} str The string to print.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011958 */
11959hterm.Terminal.prototype.print = function(str) {
11960 var startOffset = 0;
11961
11962 var strWidth = lib.wc.strWidth(str);
11963
11964 while (startOffset < strWidth) {
11965 if (this.options_.wraparound && this.screen_.cursorPosition.overflow) {
11966 this.screen_.commitLineOverflow();
11967 this.newLine();
11968 }
11969
11970 var count = strWidth - startOffset;
11971 var didOverflow = false;
11972 var substr;
11973
11974 if (this.screen_.cursorPosition.column + count >= this.screenSize.width) {
11975 didOverflow = true;
11976 count = this.screenSize.width - this.screen_.cursorPosition.column;
11977 }
11978
11979 if (didOverflow && !this.options_.wraparound) {
11980 // If the string overflowed the line but wraparound is off, then the
11981 // last printed character should be the last of the string.
11982 // TODO: This will add to our problems with multibyte UTF-16 characters.
11983 substr = lib.wc.substr(str, startOffset, count - 1) +
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011984 lib.wc.substr(str, strWidth - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011985 count = strWidth;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011986 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011987 substr = lib.wc.substr(str, startOffset, count);
11988 }
11989
11990 var tokens = hterm.TextAttributes.splitWidecharString(substr);
11991 for (var i = 0; i < tokens.length; i++) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011992 if (tokens[i].wcNode) this.screen_.textAttributes.wcNode = true;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011993
11994 if (this.options_.insertMode) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070011995 this.screen_.insertString(tokens[i].str);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070011996 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050011997 this.screen_.overwriteString(tokens[i].str);
11998 }
11999 this.screen_.textAttributes.wcNode = false;
12000 }
12001
12002 this.screen_.maybeClipCurrentRow();
12003 startOffset += count;
12004 }
12005
12006 this.scheduleSyncCursorPosition_();
12007
12008 if (this.scrollOnOutput_)
12009 this.scrollPort_.scrollRowToBottom(this.getRowCount());
12010};
12011
12012/**
12013 * Set the VT scroll region.
12014 *
12015 * This also resets the cursor position to the absolute (0, 0) position, since
12016 * that's what xterm appears to do.
12017 *
12018 * Setting the scroll region to the full height of the terminal will clear
12019 * the scroll region. This is *NOT* what most terminals do. We're explicitly
12020 * going "off-spec" here because it makes `screen` and `tmux` overflow into the
12021 * local scrollback buffer, which means the scrollbars and shift-pgup/pgdn
12022 * continue to work as most users would expect.
12023 *
12024 * @param {integer} scrollTop The zero-based top of the scroll region.
12025 * @param {integer} scrollBottom The zero-based bottom of the scroll region,
12026 * inclusive.
12027 */
12028hterm.Terminal.prototype.setVTScrollRegion = function(scrollTop, scrollBottom) {
12029 if (scrollTop == 0 && scrollBottom == this.screenSize.height - 1) {
12030 this.vtScrollTop_ = null;
12031 this.vtScrollBottom_ = null;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012032 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012033 this.vtScrollTop_ = scrollTop;
12034 this.vtScrollBottom_ = scrollBottom;
12035 }
12036};
12037
12038/**
12039 * Return the top row index according to the VT.
12040 *
12041 * This will return 0 unless the terminal has been told to restrict scrolling
12042 * to some lower row. It is used for some VT cursor positioning and scrolling
12043 * commands.
12044 *
12045 * @return {integer} The topmost row in the terminal's scroll region.
12046 */
12047hterm.Terminal.prototype.getVTScrollTop = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012048 if (this.vtScrollTop_ != null) return this.vtScrollTop_;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012049
12050 return 0;
12051};
12052
12053/**
12054 * Return the bottom row index according to the VT.
12055 *
12056 * This will return the height of the terminal unless the it has been told to
12057 * restrict scrolling to some higher row. It is used for some VT cursor
12058 * positioning and scrolling commands.
12059 *
12060 * @return {integer} The bottom most row in the terminal's scroll region.
12061 */
12062hterm.Terminal.prototype.getVTScrollBottom = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012063 if (this.vtScrollBottom_ != null) return this.vtScrollBottom_;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012064
12065 return this.screenSize.height - 1;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012066};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012067
12068/**
12069 * Process a '\n' character.
12070 *
12071 * If the cursor is on the final row of the terminal this will append a new
12072 * blank row to the screen and scroll the topmost row into the scrollback
12073 * buffer.
12074 *
12075 * Otherwise, this moves the cursor to column zero of the next row.
12076 */
12077hterm.Terminal.prototype.newLine = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012078 var cursorAtEndOfScreen =
12079 (this.screen_.cursorPosition.row == this.screen_.rowsArray.length - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012080
12081 if (this.vtScrollBottom_ != null) {
12082 // A VT Scroll region is active, we never append new rows.
12083 if (this.screen_.cursorPosition.row == this.vtScrollBottom_) {
12084 // We're at the end of the VT Scroll Region, perform a VT scroll.
12085 this.vtScrollUp(1);
12086 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012087 } else if (cursorAtEndOfScreen) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012088 // We're at the end of the screen, the only thing to do is put the
12089 // cursor to column 0.
12090 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012091 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012092 // Anywhere else, advance the cursor row, and reset the column.
12093 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
12094 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012095 } else if (cursorAtEndOfScreen) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012096 // We're at the end of the screen. Append a new row to the terminal,
12097 // shifting the top row into the scrollback.
12098 this.appendRows_(1);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012099 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012100 // Anywhere else in the screen just moves the cursor.
12101 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
12102 }
12103};
12104
12105/**
12106 * Like newLine(), except maintain the cursor column.
12107 */
12108hterm.Terminal.prototype.lineFeed = function() {
12109 var column = this.screen_.cursorPosition.column;
12110 this.newLine();
12111 this.setCursorColumn(column);
12112};
12113
12114/**
12115 * If autoCarriageReturn is set then newLine(), else lineFeed().
12116 */
12117hterm.Terminal.prototype.formFeed = function() {
12118 if (this.options_.autoCarriageReturn) {
12119 this.newLine();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012120 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012121 this.lineFeed();
12122 }
12123};
12124
12125/**
12126 * Move the cursor up one row, possibly inserting a blank line.
12127 *
12128 * The cursor column is not changed.
12129 */
12130hterm.Terminal.prototype.reverseLineFeed = function() {
12131 var scrollTop = this.getVTScrollTop();
12132 var currentRow = this.screen_.cursorPosition.row;
12133
12134 if (currentRow == scrollTop) {
12135 this.insertLines(1);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012136 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012137 this.setAbsoluteCursorRow(currentRow - 1);
12138 }
12139};
12140
12141/**
12142 * Replace all characters to the left of the current cursor with the space
12143 * character.
12144 *
12145 * TODO(rginda): This should probably *remove* the characters (not just replace
12146 * with a space) if there are no characters at or beyond the current cursor
12147 * position.
12148 */
12149hterm.Terminal.prototype.eraseToLeft = function() {
12150 var cursor = this.saveCursor();
12151 this.setCursorColumn(0);
12152 this.screen_.overwriteString(lib.f.getWhitespace(cursor.column + 1));
12153 this.restoreCursor(cursor);
12154};
12155
12156/**
12157 * Erase a given number of characters to the right of the cursor.
12158 *
12159 * The cursor position is unchanged.
12160 *
12161 * If the current background color is not the default background color this
12162 * will insert spaces rather than delete. This is unfortunate because the
12163 * trailing space will affect text selection, but it's difficult to come up
12164 * with a way to style empty space that wouldn't trip up the hterm.Screen
12165 * code.
12166 *
12167 * eraseToRight is ignored in the presence of a cursor overflow. This deviates
12168 * from xterm, but agrees with gnome-terminal and konsole, xfce4-terminal. See
12169 * crbug.com/232390 for details.
12170 *
12171 * @param {number} opt_count The number of characters to erase.
12172 */
12173hterm.Terminal.prototype.eraseToRight = function(opt_count) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012174 if (this.screen_.cursorPosition.overflow) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012175
12176 var maxCount = this.screenSize.width - this.screen_.cursorPosition.column;
12177 var count = opt_count ? Math.min(opt_count, maxCount) : maxCount;
12178
12179 if (this.screen_.textAttributes.background ===
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012180 this.screen_.textAttributes.DEFAULT_COLOR) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012181 var cursorRow = this.screen_.rowsArray[this.screen_.cursorPosition.row];
12182 if (hterm.TextAttributes.nodeWidth(cursorRow) <=
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012183 this.screen_.cursorPosition.column + count) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012184 this.screen_.deleteChars(count);
12185 this.clearCursorOverflow();
12186 return;
12187 }
12188 }
12189
12190 var cursor = this.saveCursor();
12191 this.screen_.overwriteString(lib.f.getWhitespace(count));
12192 this.restoreCursor(cursor);
12193 this.clearCursorOverflow();
12194};
12195
12196/**
12197 * Erase the current line.
12198 *
12199 * The cursor position is unchanged.
12200 */
12201hterm.Terminal.prototype.eraseLine = function() {
12202 var cursor = this.saveCursor();
12203 this.screen_.clearCursorRow();
12204 this.restoreCursor(cursor);
12205 this.clearCursorOverflow();
12206};
12207
12208/**
12209 * Erase all characters from the start of the screen to the current cursor
12210 * position, regardless of scroll region.
12211 *
12212 * The cursor position is unchanged.
12213 */
12214hterm.Terminal.prototype.eraseAbove = function() {
12215 var cursor = this.saveCursor();
12216
12217 this.eraseToLeft();
12218
12219 for (var i = 0; i < cursor.row; i++) {
12220 this.setAbsoluteCursorPosition(i, 0);
12221 this.screen_.clearCursorRow();
12222 }
12223
12224 this.restoreCursor(cursor);
12225 this.clearCursorOverflow();
12226};
12227
12228/**
12229 * Erase all characters from the current cursor position to the end of the
12230 * screen, regardless of scroll region.
12231 *
12232 * The cursor position is unchanged.
12233 */
12234hterm.Terminal.prototype.eraseBelow = function() {
12235 var cursor = this.saveCursor();
12236
12237 this.eraseToRight();
12238
12239 var bottom = this.screenSize.height - 1;
12240 for (var i = cursor.row + 1; i <= bottom; i++) {
12241 this.setAbsoluteCursorPosition(i, 0);
12242 this.screen_.clearCursorRow();
12243 }
12244
12245 this.restoreCursor(cursor);
12246 this.clearCursorOverflow();
12247};
12248
12249/**
12250 * Fill the terminal with a given character.
12251 *
12252 * This methods does not respect the VT scroll region.
12253 *
12254 * @param {string} ch The character to use for the fill.
12255 */
12256hterm.Terminal.prototype.fill = function(ch) {
12257 var cursor = this.saveCursor();
12258
12259 this.setAbsoluteCursorPosition(0, 0);
12260 for (var row = 0; row < this.screenSize.height; row++) {
12261 for (var col = 0; col < this.screenSize.width; col++) {
12262 this.setAbsoluteCursorPosition(row, col);
12263 this.screen_.overwriteString(ch);
12264 }
12265 }
12266
12267 this.restoreCursor(cursor);
12268};
12269
12270/**
12271 * Erase the entire display and leave the cursor at (0, 0).
12272 *
12273 * This does not respect the scroll region.
12274 *
12275 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
12276 * to the current screen.
12277 */
12278hterm.Terminal.prototype.clearHome = function(opt_screen) {
12279 var screen = opt_screen || this.screen_;
12280 var bottom = screen.getHeight();
12281
12282 if (bottom == 0) {
12283 // Empty screen, nothing to do.
12284 return;
12285 }
12286
12287 for (var i = 0; i < bottom; i++) {
12288 screen.setCursorPosition(i, 0);
12289 screen.clearCursorRow();
12290 }
12291
12292 screen.setCursorPosition(0, 0);
12293};
12294
12295/**
12296 * Erase the entire display without changing the cursor position.
12297 *
12298 * The cursor position is unchanged. This does not respect the scroll
12299 * region.
12300 *
12301 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
12302 * to the current screen.
12303 */
12304hterm.Terminal.prototype.clear = function(opt_screen) {
12305 var screen = opt_screen || this.screen_;
12306 var cursor = screen.cursorPosition.clone();
12307 this.clearHome(screen);
12308 screen.setCursorPosition(cursor.row, cursor.column);
12309};
12310
12311/**
12312 * VT command to insert lines at the current cursor row.
12313 *
12314 * This respects the current scroll region. Rows pushed off the bottom are
12315 * lost (they won't show up in the scrollback buffer).
12316 *
12317 * @param {integer} count The number of lines to insert.
12318 */
12319hterm.Terminal.prototype.insertLines = function(count) {
12320 var cursorRow = this.screen_.cursorPosition.row;
12321
12322 var bottom = this.getVTScrollBottom();
12323 count = Math.min(count, bottom - cursorRow);
12324
12325 // The moveCount is the number of rows we need to relocate to make room for
12326 // the new row(s). The count is the distance to move them.
12327 var moveCount = bottom - cursorRow - count + 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012328 if (moveCount) this.moveRows_(cursorRow, moveCount, cursorRow + count);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012329
12330 for (var i = count - 1; i >= 0; i--) {
12331 this.setAbsoluteCursorPosition(cursorRow + i, 0);
12332 this.screen_.clearCursorRow();
12333 }
12334};
12335
12336/**
12337 * VT command to delete lines at the current cursor row.
12338 *
12339 * New rows are added to the bottom of scroll region to take their place. New
12340 * rows are strictly there to take up space and have no content or style.
12341 *
12342 * @param {number} count The number of lines to delete.
12343 */
12344hterm.Terminal.prototype.deleteLines = function(count) {
12345 var cursor = this.saveCursor();
12346
12347 var top = cursor.row;
12348 var bottom = this.getVTScrollBottom();
12349
12350 var maxCount = bottom - top + 1;
12351 count = Math.min(count, maxCount);
12352
12353 var moveStart = bottom - count + 1;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012354 if (count != maxCount) this.moveRows_(top, count, moveStart);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012355
12356 for (var i = 0; i < count; i++) {
12357 this.setAbsoluteCursorPosition(moveStart + i, 0);
12358 this.screen_.clearCursorRow();
12359 }
12360
12361 this.restoreCursor(cursor);
12362 this.clearCursorOverflow();
12363};
12364
12365/**
12366 * Inserts the given number of spaces at the current cursor position.
12367 *
12368 * The cursor position is not changed.
12369 *
12370 * @param {number} count The number of spaces to insert.
12371 */
12372hterm.Terminal.prototype.insertSpace = function(count) {
12373 var cursor = this.saveCursor();
12374
12375 var ws = lib.f.getWhitespace(count || 1);
12376 this.screen_.insertString(ws);
12377 this.screen_.maybeClipCurrentRow();
12378
12379 this.restoreCursor(cursor);
12380 this.clearCursorOverflow();
12381};
12382
12383/**
12384 * Forward-delete the specified number of characters starting at the cursor
12385 * position.
12386 *
12387 * @param {integer} count The number of characters to delete.
12388 */
12389hterm.Terminal.prototype.deleteChars = function(count) {
12390 var deleted = this.screen_.deleteChars(count);
12391 if (deleted && !this.screen_.textAttributes.isDefault()) {
12392 var cursor = this.saveCursor();
12393 this.setCursorColumn(this.screenSize.width - deleted);
12394 this.screen_.insertString(lib.f.getWhitespace(deleted));
12395 this.restoreCursor(cursor);
12396 }
12397
12398 this.clearCursorOverflow();
12399};
12400
12401/**
12402 * Shift rows in the scroll region upwards by a given number of lines.
12403 *
12404 * New rows are inserted at the bottom of the scroll region to fill the
12405 * vacated rows. The new rows not filled out with the current text attributes.
12406 *
12407 * This function does not affect the scrollback rows at all. Rows shifted
12408 * off the top are lost.
12409 *
12410 * The cursor position is not altered.
12411 *
12412 * @param {integer} count The number of rows to scroll.
12413 */
12414hterm.Terminal.prototype.vtScrollUp = function(count) {
12415 var cursor = this.saveCursor();
12416
12417 this.setAbsoluteCursorRow(this.getVTScrollTop());
12418 this.deleteLines(count);
12419
12420 this.restoreCursor(cursor);
12421};
12422
12423/**
12424 * Shift rows below the cursor down by a given number of lines.
12425 *
12426 * This function respects the current scroll region.
12427 *
12428 * New rows are inserted at the top of the scroll region to fill the
12429 * vacated rows. The new rows not filled out with the current text attributes.
12430 *
12431 * This function does not affect the scrollback rows at all. Rows shifted
12432 * off the bottom are lost.
12433 *
12434 * @param {integer} count The number of rows to scroll.
12435 */
12436hterm.Terminal.prototype.vtScrollDown = function(opt_count) {
12437 var cursor = this.saveCursor();
12438
12439 this.setAbsoluteCursorPosition(this.getVTScrollTop(), 0);
12440 this.insertLines(opt_count);
12441
12442 this.restoreCursor(cursor);
12443};
12444
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012445/**
12446 * Set the cursor position.
12447 *
12448 * The cursor row is relative to the scroll region if the terminal has
12449 * 'origin mode' enabled, or relative to the addressable screen otherwise.
12450 *
12451 * @param {integer} row The new zero-based cursor row.
12452 * @param {integer} row The new zero-based cursor column.
12453 */
12454hterm.Terminal.prototype.setCursorPosition = function(row, column) {
12455 if (this.options_.originMode) {
12456 this.setRelativeCursorPosition(row, column);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012457 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012458 this.setAbsoluteCursorPosition(row, column);
12459 }
12460};
12461
12462/**
12463 * Move the cursor relative to its current position.
12464 *
12465 * @param {number} row
12466 * @param {number} column
12467 */
12468hterm.Terminal.prototype.setRelativeCursorPosition = function(row, column) {
12469 var scrollTop = this.getVTScrollTop();
12470 row = lib.f.clamp(row + scrollTop, scrollTop, this.getVTScrollBottom());
12471 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
12472 this.screen_.setCursorPosition(row, column);
12473};
12474
12475/**
12476 * Move the cursor to the specified position.
12477 *
12478 * @param {number} row
12479 * @param {number} column
12480 */
12481hterm.Terminal.prototype.setAbsoluteCursorPosition = function(row, column) {
12482 row = lib.f.clamp(row, 0, this.screenSize.height - 1);
12483 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
12484 this.screen_.setCursorPosition(row, column);
12485};
12486
12487/**
12488 * Set the cursor column.
12489 *
12490 * @param {integer} column The new zero-based cursor column.
12491 */
12492hterm.Terminal.prototype.setCursorColumn = function(column) {
12493 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, column);
12494};
12495
12496/**
12497 * Return the cursor column.
12498 *
12499 * @return {integer} The zero-based cursor column.
12500 */
12501hterm.Terminal.prototype.getCursorColumn = function() {
12502 return this.screen_.cursorPosition.column;
12503};
12504
12505/**
12506 * Set the cursor row.
12507 *
12508 * The cursor row is relative to the scroll region if the terminal has
12509 * 'origin mode' enabled, or relative to the addressable screen otherwise.
12510 *
12511 * @param {integer} row The new cursor row.
12512 */
12513hterm.Terminal.prototype.setAbsoluteCursorRow = function(row) {
12514 this.setAbsoluteCursorPosition(row, this.screen_.cursorPosition.column);
12515};
12516
12517/**
12518 * Return the cursor row.
12519 *
12520 * @return {integer} The zero-based cursor row.
12521 */
12522hterm.Terminal.prototype.getCursorRow = function() {
12523 return this.screen_.cursorPosition.row;
12524};
12525
12526/**
12527 * Request that the ScrollPort redraw itself soon.
12528 *
12529 * The redraw will happen asynchronously, soon after the call stack winds down.
12530 * Multiple calls will be coalesced into a single redraw.
12531 */
12532hterm.Terminal.prototype.scheduleRedraw_ = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012533 if (this.timeouts_.redraw) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012534
12535 var self = this;
12536 this.timeouts_.redraw = setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012537 delete self.timeouts_.redraw;
12538 self.scrollPort_.redraw_();
12539 }, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012540};
12541
12542/**
12543 * Request that the ScrollPort be scrolled to the bottom.
12544 *
12545 * The scroll will happen asynchronously, soon after the call stack winds down.
12546 * Multiple calls will be coalesced into a single scroll.
12547 *
12548 * This affects the scrollbar position of the ScrollPort, and has nothing to
12549 * do with the VT scroll commands.
12550 */
12551hterm.Terminal.prototype.scheduleScrollDown_ = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012552 if (this.timeouts_.scrollDown) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012553
12554 var self = this;
12555 this.timeouts_.scrollDown = setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012556 delete self.timeouts_.scrollDown;
12557 self.scrollPort_.scrollRowToBottom(self.getRowCount());
12558 }, 10);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012559};
12560
12561/**
12562 * Move the cursor up a specified number of rows.
12563 *
12564 * @param {integer} count The number of rows to move the cursor.
12565 */
12566hterm.Terminal.prototype.cursorUp = function(count) {
12567 return this.cursorDown(-(count || 1));
12568};
12569
12570/**
12571 * Move the cursor down a specified number of rows.
12572 *
12573 * @param {integer} count The number of rows to move the cursor.
12574 */
12575hterm.Terminal.prototype.cursorDown = function(count) {
12576 count = count || 1;
12577 var minHeight = (this.options_.originMode ? this.getVTScrollTop() : 0);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012578 var maxHeight =
12579 (this.options_.originMode ? this.getVTScrollBottom() :
12580 this.screenSize.height - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012581
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012582 var row = lib.f.clamp(
12583 this.screen_.cursorPosition.row + count, minHeight, maxHeight);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012584 this.setAbsoluteCursorRow(row);
12585};
12586
12587/**
12588 * Move the cursor left a specified number of columns.
12589 *
12590 * If reverse wraparound mode is enabled and the previous row wrapped into
12591 * the current row then we back up through the wraparound as well.
12592 *
12593 * @param {integer} count The number of columns to move the cursor.
12594 */
12595hterm.Terminal.prototype.cursorLeft = function(count) {
12596 count = count || 1;
12597
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012598 if (count < 1) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012599
12600 var currentColumn = this.screen_.cursorPosition.column;
12601 if (this.options_.reverseWraparound) {
12602 if (this.screen_.cursorPosition.overflow) {
12603 // If this cursor is in the right margin, consume one count to get it
12604 // back to the last column. This only applies when we're in reverse
12605 // wraparound mode.
12606 count--;
12607 this.clearCursorOverflow();
12608
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012609 if (!count) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012610 }
12611
12612 var newRow = this.screen_.cursorPosition.row;
12613 var newColumn = currentColumn - count;
12614 if (newColumn < 0) {
12615 newRow = newRow - Math.floor(count / this.screenSize.width) - 1;
12616 if (newRow < 0) {
12617 // xterm also wraps from row 0 to the last row.
12618 newRow = this.screenSize.height + newRow % this.screenSize.height;
12619 }
12620 newColumn = this.screenSize.width + newColumn % this.screenSize.width;
12621 }
12622
12623 this.setCursorPosition(Math.max(newRow, 0), newColumn);
12624
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012625 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012626 var newColumn = Math.max(currentColumn - count, 0);
12627 this.setCursorColumn(newColumn);
12628 }
12629};
12630
12631/**
12632 * Move the cursor right a specified number of columns.
12633 *
12634 * @param {integer} count The number of columns to move the cursor.
12635 */
12636hterm.Terminal.prototype.cursorRight = function(count) {
12637 count = count || 1;
12638
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012639 if (count < 1) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012640
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012641 var column = lib.f.clamp(
12642 this.screen_.cursorPosition.column + count, 0, this.screenSize.width - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012643 this.setCursorColumn(column);
12644};
12645
12646/**
12647 * Reverse the foreground and background colors of the terminal.
12648 *
12649 * This only affects text that was drawn with no attributes.
12650 *
12651 * TODO(rginda): Test xterm to see if reverse is respected for text that has
12652 * been drawn with attributes that happen to coincide with the default
12653 * 'no-attribute' colors. My guess is probably not.
12654 *
12655 * @param {boolean} state The state to set.
12656 */
12657hterm.Terminal.prototype.setReverseVideo = function(state) {
12658 this.options_.reverseVideo = state;
12659 if (state) {
12660 this.scrollPort_.setForegroundColor(this.prefs_.get('background-color'));
12661 this.scrollPort_.setBackgroundColor(this.prefs_.get('foreground-color'));
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012662 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012663 this.scrollPort_.setForegroundColor(this.prefs_.get('foreground-color'));
12664 this.scrollPort_.setBackgroundColor(this.prefs_.get('background-color'));
12665 }
12666};
12667
12668/**
12669 * Ring the terminal bell.
12670 *
12671 * This will not play the bell audio more than once per second.
12672 */
12673hterm.Terminal.prototype.ringBell = function() {
12674 this.cursorNode_.style.backgroundColor =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012675 this.scrollPort_.getForegroundColor();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012676
12677 var self = this;
12678 setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012679 self.cursorNode_.style.backgroundColor = self.prefs_.get('cursor-color');
12680 }, 200);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012681
12682 // bellSquelchTimeout_ affects both audio and notification bells.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012683 if (this.bellSquelchTimeout_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012684
12685 if (this.bellAudio_.getAttribute('src')) {
12686 this.bellAudio_.play();
12687 this.bellSequelchTimeout_ = setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012688 delete this.bellSquelchTimeout_;
12689 }.bind(this), 500);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012690 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012691 delete this.bellSquelchTimeout_;
12692 }
12693
12694 if (this.desktopNotificationBell_ && !this.document_.hasFocus()) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012695 var n = new Notification(lib.f.replaceVars(
12696 hterm.desktopNotificationTitle,
12697 {'title': window.document.title || 'hterm'}));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012698 this.bellNotificationList_.push(n);
12699 // TODO: Should we try to raise the window here?
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012700 n.onclick = function() {
12701 self.closeBellNotifications_();
12702 };
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012703 }
12704};
12705
12706/**
12707 * Set the origin mode bit.
12708 *
12709 * If origin mode is on, certain VT cursor and scrolling commands measure their
12710 * row parameter relative to the VT scroll region. Otherwise, row 0 corresponds
12711 * to the top of the addressable screen.
12712 *
12713 * Defaults to off.
12714 *
12715 * @param {boolean} state True to set origin mode, false to unset.
12716 */
12717hterm.Terminal.prototype.setOriginMode = function(state) {
12718 this.options_.originMode = state;
12719 this.setCursorPosition(0, 0);
12720};
12721
12722/**
12723 * Set the insert mode bit.
12724 *
12725 * If insert mode is on, existing text beyond the cursor position will be
12726 * shifted right to make room for new text. Otherwise, new text overwrites
12727 * any existing text.
12728 *
12729 * Defaults to off.
12730 *
12731 * @param {boolean} state True to set insert mode, false to unset.
12732 */
12733hterm.Terminal.prototype.setInsertMode = function(state) {
12734 this.options_.insertMode = state;
12735};
12736
12737/**
12738 * Set the auto carriage return bit.
12739 *
12740 * If auto carriage return is on then a formfeed character is interpreted
12741 * as a newline, otherwise it's the same as a linefeed. The difference boils
12742 * down to whether or not the cursor column is reset.
12743 *
12744 * @param {boolean} state The state to set.
12745 */
12746hterm.Terminal.prototype.setAutoCarriageReturn = function(state) {
12747 this.options_.autoCarriageReturn = state;
12748};
12749
12750/**
12751 * Set the wraparound mode bit.
12752 *
12753 * If wraparound mode is on, certain VT commands will allow the cursor to wrap
12754 * to the start of the following row. Otherwise, the cursor is clamped to the
12755 * end of the screen and attempts to write past it are ignored.
12756 *
12757 * Defaults to on.
12758 *
12759 * @param {boolean} state True to set wraparound mode, false to unset.
12760 */
12761hterm.Terminal.prototype.setWraparound = function(state) {
12762 this.options_.wraparound = state;
12763};
12764
12765/**
12766 * Set the reverse-wraparound mode bit.
12767 *
12768 * If wraparound mode is off, certain VT commands will allow the cursor to wrap
12769 * to the end of the previous row. Otherwise, the cursor is clamped to column
12770 * 0.
12771 *
12772 * Defaults to off.
12773 *
12774 * @param {boolean} state True to set reverse-wraparound mode, false to unset.
12775 */
12776hterm.Terminal.prototype.setReverseWraparound = function(state) {
12777 this.options_.reverseWraparound = state;
12778};
12779
12780/**
12781 * Selects between the primary and alternate screens.
12782 *
12783 * If alternate mode is on, the alternate screen is active. Otherwise the
12784 * primary screen is active.
12785 *
12786 * Swapping screens has no effect on the scrollback buffer.
12787 *
12788 * Each screen maintains its own cursor position.
12789 *
12790 * Defaults to off.
12791 *
12792 * @param {boolean} state True to set alternate mode, false to unset.
12793 */
12794hterm.Terminal.prototype.setAlternateMode = function(state) {
12795 var cursor = this.saveCursor();
12796 this.screen_ = state ? this.alternateScreen_ : this.primaryScreen_;
12797
12798 if (this.screen_.rowsArray.length &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012799 this.screen_.rowsArray[0].rowIndex != this.scrollbackRows_.length) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012800 // If the screen changed sizes while we were away, our rowIndexes may
12801 // be incorrect.
12802 var offset = this.scrollbackRows_.length;
12803 var ary = this.screen_.rowsArray;
12804 for (var i = 0; i < ary.length; i++) {
12805 ary[i].rowIndex = offset + i;
12806 }
12807 }
12808
12809 this.realizeWidth_(this.screenSize.width);
12810 this.realizeHeight_(this.screenSize.height);
12811 this.scrollPort_.syncScrollHeight();
12812 this.scrollPort_.invalidate();
12813
12814 this.restoreCursor(cursor);
12815 this.scrollPort_.resize();
12816};
12817
12818/**
12819 * Set the cursor-blink mode bit.
12820 *
12821 * If cursor-blink is on, the cursor will blink when it is visible. Otherwise
12822 * a visible cursor does not blink.
12823 *
12824 * You should make sure to turn blinking off if you're going to dispose of a
12825 * terminal, otherwise you'll leak a timeout.
12826 *
12827 * Defaults to on.
12828 *
12829 * @param {boolean} state True to set cursor-blink mode, false to unset.
12830 */
12831hterm.Terminal.prototype.setCursorBlink = function(state) {
12832 this.options_.cursorBlink = state;
12833
12834 if (!state && this.timeouts_.cursorBlink) {
12835 clearTimeout(this.timeouts_.cursorBlink);
12836 delete this.timeouts_.cursorBlink;
12837 }
12838
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012839 if (this.options_.cursorVisible) this.setCursorVisible(true);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012840};
12841
12842/**
12843 * Set the cursor-visible mode bit.
12844 *
12845 * If cursor-visible is on, the cursor will be visible. Otherwise it will not.
12846 *
12847 * Defaults to on.
12848 *
12849 * @param {boolean} state True to set cursor-visible mode, false to unset.
12850 */
12851hterm.Terminal.prototype.setCursorVisible = function(state) {
12852 this.options_.cursorVisible = state;
12853
12854 if (!state) {
12855 if (this.timeouts_.cursorBlink) {
12856 clearTimeout(this.timeouts_.cursorBlink);
12857 delete this.timeouts_.cursorBlink;
12858 }
12859 this.cursorNode_.style.opacity = '0';
12860 return;
12861 }
12862
12863 this.syncCursorPosition_();
12864
12865 this.cursorNode_.style.opacity = '1';
12866
12867 if (this.options_.cursorBlink) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012868 if (this.timeouts_.cursorBlink) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012869
12870 this.onCursorBlink_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012871 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012872 if (this.timeouts_.cursorBlink) {
12873 clearTimeout(this.timeouts_.cursorBlink);
12874 delete this.timeouts_.cursorBlink;
12875 }
12876 }
12877};
12878
12879/**
12880 * Synchronizes the visible cursor and document selection with the current
12881 * cursor coordinates.
12882 */
12883hterm.Terminal.prototype.syncCursorPosition_ = function() {
12884 var topRowIndex = this.scrollPort_.getTopRowIndex();
12885 var bottomRowIndex = this.scrollPort_.getBottomRowIndex(topRowIndex);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012886 var cursorRowIndex =
12887 this.scrollbackRows_.length + this.screen_.cursorPosition.row;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012888
12889 if (cursorRowIndex > bottomRowIndex) {
12890 // Cursor is scrolled off screen, move it outside of the visible area.
12891 this.cursorNode_.style.top = -this.scrollPort_.characterSize.height + 'px';
12892 return;
12893 }
12894
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012895 if (this.options_.cursorVisible && this.cursorNode_.style.display == 'none') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012896 // Re-display the terminal cursor if it was hidden by the mouse cursor.
12897 this.cursorNode_.style.display = '';
12898 }
12899
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012900 this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin +
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012901 this.scrollPort_.characterSize.height * (cursorRowIndex - topRowIndex) +
12902 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012903 this.cursorNode_.style.left = this.scrollPort_.characterSize.width *
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012904 this.screen_.cursorPosition.column +
12905 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012906
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012907 this.cursorNode_.setAttribute(
12908 'title',
12909 '(' + this.screen_.cursorPosition.row + ', ' +
12910 this.screen_.cursorPosition.column + ')');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012911
12912 // Update the caret for a11y purposes.
12913 var selection = this.document_.getSelection();
12914 if (selection && selection.isCollapsed)
12915 this.screen_.syncSelectionCaret(selection);
12916};
12917
12918/**
12919 * Adjusts the style of this.cursorNode_ according to the current cursor shape
12920 * and character cell dimensions.
12921 */
12922hterm.Terminal.prototype.restyleCursor_ = function() {
12923 var shape = this.cursorShape_;
12924
12925 if (this.cursorNode_.getAttribute('focus') == 'false') {
12926 // Always show a block cursor when unfocused.
12927 shape = hterm.Terminal.cursorShape.BLOCK;
12928 }
12929
12930 var style = this.cursorNode_.style;
12931
12932 style.width = this.scrollPort_.characterSize.width + 'px';
12933
12934 switch (shape) {
12935 case hterm.Terminal.cursorShape.BEAM:
12936 style.height = this.scrollPort_.characterSize.height + 'px';
12937 style.backgroundColor = 'transparent';
12938 style.borderBottomStyle = null;
12939 style.borderLeftStyle = 'solid';
12940 break;
12941
12942 case hterm.Terminal.cursorShape.UNDERLINE:
12943 style.height = this.scrollPort_.characterSize.baseline + 'px';
12944 style.backgroundColor = 'transparent';
12945 style.borderBottomStyle = 'solid';
12946 // correct the size to put it exactly at the baseline
12947 style.borderLeftStyle = null;
12948 break;
12949
12950 default:
12951 style.height = this.scrollPort_.characterSize.height + 'px';
12952 style.backgroundColor = this.cursorColor_;
12953 style.borderBottomStyle = null;
12954 style.borderLeftStyle = null;
12955 break;
12956 }
12957};
12958
12959/**
12960 * Synchronizes the visible cursor with the current cursor coordinates.
12961 *
12962 * The sync will happen asynchronously, soon after the call stack winds down.
12963 * Multiple calls will be coalesced into a single sync.
12964 */
12965hterm.Terminal.prototype.scheduleSyncCursorPosition_ = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012966 if (this.timeouts_.syncCursor) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012967
12968 var self = this;
12969 this.timeouts_.syncCursor = setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070012970 self.syncCursorPosition_();
12971 delete self.timeouts_.syncCursor;
12972 }, 0);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012973};
12974
12975/**
12976 * Show or hide the zoom warning.
12977 *
12978 * The zoom warning is a message warning the user that their browser zoom must
12979 * be set to 100% in order for hterm to function properly.
12980 *
12981 * @param {boolean} state True to show the message, false to hide it.
12982 */
12983hterm.Terminal.prototype.showZoomWarning_ = function(state) {
12984 if (!this.zoomWarningNode_) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012985 if (!state) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050012986
12987 this.zoomWarningNode_ = this.document_.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070012988 this.zoomWarningNode_.style.cssText =
12989 ('color: black;' +
12990 'background-color: #ff2222;' +
12991 'font-size: large;' +
12992 'border-radius: 8px;' +
12993 'opacity: 0.75;' +
12994 'padding: 0.2em 0.5em 0.2em 0.5em;' +
12995 'top: 0.5em;' +
12996 'right: 1.2em;' +
12997 'position: absolute;' +
12998 '-webkit-text-size-adjust: none;' +
12999 '-webkit-user-select: none;' +
13000 '-moz-text-size-adjust: none;' +
13001 '-moz-user-select: none;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013002
13003 this.zoomWarningNode_.addEventListener('click', function(e) {
13004 this.parentNode.removeChild(this);
13005 });
13006 }
13007
13008 this.zoomWarningNode_.textContent = lib.MessageManager.replaceReferences(
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013009 hterm.zoomWarningMessage,
13010 [parseInt(this.scrollPort_.characterSize.zoomFactor * 100)]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013011
13012 this.zoomWarningNode_.style.fontFamily = this.prefs_.get('font-family');
13013
13014 if (state) {
13015 if (!this.zoomWarningNode_.parentNode)
13016 this.div_.parentNode.appendChild(this.zoomWarningNode_);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013017 } else if (this.zoomWarningNode_.parentNode) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013018 this.zoomWarningNode_.parentNode.removeChild(this.zoomWarningNode_);
13019 }
13020};
13021
13022/**
13023 * Show the terminal overlay for a given amount of time.
13024 *
13025 * The terminal overlay appears in inverse video in a large font, centered
13026 * over the terminal. You should probably keep the overlay message brief,
13027 * since it's in a large font and you probably aren't going to check the size
13028 * of the terminal first.
13029 *
13030 * @param {string} msg The text (not HTML) message to display in the overlay.
13031 * @param {number} opt_timeout The amount of time to wait before fading out
13032 * the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
13033 * stay up forever (or until the next overlay).
13034 */
13035hterm.Terminal.prototype.showOverlay = function(msg, opt_timeout) {
13036 if (!this.overlayNode_) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013037 if (!this.div_) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013038
13039 this.overlayNode_ = this.document_.createElement('div');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013040 this.overlayNode_.style.cssText =
13041 ('border-radius: 15px;' +
13042 'font-size: xx-large;' +
13043 'opacity: 0.75;' +
13044 'padding: 0.2em 0.5em 0.2em 0.5em;' +
13045 'position: absolute;' +
13046 '-webkit-user-select: none;' +
13047 '-webkit-transition: opacity 180ms ease-in;' +
13048 '-moz-user-select: none;' +
13049 '-moz-transition: opacity 180ms ease-in;');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013050
13051 this.overlayNode_.addEventListener('mousedown', function(e) {
13052 e.preventDefault();
13053 e.stopPropagation();
13054 }, true);
13055 }
13056
13057 this.overlayNode_.style.color = this.prefs_.get('background-color');
13058 this.overlayNode_.style.backgroundColor = this.prefs_.get('foreground-color');
13059 this.overlayNode_.style.fontFamily = this.prefs_.get('font-family');
13060
13061 this.overlayNode_.textContent = msg;
13062 this.overlayNode_.style.opacity = '0.75';
13063
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013064 if (!this.overlayNode_.parentNode) this.div_.appendChild(this.overlayNode_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013065
13066 var divSize = hterm.getClientSize(this.div_);
13067 var overlaySize = hterm.getClientSize(this.overlayNode_);
13068
13069 this.overlayNode_.style.top =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013070 (divSize.height - overlaySize.height) / 2 + 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013071 this.overlayNode_.style.left = (divSize.width - overlaySize.width -
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013072 this.scrollPort_.currentScrollbarWidthPx) /
13073 2 +
13074 'px';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013075
13076 var self = this;
13077
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013078 if (this.overlayTimeout_) clearTimeout(this.overlayTimeout_);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013079
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013080 if (opt_timeout === null) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013081
13082 this.overlayTimeout_ = setTimeout(function() {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013083 self.overlayNode_.style.opacity = '0';
13084 self.overlayTimeout_ = setTimeout(function() {
13085 if (self.overlayNode_.parentNode)
13086 self.overlayNode_.parentNode.removeChild(self.overlayNode_);
13087 self.overlayTimeout_ = null;
13088 self.overlayNode_.style.opacity = '0.75';
13089 }, 200);
13090 }, opt_timeout || 1500);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013091};
13092
13093/**
13094 * Paste from the system clipboard to the terminal.
13095 */
13096hterm.Terminal.prototype.paste = function() {
13097 hterm.pasteFromClipboard(this.document_);
13098};
13099
13100/**
13101 * Copy a string to the system clipboard.
13102 *
13103 * Note: If there is a selected range in the terminal, it'll be cleared.
13104 *
13105 * @param {string} str The string to copy.
13106 */
13107hterm.Terminal.prototype.copyStringToClipboard = function(str) {
13108 if (this.prefs_.get('enable-clipboard-notice'))
13109 setTimeout(this.showOverlay.bind(this, hterm.notifyCopyMessage, 500), 200);
13110
13111 var copySource = this.document_.createElement('pre');
13112 copySource.textContent = str;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013113 copySource.style.cssText =
13114 ('-webkit-user-select: text;' +
13115 '-moz-user-select: text;' +
13116 'position: absolute;' +
13117 'top: -99px');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013118
13119 this.document_.body.appendChild(copySource);
13120
13121 var selection = this.document_.getSelection();
13122 var anchorNode = selection.anchorNode;
13123 var anchorOffset = selection.anchorOffset;
13124 var focusNode = selection.focusNode;
13125 var focusOffset = selection.focusOffset;
13126
13127 selection.selectAllChildren(copySource);
13128
13129 hterm.copySelectionToClipboard(this.document_);
13130
13131 // IE doesn't support selection.extend. This means that the selection
13132 // won't return on IE.
13133 if (selection.extend) {
13134 selection.collapse(anchorNode, anchorOffset);
13135 selection.extend(focusNode, focusOffset);
13136 }
13137
13138 copySource.parentNode.removeChild(copySource);
13139};
13140
13141/**
13142 * Returns the selected text, or null if no text is selected.
13143 *
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013144 * @return {?string}
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013145 */
13146hterm.Terminal.prototype.getSelectionText = function() {
13147 var selection = this.scrollPort_.selection;
13148 selection.sync();
13149
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013150 if (selection.isCollapsed) return null;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013151
13152 // Start offset measures from the beginning of the line.
13153 var startOffset = selection.startOffset;
13154 var node = selection.startNode;
13155
13156 if (node.nodeName != 'X-ROW') {
13157 // If the selection doesn't start on an x-row node, then it must be
13158 // somewhere inside the x-row. Add any characters from previous siblings
13159 // into the start offset.
13160
13161 if (node.nodeName == '#text' && node.parentNode.nodeName == 'SPAN') {
13162 // If node is the text node in a styled span, move up to the span node.
13163 node = node.parentNode;
13164 }
13165
13166 while (node.previousSibling) {
13167 node = node.previousSibling;
13168 startOffset += hterm.TextAttributes.nodeWidth(node);
13169 }
13170 }
13171
13172 // End offset measures from the end of the line.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013173 var endOffset =
13174 (hterm.TextAttributes.nodeWidth(selection.endNode) - selection.endOffset);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013175 node = selection.endNode;
13176
13177 if (node.nodeName != 'X-ROW') {
13178 // If the selection doesn't end on an x-row node, then it must be
13179 // somewhere inside the x-row. Add any characters from following siblings
13180 // into the end offset.
13181
13182 if (node.nodeName == '#text' && node.parentNode.nodeName == 'SPAN') {
13183 // If node is the text node in a styled span, move up to the span node.
13184 node = node.parentNode;
13185 }
13186
13187 while (node.nextSibling) {
13188 node = node.nextSibling;
13189 endOffset += hterm.TextAttributes.nodeWidth(node);
13190 }
13191 }
13192
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013193 var rv = this.getRowsText(
13194 selection.startRow.rowIndex, selection.endRow.rowIndex + 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013195 return lib.wc.substring(rv, startOffset, lib.wc.strWidth(rv) - endOffset);
13196};
13197
13198/**
13199 * Copy the current selection to the system clipboard, then clear it after a
13200 * short delay.
13201 */
13202hterm.Terminal.prototype.copySelectionToClipboard = function() {
13203 var text = this.getSelectionText();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013204 if (text != null) this.copyStringToClipboard(text);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013205};
13206
13207hterm.Terminal.prototype.overlaySize = function() {
13208 this.showOverlay(this.screenSize.width + 'x' + this.screenSize.height);
13209};
13210
13211/**
13212 * Invoked by hterm.Terminal.Keyboard when a VT keystroke is detected.
13213 *
13214 * @param {string} string The VT string representing the keystroke, in UTF-16.
13215 */
13216hterm.Terminal.prototype.onVTKeystroke = function(string) {
13217 if (this.scrollOnKeystroke_)
13218 this.scrollPort_.scrollRowToBottom(this.getRowCount());
13219
13220 this.io.onVTKeystroke(this.keyboard.encode(string));
13221};
13222
13223/**
13224 * Launches url in a new tab.
13225 *
13226 * @param {string} url URL to launch in a new tab.
13227 */
13228hterm.Terminal.prototype.openUrl = function(url) {
13229 var win = window.open(url, '_blank');
13230 win.focus();
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013231};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013232
13233/**
13234 * Open the selected url.
13235 */
13236hterm.Terminal.prototype.openSelectedUrl_ = function() {
13237 var str = this.getSelectionText();
13238
13239 // If there is no selection, try and expand wherever they clicked.
13240 if (str == null) {
13241 this.screen_.expandSelection(this.document_.getSelection());
13242 str = this.getSelectionText();
13243 }
13244
13245 // Make sure URL is valid before opening.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013246 if (str.length > 2048 || str.search(/[\s\[\](){}<>"'\\^`]/) >= 0) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013247 // If the URL isn't anchored, it'll open relative to the extension.
13248 // We have no way of knowing the correct schema, so assume http.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013249 if (str.search('^[a-zA-Z][a-zA-Z0-9+.-]*://') < 0) str = 'http://' + str;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013250
13251 this.openUrl(str);
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013252};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013253
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013254/**
13255 * Add the terminalRow and terminalColumn properties to mouse events and
13256 * then forward on to onMouse().
13257 *
13258 * The terminalRow and terminalColumn properties contain the (row, column)
13259 * coordinates for the mouse event.
13260 *
13261 * @param {Event} e The mouse event to handle.
13262 */
13263hterm.Terminal.prototype.onMouse_ = function(e) {
13264 if (e.processedByTerminalHandler_) {
13265 // We register our event handlers on the document, as well as the cursor
13266 // and the scroll blocker. Mouse events that occur on the cursor or
13267 // scroll blocker will also appear on the document, but we don't want to
13268 // process them twice.
13269 //
13270 // We can't just prevent bubbling because that has other side effects, so
13271 // we decorate the event object with this property instead.
13272 return;
13273 }
13274
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013275 var reportMouseEvents =
13276 (!this.defeatMouseReports_ &&
13277 this.vt.mouseReport != this.vt.MOUSE_REPORT_DISABLED);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013278
13279 e.processedByTerminalHandler_ = true;
13280
13281 // One based row/column stored on the mouse event.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013282 e.terminalRow = parseInt(
13283 (e.clientY - this.scrollPort_.visibleRowTopMargin) /
13284 this.scrollPort_.characterSize.height) +
13285 1;
13286 e.terminalColumn =
13287 parseInt(e.clientX / this.scrollPort_.characterSize.width) + 1;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013288
13289 if (e.type == 'mousedown' && e.terminalColumn > this.screenSize.width) {
13290 // Mousedown in the scrollbar area.
13291 return;
13292 }
13293
13294 if (this.options_.cursorVisible && !reportMouseEvents) {
13295 // If the cursor is visible and we're not sending mouse events to the
13296 // host app, then we want to hide the terminal cursor when the mouse
13297 // cursor is over top. This keeps the terminal cursor from interfering
13298 // with local text selection.
13299 if (e.terminalRow - 1 == this.screen_.cursorPosition.row &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013300 e.terminalColumn - 1 == this.screen_.cursorPosition.column) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013301 this.cursorNode_.style.display = 'none';
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013302 } else if (this.cursorNode_.style.display == 'none') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013303 this.cursorNode_.style.display = '';
13304 }
13305 }
13306
13307 if (e.type == 'mousedown') {
13308 if (e.altKey || !reportMouseEvents) {
13309 // If VT mouse reporting is disabled, or has been defeated with
13310 // alt-mousedown, then the mouse will act on the local selection.
13311 this.defeatMouseReports_ = true;
13312 this.setSelectionEnabled(true);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013313 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013314 // Otherwise we defer ownership of the mouse to the VT.
13315 this.defeatMouseReports_ = false;
13316 this.document_.getSelection().collapseToEnd();
13317 this.setSelectionEnabled(false);
13318 e.preventDefault();
13319 }
13320 }
13321
13322 if (!reportMouseEvents) {
13323 if (e.type == 'dblclick' && this.copyOnSelect) {
13324 this.screen_.expandSelection(this.document_.getSelection());
13325 this.copySelectionToClipboard(this.document_);
13326 }
13327
13328 if (e.type == 'click' && !e.shiftKey && e.ctrlKey) {
13329 // Debounce this event with the dblclick event. If you try to doubleclick
13330 // a URL to open it, Chrome will fire click then dblclick, but we won't
13331 // have expanded the selection text at the first click event.
13332 clearTimeout(this.timeouts_.openUrl);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013333 this.timeouts_.openUrl =
13334 setTimeout(this.openSelectedUrl_.bind(this), 500);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013335 return;
13336 }
13337
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013338 if (e.type == 'mousedown' && e.which == this.mousePasteButton) this.paste();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013339
13340 if (e.type == 'mouseup' && e.which == 1 && this.copyOnSelect &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013341 !this.document_.getSelection().isCollapsed) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013342 this.copySelectionToClipboard(this.document_);
13343 }
13344
13345 if ((e.type == 'mousemove' || e.type == 'mouseup') &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013346 this.scrollBlockerNode_.engaged) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013347 // Disengage the scroll-blocker after one of these events.
13348 this.scrollBlockerNode_.engaged = false;
13349 this.scrollBlockerNode_.style.top = '-99px';
13350 }
13351
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013352 } else /* if (this.reportMouseEvents) */ {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013353 if (!this.scrollBlockerNode_.engaged) {
13354 if (e.type == 'mousedown') {
13355 // Move the scroll-blocker into place if we want to keep the scrollport
13356 // from scrolling.
13357 this.scrollBlockerNode_.engaged = true;
13358 this.scrollBlockerNode_.style.top = (e.clientY - 5) + 'px';
13359 this.scrollBlockerNode_.style.left = (e.clientX - 5) + 'px';
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013360 } else if (e.type == 'mousemove') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013361 // Oh. This means that drag-scroll was disabled AFTER the mouse down,
13362 // in which case it's too late to engage the scroll-blocker.
13363 this.document_.getSelection().collapseToEnd();
13364 e.preventDefault();
13365 }
13366 }
13367
13368 this.onMouse(e);
13369 }
13370
13371 if (e.type == 'mouseup' && this.document_.getSelection().isCollapsed) {
13372 // Restore this on mouseup in case it was temporarily defeated with a
13373 // alt-mousedown. Only do this when the selection is empty so that
13374 // we don't immediately kill the users selection.
13375 this.defeatMouseReports_ = false;
13376 }
13377};
13378
13379/**
13380 * Clients should override this if they care to know about mouse events.
13381 *
13382 * The event parameter will be a normal DOM mouse click event with additional
13383 * 'terminalRow' and 'terminalColumn' properties.
13384 *
13385 * @param {Event} e The mouse event to handle.
13386 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013387hterm.Terminal.prototype.onMouse = function(e) {};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013388
13389/**
13390 * React when focus changes.
13391 *
13392 * @param {boolean} focused True if focused, false otherwise.
13393 */
13394hterm.Terminal.prototype.onFocusChange_ = function(focused) {
13395 this.cursorNode_.setAttribute('focus', focused);
13396 this.restyleCursor_();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013397 if (focused === true) this.closeBellNotifications_();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013398};
13399
13400/**
13401 * React when the ScrollPort is scrolled.
13402 */
13403hterm.Terminal.prototype.onScroll_ = function() {
13404 this.scheduleSyncCursorPosition_();
13405};
13406
13407/**
13408 * React when text is pasted into the scrollPort.
13409 *
13410 * @param {Event} e The DOM paste event to handle.
13411 */
13412hterm.Terminal.prototype.onPaste_ = function(e) {
13413 var data = e.text.replace(/\n/mg, '\r');
13414 data = this.keyboard.encode(data);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013415 if (this.options_.bracketedPaste) data = '\x1b[200~' + data + '\x1b[201~';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013416
13417 this.io.sendString(data);
13418};
13419
13420/**
13421 * React when the user tries to copy from the scrollPort.
13422 *
13423 * @param {Event} e The DOM copy event.
13424 */
13425hterm.Terminal.prototype.onCopy_ = function(e) {
13426 if (!this.useDefaultWindowCopy) {
13427 e.preventDefault();
13428 setTimeout(this.copySelectionToClipboard.bind(this), 0);
13429 }
13430};
13431
13432/**
13433 * React when the ScrollPort is resized.
13434 *
13435 * Note: This function should not directly contain code that alters the internal
13436 * state of the terminal. That kind of code belongs in realizeWidth or
13437 * realizeHeight, so that it can be executed synchronously in the case of a
13438 * programmatic width change.
13439 */
13440hterm.Terminal.prototype.onResize_ = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013441 var columnCount = Math.floor(
13442 this.scrollPort_.getScreenWidth() /
13443 this.scrollPort_.characterSize.width) ||
13444 0;
13445 var rowCount = lib.f.smartFloorDivide(
13446 this.scrollPort_.getScreenHeight(),
13447 this.scrollPort_.characterSize.height) ||
13448 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013449
13450 if (columnCount <= 0 || rowCount <= 0) {
13451 // We avoid these situations since they happen sometimes when the terminal
13452 // gets removed from the document or during the initial load, and we can't
13453 // deal with that.
13454 // This can also happen if called before the scrollPort calculates the
13455 // character size, meaning we dived by 0 above and default to 0 values.
13456 return;
13457 }
13458
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013459 var isNewSize =
13460 (columnCount != this.screenSize.width ||
13461 rowCount != this.screenSize.height);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013462
13463 // We do this even if the size didn't change, just to be sure everything is
13464 // in sync.
13465 this.realizeSize_(columnCount, rowCount);
13466 this.showZoomWarning_(this.scrollPort_.characterSize.zoomFactor != 1);
13467
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013468 if (isNewSize) this.overlaySize();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013469
13470 this.restyleCursor_();
13471 this.scheduleSyncCursorPosition_();
13472};
13473
13474/**
13475 * Service the cursor blink timeout.
13476 */
13477hterm.Terminal.prototype.onCursorBlink_ = function() {
13478 if (!this.options_.cursorBlink) {
13479 delete this.timeouts_.cursorBlink;
13480 return;
13481 }
13482
13483 if (this.cursorNode_.getAttribute('focus') == 'false' ||
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013484 this.cursorNode_.style.opacity == '0') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013485 this.cursorNode_.style.opacity = '1';
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013486 this.timeouts_.cursorBlink =
13487 setTimeout(this.myOnCursorBlink_, this.cursorBlinkCycle_[0]);
13488 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013489 this.cursorNode_.style.opacity = '0';
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013490 this.timeouts_.cursorBlink =
13491 setTimeout(this.myOnCursorBlink_, this.cursorBlinkCycle_[1]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013492 }
13493};
13494
13495/**
13496 * Set the scrollbar-visible mode bit.
13497 *
13498 * If scrollbar-visible is on, the vertical scrollbar will be visible.
13499 * Otherwise it will not.
13500 *
13501 * Defaults to on.
13502 *
13503 * @param {boolean} state True to set scrollbar-visible mode, false to unset.
13504 */
13505hterm.Terminal.prototype.setScrollbarVisible = function(state) {
13506 this.scrollPort_.setScrollbarVisible(state);
13507};
13508
13509/**
13510 * Set the scroll wheel move multiplier. This will affect how fast the page
13511 * scrolls on mousewheel events.
13512 *
13513 * Defaults to 1.
13514 *
13515 * @param {number} multiplier The multiplier to set.
13516 */
13517hterm.Terminal.prototype.setScrollWheelMoveMultipler = function(multiplier) {
13518 this.scrollPort_.setScrollWheelMoveMultipler(multiplier);
13519};
13520
13521/**
13522 * Close all web notifications created by terminal bells.
13523 */
13524hterm.Terminal.prototype.closeBellNotifications_ = function() {
13525 this.bellNotificationList_.forEach(function(n) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070013526 n.close();
13527 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013528 this.bellNotificationList_.length = 0;
13529};
13530// SOURCE FILE: hterm/js/hterm_terminal_io.js
13531// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
13532// Use of this source code is governed by a BSD-style license that can be
13533// found in the LICENSE file.
13534
13535'use strict';
13536
13537lib.rtdep('lib.encodeUTF8');
13538
13539/**
13540 * Input/Output interface used by commands to communicate with the terminal.
13541 *
13542 * Commands like `nassh` and `crosh` receive an instance of this class as
13543 * part of their argv object. This allows them to write to and read from the
13544 * terminal without exposing them to an entire hterm.Terminal instance.
13545 *
13546 * The active command must override the onVTKeystroke() and sendString() methods
13547 * of this class in order to receive keystrokes and send output to the correct
13548 * destination.
13549 *
13550 * Isolating commands from the terminal provides the following benefits:
13551 * - Provides a mechanism to save and restore onVTKeystroke and sendString
13552 * handlers when invoking subcommands (see the push() and pop() methods).
13553 * - The isolation makes it easier to make changes in Terminal and supporting
13554 * classes without affecting commands.
13555 * - In The Future commands may run in web workers where they would only be able
13556 * to talk to a Terminal instance through an IPC mechanism.
13557 *
13558 * @param {hterm.Terminal}
13559 */
13560hterm.Terminal.IO = function(terminal) {
13561 this.terminal_ = terminal;
13562
13563 // The IO object to restore on IO.pop().
13564 this.previousIO_ = null;
13565};
13566
13567/**
13568 * Show the terminal overlay for a given amount of time.
13569 *
13570 * The terminal overlay appears in inverse video in a large font, centered
13571 * over the terminal. You should probably keep the overlay message brief,
13572 * since it's in a large font and you probably aren't going to check the size
13573 * of the terminal first.
13574 *
13575 * @param {string} msg The text (not HTML) message to display in the overlay.
13576 * @param {number} opt_timeout The amount of time to wait before fading out
13577 * the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
13578 * stay up forever (or until the next overlay).
13579 */
13580hterm.Terminal.IO.prototype.showOverlay = function(message, opt_timeout) {
13581 this.terminal_.showOverlay(message, opt_timeout);
13582};
13583
13584/**
13585 * Open an frame in the current terminal window, pointed to the specified
13586 * url.
13587 *
13588 * Eventually we'll probably need size/position/decoration options.
13589 * The user should also be able to move/resize the frame.
13590 *
13591 * @param {string} url The URL to load in the frame.
13592 * @param {Object} opt_options Optional frame options. Not implemented.
13593 */
13594hterm.Terminal.IO.prototype.createFrame = function(url, opt_options) {
13595 return new hterm.Frame(this.terminal_, url, opt_options);
13596};
13597
13598/**
13599 * Change the preference profile for the terminal.
13600 *
13601 * @param profileName {string} The name of the preference profile to activate.
13602 */
13603hterm.Terminal.IO.prototype.setTerminalProfile = function(profileName) {
13604 this.terminal_.setProfile(profileName);
13605};
13606
13607/**
13608 * Create a new hterm.Terminal.IO instance and make it active on the Terminal
13609 * object associated with this instance.
13610 *
13611 * This is used to pass control of the terminal IO off to a subcommand. The
13612 * IO.pop() method can be used to restore control when the subcommand completes.
13613 */
13614hterm.Terminal.IO.prototype.push = function() {
13615 var io = new hterm.Terminal.IO(this.terminal_);
13616 io.keyboardCaptured_ = this.keyboardCaptured_;
13617
13618 io.columnCount = this.columnCount;
13619 io.rowCount = this.rowCount;
13620
13621 io.previousIO_ = this.terminal_.io;
13622 this.terminal_.io = io;
13623
13624 return io;
13625};
13626
13627/**
13628 * Restore the Terminal's previous IO object.
13629 */
13630hterm.Terminal.IO.prototype.pop = function() {
13631 this.terminal_.io = this.previousIO_;
13632};
13633
13634/**
13635 * Called when data needs to be sent to the current command.
13636 *
13637 * Clients should override this to receive notification of pending data.
13638 *
13639 * @param {string} string The data to send.
13640 */
13641hterm.Terminal.IO.prototype.sendString = function(string) {
13642 // Override this.
13643 console.log('Unhandled sendString: ' + string);
13644};
13645
13646/**
13647 * Called when a terminal keystroke is detected.
13648 *
13649 * Clients should override this to receive notification of keystrokes.
13650 *
13651 * The keystroke data will be encoded according to the 'send-encoding'
13652 * preference.
13653 *
13654 * @param {string} string The VT key sequence.
13655 */
13656hterm.Terminal.IO.prototype.onVTKeystroke = function(string) {
13657 // Override this.
13658 console.log('Unobserverd VT keystroke: ' + JSON.stringify(string));
13659};
13660
13661hterm.Terminal.IO.prototype.onTerminalResize_ = function(width, height) {
13662 var obj = this;
13663 while (obj) {
13664 obj.columnCount = width;
13665 obj.rowCount = height;
13666 obj = obj.previousIO_;
13667 }
13668
13669 this.onTerminalResize(width, height);
13670};
13671
13672/**
13673 * Called when terminal size is changed.
13674 *
13675 * Clients should override this to receive notification of resize.
13676 *
13677 * @param {string|integer} terminal width.
13678 * @param {string|integer} terminal height.
13679 */
13680hterm.Terminal.IO.prototype.onTerminalResize = function(width, height) {
13681 // Override this.
13682};
13683
13684/**
13685 * Write a UTF-8 encoded byte string to the terminal.
13686 *
13687 * @param {string} string The UTF-8 encoded string to print.
13688 */
13689hterm.Terminal.IO.prototype.writeUTF8 = function(string) {
13690 if (this.terminal_.io != this)
13691 throw 'Attempt to print from inactive IO object.';
13692
13693 this.terminal_.interpret(string);
13694};
13695
13696/**
13697 * Write a UTF-8 encoded byte string to the terminal followed by crlf.
13698 *
13699 * @param {string} string The UTF-8 encoded string to print.
13700 */
13701hterm.Terminal.IO.prototype.writelnUTF8 = function(string) {
13702 if (this.terminal_.io != this)
13703 throw 'Attempt to print from inactive IO object.';
13704
13705 this.terminal_.interpret(string + '\r\n');
13706};
13707
13708/**
13709 * Write a UTF-16 JavaScript string to the terminal.
13710 *
13711 * @param {string} string The string to print.
13712 */
13713hterm.Terminal.IO.prototype.print =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013714 hterm.Terminal.IO.prototype.writeUTF16 = function(string) {
13715 this.writeUTF8(lib.encodeUTF8(string));
13716 };
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013717
13718/**
13719 * Print a UTF-16 JavaScript string to the terminal followed by a newline.
13720 *
13721 * @param {string} string The string to print.
13722 */
13723hterm.Terminal.IO.prototype.println =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013724 hterm.Terminal.IO.prototype.writelnUTF16 = function(string) {
13725 this.writelnUTF8(lib.encodeUTF8(string));
13726 };
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013727// SOURCE FILE: hterm/js/hterm_text_attributes.js
13728// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
13729// Use of this source code is governed by a BSD-style license that can be
13730// found in the LICENSE file.
13731
13732'use strict';
13733
13734lib.rtdep('lib.colors');
13735
13736/**
13737 * Constructor for TextAttribute objects.
13738 *
13739 * These objects manage a set of text attributes such as foreground/
13740 * background color, bold, faint, italic, blink, underline, and strikethrough.
13741 *
13742 * TextAttribute instances can be used to construct a DOM container implementing
13743 * the current attributes, or to test an existing DOM container for
13744 * compatibility with the current attributes.
13745 *
13746 * @constructor
13747 * @param {HTMLDocument} document The parent document to use when creating
13748 * new DOM containers.
13749 */
13750hterm.TextAttributes = function(document) {
13751 this.document_ = document;
13752 // These variables contain the source of the color as either:
13753 // SRC_DEFAULT (use context default)
13754 // SRC_RGB (specified in 'rgb( r, g, b)' form)
13755 // number (representing the index from color palette to use)
13756 this.foregroundSource = this.SRC_DEFAULT;
13757 this.backgroundSource = this.SRC_DEFAULT;
13758
13759 // These properties cache the value in the color table, but foregroundSource
13760 // and backgroundSource contain the canonical values.
13761 this.foreground = this.DEFAULT_COLOR;
13762 this.background = this.DEFAULT_COLOR;
13763
13764 this.defaultForeground = 'rgb(255, 255, 255)';
13765 this.defaultBackground = 'rgb(0, 0, 0)';
13766
13767 this.bold = false;
13768 this.faint = false;
13769 this.italic = false;
13770 this.blink = false;
13771 this.underline = false;
13772 this.strikethrough = false;
13773 this.inverse = false;
13774 this.invisible = false;
13775 this.wcNode = false;
13776 this.tileData = null;
13777
13778 this.colorPalette = null;
13779 this.resetColorPalette();
13780};
13781
13782/**
13783 * If false, we ignore the bold attribute.
13784 *
13785 * This is used for fonts that have a bold version that is a different size
13786 * than the normal weight version.
13787 */
13788hterm.TextAttributes.prototype.enableBold = true;
13789
13790/**
13791 * If true, use bright colors (if available) for bold text.
13792 *
13793 * This setting is independent of the enableBold setting.
13794 */
13795hterm.TextAttributes.prototype.enableBoldAsBright = true;
13796
13797/**
13798 * A sentinel constant meaning "whatever the default color is in this context".
13799 */
13800hterm.TextAttributes.prototype.DEFAULT_COLOR = new String('');
13801
13802/**
13803 * A constant string used to specify that source color is context default.
13804 */
13805hterm.TextAttributes.prototype.SRC_DEFAULT = 'default';
13806
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013807/**
13808 * A constant string used to specify that the source of a color is a valid
13809 * rgb( r, g, b) specifier.
13810 */
13811hterm.TextAttributes.prototype.SRC_RGB = 'rgb';
13812
13813/**
13814 * The document object which should own the DOM nodes created by this instance.
13815 *
13816 * @param {HTMLDocument} document The parent document.
13817 */
13818hterm.TextAttributes.prototype.setDocument = function(document) {
13819 this.document_ = document;
13820};
13821
13822/**
13823 * Create a deep copy of this object.
13824 *
13825 * @return {hterm.TextAttributes} A deep copy of this object.
13826 */
13827hterm.TextAttributes.prototype.clone = function() {
13828 var rv = new hterm.TextAttributes(null);
13829
13830 for (var key in this) {
13831 rv[key] = this[key];
13832 }
13833
13834 rv.colorPalette = this.colorPalette.concat();
13835 return rv;
13836};
13837
13838/**
13839 * Reset the current set of attributes.
13840 *
13841 * This does not affect the palette. Use resetColorPalette() for that.
13842 * It also doesn't affect the tile data, it's not meant to.
13843 */
13844hterm.TextAttributes.prototype.reset = function() {
13845 this.foregroundSource = this.SRC_DEFAULT;
13846 this.backgroundSource = this.SRC_DEFAULT;
13847 this.foreground = this.DEFAULT_COLOR;
13848 this.background = this.DEFAULT_COLOR;
13849 this.bold = false;
13850 this.faint = false;
13851 this.italic = false;
13852 this.blink = false;
13853 this.underline = false;
13854 this.strikethrough = false;
13855 this.inverse = false;
13856 this.invisible = false;
13857 this.wcNode = false;
13858};
13859
13860/**
13861 * Reset the color palette to the default state.
13862 */
13863hterm.TextAttributes.prototype.resetColorPalette = function() {
13864 this.colorPalette = lib.colors.colorPalette.concat();
13865 this.syncColors();
13866};
13867
13868/**
13869 * Test if the current attributes describe unstyled text.
13870 *
13871 * @return {boolean} True if the current attributes describe unstyled text.
13872 */
13873hterm.TextAttributes.prototype.isDefault = function() {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013874 return (
13875 this.foregroundSource == this.SRC_DEFAULT &&
13876 this.backgroundSource == this.SRC_DEFAULT && !this.bold && !this.faint &&
13877 !this.italic && !this.blink && !this.underline && !this.strikethrough &&
13878 !this.inverse && !this.invisible && !this.wcNode &&
13879 this.tileData == null);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013880};
13881
13882/**
13883 * Create a DOM container (a span or a text node) with a style to match the
13884 * current set of attributes.
13885 *
13886 * This method will create a plain text node if the text is unstyled, or
13887 * an HTML span if the text is styled. Due to lack of monospace wide character
13888 * fonts on certain systems (e.g. Chrome OS), we need to put each wide character
13889 * in a span of CSS class '.wc-node' which has double column width.
13890 * Each vt_tiledata tile is also represented by a span with a single
13891 * character, with CSS classes '.tile' and '.tile_<glyph number>'.
13892 *
13893 * @param {string} opt_textContent Optional text content for the new container.
13894 * @return {HTMLNode} An HTML span or text nodes styled to match the current
13895 * attributes.
13896 */
13897hterm.TextAttributes.prototype.createContainer = function(opt_textContent) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013898 if (this.isDefault()) return this.document_.createTextNode(opt_textContent);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013899
13900 var span = this.document_.createElement('span');
13901 var style = span.style;
13902 var classes = [];
13903
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013904 if (this.foreground != this.DEFAULT_COLOR) style.color = this.foreground;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013905
13906 if (this.background != this.DEFAULT_COLOR)
13907 style.backgroundColor = this.background;
13908
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013909 if (this.enableBold && this.bold) style.fontWeight = 'bold';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013910
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013911 if (this.faint) span.faint = true;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013912
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013913 if (this.italic) style.fontStyle = 'italic';
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013914
13915 if (this.blink) {
13916 classes.push('blink-node');
13917 span.blinkNode = true;
13918 }
13919
13920 var textDecoration = '';
13921 if (this.underline) {
13922 textDecoration += ' underline';
13923 span.underline = true;
13924 }
13925 if (this.strikethrough) {
13926 textDecoration += ' line-through';
13927 span.strikethrough = true;
13928 }
13929 if (textDecoration) {
13930 style.textDecoration = textDecoration;
13931 }
13932
13933 if (this.wcNode) {
13934 classes.push('wc-node');
13935 span.wcNode = true;
13936 }
13937
13938 if (this.tileData != null) {
13939 classes.push('tile');
13940 classes.push('tile_' + this.tileData);
13941 span.tileNode = true;
13942 }
13943
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013944 if (opt_textContent) span.textContent = opt_textContent;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013945
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013946 if (classes.length) span.className = classes.join(' ');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013947
13948 return span;
13949};
13950
13951/**
13952 * Tests if the provided object (string, span or text node) has the same
13953 * style as this TextAttributes instance.
13954 *
13955 * This indicates that text with these attributes could be inserted directly
13956 * into the target DOM node.
13957 *
13958 * For the purposes of this method, a string is considered a text node.
13959 *
13960 * @param {string|HTMLNode} obj The object to test.
13961 * @return {boolean} True if the provided container has the same style as
13962 * this attributes instance.
13963 */
13964hterm.TextAttributes.prototype.matchesContainer = function(obj) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013965 if (typeof obj == 'string' || obj.nodeType == 3) return this.isDefault();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013966
13967 var style = obj.style;
13968
13969 // We don't want to put multiple characters in a wcNode or a tile.
13970 // See the comments in createContainer.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070013971 return (
13972 !(this.wcNode || obj.wcNode) &&
13973 !(this.tileData != null || obj.tileNode) &&
13974 this.foreground == style.color &&
13975 this.background == style.backgroundColor &&
13976 (this.enableBold && this.bold) == !!style.fontWeight &&
13977 this.blink == obj.blinkNode && this.italic == !!style.fontStyle &&
13978 !!this.underline == !!obj.underline &&
13979 !!this.strikethrough == !!obj.strikethrough);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050013980};
13981
13982hterm.TextAttributes.prototype.setDefaults = function(foreground, background) {
13983 this.defaultForeground = foreground;
13984 this.defaultBackground = background;
13985
13986 this.syncColors();
13987};
13988
13989/**
13990 * Updates foreground and background properties based on current indices and
13991 * other state.
13992 *
13993 * @param {string} terminalForeground The terminal foreground color for use as
13994 * inverse text background.
13995 * @param {string} terminalBackground The terminal background color for use as
13996 * inverse text foreground.
13997 *
13998 */
13999hterm.TextAttributes.prototype.syncColors = function() {
14000 function getBrightIndex(i) {
14001 if (i < 8) {
14002 // If the color is from the lower half of the ANSI 16, add 8.
14003 return i + 8;
14004 }
14005
14006 // If it's not from the 16 color palette, ignore bold requests. This
14007 // matches the behavior of gnome-terminal.
14008 return i;
14009 }
14010
14011 var foregroundSource = this.foregroundSource;
14012 var backgroundSource = this.backgroundSource;
14013 var defaultForeground = this.DEFAULT_COLOR;
14014 var defaultBackground = this.DEFAULT_COLOR;
14015
14016 if (this.inverse) {
14017 foregroundSource = this.backgroundSource;
14018 backgroundSource = this.foregroundSource;
14019 // We can't inherit the container's color anymore.
14020 defaultForeground = this.defaultBackground;
14021 defaultBackground = this.defaultForeground;
14022 }
14023
14024 if (this.enableBoldAsBright && this.bold) {
14025 if (foregroundSource != this.SRC_DEFAULT &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014026 foregroundSource != this.SRC_RGB) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014027 foregroundSource = getBrightIndex(foregroundSource);
14028 }
14029 }
14030
14031 if (this.invisible) {
14032 foregroundSource = backgroundSource;
14033 defaultForeground = this.defaultBackground;
14034 }
14035
14036 // Set fore/background colors unless already specified in rgb(r, g, b) form.
14037 if (foregroundSource != this.SRC_RGB) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014038 this.foreground =
14039 ((foregroundSource == this.SRC_DEFAULT) ?
14040 defaultForeground :
14041 this.colorPalette[foregroundSource]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014042 }
14043
14044 if (this.faint && !this.invisible) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014045 var colorToMakeFaint =
14046 ((this.foreground == this.DEFAULT_COLOR) ? this.defaultForeground :
14047 this.foreground);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014048 this.foreground = lib.colors.mix(colorToMakeFaint, 'rgb(0, 0, 0)', 0.3333);
14049 }
14050
14051 if (backgroundSource != this.SRC_RGB) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014052 this.background =
14053 ((backgroundSource == this.SRC_DEFAULT) ?
14054 defaultBackground :
14055 this.colorPalette[backgroundSource]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014056 }
14057};
14058
14059/**
14060 * Static method used to test if the provided objects (strings, spans or
14061 * text nodes) have the same style.
14062 *
14063 * For the purposes of this method, a string is considered a text node.
14064 *
14065 * @param {string|HTMLNode} obj1 An object to test.
14066 * @param {string|HTMLNode} obj2 Another object to test.
14067 * @return {boolean} True if the containers have the same style.
14068 */
14069hterm.TextAttributes.containersMatch = function(obj1, obj2) {
14070 if (typeof obj1 == 'string')
14071 return hterm.TextAttributes.containerIsDefault(obj2);
14072
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014073 if (obj1.nodeType != obj2.nodeType) return false;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014074
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014075 if (obj1.nodeType == 3) return true;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014076
14077 var style1 = obj1.style;
14078 var style2 = obj2.style;
14079
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014080 return (
14081 style1.color == style2.color &&
14082 style1.backgroundColor == style2.backgroundColor &&
14083 style1.fontWeight == style2.fontWeight &&
14084 style1.fontStyle == style2.fontStyle &&
14085 style1.textDecoration == style2.textDecoration);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014086};
14087
14088/**
14089 * Static method to test if a given DOM container represents unstyled text.
14090 *
14091 * For the purposes of this method, a string is considered a text node.
14092 *
14093 * @param {string|HTMLNode} obj1 An object to test.
14094 * @return {boolean} True if the object is unstyled.
14095 */
14096hterm.TextAttributes.containerIsDefault = function(obj) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070014097 return typeof obj == 'string' || obj.nodeType == 3;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014098};
14099
14100/**
14101 * Static method to get the column width of a node's textContent.
14102 *
14103 * @param {HTMLElement} node The HTML element to get the width of textContent
14104 * from.
14105 * @return {integer} The column width of the node's textContent.
14106 */
14107hterm.TextAttributes.nodeWidth = function(node) {
14108 if (node.wcNode) {
14109 return lib.wc.strWidth(node.textContent);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014110 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014111 return node.textContent.length;
14112 }
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070014113};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014114
14115/**
14116 * Static method to get the substr of a node's textContent. The start index
14117 * and substr width are computed in column width.
14118 *
14119 * @param {HTMLElement} node The HTML element to get the substr of textContent
14120 * from.
14121 * @param {integer} start The starting offset in column width.
14122 * @param {integer} width The width to capture in column width.
14123 * @return {integer} The extracted substr of the node's textContent.
14124 */
14125hterm.TextAttributes.nodeSubstr = function(node, start, width) {
14126 if (node.wcNode) {
14127 return lib.wc.substr(node.textContent, start, width);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014128 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014129 return node.textContent.substr(start, width);
14130 }
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070014131};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014132
14133/**
14134 * Static method to get the substring based of a node's textContent. The
14135 * start index of end index are computed in column width.
14136 *
14137 * @param {HTMLElement} node The HTML element to get the substr of textContent
14138 * from.
14139 * @param {integer} start The starting offset in column width.
14140 * @param {integer} end The ending offset in column width.
14141 * @return {integer} The extracted substring of the node's textContent.
14142 */
14143hterm.TextAttributes.nodeSubstring = function(node, start, end) {
14144 if (node.wcNode) {
14145 return lib.wc.substring(node.textContent, start, end);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014146 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014147 return node.textContent.substring(start, end);
14148 }
14149};
14150
14151/**
14152 * Static method to split a string into contiguous runs of single-width
14153 * characters and runs of double-width characters.
14154 *
14155 * @param {string} str The string to split.
14156 * @return {Array} An array of objects that contain substrings of str, where
14157 * each substring is either a contiguous runs of single-width characters
14158 * or a double-width character. For object that contains a double-width
14159 * character, its wcNode property is set to true.
14160 */
14161hterm.TextAttributes.splitWidecharString = function(str) {
14162 var rv = [];
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014163 var base = 0, length = 0;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014164
14165 for (var i = 0; i < str.length;) {
14166 var c = str.codePointAt(i);
14167 var increment = (c <= 0xffff) ? 1 : 2;
14168 if (c < 128 || lib.wc.charWidth(c) == 1) {
14169 length += increment;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014170 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014171 if (length) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014172 rv.push({str: str.substr(base, length)});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014173 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014174 rv.push({str: str.substr(i, increment), wcNode: true});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014175 base = i + increment;
14176 length = 0;
14177 }
14178 i += increment;
14179 }
14180
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014181 if (length) rv.push({str: str.substr(base, length)});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014182
14183 return rv;
14184};
14185// SOURCE FILE: hterm/js/hterm_vt.js
14186// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
14187// Use of this source code is governed by a BSD-style license that can be
14188// found in the LICENSE file.
14189
14190'use strict';
14191
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014192lib.rtdep('lib.colors', 'lib.f', 'lib.UTF8Decoder', 'hterm.VT.CharacterMap');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014193
14194/**
14195 * Constructor for the VT escape sequence interpreter.
14196 *
14197 * The interpreter operates on a terminal object capable of performing cursor
14198 * move operations, painting characters, etc.
14199 *
14200 * This interpreter is intended to be compatible with xterm, though it
14201 * ignores some of the more esoteric escape sequences.
14202 *
14203 * Some sequences are marked "Will not implement", meaning that they aren't
14204 * considered relevant to hterm and will probably never be implemented.
14205 *
14206 * Others are marked "Not currently implemented", meaning that they are lower
14207 * priority items that may be useful to implement at some point.
14208 *
14209 * See also:
14210 * [VT100] VT100 User Guide
14211 * http://vt100.net/docs/vt100-ug/chapter3.html
14212 * [VT510] VT510 Video Terminal Programmer Information
14213 * http://vt100.net/docs/vt510-rm/contents
14214 * [XTERM] Xterm Control Sequences
14215 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
14216 * [CTRL] Wikipedia: C0 and C1 Control Codes
14217 * https://en.wikipedia.org/wiki/C0_and_C1_control_codes
14218 * [CSI] Wikipedia: ANSI Escape Code
14219 * https://en.wikipedia.org/wiki/Control_Sequence_Introducer
14220 * man 5 terminfo, man infocmp, infocmp -L xterm-new
14221 *
14222 * @param {hterm.Terminal} terminal Terminal to use with the interpreter.
14223 */
14224hterm.VT = function(terminal) {
14225 /**
14226 * The display terminal object associated with this virtual terminal.
14227 */
14228 this.terminal = terminal;
14229
14230 terminal.onMouse = this.onTerminalMouse_.bind(this);
14231 this.mouseReport = this.MOUSE_REPORT_DISABLED;
14232
14233 // Parse state left over from the last parse. You should use the parseState
14234 // instance passed into your parse routine, rather than reading
14235 // this.parseState_ directly.
14236 this.parseState_ = new hterm.VT.ParseState(this.parseUnknown_);
14237
14238 // Any "leading modifiers" for the escape sequence, such as '?', ' ', or the
14239 // other modifiers handled in this.parseCSI_.
14240 this.leadingModifier_ = '';
14241
14242 // Any "trailing modifiers". Same character set as a leading modifier,
14243 // except these are found after the numeric arguments.
14244 this.trailingModifier_ = '';
14245
14246 // Whether or not to respect the escape codes for setting terminal width.
14247 this.allowColumnWidthChanges_ = false;
14248
14249 // The amount of time we're willing to wait for the end of an OSC sequence.
14250 this.oscTimeLimit_ = 20000;
14251
14252 // Construct a regular expression to match the known one-byte control chars.
14253 // This is used in parseUnknown_ to quickly scan a string for the next
14254 // control character.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014255 var cc1 = Object.keys(hterm.VT.CC1)
14256 .map(function(e) {
14257 return '\\x' + lib.f.zpad(e.charCodeAt().toString(16), 2);
14258 })
14259 .join('');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014260 this.cc1Pattern_ = new RegExp('[' + cc1 + ']');
14261
14262 // Decoder to maintain UTF-8 decode state.
14263 this.utf8Decoder_ = new lib.UTF8Decoder();
14264
14265 /**
14266 * Whether to accept the 8-bit control characters.
14267 *
14268 * An 8-bit control character is one with the eighth bit set. These
14269 * didn't work on 7-bit terminals so they all have two byte equivalents.
14270 * Most hosts still only use the two-byte versions.
14271 *
14272 * We ignore 8-bit control codes by default. This is in order to avoid
14273 * issues with "accidental" usage of codes that need to be terminated.
14274 * The "accident" usually involves cat'ing binary data.
14275 */
14276 this.enable8BitControl = false;
14277
14278 /**
14279 * Whether to allow the OSC 52 sequence to write to the system clipboard.
14280 */
14281 this.enableClipboardWrite = true;
14282
14283 /**
14284 * Respect the host's attempt to change the cursor blink status using
14285 * the DEC Private mode 12.
14286 */
14287 this.enableDec12 = false;
14288
14289 /**
14290 * The expected encoding method for data received from the host.
14291 */
14292 this.characterEncoding = 'utf-8';
14293
14294 /**
14295 * Max length of an unterminated DCS, OSC, PM or APC sequence before we give
14296 * up and ignore the code.
14297 *
14298 * These all end with a String Terminator (ST, '\x9c', ESC '\\') or
14299 * (BEL, '\x07') character, hence the "string sequence" moniker.
14300 */
14301 this.maxStringSequence = 1024;
14302
14303 /**
14304 * If true, emit warnings when we encounter a control character or escape
14305 * sequence that we don't recognize or explicitly ignore.
14306 */
14307 this.warnUnimplemented = true;
14308
14309 /**
14310 * The default G0...G3 character maps.
14311 */
14312 this.G0 = hterm.VT.CharacterMap.maps['B'];
14313 this.G1 = hterm.VT.CharacterMap.maps['0'];
14314 this.G2 = hterm.VT.CharacterMap.maps['B'];
14315 this.G3 = hterm.VT.CharacterMap.maps['B'];
14316
14317 /**
14318 * The 7-bit visible character set.
14319 *
14320 * This is a mapping from inbound data to display glyph. The GL set
14321 * contains the 94 bytes from 0x21 to 0x7e.
14322 *
14323 * The default GL set is 'B', US ASCII.
14324 */
14325 this.GL = 'G0';
14326
14327 /**
14328 * The 8-bit visible character set.
14329 *
14330 * This is a mapping from inbound data to display glyph. The GR set
14331 * contains the 94 bytes from 0xa1 to 0xfe.
14332 */
14333 this.GR = 'G0';
14334
14335 // Saved state used in DECSC.
14336 //
14337 // This is a place to store a copy VT state, it is *not* the active state.
14338 this.savedState_ = new hterm.VT.CursorState(this);
14339};
14340
14341/**
14342 * No mouse events.
14343 */
14344hterm.VT.prototype.MOUSE_REPORT_DISABLED = 0;
14345
14346/**
14347 * DECSET mode 1000.
14348 *
14349 * Report mouse down/up events only.
14350 */
14351hterm.VT.prototype.MOUSE_REPORT_CLICK = 1;
14352
14353/**
14354 * DECSET mode 1002.
14355 *
14356 * Report mouse down/up and movement while a button is down.
14357 */
14358hterm.VT.prototype.MOUSE_REPORT_DRAG = 3;
14359
14360/**
14361 * ParseState constructor.
14362 *
14363 * This object tracks the current state of the parse. It has fields for the
14364 * current buffer, position in the buffer, and the parse function.
14365 *
14366 * @param {function} defaultFunc The default parser function.
14367 * @param {string} opt_buf Optional string to use as the current buffer.
14368 */
14369hterm.VT.ParseState = function(defaultFunction, opt_buf) {
14370 this.defaultFunction = defaultFunction;
14371 this.buf = opt_buf || null;
14372 this.pos = 0;
14373 this.func = defaultFunction;
14374 this.args = [];
14375};
14376
14377/**
14378 * Reset the parser function, buffer, and position.
14379 */
14380hterm.VT.ParseState.prototype.reset = function(opt_buf) {
14381 this.resetParseFunction();
14382 this.resetBuf(opt_buf || '');
14383 this.resetArguments();
14384};
14385
14386/**
14387 * Reset the parser function only.
14388 */
14389hterm.VT.ParseState.prototype.resetParseFunction = function() {
14390 this.func = this.defaultFunction;
14391};
14392
14393/**
14394 * Reset the buffer and position only.
14395 *
14396 * @param {string} buf Optional new value for buf, defaults to null.
14397 */
14398hterm.VT.ParseState.prototype.resetBuf = function(opt_buf) {
14399 this.buf = (typeof opt_buf == 'string') ? opt_buf : null;
14400 this.pos = 0;
14401};
14402
14403/**
14404 * Reset the arguments list only.
14405 *
14406 * @param {string} opt_arg_zero Optional initial value for args[0].
14407 */
14408hterm.VT.ParseState.prototype.resetArguments = function(opt_arg_zero) {
14409 this.args.length = 0;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014410 if (typeof opt_arg_zero != 'undefined') this.args[0] = opt_arg_zero;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014411};
14412
14413/**
14414 * Get an argument as an integer.
14415 *
14416 * @param {number} argnum The argument number to retrieve.
14417 */
14418hterm.VT.ParseState.prototype.iarg = function(argnum, defaultValue) {
14419 var str = this.args[argnum];
14420 if (str) {
14421 var ret = parseInt(str, 10);
14422 // An argument of zero is treated as the default value.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014423 if (ret == 0) ret = defaultValue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014424 return ret;
14425 }
14426 return defaultValue;
14427};
14428
14429/**
14430 * Advance the parse position.
14431 *
14432 * @param {integer} count The number of bytes to advance.
14433 */
14434hterm.VT.ParseState.prototype.advance = function(count) {
14435 this.pos += count;
14436};
14437
14438/**
14439 * Return the remaining portion of the buffer without affecting the parse
14440 * position.
14441 *
14442 * @return {string} The remaining portion of the buffer.
14443 */
14444hterm.VT.ParseState.prototype.peekRemainingBuf = function() {
14445 return this.buf.substr(this.pos);
14446};
14447
14448/**
14449 * Return the next single character in the buffer without affecting the parse
14450 * position.
14451 *
14452 * @return {string} The next character in the buffer.
14453 */
14454hterm.VT.ParseState.prototype.peekChar = function() {
14455 return this.buf.substr(this.pos, 1);
14456};
14457
14458/**
14459 * Return the next single character in the buffer and advance the parse
14460 * position one byte.
14461 *
14462 * @return {string} The next character in the buffer.
14463 */
14464hterm.VT.ParseState.prototype.consumeChar = function() {
14465 return this.buf.substr(this.pos++, 1);
14466};
14467
14468/**
14469 * Return true if the buffer is empty, or the position is past the end.
14470 */
14471hterm.VT.ParseState.prototype.isComplete = function() {
14472 return this.buf == null || this.buf.length <= this.pos;
14473};
14474
14475hterm.VT.CursorState = function(vt) {
14476 this.vt_ = vt;
14477 this.save();
14478};
14479
14480hterm.VT.CursorState.prototype.save = function() {
14481 this.cursor = this.vt_.terminal.saveCursor();
14482
14483 this.textAttributes = this.vt_.terminal.getTextAttributes().clone();
14484
14485 this.GL = this.vt_.GL;
14486 this.GR = this.vt_.GR;
14487
14488 this.G0 = this.vt_.G0;
14489 this.G1 = this.vt_.G1;
14490 this.G2 = this.vt_.G2;
14491 this.G3 = this.vt_.G3;
14492};
14493
14494hterm.VT.CursorState.prototype.restore = function() {
14495 this.vt_.terminal.restoreCursor(this.cursor);
14496
14497 this.vt_.terminal.setTextAttributes(this.textAttributes.clone());
14498
14499 this.vt_.GL = this.GL;
14500 this.vt_.GR = this.GR;
14501
14502 this.vt_.G0 = this.G0;
14503 this.vt_.G1 = this.G1;
14504 this.vt_.G2 = this.G2;
14505 this.vt_.G3 = this.G3;
14506};
14507
14508hterm.VT.prototype.reset = function() {
14509 this.G0 = hterm.VT.CharacterMap.maps['B'];
14510 this.G1 = hterm.VT.CharacterMap.maps['0'];
14511 this.G2 = hterm.VT.CharacterMap.maps['B'];
14512 this.G3 = hterm.VT.CharacterMap.maps['B'];
14513
14514 this.GL = 'G0';
14515 this.GR = 'G0';
14516
14517 this.savedState_ = new hterm.VT.CursorState(this);
14518
14519 this.mouseReport = this.MOUSE_REPORT_DISABLED;
14520};
14521
14522/**
14523 * Handle terminal mouse events.
14524 *
14525 * See the "Mouse Tracking" section of [xterm].
14526 */
14527hterm.VT.prototype.onTerminalMouse_ = function(e) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014528 if (this.mouseReport == this.MOUSE_REPORT_DISABLED) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014529
14530 // Temporary storage for our response.
14531 var response;
14532
14533 // Modifier key state.
14534 var mod = 0;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014535 if (e.shiftKey) mod |= 4;
14536 if (e.metaKey || (this.terminal.keyboard.altIsMeta && e.altKey)) mod |= 8;
14537 if (e.ctrlKey) mod |= 16;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014538
14539 // TODO(rginda): We should also support mode 1005 and/or 1006 to extend the
14540 // coordinate space. Though, after poking around just a little, I wasn't
14541 // able to get vi or emacs to use either of these modes.
14542 var x = String.fromCharCode(lib.f.clamp(e.terminalColumn + 32, 32, 255));
14543 var y = String.fromCharCode(lib.f.clamp(e.terminalRow + 32, 32, 255));
14544
14545 switch (e.type) {
14546 case 'mousewheel':
14547 // Mouse wheel is treated as button 1 or 2 plus an additional 64.
14548 b = ((e.wheelDeltaY > 0) ? 0 : 1) + 96;
14549 b |= mod;
14550 response = '\x1b[M' + String.fromCharCode(b) + x + y;
14551
14552 // Keep the terminal from scrolling.
14553 e.preventDefault();
14554 break;
14555
14556 case 'mousedown':
14557 // Buttons are encoded as button number plus 32.
14558 var b = Math.min(e.which - 1, 2) + 32;
14559
14560 // And mix in the modifier keys.
14561 b |= mod;
14562
14563 response = '\x1b[M' + String.fromCharCode(b) + x + y;
14564 break;
14565
14566 case 'mouseup':
14567 // Mouse up has no indication of which button was released.
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070014568 response = '\x1b[M#' + x + y;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014569 break;
14570
14571 case 'mousemove':
14572 if (this.mouseReport == this.MOUSE_REPORT_DRAG && e.which) {
14573 // Standard button bits.
14574 b = 32 + Math.min(e.which - 1, 2);
14575
14576 // Add 32 to indicate mouse motion.
14577 b += 32;
14578
14579 // And mix in the modifier keys.
14580 b |= mod;
14581
14582 response = '\x1b[M' + String.fromCharCode(b) + x + y;
14583 }
14584
14585 break;
14586
14587 case 'click':
14588 case 'dblclick':
14589 break;
14590
14591 default:
14592 console.error('Unknown mouse event: ' + e.type, e);
14593 break;
14594 }
14595
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014596 if (response) this.terminal.io.sendString(response);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014597};
14598
14599/**
14600 * Interpret a string of characters, displaying the results on the associated
14601 * terminal object.
14602 *
14603 * The buffer will be decoded according to the 'receive-encoding' preference.
14604 */
14605hterm.VT.prototype.interpret = function(buf) {
14606 this.parseState_.resetBuf(this.decode(buf));
14607
14608 while (!this.parseState_.isComplete()) {
14609 var func = this.parseState_.func;
14610 var pos = this.parseState_.pos;
14611 var buf = this.parseState_.buf;
14612
14613 this.parseState_.func.call(this, this.parseState_);
14614
14615 if (this.parseState_.func == func && this.parseState_.pos == pos &&
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014616 this.parseState_.buf == buf) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014617 throw 'Parser did not alter the state!';
14618 }
14619 }
14620};
14621
14622/**
14623 * Decode a string according to the 'receive-encoding' preference.
14624 */
14625hterm.VT.prototype.decode = function(str) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014626 if (this.characterEncoding == 'utf-8') return this.decodeUTF8(str);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014627
14628 return str;
14629};
14630
14631/**
14632 * Encode a UTF-16 string as UTF-8.
14633 *
14634 * See also: https://en.wikipedia.org/wiki/UTF-16
14635 */
14636hterm.VT.prototype.encodeUTF8 = function(str) {
14637 return lib.encodeUTF8(str);
14638};
14639
14640/**
14641 * Decode a UTF-8 string into UTF-16.
14642 */
14643hterm.VT.prototype.decodeUTF8 = function(str) {
14644 return this.utf8Decoder_.decode(str);
14645};
14646
14647/**
14648 * The default parse function.
14649 *
14650 * This will scan the string for the first 1-byte control character (C0/C1
14651 * characters from [CTRL]). Any plain text coming before the code will be
14652 * printed to the terminal, then the control character will be dispatched.
14653 */
14654hterm.VT.prototype.parseUnknown_ = function(parseState) {
14655 var self = this;
14656
14657 function print(str) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014658 if (self[self.GL].GL) str = self[self.GL].GL(str);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014659
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014660 if (self[self.GR].GR) str = self[self.GR].GR(str);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014661
14662 self.terminal.print(str);
14663 };
14664
14665 // Search for the next contiguous block of plain text.
14666 var buf = parseState.peekRemainingBuf();
14667 var nextControl = buf.search(this.cc1Pattern_);
14668
14669 if (nextControl == 0) {
14670 // We've stumbled right into a control character.
14671 this.dispatch('CC1', buf.substr(0, 1), parseState);
14672 parseState.advance(1);
14673 return;
14674 }
14675
14676 if (nextControl == -1) {
14677 // There are no control characters in this string.
14678 print(buf);
14679 parseState.reset();
14680 return;
14681 }
14682
14683 print(buf.substr(0, nextControl));
14684 this.dispatch('CC1', buf.substr(nextControl, 1), parseState);
14685 parseState.advance(nextControl + 1);
14686};
14687
14688/**
14689 * Parse a Control Sequence Introducer code and dispatch it.
14690 *
14691 * See [CSI] for some useful information about these codes.
14692 */
14693hterm.VT.prototype.parseCSI_ = function(parseState) {
14694 var ch = parseState.peekChar();
14695 var args = parseState.args;
14696
14697 if (ch >= '@' && ch <= '~') {
14698 // This is the final character.
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014699 this.dispatch(
14700 'CSI', this.leadingModifier_ + this.trailingModifier_ + ch, parseState);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014701 parseState.resetParseFunction();
14702
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014703 } else if (ch == ';') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014704 // Parameter delimiter.
14705 if (this.trailingModifier_) {
14706 // Parameter delimiter after the trailing modifier. That's a paddlin'.
14707 parseState.resetParseFunction();
14708
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014709 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014710 if (!args.length) {
14711 // They omitted the first param, we need to supply it.
14712 args.push('');
14713 }
14714
14715 args.push('');
14716 }
14717
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014718 } else if (ch >= '0' && ch <= '9') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014719 // Next byte in the current parameter.
14720
14721 if (this.trailingModifier_) {
14722 // Numeric parameter after the trailing modifier. That's a paddlin'.
14723 parseState.resetParseFunction();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014724 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014725 if (!args.length) {
14726 args[0] = ch;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014727 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014728 args[args.length - 1] += ch;
14729 }
14730 }
14731
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014732 } else if (ch >= ' ' && ch <= '?' && ch != ':') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014733 // Modifier character.
14734 if (!args.length) {
14735 this.leadingModifier_ += ch;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014736 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014737 this.trailingModifier_ += ch;
14738 }
14739
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014740 } else if (this.cc1Pattern_.test(ch)) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014741 // Control character.
14742 this.dispatch('CC1', ch, parseState);
14743
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014744 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014745 // Unexpected character in sequence, bail out.
14746 parseState.resetParseFunction();
14747 }
14748
14749 parseState.advance(1);
14750};
14751
14752/**
14753 * Skip over the string until the next String Terminator (ST, 'ESC \') or
14754 * Bell (BEL, '\x07').
14755 *
14756 * The string is accumulated in parseState.args[0]. Make sure to reset the
14757 * arguments (with parseState.resetArguments) before starting the parse.
14758 *
14759 * You can detect that parsing in complete by checking that the parse
14760 * function has changed back to the default parse function.
14761 *
14762 * If we encounter more than maxStringSequence characters, we send back
14763 * the unterminated sequence to be re-parsed with the default parser function.
14764 *
14765 * @return {boolean} If true, parsing is ongoing or complete. If false, we've
14766 * exceeded the max string sequence.
14767 */
14768hterm.VT.prototype.parseUntilStringTerminator_ = function(parseState) {
14769 var buf = parseState.peekRemainingBuf();
14770 var nextTerminator = buf.search(/(\x1b\\|\x07)/);
14771 var args = parseState.args;
14772
14773 if (!args.length) {
14774 args[0] = '';
14775 args[1] = new Date();
14776 }
14777
14778 if (nextTerminator == -1) {
14779 // No terminator here, have to wait for the next string.
14780
14781 args[0] += buf;
14782
14783 var abortReason;
14784
14785 if (args[0].length > this.maxStringSequence)
14786 abortReason = 'too long: ' + args[0].length;
14787
14788 if (args[0].indexOf('\x1b') != -1)
14789 abortReason = 'embedded escape: ' + args[0].indexOf('\x1b');
14790
14791 if (new Date() - args[1] > this.oscTimeLimit_)
14792 abortReason = 'timeout expired: ' + new Date() - args[1];
14793
14794 if (abortReason) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014795 console.log(
14796 'parseUntilStringTerminator_: aborting: ' + abortReason, args[0]);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014797 parseState.reset(args[0]);
14798 return false;
14799 }
14800
14801 parseState.advance(buf.length);
14802 return true;
14803 }
14804
14805 if (args[0].length + nextTerminator > this.maxStringSequence) {
14806 // We found the end of the sequence, but we still think it's too long.
14807 parseState.reset(args[0] + buf);
14808 return false;
14809 }
14810
14811 args[0] += buf.substr(0, nextTerminator);
14812
14813 parseState.resetParseFunction();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014814 parseState.advance(
14815 nextTerminator + (buf.substr(nextTerminator, 1) == '\x1b' ? 2 : 1));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014816
14817 return true;
14818};
14819
14820/**
14821 * Dispatch to the function that handles a given CC1, ESC, or CSI or VT52 code.
14822 */
14823hterm.VT.prototype.dispatch = function(type, code, parseState) {
14824 var handler = hterm.VT[type][code];
14825 if (!handler) {
14826 if (this.warnUnimplemented)
14827 console.warn('Unknown ' + type + ' code: ' + JSON.stringify(code));
14828 return;
14829 }
14830
14831 if (handler == hterm.VT.ignore) {
14832 if (this.warnUnimplemented)
14833 console.warn('Ignored ' + type + ' code: ' + JSON.stringify(code));
14834 return;
14835 }
14836
14837 if (type == 'CC1' && code > '\x7f' && !this.enable8BitControl) {
14838 // It's kind of a hack to put this here, but...
14839 //
14840 // If we're dispatching a 'CC1' code, and it's got the eighth bit set,
14841 // but we're not supposed to handle 8-bit codes? Just ignore it.
14842 //
14843 // This prevents an errant (DCS, '\x90'), (OSC, '\x9d'), (PM, '\x9e') or
14844 // (APC, '\x9f') from locking up the terminal waiting for its expected
14845 // (ST, '\x9c') or (BEL, '\x07').
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014846 console.warn(
14847 'Ignoring 8-bit control code: 0x' + code.charCodeAt(0).toString(16));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014848 return;
14849 }
14850
14851 handler.apply(this, [parseState, code]);
14852};
14853
14854/**
14855 * Set one of the ANSI defined terminal mode bits.
14856 *
14857 * Invoked in response to SM/RM.
14858 *
14859 * Expected values for code:
14860 * 2 - Keyboard Action Mode (AM). Will not implement.
14861 * 4 - Insert Mode (IRM).
14862 * 12 - Send/receive (SRM). Will not implement.
14863 * 20 - Automatic Newline (LNM).
14864 *
14865 * Unexpected and unimplemented values are silently ignored.
14866 */
14867hterm.VT.prototype.setANSIMode = function(code, state) {
14868 if (code == '4') {
14869 this.terminal.setInsertMode(state);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014870 } else if (code == '20') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014871 this.terminal.setAutoCarriageReturn(state);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014872 } else if (this.warnUnimplemented) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014873 console.warn('Unimplemented ANSI Mode: ' + code);
14874 }
14875};
14876
14877/**
14878 * Set or reset one of the DEC Private modes.
14879 *
14880 * Invoked in response to DECSET/DECRST.
14881 *
14882 * Expected values for code:
14883 * 1 - Application Cursor Keys (DECCKM).
14884 * 2 - [!] Designate USASCII for character sets G0-G3 (DECANM), and set
14885 * VT100 mode.
14886 * 3 - 132 Column Mode (DECCOLM).
14887 * 4 - [x] Smooth (Slow) Scroll (DECSCLM).
14888 * 5 - Reverse Video (DECSCNM).
14889 * 6 - Origin Mode (DECOM).
14890 * 7 - Wraparound Mode (DECAWM).
14891 * 8 - [x] Auto-repeat Keys (DECARM).
14892 * 9 - [!] Send Mouse X & Y on button press.
14893 * 10 - [x] Show toolbar (rxvt).
14894 * 12 - Start Blinking Cursor (att610).
14895 * 18 - [!] Print form feed (DECPFF).
14896 * 19 - [x] Set print extent to full screen (DECPEX).
14897 * 25 - Show Cursor (DECTCEM).
14898 * 30 - [!] Show scrollbar (rxvt).
14899 * 35 - [x] Enable font-shifting functions (rxvt).
14900 * 38 - [x] Enter Tektronix Mode (DECTEK).
14901 * 40 - Allow 80 - 132 Mode.
14902 * 41 - [!] more(1) fix (see curses resource).
14903 * 42 - [!] Enable Nation Replacement Character sets (DECNRCM).
14904 * 44 - [!] Turn On Margin Bell.
14905 * 45 - Reverse-wraparound Mode.
14906 * 46 - [x] Start Logging.
14907 * 47 - [!] Use Alternate Screen Buffer.
14908 * 66 - [!] Application keypad (DECNKM).
14909 * 67 - Backarrow key sends backspace (DECBKM).
14910 * 1000 - Send Mouse X & Y on button press and release. (MOUSE_REPORT_CLICK)
14911 * 1001 - [!] Use Hilite Mouse Tracking.
14912 * 1002 - Use Cell Motion Mouse Tracking. (MOUSE_REPORT_DRAG)
14913 * 1003 - [!] Use All Motion Mouse Tracking.
14914 * 1004 - [!] Send FocusIn/FocusOut events.
14915 * 1005 - [!] Enable Extended Mouse Mode.
14916 * 1010 - Scroll to bottom on tty output (rxvt).
14917 * 1011 - Scroll to bottom on key press (rxvt).
14918 * 1034 - [x] Interpret "meta" key, sets eighth bit.
14919 * 1035 - [x] Enable special modifiers for Alt and NumLock keys.
14920 * 1036 - Send ESC when Meta modifies a key.
14921 * 1037 - [!] Send DEL from the editing-keypad Delete key.
14922 * 1039 - Send ESC when Alt modifies a key.
14923 * 1040 - [x] Keep selection even if not highlighted.
14924 * 1041 - [x] Use the CLIPBOARD selection.
14925 * 1042 - [!] Enable Urgency window manager hint when Control-G is received.
14926 * 1043 - [!] Enable raising of the window when Control-G is received.
14927 * 1047 - [!] Use Alternate Screen Buffer.
14928 * 1048 - Save cursor as in DECSC.
14929 * 1049 - Save cursor as in DECSC and use Alternate Screen Buffer, clearing
14930 * it first. (This may be disabled by the titeInhibit resource). This
14931 * combines the effects of the 1047 and 1048 modes. Use this with
14932 * terminfo-based applications rather than the 47 mode.
14933 * 1050 - [!] Set terminfo/termcap function-key mode.
14934 * 1051 - [x] Set Sun function-key mode.
14935 * 1052 - [x] Set HP function-key mode.
14936 * 1053 - [x] Set SCO function-key mode.
14937 * 1060 - [x] Set legacy keyboard emulation (X11R6).
14938 * 1061 - [!] Set VT220 keyboard emulation.
14939 * 2004 - Set bracketed paste mode.
14940 *
14941 * [!] - Not currently implemented, may be in the future.
14942 * [x] - Will not implement.
14943 */
14944hterm.VT.prototype.setDECMode = function(code, state) {
14945 switch (code) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014946 case '1': // DECCKM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014947 this.terminal.keyboard.applicationCursor = state;
14948 break;
14949
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014950 case '3': // DECCOLM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014951 if (this.allowColumnWidthChanges_) {
14952 this.terminal.setWidth(state ? 132 : 80);
14953
14954 this.terminal.clearHome();
14955 this.terminal.setVTScrollRegion(null, null);
14956 }
14957 break;
14958
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014959 case '5': // DECSCNM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014960 this.terminal.setReverseVideo(state);
14961 break;
14962
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014963 case '6': // DECOM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014964 this.terminal.setOriginMode(state);
14965 break;
14966
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014967 case '7': // DECAWM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014968 this.terminal.setWraparound(state);
14969 break;
14970
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014971 case '12': // att610
14972 if (this.enableDec12) this.terminal.setCursorBlink(state);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014973 break;
14974
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014975 case '25': // DECTCEM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014976 this.terminal.setCursorVisible(state);
14977 break;
14978
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014979 case '40': // no-spec
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014980 this.terminal.allowColumnWidthChanges_ = state;
14981 break;
14982
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014983 case '45': // no-spec
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014984 this.terminal.setReverseWraparound(state);
14985 break;
14986
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014987 case '67': // DECBKM
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014988 this.terminal.keyboard.backspaceSendsBackspace = state;
14989 break;
14990
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014991 case '1000': // Report on mouse clicks only.
14992 this.mouseReport =
14993 (state ? this.MOUSE_REPORT_CLICK : this.MOUSE_REPORT_DISABLED);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014994 break;
14995
Andrew Geisslerd27bb132018-05-24 11:07:27 -070014996 case '1002': // Report on mouse clicks and drags
14997 this.mouseReport =
14998 (state ? this.MOUSE_REPORT_DRAG : this.MOUSE_REPORT_DISABLED);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050014999 break;
15000
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015001 case '1010': // rxvt
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015002 this.terminal.scrollOnOutput = state;
15003 break;
15004
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015005 case '1011': // rxvt
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015006 this.terminal.scrollOnKeystroke = state;
15007 break;
15008
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015009 case '1036': // no-spec
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015010 this.terminal.keyboard.metaSendsEscape = state;
15011 break;
15012
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015013 case '1039': // no-spec
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015014 if (state) {
15015 if (!this.terminal.keyboard.previousAltSendsWhat_) {
15016 this.terminal.keyboard.previousAltSendsWhat_ =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015017 this.terminal.keyboard.altSendsWhat;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015018 this.terminal.keyboard.altSendsWhat = 'escape';
15019 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015020 } else if (this.terminal.keyboard.previousAltSendsWhat_) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015021 this.terminal.keyboard.altSendsWhat =
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015022 this.terminal.keyboard.previousAltSendsWhat_;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015023 this.terminal.keyboard.previousAltSendsWhat_ = null;
15024 }
15025 break;
15026
15027 case '47':
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015028 case '1047': // no-spec
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015029 this.terminal.setAlternateMode(state);
15030 break;
15031
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015032 case '1048': // Save cursor as in DECSC.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015033 this.savedState_.save();
15034
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015035 case '1049': // 1047 + 1048 + clear.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015036 if (state) {
15037 this.savedState_.save();
15038 this.terminal.setAlternateMode(state);
15039 this.terminal.clear();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015040 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015041 this.terminal.setAlternateMode(state);
15042 this.savedState_.restore();
15043 }
15044
15045 break;
15046
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015047 case '2004': // Bracketed paste mode.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015048 this.terminal.setBracketedPaste(state);
15049 break;
15050
15051 default:
15052 if (this.warnUnimplemented)
15053 console.warn('Unimplemented DEC Private Mode: ' + code);
15054 break;
15055 }
15056};
15057
15058/**
15059 * Function shared by control characters and escape sequences that are
15060 * ignored.
15061 */
15062hterm.VT.ignore = function() {};
15063
15064/**
15065 * Collection of control characters expressed in a single byte.
15066 *
15067 * This includes the characters from the C0 and C1 sets (see [CTRL]) that we
15068 * care about. Two byte versions of the C1 codes are defined in the
15069 * hterm.VT.ESC collection.
15070 *
15071 * The 'CC1' mnemonic here refers to the fact that these are one-byte Control
15072 * Codes. It's only used in this source file and not defined in any of the
15073 * referenced documents.
15074 */
15075hterm.VT.CC1 = {};
15076
15077/**
15078 * Collection of two-byte and three-byte sequences starting with ESC.
15079 */
15080hterm.VT.ESC = {};
15081
15082/**
15083 * Collection of CSI (Control Sequence Introducer) sequences.
15084 *
15085 * These sequences begin with 'ESC [', and may take zero or more arguments.
15086 */
15087hterm.VT.CSI = {};
15088
15089/**
15090 * Collection of OSC (Operating System Control) sequences.
15091 *
15092 * These sequences begin with 'ESC ]', followed by a function number and a
15093 * string terminated by either ST or BEL.
15094 */
15095hterm.VT.OSC = {};
15096
15097/**
15098 * Collection of VT52 sequences.
15099 *
15100 * When in VT52 mode, other sequences are disabled.
15101 */
15102hterm.VT.VT52 = {};
15103
15104/**
15105 * Null (NUL).
15106 *
15107 * Silently ignored.
15108 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070015109hterm.VT.CC1['\x00'] = function() {};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015110
15111/**
15112 * Enquiry (ENQ).
15113 *
15114 * Transmit answerback message.
15115 *
15116 * The default answerback message in xterm is an empty string, so we just
15117 * ignore this.
15118 */
15119hterm.VT.CC1['\x05'] = hterm.VT.ignore;
15120
15121/**
15122 * Ring Bell (BEL).
15123 */
15124hterm.VT.CC1['\x07'] = function() {
15125 this.terminal.ringBell();
15126};
15127
15128/**
15129 * Backspace (BS).
15130 *
15131 * Move the cursor to the left one character position, unless it is at the
15132 * left margin, in which case no action occurs.
15133 */
15134hterm.VT.CC1['\x08'] = function() {
15135 this.terminal.cursorLeft(1);
15136};
15137
15138/**
15139 * Horizontal Tab (HT).
15140 *
15141 * Move the cursor to the next tab stop, or to the right margin if no further
15142 * tab stops are present on the line.
15143 */
15144hterm.VT.CC1['\x09'] = function() {
15145 this.terminal.forwardTabStop();
15146};
15147
15148/**
15149 * Line Feed (LF).
15150 *
15151 * This code causes a line feed or a new line operation. See Automatic
15152 * Newline (LNM).
15153 */
15154hterm.VT.CC1['\x0a'] = function() {
15155 this.terminal.formFeed();
15156};
15157
15158/**
15159 * Vertical Tab (VT).
15160 *
15161 * Interpreted as LF.
15162 */
15163hterm.VT.CC1['\x0b'] = hterm.VT.CC1['\x0a'];
15164
15165/**
15166 * Form Feed (FF).
15167 *
15168 * Interpreted as LF.
15169 */
15170hterm.VT.CC1['\x0c'] = function() {
15171 this.terminal.formFeed();
15172};
15173
15174/**
15175 * Carriage Return (CR).
15176 *
15177 * Move cursor to the left margin on the current line.
15178 */
15179hterm.VT.CC1['\x0d'] = function() {
15180 this.terminal.setCursorColumn(0);
15181};
15182
15183/**
15184 * Shift Out (SO), aka Lock Shift 0 (LS1).
15185 *
15186 * Invoke G1 character set in GL.
15187 */
15188hterm.VT.CC1['\x0e'] = function() {
15189 this.GL = 'G1';
15190};
15191
15192/**
15193 * Shift In (SI), aka Lock Shift 0 (LS0).
15194 *
15195 * Invoke G0 character set in GL.
15196 */
15197hterm.VT.CC1['\x0f'] = function() {
15198 this.GL = 'G0';
15199};
15200
15201/**
15202 * Transmit On (XON).
15203 *
15204 * Not currently implemented.
15205 *
15206 * TODO(rginda): Implement?
15207 */
15208hterm.VT.CC1['\x11'] = hterm.VT.ignore;
15209
15210/**
15211 * Transmit Off (XOFF).
15212 *
15213 * Not currently implemented.
15214 *
15215 * TODO(rginda): Implement?
15216 */
15217hterm.VT.CC1['\x13'] = hterm.VT.ignore;
15218
15219/**
15220 * Cancel (CAN).
15221 *
15222 * If sent during a control sequence, the sequence is immediately terminated
15223 * and not executed.
15224 *
15225 * It also causes the error character to be displayed.
15226 */
15227hterm.VT.CC1['\x18'] = function(parseState) {
15228 // If we've shifted in the G1 character set, shift it back out to
15229 // the default character set.
15230 if (this.GL == 'G1') {
15231 this.GL = 'G0';
15232 }
15233 parseState.resetParseFunction();
15234 this.terminal.print('?');
15235};
15236
15237/**
15238 * Substitute (SUB).
15239 *
15240 * Interpreted as CAN.
15241 */
15242hterm.VT.CC1['\x1a'] = hterm.VT.CC1['\x18'];
15243
15244/**
15245 * Escape (ESC).
15246 */
15247hterm.VT.CC1['\x1b'] = function(parseState) {
15248 function parseESC(parseState) {
15249 var ch = parseState.consumeChar();
15250
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015251 if (ch == '\x1b') return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015252
15253 this.dispatch('ESC', ch, parseState);
15254
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015255 if (parseState.func == parseESC) parseState.resetParseFunction();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015256 };
15257
15258 parseState.func = parseESC;
15259};
15260
15261/**
15262 * Delete (DEL).
15263 */
15264hterm.VT.CC1['\x7f'] = hterm.VT.ignore;
15265
15266// 8 bit control characters and their two byte equivalents, below...
15267
15268/**
15269 * Index (IND).
15270 *
15271 * Like newline, only keep the X position
15272 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015273hterm.VT.CC1['\x84'] = hterm.VT.ESC['D'] = function() {
15274 this.terminal.lineFeed();
15275};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015276
15277/**
15278 * Next Line (NEL).
15279 *
15280 * Like newline, but doesn't add lines.
15281 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015282hterm.VT.CC1['\x85'] = hterm.VT.ESC['E'] = function() {
15283 this.terminal.setCursorColumn(0);
15284 this.terminal.cursorDown(1);
15285};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015286
15287/**
15288 * Horizontal Tabulation Set (HTS).
15289 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015290hterm.VT.CC1['\x88'] = hterm.VT.ESC['H'] = function() {
15291 this.terminal.setTabStop(this.terminal.getCursorColumn());
15292};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015293
15294/**
15295 * Reverse Index (RI).
15296 *
15297 * Move up one line.
15298 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015299hterm.VT.CC1['\x8d'] = hterm.VT.ESC['M'] = function() {
15300 this.terminal.reverseLineFeed();
15301};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015302
15303/**
15304 * Single Shift 2 (SS2).
15305 *
15306 * Select of G2 Character Set for the next character only.
15307 *
15308 * Not currently implemented.
15309 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015310hterm.VT.CC1['\x8e'] = hterm.VT.ESC['N'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015311
15312/**
15313 * Single Shift 3 (SS3).
15314 *
15315 * Select of G3 Character Set for the next character only.
15316 *
15317 * Not currently implemented.
15318 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015319hterm.VT.CC1['\x8f'] = hterm.VT.ESC['O'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015320
15321/**
15322 * Device Control String (DCS).
15323 *
15324 * Indicate a DCS sequence. See Device-Control functions in [XTERM].
15325 * Not currently implemented.
15326 *
15327 * TODO(rginda): Consider implementing DECRQSS, the rest don't seem applicable.
15328 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015329hterm.VT.CC1['\x90'] = hterm.VT.ESC['P'] = function(parseState) {
15330 parseState.resetArguments();
15331 parseState.func = this.parseUntilStringTerminator_;
15332};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015333
15334/**
15335 * Start of Protected Area (SPA).
15336 *
15337 * Will not implement.
15338 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015339hterm.VT.CC1['\x96'] = hterm.VT.ESC['V'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015340
15341/**
15342 * End of Protected Area (EPA).
15343 *
15344 * Will not implement.
15345 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015346hterm.VT.CC1['\x97'] = hterm.VT.ESC['W'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015347
15348/**
15349 * Start of String (SOS).
15350 *
15351 * Will not implement.
15352 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015353hterm.VT.CC1['\x98'] = hterm.VT.ESC['X'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015354
15355/**
15356 * Single Character Introducer (SCI, also DECID).
15357 *
15358 * Return Terminal ID. Obsolete form of 'ESC [ c' (DA).
15359 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015360hterm.VT.CC1['\x9a'] = hterm.VT.ESC['Z'] = function() {
15361 this.terminal.io.sendString('\x1b[?1;2c');
15362};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015363
15364/**
15365 * Control Sequence Introducer (CSI).
15366 *
15367 * The lead into most escape sequences. See [CSI].
15368 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015369hterm.VT.CC1['\x9b'] = hterm.VT.ESC['['] = function(parseState) {
15370 parseState.resetArguments();
15371 this.leadingModifier_ = '';
15372 this.trailingModifier_ = '';
15373 parseState.func = this.parseCSI_;
15374};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015375
15376/**
15377 * String Terminator (ST).
15378 *
15379 * Used to terminate DCS/OSC/PM/APC commands which may take string arguments.
15380 *
15381 * We don't directly handle it here, as it's only used to terminate other
15382 * sequences. See the 'parseUntilStringTerminator_' method.
15383 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015384hterm.VT.CC1['\x9c'] = hterm.VT.ESC['\\'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015385
15386/**
15387 * Operating System Command (OSC).
15388 *
15389 * Commands relating to the operating system.
15390 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015391hterm.VT.CC1['\x9d'] = hterm.VT.ESC[']'] = function(parseState) {
15392 parseState.resetArguments();
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015393
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015394 function parseOSC(parseState) {
15395 if (!this.parseUntilStringTerminator_(parseState)) {
15396 // The string sequence was too long.
15397 return;
15398 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015399
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015400 if (parseState.func == parseOSC) {
15401 // We're not done parsing the string yet.
15402 return;
15403 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015404
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015405 // We're done.
15406 var ary = parseState.args[0].match(/^(\d+);(.*)$/);
15407 if (ary) {
15408 parseState.args[0] = ary[2];
15409 this.dispatch('OSC', ary[1], parseState);
15410 } else {
15411 console.warn('Invalid OSC: ' + JSON.stringify(parseState.args[0]));
15412 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015413 };
15414
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015415 parseState.func = parseOSC;
15416};
15417
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015418/**
15419 * Privacy Message (PM).
15420 *
15421 * Will not implement.
15422 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015423hterm.VT.CC1['\x9e'] = hterm.VT.ESC['^'] = function(parseState) {
15424 parseState.resetArguments();
15425 parseState.func = this.parseUntilStringTerminator_;
15426};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015427
15428/**
15429 * Application Program Control (APC).
15430 *
15431 * Will not implement.
15432 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015433hterm.VT.CC1['\x9f'] = hterm.VT.ESC['_'] = function(parseState) {
15434 parseState.resetArguments();
15435 parseState.func = this.parseUntilStringTerminator_;
15436};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015437
15438/**
15439 * ESC \x20 - Unclear to me where these originated, possibly in xterm.
15440 *
15441 * Not currently implemented:
15442 * ESC \x20 F - Select 7 bit escape codes in responses (S7C1T).
15443 * ESC \x20 G - Select 8 bit escape codes in responses (S8C1T).
15444 * NB: We currently assume S7C1T always.
15445 *
15446 * Will not implement:
15447 * ESC \x20 L - Set ANSI conformance level 1.
15448 * ESC \x20 M - Set ANSI conformance level 2.
15449 * ESC \x20 N - Set ANSI conformance level 3.
15450 */
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070015451hterm.VT.ESC[' '] = function(parseState) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015452 parseState.func = function(parseState) {
15453 var ch = parseState.consumeChar();
15454 if (this.warnUnimplemented)
15455 console.warn('Unimplemented sequence: ESC 0x20 ' + ch);
15456 parseState.resetParseFunction();
15457 };
15458};
15459
15460/**
15461 * DEC 'ESC #' sequences.
15462 *
15463 * Handled:
15464 * ESC # 8 - DEC Screen Alignment Test (DECALN).
15465 * Fills the terminal with 'E's. Used liberally by vttest.
15466 *
15467 * Ignored:
15468 * ESC # 3 - DEC double-height line, top half (DECDHL).
15469 * ESC # 4 - DEC double-height line, bottom half (DECDHL).
15470 * ESC # 5 - DEC single-width line (DECSWL).
15471 * ESC # 6 - DEC double-width line (DECDWL).
15472 */
15473hterm.VT.ESC['#'] = function(parseState) {
15474 parseState.func = function(parseState) {
15475 var ch = parseState.consumeChar();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015476 if (ch == '8') this.terminal.fill('E');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015477
15478 parseState.resetParseFunction();
15479 };
15480};
15481
15482/**
15483 * 'ESC %' sequences, character set control. Not currently implemented.
15484 *
15485 * To be implemented (currently ignored):
15486 * ESC % @ - Set ISO 8859-1 character set.
15487 * ESC % G - Set UTF-8 character set.
15488 *
15489 * All other ESC # sequences are echoed to the terminal.
15490 *
15491 * TODO(rginda): Implement.
15492 */
15493hterm.VT.ESC['%'] = function(parseState) {
15494 parseState.func = function(parseState) {
15495 var ch = parseState.consumeChar();
15496 if (ch != '@' && ch != 'G' && this.warnUnimplemented)
15497 console.warn('Unknown ESC % argument: ' + JSON.stringify(ch));
15498 parseState.resetParseFunction();
15499 };
15500};
15501
15502/**
15503 * Character Set Selection (SCS).
15504 *
15505 * ESC ( Ps - Set G0 character set (VT100).
15506 * ESC ) Ps - Set G1 character set (VT220).
15507 * ESC * Ps - Set G2 character set (VT220).
15508 * ESC + Ps - Set G3 character set (VT220).
15509 * ESC - Ps - Set G1 character set (VT300).
15510 * ESC . Ps - Set G2 character set (VT300).
15511 * ESC / Ps - Set G3 character set (VT300).
15512 *
15513 * Values for Ps are:
15514 * 0 - DEC Special Character and Line Drawing Set.
15515 * A - United Kingdom (UK).
15516 * B - United States (USASCII).
15517 * 4 - Dutch.
15518 * C or 5 - Finnish.
15519 * R - French.
15520 * Q - French Canadian.
15521 * K - German.
15522 * Y - Italian.
15523 * E or 6 - Norwegian/Danish.
15524 * Z - Spanish.
15525 * H or 7 - Swedish.
15526 * = - Swiss.
15527 *
15528 * All other sequences are echoed to the terminal.
15529 *
15530 * TODO(rginda): Implement.
15531 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015532hterm.VT.ESC['('] = hterm.VT.ESC[')'] = hterm.VT.ESC['*'] = hterm.VT.ESC['+'] =
15533 hterm.VT.ESC['-'] = hterm.VT.ESC['.'] =
15534 hterm.VT.ESC['/'] = function(parseState, code) {
15535 parseState.func = function(parseState) {
15536 var ch = parseState.consumeChar();
15537 if (ch == '\x1b') {
15538 parseState.resetParseFunction();
15539 parseState.func();
15540 return;
15541 }
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015542
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015543 if (ch in hterm.VT.CharacterMap.maps) {
15544 if (code == '(') {
15545 this.G0 = hterm.VT.CharacterMap.maps[ch];
15546 } else if (code == ')' || code == '-') {
15547 this.G1 = hterm.VT.CharacterMap.maps[ch];
15548 } else if (code == '*' || code == '.') {
15549 this.G2 = hterm.VT.CharacterMap.maps[ch];
15550 } else if (code == '+' || code == '/') {
15551 this.G3 = hterm.VT.CharacterMap.maps[ch];
15552 }
15553 } else if (this.warnUnimplemented) {
15554 console.log('Invalid character set for "' + code + '": ' + ch);
15555 }
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070015556
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015557 parseState.resetParseFunction();
15558 };
15559 };
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015560
15561/**
15562 * Back Index (DECBI).
15563 *
15564 * VT420 and up. Not currently implemented.
15565 */
15566hterm.VT.ESC['6'] = hterm.VT.ignore;
15567
15568/**
15569 * Save Cursor (DECSC).
15570 */
15571hterm.VT.ESC['7'] = function() {
15572 this.savedState_.save();
15573};
15574
15575/**
15576 * Restore Cursor (DECSC).
15577 */
15578hterm.VT.ESC['8'] = function() {
15579 this.savedState_.restore();
15580};
15581
15582/**
15583 * Forward Index (DECFI).
15584 *
15585 * VT210 and up. Not currently implemented.
15586 */
15587hterm.VT.ESC['9'] = hterm.VT.ignore;
15588
15589/**
15590 * Application keypad (DECPAM).
15591 */
15592hterm.VT.ESC['='] = function() {
15593 this.terminal.keyboard.applicationKeypad = true;
15594};
15595
15596/**
15597 * Normal keypad (DECPNM).
15598 */
15599hterm.VT.ESC['>'] = function() {
15600 this.terminal.keyboard.applicationKeypad = false;
15601};
15602
15603/**
15604 * Cursor to lower left corner of screen.
15605 *
15606 * Will not implement.
15607 *
15608 * This is only recognized by xterm when the hpLowerleftBugCompat resource is
15609 * set.
15610 */
15611hterm.VT.ESC['F'] = hterm.VT.ignore;
15612
15613/**
15614 * Full Reset (RIS).
15615 */
15616hterm.VT.ESC['c'] = function() {
15617 this.reset();
15618 this.terminal.reset();
15619};
15620
15621/**
15622 * Memory lock/unlock.
15623 *
15624 * Will not implement.
15625 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015626hterm.VT.ESC['l'] = hterm.VT.ESC['m'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015627
15628/**
15629 * Lock Shift 2 (LS2)
15630 *
15631 * Invoke the G2 Character Set as GL.
15632 */
15633hterm.VT.ESC['n'] = function() {
15634 this.GL = 'G2';
15635};
15636
15637/**
15638 * Lock Shift 3 (LS3)
15639 *
15640 * Invoke the G3 Character Set as GL.
15641 */
15642hterm.VT.ESC['o'] = function() {
15643 this.GL = 'G3';
15644};
15645
15646/**
15647 * Lock Shift 2, Right (LS3R)
15648 *
15649 * Invoke the G3 Character Set as GR.
15650 */
15651hterm.VT.ESC['|'] = function() {
15652 this.GR = 'G3';
15653};
15654
15655/**
15656 * Lock Shift 2, Right (LS2R)
15657 *
15658 * Invoke the G2 Character Set as GR.
15659 */
15660hterm.VT.ESC['}'] = function() {
15661 this.GR = 'G2';
15662};
15663
15664/**
15665 * Lock Shift 1, Right (LS1R)
15666 *
15667 * Invoke the G1 Character Set as GR.
15668 */
15669hterm.VT.ESC['~'] = function() {
15670 this.GR = 'G1';
15671};
15672
15673/**
15674 * Change icon name and window title.
15675 *
15676 * We only change the window title.
15677 */
15678hterm.VT.OSC['0'] = function(parseState) {
15679 this.terminal.setWindowTitle(parseState.args[0]);
15680};
15681
15682/**
15683 * Change window title.
15684 */
15685hterm.VT.OSC['2'] = hterm.VT.OSC['0'];
15686
15687/**
15688 * Set/read color palette.
15689 */
15690hterm.VT.OSC['4'] = function(parseState) {
15691 // Args come in as a single 'index1;rgb1 ... ;indexN;rgbN' string.
15692 // We split on the semicolon and iterate through the pairs.
15693 var args = parseState.args[0].split(';');
15694
15695 var pairCount = parseInt(args.length / 2);
15696 var colorPalette = this.terminal.getTextAttributes().colorPalette;
15697 var responseArray = [];
15698
15699 for (var pairNumber = 0; pairNumber < pairCount; ++pairNumber) {
15700 var colorIndex = parseInt(args[pairNumber * 2]);
15701 var colorValue = args[pairNumber * 2 + 1];
15702
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015703 if (colorIndex >= colorPalette.length) continue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015704
15705 if (colorValue == '?') {
15706 // '?' means we should report back the current color value.
15707 colorValue = lib.colors.rgbToX11(colorPalette[colorIndex]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015708 if (colorValue) responseArray.push(colorIndex + ';' + colorValue);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015709
15710 continue;
15711 }
15712
15713 colorValue = lib.colors.x11ToCSS(colorValue);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015714 if (colorValue) colorPalette[colorIndex] = colorValue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015715 }
15716
15717 if (responseArray.length)
15718 this.terminal.io.sendString('\x1b]4;' + responseArray.join(';') + '\x07');
15719};
15720
15721/**
15722 * Set the cursor shape.
15723 *
15724 * Parameter is expected to be in the form "CursorShape=number", where number is
15725 * one of:
15726 *
15727 * 0 - Block
15728 * 1 - I-Beam
15729 * 2 - Underline
15730 *
15731 * This is a bit of a de-facto standard supported by iTerm 2 and Konsole. See
15732 * also: DECSCUSR.
15733 *
15734 * Invalid numbers will restore the cursor to the block shape.
15735 */
15736hterm.VT.OSC['50'] = function(parseState) {
15737 var args = parseState.args[0].match(/CursorShape=(.)/i);
15738 if (!args) {
15739 console.warn('Could not parse OSC 50 args: ' + parseState.args[0]);
15740 return;
15741 }
15742
15743 switch (args[1]) {
15744 case '1':
15745 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
15746 break;
15747
15748 case '2':
15749 this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
15750 break;
15751
15752 default:
15753 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
15754 }
15755};
15756
15757/**
15758 * Set/read system clipboard.
15759 *
15760 * Read is not implemented due to security considerations. A remote app
15761 * that is able to both write and read to the clipboard could essentially
15762 * take over your session.
15763 *
15764 * The clipboard data will be decoded according to the 'receive-encoding'
15765 * preference.
15766 */
15767hterm.VT.OSC['52'] = function(parseState) {
15768 // Args come in as a single 'clipboard;b64-data' string. The clipboard
15769 // parameter is used to select which of the X clipboards to address. Since
15770 // we're not integrating with X, we treat them all the same.
15771 var args = parseState.args[0].match(/^[cps01234567]*;(.*)/);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015772 if (!args) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015773
15774 var data = window.atob(args[1]);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015775 if (data) this.terminal.copyStringToClipboard(this.decode(data));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015776};
15777
15778/**
15779 * Insert (blank) characters (ICH).
15780 */
15781hterm.VT.CSI['@'] = function(parseState) {
15782 this.terminal.insertSpace(parseState.iarg(0, 1));
15783};
15784
15785/**
15786 * Cursor Up (CUU).
15787 */
15788hterm.VT.CSI['A'] = function(parseState) {
15789 this.terminal.cursorUp(parseState.iarg(0, 1));
15790};
15791
15792/**
15793 * Cursor Down (CUD).
15794 */
15795hterm.VT.CSI['B'] = function(parseState) {
15796 this.terminal.cursorDown(parseState.iarg(0, 1));
15797};
15798
15799/**
15800 * Cursor Forward (CUF).
15801 */
15802hterm.VT.CSI['C'] = function(parseState) {
15803 this.terminal.cursorRight(parseState.iarg(0, 1));
15804};
15805
15806/**
15807 * Cursor Backward (CUB).
15808 */
15809hterm.VT.CSI['D'] = function(parseState) {
15810 this.terminal.cursorLeft(parseState.iarg(0, 1));
15811};
15812
15813/**
15814 * Cursor Next Line (CNL).
15815 *
15816 * This is like Cursor Down, except the cursor moves to the beginning of the
15817 * line as well.
15818 */
15819hterm.VT.CSI['E'] = function(parseState) {
15820 this.terminal.cursorDown(parseState.iarg(0, 1));
15821 this.terminal.setCursorColumn(0);
15822};
15823
15824/**
15825 * Cursor Preceding Line (CPL).
15826 *
15827 * This is like Cursor Up, except the cursor moves to the beginning of the
15828 * line as well.
15829 */
15830hterm.VT.CSI['F'] = function(parseState) {
15831 this.terminal.cursorUp(parseState.iarg(0, 1));
15832 this.terminal.setCursorColumn(0);
15833};
15834
15835/**
15836 * Cursor Character Absolute (CHA).
15837 */
15838hterm.VT.CSI['G'] = function(parseState) {
15839 this.terminal.setCursorColumn(parseState.iarg(0, 1) - 1);
15840};
15841
15842/**
15843 * Cursor Position (CUP).
15844 */
15845hterm.VT.CSI['H'] = function(parseState) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015846 this.terminal.setCursorPosition(
15847 parseState.iarg(0, 1) - 1, parseState.iarg(1, 1) - 1);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015848};
15849
15850/**
15851 * Cursor Forward Tabulation (CHT).
15852 */
15853hterm.VT.CSI['I'] = function(parseState) {
15854 var count = parseState.iarg(0, 1);
15855 count = lib.f.clamp(count, 1, this.terminal.screenSize.width);
15856 for (var i = 0; i < count; i++) {
15857 this.terminal.forwardTabStop();
15858 }
15859};
15860
15861/**
15862 * Erase in Display (ED, DECSED).
15863 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015864hterm.VT.CSI['J'] = hterm.VT.CSI['?J'] = function(parseState, code) {
15865 var arg = parseState.args[0];
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015866
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015867 if (!arg || arg == '0') {
15868 this.terminal.eraseBelow();
15869 } else if (arg == '1') {
15870 this.terminal.eraseAbove();
15871 } else if (arg == '2') {
15872 this.terminal.clear();
15873 } else if (arg == '3') {
15874 // The xterm docs say this means "Erase saved lines", but we'll just clear
15875 // the display since killing the scrollback seems rude.
15876 this.terminal.clear();
15877 }
15878};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015879
15880/**
15881 * Erase in line (EL, DECSEL).
15882 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015883hterm.VT.CSI['K'] = hterm.VT.CSI['?K'] = function(parseState, code) {
15884 var arg = parseState.args[0];
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015885
Andrew Geisslerd27bb132018-05-24 11:07:27 -070015886 if (!arg || arg == '0') {
15887 this.terminal.eraseToRight();
15888 } else if (arg == '1') {
15889 this.terminal.eraseToLeft();
15890 } else if (arg == '2') {
15891 this.terminal.eraseLine();
15892 }
15893};
Iftekharul Islamdb28a382017-11-02 13:16:17 -050015894
15895/**
15896 * Insert Lines (IL).
15897 */
15898hterm.VT.CSI['L'] = function(parseState) {
15899 this.terminal.insertLines(parseState.iarg(0, 1));
15900};
15901
15902/**
15903 * Delete Lines (DL).
15904 */
15905hterm.VT.CSI['M'] = function(parseState) {
15906 this.terminal.deleteLines(parseState.iarg(0, 1));
15907};
15908
15909/**
15910 * Delete Characters (DCH).
15911 *
15912 * This command shifts the line contents left, starting at the cursor position.
15913 */
15914hterm.VT.CSI['P'] = function(parseState) {
15915 this.terminal.deleteChars(parseState.iarg(0, 1));
15916};
15917
15918/**
15919 * Scroll Up (SU).
15920 */
15921hterm.VT.CSI['S'] = function(parseState) {
15922 this.terminal.vtScrollUp(parseState.iarg(0, 1));
15923};
15924
15925/**
15926 * Scroll Down (SD).
15927 * Also 'Initiate highlight mouse tracking'. Will not implement this part.
15928 */
15929hterm.VT.CSI['T'] = function(parseState) {
15930 if (parseState.args.length <= 1)
15931 this.terminal.vtScrollDown(parseState.iarg(0, 1));
15932};
15933
15934/**
15935 * Reset one or more features of the title modes to the default value.
15936 *
15937 * ESC [ > Ps T
15938 *
15939 * Normally, "reset" disables the feature. It is possible to disable the
15940 * ability to reset features by compiling a different default for the title
15941 * modes into xterm.
15942 *
15943 * Ps values:
15944 * 0 - Do not set window/icon labels using hexadecimal.
15945 * 1 - Do not query window/icon labels using hexadecimal.
15946 * 2 - Do not set window/icon labels using UTF-8.
15947 * 3 - Do not query window/icon labels using UTF-8.
15948 *
15949 * Will not implement.
15950 */
15951hterm.VT.CSI['>T'] = hterm.VT.ignore;
15952
15953/**
15954 * Erase Characters (ECH).
15955 */
15956hterm.VT.CSI['X'] = function(parseState) {
15957 this.terminal.eraseToRight(parseState.iarg(0, 1));
15958};
15959
15960/**
15961 * Cursor Backward Tabulation (CBT).
15962 */
15963hterm.VT.CSI['Z'] = function(parseState) {
15964 var count = parseState.iarg(0, 1);
15965 count = lib.f.clamp(count, 1, this.terminal.screenSize.width);
15966 for (var i = 0; i < count; i++) {
15967 this.terminal.backwardTabStop();
15968 }
15969};
15970
15971/**
15972 * Character Position Absolute (HPA).
15973 */
15974hterm.VT.CSI['`'] = function(parseState) {
15975 this.terminal.setCursorColumn(parseState.iarg(0, 1) - 1);
15976};
15977
15978/**
15979 * Repeat the preceding graphic character.
15980 *
15981 * Not currently implemented.
15982 */
15983hterm.VT.CSI['b'] = hterm.VT.ignore;
15984
15985/**
15986 * Send Device Attributes (Primary DA).
15987 *
15988 * TODO(rginda): This is hardcoded to send back 'VT100 with Advanced Video
15989 * Option', but it may be more correct to send a VT220 response once
15990 * we fill out the 'Not currently implemented' parts.
15991 */
15992hterm.VT.CSI['c'] = function(parseState) {
15993 if (!parseState.args[0] || parseState.args[0] == '0') {
15994 this.terminal.io.sendString('\x1b[?1;2c');
15995 }
15996};
15997
15998/**
15999 * Send Device Attributes (Secondary DA).
16000 *
16001 * TODO(rginda): This is hardcoded to send back 'VT100' but it may be more
16002 * correct to send a VT220 response once we fill out more 'Not currently
16003 * implemented' parts.
16004 */
16005hterm.VT.CSI['>c'] = function(parseState) {
16006 this.terminal.io.sendString('\x1b[>0;256;0c');
16007};
16008
16009/**
16010 * Line Position Absolute (VPA).
16011 */
16012hterm.VT.CSI['d'] = function(parseState) {
16013 this.terminal.setAbsoluteCursorRow(parseState.iarg(0, 1) - 1);
16014};
16015
16016/**
16017 * Horizontal and Vertical Position (HVP).
16018 *
16019 * Same as Cursor Position (CUP).
16020 */
16021hterm.VT.CSI['f'] = hterm.VT.CSI['H'];
16022
16023/**
16024 * Tab Clear (TBC).
16025 */
16026hterm.VT.CSI['g'] = function(parseState) {
16027 if (!parseState.args[0] || parseState.args[0] == '0') {
16028 // Clear tab stop at cursor.
16029 this.terminal.clearTabStopAtCursor(false);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016030 } else if (parseState.args[0] == '3') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016031 // Clear all tab stops.
16032 this.terminal.clearAllTabStops();
16033 }
16034};
16035
16036/**
16037 * Set Mode (SM).
16038 */
16039hterm.VT.CSI['h'] = function(parseState) {
16040 for (var i = 0; i < parseState.args.length; i++) {
16041 this.setANSIMode(parseState.args[i], true);
16042 }
16043};
16044
16045/**
16046 * DEC Private Mode Set (DECSET).
16047 */
16048hterm.VT.CSI['?h'] = function(parseState) {
16049 for (var i = 0; i < parseState.args.length; i++) {
16050 this.setDECMode(parseState.args[i], true);
16051 }
16052};
16053
16054/**
16055 * Media Copy (MC).
16056 * Media Copy (MC, DEC Specific).
16057 *
16058 * These commands control the printer. Will not implement.
16059 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016060hterm.VT.CSI['i'] = hterm.VT.CSI['?i'] = hterm.VT.ignore;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016061
16062/**
16063 * Reset Mode (RM).
16064 */
16065hterm.VT.CSI['l'] = function(parseState) {
16066 for (var i = 0; i < parseState.args.length; i++) {
16067 this.setANSIMode(parseState.args[i], false);
16068 }
16069};
16070
16071/**
16072 * DEC Private Mode Reset (DECRST).
16073 */
16074hterm.VT.CSI['?l'] = function(parseState) {
16075 for (var i = 0; i < parseState.args.length; i++) {
16076 this.setDECMode(parseState.args[i], false);
16077 }
16078};
16079
16080/**
16081 * Character Attributes (SGR).
16082 *
16083 * Iterate through the list of arguments, applying the following attribute
16084 * changes based on the argument value...
16085 *
16086 * 0 Normal (default).
16087 * 1 Bold.
16088 * 2 Faint.
16089 * 3 Italic (non-xterm).
16090 * 4 Underlined.
16091 * 5 Blink (appears as Bold).
16092 * 7 Inverse.
16093 * 8 Invisible, i.e., hidden (VT300).
16094 * 9 Crossed out (ECMA-48).
16095 * 22 Normal (neither bold nor faint).
16096 * 23 Not italic (non-xterm).
16097 * 24 Not underlined.
16098 * 25 Steady (not blinking).
16099 * 27 Positive (not inverse).
16100 * 28 Visible, i.e., not hidden (VT300).
16101 * 29 Not crossed out (ECMA-48).
16102 * 30 Set foreground color to Black.
16103 * 31 Set foreground color to Red.
16104 * 32 Set foreground color to Green.
16105 * 33 Set foreground color to Yellow.
16106 * 34 Set foreground color to Blue.
16107 * 35 Set foreground color to Magenta.
16108 * 36 Set foreground color to Cyan.
16109 * 37 Set foreground color to White.
16110 * 39 Set foreground color to default (original).
16111 * 40 Set background color to Black.
16112 * 41 Set background color to Red.
16113 * 42 Set background color to Green.
16114 * 43 Set background color to Yellow.
16115 * 44 Set background color to Blue.
16116 * 45 Set background color to Magenta.
16117 * 46 Set background color to Cyan.
16118 * 47 Set background color to White.
16119 * 49 Set background color to default (original)
16120 *
16121 * Non-xterm (italic) codes have mixed support, but are supported by both
16122 * gnome-terminal and rxvt and are recognized as CSI codes on Wikipedia
16123 * (https://en.wikipedia.org/wiki/ANSI_escape_code).
16124 *
16125 * For 16-color support, the following apply.
16126 *
16127 * 90 Set foreground color to Bright Black.
16128 * 91 Set foreground color to Bright Red.
16129 * 92 Set foreground color to Bright Green.
16130 * 93 Set foreground color to Bright Yellow.
16131 * 94 Set foreground color to Bright Blue.
16132 * 95 Set foreground color to Bright Magenta.
16133 * 96 Set foreground color to Bright Cyan.
16134 * 97 Set foreground color to Bright White.
16135 * 100 Set background color to Bright Black.
16136 * 101 Set background color to Bright Red.
16137 * 102 Set background color to Bright Green.
16138 * 103 Set background color to Bright Yellow.
16139 * 104 Set background color to Bright Blue.
16140 * 105 Set background color to Bright Magenta.
16141 * 106 Set background color to Bright Cyan.
16142 * 107 Set background color to Bright White.
16143 *
16144 * For 88- or 256-color support, the following apply.
16145 * 38 ; 5 ; P Set foreground color to P.
16146 * 48 ; 5 ; P Set background color to P.
16147 *
16148 * For true color (24-bit) support, the following apply.
16149 * 38 ; 2 ; R ; G ; B Set foreground color to rgb(R, G, B)
16150 * 48 ; 2 ; R ; G ; B Set background color to rgb(R, G, B)
16151 *
16152 * Note that most terminals consider "bold" to be "bold and bright". In
16153 * some documents the bold state is even referred to as bright. We interpret
16154 * bold as bold-bright here too, but only when the "bold" setting comes before
16155 * the color selection.
16156 */
16157hterm.VT.CSI['m'] = function(parseState) {
16158 function get256(i) {
16159 if (parseState.args.length < i + 2 || parseState.args[i + 1] != '5')
16160 return null;
16161
16162 return parseState.iarg(i + 2, 0);
16163 }
16164
16165 function getTrueColor(i) {
16166 if (parseState.args.length < i + 5 || parseState.args[i + 1] != '2')
16167 return null;
16168 var r = parseState.iarg(i + 2, 0);
16169 var g = parseState.iarg(i + 3, 0);
16170 var b = parseState.iarg(i + 4, 0);
16171
16172 return 'rgb(' + r + ' ,' + g + ' ,' + b + ')';
16173 }
16174
16175 var attrs = this.terminal.getTextAttributes();
16176
16177 if (!parseState.args.length) {
16178 attrs.reset();
16179 return;
16180 }
16181
16182 for (var i = 0; i < parseState.args.length; i++) {
16183 var arg = parseState.iarg(i, 0);
16184
16185 if (arg < 30) {
16186 if (arg == 0) {
16187 attrs.reset();
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016188 } else if (arg == 1) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016189 attrs.bold = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016190 } else if (arg == 2) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016191 attrs.faint = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016192 } else if (arg == 3) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016193 attrs.italic = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016194 } else if (arg == 4) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016195 attrs.underline = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016196 } else if (arg == 5) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016197 attrs.blink = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016198 } else if (arg == 7) { // Inverse.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016199 attrs.inverse = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016200 } else if (arg == 8) { // Invisible.
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016201 attrs.invisible = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016202 } else if (arg == 9) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016203 attrs.strikethrough = true;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016204 } else if (arg == 22) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016205 attrs.bold = false;
16206 attrs.faint = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016207 } else if (arg == 23) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016208 attrs.italic = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016209 } else if (arg == 24) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016210 attrs.underline = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016211 } else if (arg == 25) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016212 attrs.blink = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016213 } else if (arg == 27) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016214 attrs.inverse = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016215 } else if (arg == 28) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016216 attrs.invisible = false;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016217 } else if (arg == 29) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016218 attrs.strikethrough = false;
16219 }
16220
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016221 } else if (arg < 50) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016222 // Select fore/background color from bottom half of 16 color palette
16223 // or from the 256 color palette or alternative specify color in fully
16224 // qualified rgb(r, g, b) form.
16225 if (arg < 38) {
16226 attrs.foregroundSource = arg - 30;
16227
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016228 } else if (arg == 38) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016229 // First check for true color definition
16230 var trueColor = getTrueColor(i);
16231 if (trueColor != null) {
16232 attrs.foregroundSource = attrs.SRC_RGB;
16233 attrs.foreground = trueColor;
16234
16235 i += 5;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016236 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016237 // Check for 256 color
16238 var c = get256(i);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016239 if (c == null) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016240
16241 i += 2;
16242
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016243 if (c >= attrs.colorPalette.length) continue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016244
16245 attrs.foregroundSource = c;
16246 }
16247
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016248 } else if (arg == 39) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016249 attrs.foregroundSource = attrs.SRC_DEFAULT;
16250
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016251 } else if (arg < 48) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016252 attrs.backgroundSource = arg - 40;
16253
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016254 } else if (arg == 48) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016255 // First check for true color definition
16256 var trueColor = getTrueColor(i);
16257 if (trueColor != null) {
16258 attrs.backgroundSource = attrs.SRC_RGB;
16259 attrs.background = trueColor;
16260
16261 i += 5;
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016262 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016263 // Check for 256 color
16264 var c = get256(i);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016265 if (c == null) break;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016266
16267 i += 2;
16268
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016269 if (c >= attrs.colorPalette.length) continue;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016270
16271 attrs.backgroundSource = c;
16272 }
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016273 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016274 attrs.backgroundSource = attrs.SRC_DEFAULT;
16275 }
16276
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016277 } else if (arg >= 90 && arg <= 97) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016278 attrs.foregroundSource = arg - 90 + 8;
16279
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016280 } else if (arg >= 100 && arg <= 107) {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016281 attrs.backgroundSource = arg - 100 + 8;
16282 }
16283 }
16284
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016285 attrs.setDefaults(
16286 this.terminal.getForegroundColor(), this.terminal.getBackgroundColor());
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016287};
16288
16289/**
16290 * Set xterm-specific keyboard modes.
16291 *
16292 * Will not implement.
16293 */
16294hterm.VT.CSI['>m'] = hterm.VT.ignore;
16295
16296/**
16297 * Device Status Report (DSR, DEC Specific).
16298 *
16299 * 5 - Status Report. Result (OK) is CSI 0 n
16300 * 6 - Report Cursor Position (CPR) [row;column]. Result is CSI r ; c R
16301 */
16302hterm.VT.CSI['n'] = function(parseState) {
16303 if (parseState.args[0] == '5') {
16304 this.terminal.io.sendString('\x1b0n');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016305 } else if (parseState.args[0] == '6') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016306 var row = this.terminal.getCursorRow() + 1;
16307 var col = this.terminal.getCursorColumn() + 1;
16308 this.terminal.io.sendString('\x1b[' + row + ';' + col + 'R');
16309 }
16310};
16311
16312/**
16313 * Disable modifiers which may be enabled via CSI['>m'].
16314 *
16315 * Will not implement.
16316 */
16317hterm.VT.CSI['>n'] = hterm.VT.ignore;
16318
16319/**
16320 * Device Status Report (DSR, DEC Specific).
16321 *
16322 * 6 - Report Cursor Position (CPR) [row;column] as CSI ? r ; c R
16323 * 15 - Report Printer status as CSI ? 1 0 n (ready) or
16324 * CSI ? 1 1 n (not ready).
16325 * 25 - Report UDK status as CSI ? 2 0 n (unlocked) or CSI ? 2 1 n (locked).
16326 * 26 - Report Keyboard status as CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).
16327 * The last two parameters apply to VT400 & up, and denote keyboard ready
16328 * and LK01 respectively.
16329 * 53 - Report Locator status as CSI ? 5 3 n Locator available, if compiled-in,
16330 * or CSI ? 5 0 n No Locator, if not.
16331 */
16332hterm.VT.CSI['?n'] = function(parseState) {
16333 if (parseState.args[0] == '6') {
16334 var row = this.terminal.getCursorRow() + 1;
16335 var col = this.terminal.getCursorColumn() + 1;
16336 this.terminal.io.sendString('\x1b[' + row + ';' + col + 'R');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016337 } else if (parseState.args[0] == '15') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016338 this.terminal.io.sendString('\x1b[?11n');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016339 } else if (parseState.args[0] == '25') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016340 this.terminal.io.sendString('\x1b[?21n');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016341 } else if (parseState.args[0] == '26') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016342 this.terminal.io.sendString('\x1b[?12;1;0;0n');
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016343 } else if (parseState.args[0] == '53') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016344 this.terminal.io.sendString('\x1b[?50n');
16345 }
16346};
16347
16348/**
16349 * This is used by xterm to decide whether to hide the pointer cursor as the
16350 * user types.
16351 *
16352 * Valid values for the parameter:
16353 * 0 - Never hide the pointer.
16354 * 1 - Hide if the mouse tracking mode is not enabled.
16355 * 2 - Always hide the pointer.
16356 *
16357 * If no parameter is given, xterm uses the default, which is 1.
16358 *
16359 * Not currently implemented.
16360 */
16361hterm.VT.CSI['>p'] = hterm.VT.ignore;
16362
16363/**
16364 * Soft terminal reset (DECSTR).
16365 */
16366hterm.VT.CSI['!p'] = function() {
16367 this.reset();
16368 this.terminal.softReset();
16369};
16370
16371/**
16372 * Request ANSI Mode (DECRQM).
16373 *
16374 * Not currently implemented.
16375 */
16376hterm.VT.CSI['$p'] = hterm.VT.ignore;
16377hterm.VT.CSI['?$p'] = hterm.VT.ignore;
16378
16379/**
16380 * Set conformance level (DECSCL).
16381 *
16382 * Not currently implemented.
16383 */
16384hterm.VT.CSI['"p'] = hterm.VT.ignore;
16385
16386/**
16387 * Load LEDs (DECLL).
16388 *
16389 * Not currently implemented. Could be implemented as virtual LEDs overlaying
16390 * the terminal if anyone cares.
16391 */
16392hterm.VT.CSI['q'] = hterm.VT.ignore;
16393
16394/**
16395 * Set cursor style (DECSCUSR, VT520).
16396 *
16397 * 0 - Blinking block.
16398 * 1 - Blinking block (default).
16399 * 2 - Steady block.
16400 * 3 - Blinking underline.
16401 * 4 - Steady underline.
16402 */
16403hterm.VT.CSI[' q'] = function(parseState) {
16404 var arg = parseState.args[0];
16405
16406 if (arg == '0' || arg == '1') {
16407 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
16408 this.terminal.setCursorBlink(true);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016409 } else if (arg == '2') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016410 this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
16411 this.terminal.setCursorBlink(false);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016412 } else if (arg == '3') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016413 this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
16414 this.terminal.setCursorBlink(true);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016415 } else if (arg == '4') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016416 this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
16417 this.terminal.setCursorBlink(false);
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016418 } else {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016419 console.warn('Unknown cursor style: ' + arg);
16420 }
16421};
16422
16423/**
16424 * Select character protection attribute (DECSCA).
16425 *
16426 * Will not implement.
16427 */
16428hterm.VT.CSI['"q'] = hterm.VT.ignore;
16429
16430/**
16431 * Set Scrolling Region (DECSTBM).
16432 */
16433hterm.VT.CSI['r'] = function(parseState) {
16434 var args = parseState.args;
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070016435 var scrollTop = args[0] ? parseInt(args[0], 10) - 1 : null;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016436 var scrollBottom = args[1] ? parseInt(args[1], 10) - 1 : null;
16437 this.terminal.setVTScrollRegion(scrollTop, scrollBottom);
16438 this.terminal.setCursorPosition(0, 0);
16439};
16440
16441/**
16442 * Restore DEC Private Mode Values.
16443 *
16444 * Will not implement.
16445 */
16446hterm.VT.CSI['?r'] = hterm.VT.ignore;
16447
16448/**
16449 * Change Attributes in Rectangular Area (DECCARA)
16450 *
16451 * Will not implement.
16452 */
16453hterm.VT.CSI['$r'] = hterm.VT.ignore;
16454
16455/**
16456 * Save cursor (ANSI.SYS)
16457 */
16458hterm.VT.CSI['s'] = function() {
16459 this.savedState_.save();
16460};
16461
16462/**
16463 * Save DEC Private Mode Values.
16464 *
16465 * Will not implement.
16466 */
16467hterm.VT.CSI['?s'] = hterm.VT.ignore;
16468
16469/**
16470 * Window manipulation (from dtterm, as well as extensions).
16471 *
16472 * Will not implement.
16473 */
16474hterm.VT.CSI['t'] = hterm.VT.ignore;
16475
16476/**
16477 * Reverse Attributes in Rectangular Area (DECRARA).
16478 *
16479 * Will not implement.
16480 */
16481hterm.VT.CSI['$t'] = hterm.VT.ignore;
16482
16483/**
16484 * Set one or more features of the title modes.
16485 *
16486 * Will not implement.
16487 */
16488hterm.VT.CSI['>t'] = hterm.VT.ignore;
16489
16490/**
16491 * Set warning-bell volume (DECSWBV, VT520).
16492 *
16493 * Will not implement.
16494 */
16495hterm.VT.CSI[' t'] = hterm.VT.ignore;
16496
16497/**
16498 * Restore cursor (ANSI.SYS).
16499 */
16500hterm.VT.CSI['u'] = function() {
16501 this.savedState_.restore();
16502};
16503
16504/**
16505 * Set margin-bell volume (DECSMBV, VT520).
16506 *
16507 * Will not implement.
16508 */
16509hterm.VT.CSI[' u'] = hterm.VT.ignore;
16510
16511/**
16512 * Copy Rectangular Area (DECCRA, VT400 and up).
16513 *
16514 * Will not implement.
16515 */
16516hterm.VT.CSI['$v'] = hterm.VT.ignore;
16517
16518/**
16519 * Enable Filter Rectangle (DECEFR).
16520 *
16521 * Will not implement.
16522 */
16523hterm.VT.CSI['\'w'] = hterm.VT.ignore;
16524
16525/**
16526 * Request Terminal Parameters (DECREQTPARM).
16527 *
16528 * Not currently implemented.
16529 */
16530hterm.VT.CSI['x'] = hterm.VT.ignore;
16531
16532/**
16533 * Select Attribute Change Extent (DECSACE).
16534 *
16535 * Will not implement.
16536 */
16537hterm.VT.CSI['*x'] = hterm.VT.ignore;
16538
16539/**
16540 * Fill Rectangular Area (DECFRA), VT420 and up.
16541 *
16542 * Will not implement.
16543 */
16544hterm.VT.CSI['$x'] = hterm.VT.ignore;
16545
16546/**
16547 * vt_tiledata (as used by NAOhack and UnNetHack)
16548 * (see https://nethackwiki.com/wiki/Vt_tiledata for more info)
16549 *
16550 * Implemented as far as we care (start a glyph and end a glyph).
16551 */
16552hterm.VT.CSI['z'] = function(parseState) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016553 if (parseState.args.length < 1) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016554 var arg = parseState.args[0];
16555 if (arg == '0') {
16556 // Start a glyph (one parameter, the glyph number).
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016557 if (parseState.args.length < 2) return;
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016558 this.terminal.getTextAttributes().tileData = parseState.args[1];
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016559 } else if (arg == '1') {
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016560 // End a glyph.
16561 this.terminal.getTextAttributes().tileData = null;
16562 }
16563};
16564
16565/**
16566 * Enable Locator Reporting (DECELR).
16567 *
16568 * Not currently implemented.
16569 */
16570hterm.VT.CSI['\'z'] = hterm.VT.ignore;
16571
16572/**
16573 * Erase Rectangular Area (DECERA), VT400 and up.
16574 *
16575 * Will not implement.
16576 */
16577hterm.VT.CSI['$z'] = hterm.VT.ignore;
16578
16579/**
16580 * Select Locator Events (DECSLE).
16581 *
16582 * Not currently implemented.
16583 */
16584hterm.VT.CSI['\'{'] = hterm.VT.ignore;
16585
16586/**
16587 * Request Locator Position (DECRQLP).
16588 *
16589 * Not currently implemented.
16590 */
16591hterm.VT.CSI['\'|'] = hterm.VT.ignore;
16592
16593/**
16594 * Insert Columns (DECIC), VT420 and up.
16595 *
16596 * Will not implement.
16597 */
16598hterm.VT.CSI[' }'] = hterm.VT.ignore;
16599
16600/**
16601 * Delete P s Columns (DECDC), VT420 and up.
16602 *
16603 * Will not implement.
16604 */
16605hterm.VT.CSI[' ~'] = hterm.VT.ignore;
16606// SOURCE FILE: hterm/js/hterm_vt_character_map.js
16607// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
16608// Use of this source code is governed by a BSD-style license that can be
16609// found in the LICENSE file.
16610
16611'use strict';
16612
16613lib.rtdep('lib.f');
16614
16615/**
16616 * Character map object.
16617 *
16618 * @param {object} The GL mapping from input characters to output characters.
16619 * The GR mapping will be automatically created.
16620 */
16621hterm.VT.CharacterMap = function(name, glmap) {
16622 /**
16623 * Short name for this character set, useful for debugging.
16624 */
16625 this.name = name;
16626
16627 /**
16628 * The function to call to when this map is installed in GL.
16629 */
16630 this.GL = null;
16631
16632 /**
16633 * The function to call to when this map is installed in GR.
16634 */
16635 this.GR = null;
16636
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016637 if (glmap) this.reset(glmap);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016638};
16639
16640/**
16641 * @param {object} The GL mapping from input characters to output characters.
16642 * The GR mapping will be automatically created.
16643 */
16644hterm.VT.CharacterMap.prototype.reset = function(glmap) {
16645 // Set the the GL mapping.
16646 this.glmap = glmap;
16647
16648 var glkeys = Object.keys(this.glmap).map(function(key) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070016649 return '\\x' + lib.f.zpad(key.charCodeAt(0).toString(16));
16650 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016651
16652 this.glre = new RegExp('[' + glkeys.join('') + ']', 'g');
16653
16654 // Compute the GR mapping.
16655 // This is the same as GL except all keys have their MSB set.
16656 this.grmap = {};
16657
16658 glkeys.forEach(function(glkey) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070016659 var grkey = String.fromCharCode(glkey.charCodeAt(0) & 0x80);
16660 this.grmap[grkey] = this.glmap[glkey];
16661 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016662
16663 var grkeys = Object.keys(this.grmap).map(function(key) {
Andrew Geisslerba5e3f32018-05-24 10:58:00 -070016664 return '\\x' + lib.f.zpad(key.charCodeAt(0).toString(16), 2);
16665 });
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016666
16667 this.grre = new RegExp('[' + grkeys.join('') + ']', 'g');
16668
16669 this.GL = function(str) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016670 return str.replace(this.glre, function(ch) {
16671 return this.glmap[ch];
16672 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016673 }.bind(this);
16674
16675 this.GR = function(str) {
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016676 return str.replace(this.grre, function(ch) {
16677 return this.grmap[ch];
16678 }.bind(this));
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016679 }.bind(this);
16680};
16681
16682/**
16683 * Mapping from received to display character, used depending on the active
16684 * VT character set.
16685 */
16686hterm.VT.CharacterMap.maps = {};
16687
16688/**
16689 * VT100 Graphic character map.
16690 * http://vt100.net/docs/vt220-rm/table2-4.html
16691 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016692hterm.VT.CharacterMap.maps['0'] = new hterm.VT.CharacterMap('graphic', {
16693 '`': '◆', // ` -> diamond
16694 'a': '▒', // a -> grey-box
16695 'b': '␉', // b -> h/t
16696 'c': '␌', // c -> f/f
16697 'd': '␍', // d -> c/r
16698 'e': '␊', // e -> l/f
16699 'f': '°', // f -> degree
16700 'g': '±', // g -> +/-
16701 'h': '␤', // h -> n/l
16702 'i': '␋', // i -> v/t
16703 'j': '┘', // j -> bottom-right
16704 'k': '┐', // k -> top-right
16705 'l': '┌', // l -> top-left
16706 'm': '└', // m -> bottom-left
16707 'n': '┼', // n -> line-cross
16708 'o': '⎺', // o -> scan1
16709 'p': '⎻', // p -> scan3
16710 'q': '─', // q -> scan5
16711 'r': '⎼', // r -> scan7
16712 's': '⎽', // s -> scan9
16713 't': '├', // t -> left-tee
16714 'u': '┤', // u -> right-tee
16715 'v': '┴', // v -> bottom-tee
16716 'w': '┬', // w -> top-tee
16717 'x': '│', // x -> vertical-line
16718 'y': '≤', // y -> less-equal
16719 'z': '≥', // z -> greater-equal
16720 '{': 'π', // { -> pi
16721 '|': '≠', // | -> not-equal
16722 '}': '£', // } -> british-pound
16723 '~': '·', // ~ -> dot
16724});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016725
16726/**
16727 * British character map.
16728 * http://vt100.net/docs/vt220-rm/table2-5.html
16729 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016730hterm.VT.CharacterMap.maps['A'] = new hterm.VT.CharacterMap('british', {
16731 '#': '£', // # -> british-pound
16732});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016733
16734/**
16735 * US ASCII map, no changes.
16736 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016737hterm.VT.CharacterMap.maps['B'] = new hterm.VT.CharacterMap('us', null);
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016738
16739/**
16740 * Dutch character map.
16741 * http://vt100.net/docs/vt220-rm/table2-6.html
16742 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016743hterm.VT.CharacterMap.maps['4'] = new hterm.VT.CharacterMap('dutch', {
16744 '#': '£', // # -> british-pound
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016745
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016746 '@': '¾', // @ -> 3/4
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016747
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016748 '[': 'IJ', // [ -> 'ij' ligature (xterm goes with \u00ff?)
16749 '\\': '½', // \ -> 1/2
16750 ']': '|', // ] -> vertical bar
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016751
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016752 '{': '¨', // { -> two dots
16753 '|': 'f', // | -> f
16754 '}': '¼', // } -> 1/4
16755 '~': '´', // ~ -> acute
16756});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016757
16758/**
16759 * Finnish character map.
16760 * http://vt100.net/docs/vt220-rm/table2-7.html
16761 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016762hterm.VT.CharacterMap.maps['C'] = hterm.VT.CharacterMap.maps['5'] =
16763 new hterm.VT.CharacterMap('finnish', {
16764 '[': 'Ä', // [ -> 'A' umlaut
16765 '\\': 'Ö', // \ -> 'O' umlaut
16766 ']': 'Å', // ] -> 'A' ring
16767 '^': 'Ü', // ~ -> 'u' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016768
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016769 '`': 'é', // ` -> 'e' acute
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016770
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016771 '{': 'ä', // { -> 'a' umlaut
16772 '|': 'ö', // | -> 'o' umlaut
16773 '}': 'å', // } -> 'a' ring
16774 '~': 'ü', // ~ -> 'u' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016775 });
16776
16777/**
16778 * French character map.
16779 * http://vt100.net/docs/vt220-rm/table2-8.html
16780 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016781hterm.VT.CharacterMap.maps['R'] = new hterm.VT.CharacterMap('french', {
16782 '#': '£', // # -> british-pound
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016783
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016784 '@': 'à', // @ -> 'a' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016785
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016786 '[': '°', // [ -> ring
16787 '\\': 'ç', // \ -> 'c' cedilla
16788 ']': '§', // ] -> section symbol (double s)
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016789
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016790 '{': 'é', // { -> 'e' acute
16791 '|': 'ù', // | -> 'u' grave
16792 '}': 'è', // } -> 'e' grave
16793 '~': '¨', // ~ -> umlaut
16794});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016795
16796/**
16797 * French Canadian character map.
16798 * http://vt100.net/docs/vt220-rm/table2-9.html
16799 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016800hterm.VT.CharacterMap.maps['Q'] = new hterm.VT.CharacterMap('french canadian', {
16801 '@': 'à', // @ -> 'a' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016802
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016803 '[': 'â', // [ -> 'a' circumflex
16804 '\\': 'ç', // \ -> 'c' cedilla
16805 ']': 'ê', // ] -> 'e' circumflex
16806 '^': 'î', // ^ -> 'i' circumflex
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016807
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016808 '`': 'ô', // ` -> 'o' circumflex
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016809
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016810 '{': 'é', // { -> 'e' acute
16811 '|': 'ù', // | -> 'u' grave
16812 '}': 'è', // } -> 'e' grave
16813 '~': 'û', // ~ -> 'u' circumflex
16814});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016815
16816/**
16817 * German character map.
16818 * http://vt100.net/docs/vt220-rm/table2-10.html
16819 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016820hterm.VT.CharacterMap.maps['K'] = new hterm.VT.CharacterMap('german', {
16821 '@': '§', // @ -> section symbol (double s)
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016822
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016823 '[': 'Ä', // [ -> 'A' umlaut
16824 '\\': 'Ö', // \ -> 'O' umlaut
16825 ']': 'Ü', // ] -> 'U' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016826
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016827 '{': 'ä', // { -> 'a' umlaut
16828 '|': 'ö', // | -> 'o' umlaut
16829 '}': 'ü', // } -> 'u' umlaut
16830 '~': 'ß', // ~ -> eszett
16831});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016832
16833/**
16834 * Italian character map.
16835 * http://vt100.net/docs/vt220-rm/table2-11.html
16836 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016837hterm.VT.CharacterMap.maps['Y'] = new hterm.VT.CharacterMap('italian', {
16838 '#': '£', // # -> british-pound
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016839
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016840 '@': '§', // @ -> section symbol (double s)
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016841
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016842 '[': '°', // [ -> ring
16843 '\\': 'ç', // \ -> 'c' cedilla
16844 ']': 'é', // ] -> 'e' acute
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016845
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016846 '`': 'ù', // ` -> 'u' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016847
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016848 '{': 'à', // { -> 'a' grave
16849 '|': 'ò', // | -> 'o' grave
16850 '}': 'è', // } -> 'e' grave
16851 '~': 'ì', // ~ -> 'i' grave
16852});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016853
16854/**
16855 * Norwegian/Danish character map.
16856 * http://vt100.net/docs/vt220-rm/table2-12.html
16857 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016858hterm.VT.CharacterMap.maps['E'] = hterm.VT.CharacterMap.maps['6'] =
16859 new hterm.VT.CharacterMap('norwegian/danish', {
16860 '@': 'Ä', // @ -> 'A' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016861
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016862 '[': 'Æ', // [ -> 'AE' ligature
16863 '\\': 'Ø', // \ -> 'O' stroke
16864 ']': 'Å', // ] -> 'A' ring
16865 '^': 'Ü', // ^ -> 'U' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016866
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016867 '`': 'ä', // ` -> 'a' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016868
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016869 '{': 'æ', // { -> 'ae' ligature
16870 '|': 'ø', // | -> 'o' stroke
16871 '}': 'å', // } -> 'a' ring
16872 '~': 'ü', // ~ -> 'u' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016873 });
16874
16875/**
16876 * Spanish character map.
16877 * http://vt100.net/docs/vt220-rm/table2-13.html
16878 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016879hterm.VT.CharacterMap.maps['Z'] = new hterm.VT.CharacterMap('spanish', {
16880 '#': '£', // # -> british-pound
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016881
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016882 '@': '§', // @ -> section symbol (double s)
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016883
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016884 '[': '¡', // [ -> '!' inverted
16885 '\\': 'Ñ', // \ -> 'N' tilde
16886 ']': '¿', // ] -> '?' inverted
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016887
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016888 '{': '°', // { -> ring
16889 '|': 'ñ', // | -> 'n' tilde
16890 '}': 'ç', // } -> 'c' cedilla
16891});
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016892
16893/**
16894 * Swedish character map.
16895 * http://vt100.net/docs/vt220-rm/table2-14.html
16896 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016897hterm.VT.CharacterMap.maps['7'] = hterm.VT.CharacterMap.maps['H'] =
16898 new hterm.VT.CharacterMap('swedish', {
16899 '@': 'É', // @ -> 'E' acute
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016900
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016901 '[': 'Ä', // [ -> 'A' umlaut
16902 '\\': 'Ö', // \ -> 'O' umlaut
16903 ']': 'Å', // ] -> 'A' ring
16904 '^': 'Ü', // ^ -> 'U' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016905
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016906 '`': 'é', // ` -> 'e' acute
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016907
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016908 '{': 'ä', // { -> 'a' umlaut
16909 '|': 'ö', // | -> 'o' umlaut
16910 '}': 'å', // } -> 'a' ring
16911 '~': 'ü', // ~ -> 'u' umlaut
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016912 });
16913
16914/**
16915 * Swiss character map.
16916 * http://vt100.net/docs/vt220-rm/table2-15.html
16917 */
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016918hterm.VT.CharacterMap.maps['='] = new hterm.VT.CharacterMap('swiss', {
16919 '#': 'ù', // # -> 'u' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016920
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016921 '@': 'à', // @ -> 'a' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016922
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016923 '[': 'é', // [ -> 'e' acute
16924 '\\': 'ç', // \ -> 'c' cedilla
16925 ']': 'ê', // ] -> 'e' circumflex
16926 '^': 'î', // ^ -> 'i' circumflex
16927 '_': 'è', // _ -> 'e' grave
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016928
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016929 '`': 'ô', // ` -> 'o' circumflex
Iftekharul Islamdb28a382017-11-02 13:16:17 -050016930
Andrew Geisslerd27bb132018-05-24 11:07:27 -070016931 '{': 'ä', // { -> 'a' umlaut
16932 '|': 'ö', // | -> 'o' umlaut
16933 '}': 'ü', // } -> 'u' umlaut
16934 '~': 'û', // ~ -> 'u' circumflex
16935});
16936lib.resource.add(
16937 'hterm/audio/bell', 'audio/ogg;base64',
16938 'T2dnUwACAAAAAAAAAADhqW5KAAAAAMFvEjYBHgF2b3JiaXMAAAAAAYC7AAAAAAAAAHcBAAAAAAC4' +
16939 'AU9nZ1MAAAAAAAAAAAAA4aluSgEAAAAAesI3EC3//////////////////8kDdm9yYmlzHQAAAFhp' +
16940 'cGguT3JnIGxpYlZvcmJpcyBJIDIwMDkwNzA5AAAAAAEFdm9yYmlzKUJDVgEACAAAADFMIMWA0JBV' +
16941 'AAAQAABgJCkOk2ZJKaWUoSh5mJRISSmllMUwiZiUicUYY4wxxhhjjDHGGGOMIDRkFQAABACAKAmO' +
16942 'o+ZJas45ZxgnjnKgOWlOOKcgB4pR4DkJwvUmY26mtKZrbs4pJQgNWQUAAAIAQEghhRRSSCGFFGKI' +
16943 'IYYYYoghhxxyyCGnnHIKKqigggoyyCCDTDLppJNOOumoo4466ii00EILLbTSSkwx1VZjrr0GXXxz' +
16944 'zjnnnHPOOeecc84JQkNWAQAgAAAEQgYZZBBCCCGFFFKIKaaYcgoyyIDQkFUAACAAgAAAAABHkRRJ' +
16945 'sRTLsRzN0SRP8ixREzXRM0VTVE1VVVVVdV1XdmXXdnXXdn1ZmIVbuH1ZuIVb2IVd94VhGIZhGIZh' +
16946 'GIZh+H3f933f930gNGQVACABAKAjOZbjKaIiGqLiOaIDhIasAgBkAAAEACAJkiIpkqNJpmZqrmmb' +
16947 'tmirtm3LsizLsgyEhqwCAAABAAQAAAAAAKBpmqZpmqZpmqZpmqZpmqZpmqZpmmZZlmVZlmVZlmVZ' +
16948 'lmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZQGjIKgBAAgBAx3Ecx3EkRVIkx3IsBwgNWQUAyAAA' +
16949 'CABAUizFcjRHczTHczzHczxHdETJlEzN9EwPCA1ZBQAAAgAIAAAAAABAMRzFcRzJ0SRPUi3TcjVX' +
16950 'cz3Xc03XdV1XVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYHQkFUAAAQAACGdZpZq' +
16951 'gAgzkGEgNGQVAIAAAAAYoQhDDAgNWQUAAAQAAIih5CCa0JrzzTkOmuWgqRSb08GJVJsnuamYm3PO' +
16952 'OeecbM4Z45xzzinKmcWgmdCac85JDJqloJnQmnPOeRKbB62p0ppzzhnnnA7GGWGcc85p0poHqdlY' +
16953 'm3POWdCa5qi5FJtzzomUmye1uVSbc84555xzzjnnnHPOqV6czsE54Zxzzonam2u5CV2cc875ZJzu' +
16954 'zQnhnHPOOeecc84555xzzglCQ1YBAEAAAARh2BjGnYIgfY4GYhQhpiGTHnSPDpOgMcgppB6NjkZK' +
16955 'qYNQUhknpXSC0JBVAAAgAACEEFJIIYUUUkghhRRSSCGGGGKIIaeccgoqqKSSiirKKLPMMssss8wy' +
16956 'y6zDzjrrsMMQQwwxtNJKLDXVVmONteaec645SGultdZaK6WUUkoppSA0ZBUAAAIAQCBkkEEGGYUU' +
16957 'UkghhphyyimnoIIKCA1ZBQAAAgAIAAAA8CTPER3RER3RER3RER3RER3P8RxREiVREiXRMi1TMz1V' +
16958 'VFVXdm1Zl3Xbt4Vd2HXf133f141fF4ZlWZZlWZZlWZZlWZZlWZZlCUJDVgEAIAAAAEIIIYQUUkgh' +
16959 'hZRijDHHnINOQgmB0JBVAAAgAIAAAAAAR3EUx5EcyZEkS7IkTdIszfI0T/M00RNFUTRNUxVd0RV1' +
16960 '0xZlUzZd0zVl01Vl1XZl2bZlW7d9WbZ93/d93/d93/d93/d939d1IDRkFQAgAQCgIzmSIimSIjmO' +
16961 '40iSBISGrAIAZAAABACgKI7iOI4jSZIkWZImeZZniZqpmZ7pqaIKhIasAgAAAQAEAAAAAACgaIqn' +
16962 'mIqniIrniI4oiZZpiZqquaJsyq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7rukBo' +
16963 'yCoAQAIAQEdyJEdyJEVSJEVyJAcIDVkFAMgAAAgAwDEcQ1Ikx7IsTfM0T/M00RM90TM9VXRFFwgN' +
16964 'WQUAAAIACAAAAAAAwJAMS7EczdEkUVIt1VI11VItVVQ9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV' +
16965 'VVVVVVVVVVVV1TRN0zSB0JCVAAAZAAAjQQYZhBCKcpBCbj1YCDHmJAWhOQahxBiEpxAzDDkNInSQ' +
16966 'QSc9uJI5wwzz4FIoFURMg40lN44gDcKmXEnlOAhCQ1YEAFEAAIAxyDHEGHLOScmgRM4xCZ2UyDkn' +
16967 'pZPSSSktlhgzKSWmEmPjnKPSScmklBhLip2kEmOJrQAAgAAHAIAAC6HQkBUBQBQAAGIMUgophZRS' +
16968 'zinmkFLKMeUcUko5p5xTzjkIHYTKMQadgxAppRxTzinHHITMQeWcg9BBKAAAIMABACDAQig0ZEUA' +
16969 'ECcA4HAkz5M0SxQlSxNFzxRl1xNN15U0zTQ1UVRVyxNV1VRV2xZNVbYlTRNNTfRUVRNFVRVV05ZN' +
16970 'VbVtzzRl2VRV3RZV1bZl2xZ+V5Z13zNNWRZV1dZNVbV115Z9X9ZtXZg0zTQ1UVRVTRRV1VRV2zZV' +
16971 '17Y1UXRVUVVlWVRVWXZlWfdVV9Z9SxRV1VNN2RVVVbZV2fVtVZZ94XRVXVdl2fdVWRZ+W9eF4fZ9' +
16972 '4RhV1dZN19V1VZZ9YdZlYbd13yhpmmlqoqiqmiiqqqmqtm2qrq1bouiqoqrKsmeqrqzKsq+rrmzr' +
16973 'miiqrqiqsiyqqiyrsqz7qizrtqiquq3KsrCbrqvrtu8LwyzrunCqrq6rsuz7qizruq3rxnHrujB8' +
16974 'pinLpqvquqm6um7runHMtm0co6rqvirLwrDKsu/rui+0dSFRVXXdlF3jV2VZ921fd55b94WybTu/' +
16975 'rfvKceu60vg5z28cubZtHLNuG7+t+8bzKz9hOI6lZ5q2baqqrZuqq+uybivDrOtCUVV9XZVl3zdd' +
16976 'WRdu3zeOW9eNoqrquirLvrDKsjHcxm8cuzAcXds2jlvXnbKtC31jyPcJz2vbxnH7OuP2daOvDAnH' +
16977 'jwAAgAEHAIAAE8pAoSErAoA4AQAGIecUUxAqxSB0EFLqIKRUMQYhc05KxRyUUEpqIZTUKsYgVI5J' +
16978 'yJyTEkpoKZTSUgehpVBKa6GU1lJrsabUYu0gpBZKaS2U0lpqqcbUWowRYxAy56RkzkkJpbQWSmkt' +
16979 'c05K56CkDkJKpaQUS0otVsxJyaCj0kFIqaQSU0mptVBKa6WkFktKMbYUW24x1hxKaS2kEltJKcYU' +
16980 'U20txpojxiBkzknJnJMSSmktlNJa5ZiUDkJKmYOSSkqtlZJSzJyT0kFIqYOOSkkptpJKTKGU1kpK' +
16981 'sYVSWmwx1pxSbDWU0lpJKcaSSmwtxlpbTLV1EFoLpbQWSmmttVZraq3GUEprJaUYS0qxtRZrbjHm' +
16982 'GkppraQSW0mpxRZbji3GmlNrNabWam4x5hpbbT3WmnNKrdbUUo0txppjbb3VmnvvIKQWSmktlNJi' +
16983 'ai3G1mKtoZTWSiqxlZJabDHm2lqMOZTSYkmpxZJSjC3GmltsuaaWamwx5ppSi7Xm2nNsNfbUWqwt' +
16984 'xppTS7XWWnOPufVWAADAgAMAQIAJZaDQkJUAQBQAAEGIUs5JaRByzDkqCULMOSepckxCKSlVzEEI' +
16985 'JbXOOSkpxdY5CCWlFksqLcVWaykptRZrLQAAoMABACDABk2JxQEKDVkJAEQBACDGIMQYhAYZpRiD' +
16986 '0BikFGMQIqUYc05KpRRjzknJGHMOQioZY85BKCmEUEoqKYUQSkklpQIAAAocAAACbNCUWByg0JAV' +
16987 'AUAUAABgDGIMMYYgdFQyKhGETEonqYEQWgutddZSa6XFzFpqrbTYQAithdYySyXG1FpmrcSYWisA' +
16988 'AOzAAQDswEIoNGQlAJAHAEAYoxRjzjlnEGLMOegcNAgx5hyEDirGnIMOQggVY85BCCGEzDkIIYQQ' +
16989 'QuYchBBCCKGDEEIIpZTSQQghhFJK6SCEEEIppXQQQgihlFIKAAAqcAAACLBRZHOCkaBCQ1YCAHkA' +
16990 'AIAxSjkHoZRGKcYglJJSoxRjEEpJqXIMQikpxVY5B6GUlFrsIJTSWmw1dhBKaS3GWkNKrcVYa64h' +
16991 'pdZirDXX1FqMteaaa0otxlprzbkAANwFBwCwAxtFNicYCSo0ZCUAkAcAgCCkFGOMMYYUYoox55xD' +
16992 'CCnFmHPOKaYYc84555RijDnnnHOMMeecc845xphzzjnnHHPOOeecc44555xzzjnnnHPOOeecc845' +
16993 '55xzzgkAACpwAAAIsFFkc4KRoEJDVgIAqQAAABFWYowxxhgbCDHGGGOMMUYSYowxxhhjbDHGGGOM' +
16994 'McaYYowxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHG' +
16995 'GFtrrbXWWmuttdZaa6211lprrQBAvwoHAP8HG1ZHOCkaCyw0ZCUAEA4AABjDmHOOOQYdhIYp6KSE' +
16996 'DkIIoUNKOSglhFBKKSlzTkpKpaSUWkqZc1JSKiWlllLqIKTUWkottdZaByWl1lJqrbXWOgiltNRa' +
16997 'a6212EFIKaXWWostxlBKSq212GKMNYZSUmqtxdhirDGk0lJsLcYYY6yhlNZaazHGGGstKbXWYoy1' +
16998 'xlprSam11mKLNdZaCwDgbnAAgEiwcYaVpLPC0eBCQ1YCACEBAARCjDnnnHMQQgghUoox56CDEEII' +
16999 'IURKMeYcdBBCCCGEjDHnoIMQQgghhJAx5hx0EEIIIYQQOucchBBCCKGEUkrnHHQQQgghlFBC6SCE' +
17000 'EEIIoYRSSikdhBBCKKGEUkopJYQQQgmllFJKKaWEEEIIoYQSSimllBBCCKWUUkoppZQSQgghlFJK' +
17001 'KaWUUkIIoZRQSimllFJKCCGEUkoppZRSSgkhhFBKKaWUUkopIYQSSimllFJKKaUAAIADBwCAACPo' +
17002 'JKPKImw04cIDUGjISgCADAAAcdhq6ynWyCDFnISWS4SQchBiLhFSijlHsWVIGcUY1ZQxpRRTUmvo' +
17003 'nGKMUU+dY0oxw6yUVkookYLScqy1dswBAAAgCAAwECEzgUABFBjIAIADhAQpAKCwwNAxXAQE5BIy' +
17004 'CgwKx4Rz0mkDABCEyAyRiFgMEhOqgaJiOgBYXGDIB4AMjY20iwvoMsAFXdx1IIQgBCGIxQEUkICD' +
17005 'E2544g1PuMEJOkWlDgIAAAAA4AAAHgAAkg0gIiKaOY4Ojw+QEJERkhKTE5QAAAAAALABgA8AgCQF' +
17006 'iIiIZo6jw+MDJERkhKTE5AQlAAAAAAAAAAAACAgIAAAAAAAEAAAACAhPZ2dTAAQYOwAAAAAAAOGp' +
17007 'bkoCAAAAmc74DRgyNjM69TAzOTk74dnLubewsbagmZiNp4d0KbsExSY/I3XUTwJgkeZdn1HY4zoj' +
17008 '33/q9DFtv3Ui1/jmx7lCUtPt18/sYf9MkgAsAGRBd3gMGP4sU+qCPYBy9VrA3YqJosW3W2/ef1iO' +
17009 '/u3cg8ZG/57jU+pPmbGEJUgkfnaI39DbPqxddZphbMRmCc5rKlkUMkyx8iIoug5dJv1OYH9a59c+' +
17010 '3Gevqc7Z2XFdDjL/qHztRfjWEWxJ/aiGezjohu9HsCZdQBKbiH0VtU/3m85lDG2T/+xkZcYnX+E+' +
17011 'aqzv/xTgOoTFG+x7SNqQ4N+oAABSxuVXw77Jd5bmmTmuJakX7509HH0kGYKvARPpwfOSAPySPAc2' +
17012 'EkneDwB2HwAAJlQDYK5586N79GJCjx4+p6aDUd27XSvRyXLJkIC5YZ1jLv5lpOhZTz0s+DmnF1di' +
17013 'ptrnM6UDgIW11Xh8cHTd0/SmbgOAdxcyWwMAAGIrZ3fNSfZbzKiYrK4+tPqtnMVLOeWOG2kVvUY+' +
17014 'p2PJ/hkCl5aFRO4TLGYPZcIU3vYM1hohS4jHFlnyW/2T5J7kGsShXWT8N05V+3C/GPqJ1QdWisGP' +
17015 'xEzHqXISBPIinWDUt7IeJv/f5OtzBxpTzZZQ+CYEhHXfqG4aABQli72GJhN4oJv+hXcApAJSErAW' +
17016 '8G2raAX4NUcABnVt77CzZAB+LsHcVe+Q4h+QB1wh/ZrJTPxSBdI8mgTeAdTsQOoFUEng9BHcVPhx' +
17017 'SRRYkKWZJXOFYP6V4AEripJoEjXgA2wJRZHSExmJDm8F0A6gEXsg5a4ZsALItrMB7+fh7UKLvYWS' +
17018 'dtsDwFf1mzYzS1F82N1h2Oyt2e76B1QdS0SAsQigLPMOgJS9JRC7hFXA6kUsLFNKD5cA5cTRvgSq' +
17019 'Pc3Fl99xW3QTi/MHR8DEm6WnvaVQATwRqRKjywQ9BrrhugR2AKTsPQeQckrAOgDOhbTESyrXQ50C' +
17020 'kNpXdtWjW7W2/3UjeX3U95gIdalfRAoAmqUEiwp53hCdcCwlg47fcbfzlmQMAgaBkh7c+fcDgF+i' +
17021 'fwDXfzegLPcLYJsAAJQArTXjnh/uXGy3v1Hk3pV6/3t5ruW81f6prfbM2Q3WNVy98BwUtbCwhFhA' +
17022 'WuPev6Oe/4ZaFQUcgKrVs4defzh1TADA1DEh5b3VlDaECw5b+bPfkKos3tIAue3vJZOih3ga3l6O' +
17023 '3PSfIkrLv0PAS86PPdL7g8oc2KteNFKKzKRehOv2gJoFLBPXmaXvPBQILgJon0bbWBszrYZYYwE7' +
17024 'jl2j+vTdU7Vpk21LiU0QajPkywAAHqbUC0/YsYOdb4e6BOp7E0cCi04Ao/TgD8ZVAMid6h/A8IeB' +
17025 'Nkp6/xsAACZELEYIk+yvI6Qz1NN6lIftB/6IMWjWJNOqPTMedAmyaj6Es0QBklJpiSWWHnQ2CoYb' +
17026 'GWAmt+0gLQBFKCBnp2QUUQZ/1thtZDBJUpFWY82z34ocorB62oX7qB5y0oPAv/foxH25wVmgIHf2' +
17027 'xFOr8leZcBq1Kx3ZvCq9Bga639AxuHuPNL/71YCF4EywJpqHFAX6XF0sjVbuANnvvdLcrufYwOM/' +
17028 'iDa6iA468AYAAB6mNBMXcgTD8HSRqJ4vw8CjAlCEPACASlX/APwPOJKl9xQAAAPmnev2eWp33Xgy' +
17029 'w3Dvfz6myGk3oyP8YTKsCOvzAgALQi0o1c6Nzs2O2Pg2h4ACIJAgAGP0aNn5x0BDgVfH7u2TtyfD' +
17030 'cRIuYAyQhBF/lvSRAttgA6TPbWZA9gaUrZWAUEAA+Dx47Q3/r87HxUUqZmB0BmUuMlojFjHt1gDu' +
17031 'nnvuX8MImsjSq5WkzSzGS62OEIlOufWWezxWpv6FBgDgJVltfXFYtNAAnqU0xQoD0YLiXo5cF5QV' +
17032 '4CnY1tBLAkZCOABAhbk/AM+/AwSCCdlWAAAMcFjS7owb8GVDzveDiZvznbt2tF4bL5odN1YKl88T' +
17033 'AEABCZvufq9YCTBtMwVAQUEAwGtNltzSaHvADYC3TxLVjqiRA+OZAMhzcqEgRcAOwoCgvdTxsTHL' +
17034 'QEF6+oOb2+PAI8ciPQcXg7pOY+LjxQSv2fjmFuj34gGwz310/bGK6z3xgT887eomWULEaDd04wHe' +
17035 'tYxdjcgV2SxvSwn0VoZXJRqkRC5ASQ/muVoAUsX7AgAQMBNaVwAAlABRxT/1PmfqLqSRNDbhXb07' +
17036 'berpB3b94jpuWEZjBCD2OcdXFpCKEgCDfcFPMw8AAADUwT4lnUm50lmwrpMMhPQIKj6u0E8fr2vG' +
17037 'BngMNdIlrZsigjahljud6AFVg+tzXwUnXL3TJLpajaWKA4VAAAAMiFfqJgKAZ08XrtS3dxtQNYcp' +
17038 'PvYEG8ClvrQRJgBephwnNWJjtGqmp6VEPSvBe7EBiU3qgJbQAwD4Le8LAMDMhHbNAAAlgK+tFs5O' +
17039 '+YyJc9yCnJa3rxLPulGnxwsXV9Fsk2k4PisCAHC8FkwbGE9gJQAAoMnyksj0CdFMZLLgoz8M+Fxz' +
17040 'iwYBgIx+zHiCBAKAlBKNpF1sO9JpVcyEi9ar15YlHgrut5fPJnkdJ6vEwZPyAHQBIEDUrlMcBAAd' +
17041 '2KAS0Qq+JwRsE4AJZtMnAD6GnOYwYlOIZvtzUNdjreB7fiMkWI0CmBB6AIAKc38A9osEFlTSGECB' +
17042 '+cbeRDC0aRpLHqNPplcK/76Lxn2rpmqyXsYJWRi/FQAAAKBQk9MCAOibrQBQADCDsqpooPutd+05' +
17043 'Ce9g6iEdiYXgVmQAI4+4wskEBEiBloNQ6Ki0/KTQ0QjWfjxzi+AeuXKoMjEVfQOZzr0y941qLgM2' +
17044 'AExvbZOqcxZ6J6krlrj4y2j9AdgKDx6GnJsVLhbc42uq584+ouSdNBpoCiCVHrz+WzUA/DDtD8AT' +
17045 'gA3h0lMCAAzcFv+S+fSSNkeYWlTpb34mf2RfmqqJeMeklhHAfu7VoAEACgAApKRktL+KkQDWMwYC' +
17046 'UAAAAHCKsp80xhp91UjqQBw3x45cetqkjQEyu3G9B6N+R650Uq8OVig7wOm6Wun0ea4lKDPoabJs' +
17047 '6aLqgbhPzpv4KR4iODilw88ZpY7q1IOMcbASAOAVtmcCnobcrkG4KGS7/ZnskVWRNF9J0RUHKOnB' +
17048 'yy9WA8Dv6L4AAARMCQUA4GritfVM2lcZfH3Q3T/vZ47J2YHhcmBazjfdyuV25gLAzrc0cwAAAAAY' +
17049 'Ch6PdwAAAGyWjFW4yScjaWa2mGcofHxWxewKALglWBpLUvwwk+UOh5eNGyUOs1/EF+pZr+ud5Ozo' +
17050 'GwYdAABg2p52LiSgAY/ZVlOmilEgHn6G3OcwYjzI7vOj1t6xsx4S3lBY96EUQBF6AIBAmPYH4PoG' +
17051 'YCoJAADWe+OZJZi7/x76/yH7Lzf9M5XzRKnFPmveMsilQHwVAAAAAKB3LQD8PCIAAADga0QujBLy' +
17052 'wzeJ4a6Z/ERVBAUlAEDqvoM7BQBAuAguzFqILtmjH3Kd4wfKobnOhA3z85qWoRPm9hwoOHoDAAlC' +
17053 'bwDAA56FHAuXflHo3fe2ttG9XUDeA9YmYCBQ0oPr/1QC8IvuCwAAApbUAQCK22MmE3O78VAbHQT9' +
17054 'PIPNoT9zNc3l2Oe7TAVLANBufT8MAQAAAGzT4PS8AQAAoELGHb2uaCwwEv1EWhFriUkbAaAZ27/f' +
17055 'VZnTZXbWz3BwWpjUaMZKRj7dZ0J//gUeTdpVEwAAZOFsNxKAjQSgA+ABPoY8Jj5y2wje81jsXc/1' +
17056 'TOQWTDYZBmAkNDiqVwuA2NJ9AQAAEBKAt9Vrsfs/2N19MO91S9rd8EHTZHnzC5MYmfQEACy/FBcA' +
17057 'AADA5c4gi4z8RANs/m6FNXVo9DV46JG1BBDukqlw/Va5G7QbuGVSI+2aZaoLXJrdVj2zlC9Z5QEA' +
17058 'EFz/5QzgVZwAAAAA/oXcxyC6WfTu+09Ve/c766J4VTAGUFmA51+VANKi/QPoPwYgYAkA715OH4S0' +
17059 's5KDHvj99MMq8TPFc3roKZnGOoT1bmIhVgc7XAMBAAAAAMAW1VbQw3gapzOpJd+Kd2fc4iSO62fJ' +
17060 'v9+movui1wUNPAj059N3OVxzk4gV73PmE8FIA2F5mRq37Evc76vLXfF4rD5UJJAw46hW6LZCb5sN' +
17061 'Ldx+kzMCAAB+hfy95+965ZCLP7B3/VlTHCvDEKtQhTm4KiCgAEAbrfbWTPssAAAAXpee1tVrozYY' +
17062 'n41wD1aeYtkKfswN5/SXPO0JDnhO/4laUortv/s412fybe/nONdncoCHnBVliu0CQGBWlPY/5Kwo' +
17063 'm2L/kruPM6Q7oz4tvDQy+bZ3HzOi+gNHA4DZEgA=' +
17064 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050017065
Andrew Geisslerd27bb132018-05-24 11:07:27 -070017066lib.resource.add(
17067 'hterm/concat/date', 'text/plain',
17068 'Tue, 25 Apr 2017 15:12:45 +0000' +
17069 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050017070
Andrew Geisslerd27bb132018-05-24 11:07:27 -070017071lib.resource.add(
17072 'hterm/changelog/version', 'text/plain',
17073 '1.62' +
17074 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050017075
Andrew Geisslerd27bb132018-05-24 11:07:27 -070017076lib.resource.add(
17077 'hterm/changelog/date', 'text/plain',
17078 '2017-04-17' +
17079 '');
Iftekharul Islamdb28a382017-11-02 13:16:17 -050017080
Andrew Geisslerd27bb132018-05-24 11:07:27 -070017081lib.resource.add(
17082 'hterm/git/HEAD', 'text/plain',
17083 'git rev-parse HEAD' +
17084 '');