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

Configure Feed

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

at type-hints-typescript 677 lines 17 kB view raw
1#include <ctype.h> 2#include <stdint.h> 3#include <signal.h> 4#include <stdio.h> 5#include <stdlib.h> 6#include <string.h> 7#include <crprintf.h> 8#include <sys/stat.h> 9 10#ifdef _WIN32 11#include <conio.h> 12#define WIN32_LEAN_AND_MEAN 13#include <windows.h> 14#include <direct.h> 15#define STDIN_FILENO 0 16#define mkdir_p(path) _mkdir(path) 17#else 18#include <sys/ioctl.h> 19#include <termios.h> 20#include <unistd.h> 21#define mkdir_p(path) mkdir(path, 0755) 22#endif 23 24#include "utf8.h" 25#include "utils.h" 26#include "readline.h" 27#include "highlight.h" 28 29#define MAX_LINE_LENGTH 4096 30 31static const unsigned char HISTORY_FILE_MAGIC[] = { 32 0x61, 0x6E, 0x74, 0x72, 0x65, 0x70, 0x6C, 0x73, 33 0x63, 0x68, 0x65, 0x6D, 0x61, 0x32, 0x0A, 0x00 34}; 35 36static volatile sig_atomic_t ctrl_c_pressed = 0; 37static crprintf_compiled *hl_prog = NULL; 38 39static highlight_state hl_line_state = HL_STATE_INIT; 40static int repl_last_cursor_row = 0; 41 42static void sigint_handler(int sig) { 43 ctrl_c_pressed++; 44} 45 46void ant_readline_install_signal_handler(void) { 47#ifdef _WIN32 48 signal(SIGINT, sigint_handler); 49#else 50 struct sigaction sa; 51 memset(&sa, 0, sizeof(sa)); 52 sa.sa_handler = sigint_handler; 53 sigemptyset(&sa.sa_mask); 54 sigaction(SIGINT, &sa, NULL); 55#endif 56} 57 58void ant_readline_shutdown(void) { 59 if (hl_prog) { 60 crprintf_compiled_free(hl_prog); 61 hl_prog = NULL; 62 } 63} 64 65void ant_history_init(ant_history_t *hist, int capacity) { 66 hist->capacity = (capacity > 0) ? capacity : 512; 67 hist->lines = malloc(sizeof(char *) * (size_t)hist->capacity); 68 if (!hist->lines) hist->capacity = 0; 69 hist->count = 0; 70 hist->current = -1; 71} 72 73void ant_history_add(ant_history_t *hist, const char *line) { 74 if (!hist || !hist->lines || hist->capacity <= 0 || !line || line[0] == '\0') return; 75 76 if (hist->count > 0 && strcmp(hist->lines[hist->count - 1], line) == 0) { 77 hist->current = hist->count; 78 return; 79 } 80 81 if (hist->count >= hist->capacity) { 82 free(hist->lines[0]); 83 memmove(hist->lines, hist->lines + 1, sizeof(char *) * (size_t)(hist->capacity - 1)); 84 hist->count--; 85 } 86 87 hist->lines[hist->count++] = strdup(line); 88 hist->current = hist->count; 89} 90 91const char *ant_history_prev(ant_history_t *hist) { 92 if (!hist || !hist->lines || hist->count == 0) return NULL; 93 if (hist->current > 0) hist->current--; 94 return hist->lines[hist->current]; 95} 96 97const char *ant_history_next(ant_history_t *hist) { 98 if (!hist || !hist->lines || hist->count == 0) return NULL; 99 if (hist->current < hist->count - 1) { 100 hist->current++; 101 return hist->lines[hist->current]; 102 } 103 hist->current = hist->count; 104 return ""; 105} 106 107void ant_history_free(ant_history_t *hist) { 108 if (!hist || !hist->lines) return; 109 for (int i = 0; i < hist->count; i++) free(hist->lines[i]); 110 free(hist->lines); 111 hist->lines = NULL; 112 hist->count = 0; 113 hist->capacity = 0; 114 hist->current = -1; 115} 116 117static char *get_history_path(void) { 118 char dir[4096]; 119 120 if (ant_xdg_state_path(dir, sizeof(dir), NULL) != 0) return NULL; 121 if (ant_mkdir_p(dir) != 0) return NULL; 122 123 size_t len = strlen(dir) + sizeof("/repl_history"); 124 char *path = malloc(len); 125 126 if (!path) return NULL; 127 snprintf(path, len, "%s/repl_history", dir); 128 129 return path; 130} 131 132void ant_history_load(ant_history_t *hist) { 133 if (!hist || !hist->lines || hist->capacity <= 0) return; 134 135 char *path = get_history_path(); 136 if (!path) return; 137 138 FILE *fp = fopen(path, "r"); 139 free(path); 140 141 if (!fp) return; 142 char header[sizeof(HISTORY_FILE_MAGIC)]; 143 144 if ( 145 !fgets(header, sizeof(header), fp) || 146 strcmp(header, (const char *)HISTORY_FILE_MAGIC) != 0 147 ) { 148 fclose(fp); 149 return; 150 } 151 152 char lenbuf[32]; 153 while (fgets(lenbuf, sizeof(lenbuf), fp)) { 154 char *end = NULL; 155 unsigned long long record_len = strtoull(lenbuf, &end, 10); 156 157 if (end == lenbuf || (*end != '\n' && *end != '\0')) break; 158 if (record_len > (unsigned long long)SIZE_MAX - 1) break; 159 160 size_t line_len = (size_t)record_len; 161 char *line = malloc(line_len + 1); 162 163 if (!line) break; 164 165 if (fread(line, 1, line_len, fp) != line_len) { 166 free(line); 167 break; 168 } 169 170 line[line_len] = '\0'; 171 if (line[0]) ant_history_add(hist, line); 172 free(line); 173 174 int sep = fgetc(fp); 175 if (sep != '\n' && sep != EOF) break; 176 } 177 178 fclose(fp); 179} 180 181void ant_history_save(const ant_history_t *hist) { 182 if (!hist || !hist->lines) return; 183 184 char *path = get_history_path(); 185 if (!path) return; 186 187 FILE *fp = fopen(path, "w"); 188 free(path); 189 190 if (!fp) return; 191 fputs((const char *)HISTORY_FILE_MAGIC, fp); 192 193 for (int i = 0; i < hist->count; i++) { 194 size_t len = strlen(hist->lines[i]); 195 fprintf(fp, "%zu\n", len); 196 fwrite(hist->lines[i], 1, len, fp); 197 fputc('\n', fp); 198 } 199 200 fclose(fp); 201} 202 203typedef enum { 204 KEY_NONE, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, 205 KEY_HOME, KEY_END, KEY_DELETE, KEY_BACKSPACE, KEY_ENTER, KEY_EOF, KEY_CHAR 206} key_type_t; 207 208typedef struct { 209 key_type_t type; 210 int ch; 211} key_event_t; 212 213static int repl_terminal_cols(void) { 214 int cols = 80; 215#ifdef _WIN32 216 CONSOLE_SCREEN_BUFFER_INFO csbi; 217 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { 218 cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; 219 } 220#else 221 struct winsize ws; 222 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) { 223 cols = ws.ws_col; 224 } 225#endif 226 return (cols > 0) ? cols : 80; 227} 228 229static void repl_jump_cursor(int x_pos, int y_offset) { 230#ifdef _WIN32 231 if (y_offset != 0) { 232 char seq[32]; 233 snprintf(seq, sizeof(seq), "\033[%d%c", abs(y_offset), (y_offset > 0) ? 'B' : 'A'); 234 fputs(seq, stdout); 235 } 236 char seq[32]; 237 snprintf(seq, sizeof(seq), "\033[%dG", x_pos + 1); 238 fputs(seq, stdout); 239#else 240 if (y_offset != 0) { 241 char seq[32]; 242 snprintf(seq, sizeof(seq), "\033[%d%c", abs(y_offset), (y_offset > 0) ? 'B' : 'A'); 243 fputs(seq, stdout); 244 } 245 char seq[32]; 246 snprintf(seq, sizeof(seq), "\033[%dG", x_pos + 1); 247 fputs(seq, stdout); 248#endif 249} 250 251static void repl_clear_to_end(void) { 252 fputs("\033[J", stdout); 253} 254 255static void repl_set_cursor_visible(bool visible) { 256#ifdef _WIN32 257 (void)visible; 258#else 259 fputs(visible ? "\033[?25h" : "\033[?25l", stdout); 260#endif 261} 262 263static size_t repl_skip_ansi_escape(const char *s, size_t len, size_t i) { 264 if (i >= len || (unsigned char)s[i] != 0x1B) return i; 265 if (i + 1 >= len) return i + 1; 266 267 unsigned char next = (unsigned char)s[i + 1]; 268 269 if (next == '[') { 270 i += 2; 271 while (i < len) { 272 unsigned char ch = (unsigned char)s[i++]; 273 if (ch >= 0x40 && ch <= 0x7E) break; 274 } 275 return i; 276 } 277 278 if (next == ']') { 279 i += 2; 280 while (i < len) { 281 unsigned char ch = (unsigned char)s[i]; 282 if (ch == '\a') return i + 1; 283 if (ch == 0x1B && i + 1 < len && s[i + 1] == '\\') return i + 2; 284 i++; 285 } 286 return i; 287 } 288 289 return i + 2; 290} 291 292static void repl_virtual_render( 293 const char *s, size_t n, 294 int screen_cols, int prompt_len, 295 int *x, int *y 296) { 297 bool wrapped = false; 298 size_t i = 0; 299 while (i < n) { 300 unsigned char c = (unsigned char)s[i]; 301 if (c == '\n' || c == '\r') { 302 if (c == '\n' && !wrapped) (*y)++; 303 *x = prompt_len; 304 i++; 305 wrapped = false; 306 continue; 307 } 308 309 if (c == 0x1B) { 310 i = repl_skip_ansi_escape(s, n, i); 311 continue; 312 } 313 314 utf8proc_int32_t cp = 0; 315 utf8proc_ssize_t clen = 316 utf8_next((const utf8proc_uint8_t *)(s + i), (utf8proc_ssize_t)(n - i), &cp); 317 if (clen <= 0) { 318 clen = 1; 319 cp = c; 320 } 321 322 int w = utf8proc_charwidth(cp); 323 if (w < 0) w = 1; 324 325 if (w > 0) { 326 *x += w; 327 wrapped = false; 328 if (*x >= screen_cols) { 329 *x = 0; 330 (*y)++; 331 wrapped = true; 332 } 333 } 334 335 i += (size_t)clen; 336 } 337} 338 339static int repl_prompt_width(const char *prompt, int cols) { 340 int x = 0; 341 int y = 0; 342 repl_virtual_render(prompt, strlen(prompt), cols, 0, &x, &y); 343 return x; 344} 345 346static void refresh_line(const char *line, int len, int pos, const char *prompt) { 347 int cols = repl_terminal_cols(); 348 int prompt_len = repl_prompt_width(prompt, cols); 349 int x_cursor = prompt_len; 350 int y_cursor = 0; 351 int x_end = prompt_len; 352 int y_end = 0; 353 354 repl_virtual_render(line, (size_t)pos, cols, prompt_len, &x_cursor, &y_cursor); 355 repl_virtual_render(line, (size_t)len, cols, prompt_len, &x_end, &y_end); 356 357 repl_set_cursor_visible(false); 358 repl_jump_cursor(prompt_len, -repl_last_cursor_row); 359 repl_clear_to_end(); 360 if (crprintf_get_color() && len > 0) { 361 if (len <= 2048) { 362 char tagged[8192]; 363 char rendered[8192]; 364 365 highlight_state state = hl_line_state; 366 ant_highlight_stateful(line, (size_t)len, tagged, sizeof(tagged), &state); 367 368 hl_prog = crprintf_recompile(hl_prog, tagged); 369 crprintf_state *rs = crprintf_state_new(); 370 crsprintf_compiled(rendered, sizeof(rendered), rs, hl_prog); 371 crprintf_state_free(rs); 372 373 fputs(rendered, stdout); 374 } else fwrite(line, 1, (size_t)len, stdout); 375 } else if (len > 0) fwrite(line, 1, (size_t)len, stdout); 376 377#ifndef _WIN32 378 if ((x_end == 0) && (y_end > 0) && (len > 0) && (line[len - 1] != '\n')) { 379 fputs("\n", stdout); 380 } 381#endif 382 383 repl_jump_cursor(x_cursor, -(y_end - y_cursor)); 384 repl_set_cursor_visible(true); 385 repl_last_cursor_row = y_cursor; 386 fflush(stdout); 387} 388 389static void move_cursor_only(const char *line, int pos, const char *prompt) { 390 int cols = repl_terminal_cols(); 391 int prompt_len = repl_prompt_width(prompt, cols); 392 int x_cursor = prompt_len; 393 int y_cursor = 0; 394 repl_virtual_render(line, (size_t)pos, cols, prompt_len, &x_cursor, &y_cursor); 395 repl_jump_cursor(x_cursor, -(repl_last_cursor_row - y_cursor)); 396 repl_last_cursor_row = y_cursor; 397 fflush(stdout); 398} 399 400static int utf8_prev_pos(const char *line, int pos) { 401 if (pos <= 0) return 0; 402 int prev = 0; 403 int i = 0; 404 while (i < pos) { 405 prev = i; 406 utf8proc_int32_t cp = 0; 407 utf8proc_ssize_t n = utf8_next( 408 (const utf8proc_uint8_t *)(line + i), 409 (utf8proc_ssize_t)(pos - i), 410 &cp 411 ); 412 i += (int)((n > 0) ? n : 1); 413 } 414 return prev; 415} 416 417static int utf8_next_pos(const char *line, int len, int pos) { 418 if (pos >= len) return len; 419 utf8proc_int32_t cp = 0; 420 utf8proc_ssize_t n = utf8_next( 421 (const utf8proc_uint8_t *)(line + pos), 422 (utf8proc_ssize_t)(len - pos), 423 &cp 424 ); 425 int next = pos + (int)((n > 0) ? n : 1); 426 return (next > len) ? len : next; 427} 428 429static void line_set(char *line, int *pos, int *len, const char *str, const char *prompt) { 430 size_t n = strlen(str); 431 if (n >= (size_t)MAX_LINE_LENGTH) n = (size_t)MAX_LINE_LENGTH - 1; 432 memcpy(line, str, n); 433 line[n] = '\0'; 434 *len = (int)strlen(line); 435 *pos = *len; 436 refresh_line(line, *len, *pos, prompt); 437} 438 439static void line_backspace(char *line, int *pos, int *len, const char *prompt) { 440 if (*pos <= 0) return; 441 442 int prev = utf8_prev_pos(line, *pos); 443 memmove(line + prev, line + *pos, (size_t)(*len - *pos + 1)); 444 *len -= (*pos - prev); 445 *pos = prev; 446 refresh_line(line, *len, *pos, prompt); 447} 448 449static void line_delete(char *line, int *pos, int *len, const char *prompt) { 450 if (*pos >= *len) return; 451 int next = utf8_next_pos(line, *len, *pos); 452 memmove(line + *pos, line + next, (size_t)(*len - next + 1)); 453 *len -= (next - *pos); 454 refresh_line(line, *len, *pos, prompt); 455} 456 457static void line_insert(char *line, int *pos, int *len, int c, const char *prompt) { 458 if (*len >= MAX_LINE_LENGTH - 1) return; 459 460 memmove(line + *pos + 1, line + *pos, (size_t)(*len - *pos + 1)); 461 line[*pos] = (char)c; 462 (*pos)++; 463 (*len)++; 464 refresh_line(line, *len, *pos, prompt); 465} 466 467#ifdef _WIN32 468static key_event_t read_key(void) { 469 if (ctrl_c_pressed > 0) return (key_event_t){ KEY_EOF, 0 }; 470 int c = _getch(); 471 if (c == 0 || c == 0xE0) { 472 int ext = _getch(); 473 switch (ext) { 474 case 72: return (key_event_t){ KEY_UP, 0 }; 475 case 80: return (key_event_t){ KEY_DOWN, 0 }; 476 case 77: return (key_event_t){ KEY_RIGHT, 0 }; 477 case 75: return (key_event_t){ KEY_LEFT, 0 }; 478 case 71: return (key_event_t){ KEY_HOME, 0 }; 479 case 79: return (key_event_t){ KEY_END, 0 }; 480 case 83: return (key_event_t){ KEY_DELETE, 0 }; 481 default: return (key_event_t){ KEY_NONE, 0 }; 482 } 483 } 484 if (c == 8) return (key_event_t){ KEY_BACKSPACE, 0 }; 485 if (c == '\r' || c == '\n') return (key_event_t){ KEY_ENTER, 0 }; 486 if (c == 3) { 487 ctrl_c_pressed++; 488 return (key_event_t){ KEY_EOF, 0 }; 489 } 490 if (c == 4 || c == 26) return (key_event_t){ KEY_EOF, 0 }; 491 if (isprint(c) || (unsigned char)c >= 0x80) return (key_event_t){ KEY_CHAR, c }; 492 return (key_event_t){ KEY_NONE, 0 }; 493} 494#else 495static struct termios saved_tio; 496 497static key_event_t read_key(void) { 498 if (ctrl_c_pressed > 0) return (key_event_t){ KEY_EOF, 0 }; 499 int c = getchar(); 500 if (c == EOF && !feof(stdin)) { 501 clearerr(stdin); 502 return (key_event_t){ KEY_EOF, 0 }; 503 } 504 if (c == EOF) return (key_event_t){ KEY_EOF, 0 }; 505 506 if (c == 27) { 507 int seq1 = getchar(); 508 if (seq1 == EOF) return (key_event_t){ KEY_NONE, 0 }; 509 if (seq1 == 'O') { 510 int seq2 = getchar(); 511 if (seq2 == 'H') return (key_event_t){ KEY_HOME, 0 }; 512 if (seq2 == 'F') return (key_event_t){ KEY_END, 0 }; 513 return (key_event_t){ KEY_NONE, 0 }; 514 } 515 if (seq1 != '[') return (key_event_t){ KEY_NONE, 0 }; 516 int seq2 = getchar(); 517 if (seq2 == EOF) return (key_event_t){ KEY_NONE, 0 }; 518 switch (seq2) { 519 case 'A': return (key_event_t){ KEY_UP, 0 }; 520 case 'B': return (key_event_t){ KEY_DOWN, 0 }; 521 case 'C': return (key_event_t){ KEY_RIGHT, 0 }; 522 case 'D': return (key_event_t){ KEY_LEFT, 0 }; 523 case 'H': return (key_event_t){ KEY_HOME, 0 }; 524 case 'F': return (key_event_t){ KEY_END, 0 }; 525 default: { 526 if (seq2 >= '0' && seq2 <= '9') { 527 int seq3 = getchar(); 528 if (seq3 == '~') { 529 if (seq2 == '1' || seq2 == '7') return (key_event_t){ KEY_HOME, 0 }; 530 if (seq2 == '4' || seq2 == '8') return (key_event_t){ KEY_END, 0 }; 531 if (seq2 == '3') return (key_event_t){ KEY_DELETE, 0 }; 532 } 533 } 534 return (key_event_t){ KEY_NONE, 0 }; 535 } 536 } 537 } 538 539 if (c == 127 || c == 8) return (key_event_t){ KEY_BACKSPACE, 0 }; 540 if (c == '\n' || c == '\r') return (key_event_t){ KEY_ENTER, 0 }; 541 if (isprint(c) || (unsigned char)c >= 0x80) return (key_event_t){ KEY_CHAR, c }; 542 return (key_event_t){ KEY_NONE, 0 }; 543} 544#endif 545 546static void term_restore(void) { 547#ifndef _WIN32 548 tcsetattr(STDIN_FILENO, TCSANOW, &saved_tio); 549#endif 550} 551 552static char *read_line_with_history(ant_history_t *hist, const char *prompt) { 553 char *line = malloc(MAX_LINE_LENGTH); 554 if (!line) return NULL; 555 int pos = 0; 556 int len = 0; 557 line[0] = '\0'; 558 repl_last_cursor_row = 0; 559 560#ifndef _WIN32 561 struct termios new_tio; 562 tcgetattr(STDIN_FILENO, &saved_tio); 563 new_tio = saved_tio; 564 new_tio.c_lflag &= ~(ICANON | ECHO); 565 tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); 566#endif 567 568 for (;;) { 569 key_event_t key = read_key(); 570 static const void *dispatch[] = { 571 [KEY_NONE] = &&l_none, 572 [KEY_UP] = &&l_up, 573 [KEY_DOWN] = &&l_down, 574 [KEY_LEFT] = &&l_left, 575 [KEY_RIGHT] = &&l_right, 576 [KEY_HOME] = &&l_home, 577 [KEY_END] = &&l_end, 578 [KEY_DELETE] = &&l_delete, 579 [KEY_BACKSPACE] = &&l_backspace, 580 [KEY_ENTER] = &&l_enter, 581 [KEY_EOF] = &&l_eof, 582 [KEY_CHAR] = &&l_char, 583 }; 584 585 unsigned int t = (unsigned int)key.type; 586 if (t < (sizeof(dispatch) / sizeof(*dispatch)) && dispatch[t]) goto *dispatch[t]; 587 goto l_none; 588 589 l_enter: 590 putchar('\n'); 591 term_restore(); 592 return line; 593 594 l_eof: 595 putchar('\n'); 596 term_restore(); 597 free(line); 598 return NULL; 599 600 l_up: { 601 const char *h = ant_history_prev(hist); 602 if (h) line_set(line, &pos, &len, h, prompt); 603 continue; 604 } 605 606 l_down: { 607 const char *h = ant_history_next(hist); 608 if (h) line_set(line, &pos, &len, h, prompt); 609 continue; 610 } 611 612 l_left: 613 if (pos > 0) { 614 pos = utf8_prev_pos(line, pos); 615 move_cursor_only(line, pos, prompt); 616 } 617 continue; 618 619 l_right: 620 if (pos < len) { 621 pos = utf8_next_pos(line, len, pos); 622 move_cursor_only(line, pos, prompt); 623 } 624 continue; 625 626 l_home: 627 if (pos != 0) { 628 pos = 0; 629 move_cursor_only(line, pos, prompt); 630 } 631 continue; 632 633 l_end: 634 if (pos != len) { 635 pos = len; 636 move_cursor_only(line, pos, prompt); 637 } 638 continue; 639 640 l_delete: 641 line_delete(line, &pos, &len, prompt); 642 continue; 643 644 l_backspace: 645 line_backspace(line, &pos, &len, prompt); 646 continue; 647 648 l_char: 649 line_insert(line, &pos, &len, key.ch, prompt); 650 continue; 651 652 l_none: 653 continue; 654 } 655} 656 657ant_readline_result_t ant_readline( 658 ant_history_t *hist, 659 const char *prompt, 660 highlight_state line_state, 661 char **out_line 662) { 663 if (out_line) *out_line = NULL; 664 665 hl_line_state = line_state; 666 ctrl_c_pressed = 0; 667 char *line = read_line_with_history(hist, prompt); 668 669 if (ctrl_c_pressed > 0) { 670 if (line) free(line); 671 return ANT_READLINE_INTERRUPT; 672 } 673 if (!line) return ANT_READLINE_EOF; 674 675 if (out_line) *out_line = line; 676 return ANT_READLINE_LINE; 677}