MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
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}