Created
May 20, 2021 19:30
-
-
Save FeepingCreature/44e370fef18235140eb364b305177c0d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module hello; | |
macro import cx.macros.cimport; | |
import c_header("ctype.h"); | |
import c_header("stdio.h"); | |
import c_header("string.h"); | |
import c_header("sys/ioctl.h"); | |
import c_header("termios.h"); | |
import c_header("time.h"); | |
import c_header("unistd.h"); | |
import helpers : itoa, print; | |
void main() { | |
mut Terminal term; | |
Buffer render(int rows, int cols) { | |
// print("render(" ~ rows.itoa ~ ", " ~ cols.itoa ~ ")"); | |
auto buf = new Buffer(rows, cols); | |
buf.clear(Color.blue); | |
time_t rawtime; | |
tm timeinfo; | |
time(&rawtime); | |
localtime_r(&rawtime, &timeinfo); | |
string time = itoa_fill(2, timeinfo.tm_hour) ~ ":" ~ itoa_fill(2, timeinfo.tm_min) ~ ":" ~ itoa_fill(2, timeinfo.tm_sec); | |
// Title row | |
buf.go(0, 0) | |
.bg(Color.grey).fg(Color.red).print(" H") | |
.fg(Color.black).print("ello World").fillRow; | |
buf.fg(Color.lwhite).bg(Color.blue).printPrettyBox(1, 0, buf.rows - 2, buf.cols - 1); | |
buf.go(-1, 0) | |
.bg(Color.grey).fg(Color.black).print(" Welcome to TUI IRC Demo").printRight("[ " ~ time ~ " ] "); | |
buf.go(0, 0); | |
return buf; | |
} | |
term = new Terminal(&render); | |
// keyDebugLoop; | |
term.updateLoop; | |
} | |
void printPrettyBox(Buffer buf, int r0, int c0, int r1, int c1) { | |
buf.go(r0, c0).print("╔"); | |
for (int i <- 0 .. c1 - c0 - 1) { | |
buf.print("═"); | |
} | |
buf.print("╗"); | |
for (int i <- r0 + 1 .. r1) { | |
buf.go(i, c0).print("║"); | |
buf.go(i, c1).print("║"); | |
} | |
buf.go(r1, c0).print("╚"); | |
for (int i <- 0 .. c1 - c0 - 1) { | |
buf.print("═"); | |
} | |
buf.print("╝"); | |
} | |
string itoa_fill(int width, int value) { | |
mut string res = itoa(value); | |
while (res.length < width) res = "0" ~ res; | |
return res; | |
} | |
// TODO variadics | |
extern(C) int ioctl(int, int, void*); | |
struct Tile | |
{ | |
string ch; // may be unicode, TODO int | |
Color fg, bg; | |
// TODO default gen | |
bool eq(Tile other) { | |
return ch == other.ch && fg == other.fg && bg == other.bg; | |
} | |
} | |
enum Color | |
{ | |
black, | |
red, | |
green, | |
yellow, | |
blue, | |
magenta, | |
cyan, | |
white, | |
grey, | |
lred, | |
lgreen, | |
lyellow, | |
lblue, | |
lmagenta, | |
lcyan, | |
lwhite | |
} | |
class Buffer | |
{ | |
int rows, cols; | |
Tile[] text; | |
// print state, todo factor out into renderer? | |
Color fg_, bg_; | |
int row, col; | |
this(this.rows, this.cols) { | |
this.fg_ = Color.lwhite; | |
this.bg_ = Color.black; | |
this.text = new Tile[](this.rows * this.cols); | |
clear(this.bg_); | |
} | |
Buffer print(string msg) { | |
// TODO cutoff | |
mut int i; | |
while (i < msg.length) { | |
auto chlen = msg[i .. $].utf8NextLength; | |
auto ch = msg[i .. i + chlen]; | |
this.text[this.row * this.cols + this.col] = Tile(ch, this.fg_, this.bg_); | |
i += chlen; | |
this.col += 1; | |
} | |
return this; | |
} | |
Buffer printRight(string msg) { | |
// TODO cutoff | |
int start = this.row * this.cols; | |
for (int i <- this.col .. this.cols - msg.utf8Length) { | |
this.text[start + i] = Tile(" ", this.fg_, this.bg_); | |
this.col += 1; | |
} | |
mut int k; | |
for (int i <- 0 .. msg.utf8Length) { | |
auto chlen = msg[k .. $].utf8NextLength; | |
auto ch = msg[k .. k + chlen]; | |
k += chlen; | |
this.text[start + this.col] = Tile(ch, this.fg_, this.bg_); | |
this.col += 1; | |
} | |
return this; | |
} | |
Buffer go(mut int row, mut int col) { | |
if (row < 0) row = this.rows - -row; | |
if (col < 0) col = this.cols - -col; | |
this.row = row; | |
this.col = col; | |
return this; | |
} | |
Buffer fg(Color fg) { | |
this.fg_ = fg; | |
return this; | |
} | |
Buffer bg(Color bg) { | |
this.bg_ = bg; | |
return this; | |
} | |
Buffer clear(Color bg) { | |
this.bg_ = bg; | |
for (int i <- 0 .. this.text.length) { | |
this.text[i] = Tile(" ", this.fg_, this.bg_); | |
} | |
return this; | |
} | |
Buffer fillRow() { | |
int base = this.row * this.cols + this.col; | |
for (int i <- 0 .. this.cols - this.col) { | |
this.text[base + i] = Tile(" ", this.fg_, this.bg_); | |
} | |
} | |
Tile[] line(int row) { | |
return this.text[this.cols * row .. this.cols * (row + 1)]; | |
} | |
} | |
void puts(string msg) { | |
write(STDOUT_FILENO, msg.ptr, msg.length); | |
} | |
class Terminal | |
{ | |
termios original; | |
Buffer currentScreenState; | |
Buffer delegate(int, int) render; | |
(int rows, int cols) getSize() { | |
mut winsize sz; | |
ioctl(0, TIOCGWINSZ, &sz); | |
return (sz.ws_row, sz.ws_col); | |
} | |
this(this.render) { | |
auto sz = getSize; | |
this.currentScreenState = new Buffer(sz.rows, sz.cols); | |
} | |
void update() { | |
auto sz = getSize; | |
auto newbuf = render(sz.rows, sz.cols); | |
// TODO compare tuples | |
if (newbuf.rows != currentScreenState.rows || newbuf.cols != currentScreenState.cols) { | |
repaint(newbuf); | |
return; | |
} | |
diffpaint(newbuf); | |
} | |
void updateLoop() { | |
enableRawMode; | |
mut char c; | |
while (c != "q"[0]) { | |
update; | |
c = 0; | |
read(STDIN_FILENO, &c, 1); | |
if (c == cast(char) 0) continue; | |
/*if (iscntrl(c)) { | |
print(itoa(c)); | |
} else { | |
print(itoa(c) ~ " (" ~ c ~ ")"); | |
}*/ | |
} | |
disableRawMode; | |
} | |
void repaint(Buffer newBuf) { | |
puts("\x1B[2J"); | |
mut Color fg, bg; | |
for (int i <- 0 .. newBuf.rows) { | |
auto line = newBuf.line(i); | |
for (int k <- 0 .. newBuf.cols) { | |
auto tile = line[k]; | |
if (fg != tile.fg || bg != tile.bg) { | |
int translate(Color c) { | |
int i = cast(int) c; | |
if (i > 7) return 90 + i - 8; | |
return 30 + i; | |
} | |
puts("\x1B[" ~ itoa(translate(tile.fg)) ~ ";" ~ itoa(translate(tile.bg) + 10) ~ "m"); | |
fg = tile.fg; | |
bg = tile.bg; | |
} | |
puts(tile.ch); | |
} | |
if (i != newBuf.rows - 1) puts("\n"); | |
} | |
puts("\x1B[" ~ itoa(newBuf.row + 1) ~ ";" ~ itoa(newBuf.col + 1) ~ "H"); | |
this.currentScreenState = newBuf; | |
} | |
void diffpaint(Buffer newBuf) { | |
mut Color fg, bg; | |
for (int i <- 0 .. newBuf.rows) { | |
auto oldLine = currentScreenState.line(i); | |
auto line = newBuf.line(i); | |
mut bool mustGoTo = true; | |
for (int k <- 0 .. newBuf.cols) { | |
auto tile = line[k]; | |
auto oldTile = oldLine[k]; | |
if (tile.eq(oldTile)) { | |
mustGoTo = true; | |
continue; | |
} | |
if (mustGoTo) { | |
puts("\x1B[" ~ itoa(i + 1) ~ ";" ~ itoa(k + 1) ~ "H"); | |
mustGoTo = false; | |
} | |
if (fg != tile.fg || bg != tile.bg) { | |
int translate(Color c) { | |
int i = cast(int) c; | |
if (i > 7) return 90 + i - 8; | |
return 30 + i; | |
} | |
puts("\x1B[" ~ itoa(translate(tile.fg)) ~ ";" ~ itoa(translate(tile.bg) + 10) ~ "m"); | |
fg = tile.fg; | |
bg = tile.bg; | |
} | |
puts(tile.ch); | |
} | |
} | |
puts("\x1B[" ~ itoa(newBuf.row + 1) ~ ";" ~ itoa(newBuf.col + 1) ~ "H"); | |
// TODO why does this leak otherwise? | |
this.currentScreenState.text = []; | |
this.currentScreenState = newBuf; | |
} | |
void enableRawMode() { | |
tcgetattr(STDIN_FILENO, &this.original); | |
mut auto raw = this.original; | |
raw.c_lflag = raw.c_lflag & (ECHO | ICANON).bitflip; | |
// raw.c_cc[VMIN] = 0; | |
// raw.c_cc[VTIME] = 1; | |
raw.c_cc._6 = 0; | |
raw.c_cc._5 = 1; | |
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); | |
puts("\x1B[?1049h"); | |
} | |
void disableRawMode() { | |
tcsetattr(STDIN_FILENO, TCSAFLUSH, &original); | |
puts("\x1B[?1049l"); | |
} | |
} | |
int bitflip(int i) { return -i - 1; } | |
void keyDebugLoop() { | |
mut char c; | |
while (c != "q"[0]) { | |
c = 0; | |
read(STDIN_FILENO, &c, 1); | |
if (c == cast(char) 0) continue; | |
if (iscntrl(c)) { | |
print(itoa(c)); | |
} else { | |
print(itoa(c) ~ " (" ~ c ~ ")"); | |
} | |
} | |
} | |
// TODO std.uni, by-codepoint iteration | |
int utf8NextLength(string text) | |
{ | |
// see https://en.wikipedia.org/wiki/UTF-8#FSS-UTF | |
if (text.length < 1) return 0; | |
int ch0 = text[0]; | |
if (ch0 < 128) return 1; | |
assert(ch0 >= 192); | |
assert(text.length >= 2); | |
if (ch0 < 224) return 2; | |
assert(text.length >= 3); | |
if (ch0 < 240) return 3; | |
assert(text.length >= 4); | |
if (ch0 < 248) return 4; | |
assert(text.length >= 5); | |
if (ch0 < 252) return 5; | |
assert(text.length >= 6); | |
if (ch0 < 254) return 6; | |
assert(false); | |
} | |
int utf8Length(mut string text) { | |
mut int i; | |
while (text.length) { | |
i += 1; | |
text = text[text.utf8NextLength .. $]; | |
} | |
return i; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment