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 1372 lines 44 kB view raw
1// TODO: cleanup module, make cleaner 2 3#include <compat.h> // IWYU pragma: keep 4 5#include <stdlib.h> 6#include <stdio.h> 7#include <string.h> 8#include <stdbool.h> 9#include <ctype.h> 10#include <uthash.h> 11#include <uv.h> 12 13#ifdef _WIN32 14#include <conio.h> 15#define WIN32_LEAN_AND_MEAN 16#include <windows.h> 17#include <io.h> 18#define STDIN_FILENO 0 19#define STDOUT_FILENO 1 20#else 21#include <termios.h> 22#include <unistd.h> 23#include <sys/select.h> 24#include <sys/ioctl.h> 25#endif 26 27#include "ant.h" 28#include "errors.h" 29#include "runtime.h" 30#include "internal.h" 31#include "descriptors.h" 32#include "tty_ctrl.h" 33#include "silver/engine.h" 34 35#include "gc/modules.h" 36#include "modules/events.h" 37#include "modules/readline.h" 38#include "modules/process.h" 39#include "modules/symbol.h" 40 41#define MAX_LINE_LENGTH 4096 42#define MAX_HISTORY 1000 43#define DEFAULT_PROMPT "> " 44#define DEFAULT_HISTORY_SIZE 30 45#define DEFAULT_TAB_SIZE 8 46 47typedef struct { 48 char **lines; 49 int count; 50 int capacity; 51 int current; 52} rl_history_t; 53 54typedef struct rl_interface { 55 uint64_t id; 56 ant_value_t input_stream; 57 ant_value_t output_stream; 58 ant_value_t completer; 59 ant_value_t js_obj; 60 char *prompt; 61 char *active_prompt; 62 char *line_buffer; 63 int line_pos; 64 int line_len; 65 rl_history_t history; 66 bool terminal; 67 bool paused; 68 bool closed; 69 bool reading; 70 int history_size; 71 bool remove_history_duplicates; 72 int crlf_delay; 73 int escape_code_timeout; 74 int tab_size; 75 ant_value_t pending_question_resolve; 76 ant_value_t pending_question_reject; 77 uv_tty_t tty_in; 78 uv_tty_t tty_out; 79 bool tty_initialized; 80 int escape_state; 81 char escape_buf[16]; 82 int escape_len; 83 int last_render_rows; 84 UT_hash_handle hh; 85#ifndef _WIN32 86 struct termios saved_termios; 87 bool raw_mode; 88 uv_signal_t sigint_watcher; 89 bool sigint_watcher_active; 90#endif 91} rl_interface_t; 92 93static uint64_t next_interface_id = 1; 94static rl_interface_t *interfaces = NULL; 95static ant_value_t g_rl_async_iter_proto = 0; 96static ant_value_t g_rl_interface_proto = 0; 97 98static const char *rl_render_prompt(const rl_interface_t *iface) { 99 if (!iface) return ""; 100 return iface->active_prompt ? iface->active_prompt : iface->prompt; 101} 102 103static void rl_set_active_prompt(rl_interface_t *iface, const char *prompt) { 104 if (!iface) return; 105 free(iface->active_prompt); 106 iface->active_prompt = prompt ? strdup(prompt) : NULL; 107} 108 109static void rl_clear_active_prompt(rl_interface_t *iface) { 110 if (!iface) return; 111 free(iface->active_prompt); 112 iface->active_prompt = NULL; 113} 114 115static void rl_history_init(rl_history_t *hist, int capacity) { 116 hist->capacity = capacity > 0 ? capacity : DEFAULT_HISTORY_SIZE; 117 hist->lines = calloc(hist->capacity, sizeof(char*)); 118 hist->count = 0; 119 hist->current = -1; 120} 121 122static void rl_history_remove_at(rl_history_t *hist, int index) { 123 if (!hist || index < 0 || index >= hist->count) return; 124 125 free(hist->lines[index]); 126 if (index < hist->count - 1) memmove( 127 hist->lines + index, 128 hist->lines + index + 1, 129 sizeof(char *) * (size_t)(hist->count - index - 1) 130 ); 131 132 hist->count--; 133} 134 135static void rl_history_add(rl_history_t *hist, const char *line, bool remove_duplicates) { 136 int duplicate_index = -1; 137 138 if (!line || line[0] == '\0') return; 139 if (!remove_duplicates && hist->count > 0 && strcmp(hist->lines[hist->count - 1], line) == 0) return; 140 141 if (remove_duplicates) { 142 for (int i = 0; i < hist->count; i++) { 143 if (strcmp(hist->lines[i], line) != 0) continue; 144 duplicate_index = i; 145 break; 146 }} 147 148 if (duplicate_index >= 0) rl_history_remove_at(hist, duplicate_index); 149 if (hist->count >= hist->capacity) rl_history_remove_at(hist, 0); 150 151 hist->lines[hist->count++] = strdup(line); 152 hist->current = hist->count; 153} 154 155static const char *rl_history_prev(rl_history_t *hist) { 156 if (hist->count == 0) return NULL; 157 if (hist->current > 0) hist->current--; 158 return hist->lines[hist->current]; 159} 160 161static const char *rl_history_next(rl_history_t *hist) { 162 if (hist->count == 0) return NULL; 163 if (hist->current < hist->count - 1) { 164 hist->current++; 165 return hist->lines[hist->current]; 166 } 167 hist->current = hist->count; 168 return ""; 169} 170 171static void rl_history_free(rl_history_t *hist) { 172 if (hist->lines) { 173 for (int i = 0; i < hist->count; i++) { 174 free(hist->lines[i]); 175 } 176 free(hist->lines); 177 hist->lines = NULL; 178 } 179 hist->count = 0; 180 hist->current = -1; 181} 182 183#ifndef _WIN32 184static void enter_raw_mode(rl_interface_t *iface) { 185 if (iface->raw_mode) return; 186 187 struct termios raw; 188 if (tcgetattr(STDIN_FILENO, &iface->saved_termios) == -1) return; 189 190 raw = iface->saved_termios; 191 cfmakeraw(&raw); 192 raw.c_lflag |= ISIG; 193 raw.c_oflag |= OPOST | ONLCR; 194 195 if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1) return; 196 iface->raw_mode = true; 197} 198 199static void exit_raw_mode(rl_interface_t *iface) { 200 if (!iface->raw_mode) return; 201 tcsetattr(STDIN_FILENO, TCSANOW, &iface->saved_termios); 202 iface->raw_mode = false; 203} 204#endif 205 206static void write_output(rl_interface_t *iface, const char *str) { 207 fputs(str, stdout); 208 fflush(stdout); 209} 210 211static int get_terminal_cols(void) { 212 int cols = 80; 213#ifndef _WIN32 214 struct winsize ws; 215 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) { 216 cols = ws.ws_col; 217 } 218#else 219 CONSOLE_SCREEN_BUFFER_INFO csbi; 220 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { 221 cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; 222 } 223#endif 224 return cols > 0 ? cols : 80; 225} 226 227static void move_cursor_to_line_start(rl_interface_t *iface, int cols) { 228 int prompt_len = (int)strlen(rl_render_prompt(iface)); 229 int cursor_cols = prompt_len + iface->line_pos; 230 int cursor_row = cursor_cols / cols; 231 if (cursor_cols > 0 && cursor_cols % cols == 0) cursor_row--; 232 233 if (cursor_row > 0) { 234 char move_buf[32]; 235 snprintf(move_buf, sizeof(move_buf), "\033[%dA", cursor_row); 236 write_output(iface, move_buf); 237 } 238 write_output(iface, "\r"); 239} 240 241static void clear_line_display(rl_interface_t *iface) { 242 int cols = get_terminal_cols(); 243 int prompt_len = (int)strlen(rl_render_prompt(iface)); 244 int line_cols = prompt_len + iface->line_len; 245 int current_rows = line_cols > 0 ? (line_cols - 1) / cols + 1 : 1; 246 int rows = iface->last_render_rows > current_rows ? iface->last_render_rows : current_rows; 247 248 move_cursor_to_line_start(iface, cols); 249 for (int i = 0; i < rows; i++) { 250 write_output(iface, "\033[K"); 251 if (i < rows - 1) { 252 write_output(iface, "\033[B\r"); 253 } 254 } 255 for (int i = 0; i < rows - 1; i++) { 256 write_output(iface, "\033[A"); 257 } 258 write_output(iface, "\r"); 259} 260 261static void refresh_line(rl_interface_t *iface) { 262 char buf[MAX_LINE_LENGTH + 256]; 263 int cols = get_terminal_cols(); 264 const char *prompt = rl_render_prompt(iface); 265 266 clear_line_display(iface); 267 snprintf(buf, sizeof(buf), "%s%s", prompt, iface->line_buffer); 268 write_output(iface, buf); 269 270 int prompt_len = (int)strlen(prompt); 271 int end_cols = prompt_len + iface->line_len; 272 int end_row = end_cols > 0 ? end_cols / cols : 0; 273 int cursor_cols = prompt_len + iface->line_pos; 274 int cursor_row = cursor_cols > 0 ? cursor_cols / cols : 0; 275 int cursor_col = cursor_cols > 0 ? cursor_cols % cols : 0; 276 int up_rows = end_row - cursor_row; 277 278 if (up_rows > 0) { 279 char move_buf[32]; 280 snprintf(move_buf, sizeof(move_buf), "\033[%dA", up_rows); 281 write_output(iface, move_buf); 282 } 283 write_output(iface, "\r"); 284 if (cursor_col > 0) { 285 char move_buf[32]; 286 snprintf(move_buf, sizeof(move_buf), "\033[%dC", cursor_col); 287 write_output(iface, move_buf); 288 } 289 290 iface->last_render_rows = end_cols > 0 ? end_cols / cols + 1 : 1; 291} 292 293static rl_interface_t *find_interface_by_id(uint64_t id) { 294 rl_interface_t *iface = NULL; 295 HASH_FIND(hh, interfaces, &id, sizeof(uint64_t), iface); 296 return iface; 297} 298 299static void handle_history_up(rl_interface_t *iface) { 300 const char *hist_line = rl_history_prev(&iface->history); 301 if (hist_line) { 302 strcpy(iface->line_buffer, hist_line); 303 iface->line_len = (int)strlen(iface->line_buffer); 304 iface->line_pos = iface->line_len; 305 refresh_line(iface); 306 } 307} 308 309static void handle_history_down(rl_interface_t *iface) { 310 const char *hist_line = rl_history_next(&iface->history); 311 if (hist_line) { 312 strcpy(iface->line_buffer, hist_line); 313 iface->line_len = (int)strlen(iface->line_buffer); 314 iface->line_pos = iface->line_len; 315 refresh_line(iface); 316 } 317} 318 319static void handle_char_input(rl_interface_t *iface, char c) { 320 if (iface->line_len < MAX_LINE_LENGTH - 1) { 321 memmove( 322 iface->line_buffer + iface->line_pos + 1, 323 iface->line_buffer + iface->line_pos, 324 iface->line_len - iface->line_pos + 1 325 ); 326 327 iface->line_buffer[iface->line_pos] = c; 328 iface->line_pos++; 329 iface->line_len++; 330 331 if (iface->line_pos == iface->line_len) { 332 int cols = get_terminal_cols(); 333 int prompt_len = (int)strlen(rl_render_prompt(iface)); 334 int total_cols = prompt_len + iface->line_len; 335 int rows = total_cols > 0 ? total_cols / cols + 1 : 1; 336 if (rows > iface->last_render_rows) iface->last_render_rows = rows; 337 printf("%c", c); 338 fflush(stdout); 339 } else refresh_line(iface); 340 } 341} 342 343static void handle_backspace(rl_interface_t *iface) { 344 if (iface->line_pos > 0) { 345 if (iface->line_pos == iface->line_len) { 346 iface->line_pos--; 347 iface->line_len--; 348 iface->line_buffer[iface->line_len] = '\0'; 349 write_output(iface, "\b \b"); 350 int cols = get_terminal_cols(); 351 int prompt_len = (int)strlen(rl_render_prompt(iface)); 352 int total_cols = prompt_len + iface->line_len; 353 iface->last_render_rows = total_cols > 0 ? total_cols / cols + 1 : 1; 354 return; 355 } 356 357 memmove( 358 iface->line_buffer + iface->line_pos - 1, 359 iface->line_buffer + iface->line_pos, 360 iface->line_len - iface->line_pos + 1 361 ); 362 iface->line_pos--; 363 iface->line_len--; 364 refresh_line(iface); 365 } 366} 367 368static void handle_delete(rl_interface_t *iface) { 369 if (iface->line_pos < iface->line_len) { 370 memmove( 371 iface->line_buffer + iface->line_pos, 372 iface->line_buffer + iface->line_pos + 1, 373 iface->line_len - iface->line_pos 374 ); 375 iface->line_len--; 376 refresh_line(iface); 377 } 378} 379 380static void handle_escape_sequence(rl_interface_t *iface, const char *seq, int len) { 381 if (len >= 2 && seq[0] == '[') { 382 switch (seq[1]) { 383 case 'A': handle_history_up(iface); break; 384 case 'B': handle_history_down(iface); break; 385 case 'C': 386 if (iface->line_pos < iface->line_len) { 387 iface->line_pos++; 388 printf("\033[C"); 389 fflush(stdout); 390 } 391 break; 392 case 'D': 393 if (iface->line_pos > 0) { 394 iface->line_pos--; 395 printf("\033[D"); 396 fflush(stdout); 397 } 398 break; 399 case 'H': 400 iface->line_pos = 0; 401 refresh_line(iface); 402 break; 403 case 'F': 404 iface->line_pos = iface->line_len; 405 refresh_line(iface); 406 break; 407 case '3': 408 if (len >= 3 && seq[2] == '~') { 409 handle_delete(iface); 410 } 411 break; 412 }} 413} 414 415static void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { 416 buf->base = malloc(suggested_size); 417 buf->len = suggested_size; 418} 419 420static void emit_event(ant_t *js, rl_interface_t *iface, const char *event_type, ant_value_t *args, int nargs) { 421 if (!iface || vtype(iface->js_obj) == T_UNDEF) return; 422 eventemitter_emit_args(js, iface->js_obj, event_type, args, nargs); 423} 424 425static ant_value_t get_history_array(ant_t *js, rl_interface_t *iface) { 426 ant_value_t arr = js_mkarr(js); 427 for (int i = 0; i < iface->history.count; i++) js_arr_push( 428 js, arr, js_mkstr(js, iface->history.lines[i], strlen(iface->history.lines[i])) 429 ); 430 return arr; 431} 432 433static void emit_history_event(ant_t *js, rl_interface_t *iface) { 434 ant_value_t history_arr = get_history_array(js, iface); 435 emit_event(js, iface, "history", &history_arr, 1); 436} 437 438 439static void stop_reading(rl_interface_t *iface) { 440 if (!iface->reading) return; 441 442 uv_read_stop((uv_stream_t *)&iface->tty_in); 443 iface->reading = false; 444#ifndef _WIN32 445 if (iface->sigint_watcher_active) { 446 uv_signal_stop(&iface->sigint_watcher); 447 iface->sigint_watcher_active = false; 448 } 449#endif 450} 451 452static bool rl_has_event_listener(ant_t *js, rl_interface_t *iface, const char *event_type) { 453 if (!iface || !event_type || vtype(iface->js_obj) == T_UNDEF) return false; 454 return eventemitter_listener_count(js, iface->js_obj, event_type) > 0; 455} 456 457static bool rl_add_listener(ant_t *js, rl_interface_t *iface, const char *event_type, ant_value_t listener, bool once) { 458 if (!iface || !event_type || vtype(iface->js_obj) == T_UNDEF) return false; 459 return eventemitter_add_listener(js, iface->js_obj, event_type, listener, once); 460} 461 462static void rl_remove_listener(ant_t *js, rl_interface_t *iface, const char *event_type, ant_value_t listener) { 463 if (!iface || !event_type || vtype(iface->js_obj) == T_UNDEF) return; 464 eventemitter_remove_listener(js, iface->js_obj, event_type, listener); 465} 466 467static ant_value_t rl_async_iter_state(ant_t *js, ant_value_t iterator) { 468 ant_value_t state = is_object_type(iterator) ? js_get_slot(iterator, SLOT_DATA) : js_mkundef(); 469 return is_object_type(state) ? state : js_mkundef(); 470} 471 472static ant_value_t rl_async_iter_queue(ant_t *js, ant_value_t state, const char *queue_key) { 473 ant_value_t queue = is_object_type(state) ? js_get(js, state, queue_key) : js_mkundef(); 474 return vtype(queue) == T_ARR ? queue : js_mkundef(); 475} 476 477static ant_offset_t rl_async_iter_queue_head(ant_t *js, ant_value_t state, const char *head_key) { 478 ant_value_t head = is_object_type(state) ? js_get(js, state, head_key) : js_mkundef(); 479 return vtype(head) == T_NUM ? (ant_offset_t)js_getnum(head) : 0; 480} 481 482static void rl_async_iter_set_queue_head(ant_t *js, ant_value_t state, const char *head_key, ant_offset_t head) { 483 if (is_object_type(state)) js_set(js, state, head_key, js_mknum((double)head)); 484} 485 486static void rl_close_interface(ant_t *js, rl_interface_t *iface) { 487 if (!iface || iface->closed) return; 488 stop_reading(iface); 489 490 if (iface->tty_initialized) { 491 uv_close((uv_handle_t *)&iface->tty_in, NULL); 492#ifndef _WIN32 493 if (!iface->sigint_watcher_active && uv_is_active((uv_handle_t *)&iface->sigint_watcher)) { 494 uv_close((uv_handle_t *)&iface->sigint_watcher, NULL); 495 } 496 exit_raw_mode(iface); 497#endif 498 iface->tty_initialized = false; 499 } 500 501 iface->closed = true; 502 emit_event(js, iface, "close", NULL, 0); 503} 504 505static void process_line(ant_t *js, rl_interface_t *iface) { 506 char *line = strdup(iface->line_buffer); 507 508 rl_history_add(&iface->history, line, iface->remove_history_duplicates); 509 emit_history_event(js, iface); 510 rl_clear_active_prompt(iface); 511 512 ant_value_t line_val = js_mkstr(js, line, strlen(line)); 513 emit_event(js, iface, "line", &line_val, 1); 514 515 if (vtype(iface->pending_question_resolve) == T_FUNC) { 516 sv_vm_call(js->vm, js, iface->pending_question_resolve, js_mkundef(), &line_val, 1, NULL, false); 517 iface->pending_question_resolve = js_mkundef(); 518 iface->pending_question_reject = js_mkundef(); 519 } 520 521 iface->line_buffer[0] = '\0'; 522 iface->line_pos = 0; 523 iface->line_len = 0; 524 525 free(line); 526} 527 528static void feed_escape(rl_interface_t *iface, char c) { 529 iface->escape_buf[iface->escape_len++] = c; 530 if (iface->escape_state == 1) { 531 iface->escape_state = (c == '[' || c == 'O') ? 2 : 0; 532 if (!iface->escape_state) iface->escape_len = 0; 533 return; 534 } 535 bool done = (c >= 'A' && c <= 'Z') || c == '~'; 536 if (done) handle_escape_sequence(iface, iface->escape_buf, iface->escape_len); 537 if (done || iface->escape_len >= 15) { iface->escape_state = 0; iface->escape_len = 0; } 538} 539 540static void process_byte(ant_t *js, rl_interface_t *iface, char c) { 541 if (iface->escape_state > 0) { feed_escape(iface, c); return; } 542 if (c == 27) { iface->escape_state = 1; iface->escape_len = 0; return; } 543 544 switch (c) { 545 case '\r': case '\n': 546 putchar('\n'); fflush(stdout); 547 process_line(js, iface); 548 break; 549 case 127: case 8: handle_backspace(iface); break; 550 case 4: 551 if (iface->line_len == 0) rl_close_interface(js, iface); 552 else handle_delete(iface); 553 break; 554 case 1: iface->line_pos = 0; refresh_line(iface); break; 555 case 5: iface->line_pos = iface->line_len; refresh_line(iface); break; 556 case 11: iface->line_buffer[iface->line_pos] = '\0'; iface->line_len = iface->line_pos; refresh_line(iface); break; 557 case 21: iface->line_buffer[0] = '\0'; iface->line_pos = 0; iface->line_len = 0; refresh_line(iface); break; 558 case 12: printf("\033[2J\033[H"); refresh_line(iface); break; 559 default: if (c >= 32 && c < 127) handle_char_input(iface, c); break; 560 } 561} 562 563static void on_stdin_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { 564 rl_interface_t *iface = (rl_interface_t *)stream->data; 565 ant_t *js = rt->js; 566 567 if (!iface || iface->closed || iface->paused) goto cleanup; 568 569 if (nread < 0) { 570 if (nread == UV_EOF) rl_close_interface(js, iface); 571 goto cleanup; 572 } 573 574 for (ssize_t i = 0; i < nread; i++) { 575 process_byte(js, iface, buf->base[i]); 576 if (iface->closed) break; 577 } 578 579 if (iface->closed) stop_reading(iface); 580 581cleanup: 582 free(buf->base); 583} 584 585#ifndef _WIN32 586static void on_sigint(uv_signal_t *handle, int signum) { 587 rl_interface_t *iface = (rl_interface_t *)handle->data; 588 ant_t *js = rt->js; 589 590 if (rl_has_event_listener(js, iface, "SIGINT")) { 591 emit_event(js, iface, "SIGINT", NULL, 0); 592 } else if (process_has_event_listeners("SIGINT")) { 593 ant_value_t sig_arg = js_mkstr(js, "SIGINT", 6); 594 emit_process_event("SIGINT", &sig_arg, 1); 595 } else { 596 uv_signal_stop(handle); 597 raise(SIGINT); 598 } 599} 600#endif 601 602static void start_reading(rl_interface_t *iface) { 603 if (iface->reading || iface->closed) return; 604 605 if (!iface->tty_initialized) { 606 uv_loop_t *loop = uv_default_loop(); 607 int is_tty = uv_guess_handle(STDIN_FILENO) == UV_TTY; 608 609 if (uv_tty_init(loop, &iface->tty_in, STDIN_FILENO, 1) != 0) return; 610 611 if (is_tty) { 612#ifndef _WIN32 613 enter_raw_mode(iface); 614 uv_signal_init(loop, &iface->sigint_watcher); 615 iface->sigint_watcher.data = iface; 616 uv_signal_start(&iface->sigint_watcher, on_sigint, SIGINT); 617 iface->sigint_watcher_active = true; 618#endif 619 } 620 621 iface->tty_in.data = iface; 622 iface->tty_initialized = true; 623 } 624 625 iface->reading = true; 626 uv_read_start((uv_stream_t *)&iface->tty_in, alloc_buffer, on_stdin_read); 627} 628 629static rl_interface_t *get_interface(ant_t *js, ant_value_t this_obj) { 630 ant_value_t id_val = js_get(js, this_obj, "_rl_id"); 631 if (vtype(id_val) != T_NUM) return NULL; 632 633 uint64_t id = (uint64_t)js_getnum(id_val); 634 rl_interface_t *iface = NULL; 635 HASH_FIND(hh, interfaces, &id, sizeof(uint64_t), iface); 636 return iface; 637} 638 639static ant_value_t rl_interface_close(ant_t *js, ant_value_t *args, int nargs) { 640 ant_value_t this_obj = js_getthis(js); 641 rl_interface_t *iface = get_interface(js, this_obj); 642 643 if (!iface || iface->closed) return js_mkundef(); 644 rl_close_interface(js, iface); 645 646 return js_mkundef(); 647} 648 649static ant_value_t rl_interface_pause(ant_t *js, ant_value_t *args, int nargs) { 650 ant_value_t this_obj = js_getthis(js); 651 rl_interface_t *iface = get_interface(js, this_obj); 652 if (!iface) return js_mkerr(js, "Invalid Interface"); 653 654 if (!iface->paused) { 655 iface->paused = true; 656 stop_reading(iface); 657 emit_event(js, iface, "pause", NULL, 0); 658 } 659 660 return this_obj; 661} 662 663static ant_value_t rl_interface_resume(ant_t *js, ant_value_t *args, int nargs) { 664 ant_value_t this_obj = js_getthis(js); 665 rl_interface_t *iface = get_interface(js, this_obj); 666 if (!iface) return js_mkerr(js, "Invalid Interface"); 667 668 if (iface->paused) { 669 iface->paused = false; 670 start_reading(iface); 671 emit_event(js, iface, "resume", NULL, 0); 672 } 673 674 return this_obj; 675} 676 677static ant_value_t rl_interface_prompt(ant_t *js, ant_value_t *args, int nargs) { 678 ant_value_t this_obj = js_getthis(js); 679 rl_interface_t *iface = get_interface(js, this_obj); 680 681 if (!iface || iface->closed) return js_mkundef(); 682 683 if (iface->paused) { 684 iface->paused = false; 685 emit_event(js, iface, "resume", NULL, 0); 686 } 687 688 bool preserve_cursor = false; 689 if (nargs > 0) preserve_cursor = js_truthy(js, args[0]); 690 691 if (!preserve_cursor) { 692 iface->line_buffer[0] = '\0'; 693 iface->line_pos = 0; 694 iface->line_len = 0; 695 } 696 697 write_output(iface, rl_render_prompt(iface)); 698 if (iface->line_len > 0) { 699 write_output(iface, iface->line_buffer); 700 } 701 702 start_reading(iface); 703 return js_mkundef(); 704} 705 706static ant_value_t rl_interface_set_prompt(ant_t *js, ant_value_t *args, int nargs) { 707 ant_value_t this_obj = js_getthis(js); 708 rl_interface_t *iface = get_interface(js, this_obj); 709 710 if (!iface) return js_mkerr(js, "Invalid Interface"); 711 if (nargs < 1) return js_mkundef(); 712 713 char *new_prompt = js_getstr(js, args[0], NULL); 714 if (new_prompt) { 715 free(iface->prompt); 716 iface->prompt = strdup(new_prompt); 717 } 718 719 return js_mkundef(); 720} 721 722static ant_value_t rl_interface_get_prompt(ant_t *js, ant_value_t *args, int nargs) { 723 ant_value_t this_obj = js_getthis(js); 724 rl_interface_t *iface = get_interface(js, this_obj); 725 if (!iface) return js_mkerr(js, "Invalid Interface"); 726 727 return js_mkstr(js, iface->prompt, strlen(iface->prompt)); 728} 729 730static void process_key_sequence(rl_interface_t *iface, const char *name, bool ctrl, bool meta, bool shift) { 731 (void)meta; (void)shift; 732 733 if (!name) return; 734 735 if (strcmp(name, "return") == 0 || strcmp(name, "enter") == 0) { 736 printf("\n"); 737 fflush(stdout); 738 } else if (strcmp(name, "backspace") == 0) { 739 if (iface->line_pos > 0) { 740 memmove(iface->line_buffer + iface->line_pos - 1, 741 iface->line_buffer + iface->line_pos, 742 iface->line_len - iface->line_pos + 1); 743 iface->line_pos--; 744 iface->line_len--; 745 } 746 } else if (strcmp(name, "delete") == 0) { 747 if (iface->line_pos < iface->line_len) { 748 memmove(iface->line_buffer + iface->line_pos, 749 iface->line_buffer + iface->line_pos + 1, 750 iface->line_len - iface->line_pos); 751 iface->line_len--; 752 } 753 } else if (strcmp(name, "left") == 0) { 754 if (iface->line_pos > 0) iface->line_pos--; 755 } else if (strcmp(name, "right") == 0) { 756 if (iface->line_pos < iface->line_len) iface->line_pos++; 757 } else if (strcmp(name, "home") == 0 || (ctrl && strcmp(name, "a") == 0)) { 758 iface->line_pos = 0; 759 } else if (strcmp(name, "end") == 0 || (ctrl && strcmp(name, "e") == 0)) { 760 iface->line_pos = iface->line_len; 761 } else if (ctrl && strcmp(name, "u") == 0) { 762 iface->line_buffer[0] = '\0'; 763 iface->line_pos = 0; 764 iface->line_len = 0; 765 } else if (ctrl && strcmp(name, "k") == 0) { 766 iface->line_buffer[iface->line_pos] = '\0'; 767 iface->line_len = iface->line_pos; 768 } 769} 770 771static ant_value_t rl_interface_write(ant_t *js, ant_value_t *args, int nargs) { 772 ant_value_t this_obj = js_getthis(js); 773 rl_interface_t *iface = get_interface(js, this_obj); 774 775 if (!iface || iface->closed) return js_mkundef(); 776 777 if (iface->paused) { 778 iface->paused = false; 779 emit_event(js, iface, "resume", NULL, 0); 780 } 781 782 if (nargs >= 2 && is_special_object(args[1])) { 783 ant_value_t key = args[1]; 784 ant_value_t name_val = js_get(js, key, "name"); 785 ant_value_t ctrl_val = js_get(js, key, "ctrl"); 786 ant_value_t meta_val = js_get(js, key, "meta"); 787 ant_value_t shift_val = js_get(js, key, "shift"); 788 789 char *name = (vtype(name_val) == T_STR) ? js_getstr(js, name_val, NULL) : NULL; 790 bool ctrl = js_truthy(js, ctrl_val); 791 bool meta = js_truthy(js, meta_val); 792 bool shift = js_truthy(js, shift_val); 793 794 if (name) { 795 process_key_sequence(iface, name, ctrl, meta, shift); 796 return js_mkundef(); 797 } 798 } 799 800 if (nargs < 1 || vtype(args[0]) == T_NULL || vtype(args[0]) == T_UNDEF) { 801 return js_mkundef(); 802 } 803 804 size_t len; 805 char *data = js_getstr(js, args[0], &len); 806 if (!data) return js_mkundef(); 807 808 for (size_t i = 0; i < len && iface->line_len < MAX_LINE_LENGTH - 1; i++) { 809 char c = data[i]; 810 811 if (c == '\n' || c == '\r') { 812 process_line(js, iface); 813 } else { 814 memmove(iface->line_buffer + iface->line_pos + 1, 815 iface->line_buffer + iface->line_pos, 816 iface->line_len - iface->line_pos + 1); 817 iface->line_buffer[iface->line_pos] = c; 818 iface->line_pos++; 819 iface->line_len++; 820 } 821 } 822 823 return js_mkundef(); 824} 825 826static ant_value_t rl_interface_line_getter(ant_t *js, ant_value_t *args, int nargs) { 827 ant_value_t this_obj = js_getthis(js); 828 rl_interface_t *iface = get_interface(js, this_obj); 829 830 if (!iface) return js_mkundef(); 831 return js_mkstr(js, iface->line_buffer, strlen(iface->line_buffer)); 832} 833 834static ant_value_t rl_interface_cursor_getter(ant_t *js, ant_value_t *args, int nargs) { 835 ant_value_t this_obj = js_getthis(js); 836 rl_interface_t *iface = get_interface(js, this_obj); 837 838 if (!iface) return js_mknum(0); 839 return js_mknum((double)iface->line_pos); 840} 841 842static ant_value_t rl_interface_question_callback(ant_t *js, ant_value_t *args, int nargs) { 843 ant_value_t this_obj = js_getthis(js); 844 rl_interface_t *iface = get_interface(js, this_obj); 845 846 if (!iface || iface->closed) return js_mkundef(); 847 if (nargs < 2) return js_mkerr(js, "question requires query and callback"); 848 849 size_t query_len; 850 char *query = js_getstr(js, args[0], &query_len); 851 if (!query) return js_mkerr(js, "query must be a string"); 852 853 int t = vtype(args[1]); 854 if (t != T_FUNC && t != T_CFUNC) { 855 return js_mkerr(js, "callback must be a function"); 856 } 857 858 rl_set_active_prompt(iface, query); 859 write_output(iface, rl_render_prompt(iface)); 860 if (!rl_add_listener(js, iface, "line", args[1], true)) return js_mkerr(js, "listener must be a function"); 861 862 return js_mkundef(); 863} 864 865static ant_value_t rl_interface_question_promise(ant_t *js, ant_value_t *args, int nargs) { 866 ant_value_t this_obj = js_getthis(js); 867 rl_interface_t *iface = get_interface(js, this_obj); 868 869 if (!iface || iface->closed) return js_mkerr(js, "Interface is closed"); 870 if (nargs < 1) return js_mkerr(js, "question requires a query string"); 871 872 size_t query_len; 873 char *query = js_getstr(js, args[0], &query_len); 874 if (!query) return js_mkerr(js, "query must be a string"); 875 876 ant_value_t promise = js_mkpromise(js); 877 878 rl_set_active_prompt(iface, query); 879 write_output(iface, rl_render_prompt(iface)); 880 881 iface->pending_question_resolve = js_get(js, promise, "_resolve"); 882 iface->pending_question_reject = js_get(js, promise, "_reject"); 883 884 return promise; 885} 886 887static ant_value_t rl_interface_get_cursor_pos(ant_t *js, ant_value_t *args, int nargs) { 888 ant_value_t this_obj = js_getthis(js); 889 rl_interface_t *iface = get_interface(js, this_obj); 890 891 if (!iface) { 892 ant_value_t result = js_mkobj(js); 893 js_set(js, result, "rows", js_mknum(0)); 894 js_set(js, result, "cols", js_mknum(0)); 895 return result; 896 } 897 898 int prompt_len = (int)strlen(rl_render_prompt(iface)); 899 int total_cols = prompt_len + iface->line_pos; 900 901 int cols = 80; 902#ifndef _WIN32 903 struct winsize ws; 904 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) { 905 cols = ws.ws_col; 906 } 907#else 908 CONSOLE_SCREEN_BUFFER_INFO csbi; 909 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { 910 cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; 911 } 912#endif 913 914 int rows = total_cols / cols; 915 int col_pos = total_cols % cols; 916 917 ant_value_t result = js_mkobj(js); 918 js_set(js, result, "rows", js_mknum((double)rows)); 919 js_set(js, result, "cols", js_mknum((double)col_pos)); 920 return result; 921} 922 923static ant_value_t rl_interface_closed_getter(ant_t *js, ant_value_t *args, int nargs) { 924 ant_value_t this_obj = js_getthis(js); 925 rl_interface_t *iface = get_interface(js, this_obj); 926 927 if (!iface) return js_true; 928 return js_bool(iface->closed); 929} 930 931static void free_interface(rl_interface_t *iface) { 932 if (!iface) return; 933 934 HASH_DEL(interfaces, iface); 935 936 free(iface->prompt); 937 free(iface->active_prompt); 938 free(iface->line_buffer); 939 rl_history_free(&iface->history); 940 free(iface); 941} 942 943static ant_value_t rl_clear_line(ant_t *js, ant_value_t *args, int nargs) { 944 int dir = 0; 945 if (!tty_ctrl_parse_clear_line_dir(args, nargs, 1, &dir)) return js_false; 946 947 size_t seq_len = 0; 948 const char *seq = tty_ctrl_clear_line_seq(dir, &seq_len); 949 return tty_ctrl_bool_result(js, tty_ctrl_write_stream(stdout, seq, seq_len, true)); 950} 951 952static ant_value_t rl_clear_screen_down(ant_t *js, ant_value_t *args, int nargs) { 953 size_t seq_len = 0; 954 const char *seq = tty_ctrl_clear_screen_down_seq(&seq_len); 955 return tty_ctrl_bool_result(js, tty_ctrl_write_stream(stdout, seq, seq_len, true)); 956} 957 958static ant_value_t rl_cursor_to(ant_t *js, ant_value_t *args, int nargs) { 959 tty_ctrl_cursor_to_args_t parsed; 960 if (!tty_ctrl_parse_cursor_to_args(args, nargs, 1, 2, &parsed)) return js_false; 961 962 char seq[64]; 963 size_t seq_len = 0; 964 bool ok = tty_ctrl_build_cursor_to( 965 seq, sizeof(seq), 966 parsed.x, parsed.has_y, parsed.y, 967 &seq_len 968 ); 969 if (!ok) return js_false; 970 971 return tty_ctrl_bool_result(js, tty_ctrl_write_stream(stdout, seq, seq_len, true)); 972} 973 974static ant_value_t rl_move_cursor(ant_t *js, ant_value_t *args, int nargs) { 975 int dx = 0; 976 int dy = 0; 977 if (!tty_ctrl_parse_move_cursor_args(args, nargs, 1, 2, &dx, &dy)) return js_false; 978 979 bool ok = true; 980 if (dx != 0) { 981 char seq_x[32]; 982 size_t len_x = 0; 983 ok = tty_ctrl_build_move_cursor_axis(seq_x, sizeof(seq_x), dx, true, &len_x); 984 if (ok) ok = tty_ctrl_write_stream(stdout, seq_x, len_x, false); 985 } 986 987 if (ok && dy != 0) { 988 char seq_y[32]; 989 size_t len_y = 0; 990 ok = tty_ctrl_build_move_cursor_axis(seq_y, sizeof(seq_y), dy, false, &len_y); 991 if (ok) ok = tty_ctrl_write_stream(stdout, seq_y, len_y, false); 992 } 993 994 if (!ok) return js_false; 995 return tty_ctrl_bool_result(js, fflush(stdout) == 0); 996} 997 998static ant_value_t rl_emit_keypress_events(ant_t *js, ant_value_t *args, int nargs) { 999 if (nargs > 0) { 1000 ant_value_t stdin_obj = js_get(js, js_get(js, js_glob(js), "process"), "stdin"); 1001 if (stdin_obj != args[0]) { 1002 return js_mkerr(js, "emitKeypressEvents only supports process.stdin"); 1003 } 1004 } 1005 process_enable_keypress_events(); 1006 return js_mkundef(); 1007} 1008 1009bool has_active_readline_interfaces(void) { 1010 rl_interface_t *iface, *tmp; 1011 HASH_ITER(hh, interfaces, iface, tmp) { 1012 if (!iface->closed && iface->reading) return true; 1013 } 1014 return false; 1015} 1016 1017static void rl_async_iter_compact_queue(ant_t *js, ant_value_t state, const char *queue_key, const char *head_key) { 1018 ant_value_t queue = rl_async_iter_queue(js, state, queue_key); 1019 ant_offset_t head = rl_async_iter_queue_head(js, state, head_key); 1020 ant_offset_t len = vtype(queue) == T_ARR ? js_arr_len(js, queue) : 0; 1021 ant_value_t compact = 0; 1022 1023 if (vtype(queue) != T_ARR || head == 0) return; 1024 1025 if (head >= len) { 1026 js_set(js, state, queue_key, js_mkarr(js)); 1027 js_set(js, state, head_key, js_mknum(0)); 1028 return; 1029 } 1030 1031 if (head <= 32 && head * 2 < len) return; 1032 1033 compact = js_mkarr(js); 1034 for (ant_offset_t i = head; i < len; i++) js_arr_push(js, compact, js_arr_get(js, queue, i)); 1035 js_set(js, state, queue_key, compact); 1036 js_set(js, state, head_key, js_mknum(0)); 1037} 1038 1039static void rl_async_iter_queue_push(ant_t *js, ant_value_t state, const char *queue_key, ant_value_t value) { 1040 ant_value_t queue = rl_async_iter_queue(js, state, queue_key); 1041 if (vtype(queue) == T_ARR) js_arr_push(js, queue, value); 1042} 1043 1044static ant_value_t rl_async_iter_queue_shift(ant_t *js, ant_value_t state, const char *queue_key, const char *head_key) { 1045 ant_value_t queue = rl_async_iter_queue(js, state, queue_key); 1046 ant_offset_t head = rl_async_iter_queue_head(js, state, head_key); 1047 ant_offset_t len = vtype(queue) == T_ARR ? js_arr_len(js, queue) : 0; 1048 ant_value_t value = js_mkundef(); 1049 1050 if (vtype(queue) != T_ARR || head >= len) return js_mkundef(); 1051 value = js_arr_get(js, queue, head); 1052 rl_async_iter_set_queue_head(js, state, head_key, head + 1); 1053 rl_async_iter_compact_queue(js, state, queue_key, head_key); 1054 1055 return value; 1056} 1057 1058static void rl_async_iter_cleanup(ant_t *js, ant_value_t state) { 1059 ant_value_t iface_obj = is_object_type(state) ? js_get(js, state, "iface") : js_mkundef(); 1060 rl_interface_t *iface = get_interface(js, iface_obj); 1061 if (!iface) return; 1062 1063 rl_remove_listener(js, iface, "line", js_get(js, state, "onLine")); 1064 rl_remove_listener(js, iface, "close", js_get(js, state, "onClose")); 1065} 1066 1067static void rl_async_iter_finish(ant_t *js, ant_value_t state) { 1068 if (!is_object_type(state)) return; 1069 1070 js_set(js, state, "done", js_true); 1071 rl_async_iter_cleanup(js, state); 1072 1073 for (;;) { 1074 ant_value_t pending = rl_async_iter_queue_shift(js, state, "pending", "pendingHead"); 1075 if (vtype(pending) != T_PROMISE) break; 1076 js_resolve_promise(js, pending, js_iter_result(js, false, js_mkundef())); 1077 } 1078} 1079 1080static ant_value_t rl_async_iter_on_line(ant_t *js, ant_value_t *args, int nargs) { 1081 ant_value_t state = rl_async_iter_state(js, js_getcurrentfunc(js)); 1082 ant_value_t line = nargs > 0 ? args[0] : js_mkundef(); 1083 ant_value_t pending = 0; 1084 1085 if (!is_object_type(state)) return js_mkundef(); 1086 if (js_truthy(js, js_get(js, state, "done"))) return js_mkundef(); 1087 1088 pending = rl_async_iter_queue_shift(js, state, "pending", "pendingHead"); 1089 if (vtype(pending) == T_PROMISE) { 1090 js_resolve_promise(js, pending, js_iter_result(js, true, line)); 1091 } else rl_async_iter_queue_push(js, state, "buffer", line); 1092 1093 return js_mkundef(); 1094} 1095 1096static ant_value_t rl_async_iter_on_close(ant_t *js, ant_value_t *args, int nargs) { 1097 ant_value_t state = rl_async_iter_state(js, js_getcurrentfunc(js)); 1098 rl_async_iter_finish(js, state); 1099 return js_mkundef(); 1100} 1101 1102static ant_value_t rl_async_iter_next(ant_t *js, ant_value_t *args, int nargs) { 1103 ant_value_t state = rl_async_iter_state(js, js_getthis(js)); 1104 ant_value_t promise = js_mkpromise(js); 1105 ant_value_t value = 0; 1106 1107 if (!is_object_type(state)) { 1108 js_resolve_promise(js, promise, js_iter_result(js, false, js_mkundef())); 1109 return promise; 1110 } 1111 1112 value = rl_async_iter_queue_shift(js, state, "buffer", "bufferHead"); 1113 if (vtype(value) != T_UNDEF) { 1114 js_resolve_promise(js, promise, js_iter_result(js, true, value)); 1115 return promise; 1116 } 1117 1118 if (js_truthy(js, js_get(js, state, "done"))) { 1119 js_resolve_promise(js, promise, js_iter_result(js, false, js_mkundef())); 1120 return promise; 1121 } 1122 1123 rl_async_iter_queue_push(js, state, "pending", promise); 1124 return promise; 1125} 1126 1127static ant_value_t rl_async_iter_return(ant_t *js, ant_value_t *args, int nargs) { 1128 ant_value_t state = rl_async_iter_state(js, js_getthis(js)); 1129 ant_value_t promise = js_mkpromise(js); 1130 1131 if (!is_object_type(state)) { 1132 js_resolve_promise(js, promise, js_iter_result(js, false, js_mkundef())); 1133 return promise; 1134 } 1135 1136 if (!js_truthy(js, js_get(js, state, "done"))) { 1137 ant_value_t iface_obj = js_get(js, state, "iface"); 1138 rl_interface_t *iface = get_interface(js, iface_obj); 1139 1140 if (iface && !iface->closed) { 1141 ant_value_t old_this = js_getthis(js); 1142 js_setthis(js, iface_obj); 1143 rl_interface_close(js, NULL, 0); 1144 js_setthis(js, old_this); 1145 } else rl_async_iter_finish(js, state); 1146 } 1147 1148 js_resolve_promise(js, promise, js_iter_result(js, false, js_mkundef())); 1149 return promise; 1150} 1151 1152static ant_value_t rl_get_async_iter_proto(ant_t *js) { 1153 if (is_object_type(g_rl_async_iter_proto)) return g_rl_async_iter_proto; 1154 1155 g_rl_async_iter_proto = js_mkobj(js); 1156 js_set(js, g_rl_async_iter_proto, "next", js_mkfun(rl_async_iter_next)); 1157 js_set(js, g_rl_async_iter_proto, "return", js_mkfun(rl_async_iter_return)); 1158 js_set_sym(js, g_rl_async_iter_proto, get_asyncIterator_sym(), js_mkfun(sym_this_cb)); 1159 js_set_sym(js, g_rl_async_iter_proto, get_toStringTag_sym(), js_mkstr(js, "AsyncIterator", 13)); 1160 1161 return g_rl_async_iter_proto; 1162} 1163 1164static ant_value_t rl_interface_async_iterator(ant_t *js, ant_value_t *args, int nargs) { 1165 ant_value_t this_obj = js_getthis(js); 1166 rl_interface_t *iface = get_interface(js, this_obj); 1167 1168 ant_value_t iterator = 0; 1169 ant_value_t state = 0; 1170 ant_value_t on_line = 0; 1171 ant_value_t on_close = 0; 1172 1173 if (!iface) return js_mkerr(js, "Invalid Interface"); 1174 1175 iterator = js_mkobj(js); 1176 state = js_mkobj(js); 1177 1178 js_set_proto_init(iterator, rl_get_async_iter_proto(js)); 1179 js_set_slot_wb(js, iterator, SLOT_DATA, state); 1180 1181 js_set(js, state, "iface", this_obj); 1182 js_set(js, state, "buffer", js_mkarr(js)); 1183 js_set(js, state, "bufferHead", js_mknum(0)); 1184 js_set(js, state, "pending", js_mkarr(js)); 1185 js_set(js, state, "pendingHead", js_mknum(0)); 1186 js_set(js, state, "done", js_bool(iface->closed)); 1187 1188 if (!iface->closed) { 1189 on_line = js_heavy_mkfun(js, rl_async_iter_on_line, state); 1190 on_close = js_heavy_mkfun(js, rl_async_iter_on_close, state); 1191 js_set(js, state, "onLine", on_line); 1192 js_set(js, state, "onClose", on_close); 1193 1194 if (!rl_add_listener(js, iface, "line", on_line, false) || !rl_add_listener(js, iface, "close", on_close, false)) { 1195 rl_remove_listener(js, iface, "line", on_line); 1196 rl_remove_listener(js, iface, "close", on_close); 1197 return js_mkerr(js, "listener must be a function"); 1198 }} 1199 1200 return iterator; 1201} 1202 1203static ant_value_t rl_get_interface_proto(ant_t *js) { 1204 if (is_object_type(g_rl_interface_proto)) return g_rl_interface_proto; 1205 1206 g_rl_interface_proto = js_mkobj(js); 1207 js_set_proto_init(g_rl_interface_proto, eventemitter_prototype(js)); 1208 1209 js_set(js, g_rl_interface_proto, "close", js_mkfun(rl_interface_close)); 1210 js_set(js, g_rl_interface_proto, "pause", js_mkfun(rl_interface_pause)); 1211 js_set(js, g_rl_interface_proto, "resume", js_mkfun(rl_interface_resume)); 1212 js_set(js, g_rl_interface_proto, "prompt", js_mkfun(rl_interface_prompt)); 1213 js_set(js, g_rl_interface_proto, "setPrompt", js_mkfun(rl_interface_set_prompt)); 1214 js_set(js, g_rl_interface_proto, "getPrompt", js_mkfun(rl_interface_get_prompt)); 1215 js_set(js, g_rl_interface_proto, "write", js_mkfun(rl_interface_write)); 1216 js_set(js, g_rl_interface_proto, "question", js_mkfun(rl_interface_question_callback)); 1217 js_set(js, g_rl_interface_proto, "getCursorPos", js_mkfun(rl_interface_get_cursor_pos)); 1218 1219 js_set_getter_desc(js, g_rl_interface_proto, "line", 4, js_mkfun(rl_interface_line_getter), JS_DESC_E | JS_DESC_C); 1220 js_set_getter_desc(js, g_rl_interface_proto, "cursor", 6, js_mkfun(rl_interface_cursor_getter), JS_DESC_E | JS_DESC_C); 1221 js_set_getter_desc(js, g_rl_interface_proto, "closed", 6, js_mkfun(rl_interface_closed_getter), JS_DESC_E | JS_DESC_C); 1222 js_set_sym(js, g_rl_interface_proto, get_asyncIterator_sym(), js_mkfun(rl_interface_async_iterator)); 1223 js_set_sym(js, g_rl_interface_proto, get_toStringTag_sym(), js_mkstr(js, "Interface", 9)); 1224 1225 return g_rl_interface_proto; 1226} 1227 1228static ant_value_t rl_create_interface(ant_t *js, ant_value_t *args, int nargs) { 1229 if (nargs < 1) return js_mkerr(js, "createInterface requires options"); 1230 1231 ant_value_t options = args[0]; 1232 if (!is_special_object(options)) return js_mkerr(js, "options must be an object"); 1233 1234 rl_interface_t *iface = calloc(1, sizeof(rl_interface_t)); 1235 if (!iface) return js_mkerr(js, "out of memory"); 1236 1237 iface->id = next_interface_id++; 1238 iface->prompt = strdup(DEFAULT_PROMPT); 1239 iface->active_prompt = NULL; 1240 iface->line_buffer = calloc(MAX_LINE_LENGTH, 1); 1241 iface->line_pos = 0; 1242 iface->line_len = 0; 1243 iface->paused = false; 1244 iface->closed = false; 1245 iface->reading = false; 1246 iface->pending_question_resolve = js_mkundef(); 1247 iface->pending_question_reject = js_mkundef(); 1248 iface->tty_initialized = false; 1249 iface->escape_state = 0; 1250 iface->escape_len = 0; 1251 iface->last_render_rows = 1; 1252 iface->js_obj = js_mkundef(); 1253#ifndef _WIN32 1254 iface->raw_mode = false; 1255#endif 1256 1257 iface->input_stream = js_get(js, options, "input"); 1258 iface->output_stream = js_get(js, options, "output"); 1259 1260 ant_value_t terminal_val = js_get(js, options, "terminal"); 1261 iface->terminal = terminal_val == js_true || vtype(terminal_val) == T_UNDEF; 1262 1263 ant_value_t history_size_val = js_get(js, options, "historySize"); 1264 iface->history_size = (vtype(history_size_val) == T_NUM) 1265 ? (int)js_getnum(history_size_val) 1266 : DEFAULT_HISTORY_SIZE; 1267 1268 ant_value_t remove_dup_val = js_get(js, options, "removeHistoryDuplicates"); 1269 iface->remove_history_duplicates = js_truthy(js, remove_dup_val); 1270 1271 ant_value_t prompt_val = js_get(js, options, "prompt"); 1272 if (vtype(prompt_val) == T_STR) { 1273 free(iface->prompt); 1274 iface->prompt = strdup(js_getstr(js, prompt_val, NULL)); 1275 } 1276 1277 ant_value_t crlf_delay_val = js_get(js, options, "crlfDelay"); 1278 iface->crlf_delay = (vtype(crlf_delay_val) == T_NUM) 1279 ? (int)js_getnum(crlf_delay_val) 1280 : 100; 1281 if (iface->crlf_delay < 100) iface->crlf_delay = 100; 1282 1283 ant_value_t tab_size_val = js_get(js, options, "tabSize"); 1284 iface->tab_size = (vtype(tab_size_val) == T_NUM) 1285 ? (int)js_getnum(tab_size_val) 1286 : DEFAULT_TAB_SIZE; 1287 if (iface->tab_size < 1) iface->tab_size = 1; 1288 1289 ant_value_t completer_val = js_get(js, options, "completer"); 1290 int ctype = vtype(completer_val); 1291 iface->completer = (ctype == T_FUNC || ctype == T_CFUNC) ? completer_val : js_mkundef(); 1292 1293 ant_value_t history_val = js_get(js, options, "history"); 1294 if (is_special_object(history_val)) { 1295 ant_value_t len_val = js_get(js, history_val, "length"); 1296 int len = (vtype(len_val) == T_NUM) ? (int)js_getnum(len_val) : 0; 1297 1298 rl_history_init(&iface->history, iface->history_size); 1299 1300 for (int i = 0; i < len; i++) { 1301 char key[16]; 1302 snprintf(key, sizeof(key), "%d", i); 1303 ant_value_t item = js_get(js, history_val, key); 1304 if (vtype(item) == T_STR) { 1305 char *line = js_getstr(js, item, NULL); 1306 if (line) rl_history_add(&iface->history, line, false); 1307 } 1308 } 1309 } else rl_history_init(&iface->history, iface->history_size); 1310 HASH_ADD(hh, interfaces, id, sizeof(uint64_t), iface); 1311 1312 ant_value_t obj = js_mkobj(js); 1313 js_set_proto_init(obj, rl_get_interface_proto(js)); 1314 iface->js_obj = obj; 1315 js_set(js, obj, "_rl_id", js_mknum((double)iface->id)); 1316 js_set(js, obj, "terminal", js_bool(iface->terminal)); 1317 1318 start_reading(iface); 1319 1320 return obj; 1321} 1322 1323static ant_value_t rl_create_interface_promises(ant_t *js, ant_value_t *args, int nargs) { 1324 ant_value_t iface_obj = rl_create_interface(js, args, nargs); 1325 if (vtype(iface_obj) == T_ERR) return iface_obj; 1326 js_set(js, iface_obj, "question", js_mkfun(rl_interface_question_promise)); 1327 1328 return iface_obj; 1329} 1330 1331ant_value_t readline_library(ant_t *js) { 1332 ant_value_t lib = js_mkobj(js); 1333 1334 js_set(js, lib, "createInterface", js_mkfun(rl_create_interface)); 1335 js_set(js, lib, "clearLine", js_mkfun(rl_clear_line)); 1336 js_set(js, lib, "clearScreenDown", js_mkfun(rl_clear_screen_down)); 1337 js_set(js, lib, "cursorTo", js_mkfun(rl_cursor_to)); 1338 js_set(js, lib, "moveCursor", js_mkfun(rl_move_cursor)); 1339 js_set(js, lib, "emitKeypressEvents", js_mkfun(rl_emit_keypress_events)); 1340 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "readline", 8)); 1341 1342 return lib; 1343} 1344 1345ant_value_t readline_promises_library(ant_t *js) { 1346 ant_value_t lib = js_mkobj(js); 1347 1348 js_set(js, lib, "createInterface", js_mkfun(rl_create_interface_promises)); 1349 js_set(js, lib, "clearLine", js_mkfun(rl_clear_line)); 1350 js_set(js, lib, "clearScreenDown", js_mkfun(rl_clear_screen_down)); 1351 js_set(js, lib, "cursorTo", js_mkfun(rl_cursor_to)); 1352 js_set(js, lib, "moveCursor", js_mkfun(rl_move_cursor)); 1353 js_set(js, lib, "emitKeypressEvents", js_mkfun(rl_emit_keypress_events)); 1354 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "readline/promises", 17)); 1355 1356 return lib; 1357} 1358 1359void gc_mark_readline(ant_t *js, gc_mark_fn mark) { 1360 if (g_rl_async_iter_proto) mark(js, g_rl_async_iter_proto); 1361 if (g_rl_interface_proto) mark(js, g_rl_interface_proto); 1362 1363 rl_interface_t *iface, *tmp; 1364 HASH_ITER(hh, interfaces, iface, tmp) { 1365 mark(js, iface->input_stream); 1366 mark(js, iface->output_stream); 1367 mark(js, iface->completer); 1368 mark(js, iface->js_obj); 1369 mark(js, iface->pending_question_resolve); 1370 mark(js, iface->pending_question_reject); 1371 } 1372}