text: /*
   * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
   *
   * Permission is hereby granted, free of charge, to any person obtaining a
   * copy of this software and associated documentation files (the "Software"),
   * to deal in the Software without restriction, including without limitation
   * the rights to use, copy, modify, merge, publish, distribute, sublicense,
   * and/or sell copies of the Software, and to permit persons to whom the
   * Software is furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included
   * in all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   * DEALINGS IN THE SOFTWARE.
   */
  
  #include "Terminal.h"
  #include "TelnetDefs.h"
  
  /**
   * \class Terminal Terminal.h <Terminal.h>
   * \brief Extended stream interface for terminal operations.
   *
   * This class extends the standard Arduino Stream class with functions that
   * are suitable for interfacing to VT100 terminal applications like PuTTY.
   *
   * The following example initializes a terminal running on the primary
   * Arduino serial port:
   *
   * \code
   * Terminal term;
   * void setup() {
   *     Serial.begin(9600);
   *     term.begin(Serial);
   * }
   * \endcode
   *
   * The readKey() function reads input from the underlying stream, decodes
   * any VT100 key escape sequences that it finds, and reports them to the
   * application using USB, ASCII, or Unicode key codes.  This is typically
   * used in the application's main loop as follows:
   *
   * \code
   * void loop() {
   *     int key = term.readKey();
   *     switch (key) {
   *     case -1:     break;      // No key available.
   *
   *     case KEY_LEFT_ARROW:
   *         // Left arrow key has been pressed.
   *         ...
   *         break;
   *
   *     case KEY_RIGHT_ARROW:
   *         // Right arrow key has been pressed.
   *         ...
   *         break;
   *
   *     case KEY_ESC:
   *         // Escape key has been pressed.
   *         ...
   *         break;
   *
   *     default:
   *         if (key >= 0x20 && key <= 7E) {
   *             // Visible ASCII character has been typed.
   *             ...
   *         }
   *         break;
   *     }
   * }
   * \endcode
   *
   * This class understands extended characters in the UTF-8 encoding,
   * allowing the full Unicode character set to be used in applications.
   * Extended Unicode characters are reported by readKey() with the key code
   * KEY_UNICODE, with the actual code point returned via unicodeKey().
   *
   * On the output side, UTF-8 strings can be written to the terminal
   * using write(), or writeUnicode() can be used to write a single
   * Unicode character in UTF-8.
   *
   * \note This class does not have any special support for right-to-left
   * scripts or composed characters.  Unicode characters are read and written
   * in the order in which they arrive.  Applications may need to alter
   * strings to display them correctly in such scripts.  Patches are
   * welcome to fix this.
   *
   * \sa Shell
   */
  
  /** @cond */
  
  // States for the key recognition state machine.
  #define STATE_INIT      0       // Initial state.
  #define STATE_CR        1       // Last character was CR, eat following LF.
  #define STATE_ESC       2       // Last character was ESC.
  #define STATE_MATCH     3       // Matching an escape sequence.
  #define STATE_UTF8      4       // Recognizing a UTF-8 sequence.
  #define STATE_IAC       5       // Recognizing telnet command after IAC (0xFF).
  #define STATE_WILL      6       // Waiting for option code for WILL command.
  #define STATE_WONT      7       // Waiting for option code for WONT command.
  #define STATE_DO        8       // Waiting for option code for DO command.
  #define STATE_DONT      9       // Waiting for option code for DONT command.
  #define STATE_SB        10      // Option sub-negotiation.
  #define STATE_SB_IAC    11      // Option sub-negotiation, byte after IAC.
  
  // Number of milliseconds to wait after an ESC character before
  // concluding that it is KEY_ESC rather than an escape sequence.
  #define ESC_TIMEOUT_MS  40
  
  // Number of milliseconds to wait for a new character within an
  // escape sequence before concluding that the sequence was invalid
  // or truncated, or not actually an escape sequence at all.
  #define SEQ_TIMEOUT_MS  200
  
  /** @endcond */
  
  /**
   * \brief Constructs a terminal object.
   *
   * This constructor must be followed by a call to begin() to specify
   * the underlying stream to use for reading and writing.
   *
   * \sa begin()
   */
  Terminal::Terminal()
      : _stream(0)
      , ucode(-1)
      , ncols(80)
      , nrows(24)
      , timer(0)
      , offset(0)
      , state(STATE_INIT)
      , utf8len(0)
      , mod(Terminal::Serial)
      , flags(0)
  {
  }
  
  /**
   * \brief Destroys this terminal object.
   */
  Terminal::~Terminal()
  {
  }
  
  /**
   * \enum Terminal::Mode
   * \brief Mode to operate in, Serial or Telnet.
   */
  
  /**
   * \var Terminal::Serial
   * \brief Operates the terminal in serial mode.
   */
  
  /**
   * \var Terminal::Telnet
   * \brief Operates the terminal in telnet mode.
   */
  
  /**
   * \brief Begins terminal operations on an underlying stream.
   *
   * \param stream The underlying stream, whether a serial port, TCP connection,
   * or some other stream.
   * \param mode The mode to operate in, either Serial or Telnet.
   *
   * If Telnet mode is selected, then embedded commands and options from the
   * telnet protocol (<a href="https://tools.ietf.org/html/rfc854">RFC 854</a>)
   * will be interpreted.  This is useful if the underlying \a stream is a TCP
   * connection on port 23.  The mode operates as a telnet server.
   *
   * \sa end(), stream(), mode()
   */
  void Terminal::begin(Stream &stream, Mode mode)
  {
      _stream = &stream;
      ucode = -1;
      state = STATE_INIT;
      flags = 0;
      mod = mode;
  }
  
  /**
   * \brief Ends terminal operations on an underlying stream.
   *
   * This function may be useful if you want to detach the terminal from
   * the underlying stream so that it can be used for something else.
   */
  void Terminal::end()
  {
      _stream = 0;
  }
  
  /**
   * \fn Stream *Terminal::stream() const
   * \brief Returns a pointer to the underlying Stream, or NULL if the
   * stream has not been set with begin() yet.
   *
   * \sa begin()
   */
  
  /**
   * \fn Terminal::Mode Terminal::mode() const
   * \brief Returns the mode this terminal is operating in, Serial or Telnet.
   *
   * \sa begin()
   */
  
  /**
   * \brief Returns the number of bytes that are available for reading.
   *
   * \note It is possible for this function to return a positive value
   * while readKey() does not produce a new key.  This can happen with
   * VT100 key escape sequences and UTF-8 characters that extend over
   * multiple bytes.
   *
   * \sa readKey()
   */
  int Terminal::available()
  {
      return _stream ? _stream->available() : 0;
  }
  
  /**
   * \brief Peeks at the next byte from the underlying stream.
   *
   * \return The next byte or -1 if no bytes are available yet.
   *
   * \sa read()
   */
  int Terminal::peek()
  {
      return _stream ? _stream->peek() : -1;
  }
  
  /**
   * \brief Reads the next byte from the underlying stream.
   *
   * \return Returns 0x00 to 0xFF if a byte is ready, or -1 if none available.
   *
   * This function performs a low-level read on the underlying byte stream
   * without applying any specific interpretation to the byte.  In particular,
   * escape sequences corresponding to arrow and function keys will not
   * be recognized.
   *
   * Applications will usually want to call readKey() instead to handle
   * escape sequences for arrow and function keys.  This function is provided
   * as a convenience to implement the parent Stream interface.
   *
   * \sa readKey()
   */
  int Terminal::read()
  {
      // Clear the key recognition state because we are bypassing readKey().
      state = STATE_INIT;
      ucode = -1;
  
      // Read the next byte from the underlying stream.
      return _stream ? _stream->read() : -1;
  }
  
  /**
   * \brief Flushes all data in the underlying stream.
   */
  void Terminal::flush()
  {
      if (_stream)
          _stream->flush();
  }
  
  /**
   * \brief Writes a single byte to the underlying stream.
   *
   * \param c The byte to write.
   * \return The number of bytes written, zero on error.
   */
  size_t Terminal::write(uint8_t c)
  {
      return _stream ? _stream->write(c) : 0;
  }
  
  /**
   * \brief Writes a buffer of data to the underlying stream.
   *
   * \param buffer Points to the buffer to write.
   * \param size The number of bytes in the \a buffer.
   *
   * \return The number of bytes written, which may be short on error.
   */
  size_t Terminal::write(const uint8_t *buffer, size_t size)
  {
      return _stream ? _stream->write(buffer, size) : 0;
  }
  
  /**
   * \brief Writes a static string that is stored in program memory.
   *
   * \param str Points to the NUL-terminated string in program memory.
   *
   * This is a convenience function for printing static strings that
   * are stored in program memory.
   *
   * \sa write()
   */
  void Terminal::writeProgMem(const char *str)
  {
      uint8_t buffer[16];
      uint8_t posn;
      uint8_t ch;
      if (!_stream || !str)
          return;
      posn = 0;
      while ((ch = pgm_read_byte((const uint8_t *)str)) != 0) {
          buffer[posn++] = ch;
          if (posn == sizeof(buffer)) {
              _stream->write(buffer, posn);
              posn = 0;
          }
          ++str;
      }
      if (posn != 0)
          _stream->write(buffer, posn);
  }
  
  /**
   * \brief Determine if a character starts an escape sequence after ESC.
   *
   * \param ch The character to test.
   *
   * \return Returns true if \a ch starts an escape sequence; false otherwise.
   */
  static bool escapeSequenceStart(int ch)
  {
      if (ch == '[' || ch == '?')
          return true;
      else if (ch >= 'A' && ch <= 'Z')
          return true;
      else
          return false;
  }
  
  /**
   * \brief Reads the next key that was typed on this terminal.
   *
   * \return Returns -1 if there is no key ready yet; 0x00 to 0x7F for
   * an ASCII character; KEY_UNICODE for an extended Unicode code point,
   * or a USB keyboard code for special arrow or function keys.
   *
   * For example, if the user types the Home key, then this function
   * will return KEY_HOME.  If the user types the capital letter A,
   * then this function will return 0x41.
   *
   * If the user types an extended Unicode character (U+0080 and higher),
   * then this function will return KEY_UNICODE.  The application should
   * call unicodeKey() to retrieve the actual code point.  All Unicode
   * characters are assumed to be in the UTF-8 encoding on the stream.
   *
   * Some ASCII control characters correspond to special keys and will
   * be mapped appropriately:
   *
   * \li 0x08 (CTRL-H) and 0x7F (DEL) are mapped to KEY_BACKSPACE
   * \li 0x0D (CTRL-M) and 0x0A (CTRL-J) are mapped to KEY_RETURN
   * \li 0x09 (CTRL-I) is mapped to KEY_TAB
   * \li 0x1B (CTRL-[) is mapped to KEY_ESCAPE
   *
   * In all of these cases, the original ASCII code will be reported
   * by unicodeKey().  As a special case, if 0x0D is immediately followed
   * by 0x0A (that is, CRLF) then KEY_RETURN will be reported only once
   * with unicodeKey() set to 0x0D.  This ensures that all line ending
   * types are mapped to a single KEY_RETURN report.
   *
   * If the window size has changed due to a remote event, then KEY_WINSIZE
   * will be returned.  This can allow the caller to clear and redraw the
   * window in the new size.
   *
   * \sa unicodeKey(), read()
   */
  int Terminal::readKey()
  {
      int ch;
  
      // Bail out if there is no underlying stream.
      if (!_stream)
          return -1;
  
      // Read the next character and bail out if nothing yet.  Some special
      // peek-ahead handling is needed just after the ESC character.
      if (state == STATE_ESC) {
          ch = _stream->peek();
          if (ch < 0) {
              // We just saw an ESC.  If there has been a timeout
              // then the key is KEY_ESC rather than the start of a
              // VT100 escape sequence.
              if ((millis() - timer) >= ESC_TIMEOUT_MS) {
                  state = STATE_INIT;
                  ucode = 0x1B;
                  return KEY_ESC;
              }
              ucode = -1;
              return -1;
          } else if (!escapeSequenceStart(ch)) {
              // The next character is not legitimate as the start of
              // an escape sequence, so the ESC must have been KEY_ESC.
              state = STATE_INIT;
              ucode = 0x1B;
              return KEY_ESC;
          } else {
              // Part of an escape sequence.  Read the character properly.
              ch = _stream->read();
          }
      } else {
          // Read the next character without any peek-ahead.
          ch = _stream->read();
      }
      if (ch < 0) {
          if (state == STATE_MATCH && (millis() - timer) >= SEQ_TIMEOUT_MS) {
              // Timeout while waiting for the next character in an
              // escape sequence.  Abort and return to the initial state.
              state = STATE_INIT;
          }
          ucode = -1;
          return -1;
      }
  
      // Determine what to do based on the key recognition state.
      switch (state) {
      case STATE_CR:
          // We just saw a CR, so check for CRLF and eat the LF.
          state = STATE_INIT;
          if (ch == 0x0A) {
              ucode = -1;
              return -1;
          } else if (ch == 0x00 && mod == Telnet) {
              // In telnet mode, CR NUL is a literal carriage return,
              // separate from the newline sequence CRLF.  Eat the NUL.
              // We already reported KEY_RETURN for the CR character.
              ucode = -1;
              return -1;
          }
          // Fall through to the next case.
  
      case STATE_INIT:
          if (ch >= 0x20 && ch <= 0x7E) {
              // Printable ASCII character.
              state = STATE_INIT;
              ucode = ch;
              return ch;
          } else if (ch == 0x1B) {
              // Start of an escape sequence, or the escape character itself.
              state = STATE_ESC;
              timer = millis();
          } else if (ch == 0x0D) {
              // CR which may be followed by an LF.
              state = STATE_CR;
              ucode = ch;
              return KEY_RETURN;
          } else if (ch == 0x0A) {
              // LF on its own without a preceding CR.
              ucode = ch;
              return KEY_RETURN;
          } else if (ch == 0x08 || ch == 0x7F) {
              // Backspace or DEL character.
              state = STATE_INIT;
              ucode = ch;
              return KEY_BACKSPACE;
          } else if (ch == 0x09) {
              // TAB character.
              state = STATE_INIT;
              ucode = ch;
              return KEY_TAB;
          } else if (ch < 0x80) {
              // Some other ASCII control character.
              state = STATE_INIT;
              ucode = ch;
              return ch;
          } else if (ch >= 0xC1 && ch <= 0xDF) {
              // Two-byte UTF-8 sequence.
              offset = ch & 0x1F;
              utf8len = 2;
              state = STATE_UTF8;
          } else if (ch >= 0xE1 && ch <= 0xEF) {
              // Three-byte UTF-8 sequence.
              offset = ch & 0x0F;
              utf8len = 3;
              state = STATE_UTF8;
          } else if (ch >= 0xF1 && ch <= 0xF7) {
              // Four-byte UTF-8 sequence.
              offset = ch & 0x07;
              utf8len = 4;
              state = STATE_UTF8;
          } else if (ch == 0xFF && mod == Telnet) {
              // Start of a telnet command (IAC byte).
              state = STATE_IAC;
          }
          break;
  
      case STATE_ESC:
          // Next character just after the ESC.  Start the escape
          // sequence matching engine at offset zero in the keymap table.
          state = STATE_MATCH;
          offset = 0;
          // Fall through to the next case.
  
      case STATE_MATCH:
          // In the middle of matching an escape sequence.
          if (ch == 0x1B) {
              // ESC character seen in the middle of an escape sequence.
              // The previous escape sequence is invalid so abort and restart.
              state = STATE_ESC;
              timer = millis();
              break;
          }
          ch = matchEscape(ch);
          if (ch == -1) {
              // Need more characters before knowing what this is.
              timer = millis();
          } else if (ch == -2) {
              // Invalid escape sequence so abort and restart.
              state = STATE_INIT;
          } else if (ch < 0x80) {
              // Escape sequence corresponds to a normal ASCII character.
              state = STATE_INIT;
              ucode = ch;
              return ch;
          } else {
              // Extended keycode for an arrow or function key.
              state = STATE_INIT;
              ucode = -1;
              return ch;
          }
          break;
  
      case STATE_UTF8:
          // Recognize a multi-byte UTF-8 character encoding.
          if ((ch & 0xC0) == 0x80) {
              if (utf8len <= 2) {
                  // Final character in the sequence.
                  ucode = (((long)offset) << 6) | (ch & 0x3F);
                  state = STATE_INIT;
                  if (ucode > 0x10FFFFL)
                      break;      // The code point is out of range.
                  return KEY_UNICODE;
              } else {
                  // More characters still yet to come.
                  --utf8len;
                  offset = (offset << 6) | (ch & 0x3F);
              }
          } else {
              // This character is invalid as part of a UTF-8 sequence.
              state = STATE_INIT;
          }
          break;
  
      case STATE_IAC:
          // Telnet command byte just after an IAC (0xFF) character.
          switch (ch) {
          case TelnetDefs::EndOfFile:
              // Convert EOF into CTRL-D.
              state = STATE_INIT;
              ucode = 0x04;
              return 0x04;
  
          case TelnetDefs::EndOfRecord:
              // Convert end of record markers into CR.
              state = STATE_INIT;
              ucode = 0x0D;
              return KEY_RETURN;
  
          case TelnetDefs::Interrupt:
              // Convert interrupt into CTRL-C.
              state = STATE_INIT;
              ucode = 0x03;
              return 0x03;
  
          case TelnetDefs::EraseChar:
              // Convert erase character into DEL.
              state = STATE_INIT;
              ucode = 0x7F;
              return KEY_BACKSPACE;
  
          case TelnetDefs::EraseLine:
              // Convert erase line into CTRL-U.
              state = STATE_INIT;
              ucode = 0x15;
              return 0x15;
  
          case TelnetDefs::SubStart:
              // Option sub-negotiation.
              utf8len = 0;
              state = STATE_SB;
              break;
  
          case TelnetDefs::WILL:
              // Option negotiation, WILL command.
              state = STATE_WILL;
              break;
  
          case TelnetDefs::WONT:
              // Option negotiation, WONT command.
              state = STATE_WONT;
              break;
  
          case TelnetDefs::DO:
              // Option negotiation, DO command.
              state = STATE_DO;
              break;
  
          case TelnetDefs::DONT:
              // Option negotiation, DONT command.
              state = STATE_DONT;
              break;
  
          case TelnetDefs::IAC:
              // IAC followed by IAC is the literal byte 0xFF,
              // but that isn't valid UTF-8 so we just drop it.
              state = STATE_INIT;
              break;
  
          default:
              // Everything else is treated as a NOP.
              state = STATE_INIT;
              break;
          }
          break;
  
      case STATE_WILL:
          // Telnet option negotiation, WILL command.  Note: We don't do any
          // loop detection.  We assume that the client will eventually break
          // the loop as it probably has more memory than us to store state.
          if (ch == TelnetDefs::WindowSize ||
                  ch == TelnetDefs::RemoteFlowControl) {
              // Send a DO command in response - we accept this option.
              telnetCommand(TelnetDefs::DO, ch);
          } else {
              // Send a DONT command in response - we don't accept this option.
              telnetCommand(TelnetDefs::DONT, ch);
          }
          if (!(flags & 0x01)) {
              // The first time we see a WILL command from the client we
              // send a request back saying that we will handle echoing.
              flags |= 0x01;
              telnetCommand(TelnetDefs::WILL, TelnetDefs::Echo);
          }
          state = STATE_INIT;
          break;
  
      case STATE_WONT:
      case STATE_DONT:
          // Telnet option negotiation, WONT/DONT command.  The other side
          // is telling us that it does not understand this option or wants
          // us to stop using it.  For now there is nothing to do.
          state = STATE_INIT;
          break;
  
      case STATE_DO:
          // Telnet option negotiation, DO command.  Note: Other than Echo
          // we don't do any loop detection.  We assume that the client will
          // break the loop as it probably has more memory than us to store state.
          if (ch == TelnetDefs::Echo) {
              // Special handling needed for Echo - don't say WILL again
              // when the client acknowledges us with a DO command.
          } else if (ch == TelnetDefs::SuppressGoAhead) {
              // Send a WILL command in response - we accept this option.
              telnetCommand(TelnetDefs::WILL, ch);
          } else {
              // Send a WONT command in response - we don't accept this option.
              telnetCommand(TelnetDefs::WONT, ch);
          }
          state = STATE_INIT;
          break;
  
      case STATE_SB:
          // Telnet option sub-negotiation.  Collect up all bytes and
          // then execute the option once "IAC SubEnd" is seen.
          if (ch == TelnetDefs::IAC) {
              // IAC byte, which will be followed by either IAC or SubEnd.
              state = STATE_SB_IAC;
              break;
          }
          if (utf8len < sizeof(sb))
              sb[utf8len++] = ch;
          break;
  
      case STATE_SB_IAC:
          // Telnet option sub-negotiation, byte after IAC.
          if (ch == TelnetDefs::IAC) {
              // Two IAC bytes in a row is a single escaped 0xFF byte.
              if (utf8len < sizeof(sb))
                  sb[utf8len++] = 0xFF;
              state = STATE_SB;
              break;
          } else if (ch == TelnetDefs::SubEnd) {
              // End of the sub-negotiation field.  Handle window size changes.
              if (utf8len >= 5 && sb[0] == TelnetDefs::WindowSize) {
                  int width  = (((int)(sb[1])) << 8) | sb[2];
                  int height = (((int)(sb[3])) << 8) | sb[4];
                  if (!width)         // Zero width or height means "unspecified".
                      width = ncols;
                  if (!height)
                      height = nrows;
                  if (setWindowSize(width, height)) {
                      // The window size has changed; notify the caller.
                      ucode = -1;
                      state = STATE_INIT;
                      return KEY_WINSIZE;
                  }
              }
          }
          state = STATE_INIT;
          break;
      }
  
      // If we get here, then we're still waiting for a full sequence.
      ucode = -1;
      return -1;
  }
  
  /**
   * \fn long Terminal::unicodeKey() const
   * \brief Gets the Unicode version of the last key returned by readKey().
   *
   * If readKey() returned an ASCII character (0x00 to 0x7F) or KEY_UNICODE,
   * then this function can be used to query the full Unicode code point for
   * the key that was typed.  If some other key is typed, or no key was
   * typed, then this function will return -1.
   *
   * Unicode code points range between 0 and 0x10FFFF.
   *
   * \sa readKey(), writeUnicode()
   */
  
  /**
   * \brief Writes a Unicode code point to the output in UTF-8 encoding.
   *
   * \param code The code point to be written between 0 and 0x10FFFF.
   * \return The number of bytes that were written to the underlying stream
   * to represent \a code.  Returns zero if \a code is not a valid code point.
   *
   * This function is useful when a specific Unicode character is desired;
   * for example the code point 0x2264 corresponds to the less than or
   * equal to operator "≤".  See the Unicode standard for more information.
   *
   * Unicode characters between 0x00 and 0x7F and strings that are already
   * in the UTF-8 encoding can also be written using write().
   *
   * \sa write()
   */
  size_t Terminal::writeUnicode(long code)
  {
      uint8_t utf8[4];
      size_t size = utf8Format(utf8, code);
      if (size > 0)
          write(utf8, size);
      return size;
  }
  
  /**
   * \fn int Terminal::columns() const
   * \brief Gets the number of columns in the window; defaults to 80.
   *
   * \sa rows(), setWindowSize(), cursorMove()
   */
  
  /**
   * \fn int Terminal::rows() const
   * \brief Gets the number of rows in the window; defaults to 24.
   *
   * \sa columns(), setWindowSize(), cursorMove()
   */
  
  /**
   * \brief Sets the number of columns and rows in the window.
   *
   * \param columns The number of columns between 1 and 10000.
   * \param rows The number of rows between 1 and 10000.
   *
   * \return Returns true if the window size has changed.
   *
   * This function should be used if the application has some information
   * about the actual window size.  For serial ports, this usually isn't
   * available but telnet and ssh sessions can get the window size from
   * the remote host.
   *
   * The window size defaults to 80x24 which is the standard default for
   * terminal programs like PuTTY that emulate a VT100.
   *
   * If the window size changes due to a remote event, readKey() will
   * return KEY_WINSIZE to inform the application.
   *
   * \sa columns(), rows(), readKey()
   */
  bool Terminal::setWindowSize(int columns, int rows)
  {
      // Sanity-check the range first.
      if (columns < 1)
          columns = 1;
      else if (columns > 10000)
          columns = 10000;
      if (rows < 1)
          rows = 1;
      else if (rows > 10000)
          rows = 10000;
      if (ncols != columns || nrows != rows) {
          ncols = columns;
          nrows = rows;
          return true;
      } else {
          return false;
      }
  }
  
  /**
   * \brief Move the cursor to the top-left position and clear the screen.
   */
  void Terminal::clear()
  {
      static char const escape[] PROGMEM = "\033[H\033[J";
      writeProgMem(escape);
  }
  
  /**
   * \brief Clears from the current cursor position to the end of the line.
   */
  void Terminal::clearToEOL()
  {
      static char const escape[] PROGMEM = "\033[K";
      writeProgMem(escape);
  }
  
  // Writes a decimal number to a buffer.
  static void writeNumber(uint8_t *buf, uint8_t &posn, int value)
  {
      int divisor = 10000;
      bool haveDigits = false;
      while (divisor >= 1) {
          int digit = value / divisor;
          if (digit || haveDigits) {
              buf[posn++] = '0' + digit;
              haveDigits = true;
          }
          value %= divisor;
          divisor /= 10;
      }
      if (!haveDigits) {
          buf[posn++] = '0';
      }
  }
  
  /**
   * \brief Moves the cursor to a specific location in the window.
   *
   * \param x The x position for the cursor between 0 and columns() - 1.
   * \param y The y position for the cursor between 0 and rows() - 1.
   *
   * \sa cursorLeft(), columns(), rows(), setWindowSize()
   */
  void Terminal::cursorMove(int x, int y)
  {
      if (!_stream)
          return;
  
      // Range check the arguments.
      if (x < 0)
          x = 0;
      else if (x >= ncols)
          x = ncols - 1;
      if (y < 0)
          y = 0;
      else if (y >= nrows)
          y = nrows - 1;
  
      // Format the command "ESC[row;colH" and send it.
      uint8_t buffer[16];
      uint8_t posn = 0;
      buffer[posn++] = 0x1B;
      buffer[posn++] = '[';
      writeNumber(buffer, posn, y + 1);
      buffer[posn++] = ';';
      writeNumber(buffer, posn, x + 1);
      buffer[posn++] = 'H';
      _stream->write(buffer, posn);
  }
  
  /**
   * \brief Moves the cursor left by one character.
   *
   * \sa cursorRight(), cursorUp(), cursorDown(), cursorMove()
   */
  void Terminal::cursorLeft()
  {
      static char const escape[] PROGMEM = "\033[D";
      writeProgMem(escape);
  }
  
  /**
   * \brief Moves the cursor right by one character.
   *
   * \sa cursorLeft(), cursorUp(), cursorDown(), cursorMove()
   */
  void Terminal::cursorRight()
  {
      static char const escape[] PROGMEM = "\033[C";
      writeProgMem(escape);
  }
  
  /**
   * \brief Moves the cursor up by one line.
   *
   * \sa cursorDown(), cursorLeft(), cursorRight(), cursorMove()
   */
  void Terminal::cursorUp()
  {
      static char const escape[] PROGMEM = "\033[A";
      writeProgMem(escape);
  }
  
  /**
   * \brief Moves the cursor down by one line.
   *
   * \sa cursorUp(), cursorLeft(), cursorRight(), cursorMove()
   */
  void Terminal::cursorDown()
  {
      static char const escape[] PROGMEM = "\033[B";
      writeProgMem(escape);
  }
  
  /**
   * \brief Backspaces over the last character.
   *
   * This function prints CTRL-H, a space, and another CTRL-H to backspace
   * over and erase the last character on the current line.
   *
   * If the last character was a wide Unicode character, then two calls to
   * this function are required to erase it.  See isWideCharacter() for
   * more information.
   *
   * \sa isWideCharacter()
   */
  void Terminal::backspace()
  {
      static char const escape[] PROGMEM = "\b \b";
      writeProgMem(escape);
  }
  
  /**
   * \brief Inserts a line at the cursor position.
   *
   * \sa insertChar(), deleteLine()
   */
  void Terminal::insertLine()
  {
      static char const escape[] PROGMEM = "\033[L";
      writeProgMem(escape);
  }
  
  /**
   * \brief Inserts a blank character at the cursor position.
   *
   * \sa insertLine(), deleteChar()
   */
  void Terminal::insertChar()
  {
      static char const escape[] PROGMEM = "\033[@";
      writeProgMem(escape);
  }
  
  /**
   * \brief Deletes a line at the cursor position.
   *
   * \sa deleteChar(), insertLine()
   */
  void Terminal::deleteLine()
  {
      static char const escape[] PROGMEM = "\033[M";
      writeProgMem(escape);
  }
  
  /**
   * \brief Deletes the character at the cursor position.
   *
   * \sa deleteLine(), insertChar()
   */
  void Terminal::deleteChar()
  {
      static char const escape[] PROGMEM = "\033[P";
      writeProgMem(escape);
  }
  
  /**
   * \brief Scrolls the contents of the window up one line.
   *
   * \sa scrollDown()
   */
  void Terminal::scrollUp()
  {
      static char const escape[] PROGMEM = "\033[S";
      writeProgMem(escape);
  }
  
  /**
   * \brief Scrolls the contents of the window down one line.
   *
   * \sa scrollUp()
   */
  void Terminal::scrollDown()
  {
      static char const escape[] PROGMEM = "\033[T";
      writeProgMem(escape);
  }
  
  /**
   * \brief Selects normal text with all attributes and colors off.
   *
   * \sa color(), bold(), underline(), blink(), reverse()
   */
  void Terminal::normal()
  {
      static char const escape[] PROGMEM = "\033[0m";
      writeProgMem(escape);
  }
  
  /**
   * \brief Enables bold text.
   *
   * \sa normal()
   */
  void Terminal::bold()
  {
      static char const escape[] PROGMEM = "\033[1m";
      writeProgMem(escape);
  }
  
  /**
   * \brief Enables underlined text.
   */
  void Terminal::underline()
  {
      static char const escape[] PROGMEM = "\033[4m";
      writeProgMem(escape);
  }
  
  /**
   * \brief Enables blinking text.
   */
  void Terminal::blink()
  {
      static char const escape[] PROGMEM = "\033[5m";
      writeProgMem(escape);
  }
  
  /**
   * \brief Reverse the foreground and background colors for inverted text.
   */
  void Terminal::reverse()
  {
      static char const escape[] PROGMEM = "\033[7m";
      writeProgMem(escape);
  }
  
  /**
   * \enum Terminal::Color
   * \brief Terminal foreground or background colors.
   */
  
  /**
   * \var Terminal::Black
   * \brief Color is black.
   */
  
  /**
   * \var Terminal::DarkRed
   * \brief Color is dark red.
   */
  
  /**
   * \var Terminal::DarkGreen
   * \brief Color is dark green.
   */
  
  /**
   * \var Terminal::DarkYellow
   * \brief Color is dark yellow.
   */
  
  /**
   * \var Terminal::DarkBlue
   * \brief Color is dark blue.
   */
  
  /**
   * \var Terminal::DarkMagenta
   * \brief Color is dark magenta.
   */
  
  /**
   * \var Terminal::DarkCyan
   * \brief Color is dark cyan.
   */
  
  /**
   * \var Terminal::LightGray
   * \brief Color is light gray.
   */
  
  /**
   * \var Terminal::DarkGray
   * \brief Color is dark gray.
   */
  
  /**
   * \var Terminal::Red
   * \brief Color is light red.
   */
  
  /**
   * \var Terminal::Green
   * \brief Color is light green.
   */
  
  /**
   * \var Terminal::Yellow
   * \brief Color is light yellow.
   */
  
  /**
   * \var Terminal::Blue
   * \brief Color is light blue.
   */
  
  /**
   * \var Terminal::Magenta
   * \brief Color is light magenta.
   */
  
  /**
   * \var Terminal::Cyan
   * \brief Color is light cyan.
   */
  
  /**
   * \var Terminal::White
   * \brief Color is white.
   */
  
  /**
   * \brief Selects a text foreground color with the default background color.
   *
   * \param fg The foreground color to select.
   *
   * All other text attributes (reverse, underline, etc) are disabled.
   *
   * The following example displays a warning string with the initial
   * word in red and all following words in normal text:
   *
   * \code
   * term.color(Terminal::Red);
   * term.print("WARNING: ");
   * term.normal();
   * term.println("All files on the SD card will be deleted!");
   * \endcode
   *
   * \sa normal()
   */
  void Terminal::color(Color fg)
  {
      uint8_t code = (fg & 0x07);
      uint8_t bold = (fg & 0x08) ? 1 : 0;
      if (!_stream)
          return;
      uint8_t buffer[16];
      uint8_t posn = 0;
      buffer[posn++] = 0x1B;
      buffer[posn++] = '[';
      buffer[posn++] = '0';   // reset all attributes first
      buffer[posn++] = ';';
      buffer[posn++] = '3';
      buffer[posn++] = '0' + code;
      if (bold) {
          buffer[posn++] = ';';
          buffer[posn++] = '1';
      }
      buffer[posn++] = 'm';
      _stream->write(buffer, posn);
  }
  
  /**
   * \brief Selects text foreground and background colors.
   *
   * \param fg The foreground color to select.
   * \param bg The background color to select.
   *
   * All other text attributes (reverse, underline, etc) are disabled.
   *
   * \sa normal()
   */
  void Terminal::color(Color fg, Color bg)
  {
      uint8_t codefg = (fg & 0x07);
      uint8_t boldfg = (fg & 0x08) ? 1 : 0;
      uint8_t codebg = (bg & 0x07);
      if (!_stream)
          return;
      uint8_t buffer[16];
      uint8_t posn = 0;
      buffer[posn++] = 0x1B;
      buffer[posn++] = '[';
      buffer[posn++] = '0';   // reset all attributes first
      buffer[posn++] = ';';
      buffer[posn++] = '3';
      buffer[posn++] = '0' + codefg;
      if (boldfg) {
          buffer[posn++] = ';';
          buffer[posn++] = '1';
      }
      buffer[posn++] = ';';
      buffer[posn++] = '4';
      buffer[posn++] = '0' + codebg;
      buffer[posn++] = 'm';
      _stream->write(buffer, posn);
  }
  
  /**
   * \brief Determine if a Unicode character is wide.
   *
   * \param code The code point for the Unicode character.
   * \return Returns true if \a code is a wide character, false otherwise.
   *
   * Wide characters typically come from East Asian languages and occupy
   * two spaces in a terminal.  Two calls to backspace() are required to
   * erase such characters.
   *
   * References: http://www.unicode.org/reports/tr11/,
   * http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt
   *
   * \sa backspace(), writeUnicode()
   */
  bool Terminal::isWideCharacter(long code)
  {
      // This function was automatically generated by genwcwidth.c
      static unsigned char const range3000[32] PROGMEM = {
          0xF1, 0xFF, 0xF3, 0x3F, 0x01, 0x00, 0x01, 0x78,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88
      };
      static unsigned char const rangeFE00[64] PROGMEM = {
          0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xE1, 0xFF,
          0x9F, 0x01, 0x00, 0x7F, 0x0C, 0x03, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x10, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
          0x01, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00
      };
      unsigned c;
      if (code < 0x2300) {
          return false;
      } else if (code >= 0x3000 && code <= 0x30FF) {
          c = (unsigned)(code - 0x3000);
          return (pgm_read_byte(range3000 + (c / 8)) & (1 << (c % 8))) != 0;
      } else if (code >= 0xFE00 && code <= 0xFFFF) {
          c = (unsigned)(code - 0xFE00);
          return (pgm_read_byte(rangeFE00 + (c / 8)) & (1 << (c % 8))) != 0;
      } else if (code >= 0x3400 && code <= 0x4DBF) {
          return true;
      } else if (code >= 0x4E00 && code <= 0x9FFF) {
          return true;
      } else if (code >= 0xF900 && code <= 0xFAFF) {
          return true;
      } else if (code >= 0x20000 && code <= 0x2FFFD) {
          return true;
      } else if (code >= 0x30000 && code <= 0x3FFFD) {
          return true;
      } else if (code == 0x2329 ||
                 code == 0x232A ||
                 code == 0x3250 ||
                 code == 0xA015) {
          return true;
      }
      return false;
  }
  /**
   * \brief Determines the length of a Unicode code point in the UTF-8 encoding.
   *
   * \param code The code point to be written between 0 and 0x10FFFF.
   * \return The number of bytes that makes up the UTF-8 encoding of \a code.
   * Returns zero if \a code is not a valid code point.
   *
   * \sa utf8Format(), writeUnicode()
   */
  size_t Terminal::utf8Length(long code)
  {
      // Reference: https://tools.ietf.org/html/rfc3629
      if (code < 0) {
          return 0;
      } else if (code <= 0x7FL) {
          return 1;
      } else if (code <= 0x07FFL) {
          return 2;
      } else if (code >= 0xD800L && code <= 0xDFFF) {
          // UTF-16 surrogate pairs are not valid in UTF-8.
          return 0;
      } else if (code <= 0xFFFFL) {
          return 3;
      } else if (code <= 0x10FFFFL) {
          return 4;
      } else {
          return 0;
      }
  }
  
  /**
   * \brief Formats a Unicode code point in a buffer in the UTF-8 encoding.
   *
   * \param buffer The buffer to write the UTF-8 encoding to.  At most 4
   * bytes will be written to this buffer.
   * \param code The code point to be written between 0 and 0x10FFFF.
   * \return The number of bytes that were written to \a buffer to represent
   * \a code.  Returns zero if \a code is not a valid code point.
   *
   * \sa utf8Length(), writeUnicode()
   */
  size_t Terminal::utf8Format(uint8_t *buffer, long code)
  {
      // Reference: https://tools.ietf.org/html/rfc3629
      if (code < 0) {
          return 0;
      } else if (code <= 0x7FL) {
          buffer[0] = (uint8_t)code;
          return 1;
      } else if (code <= 0x07FFL) {
          buffer[0] = 0xC0 | (uint8_t)(code >> 6);
          buffer[1] = 0x80 | (((uint8_t)code) & 0x3F);
          return 2;
      } else if (code >= 0xD800L && code <= 0xDFFF) {
          // UTF-16 surrogate pairs are not valid in UTF-8.
          return 0;
      } else if (code <= 0xFFFFL) {
          buffer[0] = 0xE0 | (uint8_t)(code >> 12);
          buffer[1] = 0x80 | (((uint8_t)(code >> 6)) & 0x3F);
          buffer[2] = 0x80 | (((uint8_t)code) & 0x3F);
          return 3;
      } else if (code <= 0x10FFFFL) {
          buffer[0] = 0xF0 | (uint8_t)(code >> 18);
          buffer[1] = 0x80 | (((uint8_t)(code >> 12)) & 0x3F);
          buffer[2] = 0x80 | (((uint8_t)(code >> 6)) & 0x3F);
          buffer[3] = 0x80 | (((uint8_t)code) & 0x3F);
          return 4;
      } else {
          return 0;
      }
  }
  
  // Keymap rule table.  Compact representation of a recognition tree.
  // Each tree node is an array of entries of the following forms:
  //      0           End of this tree level.
  //      ch code     Leaf node: ASCII character (bit 7 clear) plus 8-bit keycode.
  //      ch offset   Interior node: ASCII character with the high bit set
  //                  plus a 16-bit offset to the first child node.
  // This table was generated with the "genkeymap" tool.  Do not edit this
  // table but rather edit the tool and rebuild the table from it.
  static uint8_t const keymap[459] PROGMEM = {
      0xDB, 0x1A, 0x00, 0xCF, 0x57, 0x01, 0x41, 0xDA, 0x42, 0xD9, 0x43, 0xD7,
      0x44, 0xD8, 0xBF, 0xA2, 0x01, 0x50, 0xC2, 0x51, 0xC3, 0x52, 0xC4, 0x53,
      0xC5, 0x00, 0x41, 0xDA, 0x42, 0xD9, 0x43, 0xD7, 0x44, 0xD8, 0x48, 0xD2,
      0xB1, 0x42, 0x00, 0x46, 0xD5, 0xB4, 0xC9, 0x00, 0xB2, 0xCC, 0x00, 0xB3,
      0x2B, 0x01, 0xB5, 0x46, 0x01, 0xB6, 0x49, 0x01, 0xDB, 0x4C, 0x01, 0x5A,
      0x0B, 0x50, 0xD0, 0x47, 0xE5, 0x00, 0x7E, 0xD2, 0xB1, 0x5D, 0x00, 0xB2,
      0x6C, 0x00, 0xB3, 0x7B, 0x00, 0xB4, 0x88, 0x00, 0xB5, 0x95, 0x00, 0xB7,
      0xA2, 0x00, 0xB8, 0xAF, 0x00, 0xB9, 0xBC, 0x00, 0x00, 0x7E, 0xC2, 0xBB,
      0x65, 0x00, 0x5E, 0xFA, 0x00, 0xB2, 0x69, 0x00, 0x00, 0x7E, 0xF0, 0x00,
      0x7E, 0xC3, 0xBB, 0x74, 0x00, 0x5E, 0xFB, 0x00, 0xB2, 0x78, 0x00, 0x00,
      0x7E, 0xF1, 0x00, 0x7E, 0xC4, 0xBB, 0x81, 0x00, 0x00, 0xB2, 0x85, 0x00,
      0x00, 0x7E, 0xF2, 0x00, 0x7E, 0xC5, 0xBB, 0x8E, 0x00, 0x00, 0xB2, 0x92,
      0x00, 0x00, 0x7E, 0xF3, 0x00, 0x7E, 0xC6, 0xBB, 0x9B, 0x00, 0x00, 0xB2,
      0x9F, 0x00, 0x00, 0x7E, 0xF4, 0x00, 0x7E, 0xC7, 0xBB, 0xA8, 0x00, 0x00,
      0xB2, 0xAC, 0x00, 0x00, 0x7E, 0xF5, 0x00, 0x7E, 0xC8, 0xBB, 0xB5, 0x00,
      0x00, 0xB2, 0xB9, 0x00, 0x00, 0x7E, 0xF6, 0x00, 0x7E, 0xC9, 0xBB, 0xC2,
      0x00, 0x00, 0xB2, 0xC6, 0x00, 0x00, 0x7E, 0xF7, 0x00, 0x7E, 0xD5, 0x00,
      0x7E, 0xD1, 0xB0, 0xE7, 0x00, 0xB1, 0xF4, 0x00, 0xB3, 0x01, 0x01, 0xB4,
      0x10, 0x01, 0xB5, 0x1F, 0x01, 0xB6, 0x22, 0x01, 0xB8, 0x25, 0x01, 0xB9,
      0x28, 0x01, 0x00, 0x7E, 0xCA, 0xBB, 0xED, 0x00, 0x00, 0xB2, 0xF1, 0x00,
      0x00, 0x7E, 0xF8, 0x00, 0x7E, 0xCB, 0xBB, 0xFA, 0x00, 0x00, 0xB2, 0xFE,
      0x00, 0x00, 0x7E, 0xF9, 0x00, 0x7E, 0xCC, 0x24, 0xF8, 0xBB, 0x09, 0x01,
      0x00, 0xB2, 0x0D, 0x01, 0x00, 0x7E, 0xFA, 0x00, 0x7E, 0xCD, 0x24, 0xF9,
      0xBB, 0x18, 0x01, 0x00, 0xB2, 0x1C, 0x01, 0x00, 0x7E, 0xFB, 0x00, 0x7E,
      0xF0, 0x00, 0x7E, 0xF1, 0x00, 0x7E, 0xF2, 0x00, 0x7E, 0xF3, 0x00, 0x7E,
      0xD4, 0xB1, 0x3A, 0x01, 0xB2, 0x3D, 0x01, 0xB3, 0x40, 0x01, 0xB4, 0x43,
      0x01, 0x00, 0x7E, 0xF4, 0x00, 0x7E, 0xF5, 0x00, 0x7E, 0xF6, 0x00, 0x7E,
      0xF7, 0x00, 0x7E, 0xD3, 0x00, 0x7E, 0xD6, 0x00, 0x41, 0xC2, 0x42, 0xC3,
      0x43, 0xC4, 0x44, 0xC5, 0x45, 0xC6, 0x00, 0x41, 0xDA, 0x42, 0xD9, 0x43,
      0xD7, 0x44, 0xD8, 0x48, 0xD2, 0x46, 0xD5, 0x20, 0x20, 0x49, 0xB3, 0x4D,
      0xB0, 0x6A, 0x2A, 0x6B, 0x2B, 0x6C, 0x2C, 0x6D, 0x2D, 0x6E, 0x2E, 0x6F,
      0x2F, 0x70, 0x30, 0x71, 0x31, 0x72, 0x32, 0x73, 0x33, 0x74, 0x34, 0x75,
      0x35, 0x76, 0x36, 0x77, 0x37, 0x78, 0x38, 0x79, 0x39, 0x58, 0x3D, 0x50,
      0xC2, 0x51, 0xC3, 0x52, 0xC4, 0x53, 0xC5, 0xB2, 0x99, 0x01, 0x5A, 0x0B,
      0x00, 0x50, 0xF0, 0x51, 0xF1, 0x52, 0xF2, 0x53, 0xF3, 0x00, 0x20, 0x20,
      0x49, 0xB3, 0x4D, 0xB0, 0x6A, 0x2A, 0x6B, 0x2B, 0x6C, 0x2C, 0x6D, 0x2D,
      0x6E, 0x2E, 0x6F, 0x2F, 0x70, 0x30, 0x71, 0x31, 0x72, 0x32, 0x73, 0x33,
      0x74, 0x34, 0x75, 0x35, 0x76, 0x36, 0x77, 0x37, 0x78, 0x38, 0x79, 0x39,
      0x58, 0x3D, 0x00
  };
  
  /**
   * \brief Matches the next character in an escape sequence.
   *
   * \param ch The next character to match.
   * \return -1 if more characters are required, -2 if the escape sequence
   * is invalid, or a positive key code if the match is complete.
   */
  int Terminal::matchEscape(int ch)
  {
      uint8_t kch;
      for (;;) {
          kch = pgm_read_byte(keymap + offset);
          if (!kch) {
              // No match at this level, so the escape sequence is invalid.
              break;
          } else if (kch & 0x80) {
              // Interior node.
              if ((kch & 0x7F) == ch) {
                  // Interior node matches.  Go down one tree level.
                  offset = ((int)(pgm_read_byte(keymap + offset + 1))) |
                          (((int)(pgm_read_byte(keymap + offset + 2))) << 8);
                  return -1;
              }
              offset += 3;
          } else {
              // Leaf node.
              if (kch == (uint8_t)ch) {
                  // We have found a match on a full escape sequence.
                  return pgm_read_byte(keymap + offset + 1);
              }
              offset += 2;
          }
      }
      return -2;
  }
  
  /**
   * \brief Sends a telnet command to the client.
   *
   * \param type The type of command: WILL, WONT, DO, or DONT.
   * \param option The telnet option the command applies to.
   */
  void Terminal::telnetCommand(uint8_t type, uint8_t option)
  {
      uint8_t buf[3];
      buf[0] = (uint8_t)TelnetDefs::IAC;
      buf[1] = type;
      buf[2] = option;
      _stream->write(buf, 3);
  }
  