chooseAny<T extends Object?> method
Prompts user with message to choose zero or more values
from the provided choices.
An optional list of defaultValues can be specified.
The defaultValues must be one of the provided choices.
This method requires a terminal to be attached to stdout. See https://api.dart.dev/stable/dart-io/Stdout/hasTerminal.html.
Implementation
List<T> chooseAny<T extends Object?>(
String? message, {
required List<T> choices,
List<T>? defaultValues,
String Function(T choice)? display,
}) {
final resolvedDisplay = display ?? (value) => '$value';
final hasDefaults = defaultValues != null && defaultValues.isNotEmpty;
final selections = hasDefaults
? defaultValues.map((value) => choices.indexOf(value)).toSet()
: <int>{};
var index = 0;
void writeChoices() {
_stdout
// save cursor
..write('\x1b7')
// hide cursor
..write('\x1b[?25l')
..writeln('$message');
for (final choice in choices) {
final choiceIndex = choices.indexOf(choice);
final isCurrent = choiceIndex == index;
final isSelected = selections.contains(choiceIndex);
final checkBox = isSelected ? lightCyan.wrap('◉') : '◯';
if (isCurrent) {
_stdout
..write(green.wrap('❯'))
..write(' $checkBox ${lightCyan.wrap(resolvedDisplay(choice))}');
} else {
_stdout
..write(' ')
..write(' $checkBox ${resolvedDisplay(choice)}');
}
if (choices.last != choice) {
_stdout.write('\n');
}
}
}
_stdin
..echoMode = false
..lineMode = false;
writeChoices();
List<T>? results;
while (results == null) {
final key = _readKey();
final keyIsUpOrKKey =
key.controlChar == ControlCharacter.arrowUp || key.char == 'k';
final keyIsDownOrJKey =
key.controlChar == ControlCharacter.arrowDown || key.char == 'j';
final keyIsSpaceKey = key.char == ' ';
final keyIsEnterOrReturnKey = key.controlChar == ControlCharacter.ctrlJ ||
key.controlChar == ControlCharacter.ctrlM;
if (keyIsUpOrKKey) {
index = (index - 1) % (choices.length);
} else if (keyIsDownOrJKey) {
index = (index + 1) % (choices.length);
} else if (keyIsSpaceKey) {
selections.contains(index)
? selections.remove(index)
: selections.add(index);
} else if (keyIsEnterOrReturnKey) {
_stdin
..lineMode = true
..echoMode = true;
results = selections.map((index) => choices[index]).toList();
_stdout
// restore cursor
..write('\x1b8')
// clear to end of screen
..write('\x1b[J')
// show cursor
..write('\x1b[?25h')
..write('$message ')
..writeln(
styleDim.wrap(
lightCyan.wrap('${results.map(resolvedDisplay).toList()}'),
),
);
break;
}
// restore cursor
_stdout.write('\x1b8');
writeChoices();
}
return results;
}