diff --git a/include/tty_ctrl.h b/include/tty_ctrl.h index 6e2f4f3..0b77028 100644 --- a/include/tty_ctrl.h +++ b/include/tty_ctrl.h @@ -5,13 +5,40 @@ #include #include -bool tty_ctrl_write_fd(int fd, const char *data, size_t len); -bool tty_ctrl_write_stream(FILE *stream, const char *data, size_t len, bool flush); +#include "types.h" + +ant_value_t tty_ctrl_bool_result(ant_t *js, bool ok); const char *tty_ctrl_clear_line_seq(int dir, size_t *len_out); const char *tty_ctrl_clear_screen_down_seq(size_t *len_out); +int tty_ctrl_normalize_clear_line_dir(int dir); +int tty_ctrl_normalize_coord(int value); + +bool tty_ctrl_parse_int_value(ant_value_t value, int *out); +bool tty_ctrl_write_fd(int fd, const char *data, size_t len); +bool tty_ctrl_write_stream(FILE *stream, const char *data, size_t len, bool flush); + +bool tty_ctrl_parse_clear_line_dir(ant_value_t *args, int nargs, int dir_index, int *dir_out); bool tty_ctrl_build_cursor_to(char *buf, size_t buf_size, int x, bool has_y, int y, size_t *len_out); bool tty_ctrl_build_move_cursor_axis(char *buf, size_t buf_size, int delta, bool horizontal, size_t *len_out); +typedef struct { + int x; + bool has_y; + int y; +} tty_ctrl_cursor_to_args_t; + +bool tty_ctrl_parse_cursor_to_args( + ant_value_t *args, int nargs, + int x_index, int y_index, + tty_ctrl_cursor_to_args_t *out +); + +bool tty_ctrl_parse_move_cursor_args( + ant_value_t *args, int nargs, + int dx_index, int dy_index, + int *dx, int *dy +); + #endif diff --git a/src/modules/readline.c b/src/modules/readline.c index add2e33..174e6f2 100644 --- a/src/modules/readline.c +++ b/src/modules/readline.c @@ -1193,48 +1193,40 @@ static ant_value_t rl_create_interface_promises(ant_t *js, ant_value_t *args, in } static ant_value_t rl_clear_line(ant_t *js, ant_value_t *args, int nargs) { - (void)js; - if (nargs < 2) return js_false; - int dir = (int)js_getnum(args[1]); + int dir = 0; + if (!tty_ctrl_parse_clear_line_dir(args, nargs, 1, &dir)) return js_false; size_t seq_len = 0; const char *seq = tty_ctrl_clear_line_seq(dir, &seq_len); - return js_bool(tty_ctrl_write_stream(stdout, seq, seq_len, true)); + return tty_ctrl_bool_result(js, tty_ctrl_write_stream(stdout, seq, seq_len, true)); } static ant_value_t rl_clear_screen_down(ant_t *js, ant_value_t *args, int nargs) { - (void)js; (void)args; (void)nargs; - size_t seq_len = 0; const char *seq = tty_ctrl_clear_screen_down_seq(&seq_len); - return js_bool(tty_ctrl_write_stream(stdout, seq, seq_len, true)); + return tty_ctrl_bool_result(js, tty_ctrl_write_stream(stdout, seq, seq_len, true)); } static ant_value_t rl_cursor_to(ant_t *js, ant_value_t *args, int nargs) { - (void)js; - if (nargs < 2) return js_false; - int x = (int)js_getnum(args[1]); + tty_ctrl_cursor_to_args_t parsed; + if (!tty_ctrl_parse_cursor_to_args(args, nargs, 1, 2, &parsed)) return js_false; char seq[64]; size_t seq_len = 0; - bool ok = false; - if (nargs >= 3 && vtype(args[2]) == T_NUM) { - int y = (int)js_getnum(args[2]); - ok = tty_ctrl_build_cursor_to(seq, sizeof(seq), x, true, y, &seq_len); - } else { - ok = tty_ctrl_build_cursor_to(seq, sizeof(seq), x, false, 0, &seq_len); - } + bool ok = tty_ctrl_build_cursor_to( + seq, sizeof(seq), + parsed.x, parsed.has_y, parsed.y, + &seq_len + ); if (!ok) return js_false; - return js_bool(tty_ctrl_write_stream(stdout, seq, seq_len, true)); + return tty_ctrl_bool_result(js, tty_ctrl_write_stream(stdout, seq, seq_len, true)); } static ant_value_t rl_move_cursor(ant_t *js, ant_value_t *args, int nargs) { - (void)js; - if (nargs < 3) return js_false; - - int dx = (int)js_getnum(args[1]); - int dy = (int)js_getnum(args[2]); + int dx = 0; + int dy = 0; + if (!tty_ctrl_parse_move_cursor_args(args, nargs, 1, 2, &dx, &dy)) return js_false; bool ok = true; if (dx != 0) { @@ -1252,7 +1244,7 @@ static ant_value_t rl_move_cursor(ant_t *js, ant_value_t *args, int nargs) { } if (!ok) return js_false; - return js_bool(fflush(stdout) == 0); + return tty_ctrl_bool_result(js, fflush(stdout) == 0); } static ant_value_t rl_emit_keypress_events(ant_t *js, ant_value_t *args, int nargs) { diff --git a/src/modules/tty.c b/src/modules/tty.c index 4b9243a..9050e2c 100644 --- a/src/modules/tty.c +++ b/src/modules/tty.c @@ -1,6 +1,5 @@ #include // IWYU pragma: keep -#include #include #include #include @@ -43,20 +42,9 @@ static inline bool is_callable(ant_value_t value) { return t == T_FUNC || t == T_CFUNC; } -static bool parse_int(ant_value_t value, int *out) { - if (vtype(value) != T_NUM) return false; - double d = js_getnum(value); - if (!isfinite(d)) return false; - if (d < (double)INT_MIN || d > (double)INT_MAX) return false; - int i = (int)d; - if ((double)i != d) return false; - *out = i; - return true; -} - static bool parse_fd(ant_value_t value, int *fd_out) { int fd = 0; - if (!parse_int(value, &fd)) return false; + if (!tty_ctrl_parse_int_value(value, &fd)) return false; if (fd < 0) return false; *fd_out = fd; return true; @@ -413,7 +401,7 @@ static ant_value_t tty_write_stream_clear_line(ant_t *js, ant_value_t *args, int ant_value_t this_obj = js_getthis(js); int dir = 0; - if (nargs > 0 && vtype(args[0]) != T_UNDEF && !parse_int(args[0], &dir)) { + if (!tty_ctrl_parse_clear_line_dir(args, nargs, 0, &dir)) { return js_mkerr_typed(js, JS_ERR_TYPE, "clearLine(dir) requires a numeric dir"); } @@ -442,13 +430,12 @@ static ant_value_t tty_write_stream_clear_screen_down(ant_t *js, ant_value_t *ar static ant_value_t tty_write_stream_cursor_to(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); - if (nargs < 1 || !parse_int(args[0], &(int){0})) { + int x = 0; + if (nargs < 1 || !tty_ctrl_parse_int_value(args[0], &x)) { return js_mkerr_typed(js, JS_ERR_TYPE, "cursorTo(x[, y][, callback]) requires numeric x"); } - int x = 0; - (void)parse_int(args[0], &x); - if (x < 0) x = 0; + x = tty_ctrl_normalize_coord(x); bool has_y = false; int y = 0; @@ -459,9 +446,9 @@ static ant_value_t tty_write_stream_cursor_to(ant_t *js, ant_value_t *args, int cb = args[1]; } else if (vtype(args[1]) == T_UNDEF) { // no-op - } else if (parse_int(args[1], &y)) { + } else if (tty_ctrl_parse_int_value(args[1], &y)) { has_y = true; - if (y < 0) y = 0; + y = tty_ctrl_normalize_coord(y); if (nargs > 2 && is_callable(args[2])) cb = args[2]; } else { return js_mkerr_typed(js, JS_ERR_TYPE, "cursorTo y must be a number when provided"); @@ -487,7 +474,7 @@ static ant_value_t tty_write_stream_move_cursor(ant_t *js, ant_value_t *args, in int dx = 0; int dy = 0; - if (!parse_int(args[0], &dx) || !parse_int(args[1], &dy)) { + if (!tty_ctrl_parse_move_cursor_args(args, nargs, 0, 1, &dx, &dy)) { return js_mkerr_typed(js, JS_ERR_TYPE, "moveCursor(dx, dy[, callback]) requires numeric dx and dy"); } @@ -534,7 +521,7 @@ static ant_value_t tty_write_stream_has_colors(ant_t *js, ant_value_t *args, int if (nargs > 0) { if (vtype(args[0]) == T_NUM) { int parsed_count = 16; - if (!parse_int(args[0], &parsed_count)) { + if (!tty_ctrl_parse_int_value(args[0], &parsed_count)) { return js_mkerr_typed(js, JS_ERR_TYPE, "hasColors(count[, env]) count must be an integer"); } count = parsed_count; diff --git a/src/tty_ctrl.c b/src/tty_ctrl.c index f213c04..f47bdcc 100644 --- a/src/tty_ctrl.c +++ b/src/tty_ctrl.c @@ -1,7 +1,8 @@ #include // IWYU pragma: keep -#include "tty_ctrl.h" #include +#include +#include #include #include @@ -13,6 +14,9 @@ #define ANT_WRITE_FD write #endif +#include "tty_ctrl.h" +#include "internal.h" + bool tty_ctrl_write_fd(int fd, const char *data, size_t len) { if (fd < 0 || !data) return false; if (len == 0) return true; @@ -46,7 +50,78 @@ bool tty_ctrl_write_stream(FILE *stream, const char *data, size_t len, bool flus return true; } +bool tty_ctrl_parse_int_value(ant_value_t value, int *out) { + if (vtype(value) != T_NUM) return false; + double d = js_getnum(value); + if (!isfinite(d)) return false; + if (d < (double)INT_MIN || d > (double)INT_MAX) return false; + int i = (int)d; + if ((double)i != d) return false; + *out = i; + return true; +} + +int tty_ctrl_normalize_clear_line_dir(int dir) { + if (dir < 0) return -1; + if (dir > 0) return 1; + return 0; +} + +int tty_ctrl_normalize_coord(int value) { + return value < 0 ? 0 : value; +} + +bool tty_ctrl_parse_clear_line_dir(ant_value_t *args, int nargs, int dir_index, int *dir_out) { + int dir = 0; + if (nargs > dir_index && vtype(args[dir_index]) != T_UNDEF) { + if (!tty_ctrl_parse_int_value(args[dir_index], &dir)) return false; + } + *dir_out = tty_ctrl_normalize_clear_line_dir(dir); + return true; +} + +bool tty_ctrl_parse_cursor_to_args( + ant_value_t *args, + int nargs, + int x_index, + int y_index, + tty_ctrl_cursor_to_args_t *out +) { + if (!out || nargs <= x_index) return false; + + int x = 0; + if (!tty_ctrl_parse_int_value(args[x_index], &x)) return false; + out->x = tty_ctrl_normalize_coord(x); + out->has_y = false; + out->y = 0; + + if (nargs > y_index && vtype(args[y_index]) != T_UNDEF) { + int y = 0; + if (!tty_ctrl_parse_int_value(args[y_index], &y)) return false; + out->has_y = true; + out->y = tty_ctrl_normalize_coord(y); + } + + return true; +} + +bool tty_ctrl_parse_move_cursor_args( + ant_value_t *args, + int nargs, + int dx_index, + int dy_index, + int *dx, + int *dy +) { + if (!dx || !dy) return false; + if (nargs <= dy_index) return false; + if (!tty_ctrl_parse_int_value(args[dx_index], dx)) return false; + if (!tty_ctrl_parse_int_value(args[dy_index], dy)) return false; + return true; +} + const char *tty_ctrl_clear_line_seq(int dir, size_t *len_out) { + dir = tty_ctrl_normalize_clear_line_dir(dir); const char *seq = "\033[2K\r"; if (dir < 0) seq = "\033[1K"; else if (dir > 0) seq = "\033[0K"; @@ -92,3 +167,7 @@ bool tty_ctrl_build_move_cursor_axis(char *buf, size_t buf_size, int delta, bool if (len_out) *len_out = (size_t)n; return true; } + +ant_value_t tty_ctrl_bool_result(ant_t *js, bool ok) { + return js_bool(ok); +}