readLine method
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);
}
}