blob: e41eac3505153cf06e989fcd153b78b03bcfeda4 [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:async";
import "dart:io";
import "dart:isolate";
class Application {
int lastKnownTerminalColumns;
int lastKnownTerminalLines;
RawReceivePort preventClose;
StreamSubscription<List<int>> stdinListen;
StreamSubscription<ProcessSignal> sigintListen;
StreamSubscription<ProcessSignal> sigwinchListen;
Timer timer;
final Widget widget;
bool started = false;
Application(this.widget) {
lastKnownTerminalColumns = stdout.terminalColumns;
lastKnownTerminalLines = stdout.terminalLines;
preventClose = new RawReceivePort();
stdin.echoMode = false;
stdin.lineMode = false;
stdinListen = stdin.listen(stdinListener);
sigintListen = ProcessSignal.sigint.watch().listen((ProcessSignal signal) {
quit();
exit(0);
});
if (!Platform.isWindows) {
sigwinchListen =
ProcessSignal.sigwinch.watch().listen((ProcessSignal signal) {
lastKnownTerminalColumns = stdout.terminalColumns;
lastKnownTerminalLines = stdout.terminalLines;
clearScreenAlt();
widget.print(this);
});
}
}
void stdinListener(List<int> data) {
try {
widget.input(this, data);
} catch (e) {
quit();
rethrow;
}
}
void quit() {
gotoMainScreenBuffer();
showCursor();
// clearScreenAlt();
timer.cancel();
preventClose.close();
stdinListen.cancel();
sigintListen.cancel();
sigwinchListen?.cancel();
}
void start() {
if (started) throw "Already started!";
started = true;
gotoAlternativeScreenBuffer();
hideCursor();
// clearScreen();
widget.print(this);
timer = new Timer.periodic(new Duration(milliseconds: 100), (t) {
var x = stdout.terminalColumns;
bool changed = false;
if (x != lastKnownTerminalColumns) {
lastKnownTerminalColumns = x;
changed = true;
}
x = stdout.terminalLines;
if (x != lastKnownTerminalLines) {
lastKnownTerminalLines = x;
changed = true;
}
if (changed) {
clearScreenAlt();
widget.print(this);
}
});
}
}
abstract class Widget {
void print(Application app);
void input(Application app, List<int> data) {}
}
// "ESC [" is "Control Sequence Introducer" (CSI) according to Wikipedia
// (https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences).
const String CSI = "\x1b[";
void setCursorPosition(int row, int column) {
// "CSI n ; m H": Cursor Position.
stdout.write("${CSI}${row};${column}H");
}
void gotoAlternativeScreenBuffer() {
// "CSI ? 1049 h": Enable alternative screen buffer.
stdout.write("${CSI}?1049h");
}
void gotoMainScreenBuffer() {
// "CSI ? 1049 l": Disable alternative screen buffer.
stdout.write("${CSI}?1049l");
}
void clearScreen() {
// "CSI n J": Erase in Display. Clears part of the screen.
// If n is 2, clear entire screen [...].
stdout.write("${CSI}2J");
setCursorPosition(0, 0);
}
void clearScreenAlt() {
setCursorPosition(0, 0);
// "CSI n J": Erase in Display. Clears part of the screen.
// If n is 0 (or missing), clear from cursor to end of screen.
stdout.write("${CSI}0J");
setCursorPosition(0, 0);
}
void hideCursor() {
// "CSI ? 25 l": DECTCEM Hides the cursor.
stdout.write("${CSI}?25l");
}
void showCursor() {
// "CSI ? 25 h": DECTCEM Shows the cursor, from the VT320.
stdout.write("${CSI}?25h");
}
void printAt(int row, int column, String s) {
setCursorPosition(row, column);
stdout.write(s);
setCursorPosition(stdout.terminalLines, stdout.terminalColumns);
}
String colorStringBlack(String s) {
// "CSI n m": Select Graphic Rendition.
// m = 0 = Reset / Normal.
// In in range [30, 37]: Set foreground color.
// m = 30 = black.
// In total => start black; print string; reset colors.
return "${CSI}30m${s}${CSI}0m";
}
String colorStringRed(String s) {
// See above.
// m = 31 = red.
return "${CSI}31m${s}${CSI}0m";
}
String colorStringGreen(String s) {
// See above.
// m = 32 = green.
return "${CSI}32m${s}${CSI}0m";
}
String colorStringYellow(String s) {
// See above.
// m = 33 = yellow.
return "${CSI}33m${s}${CSI}0m";
}
String colorStringBlue(String s) {
// See above.
// m = 34 = blue.
return "${CSI}34m${s}${CSI}0m";
}
String colorStringMagenta(String s) {
// See above.
// m = 35 = magenta.
return "${CSI}35m${s}${CSI}0m";
}
String colorStringCyan(String s) {
// See above.
// m = 36 = cyan.
return "${CSI}36m${s}${CSI}0m";
}
String colorStringWhite(String s) {
// m = 37 = white.
return "${CSI}37m${s}${CSI}0m";
}
String colorBackgroundBlack(String s) {
// "CSI n m": Select Graphic Rendition.
// m = 0 = Reset / Normal.
// In in range [40, 47]: Set background color.
// m = 40 = black.
// In total => start black; print string; reset colors.
return "${CSI}40m${s}${CSI}0m";
}
String colorBackgroundRed(String s) {
// See above.
// m = 41 = red.
return "${CSI}41m${s}${CSI}0m";
}
String colorBackgroundGreen(String s) {
// See above.
// m = 42 = green.
return "${CSI}42m${s}${CSI}0m";
}
String colorBackgroundYellow(String s) {
// See above.
// m = 43 = yellow.
return "${CSI}43m${s}${CSI}0m";
}
String colorBackgroundBlue(String s) {
// See above.
// m = 44 = blue.
return "${CSI}44m${s}${CSI}0m";
}
String colorBackgroundMagenta(String s) {
// See above.
// m = 45 = magenta.
return "${CSI}45m${s}${CSI}0m";
}
String colorBackgroundCyan(String s) {
// See above.
// m = 46 = cyan.
return "${CSI}46m${s}${CSI}0m";
}
String colorBackgroundWhite(String s) {
// See above.
// m = 47 = white.
return "${CSI}47m${s}${CSI}0m";
}
String boldString(String s) {
// "CSI n m": Select Graphic Rendition.
// m = 0 = Reset / Normal.
// m = 1 = bold.
return "${CSI}1m${s}${CSI}0m";
}
String italicString(String s) {
// "CSI n m": Select Graphic Rendition.
// m = 0 = Reset / Normal.
// m = 3 = italic.
return "${CSI}3m${s}${CSI}0m";
}
/// Note that this doesn't really seem to work.
/// Wikipedia says "Style extensions exist for Kitty, VTE, mintty and iTerm2."
String underlineString(String s) {
// "CSI n m": Select Graphic Rendition.
// m = 0 = Reset / Normal.
// m = 4 = italic.
return "${CSI}4m${s}${CSI}0m";
}
void drawBox(int row, int column, int length, int height) {
// Top line
setCursorPosition(row, column);
stdout.write("\u250c");
stdout.write("".padLeft(length - 2, "\u2500"));
stdout.write("\u2510");
// Left line
for (int i = 1; i < height - 1; i++) {
setCursorPosition(row + i, column);
stdout.write("\u2502");
}
// Right line
for (int i = 1; i < height - 1; i++) {
setCursorPosition(row + i, column + length - 1);
stdout.write("\u2502");
}
// Bottom line
setCursorPosition(row + height - 1, column);
stdout.write("\u2514");
stdout.write("".padLeft(length - 2, "\u2500"));
stdout.write("\u2518");
}