choose<T> function
T?
choose<T>()
Displays to the user a list of options
, and returns
once one has been chosen.
Each option will be prefixed with a number, corresponding
to its index + 1
.
A default option may be provided by means of defaultsTo
.
A custom prompt
may be provided, which is then forwarded to get.
This function also supports an interactive
mode, where user arrow keys are processed.
In interactive
mode, you can provide a defaultIndex
for the UI to start on.
color
, defaultsTo
, inputColor
, conceal
, and chevron
are forwarded to get.
Example:
Choose a color:
1) Red
2) Blue
3) Green
Implementation
T? choose<T>(String message, Iterable<T> options,
{T? defaultsTo,
String prompt = 'Enter your choice',
// int defaultIndex = 0,
bool chevron = true,
@deprecated bool colon = true,
AnsiCode inputColor = cyan,
bool color = true,
bool conceal = false,
bool interactive = true,
Iterable<String>? names}) {
if (options.isEmpty) {
throw ArgumentError.value('`options` may not be empty.');
}
if (defaultsTo != null && !options.contains(defaultsTo)) {
throw ArgumentError('$defaultsTo is not contained in $options, and therefore cannot be the default value.');
}
if (names != null && names.length != options.length) {
throw ArgumentError('$names must have length ${options.length}, not ${names.length}.');
}
if (names != null && names.any((s) => s.length != 1)) {
throw ArgumentError('Every member of $names must be a string with a length of 1.');
}
var map = <T, String>{};
for (var option in options) {
map[option] = option.toString();
}
if (chevron && colon) message += ':';
var b = StringBuffer();
b.writeln(message);
if (interactive && ansiOutputEnabled && !Platform.isWindows) {
var index = defaultsTo != null ? options.toList().indexOf(defaultsTo) : 0;
var oldEchoMode = stdin.echoMode;
var oldLineMode = stdin.lineMode;
var needsClear = false;
if (color) {
print(wrapWith(b.toString(), [darkGray, styleBold]));
} else {
print(b);
}
void writeIt() {
if (!needsClear) {
needsClear = true;
} else {
for (var i = 0; i < options.length; i++) {
goUpOneLine();
clearLine();
}
}
for (var i = 0; i < options.length; i++) {
var key = map.keys.elementAt(i);
var msg = map[key];
AnsiCode code;
if (index == i) {
code = cyan;
msg = '* $msg';
} else {
code = darkGray;
msg = '$msg ';
}
if (names != null) {
msg = names.elementAt(i) + ') $msg';
}
if (color) {
print(code.wrap(msg));
} else {
print(msg);
}
}
}
do {
int ch;
writeIt();
try {
stdin.lineMode = stdin.echoMode = false;
ch = stdin.readByteSync();
if (ch == $esc) {
ch = stdin.readByteSync();
if (ch == $lbracket) {
ch = stdin.readByteSync();
if (ch == $A) {
// Up key
index--;
if (index < 0) index = options.length - 1;
writeIt();
} else if (ch == $B) {
// Down key
index++;
if (index >= options.length) index = 0;
writeIt();
}
}
} else if (ch == $lf) {
// Enter key pressed - submit
return map.keys.elementAt(index);
} else {
// Check if this matches any name
var s = String.fromCharCode(ch);
if (names != null && names.contains(s)) {
index = names.toList().indexOf(s);
return map.keys.elementAt(index);
}
}
} finally {
stdin.lineMode = oldLineMode;
stdin.echoMode = oldEchoMode;
}
} while (true);
} else {
b.writeln();
for (var i = 0; i < options.length; i++) {
var key = map.keys.elementAt(i);
var indicator = names != null ? names.elementAt(i) : (i + 1).toString();
b.write('$indicator) ${map[key]}');
if (key == defaultsTo) b.write(' [Default - Press Enter]');
b.writeln();
}
b.writeln();
if (color) {
print(wrapWith(b.toString(), [darkGray, styleBold]));
} else {
print(b);
}
var line = get(
prompt,
chevron: false,
inputColor: inputColor,
color: color,
conceal: conceal,
validate: (s) {
if (s.isEmpty) return defaultsTo != null;
if (map.values.contains(s)) return true;
if (names != null && names.contains(s)) return true;
var i = int.tryParse(s);
if (i == null) return false;
return i >= 1 && i <= options.length;
},
);
if (line.isEmpty) return defaultsTo;
int? i;
if (names != null && names.contains(line)) {
i = names.toList().indexOf(line) + 1;
} else {
i = int.tryParse(line);
}
if (i != null) return map.keys.elementAt(i - 1);
return map.keys.elementAt(map.values.toList(growable: false).indexOf(line));
}
}