readLine method

String? readLine({
  1. bool cancelOnBreak = false,
  2. bool cancelOnEscape = false,
  3. bool cancelOnEOF = false,
  4. void callback(
    1. String text,
    2. Key lastPressed
    )?,
})

Reads a line of input, handling basic keyboard navigation commands.

The Dart stdin.readLineSync() function reads a line from the input, however it does not handle cursor navigation (e.g. arrow keys, home and end keys), and has side-effects that may be unhelpful for certain console applications. For example, Ctrl+C is processed as the break character, which causes the application to immediately exit.

The implementation does not currently allow for multi-line input. It is best suited for short text fields that are not longer than the width of the current screen.

By default, readLine ignores break characters (e.g. Ctrl+C) and the Esc key, but if enabled, the function will exit and return a null string if those keys are pressed.

A callback function may be supplied, as a peek-ahead for what is being entered. This is intended for scenarios like auto-complete, where the text field is coupled with some other content.

Implementation

String? readLine(
    {bool cancelOnBreak = false,
    bool cancelOnEscape = false,
    bool cancelOnEOF = false,
    void Function(String text, Key lastPressed)? callback}) {
  var buffer = '';
  var index = 0; // cursor position relative to buffer, not screen

  final screenRow = cursorPosition!.row;
  final screenColOffset = cursorPosition!.col;

  final bufferMaxLength = windowWidth - screenColOffset - 3;

  while (true) {
    final key = readKey();

    if (key.isControl) {
      switch (key.controlChar) {
        case ControlCharacter.enter:
          if (_scrollbackBuffer != null) {
            _scrollbackBuffer!.add(buffer);
          }
          writeLine();
          return buffer;
        case ControlCharacter.ctrlC:
          if (cancelOnBreak) return null;
          break;
        case ControlCharacter.escape:
          if (cancelOnEscape) return null;
          break;
        case ControlCharacter.backspace:
        case ControlCharacter.ctrlH:
          if (index > 0) {
            buffer = buffer.substring(0, index - 1) + buffer.substring(index);
            index--;
          }
          break;
        case ControlCharacter.ctrlU:
          buffer = buffer.substring(index, buffer.length);
          index = 0;
          break;
        case ControlCharacter.delete:
        case ControlCharacter.ctrlD:
          if (index < buffer.length) {
            buffer = buffer.substring(0, index) + buffer.substring(index + 1);
          } else if (cancelOnEOF) {
            return null;
          }
          break;
        case ControlCharacter.ctrlK:
          buffer = buffer.substring(0, index);
          break;
        case ControlCharacter.arrowLeft:
        case ControlCharacter.ctrlB:
          index = index > 0 ? index - 1 : index;
          break;
        case ControlCharacter.arrowUp:
          if (_scrollbackBuffer != null) {
            buffer = _scrollbackBuffer!.up(buffer);
            index = buffer.length;
          }
          break;
        case ControlCharacter.arrowDown:
          if (_scrollbackBuffer != null) {
            final temp = _scrollbackBuffer!.down();
            if (temp != null) {
              buffer = temp;
              index = buffer.length;
            }
          }
          break;
        case ControlCharacter.arrowRight:
        case ControlCharacter.ctrlF:
          index = index < buffer.length ? index + 1 : index;
          break;
        case ControlCharacter.wordLeft:
          if (index > 0) {
            final bufferLeftOfCursor = buffer.substring(0, index - 1);
            final lastSpace = bufferLeftOfCursor.lastIndexOf(' ');
            index = lastSpace != -1 ? lastSpace + 1 : 0;
          }
          break;
        case ControlCharacter.wordRight:
          if (index < buffer.length) {
            final bufferRightOfCursor = buffer.substring(index + 1);
            final nextSpace = bufferRightOfCursor.indexOf(' ');
            index = nextSpace != -1
                ? min(index + nextSpace + 2, buffer.length)
                : buffer.length;
          }
          break;
        case ControlCharacter.home:
        case ControlCharacter.ctrlA:
          index = 0;
          break;
        case ControlCharacter.end:
        case ControlCharacter.ctrlE:
          index = buffer.length;
          break;
        default:
          break;
      }
    } else {
      if (buffer.length < bufferMaxLength) {
        if (index == buffer.length) {
          buffer += key.char;
          index++;
        } else {
          buffer =
              buffer.substring(0, index) + key.char + buffer.substring(index);
          index++;
        }
      }
    }

    cursorPosition = Coordinate(screenRow, screenColOffset);
    eraseCursorToEnd();
    write(buffer); // allow for backspace condition
    cursorPosition = Coordinate(screenRow, screenColOffset + index);

    if (callback != null) callback(buffer, key);
  }
}