terminal.c (31396B)
1 #include <immintrin.h> 2 3 static const u8 utf8overhangmask[32] = { 4 255, 255, 255, 255, 255, 255, 255, 255, 5 255, 255, 255, 255, 255, 255, 255, 255, 6 0, 0, 0, 0, 0, 0, 0, 0, 7 0, 0, 0, 0, 0, 0, 0, 0 8 }; 9 10 static Range 11 get_word_around_cell(Term *t, iv2 cell) 12 { 13 Range result = {.start = cell, .end = cell}; 14 Cell *row = t->views[t->view_idx].fb.rows[cell.y]; 15 16 b32 isspace = ISSPACE(row[cell.x].cp); 17 while (result.start.x > 0) { 18 Cell nc = row[result.start.x - 1]; 19 if (!(nc.style.attr & ATTR_WDUMMY) && isspace != ISSPACE(nc.cp)) 20 break; 21 result.start.x--; 22 } 23 while (result.end.x < t->size.w - 1) { 24 Cell nc = row[result.end.x + 1]; 25 if (!(nc.style.attr & ATTR_WDUMMY) && isspace != ISSPACE(nc.cp)) 26 break; 27 result.end.x++; 28 } 29 30 /* NOTE: ATTR_WDUMMY is invalid for start and end of range */ 31 if (row[result.start.x].style.attr & ATTR_WDUMMY) result.start.x++; 32 if (row[result.end.x].style.attr & ATTR_WDUMMY) result.end.x--; 33 return result; 34 } 35 36 static void 37 set_window_title(GLFWwindow *win, Arena a, s8 title) 38 { 39 glfwSetWindowTitle(win, s8_to_cstr(&a, title)); 40 } 41 42 static s8 43 consume(s8 raw, size count) 44 { 45 raw.data += count; 46 raw.len -= count; 47 return raw; 48 } 49 50 static u8 51 peek(s8 raw, size i) 52 { 53 ASSERT(i < raw.len); 54 return raw.data[i]; 55 } 56 57 static u32 58 get_utf8(s8 *raw) 59 { 60 u32 state = 0, cp; 61 size off = 0; 62 while (off < raw->len) { 63 if (!utf8_decode(&state, &cp, raw->data[off++])) { 64 *raw = consume(*raw, off); 65 return cp; 66 } 67 } 68 return (u32)-1; 69 } 70 71 static u32 72 get_ascii(s8 *raw) 73 { 74 ASSERT(raw->len > 0); 75 u32 result = raw->data[0]; 76 *raw = consume(*raw, 1); 77 return result; 78 } 79 80 static size 81 line_length(Line *l) 82 { 83 ASSERT(l->start <= l->end); 84 return l->end - l->start; 85 } 86 87 static s8 88 line_to_s8(Line *l, RingBuf *rb) 89 { 90 ASSERT(l->start <= l->end); 91 92 s8 result = {.len = l->end - l->start, .data = l->start}; 93 return result; 94 } 95 96 static void 97 init_line(Line *l, u8 *position, CellStyle cursor_state) 98 { 99 l->start = position; 100 l->end = position; 101 l->has_unicode = 0; 102 l->cursor_state = cursor_state; 103 } 104 105 static void 106 feed_line(LineBuf *lb, u8 *position, CellStyle cursor_state) 107 { 108 lb->buf[lb->widx++].end = position; 109 lb->widx = lb->widx >= lb->cap ? 0 : lb->widx; 110 lb->filled += lb->filled <= lb->widx; 111 init_line(lb->buf + lb->widx, position, cursor_state); 112 } 113 114 static void 115 selection_clear(Selection *s) 116 { 117 s->range.end = INVALID_RANGE_END; 118 s->state = SS_NONE; 119 } 120 121 static void 122 selection_scroll(Term *t, u32 origin, i32 n) 123 { 124 Selection *s = &t->selection; 125 if (!is_valid_range(s->range)) 126 return; 127 128 b32 start_in_bounds = BETWEEN(s->range.start.y, origin, t->bot); 129 b32 end_in_bounds = BETWEEN(s->range.end.y, origin, t->bot); 130 if (start_in_bounds != end_in_bounds) { 131 selection_clear(s); 132 } else if (start_in_bounds) { 133 s->range.start.y += n; 134 s->range.end.y += n; 135 if (s->range.start.y > t->bot || s->range.start.y < t->top || 136 s->range.end.y > t->bot || s->range.end.y < t->top) 137 selection_clear(s); 138 } 139 } 140 141 static b32 142 is_selected(Selection *s, i32 x, i32 y) 143 { 144 if (!is_valid_range(s->range)) 145 return 0; 146 147 b32 result = BETWEEN(y, s->range.start.y, s->range.end.y) && 148 (y != s->range.start.y || x >= s->range.start.x) && 149 (y != s->range.end.y || x <= s->range.end.x); 150 return result; 151 } 152 153 static void 154 fb_clear_region(Term *t, u32 r1, u32 r2, u32 c1, u32 c2) 155 { 156 u32 tmp; 157 if (r1 > r2) { 158 tmp = r1; 159 r1 = r2; 160 r2 = tmp; 161 } 162 if (c1 > c2) { 163 tmp = c1; 164 c1 = c2; 165 c2 = tmp; 166 } 167 CLAMP(c1, 0, t->size.w - 1); 168 CLAMP(c2, 0, t->size.w - 1); 169 CLAMP(r1, 0, t->size.h - 1); 170 CLAMP(r2, 0, t->size.h - 1); 171 172 TermView *tv = t->views + t->view_idx; 173 for (u32 r = r1; r <= r2; r++) { 174 for (u32 c = c1; c <= c2; c++) { 175 tv->fb.rows[r][c].style = t->cursor.style; 176 tv->fb.rows[r][c].cp = ' '; 177 if (is_selected(&t->selection, c, r)) 178 selection_clear(&t->selection); 179 } 180 } 181 } 182 183 static void 184 fb_scroll_down(Term *t, u32 top, u32 n) 185 { 186 if (!BETWEEN(top, t->top, t->bot)) 187 return; 188 189 TermView *tv = t->views + t->view_idx; 190 CLAMP(n, 0, t->bot - top + 1); 191 fb_clear_region(t, t->bot - n + 1, t->bot, 0, t->size.w); 192 for (u32 i = t->bot; i >= top + n; i--) { 193 Row tmp = tv->fb.rows[i]; 194 tv->fb.rows[i] = tv->fb.rows[i - n]; 195 tv->fb.rows[i - n] = tmp; 196 } 197 selection_scroll(t, top, n); 198 } 199 200 static void 201 fb_scroll_up(Term *t, u32 top, u32 n) 202 { 203 if (!BETWEEN(top, t->top, t->bot)) 204 return; 205 206 TermView *tv = t->views + t->view_idx; 207 CLAMP(n, 0, t->bot - top + 1); 208 fb_clear_region(t, top, top + n - 1, 0, t->size.w); 209 for (u32 i = top; i <= t->bot - n; i++) { 210 Row tmp = tv->fb.rows[i]; 211 tv->fb.rows[i] = tv->fb.rows[i + n]; 212 tv->fb.rows[i + n] = tmp; 213 } 214 selection_scroll(t, top, -n); 215 } 216 217 static void 218 swap_screen(Term *t) 219 { 220 t->mode ^= TM_ALTSCREEN; 221 t->view_idx = !!(t->mode & TM_ALTSCREEN); 222 t->gl.flags |= NEEDS_FULL_REFILL; 223 } 224 225 static void 226 cursor_reset(Term *t) 227 { 228 //(Colour){.rgba = 0x1e9e33ff}; 229 t->cursor.style.fg = g_colours.data[g_colours.fgidx]; 230 t->cursor.style.bg = g_colours.data[g_colours.bgidx]; 231 t->cursor.style.attr = ATTR_NULL; 232 t->cursor.state = CURSOR_NORMAL; 233 } 234 235 static void 236 cursor_move_to(Term *t, i32 row, i32 col) 237 { 238 i32 minr = 0, maxr = t->size.h - 1; 239 if (t->cursor.state & CURSOR_ORIGIN) { 240 minr = t->top; 241 maxr = t->bot; 242 } 243 t->cursor.pos.y = CLAMP(row, minr, maxr); 244 t->cursor.pos.x = CLAMP(col, 0, t->size.w - 1); 245 t->cursor.state &= ~CURSOR_WRAP_NEXT; 246 } 247 248 static void 249 cursor_move_abs_to(Term *t, i32 row, i32 col) 250 { 251 if (t->cursor.state & CURSOR_ORIGIN) 252 row += t->top; 253 cursor_move_to(t, row, col); 254 } 255 256 static void 257 cursor_alt(Term *t, b32 save) 258 { 259 i32 mode = t->view_idx; 260 if (save) { 261 t->saved_cursors[mode] = t->cursor; 262 } else { 263 t->cursor = t->saved_cursors[mode]; 264 cursor_move_to(t, t->cursor.pos.y, t->cursor.pos.x); 265 } 266 } 267 268 /* NOTE: advance the cursor by <n> cells; handles reverse movement */ 269 static void 270 cursor_step_column(Term *t, i32 n) 271 { 272 i32 col = t->cursor.pos.x + n; 273 i32 row = t->cursor.pos.y; 274 if (col >= t->size.w) { 275 row++; 276 col = 0; 277 if (row >= t->size.h) 278 fb_scroll_up(t, t->top, 1); 279 } 280 cursor_move_to(t, row, col); 281 } 282 283 /* NOTE: steps the cursor without causing a scroll */ 284 static void 285 cursor_step_raw(Term *t, i32 step, i32 rows, i32 cols) 286 { 287 rows *= step; 288 cols *= step; 289 cursor_move_to(t, t->cursor.pos.y + rows, t->cursor.pos.x + cols); 290 } 291 292 static void 293 term_reset(Term *t) 294 { 295 i32 mode = t->mode & TM_ALTSCREEN; 296 for (u32 i = 0; i < ARRAY_COUNT(t->saved_cursors); i++) { 297 cursor_reset(t); 298 cursor_move_to(t, 0, 0); 299 cursor_alt(t, 1); 300 swap_screen(t); 301 fb_clear_region(t, 0, t->size.h, 0, t->size.w); 302 } 303 t->top = 0; 304 t->bot = t->size.h - 1; 305 /* TODO: why is term_reset() being called when we are in the altscreen */ 306 t->mode = mode|TM_AUTO_WRAP; 307 } 308 309 static void 310 dump_csi(CSI *csi) 311 { 312 os_write_err_msg(s8("raw: ESC[")); 313 for (size i = 0; i < csi->raw.len; i++) { 314 u8 c = csi->raw.data[i]; 315 if (ISPRINT(c)) 316 os_write_err_msg((s8){.len = 1, .data = csi->raw.data + i}); 317 else if (c == '\n') 318 os_write_err_msg(s8("\\n")); 319 else if (c == '\r') 320 os_write_err_msg(s8("\\r")); 321 else 322 fprintf(stderr, "\\x%02X", c); 323 } 324 fprintf(stderr, "\n\tparsed = { .priv = %d, .mode = ", csi->priv); 325 if (ISPRINT(csi->mode)) { 326 u8 buf[1] = {csi->mode}; 327 os_write_err_msg((s8){.len = 1, .data = buf}); 328 } else { 329 fprintf(stderr, "\\x%02X", csi->mode); 330 } 331 fprintf(stderr, ", .argc = %d, .argv = {", csi->argc); 332 for (i32 i = 0; i < csi->argc; i++) 333 fprintf(stderr, " %d", csi->argv[i]); 334 os_write_err_msg(s8(" } }\n")); 335 } 336 337 /* ED/DECSED: Erase in Display */ 338 static void 339 erase_in_display(Term *t, CSI *csi) 340 { 341 iv2 cpos = t->cursor.pos; 342 switch (csi->argv[0]) { 343 case 0: /* Erase Below (default) */ 344 fb_clear_region(t, cpos.y, cpos.y, cpos.x, t->size.w); 345 if (cpos.y < t->size.h - 1) 346 fb_clear_region(t, cpos.y + 1, t->size.h, 0, t->size.w); 347 break; 348 case 1: /* Erase Above */ 349 if (cpos.y > 0) 350 fb_clear_region(t, 0, cpos.y - 1, 0, t->size.w); 351 fb_clear_region(t, cpos.y, cpos.y, 0, cpos.x); 352 break; 353 case 2: /* Erase All */ 354 fb_clear_region(t, 0, t->size.h, 0, t->size.w); 355 break; 356 case 3: /* Erase Saved Lines (xterm) */ 357 /* NOTE: ignored; we don't save lines in the way xterm does */ 358 break; 359 default: ASSERT(0); 360 } 361 } 362 363 /* EL/DECSEL: Erase in Line */ 364 static void 365 erase_in_line(Term *t, CSI *csi) 366 { 367 iv2 cpos = t->cursor.pos; 368 switch (csi->argv[0]) { 369 case 0: /* Erase to Right */ 370 fb_clear_region(t, cpos.y, cpos.y, cpos.x, t->size.w); 371 break; 372 case 1: /* Erase to Left */ 373 fb_clear_region(t, cpos.y, cpos.y, 0, cpos.x); 374 break; 375 case 2: /* Erase All */ 376 fb_clear_region(t, cpos.y, cpos.y, 0, t->size.w); 377 break; 378 default: ASSERT(0); 379 } 380 } 381 382 /* IL: Insert <count> blank lines */ 383 static void 384 insert_blank_lines(Term *t, i32 count) 385 { 386 fb_scroll_down(t, t->cursor.pos.y, count); 387 } 388 389 /* DL: Erase <count> lines */ 390 static void 391 erase_lines(Term *t, i32 count) 392 { 393 fb_scroll_up(t, t->cursor.pos.y, count); 394 } 395 396 /* DCH: Delete Characters (NOTE: DCH is technically different but we are ignoring that) */ 397 /* ECH: Erase Characters */ 398 static void 399 erase_characters(Term *t, i32 count) 400 { 401 iv2 cpos = t->cursor.pos; 402 fb_clear_region(t, cpos.y, cpos.y, cpos.x, cpos.x + count - 1); 403 } 404 405 /* SM/DECSET: Set Mode & RM/DECRST Reset Mode */ 406 static void 407 set_mode(Term *t, CSI *csi, b32 set) 408 { 409 i32 alt = t->view_idx; 410 #define PRIV(a) ((1 << 30) | (a)) 411 for (i32 i = 0; i < csi->argc; i++) { 412 i32 arg = (csi->argv[i]) | ((csi->priv & 1) << 30); 413 switch (arg) { 414 case 4: /* IRM: Insert/Replace Mode */ 415 if (set) t->mode |= TM_REPLACE; 416 else t->mode &= ~TM_REPLACE; 417 break; 418 case 20: /* LNM: Linefeed Assumes Carriage Return */ 419 if (set) t->mode |= TM_CRLF; 420 else t->mode &= ~TM_CRLF; 421 break; 422 case PRIV(1): /* DECCKM: use application cursor keys */ 423 if (set) t->gl.mode |= WIN_MODE_APPCURSOR; 424 else t->gl.mode &= ~WIN_MODE_APPCURSOR; 425 break; 426 case PRIV(5): /* DECSCNM: reverse/normal video mode */ 427 if (set) t->gl.mode |= WIN_MODE_REVERSE; 428 else t->gl.mode &= ~WIN_MODE_REVERSE; 429 break; 430 case PRIV(6): /* DECOM: Cursor Origin Mode */ 431 if (set) t->cursor.state |= CURSOR_ORIGIN; 432 else t->cursor.state &= ~CURSOR_ORIGIN; 433 cursor_move_abs_to(t, 0, 0); 434 break; 435 case PRIV(7): /* DECAWM: Auto-Wrap Mode */ 436 if (set) t->mode |= TM_AUTO_WRAP; 437 else t->mode &= ~TM_AUTO_WRAP; 438 break; 439 case PRIV(3): /* DECCOLM: 132/80 Column Mode */ 440 case PRIV(4): /* DECSCLM: Fast/Slow Scroll */ 441 case PRIV(8): /* DECARM: Auto-Repeat Keys */ 442 case PRIV(12): /* AT&T 610: Start blinking cursor */ 443 case PRIV(40): /* xterm: (dis)allow 132/80 Column Mode */ 444 case PRIV(45): /* XTREVWRAP: xterm reverse wrap around */ 445 case PRIV(1015): /* urxvt: (broken) mouse mode */ 446 /* IGNORED */ 447 break; 448 case PRIV(25): /* DECTCEM: Show/Hide Cursor */ 449 if (!set) t->gl.mode |= WIN_MODE_HIDECURSOR; 450 else t->gl.mode &= ~WIN_MODE_HIDECURSOR; 451 break; 452 case PRIV(1034): /* xterm: enable 8-bit input mode */ 453 if (set) t->gl.mode |= WIN_MODE_8BIT; 454 else t->gl.mode &= ~WIN_MODE_8BIT; 455 break; 456 case PRIV(1049): /* xterm: swap cursor then swap screen */ 457 cursor_alt(t, set); 458 case PRIV(47): /* xterm: swap screen buffer */ 459 case PRIV(1047): /* xterm: swap screen buffer */ 460 if (alt) fb_clear_region(t, 0, t->size.h, 0, t->size.w); 461 if (set ^ alt) swap_screen(t); 462 if (csi->argv[i] != 1049) break; 463 /* FALLTHROUGH */ 464 case PRIV(1048): /* xterm: swap cursor */ 465 cursor_alt(t, set); 466 break; 467 case PRIV(2004): /* xterm: bracketed paste mode */ 468 if (set) t->gl.mode |= WIN_MODE_BRACKPASTE; 469 else t->gl.mode &= ~WIN_MODE_BRACKPASTE; 470 break; 471 default: 472 os_write_err_msg(s8("set_mode: unhandled mode: ")); 473 dump_csi(csi); 474 } 475 } 476 #undef PRIV 477 } 478 479 /* NOTE: adapted from the perl script 256colres.pl in xterm src */ 480 static Colour 481 indexed_colour(i32 index) 482 { 483 Colour result; 484 if (index < 232) { 485 /* NOTE: 16-231 are colours off a 6x6x6 RGB cube */ 486 index -= 16; 487 result.r = 40 * ((index / 36)); 488 result.g = 40 * ((index % 36) / 6); 489 result.b = 40 * ((index % 6)); 490 result.a = 0xFF; 491 if (result.r) result.r += 55; 492 if (result.g) result.g += 55; 493 if (result.b) result.b += 55; 494 } else { 495 /* NOTE: 232-255 are greyscale ramp */ 496 u32 k = (10 * (index - 232) + 8) & 0xFF; 497 result.r = result.g = result.b = k; 498 result.a = 0xFF; 499 } 500 return result; 501 } 502 503 static struct conversion_result 504 direct_colour(i32 *argv, i32 argc, i32 *idx) 505 { 506 struct conversion_result result = {.status = CR_FAILURE}; 507 switch (argv[*idx + 1]) { 508 case 2: /* NOTE: defined RGB colour */ 509 if (*idx + 4 >= argc) { 510 fprintf(stderr, "direct_colour: wrong paramater count: %d\n", argc); 511 break; 512 } 513 u32 r = (u32)argv[*idx + 2]; 514 u32 g = (u32)argv[*idx + 3]; 515 u32 b = (u32)argv[*idx + 4]; 516 *idx += 4; 517 if (r > 0xFF || g > 0xFF || b > 0xFF) { 518 fprintf(stderr, "direct_colour: bad rgb colour: (%u, %u, %u)\n", r, g, b); 519 break; 520 } 521 result.colour = (Colour){.r = r, .g = g, .b = b, .a = 0xFF}; 522 result.status = CR_SUCCESS; 523 break; 524 case 5: /* NOTE: indexed colour */ 525 if (*idx + 2 >= argc) { 526 fprintf(stderr, "direct_colour: wrong paramater count: %d\n", argc); 527 break; 528 } 529 *idx += 2; 530 if (!BETWEEN(argv[*idx], 0, 255)) { 531 fprintf(stderr, "direct_colour: index parameter out of range: %d\n", 532 argv[*idx]); 533 break; 534 } 535 if (BETWEEN(argv[*idx], 0, 16)) 536 result.colour = g_colours.data[argv[*idx]]; 537 else 538 result.colour = indexed_colour(argv[*idx]); 539 result.status = CR_SUCCESS; 540 break; 541 default: 542 fprintf(stderr, "define_colour: unknown argument: %d\n", argv[*idx + 1]); 543 } 544 return result; 545 } 546 547 /* SGR: Select Graphic Rendition */ 548 static void 549 set_colours(Term *t, CSI *csi) 550 { 551 CellStyle *cs = &t->cursor.style; 552 struct conversion_result dcr; 553 for (i32 i = 0; i < csi->argc; i++) { 554 switch (csi->argv[i]) { 555 case 0: cursor_reset(t); break; 556 case 1: cs->attr |= ATTR_BOLD; break; 557 case 2: cs->attr |= ATTR_FAINT; break; 558 case 3: cs->attr |= ATTR_ITALIC; break; 559 case 4: cs->attr |= ATTR_UNDERLINED; break; 560 case 5: cs->attr |= ATTR_BLINK; break; 561 case 7: cs->attr |= ATTR_INVERSE; break; 562 case 8: cs->attr |= ATTR_INVISIBLE; break; 563 case 9: cs->attr |= ATTR_STRUCK; break; 564 case 22: cs->attr &= ~(ATTR_BOLD|ATTR_FAINT); break; 565 case 23: cs->attr &= ~ATTR_ITALIC; break; 566 case 24: cs->attr &= ~ATTR_UNDERLINED; break; 567 case 25: cs->attr &= ~ATTR_BLINK; break; 568 case 27: cs->attr &= ~ATTR_INVERSE; break; 569 case 28: cs->attr &= ~ATTR_INVISIBLE; break; 570 case 29: cs->attr &= ~ATTR_STRUCK; break; 571 case 38: 572 dcr = direct_colour(csi->argv, csi->argc, &i); 573 if (dcr.status == CR_SUCCESS) { 574 cs->fg = dcr.colour; 575 } else { 576 os_write_err_msg(s8("set_colours: ")); 577 dump_csi(csi); 578 } 579 break; 580 581 case 39: cs->fg = g_colours.data[g_colours.fgidx]; break; 582 583 case 48: 584 dcr = direct_colour(csi->argv, csi->argc, &i); 585 if (dcr.status == CR_SUCCESS) { 586 cs->bg = dcr.colour; 587 } else { 588 os_write_err_msg(s8("set_colours: ")); 589 dump_csi(csi); 590 } 591 break; 592 593 case 49: cs->bg = g_colours.data[g_colours.bgidx]; break; 594 595 default: 596 if (BETWEEN(csi->argv[i], 30, 37)) { 597 cs->fg = g_colours.data[csi->argv[i] - 30]; 598 } else if (BETWEEN(csi->argv[i], 40, 47)) { 599 cs->bg = g_colours.data[csi->argv[i] - 40]; 600 } else if (BETWEEN(csi->argv[i], 90, 97)) { 601 cs->fg = g_colours.data[csi->argv[i] - 82]; 602 } else if (BETWEEN(csi->argv[i], 100, 107)) { 603 cs->bg = g_colours.data[csi->argv[i] - 92]; 604 } else { 605 fprintf(stderr, "unhandled colour arg: %d\n", csi->argv[i]); 606 dump_csi(csi); 607 } 608 } 609 } 610 } 611 612 static void 613 set_scrolling_region(Term *t, CSI *csi) 614 { 615 t->top = csi->argv[0]? csi->argv[0] : 0; 616 t->bot = csi->argv[1]? csi->argv[1] : t->size.h - 1; 617 CLAMP(t->top, 0, t->size.h - 1); 618 CLAMP(t->bot, 0, t->size.h - 1); 619 if (t->top > t->bot) { 620 i32 tmp = t->top; 621 t->top = t->bot; 622 t->bot = tmp; 623 } 624 cursor_move_to(t, 0, 0); 625 } 626 627 static void 628 window_manipulation(Term *t, CSI *csi) 629 { 630 const char *s; 631 u32 i; 632 633 switch (csi->argv[0]) { 634 case 22: 635 s = glfwGetWindowTitle(t->gl.window); 636 for (i = 0; i < ARRAY_COUNT(t->saved_title) - 1 && s[i]; i++) 637 t->saved_title[i] = s[i]; 638 t->saved_title[i] = 0; 639 break; 640 case 23: glfwSetWindowTitle(t->gl.window, t->saved_title); break; 641 default: 642 fprintf(stderr, "unhandled xtwinops: %d\n", csi->argv[0]); 643 dump_csi(csi); 644 } 645 } 646 647 static void 648 push_newline(Term *t, b32 move_to_first_col) 649 { 650 i32 row = t->cursor.pos.y; 651 if (row == t->bot && t->scroll_offset == 0) 652 fb_scroll_up(t, t->top, 1); 653 else 654 row++; 655 cursor_move_to(t, row, move_to_first_col? 0 : t->cursor.pos.x); 656 } 657 658 static void 659 push_tab(Term *t, i32 n) 660 { 661 i32 n_abs = ABS(n); 662 i32 n_sgn = SGN(n); 663 i32 advance = n_abs * g_tabstop - (t->cursor.pos.x % g_tabstop); 664 cursor_step_column(t, n_sgn * advance); 665 } 666 667 static i32 668 parse_csi(s8 *r, CSI *csi) 669 { 670 if (peek(*r, 0) == '?') { 671 csi->priv = 1; 672 get_ascii(r); 673 } 674 675 while (r->len) { 676 u32 cp = get_ascii(r); 677 if (ISCONTROL(cp)) { 678 continue; 679 } else if (BETWEEN(cp, '0', '9')) { 680 csi->argv[csi->argc] *= 10; 681 csi->argv[csi->argc] += cp - '0'; 682 continue; 683 } 684 csi->argc++; 685 686 if (cp != ';' || csi->argc == ESC_ARG_SIZ) { 687 if (cp == ';') csi->mode = get_ascii(r); 688 else csi->mode = cp; 689 return 0; 690 } 691 } 692 /* NOTE: if we fell out of the loop then we ran out of characters */ 693 return -1; 694 } 695 696 static void 697 handle_csi(Term *t, CSI *csi) 698 { 699 s8 raw = csi->raw; 700 i32 ret = parse_csi(&raw, csi); 701 ASSERT(ret != -1); 702 703 #define ORONE(x) (x)? (x) : 1 704 705 iv2 p = t->cursor.pos; 706 707 u8 next; 708 switch (csi->mode) { 709 case 'A': cursor_step_raw(t, ORONE(csi->argv[0]), -1, 0); break; 710 case 'B': cursor_step_raw(t, ORONE(csi->argv[0]), 1, 0); break; 711 case 'C': cursor_step_raw(t, ORONE(csi->argv[0]), 0, 1); break; 712 case 'D': cursor_step_raw(t, ORONE(csi->argv[0]), 0, -1); break; 713 case 'E': cursor_move_to(t, p.y + ORONE(csi->argv[0]), 0); break; 714 case 'F': cursor_move_to(t, p.y - ORONE(csi->argv[0]), 0); break; 715 case 'G': cursor_move_to(t, p.y, csi->argv[0] - 1); break; 716 case 'H': cursor_move_abs_to(t, csi->argv[0] - 1, csi->argv[1] - 1); break; 717 case 'J': erase_in_display(t, csi); break; 718 case 'K': erase_in_line(t, csi); break; 719 case 'L': insert_blank_lines(t, ORONE(csi->argv[0])); break; 720 case 'M': erase_lines(t, ORONE(csi->argv[0])); break; 721 case 'P': erase_characters(t, ORONE(csi->argv[0])); break; 722 case 'X': erase_characters(t, ORONE(csi->argv[0])); break; 723 case 'T': insert_blank_lines(t, ORONE(csi->argv[0])); break; 724 case 'Z': push_tab(t, -(ORONE(csi->argv[0]))); break; 725 case 'a': cursor_step_raw(t, ORONE(csi->argv[0]), 0, 1); break; 726 case 'd': cursor_move_abs_to(t, csi->argv[0] - 1, p.x); break; 727 case 'e': cursor_step_raw(t, ORONE(csi->argv[0]), 1, 0); break; 728 case 'f': cursor_move_abs_to(t, csi->argv[0] - 1, csi->argv[1] - 1); break; 729 case 'h': set_mode(t, csi, 1); break; 730 case 'l': set_mode(t, csi, 0); break; 731 case 'm': set_colours(t, csi); break; 732 case 'r': 733 if (csi->priv) 734 goto unknown; 735 set_scrolling_region(t, csi); 736 cursor_move_abs_to(t, 0, 0); 737 break; 738 case 't': window_manipulation(t, csi); break; 739 case '!': 740 next = get_ascii(&raw); 741 if (next == 'p') { 742 /* NOTE: DECSTR: soft terminal reset IGNORED */ 743 break; 744 } 745 /* FALLTHROUGH */ 746 default: 747 unknown: 748 os_write_err_msg(s8("unknown csi: ")); 749 dump_csi(csi); 750 } 751 } 752 753 static i32 754 parse_osc(s8 *raw, OSC *osc) 755 { 756 *osc = (OSC){0}; 757 osc->raw.data = raw->data; 758 759 /* NOTE: parse command then store the rest as a string */ 760 u32 cp; 761 while (raw->len) { 762 cp = get_ascii(raw); 763 osc->raw.len++; 764 if (!BETWEEN(cp, '0', '9')) 765 break; 766 osc->cmd *= 10; 767 osc->cmd += cp - '0'; 768 769 /* TODO: Performance? */ 770 /* NOTE: The maximum OSC in xterm is 119 so if this 771 * exceeds that the whole sequence is malformed */ 772 if (osc->cmd > 1000) 773 break; 774 } 775 776 if (cp != ';' || osc->cmd > 1000) 777 os_die("parse_osc: malformed\n"); 778 779 osc->arg.data = raw->data; 780 while (raw->len) { 781 cp = get_ascii(raw); 782 osc->raw.len++; 783 if (cp == '\a') 784 return 0; 785 if (cp == 0x1B && peek(*raw, 0) == '\\') { 786 get_ascii(raw); 787 osc->raw.len++; 788 return 0; 789 } 790 osc->arg.len++; 791 } 792 /* NOTE: if we fell out of the loop then we ran out of characters */ 793 return -1; 794 } 795 796 static void 797 reset_csi(CSI *csi, s8 *raw) 798 { 799 *csi = (CSI){0}; 800 csi->raw.data = raw->data; 801 } 802 803 static void 804 dump_osc(OSC *osc) 805 { 806 os_write_err_msg(s8("ESC]")); 807 for (size i = 0; i < osc->raw.len; i++) { 808 u8 cp = osc->raw.data[i]; 809 if (ISPRINT(cp)) 810 os_write_err_msg((s8){.len = 1, .data = osc->raw.data + i}); 811 else if (cp == '\n') 812 os_write_err_msg(s8("\\n")); 813 else if (cp == '\r') 814 os_write_err_msg(s8("\\r")); 815 else if (cp == '\a') 816 os_write_err_msg(s8("\\a")); 817 else 818 fprintf(stderr, "\\x%02X", cp); 819 } 820 fprintf(stderr, "\n\t.cmd = %d, .arg = {.len = %zd}\n", osc->cmd, osc->arg.len); 821 } 822 823 static void 824 handle_osc(Term *t, s8 *raw, Arena a) 825 { 826 OSC osc; 827 i32 ret = parse_osc(raw, &osc); 828 ASSERT(ret != -1); 829 830 switch (osc.cmd) { 831 case 0: set_window_title(t->gl.window, a, osc.arg); break; 832 case 1: /* IGNORED: set icon name */ break; 833 case 2: set_window_title(t->gl.window, a, osc.arg); break; 834 default: 835 os_write_err_msg(s8("unhandled osc cmd: ")); 836 dump_osc(&osc); 837 break; 838 } 839 } 840 841 static void 842 handle_escape(Term *t, s8 *raw, Arena a) 843 { 844 u32 cp = get_ascii(raw); 845 switch (cp) { 846 case '[': reset_csi(&t->csi, raw); t->escape |= EM_CSI; break; 847 case ']': handle_osc(t, raw, a); break; 848 case '(': /* GZD4 -- set primary charset G0 */ 849 case ')': /* G1D4 -- set secondary charset G1 */ 850 case '*': /* G2D4 -- set tertiary charset G2 */ 851 case '+': /* G3D4 -- set quaternary charset G3 */ 852 case '%': /* utf-8 mode */ 853 get_ascii(raw); 854 break; 855 case '=': /* DECPAM -- application keypad */ 856 case '>': /* DECPNM -- normal keypad mode */ 857 /* TODO: MODE_APPKEYPAD */ 858 break; 859 case 'c': /* RIS -- Reset to Initial State */ 860 term_reset(t); 861 break; 862 case 'D': /* IND -- Linefeed */ 863 push_newline(t, 0); 864 break; 865 case 'E': /* NEL -- Next Line */ 866 push_newline(t, 1); 867 break; 868 case 'M': /* RI -- Reverse Index */ 869 if (t->cursor.pos.y == t->top) { 870 fb_scroll_down(t, t->top, 1); 871 } else { 872 cursor_move_to(t, t->cursor.pos.y - 1, t->cursor.pos.x); 873 } 874 break; 875 case '7': /* DECSC: Save Cursor */ 876 cursor_alt(t, 1); 877 break; 878 case '8': /* DECRC: Restore Cursor */ 879 cursor_alt(t, 0); 880 break; 881 default: 882 fprintf(stderr, "unknown escape sequence: ESC %c (0x%02x)\n", cp, cp); 883 break; 884 } 885 } 886 887 static void 888 push_control(Term *t, s8 *line, u32 cp, Arena a) 889 { 890 switch (cp) { 891 case 0x1B: handle_escape(t, line, a); break; 892 case '\r': t->cursor.pos.x = 0; break; 893 case '\n': push_newline(t, t->mode & TM_CRLF); break; 894 case '\t': push_tab(t, 1); break; 895 case '\a': /* TODO: ding ding? */ break; 896 case '\b': 897 cursor_move_to(t, t->cursor.pos.y, t->cursor.pos.x - 1); 898 break; 899 } 900 if (cp != 0x1B && t->escape & EM_CSI) { 901 t->csi.raw.len++; 902 } 903 } 904 905 enum escape_moves_cursor_result { 906 EMC_NORMAL_RETURN, 907 EMC_NEEDS_MORE_BYTES, 908 EMC_CURSOR_MOVED, 909 EMC_SWAPPED_SCREEN, 910 }; 911 912 static enum escape_moves_cursor_result 913 validate_osc(Term *t, s8 *raw) 914 { 915 enum escape_moves_cursor_result result = EMC_NORMAL_RETURN; 916 OSC osc; 917 if (parse_osc(raw, &osc) == -1) 918 return EMC_NEEDS_MORE_BYTES; 919 return result; 920 } 921 922 static enum escape_moves_cursor_result 923 check_if_csi_moves_cursor(Term *t, s8 *raw) 924 { 925 enum escape_moves_cursor_result result = EMC_NORMAL_RETURN; 926 CSI csi = {0}; 927 if (parse_csi(raw, &csi) == -1) 928 return EMC_NEEDS_MORE_BYTES; 929 930 i32 mode = t->mode & TM_ALTSCREEN; 931 switch (csi.mode) { 932 case 'A': result = EMC_CURSOR_MOVED; break; 933 case 'B': result = EMC_CURSOR_MOVED; break; 934 case 'C': result = EMC_CURSOR_MOVED; break; 935 case 'D': result = EMC_CURSOR_MOVED; break; 936 case 'E': result = EMC_CURSOR_MOVED; break; 937 case 'F': result = EMC_CURSOR_MOVED; break; 938 case 'G': result = EMC_CURSOR_MOVED; break; 939 case 'H': result = EMC_CURSOR_MOVED; break; 940 case 'Z': result = EMC_CURSOR_MOVED; break; 941 case 'a': result = EMC_CURSOR_MOVED; break; 942 case 'd': result = EMC_CURSOR_MOVED; break; 943 case 'e': result = EMC_CURSOR_MOVED; break; 944 case 'f': result = EMC_CURSOR_MOVED; break; 945 case 'h': set_mode(t, &csi, 1); break; 946 case 'l': set_mode(t, &csi, 0); break; 947 case 'm': set_colours(t, &csi); break; 948 default: break; 949 } 950 951 if (mode != (t->mode & TM_ALTSCREEN)) 952 result = EMC_SWAPPED_SCREEN; 953 954 return result; 955 } 956 957 static enum escape_moves_cursor_result 958 check_if_escape_moves_cursor(Term *t, s8 *raw) 959 { 960 enum escape_moves_cursor_result result = EMC_NORMAL_RETURN; 961 if (raw->len < 2) 962 return EMC_NEEDS_MORE_BYTES; 963 u32 cp = get_ascii(raw); 964 switch(cp) { 965 case '[': 966 result = check_if_csi_moves_cursor(t, raw); 967 break; 968 case ']': 969 result = validate_osc(t, raw); 970 break; 971 case '(': /* GZD4 -- set primary charset G0 */ 972 case ')': /* G1D4 -- set secondary charset G1 */ 973 case '*': /* G2D4 -- set tertiary charset G2 */ 974 case '+': /* G3D4 -- set quaternary charset G3 */ 975 case '%': /* utf-8 mode */ 976 get_ascii(raw); 977 break; 978 case 'c': /* RIS -- Reset to Initial State */ 979 result = EMC_CURSOR_MOVED; 980 break; 981 case 'D': /* IND -- Linefeed */ 982 case 'E': /* NEL -- Next Line */ 983 result = EMC_CURSOR_MOVED; 984 break; 985 case 'M': /* RI -- Reverse Index */ 986 if (t->cursor.pos.y != 0) 987 result = EMC_CURSOR_MOVED; 988 break; 989 case '7': break; 990 case '8': 991 if (!equal_iv2(t->cursor.pos, t->saved_cursors[t->view_idx].pos)) 992 result = EMC_CURSOR_MOVED; 993 break; 994 default: break; 995 } 996 return result; 997 } 998 999 static size 1000 split_raw_input_to_lines(Term *t, s8 raw) 1001 { 1002 TermView *tv = t->views + t->view_idx; 1003 size parsed_lines = 0; 1004 __m128i nl = _mm_set1_epi8('\n'); 1005 __m128i esc = _mm_set1_epi8(0x1B); 1006 __m128i uni = _mm_set1_epi8(0x80); 1007 1008 #define SPLIT_LONG 4096L 1009 while (raw.len) { 1010 __m128i hasutf8 = _mm_setzero_si128(); 1011 size count = raw.len > SPLIT_LONG ? SPLIT_LONG : raw.len; 1012 u8 *data = raw.data; 1013 while (count >= 16) { 1014 __m128i vdat = _mm_loadu_si128((__m128i_u *)data); 1015 __m128i hasnl = _mm_cmpeq_epi8(vdat, nl); 1016 __m128i hasesc = _mm_cmpeq_epi8(vdat, esc); 1017 __m128i hasuni = _mm_and_si128(vdat, uni); 1018 __m128i hasproc = _mm_or_si128(hasuni, _mm_or_si128(hasnl, hasesc)); 1019 i32 needsproc = _mm_movemask_epi8(hasproc); 1020 1021 if (needsproc) { 1022 u32 advance = _tzcnt_u32(needsproc); 1023 __m128i utf8mask = _mm_loadu_si128((__m128i_u *)(utf8overhangmask + 16 - advance)); 1024 hasuni = _mm_and_si128(utf8mask, hasuni); 1025 hasutf8 = _mm_or_si128(hasutf8, hasuni); 1026 count -= advance; 1027 data += advance; 1028 break; 1029 } 1030 1031 hasutf8 = _mm_or_si128(hasutf8, hasuni); 1032 count -= 16; 1033 data += 16; 1034 } 1035 tv->lines.buf[tv->lines.widx].has_unicode |= _mm_movemask_epi8(hasutf8); 1036 raw = consume(raw, data - raw.data); 1037 1038 if (raw.len) { 1039 u32 cp = peek(raw, 0); 1040 if (cp == 0x1B) { 1041 s8 old = raw; 1042 raw = consume(raw, 1); 1043 switch (check_if_escape_moves_cursor(t, &raw)) { 1044 case EMC_NEEDS_MORE_BYTES: 1045 t->unprocessed_bytes = old.len; 1046 return parsed_lines; 1047 case EMC_CURSOR_MOVED: 1048 if (line_length(tv->lines.buf + tv->lines.widx)) { 1049 parsed_lines++; 1050 feed_line(&tv->lines, raw.data, t->cursor.style); 1051 } 1052 break; 1053 case EMC_SWAPPED_SCREEN: 1054 parsed_lines++; 1055 feed_line(&tv->lines, old.data, t->cursor.style); 1056 TermView *nv = t->views + t->view_idx; 1057 size nstart = nv->log.widx; 1058 mem_copy(raw, (s8){nv->log.cap, nv->log.buf + nstart}); 1059 commit_to_rb(tv, -raw.len); 1060 commit_to_rb(nv, raw.len); 1061 raw.data = nv->log.buf + nstart; 1062 init_line(nv->lines.buf + nv->lines.widx, raw.data, t->cursor.style); 1063 tv = nv; 1064 break; 1065 default: break; 1066 } 1067 } else if (cp == '\n') { 1068 raw = consume(raw, 1); 1069 parsed_lines++; 1070 feed_line(&tv->lines, raw.data, t->cursor.style); 1071 } else if (cp & 0x80) { 1072 tv->lines.buf[tv->lines.widx].has_unicode = 1; 1073 /* TODO: this is probably slow */ 1074 size old_len = raw.len; 1075 if (get_utf8(&raw) == (u32)-1) { 1076 /* NOTE: Need More Bytes! */ 1077 t->unprocessed_bytes = old_len; 1078 return parsed_lines; 1079 } 1080 } else { 1081 raw = consume(raw, 1); 1082 } 1083 } 1084 1085 tv->lines.buf[tv->lines.widx].end = raw.data; 1086 1087 if (line_length(tv->lines.buf + tv->lines.widx) > SPLIT_LONG) { 1088 parsed_lines++; 1089 feed_line(&tv->lines, raw.data, t->cursor.style); 1090 } 1091 } 1092 t->unprocessed_bytes = 0; 1093 return parsed_lines; 1094 } 1095 1096 static void 1097 push_line(Term *t, Line *line, Arena a) 1098 { 1099 TermView *tv = t->views + t->view_idx; 1100 s8 l = line_to_s8(line, &tv->log); 1101 t->cursor.style = line->cursor_state; 1102 1103 Cell *c; 1104 while (l.len) { 1105 u32 cp; 1106 if (line->has_unicode) cp = get_utf8(&l); 1107 else cp = get_ascii(&l); 1108 1109 /* TODO: handle error case */ 1110 ASSERT(cp != (u32)-1); 1111 1112 if (ISCONTROL(cp)) { 1113 push_control(t, &l, cp, a); 1114 continue; 1115 } else if (t->escape & EM_CSI) { 1116 t->csi.raw.len++; 1117 if (BETWEEN(cp, '@', '~')) { 1118 handle_csi(t, &t->csi); 1119 t->escape &= ~EM_CSI; 1120 } 1121 continue; 1122 } 1123 1124 if (t->mode & TM_AUTO_WRAP && t->cursor.state & CURSOR_WRAP_NEXT) 1125 push_newline(t, 1); 1126 1127 i32 width; 1128 if (line->has_unicode) { 1129 width = wcwidth(cp); 1130 ASSERT(width != -1); 1131 } else { 1132 width = 1; 1133 } 1134 1135 if (t->cursor.pos.x + width > t->size.w) { 1136 /* NOTE: make space for character if mode enabled else 1137 * clobber whatever was on the end of the line */ 1138 if (t->mode & TM_AUTO_WRAP) 1139 push_newline(t, 1); 1140 else 1141 cursor_move_to(t, t->cursor.pos.y, t->size.w - width); 1142 } 1143 1144 c = &tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x]; 1145 c->cp = cp; 1146 c->style = t->cursor.style; 1147 1148 if (width == 2) { 1149 c->style.attr |= ATTR_WIDE; 1150 if (t->cursor.pos.x + 1 < t->size.w) { 1151 Cell *nc = c + 1; 1152 nc->style.attr |= ATTR_WDUMMY; 1153 } 1154 } 1155 1156 if (t->cursor.pos.x + width < t->size.w) 1157 cursor_step_column(t, width); 1158 else 1159 t->cursor.state |= CURSOR_WRAP_NEXT; 1160 1161 if (is_selected(&t->selection, t->cursor.pos.x, t->cursor.pos.y)) 1162 selection_clear(&t->selection); 1163 } 1164 } 1165 1166 static size 1167 get_line_idx(LineBuf *lb, size off) 1168 { 1169 ASSERT(-off <= lb->filled); 1170 size result = lb->widx + off; 1171 if (result < 0) 1172 result += lb->filled; 1173 return result; 1174 } 1175 1176 static void 1177 blit_lines(Term *t, Arena a, size line_count) 1178 { 1179 TermView *tv = t->views + t->view_idx; 1180 1181 if (t->gl.flags & NEEDS_FULL_REFILL) { 1182 term_reset(t); 1183 line_count = t->size.h - 1; 1184 } 1185 1186 size off = t->scroll_offset; 1187 CLAMP(line_count, 0, tv->lines.filled); 1188 for (size idx = -line_count; idx <= 0; idx++) { 1189 size line_idx = get_line_idx(&tv->lines, idx - off); 1190 if (line_idx == tv->last_line_idx) 1191 t->cursor.pos = tv->last_cursor_pos; 1192 tv->last_cursor_pos = t->cursor.pos; 1193 tv->last_line_idx = line_idx; 1194 push_line(t, tv->lines.buf + line_idx, a); 1195 /* TODO: can we avoid this? */ 1196 ASSERT(t->escape == 0); 1197 } 1198 1199 t->gl.flags &= ~(NEEDS_FULL_REFILL|NEEDS_REFILL); 1200 t->gl.flags |= NEEDS_BLIT; 1201 }