terminal.c (37782B)
1 /* See LICENSE for copyright details */ 2 3 /* TODO: build own wide char tables */ 4 i32 wcwidth(u32 cp); 5 6 static const u8 utf8overhangmask[32] = { 7 255, 255, 255, 255, 255, 255, 255, 255, 8 255, 255, 255, 255, 255, 255, 255, 255, 9 0, 0, 0, 0, 0, 0, 0, 0, 10 0, 0, 0, 0, 0, 0, 0, 0 11 }; 12 13 #define SPLIT_LONG 4096L 14 15 static iv2 16 initialize_framebuffer(Framebuffer *fb, iv2 term_size) 17 { 18 size cell_memory_size = sizeof(Cell) * term_size.h * term_size.w; 19 size rows_memory_size = sizeof(Row) * term_size.h; 20 21 /* NOTE: make sure cell memory size is a multiple of pointer size */ 22 cell_memory_size += (sizeof(void *) - cell_memory_size % sizeof(void *)); 23 24 if (cell_memory_size + rows_memory_size >= fb->backing_store.size) 25 term_size.h = (fb->backing_store.size - cell_memory_size) / sizeof(Row); 26 27 fb->cells = fb->backing_store.memory; 28 fb->rows = fb->backing_store.memory + cell_memory_size; 29 30 for (i32 i = 0; i < term_size.h; i++) 31 fb->rows[i] = fb->cells + i * term_size.w; 32 33 return term_size; 34 } 35 36 static Range 37 get_word_around_cell(Term *t, iv2 cell) 38 { 39 Range result = {.start = cell, .end = cell}; 40 Cell *row = t->views[t->view_idx].fb.rows[cell.y]; 41 42 b32 isspace = ISSPACE(row[cell.x].cp) && !(row[cell.x].bg & ATTR_WDUMMY); 43 while (result.start.x > 0) { 44 Cell *nc = row + result.start.x - 1; 45 if (!(nc->bg & ATTR_WDUMMY) && isspace != ISSPACE(nc->cp)) 46 break; 47 result.start.x--; 48 } 49 while (result.end.x < t->size.w - 1) { 50 Cell *nc = row + result.end.x + 1; 51 if (!(nc->bg & ATTR_WDUMMY) && isspace != ISSPACE(nc->cp)) 52 break; 53 result.end.x++; 54 } 55 56 return result; 57 } 58 59 static Range 60 get_char_around_cell(Term *t, iv2 cell) 61 { 62 Range result = {.start = cell, .end = cell}; 63 64 /* NOTE: if the cell is WDUMMY move back until we find the starting 65 * WIDE cell. then set the range end to the last WDUMMY cell */ 66 Cell *row = t->views[t->view_idx].fb.rows[cell.y]; 67 if (row[cell.x].bg & ATTR_WDUMMY) { 68 ASSERT(cell.x - 1 >= 0); 69 cell.x--; 70 ASSERT(row[cell.x].bg & ATTR_WIDE); 71 result.start = cell; 72 } else if (row[cell.x].bg & ATTR_WIDE) { 73 ASSERT(cell.x + 1 < t->size.w); 74 cell.x++; 75 ASSERT(row[cell.x].bg & ATTR_WDUMMY); 76 result.end = cell; 77 } 78 79 return result; 80 } 81 82 static s8 83 consume(s8 raw, size count) 84 { 85 raw.data += count; 86 raw.len -= count; 87 return raw; 88 } 89 90 static u8 91 peek(s8 raw, size i) 92 { 93 ASSERT(i < raw.len); 94 return raw.data[i]; 95 } 96 97 static u32 98 get_utf8(s8 *raw) 99 { 100 u32 state = 0, cp; 101 size off = 0; 102 while (off < raw->len) { 103 if (!utf8_decode(&state, &cp, raw->data[off++])) { 104 *raw = consume(*raw, off); 105 return cp; 106 } 107 } 108 return (u32)-1; 109 } 110 111 static u32 112 get_ascii(s8 *raw) 113 { 114 ASSERT(raw->len > 0); 115 u32 result = raw->data[0]; 116 *raw = consume(*raw, 1); 117 return result; 118 } 119 120 static size 121 line_length(Line *l) 122 { 123 ASSERT(l->start <= l->end); 124 return l->end - l->start; 125 } 126 127 static s8 128 line_to_s8(Line *l, RingBuf *rb) 129 { 130 ASSERT(l->start <= l->end); 131 132 s8 result = {.len = l->end - l->start, .data = l->start}; 133 return result; 134 } 135 136 static void 137 init_line(Line *l, u8 *position, CellStyle cursor_state) 138 { 139 l->start = position; 140 l->end = position; 141 l->has_unicode = 0; 142 l->cursor_state = cursor_state; 143 } 144 145 static size 146 feed_line(LineBuf *lb, u8 *position, CellStyle cursor_state) 147 { 148 size result = 0; 149 if (lb->buf[lb->widx].start != position) { 150 lb->buf[lb->widx++].end = position; 151 lb->widx = lb->widx >= lb->cap ? 0 : lb->widx; 152 lb->filled += lb->filled <= lb->widx; 153 init_line(lb->buf + lb->widx, position, cursor_state); 154 result = 1; 155 } 156 return result; 157 } 158 159 static void 160 selection_clear(Selection *s) 161 { 162 s->range.end = INVALID_RANGE_END; 163 s->state = SS_NONE; 164 } 165 166 static void 167 selection_scroll(Term *t, u32 origin, i32 n) 168 { 169 Selection *s = &t->selection; 170 if (!is_valid_range(s->range)) 171 return; 172 173 b32 start_in_bounds = BETWEEN(s->range.start.y, origin, t->bot); 174 b32 end_in_bounds = BETWEEN(s->range.end.y, origin, t->bot); 175 if (start_in_bounds != end_in_bounds) { 176 selection_clear(s); 177 } else if (start_in_bounds) { 178 s->range.start.y += n; 179 s->range.end.y += n; 180 if (s->range.start.y > t->bot || s->range.start.y < t->top || 181 s->range.end.y > t->bot || s->range.end.y < t->top) 182 selection_clear(s); 183 } 184 } 185 186 static b32 187 is_selected(Selection *s, i32 x, i32 y) 188 { 189 if (!is_valid_range(s->range)) 190 return 0; 191 192 b32 result = BETWEEN(y, s->range.start.y, s->range.end.y) && 193 (y != s->range.start.y || x >= s->range.start.x) && 194 (y != s->range.end.y || x <= s->range.end.x); 195 return result; 196 } 197 198 static b32 199 selection_intersects_region(Selection *s, uv2 tl, uv2 br) 200 { 201 /* TODO: maybe this can be further simplified (eg. with a k-map) */ 202 Range r = s->range; 203 b32 valid = is_valid_range(r); 204 b32 whole = r.start.y < tl.y && r.end.y > br.y; 205 b32 start_x = r.start.x >= tl.x && r.start.x <= br.x; 206 b32 start_y = r.start.y >= tl.y && r.start.y <= br.y; 207 b32 end_x = r.end.x >= tl.x && r.end.x <= br.x; 208 b32 end_y = r.end.y >= tl.y && r.end.y <= br.y; 209 b32 result = valid && (whole || (start_y && start_x) || (end_y && end_x)); 210 return result; 211 } 212 213 static void 214 fb_clear_region(Term *t, u32 r1, u32 r2, u32 c1, u32 c2) 215 { 216 BEGIN_TIMED_BLOCK(); 217 u32 tmp; 218 if (r1 > r2) { 219 tmp = r1; 220 r1 = r2; 221 r2 = tmp; 222 } 223 if (c1 > c2) { 224 tmp = c1; 225 c1 = c2; 226 c2 = tmp; 227 } 228 CLAMP(c1, 0, t->size.w - 1); 229 CLAMP(c2, 0, t->size.w - 1); 230 CLAMP(r1, 0, t->size.h - 1); 231 CLAMP(r2, 0, t->size.h - 1); 232 233 uv2 top_left = {.x = c1, .y = r1}; 234 uv2 bottom_right = {.x = c2, .y = r2}; 235 if (selection_intersects_region(&t->selection, top_left, bottom_right)) 236 selection_clear(&t->selection); 237 238 Row *rows = t->views[t->view_idx].fb.rows; 239 CellStyle cursor_style = t->cursor.style; 240 u32 fg = SHADER_PACK_FG(cursor_style.fg.rgba, cursor_style.attr); 241 u32 bg = SHADER_PACK_BG(cursor_style.bg.rgba, cursor_style.attr); 242 for (u32 r = top_left.y; r <= bottom_right.y; r++) { 243 for (u32 c = top_left.x; c <= bottom_right.x; c++) { 244 rows[r][c].fg = fg; 245 rows[r][c].bg = bg; 246 rows[r][c].cp = ' '; 247 } 248 } 249 250 END_TIMED_BLOCK(); 251 } 252 253 static void 254 fb_scroll_down(Term *t, u32 top, u32 n) 255 { 256 BEGIN_TIMED_BLOCK(); 257 if (!BETWEEN(top, t->top, t->bot)) 258 goto end; 259 260 TermView *tv = t->views + t->view_idx; 261 CLAMP(n, 0, t->bot - top + 1); 262 263 fb_clear_region(t, t->bot - n + 1, t->bot, 0, t->size.w); 264 for (u32 i = t->bot; i >= top + n; i--) { 265 Row tmp = tv->fb.rows[i]; 266 tv->fb.rows[i] = tv->fb.rows[i - n]; 267 tv->fb.rows[i - n] = tmp; 268 } 269 selection_scroll(t, top, n); 270 end: 271 END_TIMED_BLOCK(); 272 } 273 274 static void 275 fb_scroll_up(Term *t, u32 top, u32 n) 276 { 277 BEGIN_TIMED_BLOCK(); 278 if (!BETWEEN(top, t->top, t->bot)) 279 goto end; 280 281 TermView *tv = t->views + t->view_idx; 282 CLAMP(n, 0, t->bot - top + 1); 283 284 #if 0 285 size cell_count = (t->bot - n + 1) * t->size.w; 286 ASSERT(cell_count < tv->fb.cells_count); 287 mem_move(tv->fb.cells + t->size.w * n, tv->fb.cells, cell_count * sizeof(Cell)); 288 fb_clear_region(t, t->bot - n + 1, t->bot, 0, t->size.w); 289 #else 290 fb_clear_region(t, top, top + n - 1, 0, t->size.w); 291 for (u32 i = top; i <= t->bot - n; i++) { 292 Row tmp = tv->fb.rows[i]; 293 tv->fb.rows[i] = tv->fb.rows[i + n]; 294 tv->fb.rows[i + n] = tmp; 295 } 296 #endif 297 selection_scroll(t, top, -n); 298 end: 299 END_TIMED_BLOCK(); 300 } 301 302 static void 303 swap_screen(Term *t) 304 { 305 t->mode.term ^= TM_ALTSCREEN; 306 t->view_idx = !!(t->mode.term & TM_ALTSCREEN); 307 } 308 309 static void 310 cursor_reset(Term *t) 311 { 312 //(Colour){.rgba = 0x1e9e33ff}; 313 t->cursor.style.fg = g_colours.data[g_colours.fgidx]; 314 t->cursor.style.bg = g_colours.data[g_colours.bgidx]; 315 t->cursor.style.attr = ATTR_NULL; 316 } 317 318 static void 319 cursor_move_to(Term *t, i32 row, i32 col) 320 { 321 i32 minr = 0, maxr = t->size.h - 1; 322 if (t->cursor.state & CURSOR_ORIGIN) { 323 minr = t->top; 324 maxr = t->bot; 325 } 326 t->cursor.pos.y = CLAMP(row, minr, maxr); 327 t->cursor.pos.x = CLAMP(col, 0, t->size.w - 1); 328 t->cursor.state &= ~CURSOR_WRAP_NEXT; 329 } 330 331 static void 332 cursor_move_abs_to(Term *t, i32 row, i32 col) 333 { 334 if (t->cursor.state & CURSOR_ORIGIN) 335 row += t->top; 336 cursor_move_to(t, row, col); 337 } 338 339 static void 340 cursor_alt(Term *t, b32 save) 341 { 342 i32 mode = t->view_idx; 343 if (save) { 344 t->saved_cursors[mode] = t->cursor; 345 } else { 346 t->cursor = t->saved_cursors[mode]; 347 cursor_move_to(t, t->cursor.pos.y, t->cursor.pos.x); 348 } 349 } 350 351 /* NOTE: advance the cursor by <n> cells; handles reverse movement */ 352 static void 353 cursor_step_column(Term *t, i32 n) 354 { 355 i32 col = t->cursor.pos.x + n; 356 i32 row = t->cursor.pos.y; 357 if (col >= t->size.w) { 358 row++; 359 col = 0; 360 if (row >= t->size.h) 361 fb_scroll_up(t, t->top, 1); 362 } 363 cursor_move_to(t, row, col); 364 } 365 366 /* NOTE: steps the cursor without causing a scroll */ 367 static void 368 cursor_step_raw(Term *t, i32 step, i32 rows, i32 cols) 369 { 370 rows *= step; 371 cols *= step; 372 cursor_move_to(t, t->cursor.pos.y + rows, t->cursor.pos.x + cols); 373 } 374 375 static i32 376 next_tab_position(Term *t, b32 backwards) 377 { 378 static_assert(ARRAY_COUNT(t->tabs) == 8 * sizeof(*t->tabs), 379 "Term.tabs must be same length as the bitwidth of it's type"); 380 u32 col = t->cursor.pos.x; 381 u32 idx = col / ARRAY_COUNT(t->tabs); 382 u32 bit = col % ARRAY_COUNT(t->tabs); 383 u32 mask = safe_left_shift(1, bit - backwards) - 1; 384 385 u32 result = 32 * idx; 386 if (backwards) { 387 u32 zeroes = _lzcnt_u32(t->tabs[idx--] & mask); 388 while (idx < ARRAY_COUNT(t->tabs) && zeroes == 32) 389 zeroes = _lzcnt_u32(t->tabs[idx--]); 390 result = 32 * (idx + 1) + 32 - zeroes; 391 } else { 392 u32 zeroes = _tzcnt_u32(t->tabs[idx++] & ~mask); 393 while (idx < ARRAY_COUNT(t->tabs) && zeroes == 32) 394 zeroes = _tzcnt_u32(t->tabs[idx++]); 395 result = 32 * (idx - 1) + zeroes + 1; 396 } 397 ASSERT(result < t->size.w); 398 399 return result; 400 } 401 402 static void 403 term_tab_col(Term *t, u32 col, b32 set) 404 { 405 ASSERT(col < t->size.w); 406 u32 idx = (col - 1) / ARRAY_COUNT(t->tabs); 407 u32 bit = (col - 1) % ARRAY_COUNT(t->tabs); 408 u32 mask = 1u; 409 if (bit) mask = safe_left_shift(1, bit); 410 if (set) t->tabs[idx] |= mask; 411 else t->tabs[idx] &= ~mask; 412 } 413 414 static void 415 term_reset(Term *t) 416 { 417 i32 mode = t->mode.term & TM_ALTSCREEN; 418 t->cursor.state = CURSOR_NORMAL; 419 for (u32 i = 0; i < ARRAY_COUNT(t->saved_cursors); i++) { 420 cursor_reset(t); 421 t->cursor.charset_index = 0; 422 for (u32 i = 0; i < ARRAY_COUNT(t->cursor.charsets); i++) 423 t->cursor.charsets[i] = CS_USA; 424 cursor_move_to(t, 0, 0); 425 cursor_alt(t, 1); 426 swap_screen(t); 427 fb_clear_region(t, 0, t->size.h, 0, t->size.w); 428 } 429 for (u32 i = 0; i < ARRAY_COUNT(t->tabs); i++) 430 t->tabs[i] = 0; 431 for (u32 i = g_tabstop; i < t->size.w; i += g_tabstop) 432 term_tab_col(t, i, 1); 433 434 t->top = 0; 435 t->bot = t->size.h - 1; 436 /* TODO: why is term_reset() being called when we are in the altscreen */ 437 t->mode.term = mode|TM_AUTO_WRAP|TM_UTF8; 438 } 439 440 static void 441 dump_csi(CSI *csi, Stream *err) 442 { 443 stream_push_s8(err, s8("raw: ESC[")); 444 for (size i = 0; i < csi->raw.len; i++) { 445 u8 c = csi->raw.data[i]; 446 if (ISPRINT(c)) { 447 stream_push_byte(err, csi->raw.data[i]); 448 } else if (c == '\n') { 449 stream_push_s8(err, s8("\\n")); 450 } else if (c == '\r') { 451 stream_push_s8(err, s8("\\r")); 452 } else { 453 stream_push_s8(err, s8("\\x")); 454 stream_push_hex_u64(err, c); 455 } 456 } 457 stream_push_s8(err, s8("\n\tparsed = { .priv = ")); 458 stream_push_u64(err, csi->priv); 459 stream_push_s8(err, s8(" .mode = ")); 460 if (ISPRINT(csi->mode)) { 461 stream_push_byte(err, csi->mode); 462 } else { 463 stream_push_s8(err, s8("\\x")); 464 stream_push_hex_u64(err, csi->mode); 465 } 466 stream_push_s8(err, s8(", .argc = ")); 467 stream_push_u64(err, csi->argc); 468 stream_push_s8(err, s8(", .argv = {")); 469 for (i32 i = 0; i < csi->argc; i++) { 470 stream_push_byte(err, ' '); 471 stream_push_i64(err, csi->argv[i]); 472 } 473 474 stream_push_s8(err, s8(" } }\n")); 475 os_write_err_msg(stream_to_s8(err)); 476 err->widx = 0; 477 } 478 479 /* ED/DECSED: Erase in Display */ 480 static void 481 erase_in_display(Term *t, CSI *csi) 482 { 483 iv2 cpos = t->cursor.pos; 484 switch (csi->argv[0]) { 485 case 0: /* Erase Below (default) */ 486 fb_clear_region(t, cpos.y, cpos.y, cpos.x, t->size.w); 487 if (cpos.y < t->size.h - 1) 488 fb_clear_region(t, cpos.y + 1, t->size.h, 0, t->size.w); 489 break; 490 case 1: /* Erase Above */ 491 if (cpos.y > 0) 492 fb_clear_region(t, 0, cpos.y - 1, 0, t->size.w); 493 fb_clear_region(t, cpos.y, cpos.y, 0, cpos.x); 494 break; 495 case 2: /* Erase All */ 496 fb_clear_region(t, 0, t->size.h, 0, t->size.w); 497 break; 498 case 3: /* Erase Saved Lines (xterm) */ 499 /* NOTE: ignored; we don't save lines in the way xterm does */ 500 break; 501 default: ASSERT(0); 502 } 503 } 504 505 /* EL/DECSEL: Erase in Line */ 506 static void 507 erase_in_line(Term *t, CSI *csi) 508 { 509 iv2 cpos = t->cursor.pos; 510 switch (csi->argv[0]) { 511 case 0: /* Erase to Right */ 512 fb_clear_region(t, cpos.y, cpos.y, cpos.x, t->size.w); 513 break; 514 case 1: /* Erase to Left */ 515 fb_clear_region(t, cpos.y, cpos.y, 0, cpos.x); 516 break; 517 case 2: /* Erase All */ 518 fb_clear_region(t, cpos.y, cpos.y, 0, t->size.w); 519 break; 520 default: ASSERT(0); 521 } 522 } 523 524 /* IL: Insert <count> blank lines */ 525 static void 526 insert_blank_lines(Term *t, i32 count) 527 { 528 fb_scroll_down(t, t->cursor.pos.y, count); 529 } 530 531 /* DL: Erase <count> lines */ 532 static void 533 erase_lines(Term *t, i32 count) 534 { 535 fb_scroll_up(t, t->cursor.pos.y, count); 536 } 537 538 /* DCH: Delete Characters (NOTE: DCH is technically different but we are ignoring that) */ 539 /* ECH: Erase Characters */ 540 static void 541 erase_characters(Term *t, i32 count) 542 { 543 iv2 cpos = t->cursor.pos; 544 fb_clear_region(t, cpos.y, cpos.y, cpos.x, cpos.x + count - 1); 545 } 546 547 /* TBC: Tabulation Clear */ 548 static void 549 clear_term_tab(Term *t, i32 arg) 550 { 551 /* TODO: case 1, 2? */ 552 switch(arg) { 553 case 0: 554 term_tab_col(t, t->cursor.pos.x, 0); 555 break; 556 case 3: 557 for (u32 i = 0; i < ARRAY_COUNT(t->tabs); i++) 558 t->tabs[i] = 0; 559 break; 560 default: 561 stream_push_s8(&t->error_stream, s8("clear_term_tab: unhandled arg: ")); 562 stream_push_i64(&t->error_stream, arg); 563 stream_push_byte(&t->error_stream, '\n'); 564 os_write_err_msg(stream_to_s8(&t->error_stream)); 565 t->error_stream.widx = 0; 566 } 567 } 568 569 /* SM/DECSET: Set Mode & RM/DECRST Reset Mode */ 570 static void 571 set_mode(Term *t, CSI *csi, b32 set, ModeState src, ModeState *dest) 572 { 573 BEGIN_TIMED_BLOCK(); 574 i32 alt = t->view_idx; 575 576 /* TODO: this whole thing should be a lookup table */ 577 #define PRIV(a) ((1 << 30) | (a)) 578 for (i32 i = 0; i < csi->argc; i++) { 579 i32 arg = (csi->argv[i]) | ((csi->priv & 1) << 30); 580 switch (arg) { 581 case 4: /* IRM: Insert/Replace Mode */ 582 dest->term &= ~TM_REPLACE; 583 if (set) dest->term |= (src.term & TM_REPLACE); 584 break; 585 case 20: /* LNM: Linefeed Assumes Carriage Return */ 586 dest->term &= ~TM_CRLF; 587 if (set) dest->term |= (src.term & TM_CRLF); 588 break; 589 case PRIV(1): /* DECCKM: use application cursor keys */ 590 dest->win &= ~WM_APPCURSOR; 591 if (set) dest->win |= (src.win & WM_APPCURSOR); 592 break; 593 case PRIV(5): /* DECSCNM: reverse/normal video mode */ 594 dest->win &= ~WM_REVERSE; 595 if (set) dest->win |= (src.win & WM_REVERSE); 596 break; 597 case PRIV(6): /* DECOM: Cursor Origin Mode */ 598 if (set) t->cursor.state |= CURSOR_ORIGIN; 599 else t->cursor.state &= ~CURSOR_ORIGIN; 600 cursor_move_abs_to(t, 0, 0); 601 break; 602 case PRIV(7): /* DECAWM: Auto-Wrap Mode */ 603 dest->term &= ~TM_AUTO_WRAP; 604 if (set) dest->term |= (src.term & TM_AUTO_WRAP); 605 break; 606 case PRIV(1000): /* xterm: report mouse button presses */ 607 dest->win &= ~WM_MOUSE_MASK; 608 if (set) dest->win |= (src.win & WM_MOUSE_BTN); 609 break; 610 case PRIV(1002): /* xterm: cell motion tracking */ 611 dest->win &= ~WM_MOUSE_MASK; 612 if (set) dest->win |= (src.win & WM_MOUSE_TRK); 613 break; 614 case PRIV(1006): /* xterm: SGR mouse mode */ 615 dest->win &= ~WM_MOUSE_SGR; 616 if (set) dest->win |= (src.win & WM_MOUSE_SGR); 617 break; 618 case PRIV(3): /* DECCOLM: 132/80 Column Mode */ 619 case PRIV(4): /* DECSCLM: Fast/Slow Scroll */ 620 case PRIV(8): /* DECARM: Auto-Repeat Keys */ 621 case PRIV(12): /* AT&T 610: Start blinking cursor */ 622 case PRIV(40): /* xterm: (dis)allow 132/80 Column Mode */ 623 case PRIV(45): /* XTREVWRAP: xterm reverse wrap around */ 624 case PRIV(1001): /* xterm: (broken) mouse highlight tracking; requires 625 * the requesting program to be competely functional or will 626 * hang the terminal by design */ 627 case PRIV(1015): /* urxvt: (broken) mouse mode */ 628 /* IGNORED */ 629 break; 630 case PRIV(25): /* DECTCEM: Show/Hide Cursor */ 631 dest->win &= ~WM_HIDECURSOR; 632 if (!set) dest->win |= (src.win & WM_HIDECURSOR); 633 break; 634 case PRIV(1034): /* xterm: enable 8-bit input mode */ 635 dest->win &= ~WM_8BIT; 636 if (set) dest->win |= (src.win & WM_8BIT); 637 break; 638 case PRIV(1049): /* xterm: swap cursor then swap screen */ 639 cursor_alt(t, set); 640 case PRIV(47): /* xterm: swap screen buffer */ 641 case PRIV(1047): /* xterm: swap screen buffer */ 642 if (alt) fb_clear_region(t, 0, t->size.h, 0, t->size.w); 643 if (set ^ alt) swap_screen(t); 644 if (csi->argv[i] != 1049) break; 645 /* FALLTHROUGH */ 646 case PRIV(1048): /* xterm: swap cursor */ 647 cursor_alt(t, set); 648 break; 649 case PRIV(2004): /* xterm: bracketed paste mode */ 650 dest->win &= ~WM_BRACKPASTE; 651 if (set) dest->win |= (src.win & WM_BRACKPASTE); 652 break; 653 case PRIV(2026): /* synchronized render (eg. wezterm) */ 654 /* IGNORED: we aren't writing some slow garbage so we won't let some 655 * broken program try and stall us because they think we can't operate 656 * fast enough */ 657 break; 658 default: 659 os_write_err_msg(s8("set_mode: unhandled mode: ")); 660 dump_csi(csi, &t->error_stream); 661 } 662 } 663 #undef PRIV 664 END_TIMED_BLOCK(); 665 } 666 667 /* NOTE: adapted from the perl script 256colres.pl in xterm src */ 668 static Colour 669 indexed_colour(i32 index) 670 { 671 Colour result; 672 if (index < 232) { 673 /* NOTE: 16-231 are colours off a 6x6x6 RGB cube */ 674 index -= 16; 675 result.r = 40 * ((index / 36)); 676 result.g = 40 * ((index % 36) / 6); 677 result.b = 40 * ((index % 6)); 678 result.a = 0xFF; 679 if (result.r) result.r += 55; 680 if (result.g) result.g += 55; 681 if (result.b) result.b += 55; 682 } else { 683 /* NOTE: 232-255 are greyscale ramp */ 684 u32 k = (10 * (index - 232) + 8) & 0xFF; 685 result.r = result.g = result.b = k; 686 result.a = 0xFF; 687 } 688 return result; 689 } 690 691 static struct conversion_result 692 direct_colour(i32 *argv, i32 argc, i32 *idx, Stream *err) 693 { 694 struct conversion_result result = {.status = CR_FAILURE}; 695 switch (argv[*idx + 1]) { 696 case 2: /* NOTE: defined RGB colour */ 697 if (*idx + 4 >= argc) { 698 stream_push_s8(err, s8("direct_colour: wrong parameter count: ")); 699 stream_push_u64(err, argc); 700 stream_push_byte(err, '\n'); 701 break; 702 } 703 u32 r = (u32)argv[*idx + 2]; 704 u32 g = (u32)argv[*idx + 3]; 705 u32 b = (u32)argv[*idx + 4]; 706 *idx += 4; 707 if (r > 0xFF || g > 0xFF || b > 0xFF) { 708 stream_push_s8(err, s8("direct_colour: bad rgb colour: (")); 709 stream_push_u64(err, r); 710 stream_push_s8(err, s8(", ")); 711 stream_push_u64(err, g); 712 stream_push_s8(err, s8(", ")); 713 stream_push_u64(err, b); 714 stream_push_s8(err, s8(")\n")); 715 break; 716 } 717 result.colour = (Colour){.r = r, .g = g, .b = b, .a = 0xFF}; 718 result.status = CR_SUCCESS; 719 break; 720 case 5: /* NOTE: indexed colour */ 721 if (*idx + 2 >= argc) { 722 stream_push_s8(err, s8("direct_colour: wrong parameter count: ")); 723 stream_push_u64(err, argc); 724 stream_push_byte(err, '\n'); 725 break; 726 } 727 *idx += 2; 728 if (!BETWEEN(argv[*idx], 0, 255)) { 729 stream_push_s8(err, s8("direct_colour: index parameter out of range: ")); 730 stream_push_i64(err, argv[*idx]); 731 stream_push_byte(err, '\n'); 732 break; 733 } 734 if (BETWEEN(argv[*idx], 0, 16)) 735 result.colour = g_colours.data[argv[*idx]]; 736 else 737 result.colour = indexed_colour(argv[*idx]); 738 result.status = CR_SUCCESS; 739 break; 740 default: 741 stream_push_s8(err, s8("direct_colour: unknown argument: ")); 742 stream_push_i64(err, argv[*idx + 1]); 743 stream_push_byte(err, '\n'); 744 } 745 746 return result; 747 } 748 749 /* SGR: Select Graphic Rendition */ 750 static void 751 set_colours(Term *t, CSI *csi) 752 { 753 BEGIN_TIMED_BLOCK(); 754 CellStyle *cs = &t->cursor.style; 755 struct conversion_result dcr; 756 for (i32 i = 0; i < csi->argc; i++) { 757 switch (csi->argv[i]) { 758 case 0: cursor_reset(t); break; 759 case 1: cs->attr |= ATTR_BOLD; break; 760 case 2: cs->attr |= ATTR_FAINT; break; 761 case 3: cs->attr |= ATTR_ITALIC; break; 762 case 4: cs->attr |= ATTR_UNDERLINED; break; 763 case 5: cs->attr |= ATTR_BLINK; break; 764 case 7: cs->attr |= ATTR_INVERSE; break; 765 case 8: cs->attr |= ATTR_INVISIBLE; break; 766 case 9: cs->attr |= ATTR_STRUCK; break; 767 case 22: cs->attr &= ~(ATTR_BOLD|ATTR_FAINT); break; 768 case 23: cs->attr &= ~ATTR_ITALIC; break; 769 case 24: cs->attr &= ~ATTR_UNDERLINED; break; 770 case 25: cs->attr &= ~ATTR_BLINK; break; 771 case 27: cs->attr &= ~ATTR_INVERSE; break; 772 case 28: cs->attr &= ~ATTR_INVISIBLE; break; 773 case 29: cs->attr &= ~ATTR_STRUCK; break; 774 case 38: 775 dcr = direct_colour(csi->argv, csi->argc, &i, &t->error_stream); 776 if (dcr.status == CR_SUCCESS) { 777 cs->fg = dcr.colour; 778 } else { 779 stream_push_s8(&t->error_stream, s8("set_colours: ")); 780 dump_csi(csi, &t->error_stream); 781 } 782 break; 783 784 case 39: cs->fg = g_colours.data[g_colours.fgidx]; break; 785 786 case 48: 787 dcr = direct_colour(csi->argv, csi->argc, &i, &t->error_stream); 788 if (dcr.status == CR_SUCCESS) { 789 cs->bg = dcr.colour; 790 } else { 791 stream_push_s8(&t->error_stream, s8("set_colours: ")); 792 dump_csi(csi, &t->error_stream); 793 } 794 break; 795 796 case 49: cs->bg = g_colours.data[g_colours.bgidx]; break; 797 798 default: 799 if (BETWEEN(csi->argv[i], 30, 37)) { 800 cs->fg = g_colours.data[csi->argv[i] - 30]; 801 } else if (BETWEEN(csi->argv[i], 40, 47)) { 802 cs->bg = g_colours.data[csi->argv[i] - 40]; 803 } else if (BETWEEN(csi->argv[i], 90, 97)) { 804 cs->fg = g_colours.data[csi->argv[i] - 82]; 805 } else if (BETWEEN(csi->argv[i], 100, 107)) { 806 cs->bg = g_colours.data[csi->argv[i] - 92]; 807 } else { 808 stream_push_s8(&t->error_stream, s8("unhandled colour arg: ")); 809 stream_push_i64(&t->error_stream, csi->argv[i]); 810 stream_push_byte(&t->error_stream, '\n'); 811 dump_csi(csi, &t->error_stream); 812 } 813 } 814 } 815 END_TIMED_BLOCK(); 816 } 817 818 static void 819 set_scrolling_region(Term *t, i32 top, i32 bot) 820 { 821 CLAMP(top, 0, t->size.h - 1); 822 CLAMP(bot, 0, t->size.h - 1); 823 if (top > bot) { 824 i32 tmp = top; 825 top = bot; 826 bot = tmp; 827 } 828 t->top = top; 829 t->bot = bot; 830 } 831 832 static void 833 window_manipulation(Term *t, CSI *csi) 834 { 835 switch (csi->argv[0]) { 836 case 22: t->platform->get_window_title(&t->saved_title); break; 837 case 23: t->platform->set_window_title(&t->saved_title); break; 838 default: 839 stream_push_s8(&t->error_stream, s8("unhandled xtwinops: ")); 840 stream_push_i64(&t->error_stream, csi->argv[0]); 841 stream_push_byte(&t->error_stream, '\n'); 842 dump_csi(csi, &t->error_stream); 843 } 844 } 845 846 static void 847 push_newline(Term *t, b32 move_to_first_col) 848 { 849 i32 row = t->cursor.pos.y; 850 if (row == t->bot && t->scroll_offset == 0) 851 fb_scroll_up(t, t->top, 1); 852 else 853 row++; 854 cursor_move_to(t, row, move_to_first_col? 0 : t->cursor.pos.x); 855 } 856 857 static void 858 push_tab(Term *t, i32 n) 859 { 860 u32 end = ABS(n); 861 for (u32 i = 0; i < end; i++) 862 cursor_move_to(t, t->cursor.pos.y, next_tab_position(t, n < 0)); 863 } 864 865 static i32 866 parse_csi(s8 *r, CSI *csi) 867 { 868 BEGIN_TIMED_BLOCK(); 869 i32 result = 0; 870 871 if (peek(*r, 0) == '?') { 872 csi->priv = 1; 873 get_ascii(r); 874 } 875 876 while (r->len) { 877 u32 cp = get_ascii(r); 878 if (ISCONTROL(cp)) { 879 continue; 880 } else if (BETWEEN(cp, '0', '9')) { 881 csi->argv[csi->argc] *= 10; 882 csi->argv[csi->argc] += cp - '0'; 883 continue; 884 } 885 csi->argc++; 886 887 if (cp != ';' || csi->argc == ESC_ARG_SIZ) { 888 if (cp == ';') csi->mode = get_ascii(r); 889 else csi->mode = cp; 890 goto end; 891 } 892 } 893 /* NOTE: if we fell out of the loop then we ran out of characters */ 894 result = -1; 895 end: 896 END_TIMED_BLOCK(); 897 898 return result; 899 } 900 901 static void 902 handle_csi(Term *t, CSI *csi) 903 { 904 BEGIN_TIMED_BLOCK(); 905 s8 raw = csi->raw; 906 i32 ret = parse_csi(&raw, csi); 907 ASSERT(ret != -1); 908 909 #define ORONE(x) ((x)? (x) : 1) 910 911 iv2 p = t->cursor.pos; 912 913 switch (csi->mode) { 914 case 'A': cursor_step_raw(t, ORONE(csi->argv[0]), -1, 0); break; 915 case 'B': cursor_step_raw(t, ORONE(csi->argv[0]), 1, 0); break; 916 case 'C': cursor_step_raw(t, ORONE(csi->argv[0]), 0, 1); break; 917 case 'D': cursor_step_raw(t, ORONE(csi->argv[0]), 0, -1); break; 918 case 'E': cursor_move_to(t, p.y + ORONE(csi->argv[0]), 0); break; 919 case 'F': cursor_move_to(t, p.y - ORONE(csi->argv[0]), 0); break; 920 case 'G': cursor_move_to(t, p.y, csi->argv[0] - 1); break; 921 case 'H': cursor_move_abs_to(t, csi->argv[0] - 1, csi->argv[1] - 1); break; 922 case 'J': erase_in_display(t, csi); break; 923 case 'K': erase_in_line(t, csi); break; 924 case 'L': insert_blank_lines(t, ORONE(csi->argv[0])); break; 925 case 'M': erase_lines(t, ORONE(csi->argv[0])); break; 926 case 'P': erase_characters(t, ORONE(csi->argv[0])); break; 927 case 'X': erase_characters(t, ORONE(csi->argv[0])); break; 928 case 'S': fb_scroll_up(t, t->top, ORONE(csi->argv[0])); break; 929 case 'T': fb_scroll_down(t, t->top, ORONE(csi->argv[0])); break; 930 case 'Z': push_tab(t, -(ORONE(csi->argv[0]))); break; 931 case 'a': cursor_step_raw(t, ORONE(csi->argv[0]), 0, 1); break; 932 case 'd': cursor_move_abs_to(t, csi->argv[0] - 1, p.x); break; 933 case 'e': cursor_step_raw(t, ORONE(csi->argv[0]), 1, 0); break; 934 case 'f': cursor_move_abs_to(t, csi->argv[0] - 1, csi->argv[1] - 1); break; 935 case 'g': clear_term_tab(t, csi->argv[0]); break; 936 case 'h': set_mode(t, csi, 1, MODE_STATE_ALL_MASK, &t->mode); break; 937 case 'l': set_mode(t, csi, 0, MODE_STATE_ALL_MASK, &t->mode); break; 938 case 'm': set_colours(t, csi); break; 939 case 'r': 940 if (csi->priv) { 941 /* NOTE: XTRESTORE: restore the value of a private mode */ 942 set_mode(t, csi, 1, t->saved_mode, &t->mode); 943 } else { 944 set_scrolling_region(t, ORONE(csi->argv[0]) - 1, ORONE(csi->argv[1]) - 1); 945 cursor_move_abs_to(t, 0, 0); 946 } 947 break; 948 case 's': 949 if (csi->priv) { 950 /* NOTE: XTSAVE: save the value of a private mode */ 951 set_mode(t, csi, 1, t->mode, &t->saved_mode); 952 } else { 953 /* NOTE: SCOSC/ANSI.SYS: save the cursor */ 954 cursor_alt(t, 1); 955 } 956 break; 957 case 't': window_manipulation(t, csi); break; 958 case '!': 959 if (csi->raw.data[csi->raw.len - 1] == 'p') { 960 /* NOTE: DECSTR: soft terminal reset IGNORED */ 961 break; 962 } 963 goto unknown; 964 case '"': 965 if (csi->raw.data[csi->raw.len - 1] == 'p') { 966 /* TODO: we should look at the second parameter and 967 * use it modify C1 control character mode */ 968 /* NOTE: DECSCL: set conformance level IGNORED */ 969 break; 970 } 971 goto unknown; 972 default: 973 unknown: 974 stream_push_s8(&t->error_stream, s8("unknown csi: ")); 975 dump_csi(csi, &t->error_stream); 976 } 977 END_TIMED_BLOCK(); 978 } 979 980 static i32 981 parse_osc(s8 *raw, OSC *osc) 982 { 983 BEGIN_TIMED_BLOCK(); 984 985 i32 result = 0; 986 987 *osc = (OSC){0}; 988 osc->raw.data = raw->data; 989 990 /* NOTE: parse command then store the rest as a string */ 991 u32 cp; 992 while (raw->len) { 993 cp = get_ascii(raw); 994 osc->raw.len++; 995 if (!BETWEEN(cp, '0', '9')) 996 break; 997 osc->cmd *= 10; 998 osc->cmd += cp - '0'; 999 1000 /* TODO: Performance? */ 1001 /* NOTE: The maximum OSC in xterm is 119 so if this 1002 * exceeds that the whole sequence is malformed */ 1003 if (osc->cmd > 1000) 1004 break; 1005 } 1006 1007 if (cp != ';' || osc->cmd > 1000) 1008 os_fatal(s8("parse_osc: malformed\n")); 1009 1010 osc->arg.data = raw->data; 1011 while (raw->len) { 1012 cp = get_ascii(raw); 1013 osc->raw.len++; 1014 if (cp == '\a') 1015 goto end; 1016 if (cp == 0x1B && peek(*raw, 0) == '\\') { 1017 get_ascii(raw); 1018 osc->raw.len++; 1019 goto end; 1020 } 1021 osc->arg.len++; 1022 } 1023 /* NOTE: if we fell out of the loop then we ran out of characters */ 1024 result = -1; 1025 end: 1026 END_TIMED_BLOCK(); 1027 1028 return result; 1029 } 1030 1031 static void 1032 reset_csi(CSI *csi, s8 *raw) 1033 { 1034 *csi = (CSI){0}; 1035 csi->raw.data = raw->data; 1036 } 1037 1038 static void 1039 dump_osc(OSC *osc, Stream *err) 1040 { 1041 stream_push_s8(err, s8("ESC]")); 1042 for (size i = 0; i < osc->raw.len; i++) { 1043 u8 cp = osc->raw.data[i]; 1044 if (ISPRINT(cp)) { 1045 stream_push_byte(err, cp); 1046 } else if (cp == '\n') { 1047 stream_push_s8(err, s8("\\n")); 1048 } else if (cp == '\r') { 1049 stream_push_s8(err, s8("\\r")); 1050 } else if (cp == '\a') { 1051 stream_push_s8(err, s8("\\a")); 1052 } else { 1053 stream_push_s8(err, s8("\\x")); 1054 stream_push_hex_u64(err, cp); 1055 } 1056 } 1057 stream_push_s8(err, s8("\n\t.cmd = ")); 1058 stream_push_u64(err, osc->cmd); 1059 stream_push_s8(err, s8(", .arg = {.len = ")); 1060 stream_push_i64(err, osc->arg.len); 1061 stream_push_s8(err, s8("}\n")); 1062 os_write_err_msg(stream_to_s8(err)); 1063 err->widx = 0; 1064 } 1065 1066 static void 1067 handle_osc(Term *t, s8 *raw, Arena a) 1068 { 1069 BEGIN_TIMED_BLOCK(); 1070 OSC osc; 1071 i32 ret = parse_osc(raw, &osc); 1072 ASSERT(ret != -1); 1073 1074 Stream buffer = arena_stream(a); 1075 switch (osc.cmd) { 1076 case 0: stream_push_s8(&buffer, osc.arg); t->platform->set_window_title(&buffer); break; 1077 case 1: break; /* IGNORED: set icon name */ 1078 case 2: stream_push_s8(&buffer, osc.arg); t->platform->set_window_title(&buffer); break; 1079 default: 1080 stream_push_s8(&t->error_stream, s8("unhandled osc cmd: ")); 1081 dump_osc(&osc, &t->error_stream); 1082 break; 1083 } 1084 END_TIMED_BLOCK(); 1085 } 1086 1087 static i32 1088 handle_escape(Term *t, s8 *raw, Arena a) 1089 { 1090 BEGIN_TIMED_BLOCK(); 1091 i32 result = 0; 1092 u32 cp = get_ascii(raw); 1093 switch (cp) { 1094 case '[': reset_csi(&t->csi, raw); t->escape |= EM_CSI; break; 1095 case ']': handle_osc(t, raw, a); break; 1096 case '%': /* utf-8 mode */ 1097 /* TODO: should this really be done here? */ 1098 if (!raw->len) { 1099 result = 1; 1100 } else { 1101 switch (get_ascii(raw)) { 1102 case 'G': t->mode.term |= TM_UTF8; break; 1103 case '@': t->mode.term &= ~TM_UTF8; break; 1104 } 1105 } 1106 break; 1107 case '(': /* GZD4 -- set primary charset G0 */ 1108 case ')': /* G1D4 -- set secondary charset G1 */ 1109 case '*': /* G2D4 -- set tertiary charset G2 */ 1110 case '+': { /* G3D4 -- set quaternary charset G3 */ 1111 i32 index = cp - '('; 1112 /* TODO: should this really be done here? */ 1113 if (!raw->len) { 1114 result = 1; 1115 } else { 1116 u32 cs = get_ascii(raw); 1117 switch (cs) { 1118 case '0': t->cursor.charsets[index] = CS_GRAPHIC0; break; 1119 case 'B': t->cursor.charsets[index] = CS_USA; break; 1120 default: 1121 stream_push_s8(&t->error_stream, s8("unhandled charset: ")); 1122 stream_push_byte(&t->error_stream, cs); 1123 stream_push_byte(&t->error_stream, '\n'); 1124 os_write_err_msg(stream_to_s8(&t->error_stream)); 1125 t->error_stream.widx = 0; 1126 break; 1127 } 1128 } 1129 } break; 1130 case '=': /* DECPAM -- application keypad */ 1131 case '>': /* DECPNM -- normal keypad mode */ 1132 /* TODO: MODE_APPKEYPAD */ 1133 break; 1134 case 'c': /* RIS -- Reset to Initial State */ 1135 term_reset(t); 1136 break; 1137 case 'D': /* IND -- Linefeed */ 1138 push_newline(t, 0); 1139 break; 1140 case 'E': /* NEL -- Next Line */ 1141 push_newline(t, 1); 1142 break; 1143 case 'H': /* HTS -- Horizontal Tab Stop */ 1144 term_tab_col(t, t->cursor.pos.x, 1); 1145 break; 1146 case 'M': /* RI -- Reverse Index */ 1147 if (t->cursor.pos.y == t->top) { 1148 fb_scroll_down(t, t->top, 1); 1149 } else { 1150 cursor_move_to(t, t->cursor.pos.y - 1, t->cursor.pos.x); 1151 } 1152 break; 1153 case '7': /* DECSC: Save Cursor */ 1154 cursor_alt(t, 1); 1155 break; 1156 case '8': /* DECRC: Restore Cursor */ 1157 cursor_alt(t, 0); 1158 break; 1159 default: 1160 stream_push_s8(&t->error_stream, s8("unknown escape sequence: ESC ")); 1161 stream_push_byte(&t->error_stream, cp); 1162 stream_push_s8(&t->error_stream, s8(" (0x")); 1163 stream_push_hex_u64(&t->error_stream, cp); 1164 stream_push_s8(&t->error_stream, s8(")\n")); 1165 os_write_err_msg(stream_to_s8(&t->error_stream)); 1166 t->error_stream.widx = 0; 1167 break; 1168 } 1169 END_TIMED_BLOCK(); 1170 return result; 1171 } 1172 1173 static i32 1174 push_control(Term *t, s8 *line, u32 cp, Arena a) 1175 { 1176 i32 result = 0; 1177 switch (cp) { 1178 case 0x1B: 1179 if (!line->len) result = 1; 1180 else result = handle_escape(t, line, a); 1181 break; 1182 case '\r': cursor_move_to(t, t->cursor.pos.y, 0); break; 1183 case '\n': push_newline(t, t->mode.term & TM_CRLF); break; 1184 case '\t': push_tab(t, 1); break; 1185 case '\a': /* TODO: ding ding? */ break; 1186 case '\b': 1187 cursor_move_to(t, t->cursor.pos.y, t->cursor.pos.x - 1); 1188 break; 1189 case 0x0E: /* SO (LS1: Locking Shift 1) */ 1190 case 0x0F: /* SI (LS0: Locking Shift 0) */ 1191 t->cursor.charset_index = 1 - (cp - 0x0E); 1192 break; 1193 default: 1194 stream_push_s8(&t->error_stream, s8("unknown control code: 0x")); 1195 stream_push_hex_u64(&t->error_stream, cp); 1196 stream_push_byte(&t->error_stream, '\n'); 1197 os_write_err_msg(stream_to_s8(&t->error_stream)); 1198 t->error_stream.widx = 0; 1199 break; 1200 } 1201 if (cp != 0x1B) { 1202 if (t->escape & EM_CSI) t->csi.raw.len++; 1203 } 1204 return result; 1205 } 1206 1207 static void 1208 push_normal_cp(Term *t, TermView *tv, u32 cp) 1209 { 1210 BEGIN_TIMED_BLOCK(); 1211 1212 if (t->mode.term & TM_AUTO_WRAP && t->cursor.state & CURSOR_WRAP_NEXT) 1213 push_newline(t, 1); 1214 1215 if (t->cursor.charsets[t->cursor.charset_index] == CS_GRAPHIC0 && 1216 BETWEEN(cp, 0x41, 0x7e) && graphic_0[cp - 0x41]) 1217 cp = graphic_0[cp - 0x41]; 1218 1219 u32 width = 1; 1220 if (cp > 0x7F) { 1221 width = wcwidth(cp); 1222 ASSERT(width != -1); 1223 } 1224 1225 /* NOTE: make this '>=' for fun in vis */ 1226 if (t->cursor.pos.x + width > t->size.w) { 1227 /* NOTE: make space for character if mode enabled else 1228 * clobber whatever was on the end of the line */ 1229 if (t->mode.term & TM_AUTO_WRAP) 1230 push_newline(t, 1); 1231 else 1232 cursor_move_to(t, t->cursor.pos.y, t->size.w - width); 1233 } 1234 1235 Cell *c = &tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x]; 1236 /* TODO: pack cell into ssbo */ 1237 c->cp = cp; 1238 c->fg = SHADER_PACK_FG(t->cursor.style.fg.rgba, t->cursor.style.attr); 1239 c->bg = SHADER_PACK_BG(t->cursor.style.bg.rgba, t->cursor.style.attr); 1240 1241 if (width > 1) { 1242 ASSERT(t->cursor.pos.x + (width - 1) < t->size.w); 1243 c[0].bg |= ATTR_WIDE; 1244 c[1].bg |= ATTR_WDUMMY; 1245 } 1246 1247 if (t->cursor.pos.x + width < t->size.w) 1248 cursor_step_column(t, width); 1249 else 1250 t->cursor.state |= CURSOR_WRAP_NEXT; 1251 1252 /* TODO: region detection */ 1253 if (is_selected(&t->selection, t->cursor.pos.x, t->cursor.pos.y)) 1254 selection_clear(&t->selection); 1255 1256 END_TIMED_BLOCK(); 1257 } 1258 1259 static void 1260 push_line(Term *t, Line *line, Arena a) 1261 { 1262 BEGIN_TIMED_BLOCK(); 1263 1264 TermView *tv = t->views + t->view_idx; 1265 s8 l = line_to_s8(line, &tv->log); 1266 t->cursor.style = line->cursor_state; 1267 1268 while (l.len) { 1269 u32 cp; 1270 if (line->has_unicode) cp = get_utf8(&l); 1271 else cp = get_ascii(&l); 1272 1273 ASSERT(cp != (u32)-1); 1274 if (ISCONTROL(cp)) { 1275 if (!(t->mode.term & TM_UTF8) || !ISCONTROLC1(cp)) 1276 push_control(t, &l, cp, a); 1277 continue; 1278 } else if (t->escape & EM_CSI) { 1279 t->csi.raw.len++; 1280 if (BETWEEN(cp, '@', '~')) { 1281 handle_csi(t, &t->csi); 1282 t->escape &= ~EM_CSI; 1283 } 1284 continue; 1285 } 1286 1287 push_normal_cp(t, tv, cp); 1288 } 1289 END_TIMED_BLOCK(); 1290 } 1291 1292 static size 1293 get_line_idx(LineBuf *lb, size off) 1294 { 1295 ASSERT(-off <= lb->filled); 1296 size result = lb->widx + off; 1297 if (result < 0) 1298 result += lb->filled; 1299 return result; 1300 } 1301 1302 static void 1303 blit_lines(Term *t, Arena a) 1304 { 1305 BEGIN_TIMED_BLOCK(); 1306 1307 ASSERT(t->gl.flags & NEEDS_REFILL); 1308 term_reset(t); 1309 1310 TermView *tv = t->views + t->view_idx; 1311 size line_count = t->size.h - 1; 1312 size off = t->scroll_offset; 1313 CLAMP(line_count, 0, tv->lines.filled); 1314 for (size idx = -line_count; idx <= 0; idx++) { 1315 size line_idx = get_line_idx(&tv->lines, idx - off); 1316 push_line(t, tv->lines.buf + line_idx, a); 1317 } 1318 1319 t->gl.flags &= ~NEEDS_REFILL; 1320 1321 END_TIMED_BLOCK(); 1322 } 1323 1324 static void 1325 handle_input(Term *t, Arena a, s8 raw) 1326 { 1327 BEGIN_TIMED_BLOCK(); 1328 1329 TermView *tv = t->views + t->view_idx; 1330 1331 /* TODO: SIMD look ahead */ 1332 while (raw.len) { 1333 size start_len = raw.len; 1334 u32 cp = peek(raw, 0); 1335 /* TODO: this could be a performance issue; may need seperate code path for 1336 * terminal when not in UTF8 mode */ 1337 if (cp > 0x7F && (t->mode.term & TM_UTF8)) { 1338 cp = get_utf8(&raw); 1339 tv->lines.buf[tv->lines.widx].has_unicode = 1; 1340 if (cp == (u32)-1) { 1341 /* NOTE: Need More Bytes! */ 1342 raw.len = start_len; 1343 goto end; 1344 } 1345 } else { 1346 cp = get_ascii(&raw); 1347 } 1348 1349 ASSERT(cp != (u32)-1); 1350 1351 if (ISCONTROL(cp)) { 1352 if (!(t->mode.term & TM_UTF8) || !ISCONTROLC1(cp)) { 1353 i32 old_curs_y = t->cursor.pos.y; 1354 if (push_control(t, &raw, cp, a)) { 1355 raw.len = start_len; 1356 goto end; 1357 } 1358 if (!t->escape && (cp == '\n' || t->cursor.pos.y != old_curs_y)) 1359 feed_line(&tv->lines, raw.data, t->cursor.style); 1360 } 1361 continue; 1362 } else if (t->escape & EM_CSI) { 1363 t->csi.raw.len++; 1364 if (BETWEEN(cp, '@', '~')) { 1365 i32 old_curs_y = t->cursor.pos.y; 1366 i32 mode = t->mode.term & TM_ALTSCREEN; 1367 handle_csi(t, &t->csi); 1368 t->escape &= ~EM_CSI; 1369 if ((t->mode.term & TM_ALTSCREEN) != mode) { 1370 u8 *old = raw.data - t->csi.raw.len - 2; 1371 ASSERT(*old == 0x1B); 1372 feed_line(&tv->lines, old, t->cursor.style); 1373 TermView *nv = t->views + t->view_idx; 1374 size nstart = nv->log.widx; 1375 mem_copy(raw.data, nv->log.buf + nstart, raw.len); 1376 commit_to_rb(tv, -raw.len); 1377 commit_to_rb(nv, raw.len); 1378 raw.data = nv->log.buf + nstart; 1379 init_line(nv->lines.buf + nv->lines.widx, raw.data, 1380 t->cursor.style); 1381 tv = nv; 1382 } else if (t->cursor.pos.y != old_curs_y) { 1383 feed_line(&tv->lines, raw.data, t->cursor.style); 1384 } 1385 } 1386 continue; 1387 } 1388 1389 push_normal_cp(t, tv, cp); 1390 1391 } 1392 end: 1393 tv->lines.buf[tv->lines.widx].end = raw.data; 1394 1395 /* TODO: this shouldn't be needed */ 1396 if (tv->lines.buf[tv->lines.widx].end < tv->lines.buf[tv->lines.widx].start) 1397 tv->lines.buf[tv->lines.widx].start -= tv->log.cap; 1398 1399 if (!t->escape && line_length(tv->lines.buf + tv->lines.widx) > SPLIT_LONG) 1400 feed_line(&tv->lines, raw.data, t->cursor.style); 1401 1402 t->unprocessed_bytes = raw.len; 1403 END_TIMED_BLOCK(); 1404 }