Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925#include <compat.h> // IWYU pragma: keep
#include <stdbool.h>#include <errno.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>
#include <uv.h>
#ifdef _WIN32#include <io.h>#define WIN32_LEAN_AND_MEAN#include <windows.h>#define ANT_STDIN_FD 0#define ANT_STDOUT_FD 1#define ANT_STDERR_FD 2#else#include <signal.h>#include <sys/ioctl.h>#include <termios.h>#include <unistd.h>#define ANT_STDIN_FD STDIN_FILENO#define ANT_STDOUT_FD STDOUT_FILENO#define ANT_STDERR_FD STDERR_FILENO#endif
#include "ant.h"#include "cfunc.h"#include "gc/roots.h"#include "descriptors.h"#include "errors.h"#include "output.h"#include "internal.h"#include "sandbox/sandbox.h"#include "tty_ctrl.h"#include "silver/call.h"
#include "modules/stream.h"#include "modules/buffer.h"#include "modules/events.h"#include "modules/tty.h"
static uint16_t g_sandbox_tty_rows = 24;static uint16_t g_sandbox_tty_cols = 80;
static bool g_sandbox_terminal_enabled = false;static uint32_t g_sandbox_terminal_capabilities = 0;
typedef struct tty_read_stream_state { ant_t *js; ant_value_t stream_obj; uv_tty_t tty; int fd; bool initialized; bool reading; bool closing;} tty_read_stream_state_t;
static ant_value_t invoke_callback_if_needed(ant_t *js, ant_value_t cb, ant_value_t arg) { if (!is_callable(cb)) return js_mkundef(); ant_value_t cb_args[1] = { arg }; return sv_vm_call(js->vm, js, cb, js_mkundef(), cb_args, 1, NULL, js_mkundef());}
static bool parse_fd(ant_value_t value, int *fd_out) { int fd = 0; if (!tty_ctrl_parse_int_value(value, &fd)) return false; if (fd < 0) return false; *fd_out = fd; return true;}
static bool is_tty_fd(int fd) { if (fd < 0) return false; if (g_sandbox_terminal_enabled) { if (fd == ANT_STDOUT_FD) return (g_sandbox_terminal_capabilities & ANT_SANDBOX_CAP_STDOUT_TTY) != 0; if (fd == ANT_STDERR_FD) return (g_sandbox_terminal_capabilities & ANT_SANDBOX_CAP_STDERR_TTY) != 0; } return uv_guess_handle(fd) == UV_TTY;}
static int stream_fd_from_this(ant_t *js, int fallback_fd) { ant_value_t this_obj = js_getthis(js); if (!is_special_object(this_obj)) return fallback_fd;
ant_value_t fd_val = js_get(js, this_obj, "fd"); int fd = 0; if (!parse_fd(fd_val, &fd)) return fallback_fd; return fd;}
static tty_read_stream_state_t *tty_read_stream_state_from_obj(ant_value_t stream_obj) { return (tty_read_stream_state_t *)stream_get_attached_state(stream_obj);}
static ant_value_t make_stream_error(ant_t *js, const char *op, int fd, int uv_code) { return Ant_Error_CreateFormatted( js, JS_ERR_GENERIC, "tty stream %s failed for fd %d%s%s", op, fd, uv_code ? ": " : "", uv_code ? uv_strerror(uv_code) : "" );}
static void tty_read_stream_emit_error(tty_read_stream_state_t *state, const char *op, int uv_code) { ant_value_t err = 0; if (!state || !state->js || !is_object_type(state->stream_obj)) return; err = make_stream_error(state->js, op, state->fd, uv_code); GC_ROOT_SAVE(root_mark, state->js); GC_ROOT_PIN(state->js, err); eventemitter_emit_args(state->js, state->stream_obj, "error", &err, 1); GC_ROOT_RESTORE(state->js, root_mark);}
static void tty_read_stream_push_chunk(tty_read_stream_state_t *state, const char *data, size_t len) { ArrayBufferData *ab = NULL; ant_value_t chunk = 0;
if (!state || !state->js || !data || len == 0) return; ab = create_array_buffer_data(state->js, len); if (ab) memcpy(ab->data, data, len); chunk = ab ? create_typed_array(state->js, TYPED_ARRAY_UINT8, ab, 0, len, state->js->builtins.buffer_proto) : js_mkstr(state->js, data, len); (void)stream_readable_push(state->js, state->stream_obj, chunk, js_mkundef());}
static void tty_read_stream_alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { buf->base = malloc(suggested_size);#ifdef _WIN32 buf->len = (ULONG)suggested_size;#else buf->len = suggested_size;#endif}
static void tty_read_stream_stop(tty_read_stream_state_t *state) { if (!state || !state->initialized || !state->reading) return; uv_read_stop((uv_stream_t *)&state->tty); state->reading = false;}
static void tty_read_stream_close_cb(uv_handle_t *handle) { tty_read_stream_state_t *state = (tty_read_stream_state_t *)handle->data; free(state);}
static void tty_read_stream_finalize(ant_t *js, ant_value_t stream_obj, void *data) { tty_read_stream_state_t *state = (tty_read_stream_state_t *)data;
if (!state) return; tty_read_stream_stop(state);
if (state->initialized && !state->closing) { state->closing = true; uv_close((uv_handle_t *)&state->tty, tty_read_stream_close_cb); return; }
if (!state->initialized) free(state);}
static void tty_read_stream_on_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { tty_read_stream_state_t *state = (tty_read_stream_state_t *)stream->data;
if (!state || !state->js) goto cleanup;
if (nread > 0) { tty_read_stream_push_chunk(state, buf->base, (size_t)nread); goto cleanup; }
if (nread == UV_EOF) { tty_read_stream_stop(state); (void)stream_readable_push(state->js, state->stream_obj, js_mknull(), js_mkundef()); goto cleanup; }
if (nread < 0) { tty_read_stream_stop(state); tty_read_stream_emit_error(state, "read", (int)nread); }
cleanup: if (buf->base) free(buf->base);}
static ant_value_t tty_readstream__read(ant_params_t) { ant_value_t stream_obj = js_getthis(js); tty_read_stream_state_t *state = tty_read_stream_state_from_obj(stream_obj); int rc = 0;
if (!state) return js_mkundef(); if (state->closing || js_truthy(js, js_get(js, stream_obj, "destroyed"))) return js_mkundef();
if (!state->initialized) { rc = uv_tty_init(js->loop, &state->tty, state->fd, 0); if (rc != 0) { tty_read_stream_emit_error(state, "open", rc); return js_mkundef(); } state->tty.data = state; state->initialized = true; }
if (state->reading) return js_mkundef(); rc = uv_read_start((uv_stream_t *)&state->tty, tty_read_stream_alloc_buffer, tty_read_stream_on_read); if (rc != 0) { tty_read_stream_emit_error(state, "read", rc); return js_mkundef(); }
state->reading = true; return js_mkundef();}
static ant_value_t tty_readstream__destroy(ant_params_t) { ant_value_t stream_obj = js_getthis(js);
tty_read_stream_state_t *state = tty_read_stream_state_from_obj(stream_obj); ant_value_t cb = nargs > 1 ? args[1] : js_mkundef(); if (!state) return invoke_callback_if_needed(js, cb, js_mknull());
tty_read_stream_stop(state); stream_clear_attached_state(stream_obj);
if (state->initialized && !state->closing) { state->closing = true; uv_close((uv_handle_t *)&state->tty, tty_read_stream_close_cb); } else if (!state->initialized) free(state);
return invoke_callback_if_needed(js, cb, js_mknull());}
static void get_tty_size(int fd, int *rows, int *cols) { int out_rows = 24; int out_cols = 80;
if (g_sandbox_terminal_enabled && (fd == ANT_STDOUT_FD || fd == ANT_STDERR_FD)) { if (rows) *rows = g_sandbox_tty_rows; if (cols) *cols = g_sandbox_tty_cols; return; }
#ifdef _WIN32 HANDLE handle = INVALID_HANDLE_VALUE; if (fd == ANT_STDOUT_FD) { handle = GetStdHandle(STD_OUTPUT_HANDLE); } else if (fd == ANT_STDERR_FD) { handle = GetStdHandle(STD_ERROR_HANDLE); } else { intptr_t os_handle = _get_osfhandle(fd); if (os_handle != -1) handle = (HANDLE)os_handle; }
if (handle != INVALID_HANDLE_VALUE) { CONSOLE_SCREEN_BUFFER_INFO csbi; if (GetConsoleScreenBufferInfo(handle, &csbi)) { int width = csbi.srWindow.Right - csbi.srWindow.Left + 1; int height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; if (height > 0) out_rows = height; if (width > 0) out_cols = width; } }#else struct winsize ws; if (ioctl(fd, TIOCGWINSZ, &ws) == 0) { if (ws.ws_row > 0) out_rows = (int)ws.ws_row; if (ws.ws_col > 0) out_cols = (int)ws.ws_col; }#endif
if (rows) *rows = out_rows; if (cols) *cols = out_cols;}
static bool str_case_eq(const char *a, const char *b) { if (!a || !b) return false; while (*a && *b) { int ca = tolower((unsigned char)*a); int cb = tolower((unsigned char)*b); if (ca != cb) return false; a++; b++; } return *a == '\0' && *b == '\0';}
static bool str_case_contains(const char *haystack, const char *needle) { if (!haystack || !needle) return false; size_t needle_len = strlen(needle); if (needle_len == 0) return true;
size_t hay_len = strlen(haystack); if (needle_len > hay_len) return false;
for (size_t i = 0; i + needle_len <= hay_len; i++) { bool match = true; for (size_t j = 0; j < needle_len; j++) { int ca = tolower((unsigned char)haystack[i + j]); int cb = tolower((unsigned char)needle[j]); if (ca != cb) { match = false; break; } } if (match) return true; } return false;}
static bool read_env_object_value( ant_t *js, ant_value_t env_obj, const char *key, char *buf, size_t buf_len) { if (!is_special_object(env_obj) || !key || !buf || buf_len == 0) return false;
ant_value_t value = js_get(js, env_obj, key); if (vtype(value) == kTypeUndefined || vtype(value) == kTypeNull) return false;
ant_value_t str_val = js_tostring_val(js, value); size_t len = 0; char *str = js_getstr(js, str_val, &len); if (!str) return false;
if (len >= buf_len) len = buf_len - 1; memcpy(buf, str, len); buf[len] = '\0'; return true;}
static const char *get_env_value( ant_t *js, ant_value_t env_obj, const char *key, char *buf, size_t buf_len) { if (read_env_object_value(js, env_obj, key, buf, buf_len)) return buf; return getenv(key);}
static int force_color_depth(const char *force_color) { if (!force_color) return 0; if (*force_color == '\0') return 4; if (strcmp(force_color, "0") == 0) return 1; if (strcmp(force_color, "1") == 0) return 4; if (strcmp(force_color, "2") == 0) return 8; if (strcmp(force_color, "3") == 0) return 24; return 4;}
static int detect_color_depth(ant_t *js, int fd, ant_value_t env_obj) { if (g_sandbox_terminal_enabled && (fd == ANT_STDOUT_FD || fd == ANT_STDERR_FD)) { if (g_sandbox_terminal_capabilities & ANT_SANDBOX_CAP_COLOR_STRIP) return 1; if (g_sandbox_terminal_capabilities & ANT_SANDBOX_CAP_COLOR_FORCE) return 24; }
char scratch[128]; const char *force_color = get_env_value(js, env_obj, "FORCE_COLOR", scratch, sizeof(scratch)); int forced = force_color_depth(force_color); if (forced > 0) return forced;
const char *no_color = get_env_value(js, env_obj, "NO_COLOR", scratch, sizeof(scratch)); if (no_color && *no_color) return 1; if (!is_tty_fd(fd)) return 1;
const char *colorterm = get_env_value(js, env_obj, "COLORTERM", scratch, sizeof(scratch)); if (colorterm) { if (str_case_contains(colorterm, "truecolor") || str_case_contains(colorterm, "24bit")) return 24; }
#ifdef _WIN32 return 24;#else const char *term = get_env_value(js, env_obj, "TERM", scratch, sizeof(scratch)); if (!term) return 4; if (str_case_eq(term, "dumb")) return 1; if (str_case_contains(term, "256color")) return 8; if (str_case_contains(term, "color") || str_case_contains(term, "xterm") || str_case_contains(term, "screen") || str_case_contains(term, "ansi") || str_case_contains(term, "linux") || str_case_contains(term, "cygwin") || str_case_contains(term, "vt100") ) return 4; return 4;#endif}
static int palette_size_for_depth(int depth) { switch (depth) { case 1: return 2; case 4: return 16; case 8: return 256; case 24: return 16777216; default: return 2; }}
static ant_value_t maybe_callback_or_throw( ant_t *js, ant_value_t this_obj, ant_value_t cb, bool ok, const char *op, int fd) { if (is_callable(cb)) { ant_value_t err = ok ? js_mknull() : make_stream_error(js, op, fd, 0); ant_value_t result = invoke_callback_if_needed(js, cb, err); if (is_err(result)) return result; return this_obj; }
if (!ok) return js_throw(js, make_stream_error(js, op, fd, 0));
return this_obj;}
#ifndef _WIN32static struct { int fd; bool active; struct termios saved;} raw_state = { .fd = -1, .active = false };
static int tty_tcsetattr_no_sigtou(int fd, int optional_actions, const struct termios *tio) {#ifdef SIGTTOU sigset_t block_set; sigset_t prev_set; if (sigemptyset(&block_set) == 0 && sigaddset(&block_set, SIGTTOU) == 0 && sigprocmask(SIG_BLOCK, &block_set, &prev_set) == 0) { int rc = tcsetattr(fd, optional_actions, tio); int saved_errno = errno; sigprocmask(SIG_SETMASK, &prev_set, NULL); errno = saved_errno; return rc; }#endif return tcsetattr(fd, optional_actions, tio);}#endif
bool tty_set_raw_mode(int fd, bool enable) {#ifdef _WIN32 intptr_t os_handle = _get_osfhandle(fd); if (os_handle == -1) return false; HANDLE handle = (HANDLE)os_handle;
DWORD mode = 0; if (!GetConsoleMode(handle, &mode)) return false;
if (enable) { mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); } else { mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; }
return SetConsoleMode(handle, mode) != 0;#else if (!is_tty_fd(fd)) return false;
if (enable) { if (raw_state.active && raw_state.fd == fd) return true;
struct termios saved; if (tcgetattr(fd, &saved) == -1) return false;
struct termios raw = saved; raw.c_lflag &= ~(ICANON | ECHO | ISIG); raw.c_iflag &= ~(IXON | ICRNL); raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0;#ifdef VDISCARD raw.c_cc[VDISCARD] = _POSIX_VDISABLE;#endif#ifdef VLNEXT raw.c_cc[VLNEXT] = _POSIX_VDISABLE;#endif if (tty_tcsetattr_no_sigtou(fd, TCSANOW, &raw) == -1) return false; raw_state.fd = fd; raw_state.saved = saved; raw_state.active = true; return true; }
if (!(raw_state.active && raw_state.fd == fd)) return true; if (tty_tcsetattr_no_sigtou(fd, TCSANOW, &raw_state.saved) == -1) return false; raw_state.fd = -1; raw_state.active = false; return true;#endif}
static ant_value_t get_process_stream(ant_t *js, const char *name) { ant_value_t process_obj = js_get(js, js_glob(js), "process"); if (!is_special_object(process_obj)) return js_mkundef();
ant_value_t stream = js_get(js, process_obj, name); if (!is_special_object(stream)) return js_mkundef(); return stream;}
static void ensure_stream_common_props(ant_t *js, ant_value_t stream, int fd) { if (!is_special_object(stream)) return; js_set(js, stream, "fd", js_mknum((double)fd)); js_set(js, stream, "isTTY", js_bool(is_tty_fd(fd)));}
static ant_value_t tty_isatty(ant_params_t) { if (nargs < 1) return js_false;
int fd = 0; if (!parse_fd(args[0], &fd)) return js_false; return js_bool(is_tty_fd(fd));}
static ant_value_t tty_stream_write(ant_params_t) { if (nargs < 1) return js_false;
size_t len = 0; const char *data = NULL; const uint8_t *bytes = NULL;
if (buffer_source_get_bytes(js, args[0], &bytes, &len)) data = (const char *)bytes; else data = js_getstr(js, args[0], &len);
if (!data) return js_false;
ant_value_t cb = js_mkundef(); if (nargs > 1 && is_callable(args[1])) cb = args[1]; if (nargs > 2 && is_callable(args[2])) cb = args[2];
int fd = stream_fd_from_this(js, ANT_STDOUT_FD); bool ok = false;
if (ant_output_has_writer() && (fd == ANT_STDOUT_FD || fd == ANT_STDERR_FD)) { ant_output_stream_t *out = ant_output_stream(fd == ANT_STDERR_FD ? stderr : stdout); ant_output_stream_begin(out); ok = ant_output_stream_append(out, data, len) && ant_output_stream_flush(out); } else ok = tty_ctrl_write_fd(fd, data, len);
if (is_callable(cb)) { ant_value_t err = ok ? js_mknull() : make_stream_error(js, "write", fd, 0); ant_value_t result = invoke_callback_if_needed(js, cb, err); if (is_err(result)) return result; }
if (!ok) return js_false; return js_true;}
static ant_value_t tty_write_stream_rows_getter(ant_params_t) { int fd = stream_fd_from_this(js, ANT_STDOUT_FD); if (!is_tty_fd(fd)) return js_mkundef();
int rows = 0; int cols = 0; get_tty_size(fd, &rows, &cols); return js_mknum((double)rows);}
static ant_value_t tty_write_stream_columns_getter(ant_params_t) { int fd = stream_fd_from_this(js, ANT_STDOUT_FD); if (!is_tty_fd(fd)) return js_mkundef();
int rows = 0; int cols = 0; get_tty_size(fd, &rows, &cols); return js_mknum((double)cols);}
static ant_value_t tty_write_stream_get_window_size(ant_params_t) { int fd = stream_fd_from_this(js, ANT_STDOUT_FD); int rows = 0; int cols = 0; get_tty_size(fd, &rows, &cols);
ant_value_t arr = js_mkarr(js); js_arr_push(js, arr, js_mknum((double)cols)); js_arr_push(js, arr, js_mknum((double)rows)); return arr;}
static ant_value_t tty_write_stream_clear_line(ant_params_t) { ant_value_t this_obj = js_getthis(js);
int dir = 0; if (!tty_ctrl_parse_clear_line_dir(args, nargs, 0, &dir)) { return js_mkerr_typed(js, JS_ERR_TYPE, "clearLine(dir) requires a numeric dir"); }
ant_value_t cb = js_mkundef(); if (nargs > 1 && is_callable(args[1])) cb = args[1];
int fd = stream_fd_from_this(js, ANT_STDOUT_FD); size_t seq_len = 0; const char *seq = tty_ctrl_clear_line_seq(dir, &seq_len); bool ok = tty_ctrl_write_fd(fd, seq, seq_len); return maybe_callback_or_throw(js, this_obj, cb, ok, "clearLine", fd);}
static ant_value_t tty_write_stream_clear_screen_down(ant_params_t) { ant_value_t this_obj = js_getthis(js);
ant_value_t cb = js_mkundef(); if (nargs > 0 && is_callable(args[0])) cb = args[0];
int fd = stream_fd_from_this(js, ANT_STDOUT_FD); size_t seq_len = 0; const char *seq = tty_ctrl_clear_screen_down_seq(&seq_len); bool ok = tty_ctrl_write_fd(fd, seq, seq_len); return maybe_callback_or_throw(js, this_obj, cb, ok, "clearScreenDown", fd);}
static ant_value_t tty_write_stream_cursor_to(ant_params_t) { ant_value_t this_obj = js_getthis(js); int x = 0; if (nargs < 1 || !tty_ctrl_parse_int_value(args[0], &x)) { return js_mkerr_typed(js, JS_ERR_TYPE, "cursorTo(x[, y][, callback]) requires numeric x"); }
x = tty_ctrl_normalize_coord(x);
bool has_y = false; int y = 0; ant_value_t cb = js_mkundef();
if (nargs > 1) { if (is_callable(args[1])) { cb = args[1]; } else if (vtype(args[1]) == kTypeUndefined) { // no-op } else if (tty_ctrl_parse_int_value(args[1], &y)) { has_y = true; y = tty_ctrl_normalize_coord(y); if (nargs > 2 && is_callable(args[2])) cb = args[2]; } else { return js_mkerr_typed(js, JS_ERR_TYPE, "cursorTo y must be a number when provided"); } }
char seq[64]; size_t seq_len = 0; if (!tty_ctrl_build_cursor_to(seq, sizeof(seq), x, has_y, y, &seq_len)) { return js_mkerr(js, "Failed to build cursor sequence"); }
int fd = stream_fd_from_this(js, ANT_STDOUT_FD); bool ok = tty_ctrl_write_fd(fd, seq, seq_len); return maybe_callback_or_throw(js, this_obj, cb, ok, "cursorTo", fd);}
static ant_value_t tty_write_stream_move_cursor(ant_params_t) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) { return js_mkerr_typed(js, JS_ERR_TYPE, "moveCursor(dx, dy[, callback]) requires dx and dy"); }
int dx = 0; int dy = 0; if (!tty_ctrl_parse_move_cursor_args(args, nargs, 0, 1, &dx, &dy)) { return js_mkerr_typed(js, JS_ERR_TYPE, "moveCursor(dx, dy[, callback]) requires numeric dx and dy"); }
ant_value_t cb = js_mkundef(); if (nargs > 2 && is_callable(args[2])) cb = args[2];
int fd = stream_fd_from_this(js, ANT_STDOUT_FD); bool ok = true;
if (dx != 0) { char seq_x[32]; size_t len_x = 0; if (!tty_ctrl_build_move_cursor_axis(seq_x, sizeof(seq_x), dx, true, &len_x)) { return js_mkerr(js, "Failed to build moveCursor sequence"); } if (!tty_ctrl_write_fd(fd, seq_x, len_x)) ok = false; }
if (ok && dy != 0) { char seq_y[32]; size_t len_y = 0; if (!tty_ctrl_build_move_cursor_axis(seq_y, sizeof(seq_y), dy, false, &len_y)) { return js_mkerr(js, "Failed to build moveCursor sequence"); } if (!tty_ctrl_write_fd(fd, seq_y, len_y)) ok = false; }
return maybe_callback_or_throw(js, this_obj, cb, ok, "moveCursor", fd);}
static ant_value_t tty_write_stream_get_color_depth(ant_params_t) { ant_value_t env_obj = js_mkundef(); if (nargs > 0 && is_special_object(args[0])) env_obj = args[0];
int fd = stream_fd_from_this(js, ANT_STDOUT_FD); int depth = detect_color_depth(js, fd, env_obj); return js_mknum((double)depth);}
static ant_value_t tty_write_stream_has_colors(ant_params_t) { int count = 16; ant_value_t env_obj = js_mkundef();
if (nargs > 0) { if (vtype(args[0]) == kTypeNumber) { int parsed_count = 16; if (!tty_ctrl_parse_int_value(args[0], &parsed_count)) { return js_mkerr_typed(js, JS_ERR_TYPE, "hasColors(count[, env]) count must be an integer"); } count = parsed_count; } else if (is_special_object(args[0])) { env_obj = args[0]; } else if (vtype(args[0]) != kTypeUndefined) { return js_mkerr_typed(js, JS_ERR_TYPE, "hasColors(count[, env]) invalid first argument"); } }
if (nargs > 1 && is_special_object(args[1])) env_obj = args[1]; if (count < 1) count = 1;
int fd = stream_fd_from_this(js, ANT_STDOUT_FD); int depth = detect_color_depth(js, fd, env_obj); int max_colors = palette_size_for_depth(depth); return js_bool(max_colors >= count);}
static ant_value_t tty_read_stream_set_raw_mode(ant_params_t) { ant_value_t this_obj = js_getthis(js); if (!is_special_object(this_obj)) { return js_mkerr_typed(js, JS_ERR_TYPE, "setRawMode() requires a ReadStream receiver"); }
bool enable = nargs > 0 ? js_truthy(js, args[0]) : true; int fd = stream_fd_from_this(js, ANT_STDIN_FD); if (!tty_set_raw_mode(fd, enable)) { return js_mkerr_typed(js, JS_ERR_GENERIC, "Failed to set raw mode for fd %d", fd); } js_set(js, this_obj, "isRaw", js_bool(enable)); return this_obj;}
static ant_value_t tty_read_stream_constructor(ant_params_t) { tty_read_stream_state_t *state = NULL; if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "ReadStream(fd) requires a file descriptor");
int fd = 0; if (!parse_fd(args[0], &fd)) { return js_mkerr_typed(js, JS_ERR_TYPE, "ReadStream(fd) requires an integer file descriptor"); } if (!is_tty_fd(fd)) { return js_mkerr_typed(js, JS_ERR_TYPE, "ReadStream fd %d is not a TTY", fd); }
if (fd == ANT_STDIN_FD) { ant_value_t stdin_obj = get_process_stream(js, "stdin"); if (is_special_object(stdin_obj)) { ensure_stream_common_props(js, stdin_obj, fd); if (vtype(js_get(js, stdin_obj, "isRaw")) == kTypeUndefined) js_set(js, stdin_obj, "isRaw", js_false); return stdin_obj; }}
ant_value_t obj = stream_construct_readable(js, js->builtins.tty_readstream_proto, js_mkundef(), call_new_target); if (is_err(obj)) return obj;
state = calloc(1, sizeof(*state)); if (!state) return js_mkerr(js, "Out of memory"); state->js = js; state->stream_obj = obj; state->fd = fd;
ensure_stream_common_props(js, obj, fd); stream_set_attached_state(obj, state, tty_read_stream_finalize); js_set(js, obj, "isRaw", js_false); js_set(js, obj, "_read", Ant_Function_Create(tty_readstream__read)); js_set(js, obj, "_destroy", Ant_Function_Create(tty_readstream__destroy)); return obj;}
static ant_value_t tty_write_stream_constructor(ant_params_t) { if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "WriteStream(fd) requires a file descriptor");
int fd = 0; if (!parse_fd(args[0], &fd)) { return js_mkerr_typed(js, JS_ERR_TYPE, "WriteStream(fd) requires an integer file descriptor"); } if (!is_tty_fd(fd)) { return js_mkerr_typed(js, JS_ERR_TYPE, "WriteStream fd %d is not a TTY", fd); }
if (fd == ANT_STDOUT_FD || fd == ANT_STDERR_FD) { ant_value_t stream = get_process_stream(js, fd == ANT_STDOUT_FD ? "stdout" : "stderr"); if (is_special_object(stream)) { ensure_stream_common_props(js, stream, fd); return stream; } }
ant_value_t obj = stream_construct_writable(js, js->builtins.tty_writestream_proto, js_mkundef(), call_new_target); if (is_err(obj)) return obj;
ensure_stream_common_props(js, obj, fd); js_set(js, obj, "_write", Ant_Function_Create(tty_stream_write));
return obj;}
static void setup_readstream_proto(ant_t *js, ant_value_t proto) { if (!is_special_object(proto)) return; js_set(js, proto, "setRawMode", Ant_Function_CreateWithArity(tty_read_stream_set_raw_mode, 1)); js_set_tostringtag(js, proto, js_mkstr(js, "ReadStream", 10));}
static void setup_writestream_proto(ant_t *js, ant_value_t proto) { if (!is_special_object(proto)) return;
js_set(js, proto, "write", Ant_Function_Create(tty_stream_write)); js_set(js, proto, "clearLine", Ant_Function_CreateWithArity(tty_write_stream_clear_line, 2)); js_set(js, proto, "clearScreenDown", Ant_Function_CreateWithArity(tty_write_stream_clear_screen_down, 1)); js_set(js, proto, "cursorTo", Ant_Function_CreateWithArity(tty_write_stream_cursor_to, 3)); js_set(js, proto, "moveCursor", Ant_Function_CreateWithArity(tty_write_stream_move_cursor, 3)); js_set(js, proto, "getWindowSize", Ant_Function_Create(tty_write_stream_get_window_size)); js_set(js, proto, "getColorDepth", Ant_Function_Create(tty_write_stream_get_color_depth)); js_set(js, proto, "hasColors", Ant_Function_CreateWithArity(tty_write_stream_has_colors, 2)); js_set_getter_desc(js, proto, "rows", 4, Ant_Function_Create(tty_write_stream_rows_getter), JS_DESC_E | JS_DESC_C); js_set_getter_desc(js, proto, "columns", 7, Ant_Function_Create(tty_write_stream_columns_getter), JS_DESC_E | JS_DESC_C); js_set_tostringtag(js, proto, js_mkstr(js, "WriteStream", 11));}
static void tty_init_stream_constructors(ant_t *js) { if (js->builtins.tty_readstream_ctor && js->builtins.tty_writestream_ctor) return; stream_init_constructors(js);
js->builtins.tty_readstream_proto = Ant_Object_CreateBare(js); js_set_proto_init(js->builtins.tty_readstream_proto, stream_readable_prototype(js)); setup_readstream_proto(js, js->builtins.tty_readstream_proto); js->builtins.tty_readstream_ctor = Ant_Function_CreateConstructor(js, tty_read_stream_constructor, js->builtins.tty_readstream_proto, "ReadStream", 2); js_set_proto_init(js->builtins.tty_readstream_ctor, stream_readable_constructor(js)); js->builtins.tty_writestream_proto = Ant_Object_CreateBare(js); js_set_proto_init(js->builtins.tty_writestream_proto, stream_writable_prototype(js)); setup_writestream_proto(js, js->builtins.tty_writestream_proto); js->builtins.tty_writestream_ctor = Ant_Function_CreateConstructor(js, tty_write_stream_constructor, js->builtins.tty_writestream_proto, "WriteStream", 1); js_set_proto_init(js->builtins.tty_writestream_ctor, stream_writable_constructor(js));}
void init_tty_module(ant_t *js) { if (!js) return; tty_init_stream_constructors(js);
ant_value_t process_obj = js_get(js, js_glob(js), "process"); if (!is_special_object(process_obj)) return;
ant_value_t stdin_obj = js_get(js, process_obj, "stdin"); if (is_special_object(stdin_obj)) { ensure_stream_common_props(js, stdin_obj, ANT_STDIN_FD); if (vtype(js_get(js, stdin_obj, "isRaw")) == kTypeUndefined) js_set(js, stdin_obj, "isRaw", js_false); setup_readstream_proto(js, js_get_proto(js, stdin_obj)); }
ant_value_t stdout_obj = js_get(js, process_obj, "stdout"); if (is_special_object(stdout_obj)) { ensure_stream_common_props(js, stdout_obj, ANT_STDOUT_FD); js_set_getter_desc(js, stdout_obj, "rows", 4, Ant_Function_Create(tty_write_stream_rows_getter), JS_DESC_E | JS_DESC_C); js_set_getter_desc(js, stdout_obj, "columns", 7, Ant_Function_Create(tty_write_stream_columns_getter), JS_DESC_E | JS_DESC_C);
ant_value_t stdout_proto = js_get_proto(js, stdout_obj); if (is_special_object(stdout_proto)) js_set_proto_init(stdout_proto, stream_duplex_prototype(js)); setup_writestream_proto(js, stdout_proto); stream_init_duplex_object(js, stdout_obj, js_mkundef()); js_set(js, stdout_obj, "_write", Ant_Function_Create(tty_stream_write)); js_set(js, stdout_obj, "readable", js_false); }
ant_value_t stderr_obj = js_get(js, process_obj, "stderr"); if (is_special_object(stderr_obj)) { ensure_stream_common_props(js, stderr_obj, ANT_STDERR_FD); js_set_getter_desc(js, stderr_obj, "rows", 4, Ant_Function_Create(tty_write_stream_rows_getter), JS_DESC_E | JS_DESC_C); js_set_getter_desc(js, stderr_obj, "columns", 7, Ant_Function_Create(tty_write_stream_columns_getter), JS_DESC_E | JS_DESC_C); ant_value_t stderr_proto = js_get_proto(js, stderr_obj); if (is_special_object(stderr_proto)) js_set_proto_init(stderr_proto, stream_duplex_prototype(js)); setup_writestream_proto(js, stderr_proto); stream_init_duplex_object(js, stderr_obj, js_mkundef()); js_set(js, stderr_obj, "_write", Ant_Function_Create(tty_stream_write)); js_set(js, stderr_obj, "readable", js_false); }}
void tty_set_sandbox_terminal(uint32_t capabilities, uint16_t rows, uint16_t cols) { g_sandbox_terminal_enabled = true; g_sandbox_terminal_capabilities = capabilities; g_sandbox_tty_rows = rows ? rows : 24; g_sandbox_tty_cols = cols ? cols : 80;}
ant_value_t tty_library(ant_t *js) { ant_value_t lib = Ant_Object_CreateBare(js); tty_init_stream_constructors(js);
js_set(js, lib, "isatty", Ant_Function_CreateWithArity(tty_isatty, 1)); js_set(js, lib, "ReadStream", js->builtins.tty_readstream_ctor); js_set(js, lib, "WriteStream", js->builtins.tty_writestream_ctor); js_set_tostringtag(js, lib, js_mkstr(js, "tty", 3));
return lib;}