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.

rewrite and de-couple repl code

+691 -456
+39
include/readline.h
··· 1 + #ifndef ANT_READLINE_H 2 + #define ANT_READLINE_H 3 + 4 + #include <stdbool.h> 5 + #include "highlight.h" 6 + 7 + typedef struct { 8 + char **lines; 9 + int count; 10 + int capacity; 11 + int current; 12 + } ant_history_t; 13 + 14 + typedef enum { 15 + ANT_READLINE_LINE, 16 + ANT_READLINE_EOF, 17 + ANT_READLINE_INTERRUPT, 18 + } ant_readline_result_t; 19 + 20 + void ant_readline_install_signal_handler(void); 21 + void ant_readline_shutdown(void); 22 + 23 + void ant_history_init(ant_history_t *hist, int capacity); 24 + void ant_history_add(ant_history_t *hist, const char *line); 25 + void ant_history_load(ant_history_t *hist); 26 + void ant_history_save(const ant_history_t *hist); 27 + void ant_history_free(ant_history_t *hist); 28 + 29 + const char *ant_history_prev(ant_history_t *hist); 30 + const char *ant_history_next(ant_history_t *hist); 31 + 32 + ant_readline_result_t ant_readline( 33 + ant_history_t *hist, 34 + const char *prompt, 35 + highlight_state line_state, 36 + char **out_line 37 + ); 38 + 39 + #endif
+630
src/readline.c
··· 1 + #include <ctype.h> 2 + #include <signal.h> 3 + #include <stdio.h> 4 + #include <stdlib.h> 5 + #include <string.h> 6 + #include <sys/stat.h> 7 + 8 + #ifdef _WIN32 9 + #include <conio.h> 10 + #define WIN32_LEAN_AND_MEAN 11 + #include <windows.h> 12 + #include <direct.h> 13 + #define STDIN_FILENO 0 14 + #define mkdir_p(path) _mkdir(path) 15 + #else 16 + #include <sys/ioctl.h> 17 + #include <termios.h> 18 + #include <unistd.h> 19 + #define mkdir_p(path) mkdir(path, 0755) 20 + #endif 21 + 22 + #include <crprintf.h> 23 + #include "highlight.h" 24 + #include "readline.h" 25 + #include "utf8.h" 26 + 27 + #define MAX_LINE_LENGTH 4096 28 + 29 + static volatile sig_atomic_t ctrl_c_pressed = 0; 30 + static crprintf_compiled *hl_prog = NULL; 31 + static highlight_state hl_line_state = HL_STATE_INIT; 32 + static int repl_last_cursor_row = 0; 33 + 34 + static void sigint_handler(int sig) { 35 + (void)sig; 36 + ctrl_c_pressed++; 37 + } 38 + 39 + void ant_readline_install_signal_handler(void) { 40 + #ifdef _WIN32 41 + signal(SIGINT, sigint_handler); 42 + #else 43 + struct sigaction sa; 44 + memset(&sa, 0, sizeof(sa)); 45 + sa.sa_handler = sigint_handler; 46 + sigemptyset(&sa.sa_mask); 47 + sigaction(SIGINT, &sa, NULL); 48 + #endif 49 + } 50 + 51 + void ant_readline_shutdown(void) { 52 + if (hl_prog) { 53 + crprintf_compiled_free(hl_prog); 54 + hl_prog = NULL; 55 + } 56 + } 57 + 58 + void ant_history_init(ant_history_t *hist, int capacity) { 59 + hist->capacity = (capacity > 0) ? capacity : 512; 60 + hist->lines = malloc(sizeof(char *) * (size_t)hist->capacity); 61 + if (!hist->lines) hist->capacity = 0; 62 + hist->count = 0; 63 + hist->current = -1; 64 + } 65 + 66 + void ant_history_add(ant_history_t *hist, const char *line) { 67 + if (!hist || !hist->lines || hist->capacity <= 0 || !line || line[0] == '\0') return; 68 + if (hist->count > 0 && strcmp(hist->lines[hist->count - 1], line) == 0) return; 69 + 70 + if (hist->count >= hist->capacity) { 71 + free(hist->lines[0]); 72 + memmove(hist->lines, hist->lines + 1, sizeof(char *) * (size_t)(hist->capacity - 1)); 73 + hist->count--; 74 + } 75 + 76 + hist->lines[hist->count++] = strdup(line); 77 + hist->current = hist->count; 78 + } 79 + 80 + const char *ant_history_prev(ant_history_t *hist) { 81 + if (!hist || !hist->lines || hist->count == 0) return NULL; 82 + if (hist->current > 0) hist->current--; 83 + return hist->lines[hist->current]; 84 + } 85 + 86 + const char *ant_history_next(ant_history_t *hist) { 87 + if (!hist || !hist->lines || hist->count == 0) return NULL; 88 + if (hist->current < hist->count - 1) { 89 + hist->current++; 90 + return hist->lines[hist->current]; 91 + } 92 + hist->current = hist->count; 93 + return ""; 94 + } 95 + 96 + void ant_history_free(ant_history_t *hist) { 97 + if (!hist || !hist->lines) return; 98 + for (int i = 0; i < hist->count; i++) free(hist->lines[i]); 99 + free(hist->lines); 100 + hist->lines = NULL; 101 + hist->count = 0; 102 + hist->capacity = 0; 103 + hist->current = -1; 104 + } 105 + 106 + static char *get_history_path(void) { 107 + const char *home = getenv("HOME"); 108 + if (!home) home = getenv("USERPROFILE"); 109 + if (!home) return NULL; 110 + 111 + size_t len = strlen(home) + 32; 112 + char *path = malloc(len); 113 + snprintf(path, len, "%s/.ant", home); 114 + mkdir_p(path); 115 + snprintf(path, len, "%s/.ant/repl_history", home); 116 + return path; 117 + } 118 + 119 + void ant_history_load(ant_history_t *hist) { 120 + if (!hist || !hist->lines || hist->capacity <= 0) return; 121 + char *path = get_history_path(); 122 + if (!path) return; 123 + 124 + FILE *fp = fopen(path, "r"); 125 + free(path); 126 + if (!fp) return; 127 + 128 + char line[MAX_LINE_LENGTH]; 129 + while (fgets(line, sizeof(line), fp)) { 130 + size_t len = strlen(line); 131 + if (len > 0 && line[len - 1] == '\n') line[len - 1] = '\0'; 132 + if (line[0]) ant_history_add(hist, line); 133 + } 134 + fclose(fp); 135 + } 136 + 137 + void ant_history_save(const ant_history_t *hist) { 138 + if (!hist || !hist->lines) return; 139 + char *path = get_history_path(); 140 + if (!path) return; 141 + 142 + FILE *fp = fopen(path, "w"); 143 + free(path); 144 + if (!fp) return; 145 + 146 + for (int i = 0; i < hist->count; i++) { 147 + fprintf(fp, "%s\n", hist->lines[i]); 148 + } 149 + fclose(fp); 150 + } 151 + 152 + typedef enum { 153 + KEY_NONE, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, 154 + KEY_HOME, KEY_END, KEY_DELETE, KEY_BACKSPACE, KEY_ENTER, KEY_EOF, KEY_CHAR 155 + } key_type_t; 156 + 157 + typedef struct { 158 + key_type_t type; 159 + int ch; 160 + } key_event_t; 161 + 162 + static int repl_terminal_cols(void) { 163 + int cols = 80; 164 + #ifdef _WIN32 165 + CONSOLE_SCREEN_BUFFER_INFO csbi; 166 + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { 167 + cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; 168 + } 169 + #else 170 + struct winsize ws; 171 + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) { 172 + cols = ws.ws_col; 173 + } 174 + #endif 175 + return (cols > 0) ? cols : 80; 176 + } 177 + 178 + static void repl_jump_cursor(int x_pos, int y_offset) { 179 + #ifdef _WIN32 180 + if (y_offset != 0) { 181 + char seq[32]; 182 + snprintf(seq, sizeof(seq), "\033[%d%c", abs(y_offset), (y_offset > 0) ? 'B' : 'A'); 183 + fputs(seq, stdout); 184 + } 185 + char seq[32]; 186 + snprintf(seq, sizeof(seq), "\033[%dG", x_pos + 1); 187 + fputs(seq, stdout); 188 + #else 189 + if (y_offset != 0) { 190 + char seq[32]; 191 + snprintf(seq, sizeof(seq), "\033[%d%c", abs(y_offset), (y_offset > 0) ? 'B' : 'A'); 192 + fputs(seq, stdout); 193 + } 194 + char seq[32]; 195 + snprintf(seq, sizeof(seq), "\033[%dG", x_pos + 1); 196 + fputs(seq, stdout); 197 + #endif 198 + } 199 + 200 + static void repl_clear_to_end(void) { 201 + fputs("\033[J", stdout); 202 + } 203 + 204 + static void repl_set_cursor_visible(bool visible) { 205 + #ifdef _WIN32 206 + (void)visible; 207 + #else 208 + fputs(visible ? "\033[?25h" : "\033[?25l", stdout); 209 + #endif 210 + } 211 + 212 + static size_t repl_skip_ansi_escape(const char *s, size_t len, size_t i) { 213 + if (i >= len || (unsigned char)s[i] != 0x1B) return i; 214 + if (i + 1 >= len) return i + 1; 215 + 216 + unsigned char next = (unsigned char)s[i + 1]; 217 + 218 + if (next == '[') { 219 + i += 2; 220 + while (i < len) { 221 + unsigned char ch = (unsigned char)s[i++]; 222 + if (ch >= 0x40 && ch <= 0x7E) break; 223 + } 224 + return i; 225 + } 226 + 227 + if (next == ']') { 228 + i += 2; 229 + while (i < len) { 230 + unsigned char ch = (unsigned char)s[i]; 231 + if (ch == '\a') return i + 1; 232 + if (ch == 0x1B && i + 1 < len && s[i + 1] == '\\') return i + 2; 233 + i++; 234 + } 235 + return i; 236 + } 237 + 238 + return i + 2; 239 + } 240 + 241 + static void repl_virtual_render( 242 + const char *s, size_t n, 243 + int screen_cols, int prompt_len, 244 + int *x, int *y 245 + ) { 246 + bool wrapped = false; 247 + size_t i = 0; 248 + while (i < n) { 249 + unsigned char c = (unsigned char)s[i]; 250 + if (c == '\n' || c == '\r') { 251 + if (c == '\n' && !wrapped) (*y)++; 252 + *x = prompt_len; 253 + i++; 254 + wrapped = false; 255 + continue; 256 + } 257 + 258 + if (c == 0x1B) { 259 + i = repl_skip_ansi_escape(s, n, i); 260 + continue; 261 + } 262 + 263 + utf8proc_int32_t cp = 0; 264 + utf8proc_ssize_t clen = 265 + utf8_next((const utf8proc_uint8_t *)(s + i), (utf8proc_ssize_t)(n - i), &cp); 266 + if (clen <= 0) { 267 + clen = 1; 268 + cp = c; 269 + } 270 + 271 + int w = utf8proc_charwidth(cp); 272 + if (w < 0) w = 1; 273 + 274 + if (w > 0) { 275 + *x += w; 276 + wrapped = false; 277 + if (*x >= screen_cols) { 278 + *x = 0; 279 + (*y)++; 280 + wrapped = true; 281 + } 282 + } 283 + 284 + i += (size_t)clen; 285 + } 286 + } 287 + 288 + static int repl_prompt_width(const char *prompt, int cols) { 289 + int x = 0; 290 + int y = 0; 291 + repl_virtual_render(prompt, strlen(prompt), cols, 0, &x, &y); 292 + return x; 293 + } 294 + 295 + static void refresh_line(const char *line, int len, int pos, const char *prompt) { 296 + int cols = repl_terminal_cols(); 297 + int prompt_len = repl_prompt_width(prompt, cols); 298 + int x_cursor = prompt_len; 299 + int y_cursor = 0; 300 + int x_end = prompt_len; 301 + int y_end = 0; 302 + 303 + repl_virtual_render(line, (size_t)pos, cols, prompt_len, &x_cursor, &y_cursor); 304 + repl_virtual_render(line, (size_t)len, cols, prompt_len, &x_end, &y_end); 305 + 306 + repl_set_cursor_visible(false); 307 + repl_jump_cursor(prompt_len, -repl_last_cursor_row); 308 + repl_clear_to_end(); 309 + if (crprintf_get_color() && len > 0) { 310 + if (len <= 2048) { 311 + char tagged[8192]; 312 + char rendered[8192]; 313 + 314 + highlight_state state = hl_line_state; 315 + ant_highlight_stateful(line, (size_t)len, tagged, sizeof(tagged), &state); 316 + 317 + hl_prog = crprintf_recompile(hl_prog, tagged); 318 + crprintf_state *rs = crprintf_state_new(); 319 + crsprintf_compiled(rendered, sizeof(rendered), rs, hl_prog); 320 + crprintf_state_free(rs); 321 + 322 + fputs(rendered, stdout); 323 + } else { 324 + fwrite(line, 1, (size_t)len, stdout); 325 + } 326 + } else if (len > 0) { 327 + fwrite(line, 1, (size_t)len, stdout); 328 + } 329 + 330 + #ifndef _WIN32 331 + if ((x_end == 0) && (y_end > 0) && (len > 0) && (line[len - 1] != '\n')) { 332 + fputs("\n", stdout); 333 + } 334 + #endif 335 + 336 + repl_jump_cursor(x_cursor, -(y_end - y_cursor)); 337 + repl_set_cursor_visible(true); 338 + repl_last_cursor_row = y_cursor; 339 + fflush(stdout); 340 + } 341 + 342 + static void move_cursor_only(const char *line, int pos, const char *prompt) { 343 + int cols = repl_terminal_cols(); 344 + int prompt_len = repl_prompt_width(prompt, cols); 345 + int x_cursor = prompt_len; 346 + int y_cursor = 0; 347 + repl_virtual_render(line, (size_t)pos, cols, prompt_len, &x_cursor, &y_cursor); 348 + repl_jump_cursor(x_cursor, -(repl_last_cursor_row - y_cursor)); 349 + repl_last_cursor_row = y_cursor; 350 + fflush(stdout); 351 + } 352 + 353 + static int utf8_prev_pos(const char *line, int pos) { 354 + if (pos <= 0) return 0; 355 + int prev = 0; 356 + int i = 0; 357 + while (i < pos) { 358 + prev = i; 359 + utf8proc_int32_t cp = 0; 360 + utf8proc_ssize_t n = utf8_next( 361 + (const utf8proc_uint8_t *)(line + i), 362 + (utf8proc_ssize_t)(pos - i), 363 + &cp 364 + ); 365 + i += (int)((n > 0) ? n : 1); 366 + } 367 + return prev; 368 + } 369 + 370 + static int utf8_next_pos(const char *line, int len, int pos) { 371 + if (pos >= len) return len; 372 + utf8proc_int32_t cp = 0; 373 + utf8proc_ssize_t n = utf8_next( 374 + (const utf8proc_uint8_t *)(line + pos), 375 + (utf8proc_ssize_t)(len - pos), 376 + &cp 377 + ); 378 + int next = pos + (int)((n > 0) ? n : 1); 379 + return (next > len) ? len : next; 380 + } 381 + 382 + static void line_set(char *line, int *pos, int *len, const char *str, const char *prompt) { 383 + size_t n = strlen(str); 384 + if (n >= (size_t)MAX_LINE_LENGTH) n = (size_t)MAX_LINE_LENGTH - 1; 385 + memcpy(line, str, n); 386 + line[n] = '\0'; 387 + *len = (int)strlen(line); 388 + *pos = *len; 389 + refresh_line(line, *len, *pos, prompt); 390 + } 391 + 392 + static void line_backspace(char *line, int *pos, int *len, const char *prompt) { 393 + if (*pos <= 0) return; 394 + 395 + int prev = utf8_prev_pos(line, *pos); 396 + memmove(line + prev, line + *pos, (size_t)(*len - *pos + 1)); 397 + *len -= (*pos - prev); 398 + *pos = prev; 399 + refresh_line(line, *len, *pos, prompt); 400 + } 401 + 402 + static void line_delete(char *line, int *pos, int *len, const char *prompt) { 403 + if (*pos >= *len) return; 404 + int next = utf8_next_pos(line, *len, *pos); 405 + memmove(line + *pos, line + next, (size_t)(*len - next + 1)); 406 + *len -= (next - *pos); 407 + refresh_line(line, *len, *pos, prompt); 408 + } 409 + 410 + static void line_insert(char *line, int *pos, int *len, int c, const char *prompt) { 411 + if (*len >= MAX_LINE_LENGTH - 1) return; 412 + 413 + memmove(line + *pos + 1, line + *pos, (size_t)(*len - *pos + 1)); 414 + line[*pos] = (char)c; 415 + (*pos)++; 416 + (*len)++; 417 + refresh_line(line, *len, *pos, prompt); 418 + } 419 + 420 + #ifdef _WIN32 421 + static key_event_t read_key(void) { 422 + if (ctrl_c_pressed > 0) return (key_event_t){ KEY_EOF, 0 }; 423 + int c = _getch(); 424 + if (c == 0 || c == 0xE0) { 425 + int ext = _getch(); 426 + switch (ext) { 427 + case 72: return (key_event_t){ KEY_UP, 0 }; 428 + case 80: return (key_event_t){ KEY_DOWN, 0 }; 429 + case 77: return (key_event_t){ KEY_RIGHT, 0 }; 430 + case 75: return (key_event_t){ KEY_LEFT, 0 }; 431 + case 71: return (key_event_t){ KEY_HOME, 0 }; 432 + case 79: return (key_event_t){ KEY_END, 0 }; 433 + case 83: return (key_event_t){ KEY_DELETE, 0 }; 434 + default: return (key_event_t){ KEY_NONE, 0 }; 435 + } 436 + } 437 + if (c == 8) return (key_event_t){ KEY_BACKSPACE, 0 }; 438 + if (c == '\r' || c == '\n') return (key_event_t){ KEY_ENTER, 0 }; 439 + if (c == 3) { 440 + ctrl_c_pressed++; 441 + return (key_event_t){ KEY_EOF, 0 }; 442 + } 443 + if (c == 4 || c == 26) return (key_event_t){ KEY_EOF, 0 }; 444 + if (isprint(c) || (unsigned char)c >= 0x80) return (key_event_t){ KEY_CHAR, c }; 445 + return (key_event_t){ KEY_NONE, 0 }; 446 + } 447 + #else 448 + static struct termios saved_tio; 449 + 450 + static key_event_t read_key(void) { 451 + if (ctrl_c_pressed > 0) return (key_event_t){ KEY_EOF, 0 }; 452 + int c = getchar(); 453 + if (c == EOF && !feof(stdin)) { 454 + clearerr(stdin); 455 + return (key_event_t){ KEY_EOF, 0 }; 456 + } 457 + if (c == EOF) return (key_event_t){ KEY_EOF, 0 }; 458 + 459 + if (c == 27) { 460 + int seq1 = getchar(); 461 + if (seq1 == EOF) return (key_event_t){ KEY_NONE, 0 }; 462 + if (seq1 == 'O') { 463 + int seq2 = getchar(); 464 + if (seq2 == 'H') return (key_event_t){ KEY_HOME, 0 }; 465 + if (seq2 == 'F') return (key_event_t){ KEY_END, 0 }; 466 + return (key_event_t){ KEY_NONE, 0 }; 467 + } 468 + if (seq1 != '[') return (key_event_t){ KEY_NONE, 0 }; 469 + int seq2 = getchar(); 470 + if (seq2 == EOF) return (key_event_t){ KEY_NONE, 0 }; 471 + switch (seq2) { 472 + case 'A': return (key_event_t){ KEY_UP, 0 }; 473 + case 'B': return (key_event_t){ KEY_DOWN, 0 }; 474 + case 'C': return (key_event_t){ KEY_RIGHT, 0 }; 475 + case 'D': return (key_event_t){ KEY_LEFT, 0 }; 476 + case 'H': return (key_event_t){ KEY_HOME, 0 }; 477 + case 'F': return (key_event_t){ KEY_END, 0 }; 478 + default: { 479 + if (seq2 >= '0' && seq2 <= '9') { 480 + int seq3 = getchar(); 481 + if (seq3 == '~') { 482 + if (seq2 == '1' || seq2 == '7') return (key_event_t){ KEY_HOME, 0 }; 483 + if (seq2 == '4' || seq2 == '8') return (key_event_t){ KEY_END, 0 }; 484 + if (seq2 == '3') return (key_event_t){ KEY_DELETE, 0 }; 485 + } 486 + } 487 + return (key_event_t){ KEY_NONE, 0 }; 488 + } 489 + } 490 + } 491 + 492 + if (c == 127 || c == 8) return (key_event_t){ KEY_BACKSPACE, 0 }; 493 + if (c == '\n' || c == '\r') return (key_event_t){ KEY_ENTER, 0 }; 494 + if (isprint(c) || (unsigned char)c >= 0x80) return (key_event_t){ KEY_CHAR, c }; 495 + return (key_event_t){ KEY_NONE, 0 }; 496 + } 497 + #endif 498 + 499 + static void term_restore(void) { 500 + #ifndef _WIN32 501 + tcsetattr(STDIN_FILENO, TCSANOW, &saved_tio); 502 + #endif 503 + } 504 + 505 + static char *read_line_with_history(ant_history_t *hist, const char *prompt) { 506 + char *line = malloc(MAX_LINE_LENGTH); 507 + if (!line) return NULL; 508 + int pos = 0; 509 + int len = 0; 510 + line[0] = '\0'; 511 + repl_last_cursor_row = 0; 512 + 513 + #ifndef _WIN32 514 + struct termios new_tio; 515 + tcgetattr(STDIN_FILENO, &saved_tio); 516 + new_tio = saved_tio; 517 + new_tio.c_lflag &= ~(ICANON | ECHO); 518 + tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); 519 + #endif 520 + 521 + for (;;) { 522 + key_event_t key = read_key(); 523 + static const void *dispatch[] = { 524 + [KEY_NONE] = &&l_none, 525 + [KEY_UP] = &&l_up, 526 + [KEY_DOWN] = &&l_down, 527 + [KEY_LEFT] = &&l_left, 528 + [KEY_RIGHT] = &&l_right, 529 + [KEY_HOME] = &&l_home, 530 + [KEY_END] = &&l_end, 531 + [KEY_DELETE] = &&l_delete, 532 + [KEY_BACKSPACE] = &&l_backspace, 533 + [KEY_ENTER] = &&l_enter, 534 + [KEY_EOF] = &&l_eof, 535 + [KEY_CHAR] = &&l_char, 536 + }; 537 + 538 + unsigned int t = (unsigned int)key.type; 539 + if (t < (sizeof(dispatch) / sizeof(*dispatch)) && dispatch[t]) goto *dispatch[t]; 540 + goto l_none; 541 + 542 + l_enter: 543 + putchar('\n'); 544 + term_restore(); 545 + return line; 546 + 547 + l_eof: 548 + putchar('\n'); 549 + term_restore(); 550 + free(line); 551 + return NULL; 552 + 553 + l_up: { 554 + const char *h = ant_history_prev(hist); 555 + if (h) line_set(line, &pos, &len, h, prompt); 556 + continue; 557 + } 558 + 559 + l_down: { 560 + const char *h = ant_history_next(hist); 561 + if (h) line_set(line, &pos, &len, h, prompt); 562 + continue; 563 + } 564 + 565 + l_left: 566 + if (pos > 0) { 567 + pos = utf8_prev_pos(line, pos); 568 + move_cursor_only(line, pos, prompt); 569 + } 570 + continue; 571 + 572 + l_right: 573 + if (pos < len) { 574 + pos = utf8_next_pos(line, len, pos); 575 + move_cursor_only(line, pos, prompt); 576 + } 577 + continue; 578 + 579 + l_home: 580 + if (pos != 0) { 581 + pos = 0; 582 + move_cursor_only(line, pos, prompt); 583 + } 584 + continue; 585 + 586 + l_end: 587 + if (pos != len) { 588 + pos = len; 589 + move_cursor_only(line, pos, prompt); 590 + } 591 + continue; 592 + 593 + l_delete: 594 + line_delete(line, &pos, &len, prompt); 595 + continue; 596 + 597 + l_backspace: 598 + line_backspace(line, &pos, &len, prompt); 599 + continue; 600 + 601 + l_char: 602 + line_insert(line, &pos, &len, key.ch, prompt); 603 + continue; 604 + 605 + l_none: 606 + continue; 607 + } 608 + } 609 + 610 + ant_readline_result_t ant_readline( 611 + ant_history_t *hist, 612 + const char *prompt, 613 + highlight_state line_state, 614 + char **out_line 615 + ) { 616 + if (out_line) *out_line = NULL; 617 + 618 + hl_line_state = line_state; 619 + ctrl_c_pressed = 0; 620 + char *line = read_line_with_history(hist, prompt); 621 + 622 + if (ctrl_c_pressed > 0) { 623 + if (line) free(line); 624 + return ANT_READLINE_INTERRUPT; 625 + } 626 + if (!line) return ANT_READLINE_EOF; 627 + 628 + if (out_line) *out_line = line; 629 + return ANT_READLINE_LINE; 630 + }
+22 -456
src/repl.c
··· 2 2 #include <stdlib.h> 3 3 #include <string.h> 4 4 #include <ctype.h> 5 - #include <signal.h> 6 - #include <sys/stat.h> 7 - 8 - #ifdef _WIN32 9 - #include <conio.h> 10 - #define WIN32_LEAN_AND_MEAN 11 - #include <windows.h> 12 - #include <io.h> 13 - #include <direct.h> 14 - #define STDIN_FILENO 0 15 - #define mkdir_p(path) _mkdir(path) 16 - #else 17 - #include <sys/ioctl.h> 18 - #include <termios.h> 19 - #include <unistd.h> 20 - #define mkdir_p(path) mkdir(path, 0755) 21 - #endif 22 5 23 6 #include "ant.h" 24 7 #include "repl.h" 8 + #include "readline.h" 25 9 #include "reactor.h" 26 10 #include "runtime.h" 27 11 #include "internal.h" 28 - #include "utf8.h" 29 12 30 13 #include "silver/ast.h" 31 14 #include "silver/engine.h" ··· 35 18 #include "highlight.h" 36 19 #include "highlight/regex.h" 37 20 38 - #define MAX_HISTORY 512 39 - #define MAX_LINE_LENGTH 4096 40 - #define MAX_MULTILINE_LENGTH 65536 41 - 42 - #define INPUT \ 43 - char *line, int *pos, int *len, key_event_t *key, history_t *hist, const char *prompt 44 - 45 - static volatile sig_atomic_t ctrl_c_pressed = 0; 46 - 47 - typedef struct { 48 - char **lines; 49 - int count; 50 - int capacity; 51 - int current; 52 - } history_t; 21 + typedef ant_history_t history_t; 53 22 54 23 typedef enum { 55 24 CMD_OK, ··· 83 52 } repl_decl_pending_t; 84 53 85 54 static repl_decl_registry_t *g_repl_decl_registry = NULL; 86 - static void sigint_handler(int sig) { ctrl_c_pressed++; } 87 55 88 56 static inline void repl_clear_exception_state(ant_t *js) { 89 57 js->thrown_exists = false; ··· 386 354 printf("\n%sKeybindings:%s\n", C_BOLD, C_RESET); 387 355 printf(" Ctrl+C Abort current expression (press twice to exit)\n"); 388 356 printf(" Left/Right Move backward/forward one character\n"); 357 + printf(" Home/End Jump to start/end of line\n"); 389 358 printf(" Up/Down Navigate history\n"); 390 359 printf(" Backspace Delete character backward\n"); 360 + printf(" Delete Delete character under cursor\n"); 391 361 printf(" Enter Submit input\n"); 392 362 printf("\n%sSpecial Variables:%s\n", C_BOLD, C_RESET); 393 363 printf(" %s_%s Last expression result\n", C_CYAN, C_RESET); ··· 556 526 return CMD_NOT_FOUND; 557 527 } 558 528 559 - static void history_init(history_t *hist) { 560 - hist->capacity = MAX_HISTORY; 561 - hist->lines = malloc(sizeof(char*) * hist->capacity); 562 - hist->count = 0; 563 - hist->current = -1; 564 - } 565 - 566 - static void history_add(history_t *hist, const char *line) { 567 - if (strlen(line) == 0) return; 568 - if (hist->count > 0 && strcmp(hist->lines[hist->count - 1], line) == 0) return; 569 - 570 - if (hist->count >= hist->capacity) { 571 - free(hist->lines[0]); 572 - memmove(hist->lines, hist->lines + 1, sizeof(char*) * (hist->capacity - 1)); 573 - hist->count--; 574 - } 575 - 576 - hist->lines[hist->count++] = strdup(line); 577 - hist->current = hist->count; 578 - } 579 - 580 - static const char* history_prev(history_t *hist) { 581 - if (hist->count == 0) return NULL; 582 - if (hist->current > 0) hist->current--; 583 - return hist->lines[hist->current]; 584 - } 585 - 586 - static const char* history_next(history_t *hist) { 587 - if (hist->count == 0) return NULL; 588 - if (hist->current < hist->count - 1) { 589 - hist->current++; 590 - return hist->lines[hist->current]; 591 - } 592 - hist->current = hist->count; 593 - return ""; 594 - } 595 - 596 - static void history_free(history_t *hist) { 597 - for (int i = 0; i < hist->count; i++) free(hist->lines[i]); 598 - free(hist->lines); 599 - } 600 - 601 - static char* get_history_path(void) { 602 - const char *home = getenv("HOME"); 603 - if (!home) home = getenv("USERPROFILE"); 604 - if (!home) return NULL; 605 - 606 - size_t len = strlen(home) + 32; 607 - char *path = malloc(len); 608 - snprintf(path, len, "%s/.ant", home); 609 - mkdir_p(path); 610 - snprintf(path, len, "%s/.ant/repl_history", home); 611 - return path; 612 - } 613 - 614 - static void history_load(history_t *hist) { 615 - char *path = get_history_path(); 616 - if (!path) return; 617 - 618 - FILE *fp = fopen(path, "r"); 619 - free(path); 620 - if (!fp) return; 621 - 622 - char line[MAX_LINE_LENGTH]; 623 - while (fgets(line, sizeof(line), fp)) { 624 - size_t len = strlen(line); 625 - if (len > 0 && line[len - 1] == '\n') line[len - 1] = '\0'; 626 - if (line[0]) history_add(hist, line); 627 - } 628 - fclose(fp); 629 - } 630 - 631 - static void history_save(history_t *hist) { 632 - char *path = get_history_path(); 633 - if (!path) return; 634 - 635 - FILE *fp = fopen(path, "w"); 636 - free(path); 637 - if (!fp) return; 638 - 639 - for (int i = 0; i < hist->count; i++) { 640 - fprintf(fp, "%s\n", hist->lines[i]); 641 - } 642 - fclose(fp); 643 - } 644 - 645 - typedef enum { 646 - KEY_NONE, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, 647 - KEY_BACKSPACE, KEY_ENTER, KEY_EOF, KEY_CHAR 648 - } key_type_t; 649 - 650 - typedef struct { key_type_t type; int ch; } key_event_t; 651 - typedef void (*key_handler_t)(INPUT); 652 - 653 - static crprintf_compiled *hl_prog = NULL; 654 - static highlight_state hl_line_state = HL_STATE_INIT; 655 - static int repl_last_render_rows = 1; 656 - 657 - static int repl_terminal_cols(void) { 658 - int cols = 80; 659 - #ifdef _WIN32 660 - CONSOLE_SCREEN_BUFFER_INFO csbi; 661 - if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { 662 - cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; 663 - } 664 - #else 665 - struct winsize ws; 666 - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) { 667 - cols = ws.ws_col; 668 - } 669 - #endif 670 - return cols > 0 ? cols : 80; 671 - } 672 - 673 - static size_t repl_skip_ansi_escape(const char *s, size_t len, size_t i) { 674 - if (i >= len || (unsigned char)s[i] != 0x1B) return i; 675 - if (i + 1 >= len) return i + 1; 676 - 677 - unsigned char next = (unsigned char)s[i + 1]; 678 - 679 - if (next == '[') { 680 - i += 2; 681 - while (i < len) { 682 - unsigned char ch = (unsigned char)s[i++]; 683 - if (ch >= 0x40 && ch <= 0x7E) break; 684 - } 685 - return i; 686 - } 687 - 688 - if (next == ']') { 689 - i += 2; 690 - while (i < len) { 691 - unsigned char ch = (unsigned char)s[i]; 692 - if (ch == '\a') return i + 1; 693 - if (ch == 0x1B && i + 1 < len && s[i + 1] == '\\') return i + 2; 694 - i++; 695 - } 696 - return i; 697 - } 698 - 699 - return i + 2; 700 - } 701 - 702 - static int repl_display_width(const char *s) { 703 - if (!s) return 0; 704 - 705 - int width = 0; 706 - size_t len = strlen(s); 707 - size_t i = 0; 708 - 709 - while (i < len) { 710 - if ((unsigned char)s[i] == 0x1B) { 711 - i = repl_skip_ansi_escape(s, len, i); 712 - continue; 713 - } 714 - 715 - utf8proc_int32_t cp = 0; 716 - utf8proc_ssize_t n = utf8_next((const utf8proc_uint8_t *)(s + i), (utf8proc_ssize_t)(len - i), &cp); 717 - if (n <= 0) n = 1; 718 - 719 - int w = utf8proc_charwidth(cp); 720 - if (w > 0) width += w; 721 - 722 - i += (size_t)n; 723 - } 724 - 725 - return width; 726 - } 727 - 728 - static void repl_move_to_line_start(const char *prompt, int pos, int cols) { 729 - int prompt_len = repl_display_width(prompt); 730 - int cursor_cols = prompt_len + pos; 731 - int cursor_row = cursor_cols / cols; 732 - if (cursor_cols > 0 && cursor_cols % cols == 0) cursor_row--; 733 - 734 - if (cursor_row > 0) { 735 - char move_buf[32]; 736 - snprintf(move_buf, sizeof(move_buf), "\033[%dA", cursor_row); 737 - fputs(move_buf, stdout); 738 - } 739 - fputs("\r", stdout); 740 - } 741 - 742 - static void refresh_line(const char *line, int len, int pos, const char *prompt) { 743 - int cols = repl_terminal_cols(); 744 - int prompt_len = repl_display_width(prompt); 745 - int line_cols = prompt_len + len; 746 - int current_rows = line_cols > 0 ? (line_cols - 1) / cols + 1 : 1; 747 - int rows = repl_last_render_rows > current_rows ? repl_last_render_rows : current_rows; 748 - 749 - repl_move_to_line_start(prompt, pos, cols); 750 - for (int i = 0; i < rows; i++) { 751 - fputs("\033[K", stdout); 752 - if (i < rows - 1) fputs("\033[B\r", stdout); 753 - } 754 - for (int i = 0; i < rows - 1; i++) fputs("\033[A", stdout); 755 - fputs("\r", stdout); 756 - 757 - fputs(prompt, stdout); 758 - 759 - if (crprintf_get_color() && len > 0) { 760 - if (len <= 2048) { 761 - char tagged[8192]; 762 - char rendered[8192]; 763 - 764 - highlight_state state = hl_line_state; 765 - ant_highlight_stateful(line, (size_t)len, tagged, sizeof(tagged), &state); 766 - 767 - hl_prog = crprintf_recompile(hl_prog, tagged); 768 - crprintf_state *rs = crprintf_state_new(); 769 - 770 - crsprintf_compiled(rendered, sizeof(rendered), rs, hl_prog); 771 - crprintf_state_free(rs); 772 - 773 - fputs(rendered, stdout); 774 - } else fwrite(line, 1, (size_t)len, stdout); 775 - } else if (len > 0) fwrite(line, 1, (size_t)len, stdout); 776 - 777 - int end_cols = prompt_len + len; 778 - int end_rows = end_cols > 0 ? (end_cols - 1) / cols + 1 : 1; 779 - int end_row = end_rows - 1; 780 - 781 - int cursor_cols = prompt_len + pos; 782 - int cursor_row = cursor_cols > 0 ? cursor_cols / cols : 0; 783 - int cursor_col = cursor_cols > 0 ? cursor_cols % cols : 0; 784 - int up_rows = end_row - cursor_row; 785 - 786 - if (up_rows > 0) { 787 - char move_buf[32]; 788 - snprintf(move_buf, sizeof(move_buf), "\033[%dA", up_rows); 789 - fputs(move_buf, stdout); 790 - } 791 - 792 - fputs("\r", stdout); 793 - if (cursor_col > 0) { 794 - char move_buf[32]; 795 - snprintf(move_buf, sizeof(move_buf), "\033[%dC", cursor_col); 796 - fputs(move_buf, stdout); 797 - } 798 - 799 - repl_last_render_rows = end_rows; 800 - 801 - fflush(stdout); 802 - } 803 - 804 - static void cursor_move(int *pos, int len, int dir) { 805 - if (dir < 0 && *pos > 0) { printf("\033[D"); fflush(stdout); (*pos)--; } 806 - else if (dir > 0 && *pos < len) { printf("\033[C"); fflush(stdout); (*pos)++; } 807 - } 808 - 809 - static void line_set(char *line, int *pos, int *len, const char *str, const char *prompt) { 810 - strcpy(line, str); 811 - *len = (int)strlen(line); *pos = *len; 812 - refresh_line(line, *len, *pos, prompt); 813 - } 814 - 815 - static void line_backspace(char *line, int *pos, int *len, const char *prompt) { 816 - if (*pos <= 0) return; 817 - 818 - memmove(line + *pos - 1, line + *pos, *len - *pos + 1); 819 - (*pos)--; (*len)--; 820 - refresh_line(line, *len, *pos, prompt); 821 - } 822 - 823 - static void line_insert(char *line, int *pos, int *len, int c, const char *prompt) { 824 - if (*len >= MAX_LINE_LENGTH - 1) return; 825 - 826 - memmove(line + *pos + 1, line + *pos, *len - *pos + 1); 827 - line[*pos] = (char)c; 828 - (*pos)++; (*len)++; 829 - refresh_line(line, *len, *pos, prompt); 830 - } 831 - 832 - #ifdef _WIN32 833 - static key_event_t read_key(void) { 834 - if (ctrl_c_pressed > 0) return (key_event_t){ KEY_EOF, 0 }; 835 - int c = _getch(); 836 - if (c == 0 || c == 0xE0) { 837 - int ext = _getch(); 838 - switch (ext) { 839 - case 72: return (key_event_t){ KEY_UP, 0 }; 840 - case 80: return (key_event_t){ KEY_DOWN, 0 }; 841 - case 77: return (key_event_t){ KEY_RIGHT, 0 }; 842 - case 75: return (key_event_t){ KEY_LEFT, 0 }; 843 - } 844 - return (key_event_t){ KEY_NONE, 0 }; 845 - } 846 - if (c == 8) return (key_event_t){ KEY_BACKSPACE, 0 }; 847 - if (c == '\r' || c == '\n') return (key_event_t){ KEY_ENTER, 0 }; 848 - if (c == 3) { ctrl_c_pressed++; return (key_event_t){ KEY_EOF, 0 }; } 849 - if (c == 4 || c == 26) return (key_event_t){ KEY_EOF, 0 }; 850 - if (isprint(c) || (unsigned char)c >= 0x80) return (key_event_t){ KEY_CHAR, c }; 851 - return (key_event_t){ KEY_NONE, 0 }; 852 - } 853 - #else 854 - static struct termios saved_tio; 855 - static key_event_t read_key(void) { 856 - if (ctrl_c_pressed > 0) return (key_event_t){ KEY_EOF, 0 }; 857 - int c = getchar(); 858 - if (c == EOF && !feof(stdin)) { clearerr(stdin); return (key_event_t){ KEY_EOF, 0 }; } 859 - if (c == EOF) return (key_event_t){ KEY_EOF, 0 }; 860 - if (c == 27) { 861 - int seq1 = getchar(); 862 - if (seq1 == EOF) return (key_event_t){ KEY_NONE, 0 }; 863 - if (seq1 != '[') return (key_event_t){ KEY_NONE, 0 }; 864 - int seq2 = getchar(); 865 - if (seq2 == EOF) return (key_event_t){ KEY_NONE, 0 }; 866 - switch (seq2) { 867 - case 'A': return (key_event_t){ KEY_UP, 0 }; 868 - case 'B': return (key_event_t){ KEY_DOWN, 0 }; 869 - case 'C': return (key_event_t){ KEY_RIGHT, 0 }; 870 - case 'D': return (key_event_t){ KEY_LEFT, 0 }; 871 - } 872 - if (seq2 >= '0' && seq2 <= '9') { 873 - int seq3 = getchar(); 874 - (void)seq3; 875 - } 876 - return (key_event_t){ KEY_NONE, 0 }; 877 - } 878 - if (c == 127 || c == 8) return (key_event_t){ KEY_BACKSPACE, 0 }; 879 - if (c == '\n' || c == '\r') return (key_event_t){ KEY_ENTER, 0 }; 880 - if (isprint(c) || (unsigned char)c >= 0x80) return (key_event_t){ KEY_CHAR, c }; 881 - return (key_event_t){ KEY_NONE, 0 }; 882 - } 883 - #endif 884 - 885 - static void handle_up(INPUT) { 886 - const char *h = history_prev(hist); 887 - if (h) line_set(line, pos, len, h, prompt); 888 - } 889 - 890 - static void handle_down(INPUT) { 891 - const char *h = history_next(hist); 892 - if (h) line_set(line, pos, len, h, prompt); 893 - } 894 - 895 - static void handle_left(INPUT) { cursor_move(pos, *len, -1); } 896 - static void handle_right(INPUT) { cursor_move(pos, *len, 1); } 897 - 898 - static void handle_backspace(INPUT) { line_backspace(line, pos, len, prompt); } 899 - static void handle_char(INPUT) { line_insert(line, pos, len, key->ch, prompt); } 900 - 901 - static key_handler_t handlers[] = { 902 - [KEY_UP] = handle_up, 903 - [KEY_DOWN] = handle_down, 904 - [KEY_LEFT] = handle_left, 905 - [KEY_RIGHT] = handle_right, 906 - [KEY_BACKSPACE] = handle_backspace, 907 - [KEY_CHAR] = handle_char, 908 - }; 909 - 910 - static inline void term_restore(void) { 911 - #ifndef _WIN32 912 - tcsetattr(STDIN_FILENO, TCSANOW, &saved_tio); 913 - #endif 914 - } 915 - 916 - static char *read_line_with_history(history_t *hist, ant_t *js, const char *prompt) { 917 - char *line = malloc(MAX_LINE_LENGTH); 918 - int pos = 0, len = 0; line[0] = '\0'; 919 - repl_last_render_rows = 1; 920 - 921 - #ifndef _WIN32 922 - struct termios new_tio; 923 - tcgetattr(STDIN_FILENO, &saved_tio); 924 - new_tio = saved_tio; 925 - new_tio.c_lflag &= ~(ICANON | ECHO); 926 - tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); 927 - #endif 928 - 929 - again: 930 - key_event_t key = read_key(); 931 - 932 - switch (key.type) { 933 - case KEY_ENTER: 934 - putchar('\n'); 935 - term_restore(); 936 - return line; 937 - 938 - case KEY_EOF: 939 - putchar('\n'); 940 - term_restore(); 941 - free(line); 942 - return NULL; 943 - 944 - default: 945 - if (handlers[key.type]) handlers[key.type]( 946 - line, &pos, &len, &key, hist, prompt 947 - ); 948 - break; 949 - } 950 - 951 - goto again; 952 - } 953 - 954 529 typedef struct { 955 530 int paren, bracket, brace; 956 531 int *templates; ··· 1019 594 1020 595 void ant_repl_run() { 1021 596 ant_t *js = rt->js; 597 + ant_readline_install_signal_handler(); 1022 598 1023 599 js_set_filename(js, "[repl]"); 1024 600 js_setup_import_meta(js, "[repl]"); ··· 1029 605 ANT_VERSION 1030 606 ); 1031 607 1032 - #ifdef _WIN32 1033 - signal(SIGINT, sigint_handler); 1034 - #else 1035 - struct sigaction sa; 1036 - sa.sa_handler = sigint_handler; 1037 - sigemptyset(&sa.sa_mask); 1038 - sa.sa_flags = 0; 1039 - sigaction(SIGINT, &sa, NULL); 1040 - #endif 1041 - 1042 608 history_t history; 1043 - history_init(&history); 1044 - history_load(&history); 609 + ant_history_init(&history, 512); 610 + ant_history_load(&history); 1045 611 1046 612 repl_decl_registry_t decl_registry = {0}; 1047 613 g_repl_decl_registry = &decl_registry; ··· 1059 625 1060 626 while (1) { 1061 627 const char *prompt = multiline_buf ? "\x1b[2m|\x1b[0m " : "\x1b[2mโฏ\x1b[0m "; 1062 - 628 + highlight_state prefix_state = HL_STATE_INIT; 1063 629 if (multiline_buf && multiline_len > 0) { 1064 630 char scratch[8192]; 1065 - hl_line_state = HL_STATE_INIT; 1066 - ant_highlight_stateful(multiline_buf, multiline_len, scratch, sizeof(scratch), &hl_line_state); 1067 - } else hl_line_state = HL_STATE_INIT; 631 + ant_highlight_stateful(multiline_buf, multiline_len, scratch, sizeof(scratch), &prefix_state); 632 + } 1068 633 1069 634 fputs(prompt, stdout); 1070 635 fflush(stdout); 1071 - 1072 - ctrl_c_pressed = 0; 1073 - char *line = read_line_with_history(&history, js, prompt); 1074 - 1075 - if (ctrl_c_pressed > 0) { 636 + 637 + char *line = NULL; 638 + ant_readline_result_t readline_status = 639 + ant_readline(&history, prompt, prefix_state, &line); 640 + 641 + if (readline_status == ANT_READLINE_INTERRUPT) { 1076 642 if (multiline_buf) { 1077 643 free(multiline_buf); 1078 644 multiline_buf = NULL; ··· 1091 657 if (line) free(line); 1092 658 continue; 1093 659 } 1094 - 1095 - if (line == NULL) { 660 + 661 + if (readline_status == ANT_READLINE_EOF || line == NULL) { 1096 662 if (multiline_buf) { 1097 663 free(multiline_buf); 1098 664 multiline_buf = NULL; ··· 1151 717 free(line); 1152 718 1153 719 if (is_incomplete_input(multiline_buf, multiline_len)) continue; 1154 - history_add(&history, multiline_buf); 720 + ant_history_add(&history, multiline_buf); 1155 721 1156 722 repl_eval_chunk( 1157 723 js, &decl_registry, multiline_buf, ··· 1165 731 } 1166 732 1167 733 if (multiline_buf) free(multiline_buf); 1168 - if (hl_prog) { crprintf_compiled_free(hl_prog); hl_prog = NULL; } 734 + ant_readline_shutdown(); 1169 735 1170 736 repl_decl_registry_free(&decl_registry); 1171 737 g_repl_decl_registry = NULL; 1172 738 1173 - history_save(&history); 1174 - history_free(&history); 739 + ant_history_save(&history); 740 + ant_history_free(&history); 1175 741 }