Skip to content

Instantly share code, notes, and snippets.

@passsy
Created March 3, 2020 21:59
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save passsy/c5e3f95a4f4113024f7b2f151377d9cc to your computer and use it in GitHub Desktop.
Save passsy/c5e3f95a4f4113024f7b2f151377d9cc to your computer and use it in GitHub Desktop.

Interactive CLI in Dart

Screen-Recording-2020-03-03-22-55-27

Example how to build a interactive CLI application in Dart

/// Copyright 2020, Pascal Welsch
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
import 'dart:io' as io;
Future<void> main() async {
try {
io.stdin.echoMode = false;
io.stdin.lineMode = false;
} catch (_) {
print("Running in non-interactive mode, exiting");
return;
}
print("How would you rate writing CLIs with Dart? (Press <space> to select)");
final answer = await selectAnswer(["bad", "good", "amazing 🎉"]);
print("selected $answer");
io.exit(0);
}
const ESC = '\u001B[';
const eraseLine = ESC + '2K';
const cursorUp = ESC + '1A';
const cursorHide = ESC + '?25l';
const cursorShow = ESC + '?25h';
const arrowUp = ESC + 'A';
const arrowDown = ESC + 'B';
const space = " ";
Future<String> selectAnswer(List<String> options) async {
var selection = 0;
String _displayed = "";
/// replaces all visible text in terminal with new one
void render(String text) {
final lines = _displayed.runes.where((c) => c == 10 /*\n*/).length;
io.stdout.write(eraseLine);
for (var i = 0; i < lines; i++) {
io.stdout.write(eraseLine);
io.stdout.write(cursorUp);
}
io.stdout.writeCharCode(13);
io.stdout.write(text);
_displayed = text;
}
/// print the options + selection to terminal
void showOptions() {
final sb = StringBuffer();
for (var i = 0; i < options.length; i++) {
final icon = () {
if (selection == i) return "●";
return "○";
}();
sb.write(" $icon ${options[i]}\n");
}
render(sb.toString());
}
void select() {
render("");
io.stdout.write(cursorShow);
}
// hide cursor
io.stdout.write(cursorHide);
// show cursor on forced exit
io.ProcessSignal.sigint.watch().listen((event) {
io.stdout.write(cursorShow);
io.exit(1);
});
showOptions();
await for (final input in io.stdin) {
final String command = String.fromCharCodes(input);
switch (command) {
case arrowUp:
selection = (options.length + selection - 1) % options.length;
showOptions();
continue;
case arrowDown:
selection = (selection + 1) % options.length;
showOptions();
continue;
case space:
select();
return options[selection];
}
}
throw "nothing selected";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment