1 /* 2 * Copyright (C) 1984-2025 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information, see the README file. 8 */ 9 10 /* 11 * Routines to manipulate the "line buffer". 12 * The line buffer holds a line of output as it is being built 13 * in preparation for output to the screen. 14 */ 15 16 #include "less.h" 17 #include "charset.h" 18 #include "position.h" 19 20 #if MSDOS_COMPILER==WIN32C 21 #define WIN32_LEAN_AND_MEAN 22 #include <windows.h> 23 #endif 24 25 #define MAX_PFX_WIDTH (MAX_LINENUM_WIDTH + MAX_STATUSCOL_WIDTH + 1) 26 static struct { 27 char *buf; /* Buffer which holds the current output line */ 28 int *attr; /* Parallel to buf, to hold attributes */ 29 size_t print; /* Index in buf of first printable char */ 30 size_t end; /* Number of chars in buf */ 31 size_t prev_end; /* Number of chars in buf for previous line */ 32 char pfx[MAX_PFX_WIDTH]; /* Holds status column and line number */ 33 int pfx_attr[MAX_PFX_WIDTH]; 34 size_t pfx_end; /* Number of chars in pfx */ 35 } linebuf; 36 37 /* 38 * Buffer of ansi sequences which have been shifted off the left edge 39 * of the screen. 40 */ 41 static struct xbuffer shifted_ansi; 42 43 /* 44 * Ring buffer of last ansi sequences sent. 45 * While sending a line, these will be resent at the end 46 * of any highlighted string, to restore text modes. 47 * {{ Not ideal, since we don't really know how many to resend. }} 48 */ 49 #define NUM_LAST_ANSIS 3 50 static struct xbuffer last_ansi; 51 static struct xbuffer last_ansis[NUM_LAST_ANSIS]; 52 static int curr_last_ansi; 53 54 static size_t size_linebuf = 0; /* Size of line buffer (and attr buffer) */ 55 static struct ansi_state *line_ansi = NULL; 56 static lbool ansi_in_line; 57 static int ff_starts_line; 58 static lbool hlink_in_line; 59 static int line_mark_attr; 60 static int cshift; /* Current left-shift of output line buffer */ 61 public int hshift; /* Desired left-shift of output line buffer */ 62 public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */ 63 public int ntabstops = 1; /* Number of tabstops */ 64 public int tabdefault = 8; /* Default repeated tabstops */ 65 public POSITION highest_hilite; /* Pos of last hilite in file found so far */ 66 static POSITION line_pos; 67 static POSITION line_contig_pos = NULL_POSITION; /* One after last byte processed */ 68 69 static int end_column; /* Printable length, accounting for backspaces, etc. */ 70 static int right_curr; 71 static int right_column; 72 static int overstrike; /* Next char should overstrike previous char */ 73 static int last_overstrike = AT_NORMAL; 74 static lbool is_null_line; /* There is no current line */ 75 static LWCHAR pendc; 76 static POSITION pendpos; 77 static constant char *end_ansi_chars; 78 static constant char *mid_ansi_chars; 79 static constant char *osc_ansi_chars; 80 static int osc_ansi_allow_count; 81 static long *osc_ansi_allow; 82 static lbool in_hilite; 83 static lbool clear_after_line; 84 85 static int attr_swidth(int a); 86 static int attr_ewidth(int a); 87 static int do_append(LWCHAR ch, constant char *rep, POSITION pos); 88 89 extern int sigs; 90 extern int bs_mode; 91 extern int proc_backspace; 92 extern int proc_tab; 93 extern int proc_return; 94 extern int linenums; 95 extern int ctldisp; 96 extern int twiddle; 97 extern int status_col; 98 extern int status_col_width; 99 extern int linenum_width; 100 extern int auto_wrap, ignaw; 101 extern int bo_s_width, bo_e_width; 102 extern int ul_s_width, ul_e_width; 103 extern int bl_s_width, bl_e_width; 104 extern int so_s_width, so_e_width; 105 extern int sc_width, sc_height; 106 extern int utf_mode; 107 extern POSITION start_attnpos; 108 extern POSITION end_attnpos; 109 extern LWCHAR rscroll_char; 110 extern int rscroll_attr; 111 extern int use_color; 112 extern int status_line; 113 114 static char mbc_buf[MAX_UTF_CHAR_LEN]; 115 static int mbc_buf_len = 0; 116 static int mbc_buf_index = 0; 117 static POSITION mbc_pos; 118 static size_t saved_line_end; 119 static int saved_end_column; 120 121 /* Configurable color map */ 122 struct color_map { int attr; char color[12]; }; 123 static struct color_map color_map[] = { 124 { AT_UNDERLINE, "" }, 125 { AT_BOLD, "" }, 126 { AT_BLINK, "" }, 127 { AT_STANDOUT, "" }, 128 { AT_COLOR_ATTN, "Wm" }, 129 { AT_COLOR_BIN, "kR" }, 130 { AT_COLOR_CTRL, "kR" }, 131 { AT_COLOR_ERROR, "kY" }, 132 { AT_COLOR_LINENUM, "c" }, 133 { AT_COLOR_MARK, "Wb" }, 134 { AT_COLOR_PROMPT, "kC" }, 135 { AT_COLOR_RSCROLL, "kc" }, 136 { AT_COLOR_HEADER, "" }, 137 { AT_COLOR_SEARCH, "kG" }, 138 { AT_COLOR_SUBSEARCH(1), "ky" }, 139 { AT_COLOR_SUBSEARCH(2), "wb" }, 140 { AT_COLOR_SUBSEARCH(3), "YM" }, 141 { AT_COLOR_SUBSEARCH(4), "Yr" }, 142 { AT_COLOR_SUBSEARCH(5), "Wc" }, 143 }; 144 145 /* State while processing an ANSI escape sequence */ 146 struct ansi_state { 147 osc8_state ostate; /* State while processing OSC8 sequence */ 148 unsigned int otype; /* OSC type number */ 149 unsigned int escs_in_seq; 150 }; 151 152 /* 153 * Initialize from environment variables. 154 */ 155 public void init_line(void) 156 { 157 int ax; 158 constant char *s; 159 160 end_ansi_chars = lgetenv("LESSANSIENDCHARS"); 161 if (isnullenv(end_ansi_chars)) 162 end_ansi_chars = "m"; 163 164 mid_ansi_chars = lgetenv("LESSANSIMIDCHARS"); 165 if (isnullenv(mid_ansi_chars)) 166 mid_ansi_chars = "0123456789:;[?!\"'#%()*+ "; 167 168 osc_ansi_chars = lgetenv("LESSANSIOSCCHARS"); 169 if (isnullenv(osc_ansi_chars)) 170 osc_ansi_chars = ""; 171 172 osc_ansi_allow_count = 0; 173 s = lgetenv("LESSANSIOSCALLOW"); 174 if (!isnullenv(s)) 175 { 176 struct xbuffer xbuf; 177 xbuf_init(&xbuf); 178 for (;;) 179 { 180 long num; 181 s = skipspc(s); 182 if (*s == '\0') 183 break; 184 num = lstrtoulc(s, &s, 10); 185 s = skipspc(s); 186 if (*s == ',') 187 ++s; 188 xbuf_add_data(&xbuf, (constant void *) &num, sizeof(num)); 189 ++osc_ansi_allow_count; 190 } 191 osc_ansi_allow = (long *) xbuf.data; 192 } 193 194 linebuf.buf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); 195 linebuf.attr = (int *) ecalloc(LINEBUF_SIZE, sizeof(int)); 196 size_linebuf = LINEBUF_SIZE; 197 xbuf_init(&shifted_ansi); 198 xbuf_init(&last_ansi); 199 for (ax = 0; ax < NUM_LAST_ANSIS; ax++) 200 xbuf_init(&last_ansis[ax]); 201 curr_last_ansi = 0; 202 } 203 204 /* 205 * Expand the line buffer. 206 */ 207 static int expand_linebuf(void) 208 { 209 /* Double the size of the line buffer. */ 210 size_t new_size = size_linebuf * 2; 211 char *new_buf = (char *) calloc(new_size, sizeof(char)); 212 int *new_attr = (int *) calloc(new_size, sizeof(int)); 213 if (new_buf == NULL || new_attr == NULL) 214 { 215 if (new_attr != NULL) 216 free(new_attr); 217 if (new_buf != NULL) 218 free(new_buf); 219 return 1; 220 } 221 /* 222 * We just calloc'd the buffers; copy the old contents. 223 */ 224 memcpy(new_buf, linebuf.buf, size_linebuf * sizeof(char)); 225 memcpy(new_attr, linebuf.attr, size_linebuf * sizeof(int)); 226 free(linebuf.attr); 227 free(linebuf.buf); 228 linebuf.buf = new_buf; 229 linebuf.attr = new_attr; 230 size_linebuf = new_size; 231 return 0; 232 } 233 234 /* 235 * Is a character ASCII? 236 */ 237 public lbool is_ascii_char(LWCHAR ch) 238 { 239 return (ch <= 0x7F); 240 } 241 242 /* 243 */ 244 static void inc_end_column(int w) 245 { 246 if (end_column > right_column && w > 0) 247 { 248 right_column = end_column; 249 right_curr = (int) linebuf.end; 250 } 251 end_column += w; 252 } 253 254 public POSITION line_position(void) 255 { 256 return line_pos; 257 } 258 259 /* 260 * Is this byte the next one after the previous byte processed? 261 */ 262 public lbool is_line_contig_pos(POSITION pos) 263 { 264 return pos == line_contig_pos; 265 } 266 267 /* 268 * Set the position of the next byte to be processed. 269 */ 270 public void set_line_contig_pos(POSITION pos) 271 { 272 line_contig_pos = pos; 273 } 274 275 /* 276 * Copy any ANSI sequences from line buffer to shifted_ansi. 277 */ 278 static void pshift(size_t end) 279 { 280 size_t i; 281 for (i = linebuf.print; i < end; i++) 282 if (linebuf.attr[i] == AT_ANSI) 283 xbuf_add_char(&shifted_ansi, linebuf.buf[i]); 284 } 285 286 /* 287 * Rewind the line buffer. 288 */ 289 public void prewind(lbool contig) 290 { 291 int ax; 292 293 xbuf_reset(&shifted_ansi); 294 if (contig && linebuf.prev_end != 0) 295 pshift(linebuf.prev_end); 296 linebuf.print = 6; /* big enough for longest UTF-8 sequence */ 297 linebuf.pfx_end = 0; 298 for (linebuf.end = 0; linebuf.end < linebuf.print; linebuf.end++) 299 { 300 linebuf.buf[linebuf.end] = '\0'; 301 linebuf.attr[linebuf.end] = 0; 302 } 303 304 end_column = 0; 305 right_curr = 0; 306 right_column = 0; 307 cshift = 0; 308 overstrike = 0; 309 last_overstrike = AT_NORMAL; 310 mbc_buf_len = 0; 311 is_null_line = FALSE; 312 pendc = '\0'; 313 in_hilite = FALSE; 314 ansi_in_line = FALSE; 315 ff_starts_line = -1; 316 hlink_in_line = FALSE; 317 clear_after_line = FALSE; 318 line_mark_attr = 0; 319 line_pos = NULL_POSITION; 320 xbuf_reset(&last_ansi); 321 for (ax = 0; ax < NUM_LAST_ANSIS; ax++) 322 xbuf_reset(&last_ansis[ax]); 323 curr_last_ansi = 0; 324 } 325 326 /* 327 * Set a character in the line buffer. 328 */ 329 static void set_linebuf(size_t n, char ch, int attr) 330 { 331 if (n >= size_linebuf) 332 { 333 /* 334 * Won't fit in line buffer. 335 * Try to expand it. 336 */ 337 if (expand_linebuf()) 338 return; 339 } 340 linebuf.buf[n] = ch; 341 linebuf.attr[n] = attr; 342 } 343 344 /* 345 * Append a character to the line buffer. 346 */ 347 static void add_linebuf(char ch, int attr, int w) 348 { 349 set_linebuf(linebuf.end++, ch, attr); 350 inc_end_column(w); 351 } 352 353 /* 354 * Append a string to the line buffer. 355 */ 356 static void addstr_linebuf(constant char *s, int attr, int cw) 357 { 358 for ( ; *s != '\0'; s++) 359 add_linebuf(*s, attr, cw); 360 } 361 362 /* 363 * Set a character in the line prefix buffer. 364 */ 365 static void set_pfx(size_t n, char ch, int attr) 366 { 367 linebuf.pfx[n] = ch; 368 linebuf.pfx_attr[n] = attr; 369 } 370 371 /* 372 * Append a character to the line prefix buffer. 373 */ 374 static void add_pfx(char ch, int attr) 375 { 376 set_pfx(linebuf.pfx_end++, ch, attr); 377 } 378 379 /* 380 * Insert the status column and line number into the line buffer. 381 */ 382 public void plinestart(POSITION pos) 383 { 384 LINENUM linenum = 0; 385 386 if (linenums == OPT_ONPLUS) 387 { 388 /* 389 * Get the line number and put it in the current line. 390 * {{ Note: since find_linenum calls forw_raw_line, 391 * it may seek in the input file, requiring the caller 392 * of plinestart to re-seek if necessary. }} 393 * {{ Since forw_raw_line modifies linebuf, we must 394 * do this first, before storing anything in linebuf. }} 395 */ 396 linenum = find_linenum(pos); 397 } 398 399 /* 400 * Display a status column if the -J option is set. 401 */ 402 if (status_col || status_line) 403 { 404 char c = posmark(pos); 405 if (c != 0) 406 line_mark_attr = AT_HILITE|AT_COLOR_MARK; 407 else if (start_attnpos != NULL_POSITION && 408 pos >= start_attnpos && pos <= end_attnpos) 409 line_mark_attr = AT_HILITE|AT_COLOR_ATTN; 410 if (status_col) 411 { 412 add_pfx(c ? c : ' ', line_mark_attr); /* column 0: status */ 413 while (linebuf.pfx_end < (size_t) status_col_width) /*{{type-issue}}*/ 414 add_pfx(' ', AT_NORMAL); 415 } 416 } 417 418 /* 419 * Display the line number at the start of each line 420 * if the -N option is set. 421 */ 422 if (linenums == OPT_ONPLUS) 423 { 424 char buf[INT_STRLEN_BOUND(linenum) + 2]; 425 size_t len; 426 size_t i; 427 428 linenum = vlinenum(linenum); 429 if (linenum == 0) 430 len = 0; 431 else 432 { 433 linenumtoa(linenum, buf, 10); 434 len = strlen(buf); 435 } 436 for (i = 0; i + len < (size_t) linenum_width; i++) 437 add_pfx(' ', AT_NORMAL); 438 for (i = 0; i < len; i++) 439 add_pfx(buf[i], AT_BOLD|AT_COLOR_LINENUM); 440 add_pfx(' ', AT_NORMAL); 441 } 442 end_column = (int) linebuf.pfx_end; /*{{type-issue}}*/ 443 } 444 445 /* 446 * Return the width of the line prefix (status column and line number). 447 * {{ Actual line number can be wider than linenum_width. }} 448 */ 449 public int line_pfx_width(void) 450 { 451 int width = 0; 452 if (status_col) 453 width += status_col_width; 454 if (linenums == OPT_ONPLUS) 455 width += linenum_width + 1; 456 return width; 457 } 458 459 /* 460 * Shift line left so that the last char is just to the left 461 * of the first visible column. 462 */ 463 public void pshift_all(void) 464 { 465 pshift(linebuf.end); 466 linebuf.end = linebuf.print; 467 end_column = (int) linebuf.pfx_end; /*{{type-issue}}*/ 468 line_pos = NULL_POSITION; 469 } 470 471 /* 472 * Return the printing width of the start (enter) sequence 473 * for a given character attribute. 474 */ 475 static int attr_swidth(int a) 476 { 477 int w = 0; 478 479 a = apply_at_specials(a); 480 481 if (a & AT_UNDERLINE) 482 w += ul_s_width; 483 if (a & AT_BOLD) 484 w += bo_s_width; 485 if (a & AT_BLINK) 486 w += bl_s_width; 487 if (a & AT_STANDOUT) 488 w += so_s_width; 489 490 return w; 491 } 492 493 /* 494 * Return the printing width of the end (exit) sequence 495 * for a given character attribute. 496 */ 497 static int attr_ewidth(int a) 498 { 499 int w = 0; 500 501 a = apply_at_specials(a); 502 503 if (a & AT_UNDERLINE) 504 w += ul_e_width; 505 if (a & AT_BOLD) 506 w += bo_e_width; 507 if (a & AT_BLINK) 508 w += bl_e_width; 509 if (a & AT_STANDOUT) 510 w += so_e_width; 511 512 return w; 513 } 514 515 /* 516 * Return the printing width of a given character and attribute, 517 * if the character were added after prev_ch. 518 * Adding a character with a given attribute may cause an enter or exit 519 * attribute sequence to be inserted, so this must be taken into account. 520 */ 521 public int pwidth(LWCHAR ch, int a, LWCHAR prev_ch, int prev_a) 522 { 523 int w; 524 525 if (ch == '\b') 526 { 527 /* 528 * Backspace moves backwards one or two positions. 529 */ 530 if (prev_a & (AT_ANSI|AT_BINARY)) 531 return (int) strlen(prchar('\b')); /*{{type-issue}}*/ 532 return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1; 533 } 534 535 if (!utf_mode || is_ascii_char(ch)) 536 { 537 if (control_char(ch)) 538 { 539 /* 540 * Control characters do unpredictable things, 541 * so we don't even try to guess; say it doesn't move. 542 * This can only happen if the -r flag is in effect. 543 */ 544 return (0); 545 } 546 } else 547 { 548 if (ch == VARSEL_15) 549 /* If prev char was double width, make it single width. */ 550 return (prev_ch != 0 && pwidth(prev_ch, a, 0, 0) == 2) ? -1 : 0; 551 if (ch == VARSEL_16) 552 /* If prev char was single width, make it double width. */ 553 return (prev_ch != 0 && pwidth(prev_ch, a, 0, 0) == 1) ? +1 : 0; 554 if (is_composing_char(ch) || is_combining_char(prev_ch, ch)) 555 { 556 /* 557 * Composing and combining chars take up no space. 558 * 559 * Some terminals, upon failure to compose a 560 * composing character with the character(s) that 561 * precede(s) it will actually take up one end_column 562 * for the composing character; there isn't much 563 * we could do short of testing the (complex) 564 * composition process ourselves and printing 565 * a binary representation when it fails. 566 */ 567 return (0); 568 } 569 } 570 571 /* 572 * Other characters take one or two columns, 573 * plus the width of any attribute enter/exit sequence. 574 */ 575 w = 1; 576 if (is_wide_char(ch)) 577 w++; 578 if (linebuf.end > 0 && !is_at_equiv(linebuf.attr[linebuf.end-1], a)) 579 w += attr_ewidth(linebuf.attr[linebuf.end-1]); 580 if (apply_at_specials(a) != AT_NORMAL && 581 (linebuf.end == 0 || !is_at_equiv(linebuf.attr[linebuf.end-1], a))) 582 w += attr_swidth(a); 583 return (w); 584 } 585 586 /* 587 * Delete to the previous base character in the line buffer. 588 */ 589 static int backc(void) 590 { 591 LWCHAR ch; 592 char *p; 593 594 if (linebuf.end == 0) 595 return (0); 596 p = &linebuf.buf[linebuf.end]; 597 ch = step_char(&p, -1, linebuf.buf); 598 /* Skip back to the next nonzero-width char. */ 599 while (p > linebuf.buf) 600 { 601 LWCHAR prev_ch; 602 int width; 603 linebuf.end = ptr_diff(p, linebuf.buf); 604 prev_ch = step_char(&p, -1, linebuf.buf); 605 width = pwidth(ch, linebuf.attr[linebuf.end], prev_ch, linebuf.attr[linebuf.end-1]); 606 end_column -= width; 607 /* {{ right_column? }} */ 608 if (width > 0) 609 break; 610 ch = prev_ch; 611 } 612 return (1); 613 } 614 615 /* 616 * Preserve the current position in the line buffer (for word wrapping). 617 */ 618 public void savec(void) 619 { 620 saved_line_end = linebuf.end; 621 saved_end_column = end_column; 622 } 623 624 /* 625 * Restore the position in the line buffer (start of line for word wrapping). 626 */ 627 public void loadc(void) 628 { 629 linebuf.end = saved_line_end; 630 end_column = saved_end_column; 631 } 632 633 /* 634 * Is a character the end of an ANSI escape sequence? 635 */ 636 public lbool is_ansi_end(LWCHAR ch) 637 { 638 if (!is_ascii_char(ch)) 639 return (FALSE); 640 return (ch != 0 && strchr(end_ansi_chars, (char) ch) != NULL); 641 } 642 643 /* 644 * Can a char appear in an ANSI escape sequence, before the end char? 645 */ 646 public lbool is_ansi_middle(LWCHAR ch) 647 { 648 if (!is_ascii_char(ch)) 649 return (FALSE); 650 if (is_ansi_end(ch)) 651 return (FALSE); 652 return (ch != 0 && strchr(mid_ansi_chars, (char) ch) != NULL); 653 } 654 655 /* 656 * Skip past an ANSI escape sequence. 657 * pp is initially positioned just after the CSI_START char. 658 */ 659 public void skip_ansi(struct ansi_state *pansi, LWCHAR ch, constant char **pp, constant char *limit) 660 { 661 ansi_step(pansi, ch); 662 do { 663 ch = step_charc(pp, +1, limit); 664 } while (*pp < limit && ansi_step(pansi, ch) == ANSI_MID); 665 /* Note that we discard final char, for which is_ansi_end is true. */ 666 } 667 668 /* 669 * Determine if a character starts an ANSI escape sequence. 670 * If so, return an ansi_state struct; otherwise return NULL. 671 */ 672 public struct ansi_state * ansi_start(LWCHAR ch) 673 { 674 struct ansi_state *pansi; 675 676 if (!IS_CSI_START(ch)) 677 return NULL; 678 pansi = ecalloc(1, sizeof(struct ansi_state)); 679 pansi->ostate = OSC_START; 680 pansi->otype = 0; 681 pansi->escs_in_seq = 0; 682 return pansi; 683 } 684 685 /* 686 * Is a character a valid intro char for an OSC sequence? 687 * An intro char is the one immediately after the ESC, usually ']'. 688 */ 689 static lbool valid_osc_intro(char ch, lbool content) 690 { 691 constant char *p = strchr(osc_ansi_chars, ch); 692 if (p == NULL) 693 return FALSE; 694 return (!content || p[1] == '*'); 695 } 696 697 /* 698 * Is a given number a valid OSC type? 699 */ 700 static lbool valid_osc_type(int otype, lbool content) 701 { 702 int i; 703 if (!content) 704 return TRUE; 705 if (otype == 8) 706 return TRUE; 707 for (i = 0; i < osc_ansi_allow_count; i++) 708 if (osc_ansi_allow[i] == otype) 709 return TRUE; 710 return FALSE; 711 } 712 713 /* 714 * Helper function for ansi_step. 715 */ 716 static ansi_state osc_return(struct ansi_state *pansi, osc8_state ostate, ansi_state astate) 717 { 718 pansi->ostate = ostate; 719 return astate; 720 } 721 722 /* 723 * Determine whether the next char in an ANSI escape sequence 724 * ends the sequence. 725 */ 726 static ansi_state ansi_step2(struct ansi_state *pansi, LWCHAR ch, lbool content) 727 { 728 /* 729 * Pass thru OS commands. Assume OSC commands do not move the cursor. 730 * A "typed" OSC starts with ESC ] <integer> <semicolon>, followed by an 731 * arbitrary string, and ends with a String Terminator (ESC-backslash or BEL). 732 * An untyped OSC starts with ESC ] or ESC x where x is in osc_ansi_chars, 733 * and ends with ST. 734 * The only typed OSC we actually parse is OSC 8. 735 */ 736 switch (pansi->ostate) 737 { 738 case OSC_START: 739 if (IS_CSI_START(ch)) 740 return osc_return(pansi, OSC_INTRO, ANSI_MID); 741 break; 742 case OSC_INTRO: 743 if (ch == ']') 744 return osc_return(pansi, OSC_TYPENUM, ANSI_MID); 745 if (is_ascii_char(ch) && valid_osc_intro((char) ch, content)) 746 return osc_return(pansi, OSC_STRING, ANSI_MID); 747 if (IS_CSI_START(ch)) 748 return osc_return(pansi, OSC_INTRO, ANSI_MID); 749 /* ESC not followed by bracket; restart. */ 750 pansi->ostate = OSC_START; 751 break; 752 case OSC_TYPENUM: 753 if (ch >= '0' && ch <= '9') 754 { 755 if (ckd_mul(&pansi->otype, pansi->otype, 10) || 756 ckd_add(&pansi->otype, pansi->otype, ch - '0')) 757 return osc_return(pansi, OSC_STRING, ANSI_MID); 758 return osc_return(pansi, OSC_TYPENUM, ANSI_MID); 759 } 760 if (ch == ';') 761 return osc_return(pansi, (pansi->otype == 8) ? OSC8_PARAMS : OSC_STRING, ANSI_MID); 762 /* OSC is untyped */ 763 if (IS_CSI_START(ch)) 764 return osc_return(pansi, OSC_END_CSI, ANSI_MID); 765 if (ch == '\7') 766 return osc_return(pansi, OSC_END, ANSI_END); 767 return osc_return(pansi, OSC_STRING, ANSI_MID); 768 case OSC8_PARAMS: 769 if (ch == ';') 770 return osc_return(pansi, OSC8_URI, ANSI_MID); 771 /* FALLTHRU */ 772 case OSC8_URI: 773 case OSC_STRING: 774 /* Look for ST. */ 775 if (ch == '\7') 776 return osc_return(pansi, OSC_END, valid_osc_type(pansi->otype, content) ? ANSI_END : ANSI_ERR); 777 if (IS_CSI_START(ch)) 778 { 779 pansi->escs_in_seq++; 780 return osc_return(pansi, OSC_END_CSI, ANSI_MID); 781 } 782 /* Stay in same ostate */ 783 return ANSI_MID; 784 case OSC_END_CSI: 785 /* Got ESC of ST, expect backslash next. */ 786 if (ch == '\\') 787 return osc_return(pansi, OSC_END, valid_osc_type(pansi->otype, content) ? ANSI_END : ANSI_ERR); 788 /* ESC not followed by backslash. */ 789 return osc_return(pansi, OSC_STRING, ANSI_MID); 790 case OSC_END: 791 return ANSI_END; 792 case OSC8_NOT: 793 /* cannot happen */ 794 break; 795 } 796 /* Check for SGR sequences */ 797 if (is_ansi_middle(ch)) 798 return ANSI_MID; 799 if (is_ansi_end(ch)) 800 return ANSI_END; 801 return ANSI_ERR; 802 } 803 804 public ansi_state ansi_step(struct ansi_state *pansi, LWCHAR ch) 805 { 806 return ansi_step2(pansi, ch, TRUE); 807 } 808 809 /* 810 * Return the current OSC8 parsing state. 811 */ 812 public osc8_state ansi_osc8_state(struct ansi_state *pansi) 813 { 814 return pansi->ostate; 815 } 816 817 /* 818 * Free an ansi_state structure. 819 */ 820 public void ansi_done(struct ansi_state *pansi) 821 { 822 free(pansi); 823 } 824 825 /* 826 * Will w characters in attribute a fit on the screen? 827 */ 828 static lbool fits_on_screen(int w, int a) 829 { 830 if (ctldisp == OPT_ON) 831 /* We're not counting, so say that everything fits. */ 832 return TRUE; 833 return (end_column - cshift + w + attr_ewidth(a) <= sc_width); 834 } 835 836 /* 837 * Append a character and attribute to the line buffer. 838 */ 839 #define STORE_CHAR(ch,a,rep,pos) \ 840 do { \ 841 if (store_char((ch),(a),(rep),(pos))) return (1); \ 842 } while (0) 843 844 static int store_char(LWCHAR ch, int a, constant char *rep, POSITION pos) 845 { 846 int w; 847 size_t i; 848 size_t replen; 849 char cs; 850 int ov; 851 lbool need_shift; 852 853 ov = (a & (AT_UNDERLINE|AT_BOLD)); 854 if (ov != AT_NORMAL) 855 last_overstrike = ov; 856 857 #if HILITE_SEARCH 858 { 859 int matches; 860 int resend_last = 0; 861 int hl_attr = 0; 862 863 if (pos != NULL_POSITION && a != AT_ANSI) 864 { 865 hl_attr = is_hilited_attr(pos, pos+1, 0, &matches); 866 if (hl_attr == 0 && status_line) 867 hl_attr = line_mark_attr; 868 } 869 if (hl_attr) 870 { 871 /* 872 * This character should be highlighted. 873 * Override the attribute passed in. 874 */ 875 a |= hl_attr; 876 if (highest_hilite != NULL_POSITION && pos != NULL_POSITION && pos > highest_hilite) 877 highest_hilite = pos; 878 in_hilite = TRUE; 879 } else 880 { 881 if (in_hilite) 882 { 883 /* 884 * This is the first non-hilited char after a hilite. 885 * Resend the last ANSI seq to restore color. 886 */ 887 resend_last = 1; 888 } 889 in_hilite = FALSE; 890 } 891 if (resend_last) 892 { 893 int ai; 894 for (ai = 0; ai < NUM_LAST_ANSIS; ai++) 895 { 896 int ax = (curr_last_ansi + ai) % NUM_LAST_ANSIS; 897 for (i = 0; i < last_ansis[ax].end; i++) 898 STORE_CHAR(last_ansis[ax].data[i], AT_ANSI, NULL, pos); 899 } 900 } 901 } 902 #endif 903 904 if (a == AT_ANSI) { 905 w = 0; 906 } else { 907 char *p = &linebuf.buf[linebuf.end]; 908 LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0; 909 int prev_a = (linebuf.end > 0) ? linebuf.attr[linebuf.end-1] : 0; 910 w = pwidth(ch, a, prev_ch, prev_a); 911 } 912 913 if (!fits_on_screen(w, a)) 914 return (1); 915 916 if (rep == NULL) 917 { 918 cs = (char) ch; 919 rep = &cs; 920 replen = 1; 921 } else 922 { 923 replen = (size_t) utf_len(rep[0]); /*{{type-issue}}*/ 924 } 925 926 if (cshift == hshift) 927 { 928 if (line_pos == NULL_POSITION) 929 line_pos = pos; 930 if (shifted_ansi.end > 0) 931 { 932 /* Copy shifted ANSI sequences to beginning of line. */ 933 for (i = 0; i < shifted_ansi.end; i++) 934 add_linebuf((char) shifted_ansi.data[i], AT_ANSI, 0); 935 xbuf_reset(&shifted_ansi); 936 } 937 if (linebuf.end == linebuf.print+1) 938 { 939 /* If first char is a placeholder, the one before it is double-width. 940 * VS15 changes the double-width char to single-width, so replace the 941 * placeholder with this VS15. */ 942 if (ch == VARSEL_15 && (linebuf.attr[linebuf.end-1] & AT_PLACEHOLDER)) 943 { 944 linebuf.end--; 945 inc_end_column(-1); 946 } 947 } else if (linebuf.end == linebuf.print) 948 { 949 /* VS16 changes the previous single-width char to double-width. 950 * Add a placeholder to represent the second half of the 951 * double-width char. */ 952 if (ch == VARSEL_16) 953 { 954 char *p = &linebuf.buf[linebuf.end]; 955 LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0; 956 if (prev_ch != 0 && pwidth(prev_ch, a, 0, 0) == 1) 957 add_linebuf(' ', rscroll_attr|AT_PLACEHOLDER, 0); 958 } 959 } 960 } 961 962 /* Add the char to the buf, even if we will left-shift it next. */ 963 need_shift = (cshift < hshift); 964 if (!need_shift && w <= 0 && linebuf.end <= linebuf.print+1 && is_composing_char(ch) && 965 (linebuf.end == linebuf.print || (linebuf.end == linebuf.print+1 && (linebuf.attr[linebuf.end-1] & AT_PLACEHOLDER)))) 966 need_shift = TRUE; 967 inc_end_column(w); 968 for (i = 0; i < replen; i++) 969 add_linebuf(*rep++, a, 0); 970 971 if (need_shift) 972 { 973 /* We haven't left-shifted enough yet. */ 974 if (a == AT_ANSI) 975 xbuf_add_char(&shifted_ansi, (char) ch); /* Save ANSI attributes */ 976 if (linebuf.end > linebuf.print) 977 { 978 /* Shift left enough to put last byte of this char at print-1. */ 979 size_t i; 980 for (i = 0; i < linebuf.print; i++) 981 { 982 linebuf.buf[i] = linebuf.buf[i+replen]; 983 linebuf.attr[i] = linebuf.attr[i+replen]; 984 } 985 linebuf.end -= replen; 986 cshift += w; 987 /* 988 * If the char we just left-shifted was double width, 989 * the 2 spaces we shifted may be too much. 990 * Represent the "half char" at start of line with a highlighted space. 991 */ 992 while (cshift > hshift) 993 { 994 add_linebuf(' ', rscroll_attr|AT_PLACEHOLDER, 0); 995 cshift--; 996 } 997 } 998 } 999 return (0); 1000 } 1001 1002 #define STORE_STRING(s,a,pos) \ 1003 do { if (store_string((s),(a),(pos))) return (1); } while (0) 1004 1005 static int store_string(constant char *s, int a, POSITION pos) 1006 { 1007 if (!fits_on_screen((int) strlen(s), a)) 1008 return 1; 1009 for ( ; *s != 0; s++) 1010 STORE_CHAR((LWCHAR)*s, a, NULL, pos); 1011 return 0; 1012 } 1013 1014 /* 1015 * Return number of spaces from col to the next tab stop. 1016 */ 1017 static int tab_spaces(int col) 1018 { 1019 int to_tab = col - (int) linebuf.pfx_end; /*{{type-issue}}*/ 1020 1021 if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1]) 1022 to_tab = tabdefault - 1023 ((to_tab - tabstops[ntabstops-1]) % tabdefault); 1024 else 1025 { 1026 int i; 1027 for (i = ntabstops - 2; i >= 0; i--) 1028 if (to_tab >= tabstops[i]) 1029 break; 1030 to_tab = tabstops[i+1] - to_tab; 1031 } 1032 return to_tab; 1033 } 1034 1035 /* 1036 * Append a tab to the line buffer. 1037 * Store spaces to represent the tab. 1038 */ 1039 #define STORE_TAB(a,pos) \ 1040 do { if (store_tab((a),(pos))) return (1); } while (0) 1041 1042 static int store_tab(int attr, POSITION pos) 1043 { 1044 int to_tab = tab_spaces(end_column); 1045 do { 1046 STORE_CHAR(' ', attr, " ", pos); 1047 } while (--to_tab > 0); 1048 return 0; 1049 } 1050 1051 #define STORE_PRCHAR(c, pos) \ 1052 do { if (store_prchar((c), (pos))) return 1; } while (0) 1053 1054 static int store_prchar(LWCHAR c, POSITION pos) 1055 { 1056 /* 1057 * Convert to printable representation. 1058 */ 1059 STORE_STRING(prchar(c), AT_BINARY|AT_COLOR_CTRL, pos); 1060 return 0; 1061 } 1062 1063 static int flush_mbc_buf(POSITION pos) 1064 { 1065 int i; 1066 1067 for (i = 0; i < mbc_buf_index; i++) 1068 if (store_prchar((LWCHAR) mbc_buf[i], pos)) 1069 return mbc_buf_index - i; 1070 return 0; 1071 } 1072 1073 /* 1074 * Append a character to the line buffer. 1075 * Expand tabs into spaces, handle underlining, boldfacing, etc. 1076 * Returns 0 if ok, 1 if couldn't fit in buffer. 1077 */ 1078 public int pappend_b(char c, POSITION pos, lbool before_pendc) 1079 { 1080 LWCHAR ch = c & 0377; 1081 int r; 1082 1083 if (pendc && !before_pendc) 1084 { 1085 if (ch == '\r' && pendc == '\r') 1086 return (0); 1087 if (do_append(pendc, NULL, pendpos)) 1088 /* 1089 * Oops. We've probably lost the char which 1090 * was in pendc, since caller won't back up. 1091 */ 1092 return (1); 1093 pendc = '\0'; 1094 } 1095 1096 if (ch == '\r' && (proc_return == OPT_ON || (bs_mode == BS_SPECIAL && proc_return == OPT_OFF))) 1097 { 1098 if (mbc_buf_len > 0) /* utf_mode must be on. */ 1099 { 1100 /* Flush incomplete (truncated) sequence. */ 1101 r = flush_mbc_buf(mbc_pos); 1102 mbc_buf_index = r + 1; 1103 mbc_buf_len = 0; 1104 if (r) 1105 return (mbc_buf_index); 1106 } 1107 1108 /* 1109 * Don't put the CR into the buffer until we see 1110 * the next char. If the next char is a newline, 1111 * discard the CR. 1112 */ 1113 pendc = ch; 1114 pendpos = pos; 1115 return (0); 1116 } 1117 1118 if (!utf_mode) 1119 { 1120 r = do_append(ch, NULL, pos); 1121 } else 1122 { 1123 /* Perform strict validation in all possible cases. */ 1124 if (mbc_buf_len == 0) 1125 { 1126 retry: 1127 mbc_buf_index = 1; 1128 *mbc_buf = c; 1129 if (IS_ASCII_OCTET(c)) 1130 r = do_append(ch, NULL, pos); 1131 else if (IS_UTF8_LEAD(c)) 1132 { 1133 mbc_buf_len = utf_len(c); 1134 mbc_pos = pos; 1135 return (0); 1136 } else 1137 /* UTF8_INVALID or stray UTF8_TRAIL */ 1138 r = flush_mbc_buf(pos); 1139 } else if (IS_UTF8_TRAIL(c)) 1140 { 1141 mbc_buf[mbc_buf_index++] = c; 1142 if (mbc_buf_index < mbc_buf_len) 1143 return (0); 1144 if (is_utf8_well_formed(mbc_buf, mbc_buf_index)) 1145 r = do_append(get_wchar(mbc_buf), mbc_buf, mbc_pos); 1146 else 1147 /* Complete, but not shortest form, sequence. */ 1148 mbc_buf_index = r = flush_mbc_buf(mbc_pos); 1149 mbc_buf_len = 0; 1150 } else 1151 { 1152 /* Flush incomplete (truncated) sequence. */ 1153 r = flush_mbc_buf(mbc_pos); 1154 mbc_buf_index = r + 1; 1155 mbc_buf_len = 0; 1156 /* Handle new char. */ 1157 if (!r) 1158 goto retry; 1159 } 1160 } 1161 if (r) 1162 { 1163 /* How many chars should caller back up? */ 1164 r = (!utf_mode) ? 1 : mbc_buf_index; 1165 } 1166 return (r); 1167 } 1168 1169 public int pappend(char c, POSITION pos) 1170 { 1171 if (ff_starts_line < 0) 1172 ff_starts_line = (c == CONTROL('L')); 1173 return pappend_b(c, pos, FALSE); 1174 } 1175 1176 public lbool line_is_ff(void) 1177 { 1178 return (ff_starts_line == 1); 1179 } 1180 1181 static int store_control_char(LWCHAR ch, constant char *rep, POSITION pos) 1182 { 1183 if (ctldisp == OPT_ON) 1184 { 1185 /* Output the character itself. */ 1186 STORE_CHAR(ch, AT_NORMAL, rep, pos); 1187 } else 1188 { 1189 /* Output a printable representation of the character. */ 1190 STORE_PRCHAR(ch, pos); 1191 } 1192 return (0); 1193 } 1194 1195 static int store_ansi(LWCHAR ch, constant char *rep, POSITION pos) 1196 { 1197 switch (ansi_step2(line_ansi, ch, pos != NULL_POSITION)) 1198 { 1199 case ANSI_MID: 1200 STORE_CHAR(ch, AT_ANSI, rep, pos); 1201 switch (ansi_osc8_state(line_ansi)) 1202 { 1203 case OSC_TYPENUM: case OSC_STRING: hlink_in_line = TRUE; break; 1204 default: break; 1205 } 1206 xbuf_add_char(&last_ansi, (char) ch); 1207 break; 1208 case ANSI_END: 1209 STORE_CHAR(ch, AT_ANSI, rep, pos); 1210 ansi_done(line_ansi); 1211 line_ansi = NULL; 1212 xbuf_add_char(&last_ansi, (char) ch); 1213 xbuf_set(&last_ansis[curr_last_ansi], &last_ansi); 1214 xbuf_reset(&last_ansi); 1215 curr_last_ansi = (curr_last_ansi + 1) % NUM_LAST_ANSIS; 1216 break; 1217 case ANSI_ERR: 1218 { 1219 /* Remove whole unrecognized sequence. */ 1220 constant char *start = (cshift < hshift) ? xbuf_char_data(&shifted_ansi): linebuf.buf; 1221 size_t *end = (cshift < hshift) ? &shifted_ansi.end : &linebuf.end; 1222 constant char *p = start + *end; 1223 LWCHAR bch; 1224 do { 1225 bch = step_charc(&p, -1, start); 1226 } while (p > start && (!IS_CSI_START(bch) || line_ansi->escs_in_seq-- > 0)); 1227 *end = ptr_diff(p, start); 1228 } 1229 xbuf_reset(&last_ansi); 1230 ansi_done(line_ansi); 1231 line_ansi = NULL; 1232 break; 1233 default: 1234 break; 1235 } 1236 return (0); 1237 } 1238 1239 static int store_bs(LWCHAR ch, constant char *rep, POSITION pos) 1240 { 1241 if (proc_backspace == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_backspace == OPT_OFF)) 1242 return store_control_char(ch, rep, pos); 1243 if (linebuf.end > 0 && 1244 ((linebuf.end <= linebuf.print && linebuf.buf[linebuf.end-1] == '\0') || 1245 (linebuf.end > 0 && linebuf.attr[linebuf.end - 1] & (AT_ANSI|AT_BINARY)))) 1246 STORE_PRCHAR('\b', pos); 1247 else if (proc_backspace == OPT_OFF && bs_mode == BS_NORMAL) 1248 STORE_CHAR(ch, AT_NORMAL, NULL, pos); 1249 else if (proc_backspace == OPT_ON || (bs_mode == BS_SPECIAL && proc_backspace == OPT_OFF)) 1250 overstrike = backc(); 1251 return 0; 1252 } 1253 1254 static int do_append(LWCHAR ch, constant char *rep, POSITION pos) 1255 { 1256 int a = AT_NORMAL; 1257 int in_overstrike = overstrike; 1258 1259 if ((ctldisp == OPT_ONPLUS || pos == NULL_POSITION) && line_ansi == NULL) 1260 { 1261 line_ansi = ansi_start(ch); 1262 if (line_ansi != NULL) 1263 ansi_in_line = TRUE; 1264 } 1265 1266 overstrike = 0; 1267 if (line_ansi != NULL) 1268 return store_ansi(ch, rep, pos); 1269 1270 if (ch == '\b') 1271 return store_bs(ch, rep, pos); 1272 1273 if (in_overstrike > 0) 1274 { 1275 /* 1276 * Overstrike the character at the current position 1277 * in the line buffer. This will cause either 1278 * underline (if a "_" is overstruck), 1279 * bold (if an identical character is overstruck), 1280 * or just replacing the character in the buffer. 1281 */ 1282 LWCHAR prev_ch; 1283 overstrike = utf_mode ? -1 : 0; 1284 if (utf_mode) 1285 { 1286 /* To be correct, this must be a base character. */ 1287 prev_ch = get_wchar(&linebuf.buf[linebuf.end]); 1288 } else 1289 { 1290 prev_ch = (unsigned char) linebuf.buf[linebuf.end]; 1291 } 1292 a = linebuf.attr[linebuf.end]; 1293 if (ch == prev_ch) 1294 { 1295 /* 1296 * Overstriking a char with itself means make it bold. 1297 * But overstriking an underscore with itself is 1298 * ambiguous. It could mean make it bold, or 1299 * it could mean make it underlined. 1300 * Use the previous overstrike to resolve it. 1301 */ 1302 if (ch == '_') 1303 { 1304 if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL) 1305 a |= (AT_BOLD|AT_UNDERLINE); 1306 else if (last_overstrike != AT_NORMAL) 1307 a |= last_overstrike; 1308 else 1309 a |= AT_BOLD; 1310 } else 1311 a |= AT_BOLD; 1312 } else if (ch == '_') 1313 { 1314 a |= AT_UNDERLINE; 1315 ch = prev_ch; 1316 rep = &linebuf.buf[linebuf.end]; 1317 } else if (prev_ch == '_') 1318 { 1319 a |= AT_UNDERLINE; 1320 } 1321 /* Else we replace prev_ch, but we keep its attributes. */ 1322 } else if (in_overstrike < 0) 1323 { 1324 if ( is_composing_char(ch) 1325 || is_combining_char(get_wchar(&linebuf.buf[linebuf.end]), ch)) 1326 /* Continuation of the same overstrike. */ 1327 a = last_overstrike; 1328 else 1329 overstrike = 0; 1330 } 1331 1332 if (is_omit_char(ch)) 1333 { 1334 if (bs_mode == BS_CONTROL) 1335 { 1336 if (utf_mode) 1337 STORE_STRING(prutfchar(ch), AT_BINARY, pos); 1338 else 1339 STORE_PRCHAR(ch, pos); 1340 } 1341 return (0); /* omit the character. */ 1342 } 1343 if (ch == '\t') 1344 { 1345 /* 1346 * Expand a tab into spaces. 1347 */ 1348 if (proc_tab == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_tab == OPT_OFF)) 1349 return store_control_char(ch, rep, pos); 1350 STORE_TAB(a, pos); 1351 return (0); 1352 } 1353 if ((!utf_mode || is_ascii_char(ch)) && control_char(ch)) 1354 { 1355 return store_control_char(ch, rep, pos); 1356 } else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch)) 1357 { 1358 STORE_STRING(prutfchar(ch), AT_BINARY, pos); 1359 } else 1360 { 1361 STORE_CHAR(ch, a, rep, pos); 1362 } 1363 return (0); 1364 } 1365 1366 /* 1367 * 1368 */ 1369 public int pflushmbc(void) 1370 { 1371 int r = 0; 1372 1373 if (mbc_buf_len > 0) 1374 { 1375 /* Flush incomplete (truncated) sequence. */ 1376 r = flush_mbc_buf(mbc_pos); 1377 mbc_buf_len = 0; 1378 } 1379 return r; 1380 } 1381 1382 /* 1383 * Switch to normal attribute at end of line. 1384 */ 1385 static void add_attr_normal(void) 1386 { 1387 if (line_ansi != NULL) 1388 { 1389 switch (line_ansi->ostate) 1390 { 1391 case OSC_TYPENUM: 1392 case OSC8_PARAMS: 1393 case OSC8_URI: 1394 case OSC_STRING: 1395 addstr_linebuf("\033\\", AT_ANSI, 0); 1396 break; 1397 default: 1398 break; 1399 } 1400 ansi_done(line_ansi); 1401 line_ansi = NULL; 1402 } 1403 if (ctldisp != OPT_ONPLUS || !is_ansi_end('m')) 1404 return; 1405 addstr_linebuf("\033[m", AT_ANSI, 0); 1406 if (hlink_in_line) /* Don't send hyperlink clear if we know we don't need to. */ 1407 addstr_linebuf("\033]8;;\033\\", AT_ANSI, 0); 1408 } 1409 1410 /* 1411 * Terminate the line in the line buffer. 1412 */ 1413 public void pdone(lbool endline, lbool chopped, lbool forw) 1414 { 1415 (void) pflushmbc(); 1416 linebuf.prev_end = (!endline && !chopped) ? linebuf.end : 0; 1417 1418 if (pendc && (pendc != '\r' || !endline)) 1419 /* 1420 * If we had a pending character, put it in the buffer. 1421 * But discard a pending CR if we are at end of line 1422 * (that is, discard the CR in a CR/LF sequence). 1423 */ 1424 (void) do_append(pendc, NULL, pendpos); 1425 1426 if (chopped && rscroll_char) 1427 { 1428 char rscroll_utf8[MAX_UTF_CHAR_LEN+1]; 1429 char *up = rscroll_utf8; 1430 1431 /* 1432 * Display the right scrolling char. 1433 * If we've already filled the rightmost screen char 1434 * (in the buffer), overwrite it. 1435 */ 1436 if (end_column >= sc_width + cshift) 1437 { 1438 /* We've already written in the rightmost char. */ 1439 end_column = right_column; 1440 linebuf.end = (size_t) right_curr; 1441 } 1442 add_attr_normal(); 1443 while (end_column < sc_width-1 + cshift) 1444 { 1445 /* 1446 * Space to last (rightmost) char on screen. 1447 * This may be necessary if the char we overwrote 1448 * was double-width. 1449 */ 1450 add_linebuf(' ', 0, 1); 1451 } 1452 /* Print rscroll char. */ 1453 put_wchar(&up, rscroll_char); 1454 *up = '\0'; 1455 addstr_linebuf(rscroll_utf8, rscroll_attr, 0); 1456 inc_end_column(1); /* assume rscroll_char is single-width */ 1457 } else 1458 { 1459 add_attr_normal(); 1460 } 1461 1462 /* 1463 * If we're coloring a status line, fill out the line with spaces. 1464 */ 1465 if (status_line && line_mark_attr != 0) { 1466 while (end_column +1 < sc_width + cshift) 1467 add_linebuf(' ', line_mark_attr, 1); 1468 } 1469 1470 /* 1471 * Add a newline if necessary, 1472 * and append a '\0' to the end of the line. 1473 * We output a newline if we're not at the right edge of the screen, 1474 * or if the terminal doesn't auto wrap, 1475 * or if this is really the end of the line AND the terminal ignores 1476 * a newline at the right edge. 1477 * (In the last case we don't want to output a newline if the terminal 1478 * doesn't ignore it since that would produce an extra blank line. 1479 * But we do want to output a newline if the terminal ignores it in case 1480 * the next line is blank. In that case the single newline output for 1481 * that blank line would be ignored!) 1482 */ 1483 if (end_column < sc_width + cshift || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON) 1484 { 1485 add_linebuf('\n', AT_NORMAL, 0); 1486 } 1487 else if (ignaw && end_column >= sc_width + cshift && forw) 1488 { 1489 /* 1490 * Terminals with "ignaw" don't wrap until they *really* need 1491 * to, i.e. when the character *after* the last one to fit on a 1492 * line is output. But they are too hard to deal with when they 1493 * get in the state where a full screen width of characters 1494 * have been output but the cursor is sitting on the right edge 1495 * instead of at the start of the next line. 1496 * So we nudge them into wrapping by outputting a space 1497 * character plus a backspace. But do this only if moving 1498 * forward; if we're moving backward and drawing this line at 1499 * the top of the screen, the space would overwrite the first 1500 * char on the next line. We don't need to do this "nudge" 1501 * at the top of the screen anyway. 1502 */ 1503 add_linebuf(' ', AT_NORMAL, 1); 1504 add_linebuf('\b', AT_NORMAL, -1); 1505 } 1506 /* 1507 * If a terminal moves the cursor to the next line immediately after 1508 * writing into the last char of a line, the following line may get 1509 * colored with the last char's background color before the color 1510 * reset sequence is sent. Clear the line to reset the background color. 1511 */ 1512 if (auto_wrap && !ignaw && end_column >= sc_width + cshift) 1513 clear_after_line = TRUE; 1514 set_linebuf(linebuf.end, '\0', AT_NORMAL); 1515 } 1516 1517 /* 1518 * Return the column number (screen position) of a given file position in its line. 1519 * linepos = position of first char in line 1520 * spos = position of char being queried 1521 * saved_pos = position of a known column, or NULL_POSITION if no known column 1522 * saved_col = column number of a known column, or -1 if no known column 1523 * 1524 * This attempts to mimic the logic in pappend() and the store_*() functions. 1525 * Duplicating this complicated logic is not a good design. 1526 */ 1527 1528 struct col_pos { int col; POSITION pos; }; 1529 1530 static void col_vs_pos(POSITION linepos, mutable struct col_pos *cp, POSITION saved_pos, int saved_col) 1531 { 1532 int col = (saved_col < 0) ? 0 : saved_col; 1533 LWCHAR prev_ch = 0; 1534 struct ansi_state *pansi = NULL; 1535 char utf8_buf[MAX_UTF_CHAR_LEN]; 1536 int utf8_len = 0; 1537 POSITION chpos; 1538 1539 if (ch_seek(saved_pos != NULL_POSITION ? saved_pos : linepos)) 1540 return; 1541 for (;;) 1542 { 1543 int ich; 1544 char ch; 1545 int cw = 0; 1546 1547 chpos = ch_tell(); 1548 ich = ch_forw_get(); 1549 ch = (char) ich; 1550 if (ich == EOI || ch == '\n') 1551 break; 1552 if (pansi != NULL) 1553 { 1554 if (ansi_step(pansi, ch) != ANSI_MID) 1555 { 1556 ansi_done(pansi); 1557 pansi = NULL; 1558 } 1559 } else if (ctldisp == OPT_ONPLUS && (pansi = ansi_start(ch)) != NULL) 1560 { 1561 /* start of ansi sequence */ 1562 (void) ansi_step(pansi, ch); 1563 } else if (ch == '\b') 1564 { 1565 if (proc_backspace == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_backspace == OPT_OFF)) 1566 cw = (int) strlen(prchar(ch)); 1567 else 1568 cw = (utf_mode && is_wide_char(prev_ch)) ? -2 : -1; 1569 } else if (ch == '\t') 1570 { 1571 if (proc_tab == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_tab == OPT_OFF)) 1572 cw = (int) strlen(prchar(ch)); 1573 else 1574 cw = tab_spaces(col); 1575 } else if ((!utf_mode || is_ascii_char(ch)) && control_char(ch)) 1576 { 1577 cw = (int) strlen(prchar(ch)); 1578 } else if (utf8_len < MAX_UTF_CHAR_LEN) 1579 { 1580 utf8_buf[utf8_len++] = ch; 1581 if (is_utf8_well_formed(utf8_buf, utf8_len)) 1582 { 1583 LWCHAR wch = get_wchar(utf8_buf); 1584 int attr = 0; /* {{ ignoring attribute is not correct for magic cookie terminals }} */ 1585 utf8_len = 0; 1586 if (is_omit_char(wch)) 1587 { 1588 if (bs_mode == BS_CONTROL) 1589 cw = strlen(utf_mode ? prutfchar(wch) : prchar(wch)); 1590 } else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(wch)) 1591 cw = (int) strlen(prutfchar(wch)); 1592 else 1593 cw = pwidth(wch, attr, prev_ch, attr); 1594 prev_ch = wch; 1595 } 1596 } else 1597 { 1598 utf8_len = 0; /* flush invalid UTF-8 */ 1599 } 1600 1601 if (cp->pos != NULL_POSITION && chpos == cp->pos) /* found the position we want */ 1602 break; 1603 if (cp->col >= 0 && col >= cp->col && cw > 0) /* found the column we want */ 1604 break; 1605 col += cw; 1606 prev_ch = ch; 1607 } 1608 cp->col = col; 1609 cp->pos = chpos; 1610 } 1611 1612 public int col_from_pos(POSITION linepos, POSITION spos, POSITION saved_pos, int saved_col) 1613 { 1614 struct col_pos cp; 1615 cp.pos = spos; 1616 cp.col = -1; 1617 col_vs_pos(linepos, &cp, saved_pos, saved_col); 1618 return cp.col; 1619 } 1620 1621 public POSITION pos_from_col(POSITION linepos, int col, POSITION saved_pos, int saved_col) 1622 { 1623 struct col_pos cp; 1624 cp.col = col + hshift - line_pfx_width(); 1625 cp.pos = NULL_POSITION; 1626 col_vs_pos(linepos, &cp, saved_pos, saved_col); 1627 return cp.pos; 1628 } 1629 1630 /* 1631 * Set an attribute on each char of the line in the line buffer. 1632 */ 1633 public void set_attr_line(int a) 1634 { 1635 size_t i; 1636 1637 for (i = linebuf.print; i < linebuf.end; i++) 1638 if ((linebuf.attr[i] & AT_COLOR) == 0 || (a & AT_COLOR) == 0) 1639 linebuf.attr[i] |= a; 1640 } 1641 1642 /* 1643 * Set the char to be displayed in the status column. 1644 */ 1645 public void set_status_col(char c, int attr) 1646 { 1647 set_pfx(0, c, attr); 1648 } 1649 1650 /* 1651 * Get a character from the current line. 1652 * Return the character as the function return value, 1653 * and the character attribute in *ap. 1654 */ 1655 public int gline(size_t i, int *ap) 1656 { 1657 if (is_null_line) 1658 { 1659 /* 1660 * If there is no current line, we pretend the line is 1661 * either "~" or "", depending on the "twiddle" flag. 1662 */ 1663 if (twiddle) 1664 { 1665 if (i == 0) 1666 { 1667 *ap = AT_BOLD; 1668 return '~'; 1669 } 1670 --i; 1671 } 1672 /* Make sure we're back to AT_NORMAL before the '\n'. */ 1673 *ap = AT_NORMAL; 1674 return i ? '\0' : '\n'; 1675 } 1676 1677 if (i < linebuf.pfx_end) 1678 { 1679 *ap = linebuf.pfx_attr[i]; 1680 return linebuf.pfx[i]; 1681 } 1682 i += linebuf.print - linebuf.pfx_end; 1683 *ap = linebuf.attr[i]; 1684 return (linebuf.buf[i] & 0xFF); 1685 } 1686 1687 /* 1688 * Should we clear to end of line after printing this line? 1689 */ 1690 public lbool should_clear_after_line(void) 1691 { 1692 return clear_after_line; 1693 } 1694 1695 /* 1696 * Indicate that there is no current line. 1697 */ 1698 public void null_line(void) 1699 { 1700 is_null_line = TRUE; 1701 cshift = 0; 1702 } 1703 1704 /* 1705 * Analogous to forw_line(), but deals with "raw lines": 1706 * lines which are not split for screen width. 1707 * {{ This is supposed to be more efficient than forw_line(). }} 1708 */ 1709 public POSITION forw_raw_line_len(POSITION curr_pos, size_t read_len, constant char **linep, size_t *line_lenp) 1710 { 1711 size_t n; 1712 int c; 1713 POSITION new_pos; 1714 1715 if (curr_pos == NULL_POSITION || ch_seek(curr_pos) || 1716 (c = ch_forw_get()) == EOI) 1717 return (NULL_POSITION); 1718 1719 set_line_contig_pos(NULL_POSITION); 1720 n = 0; 1721 for (;;) 1722 { 1723 if (c == '\n' || c == EOI || ABORT_SIGS()) 1724 { 1725 new_pos = ch_tell(); 1726 break; 1727 } 1728 if (n >= size_linebuf-1) 1729 { 1730 if (expand_linebuf()) 1731 { 1732 /* 1733 * Overflowed the input buffer. 1734 * Pretend the line ended here. 1735 */ 1736 new_pos = ch_tell() - 1; 1737 break; 1738 } 1739 } 1740 linebuf.buf[n++] = (char) c; 1741 if (read_len != size_t_null && read_len > 0 && n >= read_len) 1742 { 1743 new_pos = ch_tell(); 1744 break; 1745 } 1746 c = ch_forw_get(); 1747 } 1748 linebuf.buf[n] = '\0'; 1749 if (linep != NULL) 1750 *linep = linebuf.buf; 1751 if (line_lenp != NULL) 1752 *line_lenp = n; 1753 return (new_pos); 1754 } 1755 1756 public POSITION forw_raw_line(POSITION curr_pos, constant char **linep, size_t *line_lenp) 1757 { 1758 return forw_raw_line_len(curr_pos, size_t_null, linep, line_lenp); 1759 } 1760 1761 /* 1762 * Analogous to back_line(), but deals with "raw lines". 1763 * {{ This is supposed to be more efficient than back_line(). }} 1764 */ 1765 public POSITION back_raw_line(POSITION curr_pos, constant char **linep, size_t *line_lenp) 1766 { 1767 size_t n; 1768 int c; 1769 POSITION new_pos; 1770 1771 if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() || 1772 ch_seek(curr_pos-1)) 1773 return (NULL_POSITION); 1774 1775 set_line_contig_pos(NULL_POSITION); 1776 n = size_linebuf; 1777 linebuf.buf[--n] = '\0'; 1778 for (;;) 1779 { 1780 c = ch_back_get(); 1781 if (c == '\n' || ABORT_SIGS()) 1782 { 1783 /* 1784 * This is the newline ending the previous line. 1785 * We have hit the beginning of the line. 1786 */ 1787 new_pos = ch_tell() + 1; 1788 break; 1789 } 1790 if (c == EOI) 1791 { 1792 /* 1793 * We have hit the beginning of the file. 1794 * This must be the first line in the file. 1795 * This must, of course, be the beginning of the line. 1796 */ 1797 new_pos = ch_zero(); 1798 break; 1799 } 1800 if (n <= 0) 1801 { 1802 size_t old_size_linebuf = size_linebuf; 1803 char *fm; 1804 char *to; 1805 if (expand_linebuf()) 1806 { 1807 /* 1808 * Overflowed the input buffer. 1809 * Pretend the line ended here. 1810 */ 1811 new_pos = ch_tell() + 1; 1812 break; 1813 } 1814 /* 1815 * Shift the data to the end of the new linebuf. 1816 */ 1817 for (fm = linebuf.buf + old_size_linebuf - 1, 1818 to = linebuf.buf + size_linebuf - 1; 1819 fm >= linebuf.buf; fm--, to--) 1820 *to = *fm; 1821 n = size_linebuf - old_size_linebuf; 1822 } 1823 linebuf.buf[--n] = (char) c; 1824 } 1825 if (linep != NULL) 1826 *linep = &linebuf.buf[n]; 1827 if (line_lenp != NULL) 1828 *line_lenp = size_linebuf - 1 - n; 1829 return (new_pos); 1830 } 1831 1832 /* 1833 * Skip cols printable columns at the start of line. 1834 * Return number of bytes skipped. 1835 */ 1836 public int skip_columns(int cols, constant char **linep, size_t *line_lenp) 1837 { 1838 constant char *line = *linep; 1839 constant char *eline = line + *line_lenp; 1840 LWCHAR pch = 0; 1841 size_t bytes; 1842 1843 while (cols > 0 && line < eline) 1844 { 1845 LWCHAR ch = step_charc(&line, +1, eline); 1846 struct ansi_state *pansi = ansi_start(ch); 1847 if (pansi != NULL) 1848 { 1849 skip_ansi(pansi, ch, &line, eline); 1850 ansi_done(pansi); 1851 pch = 0; 1852 } else 1853 { 1854 int w = pwidth(ch, 0, pch, 0); 1855 cols -= w; 1856 pch = ch; 1857 } 1858 } 1859 bytes = ptr_diff(line, *linep); 1860 *linep = line; 1861 *line_lenp -= bytes; 1862 return (int) bytes; /*{{type-issue}}*/ 1863 } 1864 1865 /* 1866 * Append a string to the line buffer. 1867 */ 1868 static int pappstr(constant char *str) 1869 { 1870 while (*str != '\0') 1871 { 1872 if (pappend(*str++, NULL_POSITION)) 1873 /* Doesn't fit on screen. */ 1874 return 1; 1875 } 1876 return 0; 1877 } 1878 1879 /* 1880 * Load a string into the line buffer. 1881 * If the string is too long to fit on the screen, 1882 * truncate the beginning of the string to fit. 1883 */ 1884 public void load_line(constant char *str) 1885 { 1886 int save_hshift = hshift; 1887 hshift = 0; 1888 1889 /* We're overwriting the line buffer, so what's in it will no longer be contiguous. */ 1890 set_line_contig_pos(NULL_POSITION); 1891 1892 for (;;) 1893 { 1894 prewind(FALSE); 1895 if (pappstr(str) == 0) 1896 break; 1897 /* 1898 * Didn't fit on screen; increase left shift by one. 1899 * {{ This gets very inefficient if the string 1900 * is much longer than the screen width. }} 1901 */ 1902 hshift += 1; 1903 } 1904 set_linebuf(linebuf.end, '\0', AT_NORMAL); 1905 linebuf.prev_end = 0; 1906 1907 /* Color the prompt unless it has ansi sequences in it. */ 1908 if (!ansi_in_line) 1909 { 1910 size_t i; 1911 for (i = linebuf.print; i < linebuf.end; i++) 1912 set_linebuf(i, linebuf.buf[i], AT_STANDOUT|AT_COLOR_PROMPT); 1913 } 1914 hshift = save_hshift; 1915 } 1916 1917 /* 1918 * Find the shift necessary to show the end of the longest displayed line. 1919 */ 1920 public int rrshift(void) 1921 { 1922 POSITION pos; 1923 int save_width; 1924 int sline; 1925 int longest = 0; 1926 1927 save_width = sc_width; 1928 sc_width = INT_MAX; /* so forw_line() won't chop */ 1929 for (sline = TOP; sline < sc_height; sline++) 1930 if ((pos = position(sline)) != NULL_POSITION) 1931 break; 1932 for (; sline < sc_height && pos != NULL_POSITION; sline++) 1933 { 1934 pos = forw_line(pos, NULL, NULL); 1935 if (end_column > longest) 1936 longest = end_column; 1937 } 1938 sc_width = save_width; 1939 if (longest < sc_width) 1940 return 0; 1941 return longest - sc_width; 1942 } 1943 1944 /* 1945 * Get the color_map index associated with a given attribute. 1946 */ 1947 static int lookup_color_index(int attr) 1948 { 1949 int cx; 1950 for (cx = 0; cx < countof(color_map); cx++) 1951 if (color_map[cx].attr == attr) 1952 return cx; 1953 return -1; 1954 } 1955 1956 static int color_index(int attr) 1957 { 1958 if (use_color && (attr & AT_COLOR)) 1959 return lookup_color_index(attr & AT_COLOR); 1960 if (attr & AT_UNDERLINE) 1961 return lookup_color_index(AT_UNDERLINE); 1962 if (attr & AT_BOLD) 1963 return lookup_color_index(AT_BOLD); 1964 if (attr & AT_BLINK) 1965 return lookup_color_index(AT_BLINK); 1966 if (attr & AT_STANDOUT) 1967 return lookup_color_index(AT_STANDOUT); 1968 return -1; 1969 } 1970 1971 /* 1972 * Set the color string to use for a given attribute. 1973 */ 1974 public int set_color_map(int attr, constant char *colorstr) 1975 { 1976 int cx = color_index(attr); 1977 if (cx < 0) 1978 return -1; 1979 if (strlen(colorstr)+1 > sizeof(color_map[cx].color)) 1980 return -1; 1981 if (*colorstr != '\0' && parse_color(colorstr, NULL, NULL, NULL) == CT_NULL) 1982 return -1; 1983 strcpy(color_map[cx].color, colorstr); 1984 return 0; 1985 } 1986 1987 /* 1988 * Get the color string to use for a given attribute. 1989 */ 1990 public constant char * get_color_map(int attr) 1991 { 1992 int cx = color_index(attr); 1993 if (cx < 0) 1994 return NULL; 1995 return color_map[cx].color; 1996 } 1997