text: // This file was generated by libdot/bin/concat.sh.
  // It has been marked read-only for your safety.  Rather
  // than edit it directly, please modify one of these source
  // files...
  //
  // libdot/js/lib.js
  // libdot/js/lib_colors.js
  // libdot/js/lib_f.js
  // libdot/js/lib_utf8.js
  // hterm/js/hterm.js
  // hterm/js/hterm_parser.js
  // hterm/js/hterm_text_attributes.js
  // hterm/js/hterm_vt.js
  // hterm/js/hterm_vt_character_map.js
  //
  
  // SOURCE FILE: libdot/js/lib.js
  // Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  'use strict';
  
  if (typeof lib != 'undefined')
    throw new Error('Global "lib" object already exists.');
  
  var lib = {};
  
  /**
   * Map of "dependency" to ["source", ...].
   *
   * Each dependency is a object name, like "lib.fs", "source" is the url that
   * depends on the object.
   */
  lib.runtimeDependencies_ = {};
  
  /**
   * List of functions that need to be invoked during library initialization.
   *
   * Each element in the initCallbacks_ array is itself a two-element array.
   * Element 0 is a short string describing the owner of the init routine, useful
   * for debugging.  Element 1 is the callback function.
   */
  lib.initCallbacks_ = [];
  
  /**
   * Records a runtime dependency.
   *
   * This can be useful when you want to express a run-time dependency at
   * compile time.  It is not intended to be a full-fledged library system or
   * dependency tracker.  It's just there to make it possible to debug the
   * deps without running all the code.
   *
   * Object names are specified as strings.  For example...
   *
   *     lib.rtdep('lib.colors', 'lib.PreferenceManager');
   *
   * Object names need not be rooted by 'lib'.  You may use this to declare a
   * dependency on any object.
   *
   * The client program may call lib.ensureRuntimeDependencies() at startup in
   * order to ensure that all runtime dependencies have been met.
   *
   * @param {string} var_args One or more objects specified as strings.
   */
  lib.rtdep = function(var_args) {
    var source;
  
    try {
      throw new Error();
    } catch (ex) {
      var stackArray = ex.stack.split('\n');
      // In Safari, the resulting stackArray will only have 2 elements and the
      // individual strings are formatted differently.
      if (stackArray.length >= 3) {
        source = stackArray[2].replace(/^\s*at\s+/, '');
      } else {
        source = stackArray[1].replace(/^\s*global code@/, '');
      }
    }
  
    for (var i = 0; i < arguments.length; i++) {
      var path = arguments[i];
      if (path instanceof Array) {
        lib.rtdep.apply(lib, path);
      } else {
        var ary = this.runtimeDependencies_[path];
        if (!ary)
          ary = this.runtimeDependencies_[path] = [];
        ary.push(source);
      }
    }
  };
  
  /**
   * Ensures that all runtime dependencies are met, or an exception is thrown.
   *
   * Every unmet runtime dependency will be logged to the JS console.  If at
   * least one dependency is unmet this will raise an exception.
   */
  lib.ensureRuntimeDependencies_ = function() {
    var passed = true;
  
    for (var path in lib.runtimeDependencies_) {
      var sourceList = lib.runtimeDependencies_[path];
      var names = path.split('.');
  
      // In a document context 'window' is the global object.  In a worker it's
      // called 'self'.
      var obj = (window || self);
      for (var i = 0; i < names.length; i++) {
        if (!(names[i] in obj)) {
          console.warn('Missing "' + path + '" is needed by', sourceList);
          passed = false;
          break;
        }
  
        obj = obj[names[i]];
      }
    }
  
    if (!passed)
      throw new Error('Failed runtime dependency check');
  };
  
  /**
   * Register an initialization function.
   *
   * The initialization functions are invoked in registration order when
   * lib.init() is invoked.  Each function will receive a single parameter, which
   * is a function to be invoked when it completes its part of the initialization.
   *
   * @param {string} name A short descriptive name of the init routine useful for
   *     debugging.
   * @param {function(function)} callback The initialization function to register.
   * @return {function} The callback parameter.
   */
  lib.registerInit = function(name, callback) {
    lib.initCallbacks_.push([name, callback]);
    return callback;
  };
  
  /**
   * Initialize the library.
   *
   * This will ensure that all registered runtime dependencies are met, and
   * invoke any registered initialization functions.
   *
   * Initialization is asynchronous.  The library is not ready for use until
   * the onInit function is invoked.
   *
   * @param {function()} onInit The function to invoke when initialization is
   *     complete.
   * @param {function(*)} opt_logFunction An optional function to send
   *     initialization related log messages to.
   */
  lib.init = function(onInit, opt_logFunction) {
    var ary = lib.initCallbacks_;
  
    var initNext = function() {
      if (ary.length) {
        var rec = ary.shift();
        if (opt_logFunction)
          opt_logFunction('init: ' + rec[0]);
        rec[1](lib.f.alarm(initNext));
      } else {
        onInit();
      }
    };
  
    if (typeof onInit != 'function')
      throw new Error('Missing or invalid argument: onInit');
  
    lib.ensureRuntimeDependencies_();
  
    setTimeout(initNext, 0);
  };
  // SOURCE FILE: libdot/js/lib_colors.js
  // Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  'use strict';
  
  /**
   * Namespace for color utilities.
   */
  lib.colors = {};
  
  /**
   * First, some canned regular expressions we're going to use in this file.
   *
   *
   *                              BRACE YOURSELF
   *
   *                                 ,~~~~.
   *                                 |>_< ~~
   *                                3`---'-/.
   *                                3:::::\v\
   *                               =o=:::::\,\
   *                                | :::::\,,\
   *
   *                        THE REGULAR EXPRESSIONS
   *                               ARE COMING.
   *
   * There's no way to break long RE literals in JavaScript.  Fix that why don't
   * you?  Oh, and also there's no way to write a string that doesn't interpret
   * escapes.
   *
   * Instead, we stoop to this .replace() trick.
   */
  lib.colors.re_ = {
    // CSS hex color, #RGB.
    hex16: /#([a-f0-9])([a-f0-9])([a-f0-9])/i,
  
    // CSS hex color, #RRGGBB.
    hex24: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i,
  
    // CSS rgb color, rgb(rrr,ggg,bbb).
    rgb: new RegExp(
        ('^/s*rgb/s*/(/s*(/d{1,3})/s*,/s*(/d{1,3})/s*,' +
         '/s*(/d{1,3})/s*/)/s*$'
         ).replace(/\//g, '\\'), 'i'),
  
    // CSS rgb color, rgb(rrr,ggg,bbb,aaa).
    rgba: new RegExp(
        ('^/s*rgba/s*' +
         '/(/s*(/d{1,3})/s*,/s*(/d{1,3})/s*,/s*(/d{1,3})/s*' +
         '(?:,/s*(/d+(?:/./d+)?)/s*)/)/s*$'
         ).replace(/\//g, '\\'), 'i'),
  
    // Either RGB or RGBA.
    rgbx: new RegExp(
        ('^/s*rgba?/s*' +
         '/(/s*(/d{1,3})/s*,/s*(/d{1,3})/s*,/s*(/d{1,3})/s*' +
         '(?:,/s*(/d+(?:/./d+)?)/s*)?/)/s*$'
         ).replace(/\//g, '\\'), 'i'),
  
    // An X11 "rgb:dddd/dddd/dddd" value.
    x11rgb: /^\s*rgb:([a-f0-9]{1,4})\/([a-f0-9]{1,4})\/([a-f0-9]{1,4})\s*$/i,
  
    // English color name.
    name: /[a-z][a-z0-9\s]+/,
  };
  
  /**
   * Convert a CSS rgb(ddd,ddd,ddd) color value into an X11 color value.
   *
   * Other CSS color values are ignored to ensure sanitary data handling.
   *
   * Each 'ddd' component is a one byte value specified in decimal.
   *
   * @param {string} value The CSS color value to convert.
   * @return {string} The X11 color value or null if the value could not be
   *     converted.
   */
  lib.colors.rgbToX11 = function(value) {
    function scale(v) {
      v = (Math.min(v, 255) * 257).toString(16);
      return lib.f.zpad(v, 4);
    }
  
    var ary = value.match(lib.colors.re_.rgbx);
    if (!ary)
      return null;
  
    return 'rgb:' + scale(ary[1]) + '/' + scale(ary[2]) + '/' + scale(ary[3]);
  };
  
  /**
   * Convert a legacy X11 colover value into an CSS rgb(...) color value.
   *
   * They take the form:
   * 12 bit: #RGB          -> #R000G000B000
   * 24 bit: #RRGGBB       -> #RR00GG00BB00
   * 36 bit: #RRRGGGBBB    -> #RRR0GGG0BBB0
   * 48 bit: #RRRRGGGGBBBB
   * These are the most significant bits.
   *
   * Truncate values back down to 24 bit since that's all CSS supports.
   */
  lib.colors.x11HexToCSS = function(v) {
    if (!v.startsWith('#'))
      return null;
    // Strip the leading # off.
    v = v.substr(1);
  
    // Reject unknown sizes.
    if ([3, 6, 9, 12].indexOf(v.length) == -1)
      return null;
  
    // Reject non-hex values.
    if (v.match(/[^a-f0-9]/i))
      return null;
  
    // Split the colors out.
    var size = v.length / 3;
    var r = v.substr(0, size);
    var g = v.substr(size, size);
    var b = v.substr(size + size, size);
  
    // Normalize to 16 bits.
    function norm16(v) {
      v = parseInt(v, 16);
      return size == 2 ? v :         // 16 bit
             size == 1 ? v << 4 :    // 8 bit
             v >> (4 * (size - 2));  // 24 or 32 bit
    }
    return lib.colors.arrayToRGBA([r, g, b].map(norm16));
  };
  
  /**
   * Convert an X11 color value into an CSS rgb(...) color value.
   *
   * The X11 value may be an X11 color name, or an RGB value of the form
   * rgb:hhhh/hhhh/hhhh.  If a component value is less than 4 digits it is
   * padded out to 4, then scaled down to fit in a single byte.
   *
   * @param {string} value The X11 color value to convert.
   * @return {string} The CSS color value or null if the value could not be
   *     converted.
   */
  lib.colors.x11ToCSS = function(v) {
    function scale(v) {
      // Pad out values with less than four digits.  This padding (probably)
      // matches xterm.  It's difficult to say for sure since xterm seems to
      // arrive at a padded value and then perform some combination of
      // gamma correction, color space transformation, and quantization.
  
      if (v.length == 1) {
        // Single digits pad out to four by repeating the character.  "f" becomes
        // "ffff".  Scaling down a hex value of this pattern by 257 is the same
        // as cutting off one byte.  We skip the middle step and just double
        // the character.
        return parseInt(v + v, 16);
      }
  
      if (v.length == 2) {
        // Similar deal here.  X11 pads two digit values by repeating the
        // byte (or scale up by 257).  Since we're going to scale it back
        // down anyway, we can just return the original value.
        return parseInt(v, 16);
      }
  
      if (v.length == 3) {
        // Three digit values seem to be padded by repeating the final digit.
        // e.g. 10f becomes 10ff.
        v = v + v.substr(2);
      }
  
      // Scale down the 2 byte value.
      return Math.round(parseInt(v, 16) / 257);
    }
  
    var ary = v.match(lib.colors.re_.x11rgb);
    if (!ary) {
      // Handle the legacy format.
      if (v.startsWith('#'))
        return lib.colors.x11HexToCSS(v);
      else
        return lib.colors.nameToRGB(v);
    }
  
    ary.splice(0, 1);
    return lib.colors.arrayToRGBA(ary.map(scale));
  };
  
  /**
   * Converts one or more CSS '#RRGGBB' color values into their rgb(...)
   * form.
   *
   * Arrays are converted in place. If a value cannot be converted, it is
   * replaced with null.
   *
   * @param {string|Array.<string>} A single RGB value or array of RGB values to
   *     convert.
   * @return {string|Array.<string>} The converted value or values.
   */
  lib.colors.hexToRGB = function(arg) {
    var hex16 = lib.colors.re_.hex16;
    var hex24 = lib.colors.re_.hex24;
  
    function convert(hex) {
      if (hex.length == 4) {
        hex = hex.replace(hex16, function(h, r, g, b) {
          return "#" + r + r + g + g + b + b;
        });
      }
      var ary = hex.match(hex24);
      if (!ary)
        return null;
  
      return 'rgb(' + parseInt(ary[1], 16) + ', ' +
          parseInt(ary[2], 16) + ', ' +
          parseInt(ary[3], 16) + ')';
    }
  
    if (arg instanceof Array) {
      for (var i = 0; i < arg.length; i++) {
        arg[i] = convert(arg[i]);
      }
    } else {
      arg = convert(arg);
    }
  
    return arg;
  };
  
  /**
   * Converts one or more CSS rgb(...) forms into their '#RRGGBB' color values.
   *
   * If given an rgba(...) form, the alpha field is thrown away.
   *
   * Arrays are converted in place. If a value cannot be converted, it is
   * replaced with null.
   *
   * @param {string|Array.<string>} A single rgb(...) value or array of rgb(...)
   *     values to convert.
   * @return {string|Array.<string>} The converted value or values.
   */
  lib.colors.rgbToHex = function(arg) {
    function convert(rgb) {
      var ary = lib.colors.crackRGB(rgb);
      if (!ary)
        return null;
      return '#' + lib.f.zpad(((parseInt(ary[0]) << 16) |
                               (parseInt(ary[1]) <<  8) |
                               (parseInt(ary[2]) <<  0)).toString(16), 6);
    }
  
    if (arg instanceof Array) {
      for (var i = 0; i < arg.length; i++) {
        arg[i] = convert(arg[i]);
      }
    } else {
      arg = convert(arg);
    }
  
    return arg;
  };
  
  /**
   * Take any valid css color definition and turn it into an rgb or rgba value.
   *
   * Returns null if the value could not be normalized.
   */
  lib.colors.normalizeCSS = function(def) {
    if (def.startsWith('#'))
      return lib.colors.hexToRGB(def);
  
    if (lib.colors.re_.rgbx.test(def))
      return def;
  
    return lib.colors.nameToRGB(def);
  };
  
  /**
   * Convert a 3 or 4 element array into an rgba(...) string.
   */
  lib.colors.arrayToRGBA = function(ary) {
    var alpha = (ary.length > 3) ? ary[3] : 1;
    return 'rgba(' + ary[0] + ', ' + ary[1] + ', ' + ary[2] + ', ' + alpha + ')';
  };
  
  /**
   * Overwrite the alpha channel of an rgb/rgba color.
   */
  lib.colors.setAlpha = function(rgb, alpha) {
    var ary = lib.colors.crackRGB(rgb);
    ary[3] = alpha;
    return lib.colors.arrayToRGBA(ary);
  };
  
  /**
   * Mix a percentage of a tint color into a base color.
   */
  lib.colors.mix = function(base, tint, percent) {
    var ary1 = lib.colors.crackRGB(lib.colors.normalizeCSS(base));
    var ary2 = lib.colors.crackRGB(lib.colors.normalizeCSS(tint));
  
    for (var i = 0; i < 4; ++i) {
      var diff = ary2[i] - ary1[i];
      ary1[i] = Math.round(parseInt(ary1[i]) + diff * percent);
    }
  
    return lib.colors.arrayToRGBA(ary1);
  };
  
  /**
   * Split an rgb/rgba color into an array of its components.
   *
   * On success, a 4 element array will be returned.  For rgb values, the alpha
   * will be set to 1.
   */
  lib.colors.crackRGB = function(color) {
    if (color.startsWith('rgba')) {
      var ary = color.match(lib.colors.re_.rgba);
      if (ary) {
        ary.shift();
        return ary;
      }
    } else {
      var ary = color.match(lib.colors.re_.rgb);
      if (ary) {
        ary.shift();
        ary.push('1');
        return ary;
      }
    }
  
    console.error('Couldn\'t crack: ' + color);
    return null;
  };
  
  /**
   * Convert an X11 color name into a CSS rgb(...) value.
   *
   * Names are stripped of spaces and converted to lowercase.  If the name is
   * unknown, null is returned.
   *
   * This list of color name to RGB mapping is derived from the stock X11
   * rgb.txt file.
   *
   * @param {string} name The color name to convert.
   * @return {string} The corresponding CSS rgb(...) value.
   */
  lib.colors.nameToRGB = function(name) {
    if (name in lib.colors.colorNames)
      return lib.colors.colorNames[name];
  
    name = name.toLowerCase();
    if (name in lib.colors.colorNames)
      return lib.colors.colorNames[name];
  
    name = name.replace(/\s+/g, '');
    if (name in lib.colors.colorNames)
      return lib.colors.colorNames[name];
  
    return null;
  };
  
  /**
   * The stock color palette.
   */
  lib.colors.stockColorPalette = lib.colors.hexToRGB
    ([// The "ANSI 16"...
      '#000000', '#CC0000', '#4E9A06', '#C4A000',
      '#3465A4', '#75507B', '#06989A', '#D3D7CF',
      '#555753', '#EF2929', '#00BA13', '#FCE94F',
      '#729FCF', '#F200CB', '#00B5BD', '#EEEEEC',
  
      // The 6x6 color cubes...
      '#000000', '#00005F', '#000087', '#0000AF', '#0000D7', '#0000FF',
      '#005F00', '#005F5F', '#005F87', '#005FAF', '#005FD7', '#005FFF',
      '#008700', '#00875F', '#008787', '#0087AF', '#0087D7', '#0087FF',
      '#00AF00', '#00AF5F', '#00AF87', '#00AFAF', '#00AFD7', '#00AFFF',
      '#00D700', '#00D75F', '#00D787', '#00D7AF', '#00D7D7', '#00D7FF',
      '#00FF00', '#00FF5F', '#00FF87', '#00FFAF', '#00FFD7', '#00FFFF',
  
      '#5F0000', '#5F005F', '#5F0087', '#5F00AF', '#5F00D7', '#5F00FF',
      '#5F5F00', '#5F5F5F', '#5F5F87', '#5F5FAF', '#5F5FD7', '#5F5FFF',
      '#5F8700', '#5F875F', '#5F8787', '#5F87AF', '#5F87D7', '#5F87FF',
      '#5FAF00', '#5FAF5F', '#5FAF87', '#5FAFAF', '#5FAFD7', '#5FAFFF',
      '#5FD700', '#5FD75F', '#5FD787', '#5FD7AF', '#5FD7D7', '#5FD7FF',
      '#5FFF00', '#5FFF5F', '#5FFF87', '#5FFFAF', '#5FFFD7', '#5FFFFF',
  
      '#870000', '#87005F', '#870087', '#8700AF', '#8700D7', '#8700FF',
      '#875F00', '#875F5F', '#875F87', '#875FAF', '#875FD7', '#875FFF',
      '#878700', '#87875F', '#878787', '#8787AF', '#8787D7', '#8787FF',
      '#87AF00', '#87AF5F', '#87AF87', '#87AFAF', '#87AFD7', '#87AFFF',
      '#87D700', '#87D75F', '#87D787', '#87D7AF', '#87D7D7', '#87D7FF',
      '#87FF00', '#87FF5F', '#87FF87', '#87FFAF', '#87FFD7', '#87FFFF',
  
      '#AF0000', '#AF005F', '#AF0087', '#AF00AF', '#AF00D7', '#AF00FF',
      '#AF5F00', '#AF5F5F', '#AF5F87', '#AF5FAF', '#AF5FD7', '#AF5FFF',
      '#AF8700', '#AF875F', '#AF8787', '#AF87AF', '#AF87D7', '#AF87FF',
      '#AFAF00', '#AFAF5F', '#AFAF87', '#AFAFAF', '#AFAFD7', '#AFAFFF',
      '#AFD700', '#AFD75F', '#AFD787', '#AFD7AF', '#AFD7D7', '#AFD7FF',
      '#AFFF00', '#AFFF5F', '#AFFF87', '#AFFFAF', '#AFFFD7', '#AFFFFF',
  
      '#D70000', '#D7005F', '#D70087', '#D700AF', '#D700D7', '#D700FF',
      '#D75F00', '#D75F5F', '#D75F87', '#D75FAF', '#D75FD7', '#D75FFF',
      '#D78700', '#D7875F', '#D78787', '#D787AF', '#D787D7', '#D787FF',
      '#D7AF00', '#D7AF5F', '#D7AF87', '#D7AFAF', '#D7AFD7', '#D7AFFF',
      '#D7D700', '#D7D75F', '#D7D787', '#D7D7AF', '#D7D7D7', '#D7D7FF',
      '#D7FF00', '#D7FF5F', '#D7FF87', '#D7FFAF', '#D7FFD7', '#D7FFFF',
  
      '#FF0000', '#FF005F', '#FF0087', '#FF00AF', '#FF00D7', '#FF00FF',
      '#FF5F00', '#FF5F5F', '#FF5F87', '#FF5FAF', '#FF5FD7', '#FF5FFF',
      '#FF8700', '#FF875F', '#FF8787', '#FF87AF', '#FF87D7', '#FF87FF',
      '#FFAF00', '#FFAF5F', '#FFAF87', '#FFAFAF', '#FFAFD7', '#FFAFFF',
      '#FFD700', '#FFD75F', '#FFD787', '#FFD7AF', '#FFD7D7', '#FFD7FF',
      '#FFFF00', '#FFFF5F', '#FFFF87', '#FFFFAF', '#FFFFD7', '#FFFFFF',
  
      // The greyscale ramp...
      '#080808', '#121212', '#1C1C1C', '#262626', '#303030', '#3A3A3A',
      '#444444', '#4E4E4E', '#585858', '#626262', '#6C6C6C', '#767676',
      '#808080', '#8A8A8A', '#949494', '#9E9E9E', '#A8A8A8', '#B2B2B2',
      '#BCBCBC', '#C6C6C6', '#D0D0D0', '#DADADA', '#E4E4E4', '#EEEEEE'
     ]);
  
  /**
   * The current color palette, possibly with user changes.
   */
  lib.colors.colorPalette = lib.colors.stockColorPalette;
  
  /**
   * Named colors according to the stock X11 rgb.txt file.
   */
  lib.colors.colorNames = {
    "aliceblue": "rgb(240, 248, 255)",
    "antiquewhite": "rgb(250, 235, 215)",
    "antiquewhite1": "rgb(255, 239, 219)",
    "antiquewhite2": "rgb(238, 223, 204)",
    "antiquewhite3": "rgb(205, 192, 176)",
    "antiquewhite4": "rgb(139, 131, 120)",
    "aquamarine": "rgb(127, 255, 212)",
    "aquamarine1": "rgb(127, 255, 212)",
    "aquamarine2": "rgb(118, 238, 198)",
    "aquamarine3": "rgb(102, 205, 170)",
    "aquamarine4": "rgb(69, 139, 116)",
    "azure": "rgb(240, 255, 255)",
    "azure1": "rgb(240, 255, 255)",
    "azure2": "rgb(224, 238, 238)",
    "azure3": "rgb(193, 205, 205)",
    "azure4": "rgb(131, 139, 139)",
    "beige": "rgb(245, 245, 220)",
    "bisque": "rgb(255, 228, 196)",
    "bisque1": "rgb(255, 228, 196)",
    "bisque2": "rgb(238, 213, 183)",
    "bisque3": "rgb(205, 183, 158)",
    "bisque4": "rgb(139, 125, 107)",
    "black": "rgb(0, 0, 0)",
    "blanchedalmond": "rgb(255, 235, 205)",
    "blue": "rgb(0, 0, 255)",
    "blue1": "rgb(0, 0, 255)",
    "blue2": "rgb(0, 0, 238)",
    "blue3": "rgb(0, 0, 205)",
    "blue4": "rgb(0, 0, 139)",
    "blueviolet": "rgb(138, 43, 226)",
    "brown": "rgb(165, 42, 42)",
    "brown1": "rgb(255, 64, 64)",
    "brown2": "rgb(238, 59, 59)",
    "brown3": "rgb(205, 51, 51)",
    "brown4": "rgb(139, 35, 35)",
    "burlywood": "rgb(222, 184, 135)",
    "burlywood1": "rgb(255, 211, 155)",
    "burlywood2": "rgb(238, 197, 145)",
    "burlywood3": "rgb(205, 170, 125)",
    "burlywood4": "rgb(139, 115, 85)",
    "cadetblue": "rgb(95, 158, 160)",
    "cadetblue1": "rgb(152, 245, 255)",
    "cadetblue2": "rgb(142, 229, 238)",
    "cadetblue3": "rgb(122, 197, 205)",
    "cadetblue4": "rgb(83, 134, 139)",
    "chartreuse": "rgb(127, 255, 0)",
    "chartreuse1": "rgb(127, 255, 0)",
    "chartreuse2": "rgb(118, 238, 0)",
    "chartreuse3": "rgb(102, 205, 0)",
    "chartreuse4": "rgb(69, 139, 0)",
    "chocolate": "rgb(210, 105, 30)",
    "chocolate1": "rgb(255, 127, 36)",
    "chocolate2": "rgb(238, 118, 33)",
    "chocolate3": "rgb(205, 102, 29)",
    "chocolate4": "rgb(139, 69, 19)",
    "coral": "rgb(255, 127, 80)",
    "coral1": "rgb(255, 114, 86)",
    "coral2": "rgb(238, 106, 80)",
    "coral3": "rgb(205, 91, 69)",
    "coral4": "rgb(139, 62, 47)",
    "cornflowerblue": "rgb(100, 149, 237)",
    "cornsilk": "rgb(255, 248, 220)",
    "cornsilk1": "rgb(255, 248, 220)",
    "cornsilk2": "rgb(238, 232, 205)",
    "cornsilk3": "rgb(205, 200, 177)",
    "cornsilk4": "rgb(139, 136, 120)",
    "cyan": "rgb(0, 255, 255)",
    "cyan1": "rgb(0, 255, 255)",
    "cyan2": "rgb(0, 238, 238)",
    "cyan3": "rgb(0, 205, 205)",
    "cyan4": "rgb(0, 139, 139)",
    "darkblue": "rgb(0, 0, 139)",
    "darkcyan": "rgb(0, 139, 139)",
    "darkgoldenrod": "rgb(184, 134, 11)",
    "darkgoldenrod1": "rgb(255, 185, 15)",
    "darkgoldenrod2": "rgb(238, 173, 14)",
    "darkgoldenrod3": "rgb(205, 149, 12)",
    "darkgoldenrod4": "rgb(139, 101, 8)",
    "darkgray": "rgb(169, 169, 169)",
    "darkgreen": "rgb(0, 100, 0)",
    "darkgrey": "rgb(169, 169, 169)",
    "darkkhaki": "rgb(189, 183, 107)",
    "darkmagenta": "rgb(139, 0, 139)",
    "darkolivegreen": "rgb(85, 107, 47)",
    "darkolivegreen1": "rgb(202, 255, 112)",
    "darkolivegreen2": "rgb(188, 238, 104)",
    "darkolivegreen3": "rgb(162, 205, 90)",
    "darkolivegreen4": "rgb(110, 139, 61)",
    "darkorange": "rgb(255, 140, 0)",
    "darkorange1": "rgb(255, 127, 0)",
    "darkorange2": "rgb(238, 118, 0)",
    "darkorange3": "rgb(205, 102, 0)",
    "darkorange4": "rgb(139, 69, 0)",
    "darkorchid": "rgb(153, 50, 204)",
    "darkorchid1": "rgb(191, 62, 255)",
    "darkorchid2": "rgb(178, 58, 238)",
    "darkorchid3": "rgb(154, 50, 205)",
    "darkorchid4": "rgb(104, 34, 139)",
    "darkred": "rgb(139, 0, 0)",
    "darksalmon": "rgb(233, 150, 122)",
    "darkseagreen": "rgb(143, 188, 143)",
    "darkseagreen1": "rgb(193, 255, 193)",
    "darkseagreen2": "rgb(180, 238, 180)",
    "darkseagreen3": "rgb(155, 205, 155)",
    "darkseagreen4": "rgb(105, 139, 105)",
    "darkslateblue": "rgb(72, 61, 139)",
    "darkslategray": "rgb(47, 79, 79)",
    "darkslategray1": "rgb(151, 255, 255)",
    "darkslategray2": "rgb(141, 238, 238)",
    "darkslategray3": "rgb(121, 205, 205)",
    "darkslategray4": "rgb(82, 139, 139)",
    "darkslategrey": "rgb(47, 79, 79)",
    "darkturquoise": "rgb(0, 206, 209)",
    "darkviolet": "rgb(148, 0, 211)",
    "debianred": "rgb(215, 7, 81)",
    "deeppink": "rgb(255, 20, 147)",
    "deeppink1": "rgb(255, 20, 147)",
    "deeppink2": "rgb(238, 18, 137)",
    "deeppink3": "rgb(205, 16, 118)",
    "deeppink4": "rgb(139, 10, 80)",
    "deepskyblue": "rgb(0, 191, 255)",
    "deepskyblue1": "rgb(0, 191, 255)",
    "deepskyblue2": "rgb(0, 178, 238)",
    "deepskyblue3": "rgb(0, 154, 205)",
    "deepskyblue4": "rgb(0, 104, 139)",
    "dimgray": "rgb(105, 105, 105)",
    "dimgrey": "rgb(105, 105, 105)",
    "dodgerblue": "rgb(30, 144, 255)",
    "dodgerblue1": "rgb(30, 144, 255)",
    "dodgerblue2": "rgb(28, 134, 238)",
    "dodgerblue3": "rgb(24, 116, 205)",
    "dodgerblue4": "rgb(16, 78, 139)",
    "firebrick": "rgb(178, 34, 34)",
    "firebrick1": "rgb(255, 48, 48)",
    "firebrick2": "rgb(238, 44, 44)",
    "firebrick3": "rgb(205, 38, 38)",
    "firebrick4": "rgb(139, 26, 26)",
    "floralwhite": "rgb(255, 250, 240)",
    "forestgreen": "rgb(34, 139, 34)",
    "gainsboro": "rgb(220, 220, 220)",
    "ghostwhite": "rgb(248, 248, 255)",
    "gold": "rgb(255, 215, 0)",
    "gold1": "rgb(255, 215, 0)",
    "gold2": "rgb(238, 201, 0)",
    "gold3": "rgb(205, 173, 0)",
    "gold4": "rgb(139, 117, 0)",
    "goldenrod": "rgb(218, 165, 32)",
    "goldenrod1": "rgb(255, 193, 37)",
    "goldenrod2": "rgb(238, 180, 34)",
    "goldenrod3": "rgb(205, 155, 29)",
    "goldenrod4": "rgb(139, 105, 20)",
    "gray": "rgb(190, 190, 190)",
    "gray0": "rgb(0, 0, 0)",
    "gray1": "rgb(3, 3, 3)",
    "gray10": "rgb(26, 26, 26)",
    "gray100": "rgb(255, 255, 255)",
    "gray11": "rgb(28, 28, 28)",
    "gray12": "rgb(31, 31, 31)",
    "gray13": "rgb(33, 33, 33)",
    "gray14": "rgb(36, 36, 36)",
    "gray15": "rgb(38, 38, 38)",
    "gray16": "rgb(41, 41, 41)",
    "gray17": "rgb(43, 43, 43)",
    "gray18": "rgb(46, 46, 46)",
    "gray19": "rgb(48, 48, 48)",
    "gray2": "rgb(5, 5, 5)",
    "gray20": "rgb(51, 51, 51)",
    "gray21": "rgb(54, 54, 54)",
    "gray22": "rgb(56, 56, 56)",
    "gray23": "rgb(59, 59, 59)",
    "gray24": "rgb(61, 61, 61)",
    "gray25": "rgb(64, 64, 64)",
    "gray26": "rgb(66, 66, 66)",
    "gray27": "rgb(69, 69, 69)",
    "gray28": "rgb(71, 71, 71)",
    "gray29": "rgb(74, 74, 74)",
    "gray3": "rgb(8, 8, 8)",
    "gray30": "rgb(77, 77, 77)",
    "gray31": "rgb(79, 79, 79)",
    "gray32": "rgb(82, 82, 82)",
    "gray33": "rgb(84, 84, 84)",
    "gray34": "rgb(87, 87, 87)",
    "gray35": "rgb(89, 89, 89)",
    "gray36": "rgb(92, 92, 92)",
    "gray37": "rgb(94, 94, 94)",
    "gray38": "rgb(97, 97, 97)",
    "gray39": "rgb(99, 99, 99)",
    "gray4": "rgb(10, 10, 10)",
    "gray40": "rgb(102, 102, 102)",
    "gray41": "rgb(105, 105, 105)",
    "gray42": "rgb(107, 107, 107)",
    "gray43": "rgb(110, 110, 110)",
    "gray44": "rgb(112, 112, 112)",
    "gray45": "rgb(115, 115, 115)",
    "gray46": "rgb(117, 117, 117)",
    "gray47": "rgb(120, 120, 120)",
    "gray48": "rgb(122, 122, 122)",
    "gray49": "rgb(125, 125, 125)",
    "gray5": "rgb(13, 13, 13)",
    "gray50": "rgb(127, 127, 127)",
    "gray51": "rgb(130, 130, 130)",
    "gray52": "rgb(133, 133, 133)",
    "gray53": "rgb(135, 135, 135)",
    "gray54": "rgb(138, 138, 138)",
    "gray55": "rgb(140, 140, 140)",
    "gray56": "rgb(143, 143, 143)",
    "gray57": "rgb(145, 145, 145)",
    "gray58": "rgb(148, 148, 148)",
    "gray59": "rgb(150, 150, 150)",
    "gray6": "rgb(15, 15, 15)",
    "gray60": "rgb(153, 153, 153)",
    "gray61": "rgb(156, 156, 156)",
    "gray62": "rgb(158, 158, 158)",
    "gray63": "rgb(161, 161, 161)",
    "gray64": "rgb(163, 163, 163)",
    "gray65": "rgb(166, 166, 166)",
    "gray66": "rgb(168, 168, 168)",
    "gray67": "rgb(171, 171, 171)",
    "gray68": "rgb(173, 173, 173)",
    "gray69": "rgb(176, 176, 176)",
    "gray7": "rgb(18, 18, 18)",
    "gray70": "rgb(179, 179, 179)",
    "gray71": "rgb(181, 181, 181)",
    "gray72": "rgb(184, 184, 184)",
    "gray73": "rgb(186, 186, 186)",
    "gray74": "rgb(189, 189, 189)",
    "gray75": "rgb(191, 191, 191)",
    "gray76": "rgb(194, 194, 194)",
    "gray77": "rgb(196, 196, 196)",
    "gray78": "rgb(199, 199, 199)",
    "gray79": "rgb(201, 201, 201)",
    "gray8": "rgb(20, 20, 20)",
    "gray80": "rgb(204, 204, 204)",
    "gray81": "rgb(207, 207, 207)",
    "gray82": "rgb(209, 209, 209)",
    "gray83": "rgb(212, 212, 212)",
    "gray84": "rgb(214, 214, 214)",
    "gray85": "rgb(217, 217, 217)",
    "gray86": "rgb(219, 219, 219)",
    "gray87": "rgb(222, 222, 222)",
    "gray88": "rgb(224, 224, 224)",
    "gray89": "rgb(227, 227, 227)",
    "gray9": "rgb(23, 23, 23)",
    "gray90": "rgb(229, 229, 229)",
    "gray91": "rgb(232, 232, 232)",
    "gray92": "rgb(235, 235, 235)",
    "gray93": "rgb(237, 237, 237)",
    "gray94": "rgb(240, 240, 240)",
    "gray95": "rgb(242, 242, 242)",
    "gray96": "rgb(245, 245, 245)",
    "gray97": "rgb(247, 247, 247)",
    "gray98": "rgb(250, 250, 250)",
    "gray99": "rgb(252, 252, 252)",
    "green": "rgb(0, 255, 0)",
    "green1": "rgb(0, 255, 0)",
    "green2": "rgb(0, 238, 0)",
    "green3": "rgb(0, 205, 0)",
    "green4": "rgb(0, 139, 0)",
    "greenyellow": "rgb(173, 255, 47)",
    "grey": "rgb(190, 190, 190)",
    "grey0": "rgb(0, 0, 0)",
    "grey1": "rgb(3, 3, 3)",
    "grey10": "rgb(26, 26, 26)",
    "grey100": "rgb(255, 255, 255)",
    "grey11": "rgb(28, 28, 28)",
    "grey12": "rgb(31, 31, 31)",
    "grey13": "rgb(33, 33, 33)",
    "grey14": "rgb(36, 36, 36)",
    "grey15": "rgb(38, 38, 38)",
    "grey16": "rgb(41, 41, 41)",
    "grey17": "rgb(43, 43, 43)",
    "grey18": "rgb(46, 46, 46)",
    "grey19": "rgb(48, 48, 48)",
    "grey2": "rgb(5, 5, 5)",
    "grey20": "rgb(51, 51, 51)",
    "grey21": "rgb(54, 54, 54)",
    "grey22": "rgb(56, 56, 56)",
    "grey23": "rgb(59, 59, 59)",
    "grey24": "rgb(61, 61, 61)",
    "grey25": "rgb(64, 64, 64)",
    "grey26": "rgb(66, 66, 66)",
    "grey27": "rgb(69, 69, 69)",
    "grey28": "rgb(71, 71, 71)",
    "grey29": "rgb(74, 74, 74)",
    "grey3": "rgb(8, 8, 8)",
    "grey30": "rgb(77, 77, 77)",
    "grey31": "rgb(79, 79, 79)",
    "grey32": "rgb(82, 82, 82)",
    "grey33": "rgb(84, 84, 84)",
    "grey34": "rgb(87, 87, 87)",
    "grey35": "rgb(89, 89, 89)",
    "grey36": "rgb(92, 92, 92)",
    "grey37": "rgb(94, 94, 94)",
    "grey38": "rgb(97, 97, 97)",
    "grey39": "rgb(99, 99, 99)",
    "grey4": "rgb(10, 10, 10)",
    "grey40": "rgb(102, 102, 102)",
    "grey41": "rgb(105, 105, 105)",
    "grey42": "rgb(107, 107, 107)",
    "grey43": "rgb(110, 110, 110)",
    "grey44": "rgb(112, 112, 112)",
    "grey45": "rgb(115, 115, 115)",
    "grey46": "rgb(117, 117, 117)",
    "grey47": "rgb(120, 120, 120)",
    "grey48": "rgb(122, 122, 122)",
    "grey49": "rgb(125, 125, 125)",
    "grey5": "rgb(13, 13, 13)",
    "grey50": "rgb(127, 127, 127)",
    "grey51": "rgb(130, 130, 130)",
    "grey52": "rgb(133, 133, 133)",
    "grey53": "rgb(135, 135, 135)",
    "grey54": "rgb(138, 138, 138)",
    "grey55": "rgb(140, 140, 140)",
    "grey56": "rgb(143, 143, 143)",
    "grey57": "rgb(145, 145, 145)",
    "grey58": "rgb(148, 148, 148)",
    "grey59": "rgb(150, 150, 150)",
    "grey6": "rgb(15, 15, 15)",
    "grey60": "rgb(153, 153, 153)",
    "grey61": "rgb(156, 156, 156)",
    "grey62": "rgb(158, 158, 158)",
    "grey63": "rgb(161, 161, 161)",
    "grey64": "rgb(163, 163, 163)",
    "grey65": "rgb(166, 166, 166)",
    "grey66": "rgb(168, 168, 168)",
    "grey67": "rgb(171, 171, 171)",
    "grey68": "rgb(173, 173, 173)",
    "grey69": "rgb(176, 176, 176)",
    "grey7": "rgb(18, 18, 18)",
    "grey70": "rgb(179, 179, 179)",
    "grey71": "rgb(181, 181, 181)",
    "grey72": "rgb(184, 184, 184)",
    "grey73": "rgb(186, 186, 186)",
    "grey74": "rgb(189, 189, 189)",
    "grey75": "rgb(191, 191, 191)",
    "grey76": "rgb(194, 194, 194)",
    "grey77": "rgb(196, 196, 196)",
    "grey78": "rgb(199, 199, 199)",
    "grey79": "rgb(201, 201, 201)",
    "grey8": "rgb(20, 20, 20)",
    "grey80": "rgb(204, 204, 204)",
    "grey81": "rgb(207, 207, 207)",
    "grey82": "rgb(209, 209, 209)",
    "grey83": "rgb(212, 212, 212)",
    "grey84": "rgb(214, 214, 214)",
    "grey85": "rgb(217, 217, 217)",
    "grey86": "rgb(219, 219, 219)",
    "grey87": "rgb(222, 222, 222)",
    "grey88": "rgb(224, 224, 224)",
    "grey89": "rgb(227, 227, 227)",
    "grey9": "rgb(23, 23, 23)",
    "grey90": "rgb(229, 229, 229)",
    "grey91": "rgb(232, 232, 232)",
    "grey92": "rgb(235, 235, 235)",
    "grey93": "rgb(237, 237, 237)",
    "grey94": "rgb(240, 240, 240)",
    "grey95": "rgb(242, 242, 242)",
    "grey96": "rgb(245, 245, 245)",
    "grey97": "rgb(247, 247, 247)",
    "grey98": "rgb(250, 250, 250)",
    "grey99": "rgb(252, 252, 252)",
    "honeydew": "rgb(240, 255, 240)",
    "honeydew1": "rgb(240, 255, 240)",
    "honeydew2": "rgb(224, 238, 224)",
    "honeydew3": "rgb(193, 205, 193)",
    "honeydew4": "rgb(131, 139, 131)",
    "hotpink": "rgb(255, 105, 180)",
    "hotpink1": "rgb(255, 110, 180)",
    "hotpink2": "rgb(238, 106, 167)",
    "hotpink3": "rgb(205, 96, 144)",
    "hotpink4": "rgb(139, 58, 98)",
    "indianred": "rgb(205, 92, 92)",
    "indianred1": "rgb(255, 106, 106)",
    "indianred2": "rgb(238, 99, 99)",
    "indianred3": "rgb(205, 85, 85)",
    "indianred4": "rgb(139, 58, 58)",
    "ivory": "rgb(255, 255, 240)",
    "ivory1": "rgb(255, 255, 240)",
    "ivory2": "rgb(238, 238, 224)",
    "ivory3": "rgb(205, 205, 193)",
    "ivory4": "rgb(139, 139, 131)",
    "khaki": "rgb(240, 230, 140)",
    "khaki1": "rgb(255, 246, 143)",
    "khaki2": "rgb(238, 230, 133)",
    "khaki3": "rgb(205, 198, 115)",
    "khaki4": "rgb(139, 134, 78)",
    "lavender": "rgb(230, 230, 250)",
    "lavenderblush": "rgb(255, 240, 245)",
    "lavenderblush1": "rgb(255, 240, 245)",
    "lavenderblush2": "rgb(238, 224, 229)",
    "lavenderblush3": "rgb(205, 193, 197)",
    "lavenderblush4": "rgb(139, 131, 134)",
    "lawngreen": "rgb(124, 252, 0)",
    "lemonchiffon": "rgb(255, 250, 205)",
    "lemonchiffon1": "rgb(255, 250, 205)",
    "lemonchiffon2": "rgb(238, 233, 191)",
    "lemonchiffon3": "rgb(205, 201, 165)",
    "lemonchiffon4": "rgb(139, 137, 112)",
    "lightblue": "rgb(173, 216, 230)",
    "lightblue1": "rgb(191, 239, 255)",
    "lightblue2": "rgb(178, 223, 238)",
    "lightblue3": "rgb(154, 192, 205)",
    "lightblue4": "rgb(104, 131, 139)",
    "lightcoral": "rgb(240, 128, 128)",
    "lightcyan": "rgb(224, 255, 255)",
    "lightcyan1": "rgb(224, 255, 255)",
    "lightcyan2": "rgb(209, 238, 238)",
    "lightcyan3": "rgb(180, 205, 205)",
    "lightcyan4": "rgb(122, 139, 139)",
    "lightgoldenrod": "rgb(238, 221, 130)",
    "lightgoldenrod1": "rgb(255, 236, 139)",
    "lightgoldenrod2": "rgb(238, 220, 130)",
    "lightgoldenrod3": "rgb(205, 190, 112)",
    "lightgoldenrod4": "rgb(139, 129, 76)",
    "lightgoldenrodyellow": "rgb(250, 250, 210)",
    "lightgray": "rgb(211, 211, 211)",
    "lightgreen": "rgb(144, 238, 144)",
    "lightgrey": "rgb(211, 211, 211)",
    "lightpink": "rgb(255, 182, 193)",
    "lightpink1": "rgb(255, 174, 185)",
    "lightpink2": "rgb(238, 162, 173)",
    "lightpink3": "rgb(205, 140, 149)",
    "lightpink4": "rgb(139, 95, 101)",
    "lightsalmon": "rgb(255, 160, 122)",
    "lightsalmon1": "rgb(255, 160, 122)",
    "lightsalmon2": "rgb(238, 149, 114)",
    "lightsalmon3": "rgb(205, 129, 98)",
    "lightsalmon4": "rgb(139, 87, 66)",
    "lightseagreen": "rgb(32, 178, 170)",
    "lightskyblue": "rgb(135, 206, 250)",
    "lightskyblue1": "rgb(176, 226, 255)",
    "lightskyblue2": "rgb(164, 211, 238)",
    "lightskyblue3": "rgb(141, 182, 205)",
    "lightskyblue4": "rgb(96, 123, 139)",
    "lightslateblue": "rgb(132, 112, 255)",
    "lightslategray": "rgb(119, 136, 153)",
    "lightslategrey": "rgb(119, 136, 153)",
    "lightsteelblue": "rgb(176, 196, 222)",
    "lightsteelblue1": "rgb(202, 225, 255)",
    "lightsteelblue2": "rgb(188, 210, 238)",
    "lightsteelblue3": "rgb(162, 181, 205)",
    "lightsteelblue4": "rgb(110, 123, 139)",
    "lightyellow": "rgb(255, 255, 224)",
    "lightyellow1": "rgb(255, 255, 224)",
    "lightyellow2": "rgb(238, 238, 209)",
    "lightyellow3": "rgb(205, 205, 180)",
    "lightyellow4": "rgb(139, 139, 122)",
    "limegreen": "rgb(50, 205, 50)",
    "linen": "rgb(250, 240, 230)",
    "magenta": "rgb(255, 0, 255)",
    "magenta1": "rgb(255, 0, 255)",
    "magenta2": "rgb(238, 0, 238)",
    "magenta3": "rgb(205, 0, 205)",
    "magenta4": "rgb(139, 0, 139)",
    "maroon": "rgb(176, 48, 96)",
    "maroon1": "rgb(255, 52, 179)",
    "maroon2": "rgb(238, 48, 167)",
    "maroon3": "rgb(205, 41, 144)",
    "maroon4": "rgb(139, 28, 98)",
    "mediumaquamarine": "rgb(102, 205, 170)",
    "mediumblue": "rgb(0, 0, 205)",
    "mediumorchid": "rgb(186, 85, 211)",
    "mediumorchid1": "rgb(224, 102, 255)",
    "mediumorchid2": "rgb(209, 95, 238)",
    "mediumorchid3": "rgb(180, 82, 205)",
    "mediumorchid4": "rgb(122, 55, 139)",
    "mediumpurple": "rgb(147, 112, 219)",
    "mediumpurple1": "rgb(171, 130, 255)",
    "mediumpurple2": "rgb(159, 121, 238)",
    "mediumpurple3": "rgb(137, 104, 205)",
    "mediumpurple4": "rgb(93, 71, 139)",
    "mediumseagreen": "rgb(60, 179, 113)",
    "mediumslateblue": "rgb(123, 104, 238)",
    "mediumspringgreen": "rgb(0, 250, 154)",
    "mediumturquoise": "rgb(72, 209, 204)",
    "mediumvioletred": "rgb(199, 21, 133)",
    "midnightblue": "rgb(25, 25, 112)",
    "mintcream": "rgb(245, 255, 250)",
    "mistyrose": "rgb(255, 228, 225)",
    "mistyrose1": "rgb(255, 228, 225)",
    "mistyrose2": "rgb(238, 213, 210)",
    "mistyrose3": "rgb(205, 183, 181)",
    "mistyrose4": "rgb(139, 125, 123)",
    "moccasin": "rgb(255, 228, 181)",
    "navajowhite": "rgb(255, 222, 173)",
    "navajowhite1": "rgb(255, 222, 173)",
    "navajowhite2": "rgb(238, 207, 161)",
    "navajowhite3": "rgb(205, 179, 139)",
    "navajowhite4": "rgb(139, 121, 94)",
    "navy": "rgb(0, 0, 128)",
    "navyblue": "rgb(0, 0, 128)",
    "oldlace": "rgb(253, 245, 230)",
    "olivedrab": "rgb(107, 142, 35)",
    "olivedrab1": "rgb(192, 255, 62)",
    "olivedrab2": "rgb(179, 238, 58)",
    "olivedrab3": "rgb(154, 205, 50)",
    "olivedrab4": "rgb(105, 139, 34)",
    "orange": "rgb(255, 165, 0)",
    "orange1": "rgb(255, 165, 0)",
    "orange2": "rgb(238, 154, 0)",
    "orange3": "rgb(205, 133, 0)",
    "orange4": "rgb(139, 90, 0)",
    "orangered": "rgb(255, 69, 0)",
    "orangered1": "rgb(255, 69, 0)",
    "orangered2": "rgb(238, 64, 0)",
    "orangered3": "rgb(205, 55, 0)",
    "orangered4": "rgb(139, 37, 0)",
    "orchid": "rgb(218, 112, 214)",
    "orchid1": "rgb(255, 131, 250)",
    "orchid2": "rgb(238, 122, 233)",
    "orchid3": "rgb(205, 105, 201)",
    "orchid4": "rgb(139, 71, 137)",
    "palegoldenrod": "rgb(238, 232, 170)",
    "palegreen": "rgb(152, 251, 152)",
    "palegreen1": "rgb(154, 255, 154)",
    "palegreen2": "rgb(144, 238, 144)",
    "palegreen3": "rgb(124, 205, 124)",
    "palegreen4": "rgb(84, 139, 84)",
    "paleturquoise": "rgb(175, 238, 238)",
    "paleturquoise1": "rgb(187, 255, 255)",
    "paleturquoise2": "rgb(174, 238, 238)",
    "paleturquoise3": "rgb(150, 205, 205)",
    "paleturquoise4": "rgb(102, 139, 139)",
    "palevioletred": "rgb(219, 112, 147)",
    "palevioletred1": "rgb(255, 130, 171)",
    "palevioletred2": "rgb(238, 121, 159)",
    "palevioletred3": "rgb(205, 104, 137)",
    "palevioletred4": "rgb(139, 71, 93)",
    "papayawhip": "rgb(255, 239, 213)",
    "peachpuff": "rgb(255, 218, 185)",
    "peachpuff1": "rgb(255, 218, 185)",
    "peachpuff2": "rgb(238, 203, 173)",
    "peachpuff3": "rgb(205, 175, 149)",
    "peachpuff4": "rgb(139, 119, 101)",
    "peru": "rgb(205, 133, 63)",
    "pink": "rgb(255, 192, 203)",
    "pink1": "rgb(255, 181, 197)",
    "pink2": "rgb(238, 169, 184)",
    "pink3": "rgb(205, 145, 158)",
    "pink4": "rgb(139, 99, 108)",
    "plum": "rgb(221, 160, 221)",
    "plum1": "rgb(255, 187, 255)",
    "plum2": "rgb(238, 174, 238)",
    "plum3": "rgb(205, 150, 205)",
    "plum4": "rgb(139, 102, 139)",
    "powderblue": "rgb(176, 224, 230)",
    "purple": "rgb(160, 32, 240)",
    "purple1": "rgb(155, 48, 255)",
    "purple2": "rgb(145, 44, 238)",
    "purple3": "rgb(125, 38, 205)",
    "purple4": "rgb(85, 26, 139)",
    "red": "rgb(255, 0, 0)",
    "red1": "rgb(255, 0, 0)",
    "red2": "rgb(238, 0, 0)",
    "red3": "rgb(205, 0, 0)",
    "red4": "rgb(139, 0, 0)",
    "rosybrown": "rgb(188, 143, 143)",
    "rosybrown1": "rgb(255, 193, 193)",
    "rosybrown2": "rgb(238, 180, 180)",
    "rosybrown3": "rgb(205, 155, 155)",
    "rosybrown4": "rgb(139, 105, 105)",
    "royalblue": "rgb(65, 105, 225)",
    "royalblue1": "rgb(72, 118, 255)",
    "royalblue2": "rgb(67, 110, 238)",
    "royalblue3": "rgb(58, 95, 205)",
    "royalblue4": "rgb(39, 64, 139)",
    "saddlebrown": "rgb(139, 69, 19)",
    "salmon": "rgb(250, 128, 114)",
    "salmon1": "rgb(255, 140, 105)",
    "salmon2": "rgb(238, 130, 98)",
    "salmon3": "rgb(205, 112, 84)",
    "salmon4": "rgb(139, 76, 57)",
    "sandybrown": "rgb(244, 164, 96)",
    "seagreen": "rgb(46, 139, 87)",
    "seagreen1": "rgb(84, 255, 159)",
    "seagreen2": "rgb(78, 238, 148)",
    "seagreen3": "rgb(67, 205, 128)",
    "seagreen4": "rgb(46, 139, 87)",
    "seashell": "rgb(255, 245, 238)",
    "seashell1": "rgb(255, 245, 238)",
    "seashell2": "rgb(238, 229, 222)",
    "seashell3": "rgb(205, 197, 191)",
    "seashell4": "rgb(139, 134, 130)",
    "sienna": "rgb(160, 82, 45)",
    "sienna1": "rgb(255, 130, 71)",
    "sienna2": "rgb(238, 121, 66)",
    "sienna3": "rgb(205, 104, 57)",
    "sienna4": "rgb(139, 71, 38)",
    "skyblue": "rgb(135, 206, 235)",
    "skyblue1": "rgb(135, 206, 255)",
    "skyblue2": "rgb(126, 192, 238)",
    "skyblue3": "rgb(108, 166, 205)",
    "skyblue4": "rgb(74, 112, 139)",
    "slateblue": "rgb(106, 90, 205)",
    "slateblue1": "rgb(131, 111, 255)",
    "slateblue2": "rgb(122, 103, 238)",
    "slateblue3": "rgb(105, 89, 205)",
    "slateblue4": "rgb(71, 60, 139)",
    "slategray": "rgb(112, 128, 144)",
    "slategray1": "rgb(198, 226, 255)",
    "slategray2": "rgb(185, 211, 238)",
    "slategray3": "rgb(159, 182, 205)",
    "slategray4": "rgb(108, 123, 139)",
    "slategrey": "rgb(112, 128, 144)",
    "snow": "rgb(255, 250, 250)",
    "snow1": "rgb(255, 250, 250)",
    "snow2": "rgb(238, 233, 233)",
    "snow3": "rgb(205, 201, 201)",
    "snow4": "rgb(139, 137, 137)",
    "springgreen": "rgb(0, 255, 127)",
    "springgreen1": "rgb(0, 255, 127)",
    "springgreen2": "rgb(0, 238, 118)",
    "springgreen3": "rgb(0, 205, 102)",
    "springgreen4": "rgb(0, 139, 69)",
    "steelblue": "rgb(70, 130, 180)",
    "steelblue1": "rgb(99, 184, 255)",
    "steelblue2": "rgb(92, 172, 238)",
    "steelblue3": "rgb(79, 148, 205)",
    "steelblue4": "rgb(54, 100, 139)",
    "tan": "rgb(210, 180, 140)",
    "tan1": "rgb(255, 165, 79)",
    "tan2": "rgb(238, 154, 73)",
    "tan3": "rgb(205, 133, 63)",
    "tan4": "rgb(139, 90, 43)",
    "thistle": "rgb(216, 191, 216)",
    "thistle1": "rgb(255, 225, 255)",
    "thistle2": "rgb(238, 210, 238)",
    "thistle3": "rgb(205, 181, 205)",
    "thistle4": "rgb(139, 123, 139)",
    "tomato": "rgb(255, 99, 71)",
    "tomato1": "rgb(255, 99, 71)",
    "tomato2": "rgb(238, 92, 66)",
    "tomato3": "rgb(205, 79, 57)",
    "tomato4": "rgb(139, 54, 38)",
    "turquoise": "rgb(64, 224, 208)",
    "turquoise1": "rgb(0, 245, 255)",
    "turquoise2": "rgb(0, 229, 238)",
    "turquoise3": "rgb(0, 197, 205)",
    "turquoise4": "rgb(0, 134, 139)",
    "violet": "rgb(238, 130, 238)",
    "violetred": "rgb(208, 32, 144)",
    "violetred1": "rgb(255, 62, 150)",
    "violetred2": "rgb(238, 58, 140)",
    "violetred3": "rgb(205, 50, 120)",
    "violetred4": "rgb(139, 34, 82)",
    "wheat": "rgb(245, 222, 179)",
    "wheat1": "rgb(255, 231, 186)",
    "wheat2": "rgb(238, 216, 174)",
    "wheat3": "rgb(205, 186, 150)",
    "wheat4": "rgb(139, 126, 102)",
    "white": "rgb(255, 255, 255)",
    "whitesmoke": "rgb(245, 245, 245)",
    "yellow": "rgb(255, 255, 0)",
    "yellow1": "rgb(255, 255, 0)",
    "yellow2": "rgb(238, 238, 0)",
    "yellow3": "rgb(205, 205, 0)",
    "yellow4": "rgb(139, 139, 0)",
    "yellowgreen": "rgb(154, 205, 50)"
  };
  // SOURCE FILE: libdot/js/lib_f.js
  // Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  'use strict';
  
  /**
   * Grab bag of utility functions.
   */
  lib.f = {};
  
  /**
   * Create a unique enum value.
   *
   * @suppress {lintChecks}
   * @param {string} name A human friendly name for debugging.
   * @return {Object} A unique enum that won't compare equal to anything else.
   */
  lib.f.createEnum = function(name) {
    // We use a String object as nothing else should be using them -- we want to
    // use string primitives normally.  But debuggers will include our name.
    // eslint-disable-next-line no-new-wrappers
    return new String(name);
  };
  
  /**
   * Replace variable references in a string.
   *
   * Variables are of the form %FUNCTION(VARNAME).  FUNCTION is an optional
   * escape function to apply to the value.
   *
   * For example
   *   lib.f.replaceVars("%(greeting), %encodeURIComponent(name)",
   *                     { greeting: "Hello",
   *                       name: "Google+" });
   *
   * Will result in "Hello, Google%2B".
   */
  lib.f.replaceVars = function(str, vars) {
    return str.replace(/%([a-z]*)\(([^\)]+)\)/gi, function(match, fn, varname) {
        if (typeof vars[varname] == 'undefined')
          throw 'Unknown variable: ' + varname;
  
        var rv = vars[varname];
  
        if (fn in lib.f.replaceVars.functions) {
          rv = lib.f.replaceVars.functions[fn](rv);
        } else if (fn) {
          throw 'Unknown escape function: ' + fn;
        }
  
        return rv;
      });
  };
  
  /**
   * Functions that can be used with replaceVars.
   *
   * Clients can add to this list to extend lib.f.replaceVars().
   */
  lib.f.replaceVars.functions = {
    encodeURI: encodeURI,
    encodeURIComponent: encodeURIComponent,
    escapeHTML: function(str) {
      var map = {
        '<': '&lt;',
        '>': '&gt;',
        '&': '&amp;',
        '"': '&quot;',
        "'": '&#39;'
      };
  
      return str.replace(/[<>&\"\']/g, (m) => map[m]);
    }
  };
  
  /**
   * Get the list of accepted UI languages.
   *
   * @param {function(Array)} callback Function to invoke with the results.  The
   *     parameter is a list of locale names.
   */
  lib.f.getAcceptLanguages = function(callback) {
    if (lib.f.getAcceptLanguages.chromeSupported()) {
      chrome.i18n.getAcceptLanguages(callback);
    } else {
      setTimeout(function() {
          callback([navigator.language.replace(/-/g, '_')]);
        }, 0);
    }
  };
  
  lib.f.getAcceptLanguages.chromeSupported = function() {
    return window.chrome && chrome.i18n;
  };
  
  /**
   * Parse a query string into a hash.
   *
   * This takes a url query string in the form 'name1=value&name2=value' and
   * converts it into an object of the form { name1: 'value', name2: 'value' }.
   * If a given name appears multiple times in the query string, only the
   * last value will appear in the result.  If the name ends with [], it is
   * turned into an array.
   *
   * Names and values are passed through decodeURIComponent before being added
   * to the result object.
   *
   * @param {string} queryString The string to parse.  If it starts with a
   *     leading '?', the '?' will be ignored.
   */
  lib.f.parseQuery = function(queryString) {
    if (queryString.startsWith('?'))
      queryString = queryString.substr(1);
  
    var rv = {};
  
    var pairs = queryString.split('&');
    for (var i = 0; i < pairs.length; i++) {
      var pair = pairs[i].split('=');
      let key = decodeURIComponent(pair[0]);
      let val = decodeURIComponent(pair[1]);
  
      if (key.endsWith('[]')) {
        // It's an array.
        key = key.slice(0, -2);
        // The key doesn't exist, or wasn't an array before.
        if (!(rv[key] instanceof Array))
          rv[key] = [];
        rv[key].push(val);
      } else {
        // It's a plain string.
        rv[key] = val;
      }
    }
  
    return rv;
  };
  
  lib.f.getURL = function(path) {
    if (lib.f.getURL.chromeSupported())
      return chrome.runtime.getURL(path);
  
    return path;
  };
  
  lib.f.getURL.chromeSupported = function() {
    return window.chrome && chrome.runtime && chrome.runtime.getURL;
  };
  
  /**
   * Clamp a given integer to a specified range.
   *
   * @param {integer} v The value to be clamped.
   * @param {integer} min The minimum acceptable value.
   * @param {integer} max The maximum acceptable value.
   */
  lib.f.clamp = function(v, min, max) {
    if (v < min)
      return min;
    if (v > max)
      return max;
    return v;
  };
  
  /**
   * Left pad a number to a given length with leading zeros.
   *
   * @param {string|integer} number The number to pad.
   * @param {integer} length The desired length.
   * @return {string} The padded number as a string.
   */
  lib.f.zpad = function(number, length) {
    return String(number).padStart(length, '0');
  };
  
  /**
   * Return a string containing a given number of space characters.
   *
   * This method maintains a static cache of the largest amount of whitespace
   * ever requested.  It shouldn't be used to generate an insanely huge amount of
   * whitespace.
   *
   * @param {integer} length The desired amount of whitespace.
   * @param {string} A string of spaces of the requested length.
   */
  lib.f.getWhitespace = function(length) {
    if (length <= 0)
      return '';
  
    var f = this.getWhitespace;
    if (!f.whitespace)
      f.whitespace = '          ';
  
    while (length > f.whitespace.length) {
      f.whitespace += f.whitespace;
    }
  
    return f.whitespace.substr(0, length);
  };
  
   /**
   * Ensure that a function is called within a certain time limit.
   *
   * Simple usage looks like this...
   *
   *  lib.registerInit(lib.f.alarm(onInit));
   *
   * This will log a warning to the console if onInit() is not invoked within
   * 5 seconds.
   *
   * If you're performing some operation that may take longer than 5 seconds you
   * can pass a duration in milliseconds as the optional second parameter.
   *
   * If you pass a string identifier instead of a callback function, you'll get a
   * wrapper generator rather than a single wrapper.  Each call to the
   * generator will return a wrapped version of the callback wired to
   * a shared timeout.  This is for cases where you want to ensure that at least
   * one of a set of callbacks is invoked before a timeout expires.
   *
   *   var alarm = lib.f.alarm('fetch object');
   *   lib.foo.fetchObject(alarm(onSuccess), alarm(onFailure));
   *
   * @param {function(*)} callback The function to wrap in an alarm.
   * @param {int} opt_ms Optional number of milliseconds to wait before raising
   *     an alarm.  Default is 5000 (5 seconds).
   * @return {function} If callback is a function then the return value will be
   *     the wrapped callback.  If callback is a string then the return value will
   *     be a function that generates new wrapped callbacks.
   */
  lib.f.alarm = function(callback, opt_ms) {
    var ms = opt_ms || 5 * 1000;
    var stack = lib.f.getStack(1);
  
    return (function() {
      // This outer function is called immediately.  It's here to capture a new
      // scope for the timeout variable.
  
      // The 'timeout' variable is shared by this timeout function, and the
      // callback wrapper.
      var timeout = setTimeout(function() {
        var name = (typeof callback == 'string') ? name : callback.name;
        name = name ? (': ' + name) : '';
        console.warn('lib.f.alarm: timeout expired: ' + (ms / 1000) + 's' + name);
        console.log(stack);
        timeout = null;
      }, ms);
  
      var wrapperGenerator = function(callback) {
        return function() {
          if (timeout) {
            clearTimeout(timeout);
            timeout = null;
          }
  
          return callback.apply(null, arguments);
        };
      };
  
      if (typeof callback == 'string')
        return wrapperGenerator;
  
      return wrapperGenerator(callback);
    })();
  };
  
  /**
   * Return the current call stack after skipping a given number of frames.
   *
   * This method is intended to be used for debugging only.  It returns an
   * Object instead of an Array, because the console stringifies arrays by
   * default and that's not what we want.
   *
   * A typical call might look like...
   *
   *    console.log('Something wicked this way came', lib.f.getStack());
   *    //                         Notice the comma ^
   *
   * This would print the message to the js console, followed by an object
   * which can be clicked to reveal the stack.
   *
   * @param {number} opt_ignoreFrames The optional number of stack frames to
   *     ignore.  The actual 'getStack' call is always ignored.
   */
  lib.f.getStack = function(opt_ignoreFrames) {
    var ignoreFrames = opt_ignoreFrames ? opt_ignoreFrames + 2 : 2;
  
    var stackArray;
  
    try {
      throw new Error();
    } catch (ex) {
      stackArray = ex.stack.split('\n');
    }
  
    var stackObject = {};
    for (var i = ignoreFrames; i < stackArray.length; i++) {
      stackObject[i - ignoreFrames] = stackArray[i].replace(/^\s*at\s+/, '');
    }
  
    return stackObject;
  };
  
  /**
   * Divides the two numbers and floors the results, unless the remainder is less
   * than an incredibly small value, in which case it returns the ceiling.
   * This is useful when the number are truncated approximations of longer
   * values, and so doing division with these numbers yields a result incredibly
   * close to a whole number.
   *
   * @param {number} numerator
   * @param {number} denominator
   * @return {number}
   */
  lib.f.smartFloorDivide = function(numerator,  denominator) {
    var val = numerator / denominator;
    var ceiling = Math.ceil(val);
    if (ceiling - val < .0001) {
      return ceiling;
    } else {
      return Math.floor(val);
    }
  };
  
  /**
   * Get a random integer in a range (inclusive).
   *
   * @param {number} min The lowest integer in the range.
   * @param {number} max The highest integer in the range.
   * @return {number} A random number between min & max.
   */
  lib.f.randomInt = function(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  };
  
  /**
   * Get the current OS.
   *
   * @return {Promise<string>} A promise that resolves to a constant in
   *     runtime.PlatformOs.
   */
  lib.f.getOs = function() {
    // Try the brower extensions API.
    if (window.browser && browser.runtime && browser.runtime.getPlatformInfo)
      return browser.runtime.getPlatformInfo().then((info) => info.os);
  
    // Use the native Chrome API if available.
    if (window.chrome && chrome.runtime && chrome.runtime.getPlatformInfo) {
      return new Promise((resolve, reject) =>
          chrome.runtime.getPlatformInfo((info) => resolve(info.os)));
    }
  
    // Fallback logic.  Capture the major OS's.  The rest should support the
    // browser API above.
    if (window.navigator && navigator.userAgent) {
      const ua = navigator.userAgent;
      if (ua.includes('Mac OS X'))
        return Promise.resolve('mac');
      else if (ua.includes('CrOS'))
        return Promise.resolve('cros');
      else if (ua.includes('Linux'))
        return Promise.resolve('linux');
      else if (ua.includes('Android'))
        return Promise.resolve('android');
      else if (ua.includes('Windows'))
        return Promise.resolve('windows');
    }
  
    // Still here?  No idea.
    return Promise.reject(null);
  };
  
  /**
   * Get the current Chrome milestone version.
   *
   * @return {number} The milestone number if we're running on Chrome, else NaN.
   */
  lib.f.getChromeMilestone = function() {
    if (window.navigator && navigator.userAgent) {
      const ary = navigator.userAgent.match(/\sChrome\/(\d+)/);
      if (ary)
        return parseInt(ary[1]);
    }
  
    // Returning NaN will make all number comparisons fail.
    return NaN;
  };
  // SOURCE FILE: libdot/js/lib_utf8.js
  // Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  'use strict';
  
  // TODO(davidben): When the string encoding API is implemented,
  // replace this with the native in-browser implementation.
  //
  // https://wiki.whatwg.org/wiki/StringEncoding
  // https://encoding.spec.whatwg.org/
  
  /**
   * A stateful UTF-8 decoder.
   */
  lib.UTF8Decoder = function() {
    // The number of bytes left in the current sequence.
    this.bytesLeft = 0;
    // The in-progress code point being decoded, if bytesLeft > 0.
    this.codePoint = 0;
    // The lower bound on the final code point, if bytesLeft > 0.
    this.lowerBound = 0;
  };
  
  /**
   * Decodes a some UTF-8 data, taking into account state from previous
   * data streamed through the encoder.
   *
   * @param {String} str data to decode, represented as a JavaScript
   *     String with each code unit representing a byte between 0x00 to
   *     0xFF.
   * @return {String} The data decoded into a JavaScript UTF-16 string.
   */
  lib.UTF8Decoder.prototype.decode = function(str) {
    var ret = '';
    for (var i = 0; i < str.length; i++) {
      var c = str.charCodeAt(i);
      if (this.bytesLeft == 0) {
        if (c <= 0x7F) {
          ret += str.charAt(i);
        } else if (0xC0 <= c && c <= 0xDF) {
          this.codePoint = c - 0xC0;
          this.bytesLeft = 1;
          this.lowerBound = 0x80;
        } else if (0xE0 <= c && c <= 0xEF) {
          this.codePoint = c - 0xE0;
          this.bytesLeft = 2;
          this.lowerBound = 0x800;
        } else if (0xF0 <= c && c <= 0xF7) {
          this.codePoint = c - 0xF0;
          this.bytesLeft = 3;
          this.lowerBound = 0x10000;
        } else if (0xF8 <= c && c <= 0xFB) {
          this.codePoint = c - 0xF8;
          this.bytesLeft = 4;
          this.lowerBound = 0x200000;
        } else if (0xFC <= c && c <= 0xFD) {
          this.codePoint = c - 0xFC;
          this.bytesLeft = 5;
          this.lowerBound = 0x4000000;
        } else {
          ret += '\ufffd';
        }
      } else {
        if (0x80 <= c && c <= 0xBF) {
          this.bytesLeft--;
          this.codePoint = (this.codePoint << 6) + (c - 0x80);
          if (this.bytesLeft == 0) {
            // Got a full sequence. Check if it's within bounds and
            // filter out surrogate pairs.
            var codePoint = this.codePoint;
            if (codePoint < this.lowerBound
                || (0xD800 <= codePoint && codePoint <= 0xDFFF)
                || codePoint > 0x10FFFF) {
              ret += '\ufffd';
            } else {
              // Encode as UTF-16 in the output.
              if (codePoint < 0x10000) {
                ret += String.fromCharCode(codePoint);
              } else {
                // Surrogate pair.
                codePoint -= 0x10000;
                ret += String.fromCharCode(
                  0xD800 + ((codePoint >>> 10) & 0x3FF),
                  0xDC00 + (codePoint & 0x3FF));
              }
            }
          }
        } else {
          // Too few bytes in multi-byte sequence. Rewind stream so we
          // don't lose the next byte.
          ret += '\ufffd';
          this.bytesLeft = 0;
          i--;
        }
      }
    }
    return ret;
  };
  
  /**
   * Decodes UTF-8 data. This is a convenience function for when all the
   * data is already known.
   *
   * @param {String} str data to decode, represented as a JavaScript
   *     String with each code unit representing a byte between 0x00 to
   *     0xFF.
   * @return {String} The data decoded into a JavaScript UTF-16 string.
   */
  lib.decodeUTF8 = function(utf8) {
    return (new lib.UTF8Decoder()).decode(utf8);
  };
  
  /**
   * Encodes a UTF-16 string into UTF-8.
   *
   * TODO(davidben): Do we need a stateful version of this that can
   * handle a surrogate pair split in two calls? What happens if a
   * keypress event would have contained a character outside the BMP?
   *
   * @param {String} str The string to encode.
   * @return {String} The string encoded as UTF-8, as a JavaScript
   *     string with bytes represented as code units from 0x00 to 0xFF.
   */
  lib.encodeUTF8 = function(str) {
    var ret = '';
    for (var i = 0; i < str.length; i++) {
      // Get a unicode code point out of str.
      var c = str.charCodeAt(i);
      if (0xDC00 <= c && c <= 0xDFFF) {
        c = 0xFFFD;
      } else if (0xD800 <= c && c <= 0xDBFF) {
        if (i+1 < str.length) {
          var d = str.charCodeAt(i+1);
          if (0xDC00 <= d && d <= 0xDFFF) {
            // Swallow a surrogate pair.
            c = 0x10000 + ((c & 0x3FF) << 10) + (d & 0x3FF);
            i++;
          } else {
            c = 0xFFFD;
          }
        } else {
          c = 0xFFFD;
        }
      }
  
      // Encode c in UTF-8.
      var bytesLeft;
      if (c <= 0x7F) {
        ret += str.charAt(i);
        continue;
      } else if (c <= 0x7FF) {
        ret += String.fromCharCode(0xC0 | (c >>> 6));
        bytesLeft = 1;
      } else if (c <= 0xFFFF) {
        ret += String.fromCharCode(0xE0 | (c >>> 12));
        bytesLeft = 2;
      } else /* if (c <= 0x10FFFF) */ {
        ret += String.fromCharCode(0xF0 | (c >>> 18));
        bytesLeft = 3;
      }
  
      while (bytesLeft > 0) {
        bytesLeft--;
        ret += String.fromCharCode(0x80 | ((c >>> (6 * bytesLeft)) & 0x3F));
      }
    }
    return ret;
  };
  // SOURCE FILE: hterm/js/hterm.js
  // Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  'use strict';
  
  lib.rtdep('lib.Storage');
  
  /**
   * @fileoverview Declares the hterm.* namespace and some basic shared utilities
   * that are too small to deserve dedicated files.
   */
  var hterm = {};
  
  /**
   * The type of window hosting hterm.
   *
   * This is set as part of hterm.init().  The value is invalid until
   * initialization completes.
   */
  hterm.windowType = null;
  
  /**
   * The OS we're running under.
   *
   * Used when setting up OS-specific behaviors.
   *
   * This is set as part of hterm.init().  The value is invalid until
   * initialization completes.
   */
  hterm.os = null;
  
  /**
   * Warning message to display in the terminal when browser zoom is enabled.
   *
   * You can replace it with your own localized message.
   */
  hterm.zoomWarningMessage = 'ZOOM != 100%';
  
  /**
   * Brief overlay message displayed when text is copied to the clipboard.
   *
   * By default it is the unicode BLACK SCISSORS character, but you can
   * replace it with your own localized message.
   *
   * This is only displayed when the 'enable-clipboard-notice' preference
   * is enabled.
   */
  hterm.notifyCopyMessage = '\u2702';
  
  
  /**
   * Text shown in a desktop notification for the terminal
   * bell.  \u226a is a unicode EIGHTH NOTE, %(title) will
   * be replaced by the terminal title.
   */
  hterm.desktopNotificationTitle = '\u266A %(title) \u266A';
  
  /**
   * List of known hterm test suites.
   *
   * A test harness should ensure that they all exist before running.
   */
  hterm.testDeps = ['hterm.ScrollPort.Tests', 'hterm.Screen.Tests',
                    'hterm.Terminal.Tests', 'hterm.VT.Tests',
                    'hterm.VT.CannedTests'];
  
  /**
   * The hterm init function, registered with lib.registerInit().
   *
   * This is called during lib.init().
   *
   * @param {function} onInit The function lib.init() wants us to invoke when
   *     initialization is complete.
   */
  lib.registerInit('hterm', function(onInit) {
    function initOs(os) {
      hterm.os = os;
  
      onInit();
    }
  
    function initMessageManager() {
      lib.f.getAcceptLanguages((languages) => {
        if (!hterm.messageManager)
          hterm.messageManager = new lib.MessageManager(languages);
  
        // If OS detection fails, then we'll still set the value to something.
        // The OS logic in hterm tends to be best effort anyways.
        lib.f.getOs().then(initOs).catch(initOs);
      });
    }
  
    function onWindow(window) {
      hterm.windowType = window.type;
      initMessageManager();
    }
  
    function onTab(tab) {
      if (tab && window.chrome) {
        chrome.windows.get(tab.windowId, null, onWindow);
      } else {
        // TODO(rginda): This is where we end up for a v1 app's background page.
        // Maybe windowType = 'none' would be more appropriate, or something.
        hterm.windowType = 'normal';
        initMessageManager();
      }
    }
  
    if (!hterm.defaultStorage) {
      if (window.chrome && chrome.storage && chrome.storage.sync) {
        hterm.defaultStorage = new lib.Storage.Chrome(chrome.storage.sync);
      } else {
        hterm.defaultStorage = new lib.Storage.Local();
      }
    }
  
    // The chrome.tabs API is not supported in packaged apps, and detecting if
    // you're a packaged app is a little awkward.
    var isPackagedApp = false;
    if (window.chrome && chrome.runtime && chrome.runtime.getManifest) {
      var manifest = chrome.runtime.getManifest();
      isPackagedApp = manifest.app && manifest.app.background;
    }
  
    if (isPackagedApp) {
      // Packaged apps are never displayed in browser tabs.
      setTimeout(onWindow.bind(null, {type: 'popup'}), 0);
    } else {
      if (window.chrome && chrome.tabs) {
        // The getCurrent method gets the tab that is "currently running", not the
        // topmost or focused tab.
        chrome.tabs.getCurrent(onTab);
      } else {
        setTimeout(onWindow.bind(null, {type: 'normal'}), 0);
      }
    }
  });
  
  /**
   * Return decimal { width, height } for a given dom node.
   */
  hterm.getClientSize = function(dom) {
    return dom.getBoundingClientRect();
  };
  
  /**
   * Return decimal width for a given dom node.
   */
  hterm.getClientWidth = function(dom) {
    return dom.getBoundingClientRect().width;
  };
  
  /**
   * Return decimal height for a given dom node.
   */
  hterm.getClientHeight = function(dom) {
    return dom.getBoundingClientRect().height;
  };
  
  /**
   * Copy the current selection to the system clipboard.
   *
   * @param {HTMLDocument} The document with the selection to copy.
   */
  hterm.copySelectionToClipboard = function(document) {
    try {
      document.execCommand('copy');
    } catch (firefoxException) {
      // Ignore this. FF throws an exception if there was an error, even though
      // the spec says just return false.
    }
  };
  
  /**
   * Paste the system clipboard into the element with focus.
   *
   * Note: In Chrome/Firefox app/extension environments, you'll need the
   * "clipboardRead" permission.  In other environments, this might always
   * fail as the browser frequently blocks access for security reasons.
   *
   * @param {HTMLDocument} The document to paste into.
   * @return {boolean} True if the paste succeeded.
   */
  hterm.pasteFromClipboard = function(document) {
    try {
      return document.execCommand('paste');
    } catch (firefoxException) {
      // Ignore this.  FF 40 and older would incorrectly throw an exception if
      // there was an error instead of returning false.
      return false;
    }
  };
  
  /**
   * Return a formatted message in the current locale.
   *
   * @param {string} name The name of the message to return.
   * @param {Array<string>=} args The message arguments, if required.
   * @param {string=} string The default message text.
   * @return {string} The localized message.
   */
  hterm.msg = function(name, args = [], string) {
    return hterm.messageManager.get('HTERM_' + name, args, string);
  };
  
  /**
   * Create a new notification.
   *
   * @param {Object} params Various parameters for the notification.
   * @param {string} params.title The title (defaults to the window's title).
   * @param {string} params.body The message body (main text).
   */
  hterm.notify = function(params) {
    var def = (curr, fallback) => curr !== undefined ? curr : fallback;
    if (params === undefined || params === null)
      params = {};
  
    // Merge the user's choices with the default settings.  We don't take it
    // directly in case it was stuffed with excess junk.
    var options = {
        'body': params.body,
        'icon': def(params.icon, lib.resource.getDataUrl('hterm/images/icon-96')),
    }
  
    var title = def(params.title, window.document.title);
    if (!title)
      title = 'hterm';
    title = lib.f.replaceVars(hterm.desktopNotificationTitle, {'title': title});
  
    var n = new Notification(title, options);
    n.onclick = function() {
      window.focus();
      this.close();
    };
    return n;
  };
  
  /**
   * Launches url in a new tab.
   *
   * @param {string} url URL to launch in a new tab.
   */
  hterm.openUrl = function(url) {
    if (window.chrome && chrome.browser && chrome.browser.openTab) {
      // For Chrome v2 apps, we need to use this API to properly open windows.
      chrome.browser.openTab({'url': url});
    } else {
      const win = window.open(url, '_blank');
      win.focus();
    }
  }
  
  /**
   * Constructor for a hterm.Size record.
   *
   * Instances of this class have public read/write members for width and height.
   *
   * @param {integer} width The width of this record.
   * @param {integer} height The height of this record.
   */
  hterm.Size = function(width, height) {
    this.width = width;
    this.height = height;
  };
  
  /**
   * Adjust the width and height of this record.
   *
   * @param {integer} width The new width of this record.
   * @param {integer} height The new height of this record.
   */
  hterm.Size.prototype.resize = function(width, height) {
    this.width = width;
    this.height = height;
  };
  
  /**
   * Return a copy of this record.
   *
   * @return {hterm.Size} A new hterm.Size instance with the same width and
   * height.
   */
  hterm.Size.prototype.clone = function() {
    return new hterm.Size(this.width, this.height);
  };
  
  /**
   * Set the height and width of this instance based on another hterm.Size.
   *
   * @param {hterm.Size} that The object to copy from.
   */
  hterm.Size.prototype.setTo = function(that) {
    this.width = that.width;
    this.height = that.height;
  };
  
  /**
   * Test if another hterm.Size instance is equal to this one.
   *
   * @param {hterm.Size} that The other hterm.Size instance.
   * @return {boolean} True if both instances have the same width/height, false
   *     otherwise.
   */
  hterm.Size.prototype.equals = function(that) {
    return this.width == that.width && this.height == that.height;
  };
  
  /**
   * Return a string representation of this instance.
   *
   * @return {string} A string that identifies the width and height of this
   *     instance.
   */
  hterm.Size.prototype.toString = function() {
    return '[hterm.Size: ' + this.width + ', ' + this.height + ']';
  };
  
  /**
   * Constructor for a hterm.RowCol record.
   *
   * Instances of this class have public read/write members for row and column.
   *
   * This class includes an 'overflow' bit which is use to indicate that an
   * attempt has been made to move the cursor column passed the end of the
   * screen.  When this happens we leave the cursor column set to the last column
   * of the screen but set the overflow bit.  In this state cursor movement
   * happens normally, but any attempt to print new characters causes a cr/lf
   * first.
   *
   * @param {integer} row The row of this record.
   * @param {integer} column The column of this record.
   * @param {boolean} opt_overflow Optional boolean indicating that the RowCol
   *     has overflowed.
   */
  hterm.RowCol = function(row, column, opt_overflow) {
    this.row = row;
    this.column = column;
    this.overflow = !!opt_overflow;
  };
  
  /**
   * Adjust the row and column of this record.
   *
   * @param {integer} row The new row of this record.
   * @param {integer} column The new column of this record.
   * @param {boolean} opt_overflow Optional boolean indicating that the RowCol
   *     has overflowed.
   */
  hterm.RowCol.prototype.move = function(row, column, opt_overflow) {
    this.row = row;
    this.column = column;
    this.overflow = !!opt_overflow;
  };
  
  /**
   * Return a copy of this record.
   *
   * @return {hterm.RowCol} A new hterm.RowCol instance with the same row and
   * column.
   */
  hterm.RowCol.prototype.clone = function() {
    return new hterm.RowCol(this.row, this.column, this.overflow);
  };
  
  /**
   * Set the row and column of this instance based on another hterm.RowCol.
   *
   * @param {hterm.RowCol} that The object to copy from.
   */
  hterm.RowCol.prototype.setTo = function(that) {
    this.row = that.row;
    this.column = that.column;
    this.overflow = that.overflow;
  };
  
  /**
   * Test if another hterm.RowCol instance is equal to this one.
   *
   * @param {hterm.RowCol} that The other hterm.RowCol instance.
   * @return {boolean} True if both instances have the same row/column, false
   *     otherwise.
   */
  hterm.RowCol.prototype.equals = function(that) {
    return (this.row == that.row && this.column == that.column &&
            this.overflow == that.overflow);
  };
  
  /**
   * Return a string representation of this instance.
   *
   * @return {string} A string that identifies the row and column of this
   *     instance.
   */
  hterm.RowCol.prototype.toString = function() {
    return ('[hterm.RowCol: ' + this.row + ', ' + this.column + ', ' +
            this.overflow + ']');
  };
  hterm.Keyboard = { KeyActions: {} };
  // SOURCE FILE: hterm/js/hterm_parser.js
  // Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  'use strict';
  
  lib.rtdep('hterm.Keyboard.KeyActions');
  
  /**
   * @constructor
   * Parses the key definition syntax used for user keyboard customizations.
   */
  hterm.Parser = function() {
    /**
     * @type {string} The source string.
     */
    this.source = '';
  
    /**
     * @type {number} The current position.
     */
    this.pos = 0;
  
    /**
     * @type {string?} The character at the current position.
     */
    this.ch = null;
  };
  
  hterm.Parser.prototype.error = function(message) {
    return new Error('Parse error at ' + this.pos + ': ' + message);
  };
  
  hterm.Parser.prototype.isComplete = function() {
    return this.pos == this.source.length;
  };
  
  hterm.Parser.prototype.reset = function(source, opt_pos) {
    this.source = source;
    this.pos = opt_pos || 0;
    this.ch = source.substr(0, 1);
  };
  
  /**
   * Parse a key sequence.
   *
   * A key sequence is zero or more of the key modifiers defined in
   * hterm.Parser.identifiers.modifierKeys followed by a key code.  Key
   * codes can be an integer or an identifier from
   * hterm.Parser.identifiers.keyCodes.  Modifiers and keyCodes should be joined
   * by the dash character.
   *
   * An asterisk "*" can be used to indicate that the unspecified modifiers
   * are optional.
   *
   * For example:
   *   A: Matches only an unmodified "A" character.
   *   65: Same as above.
   *   0x41: Same as above.
   *   Ctrl-A: Matches only Ctrl-A.
   *   Ctrl-65: Same as above.
   *   Ctrl-0x41: Same as above.
   *   Ctrl-Shift-A: Matches only Ctrl-Shift-A.
   *   Ctrl-*-A: Matches Ctrl-A, as well as any other key sequence that includes
   *     at least the Ctrl and A keys.
   *
   * @return {Object} An object with shift, ctrl, alt, meta, keyCode
   *   properties.
   */
  hterm.Parser.prototype.parseKeySequence = function() {
    var rv = {
      keyCode: null
    };
  
    for (var k in hterm.Parser.identifiers.modifierKeys) {
      rv[hterm.Parser.identifiers.modifierKeys[k]] = false;
    }
  
    while (this.pos < this.source.length) {
      this.skipSpace();
  
      var token = this.parseToken();
      if (token.type == 'integer') {
        rv.keyCode = token.value;
  
      } else if (token.type == 'identifier') {
        var ucValue = token.value.toUpperCase();
        if (ucValue in hterm.Parser.identifiers.modifierKeys &&
            hterm.Parser.identifiers.modifierKeys.hasOwnProperty(ucValue)) {
          var mod = hterm.Parser.identifiers.modifierKeys[ucValue];
          if (rv[mod] && rv[mod] != '*')
            throw this.error('Duplicate modifier: ' + token.value);
          rv[mod] = true;
  
        } else if (ucValue in hterm.Parser.identifiers.keyCodes &&
                   hterm.Parser.identifiers.keyCodes.hasOwnProperty(ucValue)) {
          rv.keyCode = hterm.Parser.identifiers.keyCodes[ucValue];
  
        } else {
          throw this.error('Unknown key: ' + token.value);
        }
  
      } else if (token.type == 'symbol') {
        if (token.value == '*') {
          for (var id in hterm.Parser.identifiers.modifierKeys) {
            var p = hterm.Parser.identifiers.modifierKeys[id];
            if (!rv[p])
              rv[p] =  '*';
          }
        } else {
          throw this.error('Unexpected symbol: ' + token.value);
        }
      } else {
        throw this.error('Expected integer or identifier');
      }
  
      this.skipSpace();
  
      if (this.ch != '-')
        break;
  
      if (rv.keyCode != null)
        throw this.error('Extra definition after target key');
  
      this.advance(1);
    }
  
    if (rv.keyCode == null)
      throw this.error('Missing target key');
  
    return rv;
  };
  
  hterm.Parser.prototype.parseKeyAction = function() {
    this.skipSpace();
  
    var token = this.parseToken();
  
    if (token.type == 'string')
      return token.value;
  
    if (token.type == 'identifier') {
      if (token.value in hterm.Parser.identifiers.actions &&
          hterm.Parser.identifiers.actions.hasOwnProperty(token.value))
        return hterm.Parser.identifiers.actions[token.value];
  
      throw this.error('Unknown key action: ' + token.value);
    }
  
    throw this.error('Expected string or identifier');
  
  };
  
  hterm.Parser.prototype.peekString = function() {
    return this.ch == '\'' || this.ch == '"';
  };
  
  hterm.Parser.prototype.peekIdentifier = function() {
    return this.ch.match(/[a-z_]/i);
  };
  
  hterm.Parser.prototype.peekInteger = function() {
    return this.ch.match(/[0-9]/);
  };
  
  hterm.Parser.prototype.parseToken = function() {
    if (this.ch == '*') {
      var rv = {type: 'symbol', value: this.ch};
      this.advance(1);
      return rv;
    }
  
    if (this.peekIdentifier())
      return {type: 'identifier', value: this.parseIdentifier()};
  
    if (this.peekString())
      return {type: 'string', value: this.parseString()};
  
    if (this.peekInteger())
      return {type: 'integer', value: this.parseInteger()};
  
  
    throw this.error('Unexpected token');
  };
  
  hterm.Parser.prototype.parseIdentifier = function() {
    if (!this.peekIdentifier())
      throw this.error('Expected identifier');
  
    return this.parsePattern(/[a-z0-9_]+/ig);
  };
  
  hterm.Parser.prototype.parseInteger = function() {
    var base = 10;
  
    if (this.ch == '0' && this.pos < this.source.length - 1 &&
        this.source.substr(this.pos + 1, 1) == 'x') {
      return parseInt(this.parsePattern(/0x[0-9a-f]+/gi));
    }
  
    return parseInt(this.parsePattern(/\d+/g));
  };
  
  /**
   * Parse a single or double quoted string.
   *
   * The current position should point at the initial quote character.  Single
   * quoted strings will be treated literally, double quoted will process escapes.
   *
   * TODO(rginda): Variable interpolation.
   *
   * @param {ParseState} parseState
   * @param {string} quote A single or double-quote character.
   * @return {string}
   */
  hterm.Parser.prototype.parseString = function() {
    var result = '';
  
    var quote = this.ch;
    if (quote != '"' && quote != '\'')
      throw this.error('String expected');
  
    this.advance(1);
  
    var re = new RegExp('[\\\\' + quote + ']', 'g');
  
    while (this.pos < this.source.length) {
      re.lastIndex = this.pos;
      if (!re.exec(this.source))
        throw this.error('Unterminated string literal');
  
      result += this.source.substring(this.pos, re.lastIndex - 1);
  
      this.advance(re.lastIndex - this.pos - 1);
  
      if (quote == '"' && this.ch == '\\') {
        this.advance(1);
        result += this.parseEscape();
        continue;
      }
  
      if (quote == '\'' && this.ch == '\\') {
        result += this.ch;
        this.advance(1);
        continue;
      }
  
      if (this.ch == quote) {
        this.advance(1);
        return result;
      }
    }
  
    throw this.error('Unterminated string literal');
  };
  
  
  /**
   * Parse an escape code from the current position (which should point to
   * the first character AFTER the leading backslash.)
   *
   * @return {string}
   */
  hterm.Parser.prototype.parseEscape = function() {
    var map = {
      '"': '"',
      '\'': '\'',
      '\\': '\\',
      'a': '\x07',
      'b': '\x08',
      'e': '\x1b',
      'f': '\x0c',
      'n': '\x0a',
      'r': '\x0d',
      't': '\x09',
      'v': '\x0b',
      'x': function() {
        var value = this.parsePattern(/[a-z0-9]{2}/ig);
        return String.fromCharCode(parseInt(value, 16));
      },
      'u': function() {
        var value = this.parsePattern(/[a-z0-9]{4}/ig);
        return String.fromCharCode(parseInt(value, 16));
      }
    };
  
    if (!(this.ch in map && map.hasOwnProperty(this.ch)))
      throw this.error('Unknown escape: ' + this.ch);
  
    var value = map[this.ch];
    this.advance(1);
  
    if (typeof value == 'function')
      value = value.call(this);
  
    return value;
  };
  
  /**
   * Parse the given pattern starting from the current position.
   *
   * @param {RegExp} pattern A pattern representing the characters to span.  MUST
   *   include the "global" RegExp flag.
   * @return {string}
   */
  hterm.Parser.prototype.parsePattern = function(pattern) {
    if (!pattern.global)
      throw this.error('Internal error: Span patterns must be global');
  
    pattern.lastIndex = this.pos;
    var ary = pattern.exec(this.source);
  
    if (!ary || pattern.lastIndex - ary[0].length != this.pos)
      throw this.error('Expected match for: ' + pattern);
  
    this.pos = pattern.lastIndex - 1;
    this.advance(1);
  
    return ary[0];
  };
  
  
  /**
   * Advance the current position.
   *
   * @param {number} count
   */
  hterm.Parser.prototype.advance = function(count) {
    this.pos += count;
    this.ch = this.source.substr(this.pos, 1);
  };
  
  /**
   * @param {string=} opt_expect A list of valid non-whitespace characters to
   *   terminate on.
   * @return {void}
   */
  hterm.Parser.prototype.skipSpace = function(opt_expect) {
    if (!/\s/.test(this.ch))
      return;
  
    var re = /\s+/gm;
    re.lastIndex = this.pos;
  
    var source = this.source;
    if (re.exec(source))
      this.pos = re.lastIndex;
  
    this.ch = this.source.substr(this.pos, 1);
  
    if (opt_expect) {
      if (this.ch.indexOf(opt_expect) == -1) {
        throw this.error('Expected one of ' + opt_expect + ', found: ' +
            this.ch);
      }
    }
  };
  // SOURCE FILE: hterm/js/hterm_text_attributes.js
  // Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  'use strict';
  
  lib.rtdep('lib.colors');
  
  /**
   * Constructor for TextAttribute objects.
   *
   * These objects manage a set of text attributes such as foreground/
   * background color, bold, faint, italic, blink, underline, and strikethrough.
   *
   * TextAttribute instances can be used to construct a DOM container implementing
   * the current attributes, or to test an existing DOM container for
   * compatibility with the current attributes.
   *
   * @constructor
   * @param {HTMLDocument} document The parent document to use when creating
   *     new DOM containers.
   */
  hterm.TextAttributes = function(document) {
    this.document_ = document;
    // These variables contain the source of the color as either:
    // SRC_DEFAULT  (use context default)
    // rgb(...)     (true color form)
    // number       (representing the index from color palette to use)
    this.foregroundSource = this.SRC_DEFAULT;
    this.backgroundSource = this.SRC_DEFAULT;
    this.underlineSource = this.SRC_DEFAULT;
  
    // These properties cache the value in the color table, but foregroundSource
    // and backgroundSource contain the canonical values.
    this.foreground = this.DEFAULT_COLOR;
    this.background = this.DEFAULT_COLOR;
    this.underlineColor = this.DEFAULT_COLOR;
  
    this.defaultForeground = 'rgb(255, 255, 255)';
    this.defaultBackground = 'rgb(0, 0, 0)';
  
    // Any attributes added here that do not default to falsey (e.g. undefined or
    // null) require a bit more care.  createContainer has to always attach the
    // attribute so matchesContainer can work correctly.
    this.bold = false;
    this.faint = false;
    this.italic = false;
    this.blink = false;
    this.underline = false;
    this.strikethrough = false;
    this.inverse = false;
    this.invisible = false;
    this.wcNode = false;
    this.asciiNode = true;
    this.tileData = null;
    this.uri = null;
    this.uriId = null;
  
    this.colorPalette = null;
    this.resetColorPalette();
  };
  
  /**
   * If false, we ignore the bold attribute.
   *
   * This is used for fonts that have a bold version that is a different size
   * than the normal weight version.
   */
  hterm.TextAttributes.prototype.enableBold = true;
  
  /**
   * If true, use bright colors (if available) for bold text.
   *
   * This setting is independent of the enableBold setting.
   */
  hterm.TextAttributes.prototype.enableBoldAsBright = true;
  
  /**
   * A sentinel constant meaning "whatever the default color is in this context".
   */
  hterm.TextAttributes.prototype.DEFAULT_COLOR = lib.f.createEnum('');
  
  /**
   * A constant string used to specify that source color is context default.
   */
  hterm.TextAttributes.prototype.SRC_DEFAULT = 'default';
  
  /**
   * The document object which should own the DOM nodes created by this instance.
   *
   * @param {HTMLDocument} document The parent document.
   */
  hterm.TextAttributes.prototype.setDocument = function(document) {
    this.document_ = document;
  };
  
  /**
   * Create a deep copy of this object.
   *
   * @return {hterm.TextAttributes} A deep copy of this object.
   */
  hterm.TextAttributes.prototype.clone = function() {
    var rv = new hterm.TextAttributes(null);
  
    for (var key in this) {
      rv[key] = this[key];
    }
  
    rv.colorPalette = this.colorPalette.concat();
    return rv;
  };
  
  /**
   * Reset the current set of attributes.
   *
   * This does not affect the palette.  Use resetColorPalette() for that.
   * It also doesn't affect the tile data, it's not meant to.
   */
  hterm.TextAttributes.prototype.reset = function() {
    this.foregroundSource = this.SRC_DEFAULT;
    this.backgroundSource = this.SRC_DEFAULT;
    this.underlineSource = this.SRC_DEFAULT;
    this.foreground = this.DEFAULT_COLOR;
    this.background = this.DEFAULT_COLOR;
    this.underlineColor = this.DEFAULT_COLOR;
    this.bold = false;
    this.faint = false;
    this.italic = false;
    this.blink = false;
    this.underline = false;
    this.strikethrough = false;
    this.inverse = false;
    this.invisible = false;
    this.wcNode = false;
    this.asciiNode = true;
    this.uri = null;
    this.uriId = null;
  };
  
  /**
   * Reset the color palette to the default state.
   */
  hterm.TextAttributes.prototype.resetColorPalette = function() {
    this.colorPalette = lib.colors.colorPalette.concat();
    this.syncColors();
  };
  
  /**
   * Test if the current attributes describe unstyled text.
   *
   * @return {boolean} True if the current attributes describe unstyled text.
   */
  hterm.TextAttributes.prototype.isDefault = function() {
    return (this.foregroundSource == this.SRC_DEFAULT &&
            this.backgroundSource == this.SRC_DEFAULT &&
            !this.bold &&
            !this.faint &&
            !this.italic &&
            !this.blink &&
            !this.underline &&
            !this.strikethrough &&
            !this.inverse &&
            !this.invisible &&
            !this.wcNode &&
            this.asciiNode &&
            this.tileData == null &&
            this.uri == null);
  };
  
  /**
   * Create a DOM container (a span or a text node) with a style to match the
   * current set of attributes.
   *
   * This method will create a plain text node if the text is unstyled, or
   * an HTML span if the text is styled.  Due to lack of monospace wide character
   * fonts on certain systems (e.g. Chrome OS), we need to put each wide character
   * in a span of CSS class '.wc-node' which has double column width.
   * Each vt_tiledata tile is also represented by a span with a single
   * character, with CSS classes '.tile' and '.tile_<glyph number>'.
   *
   * @param {string} opt_textContent Optional text content for the new container.
   * @return {HTMLNode} An HTML span or text nodes styled to match the current
   *     attributes.
   */
  hterm.TextAttributes.prototype.createContainer = function(opt_textContent) {
    if (this.isDefault()) {
      // Only attach attributes where we need an explicit default for the
      // matchContainer logic below.
      const node = this.document_.createTextNode(opt_textContent);
      node.asciiNode = true;
      return node;
    }
  
    var span = this.document_.createElement('span');
    var style = span.style;
    var classes = [];
  
    if (this.foreground != this.DEFAULT_COLOR)
      style.color = this.foreground;
  
    if (this.background != this.DEFAULT_COLOR)
      style.backgroundColor = this.background;
  
    if (this.enableBold && this.bold)
      style.fontWeight = 'bold';
  
    if (this.faint)
      span.faint = true;
  
    if (this.italic)
      style.fontStyle = 'italic';
  
    if (this.blink) {
      classes.push('blink-node');
      span.blinkNode = true;
    }
  
    let textDecorationLine = '';
    span.underline = this.underline;
    if (this.underline) {
      textDecorationLine += ' underline';
      style.textDecorationStyle = this.underline;
    }
    if (this.underlineSource != this.SRC_DEFAULT)
      style.textDecorationColor = this.underlineColor;
    if (this.strikethrough) {
      textDecorationLine += ' line-through';
      span.strikethrough = true;
    }
    if (textDecorationLine)
      style.textDecorationLine = textDecorationLine;
  
    if (this.wcNode) {
      classes.push('wc-node');
      span.wcNode = true;
    }
    span.asciiNode = this.asciiNode;
  
    if (this.tileData != null) {
      classes.push('tile');
      classes.push('tile_' + this.tileData);
      span.tileNode = true;
    }
  
    if (opt_textContent)
      span.textContent = opt_textContent;
  
    if (this.uri) {
      classes.push('uri-node');
      span.uriId = this.uriId;
      span.title = this.uri;
      span.addEventListener('click', hterm.openUrl.bind(this, this.uri));
    }
  
    if (classes.length)
      span.className = classes.join(' ');
  
    return span;
  };
  
  /**
   * Tests if the provided object (string, span or text node) has the same
   * style as this TextAttributes instance.
   *
   * This indicates that text with these attributes could be inserted directly
   * into the target DOM node.
   *
   * For the purposes of this method, a string is considered a text node.
   *
   * @param {string|HTMLNode} obj The object to test.
   * @return {boolean} True if the provided container has the same style as
   *     this attributes instance.
   */
  hterm.TextAttributes.prototype.matchesContainer = function(obj) {
    if (typeof obj == 'string' || obj.nodeType == Node.TEXT_NODE)
      return this.isDefault();
  
    var style = obj.style;
  
    // We don't want to put multiple characters in a wcNode or a tile.
    // See the comments in createContainer.
    // For attributes that default to false, we do not require that obj have them
    // declared, so always normalize them using !! (to turn undefined into false)
    // in the compares below.
    return (!(this.wcNode || obj.wcNode) &&
            this.asciiNode == obj.asciiNode &&
            !(this.tileData != null || obj.tileNode) &&
            this.uriId == obj.uriId &&
            this.foreground == style.color &&
            this.background == style.backgroundColor &&
            this.underlineColor == style.textDecorationColor &&
            (this.enableBold && this.bold) == !!style.fontWeight &&
            this.blink == !!obj.blinkNode &&
            this.italic == !!style.fontStyle &&
            this.underline == obj.underline &&
            !!this.strikethrough == !!obj.strikethrough);
  };
  
  hterm.TextAttributes.prototype.setDefaults = function(foreground, background) {
    this.defaultForeground = foreground;
    this.defaultBackground = background;
  
    this.syncColors();
  };
  
  /**
   * Updates foreground and background properties based on current indices and
   * other state.
   *
   * @param {string} terminalForeground The terminal foreground color for use as
   *     inverse text background.
   * @param {string} terminalBackground The terminal background color for use as
   *     inverse text foreground.
   *
   */
  hterm.TextAttributes.prototype.syncColors = function() {
    function getBrightIndex(i) {
      if (i < 8) {
        // If the color is from the lower half of the ANSI 16, add 8.
        return i + 8;
      }
  
      // If it's not from the 16 color palette, ignore bold requests.  This
      // matches the behavior of gnome-terminal.
      return i;
    }
  
    var foregroundSource = this.foregroundSource;
    var backgroundSource = this.backgroundSource;
    var defaultForeground = this.DEFAULT_COLOR;
    var defaultBackground = this.DEFAULT_COLOR;
  
    if (this.inverse) {
      foregroundSource = this.backgroundSource;
      backgroundSource = this.foregroundSource;
      // We can't inherit the container's color anymore.
      defaultForeground = this.defaultBackground;
      defaultBackground = this.defaultForeground;
    }
  
    if (this.enableBoldAsBright && this.bold) {
      if (Number.isInteger(foregroundSource)) {
        foregroundSource = getBrightIndex(foregroundSource);
      }
    }
  
    if (foregroundSource == this.SRC_DEFAULT)
      this.foreground = defaultForeground;
    else if (Number.isInteger(foregroundSource))
      this.foreground = this.colorPalette[foregroundSource];
    else
      this.foreground = foregroundSource;
  
    if (this.faint) {
      var colorToMakeFaint = ((this.foreground == this.DEFAULT_COLOR) ?
                              this.defaultForeground : this.foreground);
      this.foreground = lib.colors.mix(colorToMakeFaint, 'rgb(0, 0, 0)', 0.3333);
    }
  
    if (backgroundSource == this.SRC_DEFAULT)
      this.background = defaultBackground;
    else if (Number.isInteger(backgroundSource))
      this.background = this.colorPalette[backgroundSource];
    else
      this.background = backgroundSource;
  
    // Process invisible settings last to keep it simple.
    if (this.invisible)
      this.foreground = this.background;
  
    if (this.underlineSource == this.SRC_DEFAULT)
      this.underlineColor = '';
    else if (Number.isInteger(this.underlineSource))
      this.underlineColor = this.colorPalette[this.underlineSource];
    else
      this.underlineColor = this.underlineSource;
  };
  
  /**
   * Static method used to test if the provided objects (strings, spans or
   * text nodes) have the same style.
   *
   * For the purposes of this method, a string is considered a text node.
   *
   * @param {string|HTMLNode} obj1 An object to test.
   * @param {string|HTMLNode} obj2 Another object to test.
   * @return {boolean} True if the containers have the same style.
   */
  hterm.TextAttributes.containersMatch = function(obj1, obj2) {
    if (typeof obj1 == 'string')
      return hterm.TextAttributes.containerIsDefault(obj2);
  
    if (obj1.nodeType != obj2.nodeType)
      return false;
  
    if (obj1.nodeType == Node.TEXT_NODE)
      return true;
  
    var style1 = obj1.style;
    var style2 = obj2.style;
  
    return (style1.color == style2.color &&
            style1.backgroundColor == style2.backgroundColor &&
            style1.backgroundColor == style2.backgroundColor &&
            style1.fontWeight == style2.fontWeight &&
            style1.fontStyle == style2.fontStyle &&
            style1.textDecoration == style2.textDecoration &&
            style1.textDecorationColor == style2.textDecorationColor &&
            style1.textDecorationStyle == style2.textDecorationStyle &&
            style1.textDecorationLine == style2.textDecorationLine);
  };
  
  /**
   * Static method to test if a given DOM container represents unstyled text.
   *
   * For the purposes of this method, a string is considered a text node.
   *
   * @param {string|HTMLNode} obj1 An object to test.
   * @return {boolean} True if the object is unstyled.
   */
  hterm.TextAttributes.containerIsDefault = function(obj) {
    return typeof obj == 'string'  || obj.nodeType == Node.TEXT_NODE;
  };
  
  /**
   * Static method to get the column width of a node's textContent.
   *
   * @param {HTMLElement} node The HTML element to get the width of textContent
   *     from.
   * @return {integer} The column width of the node's textContent.
   */
  hterm.TextAttributes.nodeWidth = function(node) {
    if (!node.asciiNode) {
      return lib.wc.strWidth(node.textContent);
    } else {
      return node.textContent.length;
    }
  }
  
  /**
   * Static method to get the substr of a node's textContent.  The start index
   * and substr width are computed in column width.
   *
   * @param {HTMLElement} node The HTML element to get the substr of textContent
   *     from.
   * @param {integer} start The starting offset in column width.
   * @param {integer} width The width to capture in column width.
   * @return {integer} The extracted substr of the node's textContent.
   */
  hterm.TextAttributes.nodeSubstr = function(node, start, width) {
    if (!node.asciiNode) {
      return lib.wc.substr(node.textContent, start, width);
    } else {
      return node.textContent.substr(start, width);
    }
  }
  
  /**
   * Static method to get the substring based of a node's textContent.  The
   * start index of end index are computed in column width.
   *
   * @param {HTMLElement} node The HTML element to get the substr of textContent
   *     from.
   * @param {integer} start The starting offset in column width.
   * @param {integer} end The ending offset in column width.
   * @return {integer} The extracted substring of the node's textContent.
   */
  hterm.TextAttributes.nodeSubstring = function(node, start, end) {
    if (!node.asciiNode) {
      return lib.wc.substring(node.textContent, start, end);
    } else {
      return node.textContent.substring(start, end);
    }
  };
  
  /**
   * Static method to split a string into contiguous runs of single-width
   * characters and runs of double-width characters.
   *
   * @param {string} str The string to split.
   * @return {Array} An array of objects that contain substrings of str, where
   *     each substring is either a contiguous runs of single-width characters
   *     or a double-width character.  For objects that contain a double-width
   *     character, its wcNode property is set to true.  For objects that contain
   *     only ASCII content, its asciiNode property is set to true.
   */
  hterm.TextAttributes.splitWidecharString = function(str) {
    var rv = [];
    var base = 0, length = 0, wcStrWidth = 0, wcCharWidth;
    var asciiNode = true;
  
    for (var i = 0; i < str.length;) {
      var c = str.codePointAt(i);
      var increment;
      if (c < 128) {
        wcStrWidth += 1;
        length += 1;
        increment = 1;
      } else {
        increment = (c <= 0xffff) ? 1 : 2;
        wcCharWidth = lib.wc.charWidth(c);
        if (wcCharWidth <= 1) {
          wcStrWidth += wcCharWidth;
          length += increment;
          asciiNode = false;
        } else {
          if (length) {
            rv.push({
              str: str.substr(base, length),
              asciiNode: asciiNode,
              wcStrWidth: wcStrWidth,
            });
            asciiNode = true;
            wcStrWidth = 0;
          }
          rv.push({
            str: str.substr(i, increment),
            wcNode: true,
            asciiNode: false,
            wcStrWidth: 2,
          });
          base = i + increment;
          length = 0;
        }
      }
      i += increment;
    }
  
    if (length) {
      rv.push({
        str: str.substr(base, length),
        asciiNode: asciiNode,
        wcStrWidth: wcStrWidth,
      });
    }
  
    return rv;
  };
  // SOURCE FILE: hterm/js/hterm_vt.js
  // Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  'use strict';
  
  lib.rtdep('lib.colors', 'lib.f', 'lib.UTF8Decoder',
            'hterm.VT.CharacterMap');
  
  /**
   * Constructor for the VT escape sequence interpreter.
   *
   * The interpreter operates on a terminal object capable of performing cursor
   * move operations, painting characters, etc.
   *
   * This interpreter is intended to be compatible with xterm, though it
   * ignores some of the more esoteric escape sequences.
   *
   * Control sequences are documented in hterm/doc/ControlSequences.md.
   *
   * @param {hterm.Terminal} terminal Terminal to use with the interpreter.
   */
  hterm.VT = function(terminal) {
    /**
     * The display terminal object associated with this virtual terminal.
     */
    this.terminal = terminal;
  
    terminal.onMouse = this.onTerminalMouse_.bind(this);
    this.mouseReport = this.MOUSE_REPORT_DISABLED;
    this.mouseCoordinates = this.MOUSE_COORDINATES_X10;
  
    // We only want to report mouse moves between cells, not between pixels.
    this.lastMouseDragResponse_ = null;
  
    // Parse state left over from the last parse.  You should use the parseState
    // instance passed into your parse routine, rather than reading
    // this.parseState_ directly.
    this.parseState_ = new hterm.VT.ParseState(this.parseUnknown_);
  
    // Any "leading modifiers" for the escape sequence, such as '?', ' ', or the
    // other modifiers handled in this.parseCSI_.
    this.leadingModifier_ = '';
  
    // Any "trailing modifiers".  Same character set as a leading modifier,
    // except these are found after the numeric arguments.
    this.trailingModifier_ = '';
  
    // Whether or not to respect the escape codes for setting terminal width.
    this.allowColumnWidthChanges_ = false;
  
    // The amount of time we're willing to wait for the end of an OSC sequence.
    this.oscTimeLimit_ = 20000;
  
    // Decoder to maintain UTF-8 decode state.
    this.utf8Decoder_ = new lib.UTF8Decoder();
  
    /**
     * Whether to accept the 8-bit control characters.
     *
     * An 8-bit control character is one with the eighth bit set.  These
     * didn't work on 7-bit terminals so they all have two byte equivalents.
     * Most hosts still only use the two-byte versions.
     *
     * We ignore 8-bit control codes by default.  This is in order to avoid
     * issues with "accidental" usage of codes that need to be terminated.
     * The "accident" usually involves cat'ing binary data.
     */
    this.enable8BitControl = false;
  
    /**
     * Whether to allow the OSC 52 sequence to write to the system clipboard.
     */
    this.enableClipboardWrite = true;
  
    /**
     * Respect the host's attempt to change the cursor blink status using
     * the DEC Private mode 12.
     */
    this.enableDec12 = false;
  
    /**
     * The expected encoding method for data received from the host.
     */
    this.characterEncoding = 'utf-8';
  
    /**
     * If true, emit warnings when we encounter a control character or escape
     * sequence that we don't recognize or explicitly ignore.
     *
     * We disable this by default as the console logging can be expensive when
     * dumping binary files (e.g. `cat /dev/zero`) to the point where you can't
     * recover w/out restarting.
     */
    this.warnUnimplemented = false;
  
    /**
     * The set of available character maps (used by G0...G3 below).
     */
    this.characterMaps = new hterm.VT.CharacterMaps();
  
    /**
     * The default G0...G3 character maps.
     * We default to the US/ASCII map everywhere as that aligns with other
     * terminals, and it makes it harder to accidentally switch to the graphics
     * character map (Ctrl-N).  Any program that wants to use the graphics map
     * will usually select it anyways since there's no guarantee what state any
     * of the maps are in at any particular time.
     */
    this.G0 = this.G1 = this.G2 = this.G3 =
        this.characterMaps.getMap('B');
  
    /**
     * The 7-bit visible character set.
     *
     * This is a mapping from inbound data to display glyph.  The GL set
     * contains the 94 bytes from 0x21 to 0x7e.
     *
     * The default GL set is 'B', US ASCII.
     */
    this.GL = 'G0';
  
    /**
     * The 8-bit visible character set.
     *
     * This is a mapping from inbound data to display glyph.  The GR set
     * contains the 94 bytes from 0xa1 to 0xfe.
     */
    this.GR = 'G0';
  
    /**
     * The current encoding of the terminal.
     *
     * We only support ECMA-35 and UTF-8, so go with a boolean here.
     * The encoding can be locked too.
     */
    this.codingSystemUtf8_ = false;
    this.codingSystemLocked_ = false;
  
    // Construct a regular expression to match the known one-byte control chars.
    // This is used in parseUnknown_ to quickly scan a string for the next
    // control character.
    this.cc1Pattern_ = null;
    this.updateEncodingState_();
  };
  
  /**
   * No mouse events.
   */
  hterm.VT.prototype.MOUSE_REPORT_DISABLED = 0;
  
  /**
   * DECSET mode 9.
   *
   * Report mouse down events only.
   */
  hterm.VT.prototype.MOUSE_REPORT_PRESS = 1;
  
  /**
   * DECSET mode 1000.
   *
   * Report mouse down/up events only.
   */
  hterm.VT.prototype.MOUSE_REPORT_CLICK = 2;
  
  /**
   * DECSET mode 1002.
   *
   * Report mouse down/up and movement while a button is down.
   */
  hterm.VT.prototype.MOUSE_REPORT_DRAG = 3;
  
  /**
   * DEC mode for X10 coorindates (the default).
   */
  hterm.VT.prototype.MOUSE_COORDINATES_X10 = 0;
  
  /**
   * DEC mode 1005 for UTF-8 coorindates.
   */
  hterm.VT.prototype.MOUSE_COORDINATES_UTF8 = 1;
  
  /**
   * DEC mode 1006 for SGR coorindates.
   */
  hterm.VT.prototype.MOUSE_COORDINATES_SGR = 2;
  
  /**
   * ParseState constructor.
   *
   * This object tracks the current state of the parse.  It has fields for the
   * current buffer, position in the buffer, and the parse function.
   *
   * @param {function} defaultFunc The default parser function.
   * @param {string} opt_buf Optional string to use as the current buffer.
   */
  hterm.VT.ParseState = function(defaultFunction, opt_buf) {
    this.defaultFunction = defaultFunction;
    this.buf = opt_buf || null;
    this.pos = 0;
    this.func = defaultFunction;
    this.args = [];
    // Whether any of the arguments in the args array have subarguments.
    // e.g. All CSI sequences are integer arguments separated by semi-colons,
    // so subarguments are further colon separated.
    this.subargs = null;
  };
  
  /**
   * Reset the parser function, buffer, and position.
   */
  hterm.VT.ParseState.prototype.reset = function(opt_buf) {
    this.resetParseFunction();
    this.resetBuf(opt_buf || '');
    this.resetArguments();
  };
  
  /**
   * Reset the parser function only.
   */
  hterm.VT.ParseState.prototype.resetParseFunction = function() {
    this.func = this.defaultFunction;
  };
  
  /**
   * Reset the buffer and position only.
   *
   * @param {string} buf Optional new value for buf, defaults to null.
   */
  hterm.VT.ParseState.prototype.resetBuf = function(opt_buf) {
    this.buf = (typeof opt_buf == 'string') ? opt_buf : null;
    this.pos = 0;
  };
  
  /**
   * Reset the arguments list only.
   *
   * Typically we reset arguments before parsing a sequence that uses them rather
   * than always trying to make sure they're in a good state.  This can lead to
   * confusion during debugging where args from a previous sequence appear to be
   * "sticking around" in other sequences (which in reality don't use args).
   *
   * @param {string} opt_arg_zero Optional initial value for args[0].
   */
  hterm.VT.ParseState.prototype.resetArguments = function(opt_arg_zero) {
    this.args.length = 0;
    if (typeof opt_arg_zero != 'undefined')
      this.args[0] = opt_arg_zero;
  };
  
  /**
   * Parse an argument as an integer.
   *
   * This assumes the inputs are already in the proper format.  e.g. This won't
   * handle non-numeric arguments.
   *
   * An "0" argument is treated the same as "" which means the default value will
   * be applied.  This is what most terminal sequences expect.
   *
   * @param {string} argstr The argument to parse directly.
   * @param {number=} defaultValue Default value if argstr is empty.
   * @return {number} The parsed value.
   */
  hterm.VT.ParseState.prototype.parseInt = function(argstr, defaultValue) {
    if (defaultValue === undefined)
      defaultValue = 0;
  
    if (argstr) {
      const ret = parseInt(argstr, 10);
      // An argument of zero is treated as the default value.
      return ret == 0 ? defaultValue : ret;
    }
    return defaultValue;
  };
  
  /**
   * Get an argument as an integer.
   *
   * @param {number} argnum The argument number to retrieve.
   * @param {number=} defaultValue Default value if the argument is empty.
   * @return {number} The parsed value.
   */
  hterm.VT.ParseState.prototype.iarg = function(argnum, defaultValue) {
    return this.parseInt(this.args[argnum], defaultValue);
  };
  
  /**
   * Check whether an argument has subarguments.
   *
   * @param {number} argnum The argument number to check.
   * @return {number} Whether the argument has subarguments.
   */
  hterm.VT.ParseState.prototype.argHasSubargs = function(argnum) {
    return this.subargs && this.subargs[argnum];
  };
  
  /**
   * Mark an argument as having subarguments.
   *
   * @param {number} argnum The argument number that has subarguments.
   */
  hterm.VT.ParseState.prototype.argSetSubargs = function(argnum) {
    if (this.subargs === null)
      this.subargs = {};
    this.subargs[argnum] = true;
  };
  
  /**
   * Advance the parse position.
   *
   * @param {integer} count The number of bytes to advance.
   */
  hterm.VT.ParseState.prototype.advance = function(count) {
    this.pos += count;
  };
  
  /**
   * Return the remaining portion of the buffer without affecting the parse
   * position.
   *
   * @return {string} The remaining portion of the buffer.
   */
  hterm.VT.ParseState.prototype.peekRemainingBuf = function() {
    return this.buf.substr(this.pos);
  };
  
  /**
   * Return the next single character in the buffer without affecting the parse
   * position.
   *
   * @return {string} The next character in the buffer.
   */
  hterm.VT.ParseState.prototype.peekChar = function() {
    return this.buf.substr(this.pos, 1);
  };
  
  /**
   * Return the next single character in the buffer and advance the parse
   * position one byte.
   *
   * @return {string} The next character in the buffer.
   */
  hterm.VT.ParseState.prototype.consumeChar = function() {
    return this.buf.substr(this.pos++, 1);
  };
  
  /**
   * Return true if the buffer is empty, or the position is past the end.
   */
  hterm.VT.ParseState.prototype.isComplete = function() {
    return this.buf == null || this.buf.length <= this.pos;
  };
  
  /**
   * Reset the VT back to baseline state.
   */
  hterm.VT.prototype.reset = function() {
    this.G0 = this.G1 = this.G2 = this.G3 =
        this.characterMaps.getMap('B');
  
    this.GL = 'G0';
    this.GR = 'G0';
  
    this.mouseReport = this.MOUSE_REPORT_DISABLED;
    this.mouseCoordinates = this.MOUSE_COORDINATES_X10;
    this.lastMouseDragResponse_ = null;
  };
  
  /**
   * Handle terminal mouse events.
   *
   * See the "Mouse Tracking" section of [xterm].
   */
  hterm.VT.prototype.onTerminalMouse_ = function(e) {
    // Short circuit a few events to avoid unnecessary processing.
    if (this.mouseReport == this.MOUSE_REPORT_DISABLED)
      return;
    else if (this.mouseReport != this.MOUSE_REPORT_DRAG && e.type == 'mousemove')
      return;
  
    // Temporary storage for our response.
    var response;
  
    // Modifier key state.
    var mod = 0;
    if (this.mouseReport != this.MOUSE_REPORT_PRESS) {
      if (e.shiftKey)
        mod |= 4;
      if (e.metaKey || (this.terminal.keyboard.altIsMeta && e.altKey))
        mod |= 8;
      if (e.ctrlKey)
        mod |= 16;
    }
  
    // X & Y coordinate reporting.
    let x;
    let y;
    let limit = 255;
    switch (this.mouseCoordinates) {
      case this.MOUSE_COORDINATES_UTF8:
        // UTF-8 mode is the same as X10 but with higher limits.
        limit = 2047;
      case this.MOUSE_COORDINATES_X10:
        // X10 reports coordinates by encoding into strings.
        x = String.fromCharCode(lib.f.clamp(e.terminalColumn + 32, 32, limit));
        y = String.fromCharCode(lib.f.clamp(e.terminalRow + 32, 32, limit));
        break;
      case this.MOUSE_COORDINATES_SGR:
        // SGR reports coordinates by transmitting the numbers directly.
        x = e.terminalColumn;
        y = e.terminalRow;
        break;
    }
  
    switch (e.type) {
      case 'wheel':
        // Mouse wheel is treated as button 1 or 2 plus an additional 64.
        b = (((e.deltaY * -1) > 0) ? 0 : 1) + 64;
        b |= mod;
        if (this.mouseCoordinates == this.MOUSE_COORDINATES_SGR)
          response = `\x1b[<${b};${x};${y}M`;
        else
          response = '\x1b[M' + String.fromCharCode(b) + x + y;
  
        // Keep the terminal from scrolling.
        e.preventDefault();
        break;
  
      case 'mousedown':
        // Buttons are encoded as button number.
        var b = Math.min(e.button, 2);
        // In X10 mode, we also add 32 for legacy reasons.
        if (this.mouseCoordinates != this.MOUSE_COORDINATES_SGR)
          b += 32;
  
        // And mix in the modifier keys.
        b |= mod;
  
        if (this.mouseCoordinates == this.MOUSE_COORDINATES_SGR)
          response = `\x1b[<${b};${x};${y}M`;
        else
          response = '\x1b[M' + String.fromCharCode(b) + x + y;
        break;
  
      case 'mouseup':
        if (this.mouseReport != this.MOUSE_REPORT_PRESS) {
          if (this.mouseCoordinates == this.MOUSE_COORDINATES_SGR) {
            // SGR mode can report the released button.
            response = `\x1b[<${e.button};${x};${y}m`;
          } else {
            // X10 mode has no indication of which button was released.
            response = '\x1b[M\x23' + x + y;
          }
        }
        break;
  
      case 'mousemove':
        if (this.mouseReport == this.MOUSE_REPORT_DRAG && e.buttons) {
          // Standard button bits.  The XTerm protocol only reports the first
          // button press (e.g. if left & right are pressed, right is ignored),
          // and it only supports the first three buttons.  If none of them are
          // pressed, then XTerm flags it as a release.  We'll do the same.
          b = this.mouseCoordinates == this.MOUSE_COORDINATES_SGR ? 0 : 32;
  
          // Priority here matches XTerm: left, middle, right.
          if (e.buttons & 0x1) {
            // Report left button.
            b += 0;
          } else if (e.buttons & 0x4) {
            // Report middle button.
            b += 1;
          } else if (e.buttons & 0x2) {
            // Report right button.
            b += 2;
          } else {
            // Release higher buttons.
            b += 3;
          }
  
          // Add 32 to indicate mouse motion.
          b += 32;
  
          // And mix in the modifier keys.
          b |= mod;
  
          if (this.mouseCoordinates == this.MOUSE_COORDINATES_SGR)
            response = `\x1b[<${b};${x};${y}M`;
          else
            response = '\x1b[M' + String.fromCharCode(b) + x + y;
  
          // If we were going to report the same cell because we moved pixels
          // within, suppress the report.  This is what xterm does and cuts
          // down on duplicate messages.
          if (this.lastMouseDragResponse_ == response)
            response = '';
          else
            this.lastMouseDragResponse_ = response;
        }
  
        break;
  
      case 'click':
      case 'dblclick':
        break;
  
      default:
        console.error('Unknown mouse event: ' + e.type, e);
        break;
    }
  
    if (response)
      this.terminal.io.sendString(response);
  };
  
  /**
   * Interpret a string of characters, displaying the results on the associated
   * terminal object.
   *
   * The buffer will be decoded according to the 'receive-encoding' preference.
   */
  hterm.VT.prototype.interpret = function(buf) {
    this.parseState_.resetBuf(this.decode(buf));
  
    while (!this.parseState_.isComplete()) {
      var func = this.parseState_.func;
      var pos = this.parseState_.pos;
      var buf = this.parseState_.buf;
  
      this.parseState_.func.call(this, this.parseState_);
  
      if (this.parseState_.func == func && this.parseState_.pos == pos &&
          this.parseState_.buf == buf) {
        throw 'Parser did not alter the state!';
      }
    }
  };
  
  /**
   * Decode a string according to the 'receive-encoding' preference.
   */
  hterm.VT.prototype.decode = function(str) {
    if (this.characterEncoding == 'utf-8')
      return this.decodeUTF8(str);
  
    return str;
  };
  
  /**
   * Encode a UTF-16 string as UTF-8.
   *
   * See also: https://en.wikipedia.org/wiki/UTF-16
   */
  hterm.VT.prototype.encodeUTF8 = function(str) {
    return lib.encodeUTF8(str);
  };
  
  /**
   * Decode a UTF-8 string into UTF-16.
   */
  hterm.VT.prototype.decodeUTF8 = function(str) {
    return this.utf8Decoder_.decode(str);
  };
  
  /**
   * Set the encoding of the terminal.
   *
   * @param {string} encoding The name of the encoding to set.
   */
  hterm.VT.prototype.setEncoding = function(encoding) {
    switch (encoding) {
      default:
        console.warn('Invalid value for "terminal-encoding": ' + encoding);
        // Fall through.
      case 'iso-2022':
        this.codingSystemUtf8_ = false;
        this.codingSystemLocked_ = false;
        break;
      case 'utf-8-locked':
        this.codingSystemUtf8_ = true;
        this.codingSystemLocked_ = true;
        break;
      case 'utf-8':
        this.codingSystemUtf8_ = true;
        this.codingSystemLocked_ = false;
        break;
    }
  
    this.updateEncodingState_();
  };
  
  /**
   * Refresh internal state when the encoding changes.
   */
  hterm.VT.prototype.updateEncodingState_ = function() {
    // If we're in UTF8 mode, don't suport 8-bit escape sequences as we'll never
    // see those -- everything should be UTF8!
    var cc1 = Object.keys(hterm.VT.CC1)
        .filter((e) => !this.codingSystemUtf8_ || e.charCodeAt() < 0x80)
        .map((e) => '\\x' + lib.f.zpad(e.charCodeAt().toString(16), 2))
        .join('');
    this.cc1Pattern_ = new RegExp(`[${cc1}]`);
  };
  
  /**
   * The default parse function.
   *
   * This will scan the string for the first 1-byte control character (C0/C1
   * characters from [CTRL]).  Any plain text coming before the code will be
   * printed to the terminal, then the control character will be dispatched.
   */
  hterm.VT.prototype.parseUnknown_ = function(parseState) {
    var self = this;
  
    function print(str) {
      if (!self.codingSystemUtf8_ && self[self.GL].GL)
        str = self[self.GL].GL(str);
  
      self.terminal.print(str);
    };
  
    // Search for the next contiguous block of plain text.
    var buf = parseState.peekRemainingBuf();
    var nextControl = buf.search(this.cc1Pattern_);
  
    if (nextControl == 0) {
      // We've stumbled right into a control character.
      this.dispatch('CC1', buf.substr(0, 1), parseState);
      parseState.advance(1);
      return;
    }
  
    if (nextControl == -1) {
      // There are no control characters in this string.
      print(buf);
      parseState.reset();
      return;
    }
  
    print(buf.substr(0, nextControl));
    this.dispatch('CC1', buf.substr(nextControl, 1), parseState);
    parseState.advance(nextControl + 1);
  };
  
  /**
   * Parse a Control Sequence Introducer code and dispatch it.
   *
   * See [CSI] for some useful information about these codes.
   */
  hterm.VT.prototype.parseCSI_ = function(parseState) {
    var ch = parseState.peekChar();
    var args = parseState.args;
  
    const finishParsing = () => {
      // Resetting the arguments isn't strictly necessary, but it makes debugging
      // less confusing (otherwise args will stick around until the next sequence
      // that needs arguments).
      parseState.resetArguments();
      // We need to clear subargs since we explicitly set it.
      parseState.subargs = null;
      parseState.resetParseFunction();
    };
  
    if (ch >= '@' && ch <= '~') {
      // This is the final character.
      this.dispatch('CSI', this.leadingModifier_ + this.trailingModifier_ + ch,
                    parseState);
      finishParsing();
  
    } else if (ch == ';') {
      // Parameter delimiter.
      if (this.trailingModifier_) {
        // Parameter delimiter after the trailing modifier.  That's a paddlin'.
        finishParsing();
  
      } else {
        if (!args.length) {
          // They omitted the first param, we need to supply it.
          args.push('');
        }
  
        args.push('');
      }
  
    } else if (ch >= '0' && ch <= '9' || ch == ':') {
      // Next byte in the current parameter.
  
      if (this.trailingModifier_) {
        // Numeric parameter after the trailing modifier.  That's a paddlin'.
        finishParsing();
      } else {
        if (!args.length) {
          args[0] = ch;
        } else {
          args[args.length - 1] += ch;
        }
  
        // Possible sub-parameters.
        if (ch == ':')
          parseState.argSetSubargs(args.length - 1);
      }
  
    } else if (ch >= ' ' && ch <= '?') {
      // Modifier character.
      if (!args.length) {
        this.leadingModifier_ += ch;
      } else {
        this.trailingModifier_ += ch;
      }
  
    } else if (this.cc1Pattern_.test(ch)) {
      // Control character.
      this.dispatch('CC1', ch, parseState);
  
    } else {
      // Unexpected character in sequence, bail out.
      finishParsing();
    }
  
    parseState.advance(1);
  };
  
  /**
   * Skip over the string until the next String Terminator (ST, 'ESC \') or
   * Bell (BEL, '\x07').
   *
   * The string is accumulated in parseState.args[0].  Make sure to reset the
   * arguments (with parseState.resetArguments) before starting the parse.
   *
   * You can detect that parsing in complete by checking that the parse
   * function has changed back to the default parse function.
   *
   * @return {boolean} If true, parsing is ongoing or complete.  If false, we've
   *     exceeded the max string sequence.
   */
  hterm.VT.prototype.parseUntilStringTerminator_ = function(parseState) {
    var buf = parseState.peekRemainingBuf();
    var args = parseState.args;
    // Since we might modify parse state buffer locally, if we want to advance
    // the parse state buffer later on, we need to know how many chars we added.
    let bufInserted = 0;
  
    if (!args.length) {
      args[0] = '';
      args[1] = new Date();
    } else {
      // If our saved buffer ends with an escape, it's because we were hoping
      // it's an ST split across two buffers.  Move it from our saved buffer
      // to the start of our current buffer for processing anew.
      if (args[0].slice(-1) == '\x1b') {
        args[0] = args[0].slice(0, -1);
        buf = '\x1b' + buf;
        bufInserted = 1;
      }
    }
  
    const nextTerminator = buf.search(/[\x1b\x07]/);
    const terminator = buf[nextTerminator];
    let foundTerminator;
  
    // If the next escape we see is not a start of a ST, fall through.  This will
    // either be invalid (embedded escape), or we'll queue it up (wait for \\).
    if (terminator == '\x1b' && buf[nextTerminator + 1] != '\\')
      foundTerminator = false;
    else
      foundTerminator = (nextTerminator != -1);
  
    if (!foundTerminator) {
      // No terminator here, have to wait for the next string.
  
      args[0] += buf;
  
      var abortReason;
  
      // Special case: If our buffering happens to split the ST (\e\\), we have to
      // buffer the content temporarily.  So don't reject a trailing escape here,
      // instead we let it timeout or be rejected in the next pass.
      if (terminator == '\x1b' && nextTerminator != buf.length - 1)
        abortReason = 'embedded escape: ' + nextTerminator;
  
      if (new Date() - args[1] > this.oscTimeLimit_)
        abortReason = 'timeout expired: ' + (new Date() - args[1]);
  
      if (abortReason) {
        if (this.warnUnimplemented)
          console.log('parseUntilStringTerminator_: aborting: ' + abortReason,
                      args[0]);
        parseState.reset(args[0]);
        return false;
      }
  
      parseState.advance(buf.length - bufInserted);
      return true;
    }
  
    args[0] += buf.substr(0, nextTerminator);
  
    parseState.resetParseFunction();
    parseState.advance(nextTerminator +
                       (terminator == '\x1b' ? 2 : 1) - bufInserted);
  
    return true;
  };
  
  /**
   * Dispatch to the function that handles a given CC1, ESC, or CSI or VT52 code.
   */
  hterm.VT.prototype.dispatch = function(type, code, parseState) {
    var handler = hterm.VT[type][code];
    if (!handler) {
      if (this.warnUnimplemented)
        console.warn('Unknown ' + type + ' code: ' + JSON.stringify(code));
      return;
    }
  
    if (handler == hterm.VT.ignore) {
      if (this.warnUnimplemented)
        console.warn('Ignored ' + type + ' code: ' + JSON.stringify(code));
      return;
    }
  
    if (parseState.subargs && !handler.supportsSubargs) {
      if (this.warnUnimplemented)
        console.warn('Ignored ' + type + ' code w/subargs: ' + JSON.stringify(code));
      return;
    }
  
    if (type == 'CC1' && code > '\x7f' && !this.enable8BitControl) {
      // It's kind of a hack to put this here, but...
      //
      // If we're dispatching a 'CC1' code, and it's got the eighth bit set,
      // but we're not supposed to handle 8-bit codes?  Just ignore it.
      //
      // This prevents an errant (DCS, '\x90'), (OSC, '\x9d'), (PM, '\x9e') or
      // (APC, '\x9f') from locking up the terminal waiting for its expected
      // (ST, '\x9c') or (BEL, '\x07').
      console.warn('Ignoring 8-bit control code: 0x' +
                   code.charCodeAt(0).toString(16));
      return;
    }
  
    handler.apply(this, [parseState, code]);
  };
  
  /**
   * Set one of the ANSI defined terminal mode bits.
   *
   * Invoked in response to SM/RM.
   *
   * Unexpected and unimplemented values are silently ignored.
   */
  hterm.VT.prototype.setANSIMode = function(code, state) {
    if (code == 4) {  // Insert Mode (IRM)
      this.terminal.setInsertMode(state);
    } else if (code == 20) {  // Automatic Newline (LNM)
      this.terminal.setAutoCarriageReturn(state);
    } else if (this.warnUnimplemented) {
      console.warn('Unimplemented ANSI Mode: ' + code);
    }
  };
  
  /**
   * Set or reset one of the DEC Private modes.
   *
   * Invoked in response to DECSET/DECRST.
   */
  hterm.VT.prototype.setDECMode = function(code, state) {
    switch (parseInt(code, 10)) {
      case 1:  // DECCKM
        this.terminal.keyboard.applicationCursor = state;
        break;
  
      case 3:  // DECCOLM
        if (this.allowColumnWidthChanges_) {
          this.terminal.setWidth(state ? 132 : 80);
  
          this.terminal.clearHome();
          this.terminal.setVTScrollRegion(null, null);
        }
        break;
  
      case 5:  // DECSCNM
        this.terminal.setReverseVideo(state);
        break;
  
      case 6:  // DECOM
        this.terminal.setOriginMode(state);
        break;
  
      case 7:  // DECAWM
        this.terminal.setWraparound(state);
        break;
  
      case 9:  // Report on mouse down events only (X10).
        this.mouseReport = (
            state ? this.MOUSE_REPORT_PRESS : this.MOUSE_REPORT_DISABLED);
        this.terminal.syncMouseStyle();
        break;
  
      case 12:  // Start blinking cursor
        if (this.enableDec12)
          this.terminal.setCursorBlink(state);
        break;
  
      case 25:  // DECTCEM
        this.terminal.setCursorVisible(state);
        break;
  
      case 30:  // Show scrollbar
        this.terminal.setScrollbarVisible(state);
        break;
  
      case 40:  // Allow 80 - 132 (DECCOLM) Mode
        this.terminal.allowColumnWidthChanges_ = state;
        break;
  
      case 45:  // Reverse-wraparound Mode
        this.terminal.setReverseWraparound(state);
        break;
  
      case 67:  // Backarrow key sends backspace (DECBKM)
        this.terminal.keyboard.backspaceSendsBackspace = state;
        break;
  
      case 1000:  // Report on mouse clicks only (X11).
        this.mouseReport = (
            state ? this.MOUSE_REPORT_CLICK : this.MOUSE_REPORT_DISABLED);
        this.terminal.syncMouseStyle();
        break;
  
      case 1002:  // Report on mouse clicks and drags
        this.mouseReport = (
            state ? this.MOUSE_REPORT_DRAG : this.MOUSE_REPORT_DISABLED);
        this.terminal.syncMouseStyle();
        break;
  
      case 1004:  // Report on window focus change.
        this.terminal.reportFocus = state;
        break;
  
      case 1005:  // Extended coordinates in UTF-8 mode.
        this.mouseCoordinates = (
            state ? this.MOUSE_COORDINATES_UTF8 : this.MOUSE_COORDINATES_X10);
        break;
  
      case 1006:  // Extended coordinates in SGR mode.
        this.mouseCoordinates = (
            state ? this.MOUSE_COORDINATES_SGR : this.MOUSE_COORDINATES_X10);
        break;
  
      case 1007:  // Enable Alternate Scroll Mode.
        this.terminal.scrollWheelArrowKeys_ = state;
        break;
  
      case 1010:  // Scroll to bottom on tty output
        this.terminal.scrollOnOutput = state;
        break;
  
      case 1011:  // Scroll to bottom on key press
        this.terminal.scrollOnKeystroke = state;
        break;
  
      case 1036:  // Send ESC when Meta modifies a key
        this.terminal.keyboard.metaSendsEscape = state;
        break;
  
      case 1039:  // Send ESC when Alt modifies a key
        if (state) {
          if (!this.terminal.keyboard.previousAltSendsWhat_) {
            this.terminal.keyboard.previousAltSendsWhat_ =
                this.terminal.keyboard.altSendsWhat;
            this.terminal.keyboard.altSendsWhat = 'escape';
          }
        } else if (this.terminal.keyboard.previousAltSendsWhat_) {
          this.terminal.keyboard.altSendsWhat =
              this.terminal.keyboard.previousAltSendsWhat_;
          this.terminal.keyboard.previousAltSendsWhat_ = null;
        }
        break;
  
      case 47:  // Use Alternate Screen Buffer
      case 1047:
        this.terminal.setAlternateMode(state);
        break;
  
      case 1048:  // Save cursor as in DECSC.
        if (state)
          this.terminal.saveCursorAndState();
        else
          this.terminal.restoreCursorAndState();
        break;
  
      case 1049:  // 1047 + 1048 + clear.
        if (state) {
          this.terminal.saveCursorAndState();
          this.terminal.setAlternateMode(state);
          this.terminal.clear();
        } else {
          this.terminal.setAlternateMode(state);
          this.terminal.restoreCursorAndState();
        }
  
        break;
  
      case 2004:  // Bracketed paste mode.
        this.terminal.setBracketedPaste(state);
        break;
  
      default:
        if (this.warnUnimplemented)
          console.warn('Unimplemented DEC Private Mode: ' + code);
        break;
    }
  };
  
  /**
   * Function shared by control characters and escape sequences that are
   * ignored.
   */
  hterm.VT.ignore = function() {};
  
  /**
   * Collection of control characters expressed in a single byte.
   *
   * This includes the characters from the C0 and C1 sets (see [CTRL]) that we
   * care about.  Two byte versions of the C1 codes are defined in the
   * hterm.VT.ESC collection.
   *
   * The 'CC1' mnemonic here refers to the fact that these are one-byte Control
   * Codes.  It's only used in this source file and not defined in any of the
   * referenced documents.
   */
  hterm.VT.CC1 = {};
  
  /**
   * Collection of two-byte and three-byte sequences starting with ESC.
   */
  hterm.VT.ESC = {};
  
  /**
   * Collection of CSI (Control Sequence Introducer) sequences.
   *
   * These sequences begin with 'ESC [', and may take zero or more arguments.
   */
  hterm.VT.CSI = {};
  
  /**
   * Collection of OSC (Operating System Control) sequences.
   *
   * These sequences begin with 'ESC ]', followed by a function number and a
   * string terminated by either ST or BEL.
   */
  hterm.VT.OSC = {};
  
  /**
   * Collection of VT52 sequences.
   *
   * When in VT52 mode, other sequences are disabled.
   */
  hterm.VT.VT52 = {};
  
  /**
   * Null (NUL).
   *
   * Silently ignored.
   */
  hterm.VT.CC1['\x00'] = hterm.VT.ignore;
  
  /**
   * Enquiry (ENQ).
   *
   * Transmit answerback message.
   *
   * The default answerback message in xterm is an empty string, so we just
   * ignore this.
   */
  hterm.VT.CC1['\x05'] = hterm.VT.ignore;
  
  /**
   * Ring Bell (BEL).
   */
  hterm.VT.CC1['\x07'] = function() {
    this.terminal.ringBell();
  };
  
  /**
   * Backspace (BS).
   *
   * Move the cursor to the left one character position, unless it is at the
   * left margin, in which case no action occurs.
   */
  hterm.VT.CC1['\x08'] = function() {
    this.terminal.cursorLeft(1);
  };
  
  /**
   * Horizontal Tab (HT).
   *
   * Move the cursor to the next tab stop, or to the right margin if no further
   * tab stops are present on the line.
   */
  hterm.VT.CC1['\x09'] = function() {
    this.terminal.forwardTabStop();
  };
  
  /**
   * Line Feed (LF).
   *
   * This code causes a line feed or a new line operation.  See Automatic
   * Newline (LNM).
   */
  hterm.VT.CC1['\x0a'] = function() {
    this.terminal.formFeed();
  };
  
  /**
   * Vertical Tab (VT).
   *
   * Interpreted as LF.
   */
  hterm.VT.CC1['\x0b'] = hterm.VT.CC1['\x0a'];
  
  /**
   * Form Feed (FF).
   *
   * Interpreted as LF.
   */
  hterm.VT.CC1['\x0c'] = hterm.VT.CC1['\x0a'];
  
  /**
   * Carriage Return (CR).
   *
   * Move cursor to the left margin on the current line.
   */
  hterm.VT.CC1['\x0d'] = function() {
    this.terminal.setCursorColumn(0);
  };
  
  /**
   * Shift Out (SO), aka Lock Shift 0 (LS1).
   *
   * Invoke G1 character set in GL.
   */
  hterm.VT.CC1['\x0e'] = function() {
    this.GL = 'G1';
  };
  
  /**
   * Shift In (SI), aka Lock Shift 0 (LS0).
   *
   * Invoke G0 character set in GL.
   */
  hterm.VT.CC1['\x0f'] = function() {
    this.GL = 'G0';
  };
  
  /**
   * Transmit On (XON).
   *
   * Not currently implemented.
   *
   * TODO(rginda): Implement?
   */
  hterm.VT.CC1['\x11'] = hterm.VT.ignore;
  
  /**
   * Transmit Off (XOFF).
   *
   * Not currently implemented.
   *
   * TODO(rginda): Implement?
   */
  hterm.VT.CC1['\x13'] = hterm.VT.ignore;
  
  /**
   * Cancel (CAN).
   *
   * If sent during a control sequence, the sequence is immediately terminated
   * and not executed.
   *
   * It also causes the error character to be displayed.
   */
  hterm.VT.CC1['\x18'] = function(parseState) {
    // If we've shifted in the G1 character set, shift it back out to
    // the default character set.
    if (this.GL == 'G1') {
      this.GL = 'G0';
    }
    parseState.resetParseFunction();
    this.terminal.print('?');
  };
  
  /**
   * Substitute (SUB).
   *
   * Interpreted as CAN.
   */
  hterm.VT.CC1['\x1a'] = hterm.VT.CC1['\x18'];
  
  /**
   * Escape (ESC).
   */
  hterm.VT.CC1['\x1b'] = function(parseState) {
    function parseESC(parseState) {
      var ch = parseState.consumeChar();
  
      if (ch == '\x1b')
        return;
  
      this.dispatch('ESC', ch, parseState);
  
      if (parseState.func == parseESC)
        parseState.resetParseFunction();
    };
  
    parseState.func = parseESC;
  };
  
  /**
   * Delete (DEL).
   */
  hterm.VT.CC1['\x7f'] = hterm.VT.ignore;
  
  // 8 bit control characters and their two byte equivalents, below...
  
  /**
   * Index (IND).
   *
   * Like newline, only keep the X position
   */
  hterm.VT.CC1['\x84'] =
  hterm.VT.ESC['D'] = function() {
    this.terminal.lineFeed();
  };
  
  /**
   * Next Line (NEL).
   *
   * Like newline, but doesn't add lines.
   */
  hterm.VT.CC1['\x85'] =
  hterm.VT.ESC['E'] = function() {
    this.terminal.setCursorColumn(0);
    this.terminal.cursorDown(1);
  };
  
  /**
   * Horizontal Tabulation Set (HTS).
   */
  hterm.VT.CC1['\x88'] =
  hterm.VT.ESC['H'] = function() {
    this.terminal.setTabStop(this.terminal.getCursorColumn());
  };
  
  /**
   * Reverse Index (RI).
   *
   * Move up one line.
   */
  hterm.VT.CC1['\x8d'] =
  hterm.VT.ESC['M'] = function() {
    this.terminal.reverseLineFeed();
  };
  
  /**
   * Single Shift 2 (SS2).
   *
   * Select of G2 Character Set for the next character only.
   *
   * Not currently implemented.
   */
  hterm.VT.CC1['\x8e'] =
  hterm.VT.ESC['N'] = hterm.VT.ignore;
  
  /**
   * Single Shift 3 (SS3).
   *
   * Select of G3 Character Set for the next character only.
   *
   * Not currently implemented.
   */
  hterm.VT.CC1['\x8f'] =
  hterm.VT.ESC['O'] = hterm.VT.ignore;
  
  /**
   * Device Control String (DCS).
   *
   * Indicate a DCS sequence.  See Device-Control functions in [XTERM].
   * Not currently implemented.
   *
   * TODO(rginda): Consider implementing DECRQSS, the rest don't seem applicable.
   */
  hterm.VT.CC1['\x90'] =
  hterm.VT.ESC['P'] = function(parseState) {
    parseState.resetArguments();
    parseState.func = this.parseUntilStringTerminator_;
  };
  
  /**
   * Start of Guarded Area (SPA).
   *
   * Will not implement.
   */
  hterm.VT.CC1['\x96'] =
  hterm.VT.ESC['V'] = hterm.VT.ignore;
  
  /**
   * End of Guarded Area (EPA).
   *
   * Will not implement.
   */
  hterm.VT.CC1['\x97'] =
  hterm.VT.ESC['W'] = hterm.VT.ignore;
  
  /**
   * Start of String (SOS).
   *
   * Will not implement.
   */
  hterm.VT.CC1['\x98'] =
  hterm.VT.ESC['X'] = hterm.VT.ignore;
  
  /**
   * Single Character Introducer (SCI, also DECID).
   *
   * Return Terminal ID.  Obsolete form of 'ESC [ c' (DA).
   */
  hterm.VT.CC1['\x9a'] =
  hterm.VT.ESC['Z'] = function() {
    this.terminal.io.sendString('\x1b[?1;2c');
  };
  
  /**
   * Control Sequence Introducer (CSI).
   *
   * The lead into most escape sequences.  See [CSI].
   */
  hterm.VT.CC1['\x9b'] =
  hterm.VT.ESC['['] = function(parseState) {
    parseState.resetArguments();
    this.leadingModifier_ = '';
    this.trailingModifier_ = '';
    parseState.func = this.parseCSI_;
  };
  
  /**
   * String Terminator (ST).
   *
   * Used to terminate DCS/OSC/PM/APC commands which may take string arguments.
   *
   * We don't directly handle it here, as it's only used to terminate other
   * sequences.  See the 'parseUntilStringTerminator_' method.
   */
  hterm.VT.CC1['\x9c'] =
  hterm.VT.ESC['\\'] = hterm.VT.ignore;
  
  /**
   * Operating System Command (OSC).
   *
   * Commands relating to the operating system.
   */
  hterm.VT.CC1['\x9d'] =
  hterm.VT.ESC[']'] = function(parseState) {
    parseState.resetArguments();
  
    function parseOSC(parseState) {
      if (!this.parseUntilStringTerminator_(parseState)) {
        // The string sequence was too long.
        return;
      }
  
      if (parseState.func == parseOSC) {
        // We're not done parsing the string yet.
        return;
      }
  
      // We're done.
      var ary = parseState.args[0].match(/^(\d+);(.*)$/);
      if (ary) {
        parseState.args[0] = ary[2];
        this.dispatch('OSC', ary[1], parseState);
      } else {
        console.warn('Invalid OSC: ' + JSON.stringify(parseState.args[0]));
      }
  
      // Resetting the arguments isn't strictly necessary, but it makes debugging
      // less confusing (otherwise args will stick around until the next sequence
      // that needs arguments).
      parseState.resetArguments();
    };
  
    parseState.func = parseOSC;
  };
  
  /**
   * Privacy Message (PM).
   *
   * Will not implement.
   */
  hterm.VT.CC1['\x9e'] =
  hterm.VT.ESC['^'] = function(parseState) {
    parseState.resetArguments();
    parseState.func = this.parseUntilStringTerminator_;
  };
  
  /**
   * Application Program Control (APC).
   *
   * Will not implement.
   */
  hterm.VT.CC1['\x9f'] =
  hterm.VT.ESC['_'] = function(parseState) {
    parseState.resetArguments();
    parseState.func = this.parseUntilStringTerminator_;
  };
  
  /**
   * ESC \x20 - Unclear to me where these originated, possibly in xterm.
   *
   * Not currently implemented:
   *   ESC \x20 F - Select 7 bit escape codes in responses (S7C1T).
   *   ESC \x20 G - Select 8 bit escape codes in responses (S8C1T).
   *                NB: We currently assume S7C1T always.
   *
   * Will not implement:
   *   ESC \x20 L - Set ANSI conformance level 1.
   *   ESC \x20 M - Set ANSI conformance level 2.
   *   ESC \x20 N - Set ANSI conformance level 3.
   */
  hterm.VT.ESC['\x20'] = function(parseState) {
    parseState.func = function(parseState) {
      var ch = parseState.consumeChar();
      if (this.warnUnimplemented)
        console.warn('Unimplemented sequence: ESC 0x20 ' + ch);
      parseState.resetParseFunction();
    };
  };
  
  /**
   * DEC 'ESC #' sequences.
   */
  hterm.VT.ESC['#'] = function(parseState) {
    parseState.func = function(parseState) {
      var ch = parseState.consumeChar();
      if (ch == '8')  // DEC Screen Alignment Test (DECALN)
        this.terminal.fill('E');
  
      parseState.resetParseFunction();
    };
  };
  
  /**
   * Designate Other Coding System (DOCS).
   */
  hterm.VT.ESC['%'] = function(parseState) {
    parseState.func = function(parseState) {
      var ch = parseState.consumeChar();
  
      // If we've locked the encoding, then just eat the bytes and return.
      if (this.codingSystemLocked_) {
        if (ch == '/')
          parseState.consumeChar();
        parseState.resetParseFunction();
        return;
      }
  
      // Process the encoding requests.
      switch (ch) {
        case '@':
          // Switch to ECMA 35.
          this.setEncoding('iso-2022');
          break;
  
        case 'G':
          // Switch to UTF-8.
          this.setEncoding('utf-8');
          break;
  
        case '/':
          // One way transition to something else.
          ch = parseState.consumeChar();
          switch (ch) {
            case 'G':  // UTF-8 Level 1.
            case 'H':  // UTF-8 Level 2.
            case 'I':  // UTF-8 Level 3.
              // We treat all UTF-8 levels the same.
              this.setEncoding('utf-8-locked');
              break;
  
            default:
              if (this.warnUnimplemented)
                console.warn('Unknown ESC % / argument: ' + JSON.stringify(ch));
              break;
          }
          break;
  
        default:
          if (this.warnUnimplemented)
            console.warn('Unknown ESC % argument: ' + JSON.stringify(ch));
          break;
      }
  
      parseState.resetParseFunction();
    };
  };
  
  /**
   * Character Set Selection (SCS).
   *
   *   ESC ( Ps - Set G0 character set (VT100).
   *   ESC ) Ps - Set G1 character set (VT220).
   *   ESC * Ps - Set G2 character set (VT220).
   *   ESC + Ps - Set G3 character set (VT220).
   *   ESC - Ps - Set G1 character set (VT300).
   *   ESC . Ps - Set G2 character set (VT300).
   *   ESC / Ps - Set G3 character set (VT300).
   *
   * All other sequences are echoed to the terminal.
   */
  hterm.VT.ESC['('] =
  hterm.VT.ESC[')'] =
  hterm.VT.ESC['*'] =
  hterm.VT.ESC['+'] =
  hterm.VT.ESC['-'] =
  hterm.VT.ESC['.'] =
  hterm.VT.ESC['/'] = function(parseState, code) {
    parseState.func = function(parseState) {
      var ch = parseState.consumeChar();
      if (ch == '\x1b') {
        parseState.resetParseFunction();
        parseState.func();
        return;
      }
  
      var map = this.characterMaps.getMap(ch);
      if (map !== undefined) {
        if (code == '(') {
          this.G0 = map;
        } else if (code == ')' || code == '-') {
          this.G1 = map;
        } else if (code == '*' || code == '.') {
          this.G2 = map;
        } else if (code == '+' || code == '/') {
          this.G3 = map;
        }
      } else if (this.warnUnimplemented) {
        console.log('Invalid character set for "' + code + '": ' + ch);
      }
  
      parseState.resetParseFunction();
    };
  };
  
  /**
   * Back Index (DECBI).
   *
   * VT420 and up.  Not currently implemented.
   */
  hterm.VT.ESC['6'] = hterm.VT.ignore;
  
  /**
   * Save Cursor (DECSC).
   */
  hterm.VT.ESC['7'] = function() {
    this.terminal.saveCursorAndState();
  };
  
  /**
   * Restore Cursor (DECRC).
   */
  hterm.VT.ESC['8'] = function() {
    this.terminal.restoreCursorAndState();
  };
  
  /**
   * Forward Index (DECFI).
   *
   * VT210 and up.  Not currently implemented.
   */
  hterm.VT.ESC['9'] = hterm.VT.ignore;
  
  /**
   * Application keypad (DECKPAM).
   */
  hterm.VT.ESC['='] = function() {
    this.terminal.keyboard.applicationKeypad = true;
  };
  
  /**
   * Normal keypad (DECKPNM).
   */
  hterm.VT.ESC['>'] = function() {
    this.terminal.keyboard.applicationKeypad = false;
  };
  
  /**
   * Cursor to lower left corner of screen.
   *
   * Will not implement.
   *
   * This is only recognized by xterm when the hpLowerleftBugCompat resource is
   * set.
   */
  hterm.VT.ESC['F'] = hterm.VT.ignore;
  
  /**
   * Full Reset (RIS).
   */
  hterm.VT.ESC['c'] = function() {
    this.terminal.reset();
  };
  
  /**
   * Memory lock/unlock.
   *
   * Will not implement.
   */
  hterm.VT.ESC['l'] =
  hterm.VT.ESC['m'] = hterm.VT.ignore;
  
  /**
   * Lock Shift 2 (LS2)
   *
   * Invoke the G2 Character Set as GL.
   */
  hterm.VT.ESC['n'] = function() {
    this.GL = 'G2';
  };
  
  /**
   * Lock Shift 3 (LS3)
   *
   * Invoke the G3 Character Set as GL.
   */
  hterm.VT.ESC['o'] = function() {
    this.GL = 'G3';
  };
  
  /**
   * Lock Shift 2, Right (LS3R)
   *
   * Invoke the G3 Character Set as GR.
   */
  hterm.VT.ESC['|'] = function() {
    this.GR = 'G3';
  };
  
  /**
   * Lock Shift 2, Right (LS2R)
   *
   * Invoke the G2 Character Set as GR.
   */
  hterm.VT.ESC['}'] = function() {
    this.GR = 'G2';
  };
  
  /**
   * Lock Shift 1, Right (LS1R)
   *
   * Invoke the G1 Character Set as GR.
   */
  hterm.VT.ESC['~'] = function() {
    this.GR = 'G1';
  };
  
  /**
   * Change icon name and window title.
   *
   * We only change the window title.
   */
  hterm.VT.OSC['0'] = function(parseState) {
    this.terminal.setWindowTitle(parseState.args[0]);
  };
  
  /**
   * Change window title.
   */
  hterm.VT.OSC['2'] = hterm.VT.OSC['0'];
  
  /**
   * Set/read color palette.
   */
  hterm.VT.OSC['4'] = function(parseState) {
    // Args come in as a single 'index1;rgb1 ... ;indexN;rgbN' string.
    // We split on the semicolon and iterate through the pairs.
    var args = parseState.args[0].split(';');
  
    var pairCount = parseInt(args.length / 2);
    var colorPalette = this.terminal.getTextAttributes().colorPalette;
    var responseArray = [];
  
    for (var pairNumber = 0; pairNumber < pairCount; ++pairNumber) {
      var colorIndex = parseInt(args[pairNumber * 2]);
      var colorValue = args[pairNumber * 2 + 1];
  
      if (colorIndex >= colorPalette.length)
        continue;
  
      if (colorValue == '?') {
        // '?' means we should report back the current color value.
        colorValue = lib.colors.rgbToX11(colorPalette[colorIndex]);
        if (colorValue)
          responseArray.push(colorIndex + ';' + colorValue);
  
        continue;
      }
  
      colorValue = lib.colors.x11ToCSS(colorValue);
      if (colorValue)
        colorPalette[colorIndex] = colorValue;
    }
  
    if (responseArray.length)
      this.terminal.io.sendString('\x1b]4;' + responseArray.join(';') + '\x07');
  };
  
  /**
   * Hyperlinks.
   *
   * The first argument is optional and colon separated:
   *   id=<id>
   * The second argument is the link itself.
   *
   * Calling with a non-blank URI starts it.  A blank URI stops it.
   *
   * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
   */
  hterm.VT.OSC['8'] = function(parseState) {
    const args = parseState.args[0].split(';');
    let id = null;
    let uri = null;
  
    if (args.length != 2 || args[1].length == 0) {
      // Reset link.
    } else {
      // Pull out any colon separated parameters in the first argument.
      const params = args[0].split(':');
      id = '';
      params.forEach((param) => {
        const idx = param.indexOf('=');
        if (idx == -1)
          return;
  
        const key = param.slice(0, idx);
        const value = param.slice(idx + 1);
        switch (key) {
          case 'id':
            id = value;
            break;
          default:
            // Ignore unknown keys.
            break;
        }
      });
  
      // The URI is in the second argument.
      uri = args[1];
    }
  
    const attrs = this.terminal.getTextAttributes();
    attrs.uri = uri;
    attrs.uriId = id;
  };
  
  /**
   * iTerm2 growl notifications.
   */
  hterm.VT.OSC['9'] = function(parseState) {
    // This just dumps the entire string as the message.
    hterm.notify({'body': parseState.args[0]});
  };
  
  /**
   * Change VT100 text foreground color.
   */
  hterm.VT.OSC['10'] = function(parseState) {
    // Args come in as a single string, but extra args will chain to the following
    // OSC sequences.
    var args = parseState.args[0].split(';');
    if (!args)
      return;
  
    var colorArg;
    var colorX11 = lib.colors.x11ToCSS(args.shift());
    if (colorX11)
      this.terminal.setForegroundColor(colorX11);
  
    if (args.length > 0) {
      parseState.args[0] = args.join(';');
      hterm.VT.OSC['11'].apply(this, [parseState]);
    }
  };
  
  /**
   * Change VT100 text background color.
   */
  hterm.VT.OSC['11'] = function(parseState) {
    // Args come in as a single string, but extra args will chain to the following
    // OSC sequences.
    var args = parseState.args[0].split(';');
    if (!args)
      return;
  
    var colorArg;
    var colorX11 = lib.colors.x11ToCSS(args.shift());
    if (colorX11)
      this.terminal.setBackgroundColor(colorX11);
  
    if (args.length > 0) {
      parseState.args[0] = args.join(';');
      hterm.VT.OSC['12'].apply(this, [parseState]);
    }
  };
  
  /**
   * Change text cursor color.
   */
  hterm.VT.OSC['12'] = function(parseState) {
    // Args come in as a single string, but extra args will chain to the following
    // OSC sequences.
    var args = parseState.args[0].split(';');
    if (!args)
      return;
  
    var colorArg;
    var colorX11 = lib.colors.x11ToCSS(args.shift());
    if (colorX11)
      this.terminal.setCursorColor(colorX11);
  
    /* Note: If we support OSC 13+, we'd chain it here.
    if (args.length > 0) {
      parseState.args[0] = args.join(';');
      hterm.VT.OSC['13'].apply(this, [parseState]);
    }
    */
  };
  
  /**
   * Set the cursor shape.
   *
   * Parameter is expected to be in the form "CursorShape=number", where number is
   * one of:
   *
   *   0 - Block
   *   1 - I-Beam
   *   2 - Underline
   *
   * This is a bit of a de-facto standard supported by iTerm 2 and Konsole.  See
   * also: DECSCUSR.
   *
   * Invalid numbers will restore the cursor to the block shape.
   */
  hterm.VT.OSC['50'] = function(parseState) {
    var args = parseState.args[0].match(/CursorShape=(.)/i);
    if (!args) {
      console.warn('Could not parse OSC 50 args: ' + parseState.args[0]);
      return;
    }
  
    switch (args[1]) {
      case '1':  // CursorShape=1: I-Beam.
        this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
        break;
  
      case '2':  // CursorShape=2: Underline.
        this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
        break;
  
      default:  // CursorShape=0: Block.
        this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
    }
  };
  
  /**
   * Set/read system clipboard.
   *
   * Read is not implemented due to security considerations.  A remote app
   * that is able to both write and read to the clipboard could essentially
   * take over your session.
   *
   * The clipboard data will be decoded according to the 'receive-encoding'
   * preference.
   */
  hterm.VT.OSC['52'] = function(parseState) {
    if (!this.enableClipboardWrite)
      return;
  
    // Args come in as a single 'clipboard;b64-data' string.  The clipboard
    // parameter is used to select which of the X clipboards to address.  Since
    // we're not integrating with X, we treat them all the same.
    var args = parseState.args[0].match(/^[cps01234567]*;(.*)/);
    if (!args)
      return;
  
    var data = window.atob(args[1]);
    if (data)
      this.terminal.copyStringToClipboard(this.decode(data));
  };
  
  /**
   * Reset foreground color.
   */
  hterm.VT.OSC['110'] = function(parseState) {
    this.terminal.setForegroundColor();
  };
  
  /**
   * Reset background color.
   */
  hterm.VT.OSC['111'] = function(parseState) {
    this.terminal.setBackgroundColor();
  };
  
  /**
   * Reset text cursor color.
   */
  hterm.VT.OSC['112'] = function(parseState) {
    this.terminal.setCursorColor();
  };
  
  /**
   * iTerm2 extended sequences.
   *
   * We only support image display atm.
   */
  hterm.VT.OSC['1337'] = function(parseState) {
    // Args come in as a set of key value pairs followed by data.
    // File=name=<base64>;size=123;inline=1:<base64 data>
    let args = parseState.args[0].match(/^File=([^:]*):([\s\S]*)$/m);
    if (!args) {
      if (this.warnUnimplemented)
        console.log(`iTerm2 1337: unsupported sequence: ${args[1]}`);
      return;
    }
  
    const options = {
      name: '',
      size: 0,
      preserveAspectRatio: true,
      inline: false,
      width: 'auto',
      height: 'auto',
      align: 'left',
      uri: 'data:application/octet-stream;base64,' + args[2].replace(/[\n\r]+/gm, ''),
    };
    // Walk the "key=value;" sets.
    args[1].split(';').forEach((ele) => {
      const kv = ele.match(/^([^=]+)=(.*)$/m);
      if (!kv)
        return;
  
      // Sanitize values nicely.
      switch (kv[1]) {
        case 'name':
          try {
            options.name = window.atob(kv[2]);
          } catch (e) {}
          break;
        case 'size':
          try {
            options.size = parseInt(kv[2]);
          } catch (e) {}
          break;
        case 'width':
          options.width = kv[2];
          break;
        case 'height':
          options.height = kv[2];
          break;
        case 'preserveAspectRatio':
          options.preserveAspectRatio = !(kv[2] == '0');
          break;
        case 'inline':
          options.inline = !(kv[2] == '0');
          break;
        // hterm-specific keys.
        case 'align':
          options.align = kv[2];
          break;
        default:
          // Ignore unknown keys.  Don't want remote stuffing our JS env.
          break;
      }
    });
  
    // This is a bit of a hack.  If the buffer has data following the image, we
    // need to delay processing of it until after we've finished with the image.
    // Otherwise while we wait for the the image to load asynchronously, the new
    // text data will intermingle with the image.
    if (options.inline) {
      const io = this.terminal.io;
      const queued = parseState.peekRemainingBuf();
      parseState.advance(queued.length);
      this.terminal.displayImage(options);
      io.writeUTF8(queued);
    } else
      this.terminal.displayImage(options);
  };
  
  /**
   * URxvt perl modules.
   *
   * This is the escape system used by rxvt-unicode and its perl modules.
   * Obviously we don't support perl or custom modules, so we list a few common
   * ones that we find useful.
   *
   * Technically there is no format here, but most modules obey:
   * <module name>;<module args, usually ; delimited>
   */
  hterm.VT.OSC['777'] = function(parseState) {
    var ary;
    var urxvtMod = parseState.args[0].split(';', 1)[0];
  
    switch (urxvtMod) {
      case 'notify':
        // Format:
        // notify;title;message
        var title, message;
        ary = parseState.args[0].match(/^[^;]+;([^;]*)(;([\s\S]*))?$/);
        if (ary) {
          title = ary[1];
          message = ary[3];
        }
        hterm.notify({'title': title, 'body': message});
        break;
  
      default:
        console.warn('Unknown urxvt module: ' + parseState.args[0]);
        break;
    }
  };
  
  /**
   * Insert (blank) characters (ICH).
   */
  hterm.VT.CSI['@'] = function(parseState) {
    this.terminal.insertSpace(parseState.iarg(0, 1));
  };
  
  /**
   * Cursor Up (CUU).
   */
  hterm.VT.CSI['A'] = function(parseState) {
    this.terminal.cursorUp(parseState.iarg(0, 1));
  };
  
  /**
   * Cursor Down (CUD).
   */
  hterm.VT.CSI['B'] = function(parseState) {
    this.terminal.cursorDown(parseState.iarg(0, 1));
  };
  
  /**
   * Cursor Forward (CUF).
   */
  hterm.VT.CSI['C'] = function(parseState) {
    this.terminal.cursorRight(parseState.iarg(0, 1));
  };
  
  /**
   * Cursor Backward (CUB).
   */
  hterm.VT.CSI['D'] = function(parseState) {
    this.terminal.cursorLeft(parseState.iarg(0, 1));
  };
  
  /**
   * Cursor Next Line (CNL).
   *
   * This is like Cursor Down, except the cursor moves to the beginning of the
   * line as well.
   */
  hterm.VT.CSI['E'] = function(parseState) {
    this.terminal.cursorDown(parseState.iarg(0, 1));
    this.terminal.setCursorColumn(0);
  };
  
  /**
   * Cursor Preceding Line (CPL).
   *
   * This is like Cursor Up, except the cursor moves to the beginning of the
   * line as well.
   */
  hterm.VT.CSI['F'] = function(parseState) {
    this.terminal.cursorUp(parseState.iarg(0, 1));
    this.terminal.setCursorColumn(0);
  };
  
  /**
   * Cursor Horizontal Absolute (CHA).
   *
   * Xterm calls this Cursor Character Absolute.
   */
  hterm.VT.CSI['G'] = function(parseState) {
    this.terminal.setCursorColumn(parseState.iarg(0, 1) - 1);
  };
  
  /**
   * Cursor Position (CUP).
   */
  hterm.VT.CSI['H'] = function(parseState) {
    this.terminal.setCursorPosition(parseState.iarg(0, 1) - 1,
                                    parseState.iarg(1, 1) - 1);
  };
  
  /**
   * Cursor Forward Tabulation (CHT).
   */
  hterm.VT.CSI['I'] = function(parseState) {
    var count = parseState.iarg(0, 1);
    count = lib.f.clamp(count, 1, this.terminal.screenSize.width);
    for (var i = 0; i < count; i++) {
      this.terminal.forwardTabStop();
    }
  };
  
  /**
   * Erase in Display (ED, DECSED).
   */
  hterm.VT.CSI['J'] =
  hterm.VT.CSI['?J'] = function(parseState, code) {
    var arg = parseState.args[0];
  
    if (!arg || arg == 0) {
      this.terminal.eraseBelow();
    } else if (arg == 1) {
      this.terminal.eraseAbove();
    } else if (arg == 2) {
      this.terminal.clear();
    } else if (arg == 3) {
      // The xterm docs say this means "Erase saved lines", but we'll just clear
      // the display since killing the scrollback seems rude.
      this.terminal.clear();
    }
  };
  
  /**
   * Erase in line (EL, DECSEL).
   */
  hterm.VT.CSI['K'] =
  hterm.VT.CSI['?K'] = function(parseState, code) {
    var arg = parseState.args[0];
  
    if (!arg || arg == 0) {
      this.terminal.eraseToRight();
    } else if (arg == 1) {
      this.terminal.eraseToLeft();
    } else if (arg == 2) {
      this.terminal.eraseLine();
    }
  };
  
  /**
   * Insert Lines (IL).
   */
  hterm.VT.CSI['L'] = function(parseState) {
    this.terminal.insertLines(parseState.iarg(0, 1));
  };
  
  /**
   * Delete Lines (DL).
   */
  hterm.VT.CSI['M'] = function(parseState) {
    this.terminal.deleteLines(parseState.iarg(0, 1));
  };
  
  /**
   * Delete Characters (DCH).
   *
   * This command shifts the line contents left, starting at the cursor position.
   */
  hterm.VT.CSI['P'] = function(parseState) {
    this.terminal.deleteChars(parseState.iarg(0, 1));
  };
  
  /**
   * Scroll Up (SU).
   */
  hterm.VT.CSI['S'] = function(parseState) {
    this.terminal.vtScrollUp(parseState.iarg(0, 1));
  };
  
  /**
   * Scroll Down (SD).
   * Also 'Initiate highlight mouse tracking'.  Will not implement this part.
   */
  hterm.VT.CSI['T'] = function(parseState) {
    if (parseState.args.length <= 1)
      this.terminal.vtScrollDown(parseState.iarg(0, 1));
  };
  
  /**
   * Reset one or more features of the title modes to the default value.
   *
   *   ESC [ > Ps T
   *
   * Normally, "reset" disables the feature. It is possible to disable the
   * ability to reset features by compiling a different default for the title
   * modes into xterm.
   *
   * Ps values:
   *   0 - Do not set window/icon labels using hexadecimal.
   *   1 - Do not query window/icon labels using hexadecimal.
   *   2 - Do not set window/icon labels using UTF-8.
   *   3 - Do not query window/icon labels using UTF-8.
   *
   * Will not implement.
   */
  hterm.VT.CSI['>T'] = hterm.VT.ignore;
  
  /**
   * Erase Characters (ECH).
   */
  hterm.VT.CSI['X'] = function(parseState) {
    this.terminal.eraseToRight(parseState.iarg(0, 1));
  };
  
  /**
   * Cursor Backward Tabulation (CBT).
   */
  hterm.VT.CSI['Z'] = function(parseState) {
    var count = parseState.iarg(0, 1);
    count = lib.f.clamp(count, 1, this.terminal.screenSize.width);
    for (var i = 0; i < count; i++) {
      this.terminal.backwardTabStop();
    }
  };
  
  /**
   * Character Position Absolute (HPA).
   *
   * Same as Cursor Horizontal Absolute (CHA).
   */
  hterm.VT.CSI['`'] = hterm.VT.CSI['G'];
  
  /**
   * Character Position Relative (HPR).
   */
  hterm.VT.CSI['a'] = function(parseState) {
    this.terminal.setCursorColumn(this.terminal.getCursorColumn() +
                                  parseState.iarg(0, 1));
  };
  
  /**
   * Repeat the preceding graphic character.
   *
   * Not currently implemented.
   */
  hterm.VT.CSI['b'] = hterm.VT.ignore;
  
  /**
   * Send Device Attributes (Primary DA).
   *
   * TODO(rginda): This is hardcoded to send back 'VT100 with Advanced Video
   * Option', but it may be more correct to send a VT220 response once
   * we fill out the 'Not currently implemented' parts.
   */
  hterm.VT.CSI['c'] = function(parseState) {
    if (!parseState.args[0] || parseState.args[0] == 0) {
      this.terminal.io.sendString('\x1b[?1;2c');
    }
  };
  
  /**
   * Send Device Attributes (Secondary DA).
   *
   * TODO(rginda): This is hardcoded to send back 'VT100' but it may be more
   * correct to send a VT220 response once we fill out more 'Not currently
   * implemented' parts.
   */
  hterm.VT.CSI['>c'] = function(parseState) {
    this.terminal.io.sendString('\x1b[>0;256;0c');
  };
  
  /**
   * Line Position Absolute (VPA).
   */
  hterm.VT.CSI['d'] = function(parseState) {
    this.terminal.setAbsoluteCursorRow(parseState.iarg(0, 1) - 1);
  };
  
  /**
   * Horizontal and Vertical Position (HVP).
   *
   * Same as Cursor Position (CUP).
   */
  hterm.VT.CSI['f'] = hterm.VT.CSI['H'];
  
  /**
   * Tab Clear (TBC).
   */
  hterm.VT.CSI['g'] = function(parseState) {
    if (!parseState.args[0] || parseState.args[0] == 0) {
      // Clear tab stop at cursor.
      this.terminal.clearTabStopAtCursor(false);
    } else if (parseState.args[0] == 3) {
      // Clear all tab stops.
      this.terminal.clearAllTabStops();
    }
  };
  
  /**
   * Set Mode (SM).
   */
  hterm.VT.CSI['h'] = function(parseState) {
    for (var i = 0; i < parseState.args.length; i++) {
      this.setANSIMode(parseState.args[i], true);
    }
  };
  
  /**
   * DEC Private Mode Set (DECSET).
   */
  hterm.VT.CSI['?h'] = function(parseState) {
    for (var i = 0; i < parseState.args.length; i++) {
      this.setDECMode(parseState.args[i], true);
    }
  };
  
  /**
   * Media Copy (MC).
   * Media Copy (MC, DEC Specific).
   *
   * These commands control the printer.  Will not implement.
   */
  hterm.VT.CSI['i'] =
  hterm.VT.CSI['?i'] = hterm.VT.ignore;
  
  /**
   * Reset Mode (RM).
   */
  hterm.VT.CSI['l'] = function(parseState) {
    for (var i = 0; i < parseState.args.length; i++) {
      this.setANSIMode(parseState.args[i], false);
    }
  };
  
  /**
   * DEC Private Mode Reset (DECRST).
   */
  hterm.VT.CSI['?l'] = function(parseState) {
    for (var i = 0; i < parseState.args.length; i++) {
      this.setDECMode(parseState.args[i], false);
    }
  };
  
  /**
   * Parse extended SGR 38/48 sequences.
   *
   * This deals with the various ISO 8613-6 forms, and with legacy xterm forms
   * that are common in the wider application world.
   *
   * @param {hterm.VT.ParseState} parseState The current input state.
   * @param {number} i The argument in parseState to start processing.
   * @param {hterm.TextAttributes} attrs The current text attributes.
   * @return {Object} The skipCount member defines how many arguments to skip
   *     (i.e. how many were processed), and the color member is the color that
   *     was successfully processed, or undefined if not.
   */
  hterm.VT.prototype.parseSgrExtendedColors = function(parseState, i, attrs) {
    let ary;
    let usedSubargs;
  
    if (parseState.argHasSubargs(i)) {
      // The ISO 8613-6 compliant form.
      // e.g. 38:[color choice]:[arg1]:[arg2]:...
      ary = parseState.args[i].split(':');
      ary.shift();  // Remove "38".
      usedSubargs = true;
    } else if (parseState.argHasSubargs(i + 1)) {
      // The xterm form which isn't ISO 8613-6 compliant.  Not many emulators
      // support this, and others actively do not want to.  We'll ignore it so
      // at least the rest of the stream works correctly.  e.g. 38;2:R:G:B
      // We return 0 here so we only skip the "38" ... we can't be confident the
      // next arg is actually supposed to be part of it vs a typo where the next
      // arg is legit.
      return {skipCount: 0};
    } else {
      // The xterm form which isn't ISO 8613-6 compliant, but many emulators
      // support, and many applications rely on.
      // e.g. 38;2;R;G;B
      ary = parseState.args.slice(i + 1);
      usedSubargs = false;
    }
  
    // Figure out which form to parse.
    switch (parseInt(ary[0])) {
      default:  // Unknown.
      case 0:  // Implementation defined.  We ignore it.
        return {skipCount: 0};
  
      case 1: {  // Transparent color.
        // Require ISO 8613-6 form.
        if (!usedSubargs)
          return {skipCount: 0};
  
        return {
          color: 'rgba(0, 0, 0, 0)',
          skipCount: 0,
        };
      }
  
      case 2: {  // RGB color.
        // Skip over the color space identifier, if it exists.
        let start;
        if (usedSubargs) {
          // The ISO 8613-6 compliant form:
          //   38:2:<color space id>:R:G:B[:...]
          // The xterm form isn't ISO 8613-6 compliant.
          //   38:2:R:G:B
          // Since the ISO 8613-6 form requires at least 5 arguments,
          // we can still support the xterm form unambiguously.
          if (ary.length == 4)
            start = 1;
          else
            start = 2;
        } else {
          // The legacy xterm form: 38;2;R;G;B
          start = 1;
        }
  
        // We need at least 3 args for RGB.  If we don't have them, assume this
        // sequence is corrupted, so don't eat anything more.
        // We ignore more than 3 args on purpose since ISO 8613-6 defines some,
        // and we don't care about them.
        if (ary.length < start + 3)
          return {skipCount: 0};
  
        const r = parseState.parseInt(ary[start + 0]);
        const g = parseState.parseInt(ary[start + 1]);
        const b = parseState.parseInt(ary[start + 2]);
        return {
          color: `rgb(${r}, ${g}, ${b})`,
          skipCount: usedSubargs ? 0 : 4,
        };
      }
  
      case 3: {  // CMY color.
        // No need to support xterm/legacy forms as xterm doesn't support CMY.
        if (!usedSubargs)
          return {skipCount: 0};
  
        // We need at least 4 args for CMY.  If we don't have them, assume
        // this sequence is corrupted.  We ignore the color space identifier,
        // tolerance, etc...
        if (ary.length < 4)
          return {skipCount: 0};
  
        // TODO: See CMYK below.
        const c = parseState.parseInt(ary[1]);
        const m = parseState.parseInt(ary[2]);
        const y = parseState.parseInt(ary[3]);
        return {skipCount: 0};
      }
  
      case 4: {  // CMYK color.
        // No need to support xterm/legacy forms as xterm doesn't support CMYK.
        if (!usedSubargs)
          return {skipCount: 0};
  
        // We need at least 5 args for CMYK.  If we don't have them, assume
        // this sequence is corrupted.  We ignore the color space identifier,
        // tolerance, etc...
        if (ary.length < 5)
          return {skipCount: 0};
  
        // TODO: Implement this.
        // Might wait until CSS4 is adopted for device-cmyk():
        // https://www.w3.org/TR/css-color-4/#cmyk-colors
        // Or normalize it to RGB ourselves:
        // https://www.w3.org/TR/css-color-4/#cmyk-rgb
        const c = parseState.parseInt(ary[1]);
        const m = parseState.parseInt(ary[2]);
        const y = parseState.parseInt(ary[3]);
        const k = parseState.parseInt(ary[4]);
        return {skipCount: 0};
      }
  
      case 5: {  // Color palette index.
        // If we're short on args, assume this sequence is corrupted, so don't
        // eat anything more.
        if (ary.length < 2)
          return {skipCount: 0};
  
        // Support 38:5:P (ISO 8613-6) and 38;5;P (xterm/legacy).
        // We also ignore extra args with 38:5:P:[...], but more for laziness.
        const ret = {
          skipCount: usedSubargs ? 0 : 2,
        };
        const color = parseState.parseInt(ary[1]);
        if (color < attrs.colorPalette.length)
          ret.color = color;
        return ret;
      }
    }
  };
  
  /**
   * Character Attributes (SGR).
   *
   * Iterate through the list of arguments, applying the attribute changes based
   * on the argument value...
   */
  hterm.VT.CSI['m'] = function(parseState) {
    var attrs = this.terminal.getTextAttributes();
  
    if (!parseState.args.length) {
      attrs.reset();
      return;
    }
  
    for (var i = 0; i < parseState.args.length; i++) {
      // If this argument has subargs (i.e. it has args followed by colons),
      // the iarg logic will implicitly truncate that off for us.
      var arg = parseState.iarg(i, 0);
  
      if (arg < 30) {
        if (arg == 0) {  // Normal (default).
          attrs.reset();
        } else if (arg == 1) {  // Bold.
          attrs.bold = true;
        } else if (arg == 2) {  // Faint.
          attrs.faint = true;
        } else if (arg == 3) {  // Italic.
          attrs.italic = true;
        } else if (arg == 4) {  // Underline.
          if (parseState.argHasSubargs(i)) {
            const uarg = parseState.args[i].split(':')[1];
            if (uarg == 0)
              attrs.underline = false;
            else if (uarg == 1)
              attrs.underline = 'solid';
            else if (uarg == 2)
              attrs.underline = 'double';
            else if (uarg == 3)
              attrs.underline = 'wavy';
            else if (uarg == 4)
              attrs.underline = 'dotted';
            else if (uarg == 5)
              attrs.underline = 'dashed';
          } else {
            attrs.underline = 'solid';
          }
        } else if (arg == 5) {  // Blink.
          attrs.blink = true;
        } else if (arg == 7) {  // Inverse.
          attrs.inverse = true;
        } else if (arg == 8) {  // Invisible.
          attrs.invisible = true;
        } else if (arg == 9) {  // Crossed out.
          attrs.strikethrough = true;
        } else if (arg == 21) {  // Double underlined.
          attrs.underline = 'double';
        } else if (arg == 22) {  // Not bold & not faint.
          attrs.bold = false;
          attrs.faint = false;
        } else if (arg == 23) {  // Not italic.
          attrs.italic = false;
        } else if (arg == 24) {  // Not underlined.
          attrs.underline = false;
        } else if (arg == 25) {  // Not blink.
          attrs.blink = false;
        } else if (arg == 27) {  // Steady.
          attrs.inverse = false;
        } else if (arg == 28) {  // Visible.
          attrs.invisible = false;
        } else if (arg == 29) {  // Not crossed out.
          attrs.strikethrough = false;
        }
  
      } else if (arg < 50) {
        // Select fore/background color from bottom half of 16 color palette
        // or from the 256 color palette or alternative specify color in fully
        // qualified rgb(r, g, b) form.
        if (arg < 38) {
          attrs.foregroundSource = arg - 30;
  
        } else if (arg == 38) {
          const result = this.parseSgrExtendedColors(parseState, i, attrs);
          if (result.color !== undefined)
            attrs.foregroundSource = result.color;
          i += result.skipCount;
  
        } else if (arg == 39) {
          attrs.foregroundSource = attrs.SRC_DEFAULT;
  
        } else if (arg < 48) {
          attrs.backgroundSource = arg - 40;
  
        } else if (arg == 48) {
          const result = this.parseSgrExtendedColors(parseState, i, attrs);
          if (result.color !== undefined)
            attrs.backgroundSource = result.color;
          i += result.skipCount;
  
        } else {
          attrs.backgroundSource = attrs.SRC_DEFAULT;
        }
  
      } else if (arg == 58) {  // Underline coloring.
        const result = this.parseSgrExtendedColors(parseState, i, attrs);
        if (result.color !== undefined)
          attrs.underlineSource = result.color;
        i += result.skipCount;
  
      } else if (arg == 59) {  // Disable underline coloring.
        attrs.underlineSource = attrs.SRC_DEFAULT;
  
      } else if (arg >= 90 && arg <= 97) {
        attrs.foregroundSource = arg - 90 + 8;
  
      } else if (arg >= 100 && arg <= 107) {
        attrs.backgroundSource = arg - 100 + 8;
      }
    }
  
    attrs.setDefaults(this.terminal.getForegroundColor(),
                      this.terminal.getBackgroundColor());
  };
  
  // SGR calls can handle subargs.
  hterm.VT.CSI['m'].supportsSubargs = true;
  
  /**
   * Set xterm-specific keyboard modes.
   *
   * Will not implement.
   */
  hterm.VT.CSI['>m'] = hterm.VT.ignore;
  
  /**
   * Device Status Report (DSR, DEC Specific).
   *
   * 5 - Status Report. Result (OK) is CSI 0 n
   * 6 - Report Cursor Position (CPR) [row;column]. Result is CSI r ; c R
   */
  hterm.VT.CSI['n'] = function(parseState) {
    if (parseState.args[0] == 5) {
      this.terminal.io.sendString('\x1b0n');
    } else if (parseState.args[0] == 6) {
      var row = this.terminal.getCursorRow() + 1;
      var col = this.terminal.getCursorColumn() + 1;
      this.terminal.io.sendString('\x1b[' + row + ';' + col + 'R');
    }
  };
  
  /**
   * Disable modifiers which may be enabled via CSI['>m'].
   *
   * Will not implement.
   */
  hterm.VT.CSI['>n'] = hterm.VT.ignore;
  
  /**
   * Device Status Report (DSR, DEC Specific).
   *
   * 6  - Report Cursor Position (CPR) [row;column] as CSI ? r ; c R
   * 15 - Report Printer status as CSI ? 1 0 n (ready) or
   *      CSI ? 1 1 n (not ready).
   * 25 - Report UDK status as CSI ? 2 0 n (unlocked) or CSI ? 2 1 n (locked).
   * 26 - Report Keyboard status as CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).
   *      The last two parameters apply to VT400 & up, and denote keyboard ready
   *      and LK01 respectively.
   * 53 - Report Locator status as CSI ? 5 3 n Locator available, if compiled-in,
   *      or CSI ? 5 0 n No Locator, if not.
   */
  hterm.VT.CSI['?n'] = function(parseState) {
    if (parseState.args[0] == 6) {
      var row = this.terminal.getCursorRow() + 1;
      var col = this.terminal.getCursorColumn() + 1;
      this.terminal.io.sendString('\x1b[' + row + ';' + col + 'R');
    } else if (parseState.args[0] == 15) {
      this.terminal.io.sendString('\x1b[?11n');
    } else if (parseState.args[0] == 25) {
      this.terminal.io.sendString('\x1b[?21n');
    } else if (parseState.args[0] == 26) {
      this.terminal.io.sendString('\x1b[?12;1;0;0n');
    } else if (parseState.args[0] == 53) {
      this.terminal.io.sendString('\x1b[?50n');
    }
  };
  
  /**
   * This is used by xterm to decide whether to hide the pointer cursor as the
   * user types.
   *
   * Valid values for the parameter:
   *   0 - Never hide the pointer.
   *   1 - Hide if the mouse tracking mode is not enabled.
   *   2 - Always hide the pointer.
   *
   * If no parameter is given, xterm uses the default, which is 1.
   *
   * Not currently implemented.
   */
  hterm.VT.CSI['>p'] = hterm.VT.ignore;
  
  /**
   * Soft terminal reset (DECSTR).
   */
  hterm.VT.CSI['!p'] = function() {
    this.terminal.softReset();
  };
  
  /**
   * Request ANSI Mode (DECRQM).
   *
   * Not currently implemented.
   */
  hterm.VT.CSI['$p'] = hterm.VT.ignore;
  hterm.VT.CSI['?$p'] = hterm.VT.ignore;
  
  /**
   * Set conformance level (DECSCL).
   *
   * Not currently implemented.
   */
  hterm.VT.CSI['"p'] = hterm.VT.ignore;
  
  /**
   * Load LEDs (DECLL).
   *
   * Not currently implemented.  Could be implemented as virtual LEDs overlaying
   * the terminal if anyone cares.
   */
  hterm.VT.CSI['q'] = hterm.VT.ignore;
  
  /**
   * Set cursor style (DECSCUSR, VT520).
   */
  hterm.VT.CSI[' q'] = function(parseState) {
    var arg = parseState.args[0];
  
    if (arg == 0 || arg == 1) {
      this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
      this.terminal.setCursorBlink(true);
    } else if (arg == 2) {
      this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
      this.terminal.setCursorBlink(false);
    } else if (arg == 3) {
      this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
      this.terminal.setCursorBlink(true);
    } else if (arg == 4) {
      this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
      this.terminal.setCursorBlink(false);
    } else if (arg == 5) {
      this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
      this.terminal.setCursorBlink(true);
    } else if (arg == 6) {
      this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
      this.terminal.setCursorBlink(false);
    } else {
      console.warn('Unknown cursor style: ' + arg);
    }
  };
  
  /**
   * Select character protection attribute (DECSCA).
   *
   * Will not implement.
   */
  hterm.VT.CSI['"q'] = hterm.VT.ignore;
  
  /**
   * Set Scrolling Region (DECSTBM).
   */
  hterm.VT.CSI['r'] = function(parseState) {
    var args = parseState.args;
    var scrollTop = args[0] ? parseInt(args[0], 10) -1 : null;
    var scrollBottom = args[1] ? parseInt(args[1], 10) - 1 : null;
    this.terminal.setVTScrollRegion(scrollTop, scrollBottom);
    this.terminal.setCursorPosition(0, 0);
  };
  
  /**
   * Restore DEC Private Mode Values.
   *
   * Will not implement.
   */
  hterm.VT.CSI['?r'] = hterm.VT.ignore;
  
  /**
   * Change Attributes in Rectangular Area (DECCARA)
   *
   * Will not implement.
   */
  hterm.VT.CSI['$r'] = hterm.VT.ignore;
  
  /**
   * Save cursor (ANSI.SYS)
   */
  hterm.VT.CSI['s'] = function() {
    this.terminal.saveCursorAndState();
  };
  
  /**
   * Save DEC Private Mode Values.
   *
   * Will not implement.
   */
  hterm.VT.CSI['?s'] = hterm.VT.ignore;
  
  /**
   * Window manipulation (from dtterm, as well as extensions).
   *
   * Will not implement.
   */
  hterm.VT.CSI['t'] = hterm.VT.ignore;
  
  /**
   * Reverse Attributes in Rectangular Area (DECRARA).
   *
   * Will not implement.
   */
  hterm.VT.CSI['$t'] = hterm.VT.ignore;
  
  /**
   * Set one or more features of the title modes.
   *
   * Will not implement.
   */
  hterm.VT.CSI['>t'] = hterm.VT.ignore;
  
  /**
   * Set warning-bell volume (DECSWBV, VT520).
   *
   * Will not implement.
   */
  hterm.VT.CSI[' t'] = hterm.VT.ignore;
  
  /**
   * Restore cursor (ANSI.SYS).
   */
  hterm.VT.CSI['u'] = function() {
    this.terminal.restoreCursorAndState();
  };
  
  /**
   * Set margin-bell volume (DECSMBV, VT520).
   *
   * Will not implement.
   */
  hterm.VT.CSI[' u'] = hterm.VT.ignore;
  
  /**
   * Copy Rectangular Area (DECCRA, VT400 and up).
   *
   * Will not implement.
   */
  hterm.VT.CSI['$v'] = hterm.VT.ignore;
  
  /**
   * Enable Filter Rectangle (DECEFR).
   *
   * Will not implement.
   */
  hterm.VT.CSI['\'w'] = hterm.VT.ignore;
  
  /**
   * Request Terminal Parameters (DECREQTPARM).
   *
   * Not currently implemented.
   */
  hterm.VT.CSI['x'] = hterm.VT.ignore;
  
  /**
   * Select Attribute Change Extent (DECSACE).
   *
   * Will not implement.
   */
  hterm.VT.CSI['*x'] = hterm.VT.ignore;
  
  /**
   * Fill Rectangular Area (DECFRA), VT420 and up.
   *
   * Will not implement.
   */
  hterm.VT.CSI['$x'] = hterm.VT.ignore;
  
  /**
   * vt_tiledata (as used by NAOhack and UnNetHack)
   * (see https://nethackwiki.com/wiki/Vt_tiledata for more info)
   *
   * Implemented as far as we care (start a glyph and end a glyph).
   */
  hterm.VT.CSI['z'] = function(parseState) {
    if (parseState.args.length < 1)
      return;
    var arg = parseState.args[0];
    if (arg == 0) {
      // Start a glyph (one parameter, the glyph number).
      if (parseState.args.length < 2)
        return;
      this.terminal.getTextAttributes().tileData = parseState.args[1];
    } else if (arg == 1) {
      // End a glyph.
      this.terminal.getTextAttributes().tileData = null;
    }
  };
  
  /**
   * Enable Locator Reporting (DECELR).
   *
   * Not currently implemented.
   */
  hterm.VT.CSI['\'z'] = hterm.VT.ignore;
  
  /**
   * Erase Rectangular Area (DECERA), VT400 and up.
   *
   * Will not implement.
   */
  hterm.VT.CSI['$z'] = hterm.VT.ignore;
  
  /**
   * Select Locator Events (DECSLE).
   *
   * Not currently implemented.
   */
  hterm.VT.CSI['\'{'] = hterm.VT.ignore;
  
  /**
   * Request Locator Position (DECRQLP).
   *
   * Not currently implemented.
   */
  hterm.VT.CSI['\'|'] = hterm.VT.ignore;
  
  /**
   * Insert Columns (DECIC), VT420 and up.
   *
   * Will not implement.
   */
  hterm.VT.CSI['\'}'] = hterm.VT.ignore;
  
  /**
   * Delete P s Columns (DECDC), VT420 and up.
   *
   * Will not implement.
   */
  hterm.VT.CSI['\'~'] = hterm.VT.ignore;
  // SOURCE FILE: hterm/js/hterm_vt_character_map.js
  // Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  'use strict';
  
  lib.rtdep('lib.f');
  
  /**
   * Character map object.
   *
   * Mapping from received to display character, used depending on the active
   * VT character set.
   *
   * GR maps are not currently supported.
   *
   * @param {string} description A human readable description of this map.
   * @param {Object} glmap The GL mapping from input to output characters.
   */
  hterm.VT.CharacterMap = function(description, glmap) {
    /**
     * Short description for this character set, useful for debugging.
     */
    this.description = description;
  
    /**
     * The function to call to when this map is installed in GL.
     */
    this.GL = null;
  
    // Always keep an unmodified reference to the map.
    // This allows us to sanely reset back to the original state.
    this.glmapBase_ = glmap;
  
    // Now sync the internal state as needed.
    this.sync_();
  };
  
  /**
   * Internal helper for resyncing internal state.
   *
   * Used when the mappings change.
   *
   * @param {Object?} opt_glmap Additional mappings to overlay on top of the
   *     base mapping.
   */
  hterm.VT.CharacterMap.prototype.sync_ = function(opt_glmap) {
    // If there are no maps, then reset the state back.
    if (!this.glmapBase_ && !opt_glmap) {
      this.GL = null;
      delete this.glmap_;
      delete this.glre_;
      return;
    }
  
    // Set the the GL mapping.  If we're given a custom mapping, then create a
    // new object to hold the merged map.  This way we can cleanly reset back.
    if (opt_glmap)
      this.glmap_ = Object.assign({}, this.glmapBase_, opt_glmap);
    else
      this.glmap_ = this.glmapBase_;
  
    var glchars = Object.keys(this.glmap_).map((key) =>
        '\\x' + lib.f.zpad(key.charCodeAt(0).toString(16)));
    this.glre_ = new RegExp('[' + glchars.join('') + ']', 'g');
  
    this.GL = (str) => str.replace(this.glre_, (ch) => this.glmap_[ch]);
  };
  
  /**
   * Reset map back to original mappings (discarding runtime updates).
   *
   * Specifically, any calls to setOverrides will be discarded.
   */
  hterm.VT.CharacterMap.prototype.reset = function() {
    // If we haven't been given a custom mapping, then there's nothing to reset.
    if (this.glmap_ !== this.glmapBase_)
      this.sync_();
  };
  
  /**
   * Merge custom changes to this map.
   *
   * The input map need not duplicate the existing mappings as it is merged with
   * the existing base map (what was created with).  Subsequent calls to this
   * will throw away previous override settings.
   *
   * @param {Object} glmap The custom map to override existing mappings.
   */
  hterm.VT.CharacterMap.prototype.setOverrides = function(glmap) {
    this.sync_(glmap);
  };
  
  /**
   * Return a copy of this mapping.
   *
   * @return {hterm.VT.CharacterMap} A new hterm.VT.CharacterMap instance.
   */
  hterm.VT.CharacterMap.prototype.clone = function() {
    var map = new hterm.VT.CharacterMap(this.description, this.glmapBase_);
    if (this.glmap_ !== this.glmapBase_)
      map.setOverrides(this.glmap_);
    return map;
  };
  
  /**
   * Table of character maps.
   */
  hterm.VT.CharacterMaps = function() {
    this.maps_ = hterm.VT.CharacterMaps.DefaultMaps;
  
    // Always keep an unmodified reference to the map.
    // This allows us to sanely reset back to the original state.
    this.mapsBase_ = this.maps_;
  };
  
  /**
   * Look up a previously registered map.
   *
   * @param {String} name The name of the map to lookup.
   * @return {hterm.VT.CharacterMap} The map, if it's been registered.
   */
  hterm.VT.CharacterMaps.prototype.getMap = function(name) {
    if (this.maps_.hasOwnProperty(name))
      return this.maps_[name];
    else
      return undefined;
  };
  
  /**
   * Register a new map.
   *
   * Any previously registered maps by this name will be discarded.
   *
   * @param {String} name The name of the map.
   * @param {hterm.VT.CharacterMap} map The map to register.
   */
  hterm.VT.CharacterMaps.prototype.addMap = function(name, map) {
    if (this.maps_ === this.mapsBase_)
      this.maps_ = Object.assign({}, this.mapsBase_);
    this.maps_[name] = map;
  };
  
  /**
   * Reset the table and all its maps back to original state.
   */
  hterm.VT.CharacterMaps.prototype.reset = function() {
    if (this.maps_ !== hterm.VT.CharacterMaps.DefaultMaps)
      this.maps_ = hterm.VT.CharacterMaps.DefaultMaps;
  };
  
  /**
   * Merge custom changes to this table.
   *
   * @param {Object} maps A set of hterm.VT.CharacterMap objects.
   */
  hterm.VT.CharacterMaps.prototype.setOverrides = function(maps) {
    if (this.maps_ === this.mapsBase_)
      this.maps_ = Object.assign({}, this.mapsBase_);
  
    for (var name in maps) {
      var map = this.getMap(name);
      if (map !== undefined) {
        this.maps_[name] = map.clone();
        this.maps_[name].setOverrides(maps[name]);
      } else
        this.addMap(name, new hterm.VT.CharacterMap('user ' + name, maps[name]));
    }
  };
  
  /**
   * The default set of supported character maps.
   */
  hterm.VT.CharacterMaps.DefaultMaps = {};
  
  /**
   * VT100 Graphic character map.
   * http://vt100.net/docs/vt220-rm/table2-4.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['0'] = new hterm.VT.CharacterMap(
      'graphic', {
        '\x60':'\u25c6',  // ` -> diamond
        '\x61':'\u2592',  // a -> grey-box
        '\x62':'\u2409',  // b -> h/t
        '\x63':'\u240c',  // c -> f/f
        '\x64':'\u240d',  // d -> c/r
        '\x65':'\u240a',  // e -> l/f
        '\x66':'\u00b0',  // f -> degree
        '\x67':'\u00b1',  // g -> +/-
        '\x68':'\u2424',  // h -> n/l
        '\x69':'\u240b',  // i -> v/t
        '\x6a':'\u2518',  // j -> bottom-right
        '\x6b':'\u2510',  // k -> top-right
        '\x6c':'\u250c',  // l -> top-left
        '\x6d':'\u2514',  // m -> bottom-left
        '\x6e':'\u253c',  // n -> line-cross
        '\x6f':'\u23ba',  // o -> scan1
        '\x70':'\u23bb',  // p -> scan3
        '\x71':'\u2500',  // q -> scan5
        '\x72':'\u23bc',  // r -> scan7
        '\x73':'\u23bd',  // s -> scan9
        '\x74':'\u251c',  // t -> left-tee
        '\x75':'\u2524',  // u -> right-tee
        '\x76':'\u2534',  // v -> bottom-tee
        '\x77':'\u252c',  // w -> top-tee
        '\x78':'\u2502',  // x -> vertical-line
        '\x79':'\u2264',  // y -> less-equal
        '\x7a':'\u2265',  // z -> greater-equal
        '\x7b':'\u03c0',  // { -> pi
        '\x7c':'\u2260',  // | -> not-equal
        '\x7d':'\u00a3',  // } -> british-pound
        '\x7e':'\u00b7',  // ~ -> dot
      });
  
  /**
   * British character map.
   * http://vt100.net/docs/vt220-rm/table2-5.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['A'] = new hterm.VT.CharacterMap(
      'british', {
        '\x23': '\u00a3',  // # -> british-pound
      });
  
  /**
   * US ASCII map, no changes.
   */
  hterm.VT.CharacterMaps.DefaultMaps['B'] = new hterm.VT.CharacterMap(
      'us', null);
  
  /**
   * Dutch character map.
   * http://vt100.net/docs/vt220-rm/table2-6.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['4'] = new hterm.VT.CharacterMap(
      'dutch', {
        '\x23': '\u00a3',  // # -> british-pound
  
        '\x40': '\u00be',  // @ -> 3/4
  
        '\x5b': '\u0132',  // [ -> 'ij' ligature (xterm goes with \u00ff?)
        '\x5c': '\u00bd',  // \ -> 1/2
        '\x5d': '\u007c',  // ] -> vertical bar
  
        '\x7b': '\u00a8',  // { -> two dots
        '\x7c': '\u0066',  // | -> f
        '\x7d': '\u00bc',  // } -> 1/4
        '\x7e': '\u00b4',  // ~ -> acute
      });
  
  /**
   * Finnish character map.
   * http://vt100.net/docs/vt220-rm/table2-7.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['C'] =
  hterm.VT.CharacterMaps.DefaultMaps['5'] = new hterm.VT.CharacterMap(
      'finnish', {
        '\x5b': '\u00c4',  // [ -> 'A' umlaut
        '\x5c': '\u00d6',  // \ -> 'O' umlaut
        '\x5d': '\u00c5',  // ] -> 'A' ring
        '\x5e': '\u00dc',  // ~ -> 'u' umlaut
  
        '\x60': '\u00e9',  // ` -> 'e' acute
  
        '\x7b': '\u00e4',  // { -> 'a' umlaut
        '\x7c': '\u00f6',  // | -> 'o' umlaut
        '\x7d': '\u00e5',  // } -> 'a' ring
        '\x7e': '\u00fc',  // ~ -> 'u' umlaut
      });
  
  /**
   * French character map.
   * http://vt100.net/docs/vt220-rm/table2-8.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['R'] = new hterm.VT.CharacterMap(
      'french', {
        '\x23': '\u00a3',  // # -> british-pound
  
        '\x40': '\u00e0',  // @ -> 'a' grave
  
        '\x5b': '\u00b0',  // [ -> ring
        '\x5c': '\u00e7',  // \ -> 'c' cedilla
        '\x5d': '\u00a7',  // ] -> section symbol (double s)
  
        '\x7b': '\u00e9',  // { -> 'e' acute
        '\x7c': '\u00f9',  // | -> 'u' grave
        '\x7d': '\u00e8',  // } -> 'e' grave
        '\x7e': '\u00a8',  // ~ -> umlaut
      });
  
  /**
   * French Canadian character map.
   * http://vt100.net/docs/vt220-rm/table2-9.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['Q'] = new hterm.VT.CharacterMap(
      'french canadian', {
        '\x40': '\u00e0',  // @ -> 'a' grave
  
        '\x5b': '\u00e2',  // [ -> 'a' circumflex
        '\x5c': '\u00e7',  // \ -> 'c' cedilla
        '\x5d': '\u00ea',  // ] -> 'e' circumflex
        '\x5e': '\u00ee',  // ^ -> 'i' circumflex
  
        '\x60': '\u00f4',  // ` -> 'o' circumflex
  
        '\x7b': '\u00e9',  // { -> 'e' acute
        '\x7c': '\u00f9',  // | -> 'u' grave
        '\x7d': '\u00e8',  // } -> 'e' grave
        '\x7e': '\u00fb',  // ~ -> 'u' circumflex
      });
  
  /**
   * German character map.
   * http://vt100.net/docs/vt220-rm/table2-10.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['K'] = new hterm.VT.CharacterMap(
      'german', {
        '\x40': '\u00a7',  // @ -> section symbol (double s)
  
        '\x5b': '\u00c4',  // [ -> 'A' umlaut
        '\x5c': '\u00d6',  // \ -> 'O' umlaut
        '\x5d': '\u00dc',  // ] -> 'U' umlaut
  
        '\x7b': '\u00e4',  // { -> 'a' umlaut
        '\x7c': '\u00f6',  // | -> 'o' umlaut
        '\x7d': '\u00fc',  // } -> 'u' umlaut
        '\x7e': '\u00df',  // ~ -> eszett
      });
  
  /**
   * Italian character map.
   * http://vt100.net/docs/vt220-rm/table2-11.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['Y'] = new hterm.VT.CharacterMap(
      'italian', {
        '\x23': '\u00a3',  // # -> british-pound
  
        '\x40': '\u00a7',  // @ -> section symbol (double s)
  
        '\x5b': '\u00b0',  // [ -> ring
        '\x5c': '\u00e7',  // \ -> 'c' cedilla
        '\x5d': '\u00e9',  // ] -> 'e' acute
  
        '\x60': '\u00f9',  // ` -> 'u' grave
  
        '\x7b': '\u00e0',  // { -> 'a' grave
        '\x7c': '\u00f2',  // | -> 'o' grave
        '\x7d': '\u00e8',  // } -> 'e' grave
        '\x7e': '\u00ec',  // ~ -> 'i' grave
      });
  
  /**
   * Norwegian/Danish character map.
   * http://vt100.net/docs/vt220-rm/table2-12.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['E'] =
  hterm.VT.CharacterMaps.DefaultMaps['6'] = new hterm.VT.CharacterMap(
      'norwegian/danish', {
        '\x40': '\u00c4',  // @ -> 'A' umlaut
  
        '\x5b': '\u00c6',  // [ -> 'AE' ligature
        '\x5c': '\u00d8',  // \ -> 'O' stroke
        '\x5d': '\u00c5',  // ] -> 'A' ring
        '\x5e': '\u00dc',  // ^ -> 'U' umlaut
  
        '\x60': '\u00e4',  // ` -> 'a' umlaut
  
        '\x7b': '\u00e6',  // { -> 'ae' ligature
        '\x7c': '\u00f8',  // | -> 'o' stroke
        '\x7d': '\u00e5',  // } -> 'a' ring
        '\x7e': '\u00fc',  // ~ -> 'u' umlaut
      });
  
  /**
   * Spanish character map.
   * http://vt100.net/docs/vt220-rm/table2-13.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['Z'] = new hterm.VT.CharacterMap(
      'spanish', {
        '\x23': '\u00a3',  // # -> british-pound
  
        '\x40': '\u00a7',  // @ -> section symbol (double s)
  
        '\x5b': '\u00a1',  // [ -> '!' inverted
        '\x5c': '\u00d1',  // \ -> 'N' tilde
        '\x5d': '\u00bf',  // ] -> '?' inverted
  
        '\x7b': '\u00b0',  // { -> ring
        '\x7c': '\u00f1',  // | -> 'n' tilde
        '\x7d': '\u00e7',  // } -> 'c' cedilla
      });
  
  /**
   * Swedish character map.
   * http://vt100.net/docs/vt220-rm/table2-14.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['7'] =
  hterm.VT.CharacterMaps.DefaultMaps['H'] = new hterm.VT.CharacterMap(
      'swedish', {
        '\x40': '\u00c9',  // @ -> 'E' acute
  
        '\x5b': '\u00c4',  // [ -> 'A' umlaut
        '\x5c': '\u00d6',  // \ -> 'O' umlaut
        '\x5d': '\u00c5',  // ] -> 'A' ring
        '\x5e': '\u00dc',  // ^ -> 'U' umlaut
  
        '\x60': '\u00e9',  // ` -> 'e' acute
  
        '\x7b': '\u00e4',  // { -> 'a' umlaut
        '\x7c': '\u00f6',  // | -> 'o' umlaut
        '\x7d': '\u00e5',  // } -> 'a' ring
        '\x7e': '\u00fc',  // ~ -> 'u' umlaut
      });
  
  /**
   * Swiss character map.
   * http://vt100.net/docs/vt220-rm/table2-15.html
   */
  hterm.VT.CharacterMaps.DefaultMaps['='] = new hterm.VT.CharacterMap(
      'swiss', {
        '\x23': '\u00f9',  // # -> 'u' grave
  
        '\x40': '\u00e0',  // @ -> 'a' grave
  
        '\x5b': '\u00e9',  // [ -> 'e' acute
        '\x5c': '\u00e7',  // \ -> 'c' cedilla
        '\x5d': '\u00ea',  // ] -> 'e' circumflex
        '\x5e': '\u00ee',  // ^ -> 'i' circumflex
        '\x5f': '\u00e8',  // _ -> 'e' grave
  
        '\x60': '\u00f4',  // ` -> 'o' circumflex
  
        '\x7b': '\u00e4',  // { -> 'a' umlaut
        '\x7c': '\u00f6',  // | -> 'o' umlaut
        '\x7d': '\u00fc',  // } -> 'u' umlaut
        '\x7e': '\u00fb',  // ~ -> 'u' circumflex
      });
  exports.lib = lib; exports.hterm = hterm;
  
  