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