diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index 192e05fa..d6140e78 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -87,8 +87,30 @@ static void simpleline(JanetBuffer *buffer) { } } -/* Windows */ -#if defined(JANET_WINDOWS) || defined(JANET_SIMPLE_GETLINE) +/* State */ + +#ifndef JANET_SIMPLE_GETLINE +/* static state */ +#define JANET_LINE_MAX 1024 +#define JANET_MATCH_MAX 256 +#define JANET_HISTORY_MAX 100 +static JANET_THREAD_LOCAL int gbl_israwmode = 0; +static JANET_THREAD_LOCAL const char *gbl_prompt = "> "; +static JANET_THREAD_LOCAL int gbl_plen = 2; +static JANET_THREAD_LOCAL char gbl_buf[JANET_LINE_MAX]; +static JANET_THREAD_LOCAL int gbl_len = 0; +static JANET_THREAD_LOCAL int gbl_pos = 0; +static JANET_THREAD_LOCAL int gbl_cols = 80; +static JANET_THREAD_LOCAL char *gbl_history[JANET_HISTORY_MAX]; +static JANET_THREAD_LOCAL int gbl_history_count = 0; +static JANET_THREAD_LOCAL int gbl_historyi = 0; +static JANET_THREAD_LOCAL JanetByteView gbl_matches[JANET_MATCH_MAX]; +static JANET_THREAD_LOCAL int gbl_match_count = 0; +static JANET_THREAD_LOCAL int gbl_lines_below = 0; +#endif + +/* Fallback */ +#if defined(JANET_SIMPLE_GETLINE) void janet_line_init() { ; @@ -105,6 +127,79 @@ void janet_line_get(const char *p, JanetBuffer *buffer) { simpleline(buffer); } +/* Rich implementation */ +#else + +/* Windows */ +#ifdef _WIN32 + +#include +#include +#include + +static void setup_console_output(void) { + /* Enable color console on windows 10 console and utf8 output and other processing */ + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD dwMode = 0; + GetConsoleMode(hOut, &dwMode); + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(hOut, dwMode); + SetConsoleOutputCP(65001); +} + +/* Ansi terminal raw mode */ +static int rawmode(void) { + if (gbl_israwmode) return 0; + HANDLE hOut = GetStdHandle(STD_INPUT_HANDLE); + DWORD dwMode = 0; + GetConsoleMode(hOut, &dwMode); + dwMode &= ~ENABLE_LINE_INPUT; + dwMode &= ~ENABLE_INSERT_MODE; + dwMode &= ~ENABLE_ECHO_INPUT; + dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT; + dwMode &= ~ENABLE_PROCESSED_INPUT; + SetConsoleMode(hOut, dwMode); + gbl_israwmode = 1; + return 0; +} + +/* Disable raw mode */ +static void norawmode(void) { + if (!gbl_israwmode) return; + HANDLE hOut = GetStdHandle(STD_INPUT_HANDLE); + DWORD dwMode = 0; + GetConsoleMode(hOut, &dwMode); + dwMode |= ENABLE_LINE_INPUT; + dwMode |= ENABLE_INSERT_MODE; + dwMode |= ENABLE_ECHO_INPUT; + dwMode &= ~ENABLE_VIRTUAL_TERMINAL_INPUT; + dwMode |= ENABLE_PROCESSED_INPUT; + SetConsoleMode(hOut, dwMode); + gbl_israwmode = 0; +} + +static long write_console(const char *bytes, size_t n) { + DWORD nwritten = 0; + BOOL result = WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), bytes, (DWORD) n, &nwritten, NULL); + if (!result) return -1; /* error */ + return (long)nwritten; +} + +static long read_console(char *into, size_t n) { + DWORD numread; + BOOL result = ReadConsole(GetStdHandle(STD_INPUT_HANDLE), into, (DWORD) n, &numread, NULL); + if (!result) return -1; /* error */ + return (long)numread; +} + +static int check_simpleline(JanetBuffer *buffer) { + if (rawmode()) { + simpleline(buffer); + return 1; + } + return 0; +} + /* Posix */ #else @@ -125,24 +220,7 @@ https://github.com/antirez/linenoise/blob/master/linenoise.c #include #include -/* static state */ -#define JANET_LINE_MAX 1024 -#define JANET_MATCH_MAX 256 -#define JANET_HISTORY_MAX 100 -static JANET_THREAD_LOCAL int gbl_israwmode = 0; -static JANET_THREAD_LOCAL const char *gbl_prompt = "> "; -static JANET_THREAD_LOCAL int gbl_plen = 2; -static JANET_THREAD_LOCAL char gbl_buf[JANET_LINE_MAX]; -static JANET_THREAD_LOCAL int gbl_len = 0; -static JANET_THREAD_LOCAL int gbl_pos = 0; -static JANET_THREAD_LOCAL int gbl_cols = 80; -static JANET_THREAD_LOCAL char *gbl_history[JANET_HISTORY_MAX]; -static JANET_THREAD_LOCAL int gbl_history_count = 0; -static JANET_THREAD_LOCAL int gbl_historyi = 0; static JANET_THREAD_LOCAL struct termios gbl_termios_start; -static JANET_THREAD_LOCAL JanetByteView gbl_matches[JANET_MATCH_MAX]; -static JANET_THREAD_LOCAL int gbl_match_count = 0; -static JANET_THREAD_LOCAL int gbl_lines_below = 0; /* Unsupported terminal list from linenoise */ static const char *badterms[] = { @@ -152,15 +230,6 @@ static const char *badterms[] = { NULL }; -static char *sdup(const char *s) { - size_t len = strlen(s) + 1; - char *mem = janet_malloc(len); - if (!mem) { - return NULL; - } - return memcpy(mem, s, len); -} - /* Ansi terminal raw mode */ static int rawmode(void) { struct termios t; @@ -186,13 +255,53 @@ static void norawmode(void) { gbl_israwmode = 0; } +static int checktermsupport() { + const char *t = getenv("TERM"); + int i; + if (!t) return 1; + for (i = 0; badterms[i]; i++) + if (!strcmp(t, badterms[i])) return 0; + return 1; +} + +static long write_console(char *bytes, size_t n) { + return write(STDOUT_FILENO, bytes, n); +} + +static long read_console(char *into, size_t n) { + return read(STDIN_FILENO, into, n); +} + +static int check_simpleline(JanetBuffer *buffer) { + if (!isatty(STDIN_FILENO) || !checktermsupport()) { + simpleline(buffer); + return 1; + } + if (rawmode()) { + simpleline(buffer); + return 1; + } + return 0; +} + +#endif + +static char *sdup(const char *s) { + size_t len = strlen(s) + 1; + char *mem = janet_malloc(len); + if (!mem) { + return NULL; + } + return memcpy(mem, s, len); +} + static int curpos(void) { char buf[32]; int cols, rows; unsigned int i = 0; - if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) return -1; + if (write_console("\x1b[6n", 4) != 4) return -1; while (i < sizeof(buf) - 1) { - if (read(STDIN_FILENO, buf + i, 1) != 1) break; + if (read_console(buf + i, 1) != 1) break; if (buf[i] == 'R') break; i++; } @@ -203,18 +312,23 @@ static int curpos(void) { } static int getcols(void) { +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); + return (int)(csbi.srWindow.Right - csbi.srWindow.Left + 1); +#else struct winsize ws; if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { int start, cols; start = curpos(); if (start == -1) goto failed; - if (write(STDOUT_FILENO, "\x1b[999C", 6) != 6) goto failed; + if (write_console("\x1b[999C", 6) != 6) goto failed; cols = curpos(); if (cols == -1) goto failed; if (cols > start) { char seq[32]; snprintf(seq, 32, "\x1b[%dD", cols - start); - if (write(STDOUT_FILENO, seq, strlen(seq)) == -1) { + if (write_console(seq, strlen(seq)) == -1) { exit(1); } } @@ -224,10 +338,11 @@ static int getcols(void) { } failed: return 80; +#endif } static void clear(void) { - if (write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7) <= 0) { + if (write_console("\x1b[H\x1b[2J", 7) <= 0) { exit(1); } } @@ -259,7 +374,7 @@ static void refresh(void) { /* Move cursor to original position. */ snprintf(seq, 64, "\r\x1b[%dC", (int)(_pos + gbl_plen)); janet_buffer_push_cstring(&b, seq); - if (write(STDOUT_FILENO, b.data, b.count) == -1) { + if (write_console(b.data, b.count) == -1) { exit(1); } janet_buffer_deinit(&b); @@ -285,7 +400,7 @@ static int insert(char c, int draw) { if (gbl_plen + gbl_len < gbl_cols) { /* Avoid a full update of the line in the * trivial case. */ - if (write(STDOUT_FILENO, &c, 1) == -1) return -1; + if (write_console(&c, 1) == -1) return -1; } else { refresh(); } @@ -312,7 +427,7 @@ static void historymove(int delta) { gbl_historyi = gbl_history_count - 1; } strncpy(gbl_buf, gbl_history[gbl_historyi], JANET_LINE_MAX - 1); - gbl_pos = gbl_len = strlen(gbl_buf); + gbl_pos = gbl_len = (int) strlen(gbl_buf); gbl_buf[gbl_len] = '\0'; refresh(); @@ -527,6 +642,7 @@ static void check_specials(JanetByteView src) { check_cmatch(src, "unquote"); check_cmatch(src, "var"); check_cmatch(src, "while"); + check_cmatch(src, "upscope"); } static void resolve_format(JanetTable *entry) { @@ -740,14 +856,14 @@ static int line() { addhistory(); - if (write(STDOUT_FILENO, gbl_prompt, gbl_plen) == -1) return -1; + if (write_console(gbl_prompt, gbl_plen) == -1) return -1; for (;;) { char c; char seq[3]; int rc; do { - rc = read(STDIN_FILENO, &c, 1); + rc = read_console(&c, 1); } while (rc < 0 && errno == EINTR); if (rc <= 0) return -1; @@ -764,8 +880,13 @@ static int line() { kleft(); break; case 3: /* ctrl-c */ + clearlines(); norawmode(); +#ifdef _WIN32 + ExitProcess(1); +#else kill(getpid(), SIGINT); +#endif /* fallthrough */ case 17: /* ctrl-q */ gbl_cancel_current_repl_form = 1; @@ -826,23 +947,25 @@ static int line() { case 23: /* ctrl-w */ kbackspacew(); break; +#ifndef _WIN32 case 26: /* ctrl-z */ norawmode(); kill(getpid(), SIGSTOP); rawmode(); refresh(); break; +#endif case 27: /* escape sequence */ /* Read the next two bytes representing the escape sequence. * Use two calls to handle slow terminals returning the two * chars at different times. */ - if (read(STDIN_FILENO, seq, 1) == -1) break; + if (read_console(seq, 1) == -1) break; /* Esc[ = Control Sequence Introducer (CSI) */ if (seq[0] == '[') { - if (read(STDIN_FILENO, seq + 1, 1) == -1) break; + if (read_console(seq + 1, 1) == -1) break; if (seq[1] >= '0' && seq[1] <= '9') { /* Extended escape, read additional byte. */ - if (read(STDIN_FILENO, seq + 2, 1) == -1) break; + if (read_console(seq + 2, 1) == -1) break; if (seq[2] == '~') { switch (seq[1]) { case '1': /* Home */ @@ -861,7 +984,7 @@ static int line() { } } } else if (seq[0] == 'O') { - if (read(STDIN_FILENO, seq + 1, 1) == -1) break; + if (read_console(seq + 1, 1) == -1) break; switch (seq[1]) { default: break; @@ -944,28 +1067,12 @@ void janet_line_deinit() { gbl_historyi = 0; } -static int checktermsupport() { - const char *t = getenv("TERM"); - int i; - if (!t) return 1; - for (i = 0; badterms[i]; i++) - if (!strcmp(t, badterms[i])) return 0; - return 1; -} - void janet_line_get(const char *p, JanetBuffer *buffer) { gbl_prompt = p; buffer->count = 0; gbl_historyi = 0; + if (check_simpleline(buffer)) return; FILE *out = janet_dynfile("err", stderr); - if (!isatty(STDIN_FILENO) || !checktermsupport()) { - simpleline(buffer); - return; - } - if (rawmode()) { - simpleline(buffer); - return; - } if (line()) { norawmode(); fputc('\n', out); @@ -981,6 +1088,13 @@ void janet_line_get(const char *p, JanetBuffer *buffer) { replacehistory(); } +static void clear_at_exit(void) { + if (!gbl_israwmode) { + clearlines(); + norawmode(); + } +} + #endif /* @@ -993,18 +1107,11 @@ int main(int argc, char **argv) { JanetTable *env; #ifdef _WIN32 - /* Enable color console on windows 10 console and utf8 output. */ - HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD dwMode = 0; - GetConsoleMode(hOut, &dwMode); - dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - SetConsoleMode(hOut, dwMode); - SetConsoleOutputCP(65001); + setup_console_output(); #endif -#if !defined(JANET_WINDOWS) && !defined(JANET_SIMPLE_GETLINE) - /* Try and not leave the terminal in a bad state */ - atexit(norawmode); +#if !defined(JANET_SIMPLE_GETLINE) + atexit(clear_at_exit); #endif #if defined(JANET_PRF) diff --git a/tools/format.sh b/tools/format.sh old mode 100755 new mode 100644