read_line method

  1. @override
String? read_line({
  1. bool cancel_on_break = false,
  2. bool cancel_on_escape = false,
  3. bool cancel_on_eof = false,
  4. void callback(
    1. String text,
    2. Key lastPressed
    )?,
})
override

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

@override
String? read_line({
  final bool cancel_on_break = false,
  final bool cancel_on_escape = false,
  final bool cancel_on_eof = false,
  final void Function(String text, Key lastPressed)? callback,
}) {
  String buffer = '';
  // Cursor position relative to buffer, not screen.
  int index = 0;
  final current_cursor_position = cursor_position.get();
  final screen_row = current_cursor_position!.row;
  final screen_col_offset = current_cursor_position.col;
  final buffer_max_length = dimensions.width - screen_col_offset - 3;
  for (;;) {
    final key = read_key();
    key.match(
      printable: (final key) {
        if (buffer.length < buffer_max_length) {
          if (index == buffer.length) {
            buffer += key.char;
            index++;
          } else {
            buffer = buffer.substring(0, index) +
                key.char +
                buffer.substring(index);
            index++;
          }
        }
      },
      control: (final key) {
        switch (key.control_char) {
          case ControlCharacters.enter:
            if (_scrollback_buffer != null) {
              _scrollback_buffer!.add(buffer);
            }
            write_line();
            return buffer;
          case ControlCharacters.ctrlC:
            if (cancel_on_break) {
              return null;
            }
            break;
          case ControlCharacters.escape:
            if (cancel_on_escape) {
              return null;
            }
            break;
          case ControlCharacters.backspace:
          case ControlCharacters.ctrlH:
            if (index > 0) {
              buffer =
                  buffer.substring(0, index - 1) + buffer.substring(index);
              index--;
            }
            break;
          case ControlCharacters.ctrlU:
            buffer = buffer.substring(index, buffer.length);
            index = 0;
            break;
          case ControlCharacters.delete:
          case ControlCharacters.ctrlD:
            if (index < buffer.length - 1) {
              buffer =
                  buffer.substring(0, index) + buffer.substring(index + 1);
            } else if (cancel_on_eof) {
              return null;
            }
            break;
          case ControlCharacters.ctrlK:
            buffer = buffer.substring(0, index);
            break;
          case ControlCharacters.arrowLeft:
          case ControlCharacters.ctrlB:
            index = () {
              if (index > 0) {
                return index - 1;
              } else {
                return index;
              }
            }();
            break;
          case ControlCharacters.arrowUp:
            if (_scrollback_buffer != null) {
              buffer = _scrollback_buffer!.up(buffer);
              index = buffer.length;
            }
            break;
          case ControlCharacters.arrowDown:
            if (_scrollback_buffer != null) {
              final temp = _scrollback_buffer!.down();
              if (temp != null) {
                buffer = temp;
                index = buffer.length;
              }
            }
            break;
          case ControlCharacters.arrowRight:
          case ControlCharacters.ctrlF:
            index = () {
              if (index < buffer.length) {
                return index + 1;
              } else {
                return index;
              }
            }();
            break;
          case ControlCharacters.wordLeft:
            if (index > 0) {
              final bufferLeftOfCursor = buffer.substring(0, index - 1);
              final lastSpace = bufferLeftOfCursor.lastIndexOf(' ');
              index = () {
                if (lastSpace != -1) {
                  return lastSpace + 1;
                } else {
                  return 0;
                }
              }();
            }
            break;
          case ControlCharacters.wordRight:
            if (index < buffer.length) {
              final bufferRightOfCursor = buffer.substring(index + 1);
              final nextSpace = bufferRightOfCursor.indexOf(' ');
              if (nextSpace != -1) {
                index = min(index + nextSpace + 2, buffer.length);
              } else {
                index = buffer.length;
              }
            }
            break;
          case ControlCharacters.home:
          case ControlCharacters.ctrlA:
            index = 0;
            break;
          case ControlCharacters.end:
          case ControlCharacters.ctrlE:
            index = buffer.length;
            break;
          case ControlCharacters.ctrlG:
          case ControlCharacters.tab:
          case ControlCharacters.ctrlJ:
          case ControlCharacters.ctrlL:
          case ControlCharacters.ctrlN:
          case ControlCharacters.ctrlO:
          case ControlCharacters.ctrlP:
          case ControlCharacters.ctrlQ:
          case ControlCharacters.ctrlR:
          case ControlCharacters.ctrlS:
          case ControlCharacters.ctrlT:
          case ControlCharacters.ctrlV:
          case ControlCharacters.ctrlW:
          case ControlCharacters.ctrlX:
          case ControlCharacters.ctrlY:
          case ControlCharacters.ctrlZ:
          case ControlCharacters.pageUp:
          case ControlCharacters.pageDown:
          case ControlCharacters.wordBackspace:
          case ControlCharacters.F1:
          case ControlCharacters.F2:
          case ControlCharacters.F3:
          case ControlCharacters.F4:
          case ControlCharacters.unknown:
            // Do nothing.
            break;
        }
      },
    );
    cursor_position.update(
      SneathCoordinateImpl(
        row: screen_row,
        col: screen_col_offset,
      ),
    );
    erase_cursor_to_end();
    // Allow for backspace condition.
    write(buffer);
    cursor_position.update(
      SneathCoordinateImpl(
        row: screen_row,
        col: screen_col_offset + index,
      ),
    );
    if (callback != null) {
      callback(buffer, key);
    }
  }
}