From 96172eac84efb4a82b8f14d0e67847d84615e264 Mon Sep 17 00:00:00 2001 From: Max Wash Date: Mon, 18 Nov 2024 15:15:04 +0000 Subject: [PATCH] frontend: line-ed: move platform-specific code to separate directory --- frontend/CMakeLists.txt | 13 +- frontend/line-ed/input.c | 27 +- frontend/line-ed/line-ed.c | 3 +- frontend/line-ed/line-ed.h | 4 + frontend/line-ed/tty.h | 10 + frontend/{ => sys/darwin}/line-ed/tty.c | 0 frontend/sys/linux/line-ed/tty.c | 384 +++++++++++++++++++++ frontend/sys/windows/line-ed/tty.c | 440 ++++++++++++++++++++++++ 8 files changed, 871 insertions(+), 10 deletions(-) rename frontend/{ => sys/darwin}/line-ed/tty.c (100%) create mode 100644 frontend/sys/linux/line-ed/tty.c create mode 100644 frontend/sys/windows/line-ed/tty.c diff --git a/frontend/CMakeLists.txt b/frontend/CMakeLists.txt index 9e9d7a4..21bccaa 100644 --- a/frontend/CMakeLists.txt +++ b/frontend/CMakeLists.txt @@ -1,6 +1,15 @@ -file(GLOB_RECURSE ivy_sources *.c *.h) +string(TOLOWER ${CMAKE_SYSTEM_NAME} system_name) -add_executable(ivy ${ivy_sources}) +file(GLOB ivy_sources + *.c *.h + cmd/*.c cmd/*.h + line-ed/*.c line-ed/*.h) + +file(GLOB_RECURSE sys_sources + sys/${system_name}/*.c + sys/${system_name}/*.h) + +add_executable(ivy ${ivy_sources} ${sys_sources}) target_link_libraries( ivy ivy-rt diff --git a/frontend/line-ed/input.c b/frontend/line-ed/input.c index db576cb..006f333 100644 --- a/frontend/line-ed/input.c +++ b/frontend/line-ed/input.c @@ -112,7 +112,8 @@ void backspace(struct line_ed *ed) void cursor_left(struct line_ed *ed) { if (ed->l_cursor_x != 0) { - fputs("\010", stdout); + //fputs("\010", stdout); + s_tty_move_cursor_x(ed->l_tty, TTY_POS_CURSOR, -1); fflush(stdout); ed->l_cursor_x--; ed->l_buf_ptr--; @@ -133,7 +134,10 @@ void cursor_left(struct line_ed *ed) unsigned int len = line_length(ed, ed->l_cursor_y); ed->l_cursor_x = len - 1; - printf("\033[A\033[%dG", len + prompt_len); + //printf("\033[A\033[%dG", len + prompt_len); + s_tty_move_cursor_y(ed->l_tty, TTY_POS_CURSOR, -1); + s_tty_move_cursor_x(ed->l_tty, TTY_POS_START, len + prompt_len); + fflush(stdout); } @@ -146,7 +150,8 @@ void cursor_right(struct line_ed *ed) if (*ed->l_buf_ptr != '\n') { ed->l_cursor_x++; ed->l_buf_ptr++; - fputs("\033[C", stdout); + //fputs("\033[C", stdout); + s_tty_move_cursor_x(ed->l_tty, TTY_POS_CURSOR, 1); fflush(stdout); return; } @@ -159,7 +164,9 @@ void cursor_right(struct line_ed *ed) ed->l_cursor_x = 0; ed->l_buf_ptr++; - printf("\033[B\033[G"); + //printf("\033[B\033[G"); + s_tty_move_cursor_y(ed->l_tty, TTY_POS_CURSOR, 1); + s_tty_move_cursor_x(ed->l_tty, TTY_POS_START, 0); fflush(stdout); } @@ -170,10 +177,13 @@ void arrow_up(struct line_ed *ed) } if (ed->l_cursor_y > 0) { - printf("\033[%uA", ed->l_cursor_y); + //printf("\033[%uA", ed->l_cursor_y); + s_tty_move_cursor_y(ed->l_tty, TTY_POS_CURSOR, ed->l_cursor_y); } printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1); + s_tty_move_cursor_x( + ed->l_tty, TTY_POS_START, prompt_length(ed, PROMPT_MAIN) + 1); save_buf_to_history(ed); ed->l_history_pos--; @@ -190,10 +200,13 @@ void arrow_down(struct line_ed *ed) } if (ed->l_cursor_y > 0) { - printf("\033[%uA", ed->l_cursor_y); + //printf("\033[%uA", ed->l_cursor_y); + s_tty_move_cursor_y(ed->l_tty, TTY_POS_CURSOR, ed->l_cursor_y); } - printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1); + //printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1); + s_tty_move_cursor_x( + ed->l_tty, TTY_POS_START, prompt_length(ed, PROMPT_MAIN) + 1); save_buf_to_history(ed); ed->l_history_pos++; diff --git a/frontend/line-ed/line-ed.c b/frontend/line-ed/line-ed.c index b6e74e6..28e79f5 100644 --- a/frontend/line-ed/line-ed.c +++ b/frontend/line-ed/line-ed.c @@ -54,6 +54,7 @@ struct line_ed *line_ed_create(void) return NULL; } + out->l_tty = s_get_tty(); out->l_buf_end = out->l_buf + LINE_MAX; out->l_buf_ptr = out->l_buf; out->l_line_end = out->l_buf; @@ -178,7 +179,7 @@ long line_ed_readline(struct line_ed *ed, char *out, size_t max) append_to_index = ed->l_history_pos; } - struct s_tty *tty = s_get_tty(); + struct s_tty *tty = ed->l_tty; s_tty_set_raw(tty); show_prompt(ed); diff --git a/frontend/line-ed/line-ed.h b/frontend/line-ed/line-ed.h index 5a6b556..c47551b 100644 --- a/frontend/line-ed/line-ed.h +++ b/frontend/line-ed/line-ed.h @@ -10,6 +10,7 @@ #include #include +struct s_tty; struct s_tty_vmode; struct line_ed; @@ -56,6 +57,9 @@ struct line_ed { struct ivy_line_source l_line_source; + /* pointer to tty interface */ + struct s_tty *l_tty; + /* the lexical scope that we are currently in. * this is provided by components further up the input pipeline, * for example, when we are inside a string or if-statement. */ diff --git a/frontend/line-ed/tty.h b/frontend/line-ed/tty.h index 717ec98..7ca5789 100644 --- a/frontend/line-ed/tty.h +++ b/frontend/line-ed/tty.h @@ -89,6 +89,11 @@ enum s_tty_colour_mode { TTY_COLOUR_TRUE, }; +enum s_tty_position_base { + TTY_POS_START, + TTY_POS_CURSOR, +}; + enum s_tty_attrib { TTY_ATTRIB_NORMAL = 0x00u, TTY_ATTRIB_BOLD = 0x01u, @@ -137,4 +142,9 @@ extern void s_tty_set_vmode(struct s_tty *tty, const struct s_tty_vmode *vmode); extern s_keycode s_tty_read_key(struct s_tty *tty); +extern void s_tty_move_cursor_x( + struct s_tty *tty, enum s_tty_position_base base, int pos); +extern void s_tty_move_cursor_y( + struct s_tty *tty, enum s_tty_position_base base, int pos); + #endif diff --git a/frontend/line-ed/tty.c b/frontend/sys/darwin/line-ed/tty.c similarity index 100% rename from frontend/line-ed/tty.c rename to frontend/sys/darwin/line-ed/tty.c diff --git a/frontend/sys/linux/line-ed/tty.c b/frontend/sys/linux/line-ed/tty.c new file mode 100644 index 0000000..0368583 --- /dev/null +++ b/frontend/sys/linux/line-ed/tty.c @@ -0,0 +1,384 @@ +#include +#include +#include +#include +#include +#include "tty.h" + +#define ANSI_BOLD_ON "1" +#define ANSI_BOLD_OFF "22" +#define ANSI_ITALIC_ON "3" +#define ANSI_ITALIC_OFF "23" +#define ANSI_UNDERLINE_ON "4" +#define ANSI_UNDERLINE_OFF "24" + +#define ANSI_DEFAULTCOLOUR_FG "39" +#define ANSI_DEFAULTCOLOUR_BG "49" + +#define ANSI_256COLOUR_FG "38;5" +#define ANSI_256COLOUR_BG "48;5" + +#define ANSI_TRUECOLOUR_FG "38;2" +#define ANSI_TRUECOLOUR_BG "48;2" + +enum tty_flags { + TTY_INIT = 0x01u, + TTY_INTERACTIVE = 0x02u, +}; + +struct s_tty { + FILE *t_in, *t_out; + enum tty_flags t_flags; + struct termios t_ios_raw, t_ios_default; + struct s_tty_vmode t_vmode; + unsigned char t_mcount; +}; + +static struct s_tty tty = {}; + +static const char *ansi_colour16_fg[] = { + [TTY_COLOUR16_BLACK] = "30", + [TTY_COLOUR16_RED] = "31", + [TTY_COLOUR16_GREEN] = "32", + [TTY_COLOUR16_YELLOW] = "33", + [TTY_COLOUR16_BLUE] = "34", + [TTY_COLOUR16_MAGENTA] = "35", + [TTY_COLOUR16_CYAN] = "36", + [TTY_COLOUR16_WHITE] = "37", + [TTY_COLOUR16_GREY] = "90", + [TTY_COLOUR16_BRIGHT_RED] = "91", + [TTY_COLOUR16_BRIGHT_GREEN] = "92", + [TTY_COLOUR16_BRIGHT_YELLOW] = "93", + [TTY_COLOUR16_BRIGHT_BLUE] = "94", + [TTY_COLOUR16_BRIGHT_MAGENTA] = "95", + [TTY_COLOUR16_BRIGHT_CYAN] = "96", + [TTY_COLOUR16_BRIGHT_WHITE] = "97", +}; + +static const char *ansi_colour16_bg[] = { + [TTY_COLOUR16_BLACK] = "40", + [TTY_COLOUR16_RED] = "41", + [TTY_COLOUR16_GREEN] = "42", + [TTY_COLOUR16_YELLOW] = "43", + [TTY_COLOUR16_BLUE] = "44", + [TTY_COLOUR16_MAGENTA] = "45", + [TTY_COLOUR16_CYAN] = "46", + [TTY_COLOUR16_WHITE] = "47", + [TTY_COLOUR16_GREY] = "100", + [TTY_COLOUR16_BRIGHT_RED] = "101", + [TTY_COLOUR16_BRIGHT_GREEN] = "102", + [TTY_COLOUR16_BRIGHT_YELLOW] = "103", + [TTY_COLOUR16_BRIGHT_BLUE] = "104", + [TTY_COLOUR16_BRIGHT_MAGENTA] = "105", + [TTY_COLOUR16_BRIGHT_CYAN] = "106", + [TTY_COLOUR16_BRIGHT_WHITE] = "107", +}; + +static void init_tty(struct s_tty *tty, FILE *in, FILE *out) +{ + tty->t_in = in; + tty->t_out = out; + + int fd = fileno(in); + + if (isatty(fd)) { + tty->t_flags |= TTY_INTERACTIVE; + } + + tcgetattr(fd, &tty->t_ios_default); + memcpy(&tty->t_ios_raw, &tty->t_ios_default, sizeof tty->t_ios_raw); + + tty->t_ios_raw.c_iflag &= ~(ICRNL | IXON); + tty->t_ios_raw.c_oflag &= ~(OPOST); + tty->t_ios_raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); + + tty->t_flags |= TTY_INIT; +} + +struct s_tty *s_get_tty(void) +{ + if (!(tty.t_flags & TTY_INIT)) { + init_tty(&tty, stdin, stdout); + } + + return &tty; +} + +bool s_tty_is_interactive(const struct s_tty *tty) +{ + return (tty->t_flags & TTY_INTERACTIVE) == TTY_INTERACTIVE; +} + +void s_tty_set_raw(struct s_tty *tty) +{ + tcsetattr(STDIN_FILENO, TCSAFLUSH, &tty->t_ios_raw); +} + +void s_tty_set_canon(struct s_tty *tty) +{ + tcsetattr(STDIN_FILENO, TCSAFLUSH, &tty->t_ios_default); +} + +void s_tty_reset_vmode(struct s_tty *tty) +{ + if (tty->t_vmode.v_fg.c_mode == TTY_COLOUR_NONE + && tty->t_vmode.v_bg.c_mode == TTY_COLOUR_NONE + && tty->t_vmode.v_attrib == TTY_ATTRIB_NORMAL) { + return; + } + + fprintf(tty->t_out, "\033[0;39;49m"); + + memset(&tty->t_vmode, 0x0, sizeof tty->t_vmode); + + tty->t_vmode.v_fg.c_mode = TTY_COLOUR_NONE; + tty->t_vmode.v_bg.c_mode = TTY_COLOUR_NONE; + tty->t_vmode.v_attrib = TTY_ATTRIB_NORMAL; +} + +static int compare_colour(const struct s_tty_colour *a, const struct s_tty_colour *b) +{ + if (a->c_mode != b->c_mode) { + return -1; + } + + switch (a->c_mode) { + case TTY_COLOUR_16: + if (a->c_16.value != b->c_16.value) { + return -1; + } + break; + case TTY_COLOUR_256: + if (a->c_256.value != b->c_256.value) { + return -1; + } + break; + case TTY_COLOUR_TRUE: + if (a->c_true.r != b->c_true.r) { + return -1; + } + + if (a->c_true.g != b->c_true.g) { + return -1; + } + + if (a->c_true.b != b->c_true.b) { + return -1; + } + break; + default: + break; + } + + return 0; +} + +static int compare_vmode(const struct s_tty_vmode *a, const struct s_tty_vmode *b) +{ + if (a->v_attrib != b->v_attrib) { + return -1; + } + + if (compare_colour(&a->v_fg, &b->v_fg) != 0) { + return -1; + } + + if (compare_colour(&a->v_bg, &b->v_bg) != 0) { + return -1; + } + + return 0; +} + +static void put_ansi_attrib(struct s_tty *tty, const char *s) +{ + if (tty->t_mcount > 0) { + fputs(";", tty->t_out); + } + + fputs(s, tty->t_out); + tty->t_mcount++; +} + +static void set_attrib(struct s_tty *tty, enum s_tty_attrib old, enum s_tty_attrib new) +{ + if (old == new) { + return; + } + + /* Bold ON */ + if (!(old & TTY_ATTRIB_BOLD) && new & TTY_ATTRIB_BOLD) { + put_ansi_attrib(tty, ANSI_BOLD_ON); + } + + /* Bold OFF */ + if (old & TTY_ATTRIB_BOLD && !(new & TTY_ATTRIB_BOLD)) { + put_ansi_attrib(tty, ANSI_BOLD_OFF); + } + + /* Underline ON */ + if (!(old & TTY_ATTRIB_UNDERLINE) && new & TTY_ATTRIB_UNDERLINE) { + put_ansi_attrib(tty, ANSI_UNDERLINE_ON); + } + + /* Underline OFF */ + if (old & TTY_ATTRIB_UNDERLINE && !(new & TTY_ATTRIB_UNDERLINE)) { + put_ansi_attrib(tty, ANSI_UNDERLINE_OFF); + } + + /* Italic ON */ + if (!(old & TTY_ATTRIB_ITALIC) && new & TTY_ATTRIB_ITALIC) { + put_ansi_attrib(tty, ANSI_ITALIC_ON); + } + + /* Italic OFF */ + if (old & TTY_ATTRIB_ITALIC && !(new & TTY_ATTRIB_ITALIC)) { + put_ansi_attrib(tty, ANSI_ITALIC_OFF); + } +} + +static void set_fg(struct s_tty *tty, const struct s_tty_colour *old, const struct s_tty_colour *new) +{ + if (compare_colour(old, new) == 0) { + return; + } + + char buf[8]; + + switch (new->c_mode) { + case TTY_COLOUR_NONE: + put_ansi_attrib(tty, ANSI_DEFAULTCOLOUR_FG); + break; + case TTY_COLOUR_16: + put_ansi_attrib(tty, ansi_colour16_fg[new->c_16.value]); + break; + case TTY_COLOUR_256: + put_ansi_attrib(tty, ANSI_256COLOUR_FG); + snprintf(buf, sizeof buf, "%u", new->c_256.value); + put_ansi_attrib(tty, buf); + break; + case TTY_COLOUR_TRUE: + put_ansi_attrib(tty, ANSI_TRUECOLOUR_FG); + snprintf(buf, sizeof buf, "%u", new->c_true.r); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.g); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.b); + put_ansi_attrib(tty, buf); + break; + default: + break; + } +} + +static void set_bg(struct s_tty *tty, const struct s_tty_colour *old, const struct s_tty_colour *new) +{ + if (compare_colour(old, new) == 0) { + return; + } + + char buf[8]; + + switch (new->c_mode) { + case TTY_COLOUR_NONE: + put_ansi_attrib(tty, ANSI_DEFAULTCOLOUR_BG); + break; + case TTY_COLOUR_16: + put_ansi_attrib(tty, ansi_colour16_bg[new->c_16.value]); + break; + case TTY_COLOUR_256: + put_ansi_attrib(tty, ANSI_256COLOUR_BG); + snprintf(buf, sizeof buf, "%u", new->c_256.value); + put_ansi_attrib(tty, buf); + break; + case TTY_COLOUR_TRUE: + put_ansi_attrib(tty, ANSI_TRUECOLOUR_BG); + snprintf(buf, sizeof buf, "%u", new->c_true.r); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.g); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.b); + put_ansi_attrib(tty, buf); + break; + default: + break; + } +} + +void s_tty_set_vmode(struct s_tty *tty, const struct s_tty_vmode *vmode) +{ + if (compare_vmode(&tty->t_vmode, vmode) == 0) { + return; + } + + tty->t_mcount = 0; + + fprintf(tty->t_out, "\033["); + + set_attrib(tty, tty->t_vmode.v_attrib, vmode->v_attrib); + set_fg(tty, &tty->t_vmode.v_fg, &vmode->v_fg); + set_bg(tty, &tty->t_vmode.v_bg, &vmode->v_bg); + + fprintf(tty->t_out, "m"); + + memcpy(&tty->t_vmode, vmode, sizeof *vmode); +} + +s_keycode s_tty_read_key(struct s_tty *tty) +{ + char c; + int v; + int fd = fileno(tty->t_in); + + while (1) { + v = read(fd, &c, 1); + if (v < 1) { + return S_KEY_EOF; + } + + if (c == '\r' || c == '\n') { + return S_KEY_RETURN; + } + + if (c == '\b' || c == 0x7F) { + return S_KEY_BACKSPACE; + } + + if (c >= 1 && c <= 26) { + return S_TTY_CTRL_KEY(c + 'a' - 1); + } + + if (c != 0x1b) { + return c; + } + + v = read(fd, &c, 1); + if (v < 1) { + return S_KEY_EOF; + } + + if (c != '[') { + continue; + } + + v = read(fd, &c, 1); + if (v < 1) { + return S_KEY_EOF; + } + + switch (c) { + case 'A': + return S_KEY_ARROW_UP; + case 'B': + return S_KEY_ARROW_DOWN; + case 'C': + return S_KEY_ARROW_RIGHT; + case 'D': + return S_KEY_ARROW_LEFT; + default: + continue; + } + } + + return c; +} diff --git a/frontend/sys/windows/line-ed/tty.c b/frontend/sys/windows/line-ed/tty.c new file mode 100644 index 0000000..a633e29 --- /dev/null +++ b/frontend/sys/windows/line-ed/tty.c @@ -0,0 +1,440 @@ +#include "../../../line-ed/tty.h" + +#include +#include +#include +#include + +#define ANSI_BOLD_ON "1" +#define ANSI_BOLD_OFF "22" +#define ANSI_ITALIC_ON "3" +#define ANSI_ITALIC_OFF "23" +#define ANSI_UNDERLINE_ON "4" +#define ANSI_UNDERLINE_OFF "24" + +#define ANSI_DEFAULTCOLOUR_FG "39" +#define ANSI_DEFAULTCOLOUR_BG "49" + +#define ANSI_256COLOUR_FG "38;5" +#define ANSI_256COLOUR_BG "48;5" + +#define ANSI_TRUECOLOUR_FG "38;2" +#define ANSI_TRUECOLOUR_BG "48;2" + +enum tty_flags { + TTY_INIT = 0x01u, + TTY_INTERACTIVE = 0x02u, +}; + +struct s_tty { + HANDLE t_in, t_out; + DWORD t_canon_mode_in, t_canon_mode_out; + enum s_key_code t_repeat_key; + unsigned int t_repeat_count; + enum tty_flags t_flags; + struct s_tty_vmode t_vmode; + unsigned char t_mcount; +}; + +static struct s_tty tty = {0}; + +static WORD ansi_colour16_fg[] = { + [TTY_COLOUR16_BLACK] = 0, + [TTY_COLOUR16_RED] = FOREGROUND_RED, + [TTY_COLOUR16_GREEN] = FOREGROUND_GREEN, + [TTY_COLOUR16_YELLOW] = FOREGROUND_RED | FOREGROUND_GREEN, + [TTY_COLOUR16_BLUE] = FOREGROUND_BLUE, + [TTY_COLOUR16_MAGENTA] = FOREGROUND_RED | FOREGROUND_BLUE, + [TTY_COLOUR16_CYAN] = FOREGROUND_GREEN | FOREGROUND_BLUE, + [TTY_COLOUR16_WHITE] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, + [TTY_COLOUR16_GREY] = FOREGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_RED] = FOREGROUND_RED | FOREGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_GREEN] = FOREGROUND_GREEN | FOREGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_YELLOW] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_BLUE] = FOREGROUND_BLUE | FOREGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_MAGENTA] = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_CYAN] = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_WHITE] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, +}; + +static WORD ansi_colour16_bg[] = { + [TTY_COLOUR16_BLACK] = 0, + [TTY_COLOUR16_RED] = BACKGROUND_RED, + [TTY_COLOUR16_GREEN] = BACKGROUND_GREEN, + [TTY_COLOUR16_YELLOW] = BACKGROUND_RED | BACKGROUND_GREEN, + [TTY_COLOUR16_BLUE] = BACKGROUND_BLUE, + [TTY_COLOUR16_MAGENTA] = BACKGROUND_RED | BACKGROUND_BLUE, + [TTY_COLOUR16_CYAN] = BACKGROUND_GREEN | BACKGROUND_BLUE, + [TTY_COLOUR16_WHITE] = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE, + [TTY_COLOUR16_GREY] = BACKGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_RED] = BACKGROUND_RED | BACKGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_GREEN] = BACKGROUND_GREEN | BACKGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_YELLOW] = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_BLUE] = BACKGROUND_BLUE | BACKGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_MAGENTA] = BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_CYAN] = BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY, + [TTY_COLOUR16_BRIGHT_WHITE] = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY, +}; + +static void init_tty(struct s_tty *tty, FILE *in, FILE *out) +{ + HANDLE in_handle = (HANDLE)_get_osfhandle(fileno(in)); + HANDLE out_handle = (HANDLE)_get_osfhandle(fileno(out)); + + tty->t_in = in_handle; + tty->t_out = out_handle; + tty->t_flags |= TTY_INIT; + + DWORD mode = 0; + if (GetConsoleMode(in_handle, &mode)) { + tty->t_flags |= TTY_INTERACTIVE; + tty->t_canon_mode_in = mode; + } + + if (GetConsoleMode(out_handle, &mode)) { + tty->t_canon_mode_out = mode; + } +} + +struct s_tty *s_get_tty(void) +{ + if (!(tty.t_flags & TTY_INIT)) { + init_tty(&tty, stdin, stdout); + } + + return &tty; +} + +bool s_tty_is_interactive(const struct s_tty *tty) +{ + return (tty->t_flags & TTY_INTERACTIVE) == TTY_INTERACTIVE; +} + +void s_tty_set_raw(struct s_tty *tty) +{ + DWORD mode = tty->t_canon_mode_in; + mode &= ~(ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE | ENABLE_WINDOW_INPUT); + SetConsoleMode(tty->t_in, mode); + + mode = tty->t_canon_mode_out; + mode &= ~(ENABLE_PROCESSED_OUTPUT); + SetConsoleMode(tty->t_out, mode); +} + +void s_tty_set_canon(struct s_tty *tty) +{ + SetConsoleMode(tty->t_in, tty->t_canon_mode_in); + SetConsoleMode(tty->t_out, tty->t_canon_mode_out); +} + +void s_tty_reset_vmode(struct s_tty *tty) +{ + if (tty->t_vmode.v_fg.c_mode == TTY_COLOUR_NONE + && tty->t_vmode.v_bg.c_mode == TTY_COLOUR_NONE + && tty->t_vmode.v_attrib == TTY_ATTRIB_NORMAL) { + return; + } + + WORD attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + SetConsoleTextAttribute(tty->t_out, attrib); + + memset(&tty->t_vmode, 0x0, sizeof tty->t_vmode); + + tty->t_vmode.v_fg.c_mode = TTY_COLOUR_NONE; + tty->t_vmode.v_bg.c_mode = TTY_COLOUR_NONE; + tty->t_vmode.v_attrib = TTY_ATTRIB_NORMAL; +} + +static int compare_colour( + const struct s_tty_colour *a, const struct s_tty_colour *b) +{ + if (a->c_mode != b->c_mode) { + return -1; + } + + switch (a->c_mode) { + case TTY_COLOUR_16: + if (a->c_16.value != b->c_16.value) { + return -1; + } + break; + case TTY_COLOUR_256: + if (a->c_256.value != b->c_256.value) { + return -1; + } + break; + case TTY_COLOUR_TRUE: + if (a->c_true.r != b->c_true.r) { + return -1; + } + + if (a->c_true.g != b->c_true.g) { + return -1; + } + + if (a->c_true.b != b->c_true.b) { + return -1; + } + break; + default: + break; + } + + return 0; +} + +static int compare_vmode(const struct s_tty_vmode *a, const struct s_tty_vmode *b) +{ + if (a->v_attrib != b->v_attrib) { + return -1; + } + + if (compare_colour(&a->v_fg, &b->v_fg) != 0) { + return -1; + } + + if (compare_colour(&a->v_bg, &b->v_bg) != 0) { + return -1; + } + + return 0; +} + +static void put_ansi_attrib(struct s_tty *tty, const char *s) +{ + if (tty->t_mcount > 0) { + fputs(";", tty->t_out); + } + + fputs(s, tty->t_out); + tty->t_mcount++; +} + +static void set_attrib(WORD *attrp, enum s_tty_attrib old, enum s_tty_attrib new) +{ + if (old == new) { + return; + } + + WORD attrib = *attrp; + + /* Bold ON */ + if (!(old & TTY_ATTRIB_BOLD) && new & TTY_ATTRIB_BOLD) { + attrib |= FOREGROUND_INTENSITY; + } + + /* Bold OFF */ + if (old & TTY_ATTRIB_BOLD && !(new & TTY_ATTRIB_BOLD)) { + attrib &= ~FOREGROUND_INTENSITY; + } + + /* Underline ON */ + if (!(old & TTY_ATTRIB_UNDERLINE) && new & TTY_ATTRIB_UNDERLINE) { + attrib |= COMMON_LVB_UNDERSCORE; + } + + /* Underline OFF */ + if (old & TTY_ATTRIB_UNDERLINE && !(new & TTY_ATTRIB_UNDERLINE)) { + attrib &= ~COMMON_LVB_UNDERSCORE; + } + + /* Italic ON */ + if (!(old & TTY_ATTRIB_ITALIC) && new & TTY_ATTRIB_ITALIC) { + /* not supported */ + } + + /* Italic OFF */ + if (old & TTY_ATTRIB_ITALIC && !(new & TTY_ATTRIB_ITALIC)) { + /* not supported */ + } + + *attrp = attrib; +} + +static void set_fg( + WORD *attrp, const struct s_tty_colour *old, + const struct s_tty_colour *new) +{ + if (compare_colour(old, new) == 0) { + return; + } + + WORD attrib = *attrp; + attrib + &= ~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE + | FOREGROUND_INTENSITY); + + switch (new->c_mode) { + case TTY_COLOUR_16: + attrib |= ansi_colour16_fg[new->c_16.value]; + break; + default: + attrib |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + break; + } + + *attrp = attrib; +} + +static void set_bg( + WORD *attrp, const struct s_tty_colour *old, + const struct s_tty_colour *new) +{ + if (compare_colour(old, new) == 0) { + return; + } + + WORD attrib = *attrp; + attrib + &= ~(BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE + | BACKGROUND_INTENSITY); + + switch (new->c_mode) { + case TTY_COLOUR_16: + attrib |= ansi_colour16_bg[new->c_16.value]; + break; + default: + attrib |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; + break; + } +} + +void s_tty_set_vmode(struct s_tty *tty, const struct s_tty_vmode *vmode) +{ + if (compare_vmode(&tty->t_vmode, vmode) == 0) { + return; + } + + tty->t_mcount = 0; + + WORD attrib = 0; + + set_attrib(&attrib, tty->t_vmode.v_attrib, vmode->v_attrib); + set_fg(&attrib, &tty->t_vmode.v_fg, &vmode->v_fg); + set_bg(&attrib, &tty->t_vmode.v_bg, &vmode->v_bg); + + SetConsoleTextAttribute(tty->t_out, attrib); + + memcpy(&tty->t_vmode, vmode, sizeof *vmode); +} + +s_keycode s_tty_read_key(struct s_tty *tty) +{ + if (tty->t_repeat_count > 0) { + tty->t_repeat_count--; + return tty->t_repeat_key; + } + + INPUT_RECORD d; + HANDLE in = tty->t_in; + BOOL status = TRUE; + CONSOLE_READCONSOLE_CONTROL ctrl = {0}; + DWORD nr_read = 0; + + while (1) { + status = ReadConsoleInputA(in, &d, 1, &nr_read); + if (status == FALSE) { + return S_KEY_EOF; + } + + if (d.EventType != KEY_EVENT) { + continue; + } + + if (!d.Event.KeyEvent.bKeyDown) { + continue; + } + + s_keycode key = 0; + + if (d.Event.KeyEvent.uChar.UnicodeChar == 0) { + continue; + } + + switch (d.Event.KeyEvent.wVirtualKeyCode) { + case VK_CONTROL: + case VK_RCONTROL: + case VK_SHIFT: + case VK_RSHIFT: + case VK_MENU: + continue; + case VK_UP: + key = S_KEY_ARROW_UP; + break; + case VK_DOWN: + key = S_KEY_ARROW_DOWN; + break; + case VK_LEFT: + key = S_KEY_ARROW_LEFT; + break; + case VK_RIGHT: + key = S_KEY_ARROW_RIGHT; + break; + case VK_BACK: + key = S_KEY_BACKSPACE; + break; + case VK_RETURN: + key = S_KEY_RETURN; + break; + default: + key = d.Event.KeyEvent.uChar.AsciiChar; + break; + } + + if (d.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) { + key = S_TTY_CTRL_KEY('a' + key - 1); + } + + if (d.Event.KeyEvent.wRepeatCount > 1) { + tty->t_repeat_count = d.Event.KeyEvent.wRepeatCount - 1; + tty->t_repeat_key = key; + } + + return key; + } + + return S_KEY_EOF; +} + +void s_tty_move_cursor_x( + struct s_tty* tty, enum s_tty_position_base base, int pos) +{ + CONSOLE_SCREEN_BUFFER_INFO console = {0}; + GetConsoleScreenBufferInfo(tty->t_out, &console); + COORD cursor_pos; + cursor_pos.Y = console.dwCursorPosition.Y; + + switch (base) { + case TTY_POS_CURSOR: + cursor_pos.X = console.dwCursorPosition.X + pos; + break; + case TTY_POS_START: + cursor_pos.X = pos; + break; + default: + break; + } + + SetConsoleCursorPosition(tty->t_out, cursor_pos); +} + +void s_tty_move_cursor_y(struct s_tty *tty, enum s_tty_position_base base, int pos) +{ + CONSOLE_SCREEN_BUFFER_INFO console = {0}; + GetConsoleScreenBufferInfo(tty->t_out, &console); + COORD cursor_pos; + cursor_pos.X = console.dwCursorPosition.X; + + switch (base) { + case TTY_POS_CURSOR: + cursor_pos.Y = console.dwCursorPosition.Y + pos; + break; + case TTY_POS_START: + cursor_pos.Y = pos; + break; + default: + break; + } + + SetConsoleCursorPosition(tty->t_out, cursor_pos); +} \ No newline at end of file