readKey method

KeyStroke readKey()

Reads a single key from the input, including a variety of control characters.

Keys are represented by the KeyStroke class. Keys may be printable (if so, Key.isControl is false, and the Key.char property may be used to identify the key pressed. Non-printable keys have Key.isControl set to true, and if so the Key.char property is empty and instead the KeyStroke.controlChar property will be set to a value from the ControlCharacter enumeration that describes which key was pressed.

Owing to the limitations of terminal key handling, certain keys may be represented by multiple control key sequences. An example showing basic key handling can be found in the example/command_line.dart file in the package source code.

Implementation

KeyStroke readKey() {
  int charCode;
  var codeUnit = 0;

  rawMode = true;
  while (codeUnit <= 0) {
    codeUnit = stdin.readByteSync();
  }

  KeyStroke key;

  if (codeUnit >= 0x01 && codeUnit <= 0x1a) {
    // Ctrl+A thru Ctrl+Z are mapped to the 1st-26th entries in the
    // enum, so it's easy to convert them across
    key = KeyStroke.control(ControlCharacter.values[codeUnit]);
  } else if (codeUnit == 0x1b) {
    // escape sequence (e.g. \x1b[A for up arrow)
    charCode = stdin.readByteSync();
    if (charCode == -1) {
      rawMode = false;
      return KeyStroke.control(ControlCharacter.escape);
    }
    final firstChar = String.fromCharCode(charCode);

    if (charCode == 127) {
      key = KeyStroke.control(ControlCharacter.wordBackspace);
    } else if (firstChar == '[') {
      // CSI sequence
      charCode = stdin.readByteSync();
      if (charCode == -1) {
        rawMode = false;
        return KeyStroke.control(ControlCharacter.escape);
      }
      final secondChar = String.fromCharCode(charCode);

      final csiResult = _parseCsiSequence(secondChar);
      if (csiResult != ControlCharacter.unknown) {
        key = KeyStroke.control(csiResult);
      } else if (secondChar.codeUnits[0] >= '0'.codeUnits[0] &&
          secondChar.codeUnits[0] <= '9'.codeUnits[0]) {
        // Numeric CSI sequence (e.g., ESC [ 3 ~ or ESC [ 15 ~)
        // Collect all digits until we hit a non-digit character
        final numBuffer = StringBuffer(secondChar);
        ControlCharacter resultChar = ControlCharacter.escape;
        while (true) {
          charCode = stdin.readByteSync();
          if (charCode == -1) {
            rawMode = false;
            return KeyStroke.control(ControlCharacter.escape);
          }
          final nextChar = String.fromCharCode(charCode);
          if (nextChar.codeUnits[0] >= '0'.codeUnits[0] &&
              nextChar.codeUnits[0] <= '9'.codeUnits[0]) {
            numBuffer.write(nextChar);
          } else if (nextChar == '~') {
            resultChar = _parseNumericCsiSequence(numBuffer.toString());
            break;
          } else {
            resultChar = ControlCharacter.unknown;
            break;
          }
        }
        key = KeyStroke.control(resultChar);
      } else {
        key = KeyStroke.control(ControlCharacter.unknown);
      }
    } else if (firstChar == 'O') {
      // SS3 sequence
      charCode = stdin.readByteSync();
      if (charCode == -1) {
        rawMode = false;
        return KeyStroke.control(ControlCharacter.escape);
      }
      final secondChar = String.fromCharCode(charCode);
      key = KeyStroke.control(_parseSs3Sequence(secondChar));
    } else if (firstChar == 'b') {
      key = KeyStroke.control(ControlCharacter.wordLeft);
    } else if (firstChar == 'f') {
      key = KeyStroke.control(ControlCharacter.wordRight);
    } else {
      key = KeyStroke.control(ControlCharacter.unknown);
    }
  } else if (codeUnit == 0x7f) {
    key = KeyStroke.control(ControlCharacter.backspace);
  } else if (codeUnit == 0x00 || (codeUnit >= 0x1c && codeUnit <= 0x1f)) {
    key = KeyStroke.control(ControlCharacter.unknown);
  } else {
    // assume other characters are printable
    key = KeyStroke.printable(String.fromCharCode(codeUnit));
  }
  rawMode = false;
  return key;
}