MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at mir/inline-method 910 lines 29 kB view raw
1#include <compat.h> // IWYU pragma: keep 2 3#include <stdbool.h> 4#include <errno.h> 5#include <stdio.h> 6#include <stdlib.h> 7#include <string.h> 8#include <ctype.h> 9 10#include <uv.h> 11 12#ifdef _WIN32 13#include <io.h> 14#define WIN32_LEAN_AND_MEAN 15#include <windows.h> 16#define ANT_ISATTY _isatty 17#define ANT_STDIN_FD 0 18#define ANT_STDOUT_FD 1 19#define ANT_STDERR_FD 2 20#else 21#include <signal.h> 22#include <sys/ioctl.h> 23#include <termios.h> 24#include <unistd.h> 25#define ANT_ISATTY isatty 26#define ANT_STDIN_FD STDIN_FILENO 27#define ANT_STDOUT_FD STDOUT_FILENO 28#define ANT_STDERR_FD STDERR_FILENO 29#endif 30 31#include "ant.h" 32#include "gc/roots.h" 33#include "descriptors.h" 34#include "errors.h" 35#include "internal.h" 36#include "runtime.h" 37#include "tty_ctrl.h" 38#include "silver/engine.h" 39 40#include "modules/stream.h" 41#include "modules/buffer.h" 42#include "modules/events.h" 43#include "modules/symbol.h" 44#include "modules/tty.h" 45 46static ant_value_t g_tty_readstream_proto = 0; 47static ant_value_t g_tty_readstream_ctor = 0; 48static ant_value_t g_tty_writestream_proto = 0; 49static ant_value_t g_tty_writestream_ctor = 0; 50 51typedef struct tty_read_stream_state { 52 ant_t *js; 53 ant_value_t stream_obj; 54 uv_tty_t tty; 55 56 int fd; 57 bool initialized; 58 bool reading; 59 bool closing; 60} tty_read_stream_state_t; 61 62static void invoke_callback_if_needed(ant_t *js, ant_value_t cb, ant_value_t arg) { 63 if (!is_callable(cb)) return; 64 ant_value_t cb_args[1] = { arg }; 65 sv_vm_call(js->vm, js, cb, js_mkundef(), cb_args, 1, NULL, false); 66} 67 68static bool parse_fd(ant_value_t value, int *fd_out) { 69 int fd = 0; 70 if (!tty_ctrl_parse_int_value(value, &fd)) return false; 71 if (fd < 0) return false; 72 *fd_out = fd; 73 return true; 74} 75 76static bool is_tty_fd(int fd) { 77 if (fd < 0) return false; 78 return uv_guess_handle(fd) == UV_TTY; 79} 80 81static int stream_fd_from_this(ant_t *js, int fallback_fd) { 82 ant_value_t this_obj = js_getthis(js); 83 if (!is_special_object(this_obj)) return fallback_fd; 84 85 ant_value_t fd_val = js_get(js, this_obj, "fd"); 86 int fd = 0; 87 if (!parse_fd(fd_val, &fd)) return fallback_fd; 88 return fd; 89} 90 91static tty_read_stream_state_t *tty_read_stream_state_from_obj(ant_value_t stream_obj) { 92 return (tty_read_stream_state_t *)stream_get_attached_state(stream_obj); 93} 94 95static ant_value_t make_stream_error(ant_t *js, const char *op, int fd, int uv_code) { 96 if (uv_code != 0) return js_mkerr_typed( 97 js, JS_ERR_GENERIC, 98 "tty stream %s failed for fd %d: %s", 99 op, fd, uv_strerror(uv_code) 100 ); 101 102 return js_mkerr_typed(js, JS_ERR_GENERIC, "tty stream %s failed for fd %d", op, fd); 103} 104 105static void tty_read_stream_emit_error(tty_read_stream_state_t *state, const char *op, int uv_code) { 106 ant_value_t err = 0; 107 if (!state || !state->js || !is_object_type(state->stream_obj)) return; 108 err = make_stream_error(state->js, op, state->fd, uv_code); 109 eventemitter_emit_args(state->js, state->stream_obj, "error", &err, 1); 110} 111 112static void tty_read_stream_push_chunk(tty_read_stream_state_t *state, const char *data, size_t len) { 113 ArrayBufferData *ab = NULL; 114 ant_value_t chunk = 0; 115 116 if (!state || !state->js || !data || len == 0) return; 117 ab = create_array_buffer_data(len); 118 if (ab) memcpy(ab->data, data, len); 119 120 chunk = ab 121 ? create_typed_array(state->js, TYPED_ARRAY_UINT8, ab, 0, len, "Buffer") 122 : js_mkstr(state->js, data, len); 123 (void)stream_readable_push(state->js, state->stream_obj, chunk, js_mkundef()); 124} 125 126static void tty_read_stream_alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { 127 buf->base = malloc(suggested_size); 128#ifdef _WIN32 129 buf->len = (ULONG)suggested_size; 130#else 131 buf->len = suggested_size; 132#endif 133} 134 135static void tty_read_stream_stop(tty_read_stream_state_t *state) { 136 if (!state || !state->initialized || !state->reading) return; 137 uv_read_stop((uv_stream_t *)&state->tty); 138 state->reading = false; 139} 140 141static void tty_read_stream_close_cb(uv_handle_t *handle) { 142 tty_read_stream_state_t *state = (tty_read_stream_state_t *)handle->data; 143 free(state); 144} 145 146static void tty_read_stream_finalize(ant_t *js, ant_value_t stream_obj, void *data) { 147 tty_read_stream_state_t *state = (tty_read_stream_state_t *)data; 148 149 if (!state) return; 150 tty_read_stream_stop(state); 151 152 if (state->initialized && !state->closing) { 153 state->closing = true; 154 uv_close((uv_handle_t *)&state->tty, tty_read_stream_close_cb); 155 return; 156 } 157 158 if (!state->initialized) free(state); 159} 160 161static void tty_read_stream_on_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { 162 tty_read_stream_state_t *state = (tty_read_stream_state_t *)stream->data; 163 164 if (!state || !state->js) goto cleanup; 165 166 if (nread > 0) { 167 tty_read_stream_push_chunk(state, buf->base, (size_t)nread); 168 goto cleanup; 169 } 170 171 if (nread == UV_EOF) { 172 tty_read_stream_stop(state); 173 (void)stream_readable_push(state->js, state->stream_obj, js_mknull(), js_mkundef()); 174 goto cleanup; 175 } 176 177 if (nread < 0) { 178 tty_read_stream_stop(state); 179 tty_read_stream_emit_error(state, "read", (int)nread); 180 } 181 182cleanup: 183 if (buf->base) free(buf->base); 184} 185 186static ant_value_t tty_readstream__read(ant_t *js, ant_value_t *args, int nargs) { 187 ant_value_t stream_obj = js_getthis(js); 188 tty_read_stream_state_t *state = tty_read_stream_state_from_obj(stream_obj); 189 int rc = 0; 190 191 if (!state) return js_mkundef(); 192 if (state->closing || js_truthy(js, js_get(js, stream_obj, "destroyed"))) return js_mkundef(); 193 194 if (!state->initialized) { 195 rc = uv_tty_init(uv_default_loop(), &state->tty, state->fd, 0); 196 if (rc != 0) { 197 tty_read_stream_emit_error(state, "open", rc); 198 return js_mkundef(); 199 } 200 state->tty.data = state; 201 state->initialized = true; 202 } 203 204 if (state->reading) return js_mkundef(); 205 rc = uv_read_start((uv_stream_t *)&state->tty, tty_read_stream_alloc_buffer, tty_read_stream_on_read); 206 207 if (rc != 0) { 208 tty_read_stream_emit_error(state, "read", rc); 209 return js_mkundef(); 210 } 211 212 state->reading = true; 213 return js_mkundef(); 214} 215 216static ant_value_t tty_readstream__destroy(ant_t *js, ant_value_t *args, int nargs) { 217 ant_value_t stream_obj = js_getthis(js); 218 tty_read_stream_state_t *state = tty_read_stream_state_from_obj(stream_obj); 219 ant_value_t cb = nargs > 1 ? args[1] : js_mkundef(); 220 221 if (!state) { 222 invoke_callback_if_needed(js, cb, js_mknull()); 223 return js_mkundef(); 224 } 225 226 tty_read_stream_stop(state); 227 stream_clear_attached_state(stream_obj); 228 229 if (state->initialized && !state->closing) { 230 state->closing = true; 231 uv_close((uv_handle_t *)&state->tty, tty_read_stream_close_cb); 232 } else if (!state->initialized) free(state); 233 234 invoke_callback_if_needed(js, cb, js_mknull()); 235 return js_mkundef(); 236} 237 238static void get_tty_size(int fd, int *rows, int *cols) { 239 int out_rows = 24; 240 int out_cols = 80; 241 242#ifdef _WIN32 243 HANDLE handle = INVALID_HANDLE_VALUE; 244 if (fd == ANT_STDOUT_FD) { 245 handle = GetStdHandle(STD_OUTPUT_HANDLE); 246 } else if (fd == ANT_STDERR_FD) { 247 handle = GetStdHandle(STD_ERROR_HANDLE); 248 } else { 249 intptr_t os_handle = _get_osfhandle(fd); 250 if (os_handle != -1) handle = (HANDLE)os_handle; 251 } 252 253 if (handle != INVALID_HANDLE_VALUE) { 254 CONSOLE_SCREEN_BUFFER_INFO csbi; 255 if (GetConsoleScreenBufferInfo(handle, &csbi)) { 256 int width = csbi.srWindow.Right - csbi.srWindow.Left + 1; 257 int height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; 258 if (height > 0) out_rows = height; 259 if (width > 0) out_cols = width; 260 } 261 } 262#else 263 struct winsize ws; 264 if (ioctl(fd, TIOCGWINSZ, &ws) == 0) { 265 if (ws.ws_row > 0) out_rows = (int)ws.ws_row; 266 if (ws.ws_col > 0) out_cols = (int)ws.ws_col; 267 } 268#endif 269 270 if (rows) *rows = out_rows; 271 if (cols) *cols = out_cols; 272} 273 274static bool str_case_eq(const char *a, const char *b) { 275 if (!a || !b) return false; 276 while (*a && *b) { 277 int ca = tolower((unsigned char)*a); 278 int cb = tolower((unsigned char)*b); 279 if (ca != cb) return false; 280 a++; 281 b++; 282 } 283 return *a == '\0' && *b == '\0'; 284} 285 286static bool str_case_contains(const char *haystack, const char *needle) { 287 if (!haystack || !needle) return false; 288 size_t needle_len = strlen(needle); 289 if (needle_len == 0) return true; 290 291 size_t hay_len = strlen(haystack); 292 if (needle_len > hay_len) return false; 293 294 for (size_t i = 0; i + needle_len <= hay_len; i++) { 295 bool match = true; 296 for (size_t j = 0; j < needle_len; j++) { 297 int ca = tolower((unsigned char)haystack[i + j]); 298 int cb = tolower((unsigned char)needle[j]); 299 if (ca != cb) { 300 match = false; 301 break; 302 } 303 } 304 if (match) return true; 305 } 306 return false; 307} 308 309static bool read_env_object_value( 310 ant_t *js, 311 ant_value_t env_obj, 312 const char *key, 313 char *buf, 314 size_t buf_len 315) { 316 if (!is_special_object(env_obj) || !key || !buf || buf_len == 0) return false; 317 318 ant_value_t value = js_get(js, env_obj, key); 319 if (vtype(value) == T_UNDEF || vtype(value) == T_NULL) return false; 320 321 ant_value_t str_val = js_tostring_val(js, value); 322 size_t len = 0; 323 char *str = js_getstr(js, str_val, &len); 324 if (!str) return false; 325 326 if (len >= buf_len) len = buf_len - 1; 327 memcpy(buf, str, len); 328 buf[len] = '\0'; 329 return true; 330} 331 332static const char *get_env_value( 333 ant_t *js, 334 ant_value_t env_obj, 335 const char *key, 336 char *buf, 337 size_t buf_len 338) { 339 if (read_env_object_value(js, env_obj, key, buf, buf_len)) return buf; 340 return getenv(key); 341} 342 343static int force_color_depth(const char *force_color) { 344 if (!force_color) return 0; 345 if (*force_color == '\0') return 4; 346 if (strcmp(force_color, "0") == 0) return 1; 347 if (strcmp(force_color, "1") == 0) return 4; 348 if (strcmp(force_color, "2") == 0) return 8; 349 if (strcmp(force_color, "3") == 0) return 24; 350 return 4; 351} 352 353static int detect_color_depth(ant_t *js, int fd, ant_value_t env_obj) { 354 char scratch[128]; 355 const char *force_color = get_env_value(js, env_obj, "FORCE_COLOR", scratch, sizeof(scratch)); 356 357 int forced = force_color_depth(force_color); 358 if (forced > 0) return forced; 359 360 const char *no_color = get_env_value(js, env_obj, "NO_COLOR", scratch, sizeof(scratch)); 361 if (no_color && *no_color) return 1; 362 if (!is_tty_fd(fd)) return 1; 363 364 const char *colorterm = get_env_value(js, env_obj, "COLORTERM", scratch, sizeof(scratch)); 365 if (colorterm) { 366 if (str_case_contains(colorterm, "truecolor") || str_case_contains(colorterm, "24bit")) return 24; 367 } 368 369#ifdef _WIN32 370 return 24; 371#else 372 const char *term = get_env_value(js, env_obj, "TERM", scratch, sizeof(scratch)); 373 if (!term) return 4; 374 if (str_case_eq(term, "dumb")) return 1; 375 if (str_case_contains(term, "256color")) return 8; 376 if (str_case_contains(term, "color") 377 || str_case_contains(term, "xterm") 378 || str_case_contains(term, "screen") 379 || str_case_contains(term, "ansi") 380 || str_case_contains(term, "linux") 381 || str_case_contains(term, "cygwin") 382 || str_case_contains(term, "vt100") 383 ) return 4; 384 return 4; 385#endif 386} 387 388static int palette_size_for_depth(int depth) { 389 switch (depth) { 390 case 1: return 2; 391 case 4: return 16; 392 case 8: return 256; 393 case 24: return 16777216; 394 default: return 2; 395 } 396} 397 398static ant_value_t maybe_callback_or_throw( 399 ant_t *js, ant_value_t this_obj, 400 ant_value_t cb, bool ok, 401 const char *op, int fd 402) { 403 if (is_callable(cb)) { 404 if (ok) invoke_callback_if_needed(js, cb, js_mknull()); 405 else { 406 ant_value_t err = make_stream_error(js, op, fd, 0); 407 invoke_callback_if_needed(js, cb, err); 408 } 409 return this_obj; 410 } 411 if (!ok) return make_stream_error(js, op, fd, 0); 412 return this_obj; 413} 414 415#ifndef _WIN32 416static struct { 417 int fd; 418 bool active; 419 struct termios saved; 420} raw_state = { .fd = -1, .active = false }; 421 422static int tty_tcsetattr_no_sigtou(int fd, int optional_actions, const struct termios *tio) { 423#ifdef SIGTTOU 424 sigset_t block_set; 425 sigset_t prev_set; 426 if (sigemptyset(&block_set) == 0 427 && sigaddset(&block_set, SIGTTOU) == 0 428 && sigprocmask(SIG_BLOCK, &block_set, &prev_set) == 0) { 429 int rc = tcsetattr(fd, optional_actions, tio); 430 int saved_errno = errno; 431 sigprocmask(SIG_SETMASK, &prev_set, NULL); 432 errno = saved_errno; 433 return rc; 434 } 435#endif 436 return tcsetattr(fd, optional_actions, tio); 437} 438#endif 439 440bool tty_set_raw_mode(int fd, bool enable) { 441#ifdef _WIN32 442 intptr_t os_handle = _get_osfhandle(fd); 443 if (os_handle == -1) return false; 444 HANDLE handle = (HANDLE)os_handle; 445 446 DWORD mode = 0; 447 if (!GetConsoleMode(handle, &mode)) return false; 448 449 if (enable) { 450 mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); 451 } else { 452 mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; 453 } 454 455 return SetConsoleMode(handle, mode) != 0; 456#else 457 if (!is_tty_fd(fd)) return false; 458 459 if (enable) { 460 if (raw_state.active && raw_state.fd == fd) return true; 461 462 struct termios saved; 463 if (tcgetattr(fd, &saved) == -1) return false; 464 465 struct termios raw = saved; 466 raw.c_lflag &= ~(ICANON | ECHO | ISIG); 467 raw.c_iflag &= ~(IXON | ICRNL); 468 raw.c_cc[VMIN] = 1; 469 raw.c_cc[VTIME] = 0; 470#ifdef VDISCARD 471 raw.c_cc[VDISCARD] = _POSIX_VDISABLE; 472#endif 473#ifdef VLNEXT 474 raw.c_cc[VLNEXT] = _POSIX_VDISABLE; 475#endif 476 if (tty_tcsetattr_no_sigtou(fd, TCSANOW, &raw) == -1) return false; 477 raw_state.fd = fd; 478 raw_state.saved = saved; 479 raw_state.active = true; 480 return true; 481 } 482 483 if (!(raw_state.active && raw_state.fd == fd)) return true; 484 if (tty_tcsetattr_no_sigtou(fd, TCSANOW, &raw_state.saved) == -1) return false; 485 raw_state.fd = -1; 486 raw_state.active = false; 487 return true; 488#endif 489} 490 491bool tty_is_raw_mode(int fd) { 492#ifdef _WIN32 493 return false; 494#else 495 return raw_state.active && raw_state.fd == fd; 496#endif 497} 498 499static ant_value_t get_process_stream(ant_t *js, const char *name) { 500 ant_value_t process_obj = js_get(js, js_glob(js), "process"); 501 if (!is_special_object(process_obj)) return js_mkundef(); 502 503 ant_value_t stream = js_get(js, process_obj, name); 504 if (!is_special_object(stream)) return js_mkundef(); 505 return stream; 506} 507 508static void ensure_stream_common_props(ant_t *js, ant_value_t stream, int fd) { 509 if (!is_special_object(stream)) return; 510 js_set(js, stream, "fd", js_mknum((double)fd)); 511 js_set(js, stream, "isTTY", js_bool(is_tty_fd(fd))); 512} 513 514static ant_value_t tty_isatty(ant_t *js, ant_value_t *args, int nargs) { 515 if (nargs < 1) return js_false; 516 517 int fd = 0; 518 if (!parse_fd(args[0], &fd)) return js_false; 519 return js_bool(ANT_ISATTY(fd) != 0); 520} 521 522static ant_value_t tty_stream_write(ant_t *js, ant_value_t *args, int nargs) { 523 if (nargs < 1) return js_false; 524 525 size_t len = 0; 526 char *data = js_getstr(js, args[0], &len); 527 if (!data) return js_false; 528 529 ant_value_t cb = js_mkundef(); 530 if (nargs > 1 && is_callable(args[1])) cb = args[1]; 531 532 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 533 bool ok = tty_ctrl_write_fd(fd, data, len); 534 535 if (is_callable(cb)) { 536 if (ok) invoke_callback_if_needed(js, cb, js_mknull()); 537 else invoke_callback_if_needed(js, cb, make_stream_error(js, "write", fd, 0)); 538 } 539 540 if (!ok) return js_false; 541 return js_true; 542} 543 544static ant_value_t tty_write_stream_rows_getter(ant_t *js, ant_value_t *args, int nargs) { 545 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 546 if (!is_tty_fd(fd)) return js_mkundef(); 547 548 int rows = 0; 549 int cols = 0; 550 get_tty_size(fd, &rows, &cols); 551 return js_mknum((double)rows); 552} 553 554static ant_value_t tty_write_stream_columns_getter(ant_t *js, ant_value_t *args, int nargs) { 555 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 556 if (!is_tty_fd(fd)) return js_mkundef(); 557 558 int rows = 0; 559 int cols = 0; 560 get_tty_size(fd, &rows, &cols); 561 return js_mknum((double)cols); 562} 563 564static ant_value_t tty_write_stream_get_window_size(ant_t *js, ant_value_t *args, int nargs) { 565 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 566 int rows = 0; 567 int cols = 0; 568 get_tty_size(fd, &rows, &cols); 569 570 ant_value_t arr = js_mkarr(js); 571 js_arr_push(js, arr, js_mknum((double)cols)); 572 js_arr_push(js, arr, js_mknum((double)rows)); 573 return arr; 574} 575 576static ant_value_t tty_write_stream_clear_line(ant_t *js, ant_value_t *args, int nargs) { 577 ant_value_t this_obj = js_getthis(js); 578 579 int dir = 0; 580 if (!tty_ctrl_parse_clear_line_dir(args, nargs, 0, &dir)) { 581 return js_mkerr_typed(js, JS_ERR_TYPE, "clearLine(dir) requires a numeric dir"); 582 } 583 584 ant_value_t cb = js_mkundef(); 585 if (nargs > 1 && is_callable(args[1])) cb = args[1]; 586 587 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 588 size_t seq_len = 0; 589 590 const char *seq = tty_ctrl_clear_line_seq(dir, &seq_len); 591 bool ok = tty_ctrl_write_fd(fd, seq, seq_len); 592 593 return maybe_callback_or_throw(js, this_obj, cb, ok, "clearLine", fd); 594} 595 596static ant_value_t tty_write_stream_clear_screen_down(ant_t *js, ant_value_t *args, int nargs) { 597 ant_value_t this_obj = js_getthis(js); 598 599 ant_value_t cb = js_mkundef(); 600 if (nargs > 0 && is_callable(args[0])) cb = args[0]; 601 602 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 603 size_t seq_len = 0; 604 605 const char *seq = tty_ctrl_clear_screen_down_seq(&seq_len); 606 bool ok = tty_ctrl_write_fd(fd, seq, seq_len); 607 608 return maybe_callback_or_throw(js, this_obj, cb, ok, "clearScreenDown", fd); 609} 610 611static ant_value_t tty_write_stream_cursor_to(ant_t *js, ant_value_t *args, int nargs) { 612 ant_value_t this_obj = js_getthis(js); 613 int x = 0; 614 615 if (nargs < 1 || !tty_ctrl_parse_int_value(args[0], &x)) { 616 return js_mkerr_typed(js, JS_ERR_TYPE, "cursorTo(x[, y][, callback]) requires numeric x"); 617 } 618 619 x = tty_ctrl_normalize_coord(x); 620 621 bool has_y = false; 622 int y = 0; 623 ant_value_t cb = js_mkundef(); 624 625 if (nargs > 1) { 626 if (is_callable(args[1])) { 627 cb = args[1]; 628 } else if (vtype(args[1]) == T_UNDEF) { 629 // no-op 630 } else if (tty_ctrl_parse_int_value(args[1], &y)) { 631 has_y = true; 632 y = tty_ctrl_normalize_coord(y); 633 if (nargs > 2 && is_callable(args[2])) cb = args[2]; 634 } else { 635 return js_mkerr_typed(js, JS_ERR_TYPE, "cursorTo y must be a number when provided"); 636 } 637 } 638 639 char seq[64]; 640 size_t seq_len = 0; 641 if (!tty_ctrl_build_cursor_to(seq, sizeof(seq), x, has_y, y, &seq_len)) { 642 return js_mkerr(js, "Failed to build cursor sequence"); 643 } 644 645 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 646 bool ok = tty_ctrl_write_fd(fd, seq, seq_len); 647 return maybe_callback_or_throw(js, this_obj, cb, ok, "cursorTo", fd); 648} 649 650static ant_value_t tty_write_stream_move_cursor(ant_t *js, ant_value_t *args, int nargs) { 651 ant_value_t this_obj = js_getthis(js); 652 if (nargs < 2) { 653 return js_mkerr_typed(js, JS_ERR_TYPE, "moveCursor(dx, dy[, callback]) requires dx and dy"); 654 } 655 656 int dx = 0; 657 int dy = 0; 658 if (!tty_ctrl_parse_move_cursor_args(args, nargs, 0, 1, &dx, &dy)) { 659 return js_mkerr_typed(js, JS_ERR_TYPE, "moveCursor(dx, dy[, callback]) requires numeric dx and dy"); 660 } 661 662 ant_value_t cb = js_mkundef(); 663 if (nargs > 2 && is_callable(args[2])) cb = args[2]; 664 665 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 666 bool ok = true; 667 668 if (dx != 0) { 669 char seq_x[32]; 670 size_t len_x = 0; 671 if (!tty_ctrl_build_move_cursor_axis(seq_x, sizeof(seq_x), dx, true, &len_x)) { 672 return js_mkerr(js, "Failed to build moveCursor sequence"); 673 } 674 if (!tty_ctrl_write_fd(fd, seq_x, len_x)) ok = false; 675 } 676 677 if (ok && dy != 0) { 678 char seq_y[32]; 679 size_t len_y = 0; 680 if (!tty_ctrl_build_move_cursor_axis(seq_y, sizeof(seq_y), dy, false, &len_y)) { 681 return js_mkerr(js, "Failed to build moveCursor sequence"); 682 } 683 if (!tty_ctrl_write_fd(fd, seq_y, len_y)) ok = false; 684 } 685 686 return maybe_callback_or_throw(js, this_obj, cb, ok, "moveCursor", fd); 687} 688 689static ant_value_t tty_write_stream_get_color_depth(ant_t *js, ant_value_t *args, int nargs) { 690 ant_value_t env_obj = js_mkundef(); 691 if (nargs > 0 && is_special_object(args[0])) env_obj = args[0]; 692 693 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 694 int depth = detect_color_depth(js, fd, env_obj); 695 return js_mknum((double)depth); 696} 697 698static ant_value_t tty_write_stream_has_colors(ant_t *js, ant_value_t *args, int nargs) { 699 int count = 16; 700 ant_value_t env_obj = js_mkundef(); 701 702 if (nargs > 0) { 703 if (vtype(args[0]) == T_NUM) { 704 int parsed_count = 16; 705 if (!tty_ctrl_parse_int_value(args[0], &parsed_count)) { 706 return js_mkerr_typed(js, JS_ERR_TYPE, "hasColors(count[, env]) count must be an integer"); 707 } 708 count = parsed_count; 709 } else if (is_special_object(args[0])) { 710 env_obj = args[0]; 711 } else if (vtype(args[0]) != T_UNDEF) { 712 return js_mkerr_typed(js, JS_ERR_TYPE, "hasColors(count[, env]) invalid first argument"); 713 } 714 } 715 716 if (nargs > 1 && is_special_object(args[1])) env_obj = args[1]; 717 if (count < 1) count = 1; 718 719 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 720 int depth = detect_color_depth(js, fd, env_obj); 721 int max_colors = palette_size_for_depth(depth); 722 return js_bool(max_colors >= count); 723} 724 725static ant_value_t tty_read_stream_set_raw_mode(ant_t *js, ant_value_t *args, int nargs) { 726 ant_value_t this_obj = js_getthis(js); 727 if (!is_special_object(this_obj)) { 728 return js_mkerr_typed(js, JS_ERR_TYPE, "setRawMode() requires a ReadStream receiver"); 729 } 730 731 bool enable = nargs > 0 ? js_truthy(js, args[0]) : true; 732 int fd = stream_fd_from_this(js, ANT_STDIN_FD); 733 if (!tty_set_raw_mode(fd, enable)) { 734 return js_mkerr_typed(js, JS_ERR_GENERIC, "Failed to set raw mode for fd %d", fd); 735 } 736 js_set(js, this_obj, "isRaw", js_bool(enable)); 737 return this_obj; 738} 739 740static ant_value_t tty_read_stream_constructor(ant_t *js, ant_value_t *args, int nargs) { 741 tty_read_stream_state_t *state = NULL; 742 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "ReadStream(fd) requires a file descriptor"); 743 744 int fd = 0; 745 if (!parse_fd(args[0], &fd)) { 746 return js_mkerr_typed(js, JS_ERR_TYPE, "ReadStream(fd) requires an integer file descriptor"); 747 } 748 if (!is_tty_fd(fd)) { 749 return js_mkerr_typed(js, JS_ERR_TYPE, "ReadStream fd %d is not a TTY", fd); 750 } 751 752 if (fd == ANT_STDIN_FD) { 753 ant_value_t stdin_obj = get_process_stream(js, "stdin"); 754 if (is_special_object(stdin_obj)) { 755 ensure_stream_common_props(js, stdin_obj, fd); 756 if (vtype(js_get(js, stdin_obj, "isRaw")) == T_UNDEF) js_set(js, stdin_obj, "isRaw", js_false); 757 return stdin_obj; 758 }} 759 760 ant_value_t obj = stream_construct_readable(js, g_tty_readstream_proto, js_mkundef()); 761 if (is_err(obj)) return obj; 762 763 state = calloc(1, sizeof(*state)); 764 if (!state) return js_mkerr(js, "Out of memory"); 765 state->js = js; 766 state->stream_obj = obj; 767 state->fd = fd; 768 769 ensure_stream_common_props(js, obj, fd); 770 stream_set_attached_state(obj, state, tty_read_stream_finalize); 771 772 js_set(js, obj, "isRaw", js_false); 773 js_set(js, obj, "_read", js_mkfun(tty_readstream__read)); 774 js_set(js, obj, "_destroy", js_mkfun(tty_readstream__destroy)); 775 776 return obj; 777} 778 779static ant_value_t tty_write_stream_constructor(ant_t *js, ant_value_t *args, int nargs) { 780 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "WriteStream(fd) requires a file descriptor"); 781 782 int fd = 0; 783 if (!parse_fd(args[0], &fd)) { 784 return js_mkerr_typed(js, JS_ERR_TYPE, "WriteStream(fd) requires an integer file descriptor"); 785 } 786 if (!is_tty_fd(fd)) { 787 return js_mkerr_typed(js, JS_ERR_TYPE, "WriteStream fd %d is not a TTY", fd); 788 } 789 790 if (fd == ANT_STDOUT_FD || fd == ANT_STDERR_FD) { 791 ant_value_t stream = get_process_stream(js, fd == ANT_STDOUT_FD ? "stdout" : "stderr"); 792 if (is_special_object(stream)) { 793 ensure_stream_common_props(js, stream, fd); 794 return stream; 795 } 796 } 797 798 ant_value_t obj = stream_construct_writable(js, g_tty_writestream_proto, js_mkundef()); 799 if (is_err(obj)) return obj; 800 801 ensure_stream_common_props(js, obj, fd); 802 return obj; 803} 804 805static void setup_readstream_proto(ant_t *js, ant_value_t proto) { 806 if (!is_special_object(proto)) return; 807 js_set(js, proto, "setRawMode", js_mkfun(tty_read_stream_set_raw_mode)); 808 js_set_sym(js, proto, get_toStringTag_sym(), js_mkstr(js, "ReadStream", 10)); 809} 810 811static void setup_writestream_proto(ant_t *js, ant_value_t proto) { 812 if (!is_special_object(proto)) return; 813 814 js_set(js, proto, "write", js_mkfun(tty_stream_write)); 815 js_set(js, proto, "clearLine", js_mkfun(tty_write_stream_clear_line)); 816 js_set(js, proto, "clearScreenDown", js_mkfun(tty_write_stream_clear_screen_down)); 817 js_set(js, proto, "cursorTo", js_mkfun(tty_write_stream_cursor_to)); 818 js_set(js, proto, "moveCursor", js_mkfun(tty_write_stream_move_cursor)); 819 js_set(js, proto, "getWindowSize", js_mkfun(tty_write_stream_get_window_size)); 820 js_set(js, proto, "getColorDepth", js_mkfun(tty_write_stream_get_color_depth)); 821 js_set(js, proto, "hasColors", js_mkfun(tty_write_stream_has_colors)); 822 js_set_getter_desc(js, proto, "rows", 4, js_mkfun(tty_write_stream_rows_getter), JS_DESC_E | JS_DESC_C); 823 js_set_getter_desc(js, proto, "columns", 7, js_mkfun(tty_write_stream_columns_getter), JS_DESC_E | JS_DESC_C); 824 js_set_sym(js, proto, get_toStringTag_sym(), js_mkstr(js, "WriteStream", 11)); 825} 826 827static void tty_init_stream_constructors(ant_t *js) { 828 if (g_tty_readstream_ctor && g_tty_writestream_ctor) return; 829 stream_init_constructors(js); 830 831 g_tty_readstream_proto = js_mkobj(js); 832 js_set_proto_init(g_tty_readstream_proto, stream_readable_prototype(js)); 833 setup_readstream_proto(js, g_tty_readstream_proto); 834 835 g_tty_readstream_ctor = js_make_ctor(js, tty_read_stream_constructor, g_tty_readstream_proto, "ReadStream", 10); 836 js_set_proto_init(g_tty_readstream_ctor, stream_readable_constructor(js)); 837 838 gc_register_root(&g_tty_readstream_proto); 839 gc_register_root(&g_tty_readstream_ctor); 840 841 g_tty_writestream_proto = js_mkobj(js); 842 js_set_proto_init(g_tty_writestream_proto, stream_writable_prototype(js)); 843 setup_writestream_proto(js, g_tty_writestream_proto); 844 845 g_tty_writestream_ctor = js_make_ctor(js, tty_write_stream_constructor, g_tty_writestream_proto, "WriteStream", 11); 846 js_set_proto_init(g_tty_writestream_ctor, stream_writable_constructor(js)); 847 848 gc_register_root(&g_tty_writestream_proto); 849 gc_register_root(&g_tty_writestream_ctor); 850} 851 852void init_tty_module(void) { 853 ant_t *js = rt->js; 854 if (!js) return; 855 tty_init_stream_constructors(js); 856 857 ant_value_t process_obj = js_get(js, js_glob(js), "process"); 858 if (!is_special_object(process_obj)) return; 859 860 ant_value_t stdin_obj = js_get(js, process_obj, "stdin"); 861 if (is_special_object(stdin_obj)) { 862 ensure_stream_common_props(js, stdin_obj, ANT_STDIN_FD); 863 if (vtype(js_get(js, stdin_obj, "isRaw")) == T_UNDEF) js_set(js, stdin_obj, "isRaw", js_false); 864 865 ant_value_t stdin_proto = js_get_proto(js, stdin_obj); 866 if (is_special_object(stdin_proto)) { 867 js_set_proto_init(stdin_proto, stream_readable_prototype(js)); 868 setup_readstream_proto(js, stdin_proto); 869 } 870 stream_init_readable_object(js, stdin_obj, js_mkundef()); 871 } 872 873 ant_value_t stdout_obj = js_get(js, process_obj, "stdout"); 874 if (is_special_object(stdout_obj)) { 875 ensure_stream_common_props(js, stdout_obj, ANT_STDOUT_FD); 876 js_set_getter_desc(js, stdout_obj, "rows", 4, js_mkfun(tty_write_stream_rows_getter), JS_DESC_E | JS_DESC_C); 877 js_set_getter_desc(js, stdout_obj, "columns", 7, js_mkfun(tty_write_stream_columns_getter), JS_DESC_E | JS_DESC_C); 878 879 ant_value_t stdout_proto = js_get_proto(js, stdout_obj); 880 if (is_special_object(stdout_proto)) js_set_proto_init(stdout_proto, stream_duplex_prototype(js)); 881 setup_writestream_proto(js, stdout_proto); 882 stream_init_duplex_object(js, stdout_obj, js_mkundef()); 883 js_set(js, stdout_obj, "readable", js_false); 884 } 885 886 ant_value_t stderr_obj = js_get(js, process_obj, "stderr"); 887 if (is_special_object(stderr_obj)) { 888 ensure_stream_common_props(js, stderr_obj, ANT_STDERR_FD); 889 js_set_getter_desc(js, stderr_obj, "rows", 4, js_mkfun(tty_write_stream_rows_getter), JS_DESC_E | JS_DESC_C); 890 js_set_getter_desc(js, stderr_obj, "columns", 7, js_mkfun(tty_write_stream_columns_getter), JS_DESC_E | JS_DESC_C); 891 892 ant_value_t stderr_proto = js_get_proto(js, stderr_obj); 893 if (is_special_object(stderr_proto)) js_set_proto_init(stderr_proto, stream_duplex_prototype(js)); 894 setup_writestream_proto(js, stderr_proto); 895 stream_init_duplex_object(js, stderr_obj, js_mkundef()); 896 js_set(js, stderr_obj, "readable", js_false); 897 } 898} 899 900ant_value_t tty_library(ant_t *js) { 901 ant_value_t lib = js_mkobj(js); 902 tty_init_stream_constructors(js); 903 904 js_set(js, lib, "isatty", js_mkfun(tty_isatty)); 905 js_set(js, lib, "ReadStream", g_tty_readstream_ctor); 906 js_set(js, lib, "WriteStream", g_tty_writestream_ctor); 907 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "tty", 3)); 908 909 return lib; 910}