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