1 /* 2 * Copyright (C) 1984-2021 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 int print; /* Index in buf of first printable char */ 30 int 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 int pfx_end; /* Number of chars in pfx */ 34 } linebuf; 35 36 struct xbuffer shifted_ansi; 37 struct xbuffer last_ansi; 38 39 public int size_linebuf = 0; /* Size of line buffer (and attr buffer) */ 40 static struct ansi_state *line_ansi = NULL; 41 static int cshift; /* Current left-shift of output line buffer */ 42 public int hshift; /* Desired left-shift of output line buffer */ 43 public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */ 44 public int ntabstops = 1; /* Number of tabstops */ 45 public int tabdefault = 8; /* Default repeated tabstops */ 46 public POSITION highest_hilite; /* Pos of last hilite in file found so far */ 47 48 static int end_column; /* Printable length, accounting for backspaces, etc. */ 49 static int right_curr; 50 static int right_column; 51 static int overstrike; /* Next char should overstrike previous char */ 52 static int last_overstrike = AT_NORMAL; 53 static int is_null_line; /* There is no current line */ 54 static LWCHAR pendc; 55 static POSITION pendpos; 56 static char *end_ansi_chars; 57 static char *mid_ansi_chars; 58 static int in_hilite; 59 60 static int attr_swidth LESSPARAMS ((int a)); 61 static int attr_ewidth LESSPARAMS ((int a)); 62 static int do_append LESSPARAMS ((LWCHAR ch, char *rep, POSITION pos)); 63 64 extern int sigs; 65 extern int bs_mode; 66 extern int linenums; 67 extern int ctldisp; 68 extern int twiddle; 69 extern int binattr; 70 extern int status_col; 71 extern int status_col_width; 72 extern int linenum_width; 73 extern int auto_wrap, ignaw; 74 extern int bo_s_width, bo_e_width; 75 extern int ul_s_width, ul_e_width; 76 extern int bl_s_width, bl_e_width; 77 extern int so_s_width, so_e_width; 78 extern int sc_width, sc_height; 79 extern int utf_mode; 80 extern POSITION start_attnpos; 81 extern POSITION end_attnpos; 82 extern char rscroll_char; 83 extern int rscroll_attr; 84 extern int use_color; 85 86 static char mbc_buf[MAX_UTF_CHAR_LEN]; 87 static int mbc_buf_len = 0; 88 static int mbc_buf_index = 0; 89 static POSITION mbc_pos; 90 91 /* Configurable color map */ 92 static char color_map[AT_NUM_COLORS][12] = { 93 "Wm", /* AT_COLOR_ATTN */ 94 "kR", /* AT_COLOR_BIN */ 95 "kR", /* AT_COLOR_CTRL */ 96 "kY", /* AT_COLOR_ERROR */ 97 "c", /* AT_COLOR_LINENUM */ 98 "Wb", /* AT_COLOR_MARK */ 99 "kC", /* AT_COLOR_PROMPT */ 100 "kc", /* AT_COLOR_RSCROLL */ 101 "kG", /* AT_COLOR_SEARCH */ 102 "", /* AT_UNDERLINE */ 103 "", /* AT_BOLD */ 104 "", /* AT_BLINK */ 105 "", /* AT_STANDOUT */ 106 }; 107 108 /* State while processing an ANSI escape sequence */ 109 struct ansi_state { 110 int hindex; /* Index into hyperlink prefix */ 111 int hlink; /* Processing hyperlink address? */ 112 int prev_esc; /* Prev char was ESC (to detect ESC-\ seq) */ 113 }; 114 115 /* 116 * Initialize from environment variables. 117 */ 118 public void 119 init_line(VOID_PARAM) 120 { 121 end_ansi_chars = lgetenv("LESSANSIENDCHARS"); 122 if (isnullenv(end_ansi_chars)) 123 end_ansi_chars = "m"; 124 125 mid_ansi_chars = lgetenv("LESSANSIMIDCHARS"); 126 if (isnullenv(mid_ansi_chars)) 127 mid_ansi_chars = "0123456789:;[?!\"'#%()*+ "; 128 129 linebuf.buf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); 130 linebuf.attr = (int *) ecalloc(LINEBUF_SIZE, sizeof(int)); 131 size_linebuf = LINEBUF_SIZE; 132 xbuf_init(&shifted_ansi); 133 xbuf_init(&last_ansi); 134 } 135 136 /* 137 * Expand the line buffer. 138 */ 139 static int 140 expand_linebuf(VOID_PARAM) 141 { 142 /* Double the size of the line buffer. */ 143 int new_size = size_linebuf * 2; 144 145 /* Just realloc to expand the buffer, if we can. */ 146 #if HAVE_REALLOC 147 char *new_buf = (char *) realloc(linebuf.buf, new_size); 148 int *new_attr = (int *) realloc(linebuf.attr, new_size*sizeof(int)); 149 #else 150 char *new_buf = (char *) calloc(new_size, sizeof(char)); 151 int *new_attr = (int *) calloc(new_size, sizeof(int)); 152 #endif 153 if (new_buf == NULL || new_attr == NULL) 154 { 155 if (new_attr != NULL) 156 free(new_attr); 157 if (new_buf != NULL) 158 free(new_buf); 159 return 1; 160 } 161 #if !HAVE_REALLOC 162 /* 163 * We just calloc'd the buffers; copy the old contents. 164 */ 165 memcpy(new_buf, linebuf.buf, size_linebuf * sizeof(char)); 166 memcpy(new_attr, linebuf.attr, size_linebuf * sizeof(int)); 167 free(linebuf.attr); 168 free(linebuf.buf); 169 #endif 170 linebuf.buf = new_buf; 171 linebuf.attr = new_attr; 172 size_linebuf = new_size; 173 return 0; 174 } 175 176 /* 177 * Is a character ASCII? 178 */ 179 public int 180 is_ascii_char(ch) 181 LWCHAR ch; 182 { 183 return (ch <= 0x7F); 184 } 185 186 /* 187 */ 188 static void 189 inc_end_column(w) 190 int w; 191 { 192 if (end_column > right_column && w > 0) 193 { 194 right_column = end_column; 195 right_curr = linebuf.end; 196 } 197 end_column += w; 198 } 199 200 /* 201 * Rewind the line buffer. 202 */ 203 public void 204 prewind(VOID_PARAM) 205 { 206 linebuf.print = 6; /* big enough for longest UTF-8 sequence */ 207 linebuf.pfx_end = 0; 208 for (linebuf.end = 0; linebuf.end < linebuf.print; linebuf.end++) 209 { 210 linebuf.buf[linebuf.end] = '\0'; 211 linebuf.attr[linebuf.end] = 0; 212 } 213 214 end_column = 0; 215 right_curr = 0; 216 right_column = 0; 217 cshift = 0; 218 overstrike = 0; 219 last_overstrike = AT_NORMAL; 220 mbc_buf_len = 0; 221 is_null_line = 0; 222 pendc = '\0'; 223 in_hilite = 0; 224 xbuf_reset(&shifted_ansi); 225 xbuf_reset(&last_ansi); 226 } 227 228 /* 229 * Set a character in the line buffer. 230 */ 231 static void 232 set_linebuf(n, ch, attr) 233 int n; 234 char ch; 235 int attr; 236 { 237 linebuf.buf[n] = ch; 238 linebuf.attr[n] = attr; 239 } 240 241 /* 242 * Append a character to the line buffer. 243 */ 244 static void 245 add_linebuf(ch, attr, w) 246 char ch; 247 int attr; 248 int w; 249 { 250 set_linebuf(linebuf.end++, ch, attr); 251 inc_end_column(w); 252 } 253 254 /* 255 * Set a character in the line prefix buffer. 256 */ 257 static void 258 set_pfx(n, ch, attr) 259 int n; 260 char ch; 261 int attr; 262 { 263 linebuf.pfx[n] = ch; 264 linebuf.pfx_attr[n] = attr; 265 } 266 267 /* 268 * Append a character to the line prefix buffer. 269 */ 270 static void 271 add_pfx(ch, attr) 272 char ch; 273 int attr; 274 { 275 set_pfx(linebuf.pfx_end++, ch, attr); 276 } 277 278 /* 279 * Insert the status column and line number into the line buffer. 280 */ 281 public void 282 plinestart(pos) 283 POSITION pos; 284 { 285 LINENUM linenum = 0; 286 int i; 287 288 if (linenums == OPT_ONPLUS) 289 { 290 /* 291 * Get the line number and put it in the current line. 292 * {{ Note: since find_linenum calls forw_raw_line, 293 * it may seek in the input file, requiring the caller 294 * of plinestart to re-seek if necessary. }} 295 * {{ Since forw_raw_line modifies linebuf, we must 296 * do this first, before storing anything in linebuf. }} 297 */ 298 linenum = find_linenum(pos); 299 } 300 301 /* 302 * Display a status column if the -J option is set. 303 */ 304 if (status_col) 305 { 306 int a = AT_NORMAL; 307 char c = posmark(pos); 308 if (c != 0) 309 a |= AT_HILITE|AT_COLOR_MARK; 310 else 311 { 312 c = ' '; 313 if (start_attnpos != NULL_POSITION && 314 pos >= start_attnpos && pos <= end_attnpos) 315 a |= AT_HILITE|AT_COLOR_ATTN; 316 } 317 add_pfx(c, a); /* column 0: status */ 318 while (linebuf.pfx_end < status_col_width) 319 add_pfx(' ', AT_NORMAL); 320 } 321 322 /* 323 * Display the line number at the start of each line 324 * if the -N option is set. 325 */ 326 if (linenums == OPT_ONPLUS) 327 { 328 char buf[INT_STRLEN_BOUND(linenum) + 2]; 329 int len; 330 331 linenumtoa(linenum, buf); 332 len = (int) strlen(buf); 333 for (i = 0; i < linenum_width - len; i++) 334 add_pfx(' ', AT_NORMAL); 335 for (i = 0; i < len; i++) 336 add_pfx(buf[i], AT_NORMAL|AT_COLOR_LINENUM); 337 add_pfx(' ', AT_NORMAL); 338 } 339 end_column = linebuf.pfx_end; 340 } 341 342 /* 343 * Return the width of the line prefix (status column and line number). 344 * {{ Actual line number can be wider than linenum_width. }} 345 */ 346 public int 347 line_pfx_width(VOID_PARAM) 348 { 349 int width = 0; 350 if (status_col) 351 width += status_col_width; 352 if (linenums == OPT_ONPLUS) 353 width += linenum_width + 1; 354 return width; 355 } 356 357 /* 358 * Shift line left so that the last char is just to the left 359 * of the first visible column. 360 */ 361 public void 362 pshift_all(VOID_PARAM) 363 { 364 int i; 365 for (i = linebuf.print; i < linebuf.end; i++) 366 if (linebuf.attr[i] == AT_ANSI) 367 xbuf_add(&shifted_ansi, linebuf.buf[i]); 368 linebuf.end = linebuf.print; 369 end_column = linebuf.pfx_end; 370 } 371 372 /* 373 * Return the printing width of the start (enter) sequence 374 * for a given character attribute. 375 */ 376 static int 377 attr_swidth(a) 378 int a; 379 { 380 int w = 0; 381 382 a = apply_at_specials(a); 383 384 if (a & AT_UNDERLINE) 385 w += ul_s_width; 386 if (a & AT_BOLD) 387 w += bo_s_width; 388 if (a & AT_BLINK) 389 w += bl_s_width; 390 if (a & AT_STANDOUT) 391 w += so_s_width; 392 393 return w; 394 } 395 396 /* 397 * Return the printing width of the end (exit) sequence 398 * for a given character attribute. 399 */ 400 static int 401 attr_ewidth(a) 402 int a; 403 { 404 int w = 0; 405 406 a = apply_at_specials(a); 407 408 if (a & AT_UNDERLINE) 409 w += ul_e_width; 410 if (a & AT_BOLD) 411 w += bo_e_width; 412 if (a & AT_BLINK) 413 w += bl_e_width; 414 if (a & AT_STANDOUT) 415 w += so_e_width; 416 417 return w; 418 } 419 420 /* 421 * Return the printing width of a given character and attribute, 422 * if the character were added after prev_ch. 423 * Adding a character with a given attribute may cause an enter or exit 424 * attribute sequence to be inserted, so this must be taken into account. 425 */ 426 public int 427 pwidth(ch, a, prev_ch, prev_a) 428 LWCHAR ch; 429 int a; 430 LWCHAR prev_ch; 431 int prev_a; 432 { 433 int w; 434 435 if (ch == '\b') 436 { 437 /* 438 * Backspace moves backwards one or two positions. 439 */ 440 if (prev_a & (AT_ANSI|AT_BINARY)) 441 return strlen(prchar('\b')); 442 return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1; 443 } 444 445 if (!utf_mode || is_ascii_char(ch)) 446 { 447 if (control_char((char)ch)) 448 { 449 /* 450 * Control characters do unpredictable things, 451 * so we don't even try to guess; say it doesn't move. 452 * This can only happen if the -r flag is in effect. 453 */ 454 return (0); 455 } 456 } else 457 { 458 if (is_composing_char(ch) || is_combining_char(prev_ch, ch)) 459 { 460 /* 461 * Composing and combining chars take up no space. 462 * 463 * Some terminals, upon failure to compose a 464 * composing character with the character(s) that 465 * precede(s) it will actually take up one end_column 466 * for the composing character; there isn't much 467 * we could do short of testing the (complex) 468 * composition process ourselves and printing 469 * a binary representation when it fails. 470 */ 471 return (0); 472 } 473 } 474 475 /* 476 * Other characters take one or two columns, 477 * plus the width of any attribute enter/exit sequence. 478 */ 479 w = 1; 480 if (is_wide_char(ch)) 481 w++; 482 if (linebuf.end > 0 && !is_at_equiv(linebuf.attr[linebuf.end-1], a)) 483 w += attr_ewidth(linebuf.attr[linebuf.end-1]); 484 if (apply_at_specials(a) != AT_NORMAL && 485 (linebuf.end == 0 || !is_at_equiv(linebuf.attr[linebuf.end-1], a))) 486 w += attr_swidth(a); 487 return (w); 488 } 489 490 /* 491 * Delete to the previous base character in the line buffer. 492 */ 493 static int 494 backc(VOID_PARAM) 495 { 496 LWCHAR ch; 497 char *p; 498 499 if (linebuf.end == 0) 500 return (0); 501 p = &linebuf.buf[linebuf.end]; 502 ch = step_char(&p, -1, linebuf.buf); 503 /* Skip back to the next nonzero-width char. */ 504 while (p > linebuf.buf) 505 { 506 LWCHAR prev_ch; 507 int width; 508 linebuf.end = (int) (p - linebuf.buf); 509 prev_ch = step_char(&p, -1, linebuf.buf); 510 width = pwidth(ch, linebuf.attr[linebuf.end], prev_ch, linebuf.attr[linebuf.end-1]); 511 end_column -= width; 512 /* {{ right_column? }} */ 513 if (width > 0) 514 break; 515 ch = prev_ch; 516 } 517 return (1); 518 } 519 520 /* 521 * Is a character the end of an ANSI escape sequence? 522 */ 523 public int 524 is_ansi_end(ch) 525 LWCHAR ch; 526 { 527 if (!is_ascii_char(ch)) 528 return (0); 529 return (strchr(end_ansi_chars, (char) ch) != NULL); 530 } 531 532 /* 533 * Can a char appear in an ANSI escape sequence, before the end char? 534 */ 535 public int 536 is_ansi_middle(ch) 537 LWCHAR ch; 538 { 539 if (!is_ascii_char(ch)) 540 return (0); 541 if (is_ansi_end(ch)) 542 return (0); 543 return (strchr(mid_ansi_chars, (char) ch) != NULL); 544 } 545 546 /* 547 * Skip past an ANSI escape sequence. 548 * pp is initially positioned just after the CSI_START char. 549 */ 550 public void 551 skip_ansi(pansi, pp, limit) 552 struct ansi_state *pansi; 553 char **pp; 554 constant char *limit; 555 { 556 LWCHAR c; 557 do { 558 c = step_char(pp, +1, limit); 559 } while (*pp < limit && ansi_step(pansi, c) == ANSI_MID); 560 /* Note that we discard final char, for which is_ansi_end is true. */ 561 } 562 563 /* 564 * Determine if a character starts an ANSI escape sequence. 565 * If so, return an ansi_state struct; otherwise return NULL. 566 */ 567 public struct ansi_state * 568 ansi_start(ch) 569 LWCHAR ch; 570 { 571 struct ansi_state *pansi; 572 573 if (!IS_CSI_START(ch)) 574 return NULL; 575 pansi = ecalloc(1, sizeof(struct ansi_state)); 576 pansi->hindex = 0; 577 pansi->hlink = 0; 578 pansi->prev_esc = 0; 579 return pansi; 580 } 581 582 /* 583 * Determine whether the next char in an ANSI escape sequence 584 * ends the sequence. 585 */ 586 public int 587 ansi_step(pansi, ch) 588 struct ansi_state *pansi; 589 LWCHAR ch; 590 { 591 if (pansi->hlink) 592 { 593 /* Hyperlink ends with \7 or ESC-backslash. */ 594 if (ch == '\7') 595 return ANSI_END; 596 if (pansi->prev_esc && ch == '\\') 597 return ANSI_END; 598 pansi->prev_esc = (ch == ESC); 599 return ANSI_MID; 600 } 601 if (pansi->hindex >= 0) 602 { 603 static char hlink_prefix[] = ESCS "]8;"; 604 if (ch == hlink_prefix[pansi->hindex] || 605 (pansi->hindex == 0 && IS_CSI_START(ch))) 606 { 607 pansi->hindex++; 608 if (hlink_prefix[pansi->hindex] == '\0') 609 pansi->hlink = 1; /* now processing hyperlink addr */ 610 return ANSI_MID; 611 } 612 pansi->hindex = -1; /* not a hyperlink */ 613 } 614 /* Check for SGR sequences */ 615 if (is_ansi_middle(ch)) 616 return ANSI_MID; 617 if (is_ansi_end(ch)) 618 return ANSI_END; 619 return ANSI_ERR; 620 } 621 622 /* 623 * Free an ansi_state structure. 624 */ 625 public void 626 ansi_done(pansi) 627 struct ansi_state *pansi; 628 { 629 free(pansi); 630 } 631 632 /* 633 * Append a character and attribute to the line buffer. 634 */ 635 #define STORE_CHAR(ch,a,rep,pos) \ 636 do { \ 637 if (store_char((ch),(a),(rep),(pos))) return (1); \ 638 } while (0) 639 640 static int 641 store_char(ch, a, rep, pos) 642 LWCHAR ch; 643 int a; 644 char *rep; 645 POSITION pos; 646 { 647 int w; 648 int i; 649 int replen; 650 char cs; 651 652 i = (a & (AT_UNDERLINE|AT_BOLD)); 653 if (i != AT_NORMAL) 654 last_overstrike = i; 655 656 #if HILITE_SEARCH 657 { 658 int matches; 659 int resend_last = 0; 660 int hl_attr = is_hilited_attr(pos, pos+1, 0, &matches); 661 if (hl_attr) 662 { 663 /* 664 * This character should be highlighted. 665 * Override the attribute passed in. 666 */ 667 if (a != AT_ANSI) 668 { 669 if (highest_hilite != NULL_POSITION && pos > highest_hilite) 670 highest_hilite = pos; 671 a |= hl_attr; 672 } 673 in_hilite = 1; 674 } else 675 { 676 if (in_hilite) 677 { 678 /* 679 * This is the first non-hilited char after a hilite. 680 * Resend the last ANSI seq to restore color. 681 */ 682 resend_last = 1; 683 } 684 in_hilite = 0; 685 } 686 if (resend_last) 687 { 688 for (i = 0; i < last_ansi.end; i++) 689 STORE_CHAR(last_ansi.data[i], AT_ANSI, NULL, pos); 690 } 691 } 692 #endif 693 694 if (a == AT_ANSI) { 695 w = 0; 696 } else { 697 char *p = &linebuf.buf[linebuf.end]; 698 LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0; 699 int prev_a = (linebuf.end > 0) ? linebuf.attr[linebuf.end-1] : 0; 700 w = pwidth(ch, a, prev_ch, prev_a); 701 } 702 703 if (ctldisp != OPT_ON && end_column - cshift + w + attr_ewidth(a) > sc_width) 704 /* 705 * Won't fit on screen. 706 */ 707 return (1); 708 709 if (rep == NULL) 710 { 711 cs = (char) ch; 712 rep = &cs; 713 replen = 1; 714 } else 715 { 716 replen = utf_len(rep[0]); 717 } 718 if (linebuf.end + replen >= size_linebuf-6) 719 { 720 /* 721 * Won't fit in line buffer. 722 * Try to expand it. 723 */ 724 if (expand_linebuf()) 725 return (1); 726 } 727 728 if (cshift == hshift && shifted_ansi.end > 0) 729 { 730 /* Copy shifted ANSI sequences to beginning of line. */ 731 for (i = 0; i < shifted_ansi.end; i++) 732 add_linebuf(shifted_ansi.data[i], AT_ANSI, 0); 733 xbuf_reset(&shifted_ansi); 734 } 735 /* Add the char to the buf, even if we will left-shift it next. */ 736 inc_end_column(w); 737 for (i = 0; i < replen; i++) 738 add_linebuf(*rep++, a, 0); 739 740 if (cshift < hshift) 741 { 742 /* We haven't left-shifted enough yet. */ 743 if (a == AT_ANSI) 744 xbuf_add(&shifted_ansi, ch); /* Save ANSI attributes */ 745 if (linebuf.end > linebuf.print) 746 { 747 /* Shift left enough to put last byte of this char at print-1. */ 748 int i; 749 for (i = 0; i < linebuf.print; i++) 750 { 751 linebuf.buf[i] = linebuf.buf[i+replen]; 752 linebuf.attr[i] = linebuf.attr[i+replen]; 753 } 754 linebuf.end -= replen; 755 cshift += w; 756 /* 757 * If the char we just left-shifted was double width, 758 * the 2 spaces we shifted may be too much. 759 * Represent the "half char" at start of line with a highlighted space. 760 */ 761 while (cshift > hshift) 762 { 763 add_linebuf(' ', rscroll_attr, 0); 764 cshift--; 765 } 766 } 767 } 768 return (0); 769 } 770 771 /* 772 * Append a tab to the line buffer. 773 * Store spaces to represent the tab. 774 */ 775 #define STORE_TAB(a,pos) \ 776 do { if (store_tab((a),(pos))) return (1); } while (0) 777 778 static int 779 store_tab(attr, pos) 780 int attr; 781 POSITION pos; 782 { 783 int to_tab = end_column - linebuf.pfx_end; 784 785 if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1]) 786 to_tab = tabdefault - 787 ((to_tab - tabstops[ntabstops-1]) % tabdefault); 788 else 789 { 790 int i; 791 for (i = ntabstops - 2; i >= 0; i--) 792 if (to_tab >= tabstops[i]) 793 break; 794 to_tab = tabstops[i+1] - to_tab; 795 } 796 797 do { 798 STORE_CHAR(' ', attr, " ", pos); 799 } while (--to_tab > 0); 800 return 0; 801 } 802 803 #define STORE_PRCHAR(c, pos) \ 804 do { if (store_prchar((c), (pos))) return 1; } while (0) 805 806 static int 807 store_prchar(c, pos) 808 LWCHAR c; 809 POSITION pos; 810 { 811 char *s; 812 813 /* 814 * Convert to printable representation. 815 */ 816 s = prchar(c); 817 for ( ; *s != 0; s++) 818 STORE_CHAR(*s, AT_BINARY|AT_COLOR_CTRL, NULL, pos); 819 return 0; 820 } 821 822 static int 823 flush_mbc_buf(pos) 824 POSITION pos; 825 { 826 int i; 827 828 for (i = 0; i < mbc_buf_index; i++) 829 if (store_prchar(mbc_buf[i], pos)) 830 return mbc_buf_index - i; 831 return 0; 832 } 833 834 /* 835 * Append a character to the line buffer. 836 * Expand tabs into spaces, handle underlining, boldfacing, etc. 837 * Returns 0 if ok, 1 if couldn't fit in buffer. 838 */ 839 public int 840 pappend(c, pos) 841 int c; 842 POSITION pos; 843 { 844 int r; 845 846 if (pendc) 847 { 848 if (c == '\r' && pendc == '\r') 849 return (0); 850 if (do_append(pendc, NULL, pendpos)) 851 /* 852 * Oops. We've probably lost the char which 853 * was in pendc, since caller won't back up. 854 */ 855 return (1); 856 pendc = '\0'; 857 } 858 859 if (c == '\r' && bs_mode == BS_SPECIAL) 860 { 861 if (mbc_buf_len > 0) /* utf_mode must be on. */ 862 { 863 /* Flush incomplete (truncated) sequence. */ 864 r = flush_mbc_buf(mbc_pos); 865 mbc_buf_index = r + 1; 866 mbc_buf_len = 0; 867 if (r) 868 return (mbc_buf_index); 869 } 870 871 /* 872 * Don't put the CR into the buffer until we see 873 * the next char. If the next char is a newline, 874 * discard the CR. 875 */ 876 pendc = c; 877 pendpos = pos; 878 return (0); 879 } 880 881 if (!utf_mode) 882 { 883 r = do_append(c, NULL, pos); 884 } else 885 { 886 /* Perform strict validation in all possible cases. */ 887 if (mbc_buf_len == 0) 888 { 889 retry: 890 mbc_buf_index = 1; 891 *mbc_buf = c; 892 if (IS_ASCII_OCTET(c)) 893 r = do_append(c, NULL, pos); 894 else if (IS_UTF8_LEAD(c)) 895 { 896 mbc_buf_len = utf_len(c); 897 mbc_pos = pos; 898 return (0); 899 } else 900 /* UTF8_INVALID or stray UTF8_TRAIL */ 901 r = flush_mbc_buf(pos); 902 } else if (IS_UTF8_TRAIL(c)) 903 { 904 mbc_buf[mbc_buf_index++] = c; 905 if (mbc_buf_index < mbc_buf_len) 906 return (0); 907 if (is_utf8_well_formed(mbc_buf, mbc_buf_index)) 908 r = do_append(get_wchar(mbc_buf), mbc_buf, mbc_pos); 909 else 910 /* Complete, but not shortest form, sequence. */ 911 mbc_buf_index = r = flush_mbc_buf(mbc_pos); 912 mbc_buf_len = 0; 913 } else 914 { 915 /* Flush incomplete (truncated) sequence. */ 916 r = flush_mbc_buf(mbc_pos); 917 mbc_buf_index = r + 1; 918 mbc_buf_len = 0; 919 /* Handle new char. */ 920 if (!r) 921 goto retry; 922 } 923 } 924 if (r) 925 { 926 /* How many chars should caller back up? */ 927 r = (!utf_mode) ? 1 : mbc_buf_index; 928 } 929 return (r); 930 } 931 932 static int 933 store_control_char(ch, rep, pos) 934 LWCHAR ch; 935 char *rep; 936 POSITION pos; 937 { 938 if (ctldisp == OPT_ON) 939 { 940 /* Output the character itself. */ 941 STORE_CHAR(ch, AT_NORMAL, rep, pos); 942 } else 943 { 944 /* Output a printable representation of the character. */ 945 STORE_PRCHAR((char) ch, pos); 946 } 947 return (0); 948 } 949 950 static int 951 store_ansi(ch, rep, pos) 952 LWCHAR ch; 953 char *rep; 954 POSITION pos; 955 { 956 switch (ansi_step(line_ansi, ch)) 957 { 958 case ANSI_MID: 959 if (!in_hilite) 960 STORE_CHAR(ch, AT_ANSI, rep, pos); 961 break; 962 case ANSI_END: 963 if (!in_hilite) 964 STORE_CHAR(ch, AT_ANSI, rep, pos); 965 ansi_done(line_ansi); 966 line_ansi = NULL; 967 break; 968 case ANSI_ERR: { 969 /* Remove whole unrecognized sequence. */ 970 char *start = (cshift < hshift) ? shifted_ansi.data : linebuf.buf; 971 int *end = (cshift < hshift) ? &shifted_ansi.end : &linebuf.end; 972 char *p = start + *end; 973 LWCHAR bch; 974 do { 975 bch = step_char(&p, -1, start); 976 } while (p > start && !IS_CSI_START(bch)); 977 *end = (int) (p - start); 978 ansi_done(line_ansi); 979 line_ansi = NULL; 980 break; } 981 } 982 return (0); 983 } 984 985 static int 986 store_bs(ch, rep, pos) 987 LWCHAR ch; 988 char *rep; 989 POSITION pos; 990 { 991 if (bs_mode == BS_CONTROL) 992 return store_control_char(ch, rep, pos); 993 if (linebuf.end > 0 && 994 ((linebuf.end <= linebuf.print && linebuf.buf[linebuf.end-1] == '\0') || 995 (linebuf.end > 0 && linebuf.attr[linebuf.end - 1] & (AT_ANSI|AT_BINARY)))) 996 STORE_PRCHAR('\b', pos); 997 else if (bs_mode == BS_NORMAL) 998 STORE_CHAR(ch, AT_NORMAL, NULL, pos); 999 else if (bs_mode == BS_SPECIAL) 1000 overstrike = backc(); 1001 return 0; 1002 } 1003 1004 static int 1005 do_append(ch, rep, pos) 1006 LWCHAR ch; 1007 char *rep; 1008 POSITION pos; 1009 { 1010 int a = AT_NORMAL; 1011 1012 if (ctldisp == OPT_ONPLUS && line_ansi == NULL) 1013 { 1014 line_ansi = ansi_start(ch); 1015 if (line_ansi != NULL) 1016 xbuf_reset(&last_ansi); 1017 } 1018 1019 if (line_ansi != NULL) 1020 { 1021 xbuf_add(&last_ansi, ch); 1022 return store_ansi(ch, rep, pos); 1023 } 1024 1025 if (ch == '\b') 1026 return store_bs(ch, rep, pos); 1027 1028 if (overstrike > 0) 1029 { 1030 /* 1031 * Overstrike the character at the current position 1032 * in the line buffer. This will cause either 1033 * underline (if a "_" is overstruck), 1034 * bold (if an identical character is overstruck), 1035 * or just replacing the character in the buffer. 1036 */ 1037 LWCHAR prev_ch; 1038 overstrike = utf_mode ? -1 : 0; 1039 if (utf_mode) 1040 { 1041 /* To be correct, this must be a base character. */ 1042 prev_ch = get_wchar(&linebuf.buf[linebuf.end]); 1043 } else 1044 { 1045 prev_ch = (unsigned char) linebuf.buf[linebuf.end]; 1046 } 1047 a = linebuf.attr[linebuf.end]; 1048 if (ch == prev_ch) 1049 { 1050 /* 1051 * Overstriking a char with itself means make it bold. 1052 * But overstriking an underscore with itself is 1053 * ambiguous. It could mean make it bold, or 1054 * it could mean make it underlined. 1055 * Use the previous overstrike to resolve it. 1056 */ 1057 if (ch == '_') 1058 { 1059 if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL) 1060 a |= (AT_BOLD|AT_UNDERLINE); 1061 else if (last_overstrike != AT_NORMAL) 1062 a |= last_overstrike; 1063 else 1064 a |= AT_BOLD; 1065 } else 1066 a |= AT_BOLD; 1067 } else if (ch == '_') 1068 { 1069 a |= AT_UNDERLINE; 1070 ch = prev_ch; 1071 rep = &linebuf.buf[linebuf.end]; 1072 } else if (prev_ch == '_') 1073 { 1074 a |= AT_UNDERLINE; 1075 } 1076 /* Else we replace prev_ch, but we keep its attributes. */ 1077 } else if (overstrike < 0) 1078 { 1079 if ( is_composing_char(ch) 1080 || is_combining_char(get_wchar(&linebuf.buf[linebuf.end]), ch)) 1081 /* Continuation of the same overstrike. */ 1082 a = last_overstrike; 1083 else 1084 overstrike = 0; 1085 } 1086 1087 if (ch == '\t') 1088 { 1089 /* 1090 * Expand a tab into spaces. 1091 */ 1092 switch (bs_mode) 1093 { 1094 case BS_CONTROL: 1095 return store_control_char(ch, rep, pos); 1096 case BS_NORMAL: 1097 case BS_SPECIAL: 1098 STORE_TAB(a, pos); 1099 break; 1100 } 1101 return (0); 1102 } 1103 if ((!utf_mode || is_ascii_char(ch)) && control_char((char)ch)) 1104 { 1105 return store_control_char(ch, rep, pos); 1106 } else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch)) 1107 { 1108 char *s = prutfchar(ch); 1109 for ( ; *s != 0; s++) 1110 STORE_CHAR(*s, AT_BINARY, NULL, pos); 1111 } else 1112 { 1113 STORE_CHAR(ch, a, rep, pos); 1114 } 1115 return (0); 1116 } 1117 1118 /* 1119 * 1120 */ 1121 public int 1122 pflushmbc(VOID_PARAM) 1123 { 1124 int r = 0; 1125 1126 if (mbc_buf_len > 0) 1127 { 1128 /* Flush incomplete (truncated) sequence. */ 1129 r = flush_mbc_buf(mbc_pos); 1130 mbc_buf_len = 0; 1131 } 1132 return r; 1133 } 1134 1135 /* 1136 * Switch to normal attribute at end of line. 1137 */ 1138 static void 1139 add_attr_normal(VOID_PARAM) 1140 { 1141 char *p = "\033[m"; 1142 1143 if (ctldisp != OPT_ONPLUS || !is_ansi_end('m')) 1144 return; 1145 for ( ; *p != '\0'; p++) 1146 add_linebuf(*p, AT_ANSI, 0); 1147 } 1148 1149 /* 1150 * Terminate the line in the line buffer. 1151 */ 1152 public void 1153 pdone(endline, chopped, forw) 1154 int endline; 1155 int chopped; 1156 int forw; 1157 { 1158 (void) pflushmbc(); 1159 1160 if (pendc && (pendc != '\r' || !endline)) 1161 /* 1162 * If we had a pending character, put it in the buffer. 1163 * But discard a pending CR if we are at end of line 1164 * (that is, discard the CR in a CR/LF sequence). 1165 */ 1166 (void) do_append(pendc, NULL, pendpos); 1167 1168 if (chopped && rscroll_char) 1169 { 1170 /* 1171 * Display the right scrolling char. 1172 * If we've already filled the rightmost screen char 1173 * (in the buffer), overwrite it. 1174 */ 1175 if (end_column >= sc_width + cshift) 1176 { 1177 /* We've already written in the rightmost char. */ 1178 end_column = right_column; 1179 linebuf.end = right_curr; 1180 } 1181 add_attr_normal(); 1182 while (end_column < sc_width-1 + cshift) 1183 { 1184 /* 1185 * Space to last (rightmost) char on screen. 1186 * This may be necessary if the char we overwrote 1187 * was double-width. 1188 */ 1189 add_linebuf(' ', rscroll_attr, 1); 1190 } 1191 /* Print rscroll char. It must be single-width. */ 1192 add_linebuf(rscroll_char, rscroll_attr, 1); 1193 } else 1194 { 1195 add_attr_normal(); 1196 } 1197 1198 /* 1199 * Add a newline if necessary, 1200 * and append a '\0' to the end of the line. 1201 * We output a newline if we're not at the right edge of the screen, 1202 * or if the terminal doesn't auto wrap, 1203 * or if this is really the end of the line AND the terminal ignores 1204 * a newline at the right edge. 1205 * (In the last case we don't want to output a newline if the terminal 1206 * doesn't ignore it since that would produce an extra blank line. 1207 * But we do want to output a newline if the terminal ignores it in case 1208 * the next line is blank. In that case the single newline output for 1209 * that blank line would be ignored!) 1210 */ 1211 if (end_column < sc_width + cshift || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON) 1212 { 1213 add_linebuf('\n', AT_NORMAL, 0); 1214 } 1215 else if (ignaw && end_column >= sc_width + cshift && forw) 1216 { 1217 /* 1218 * Terminals with "ignaw" don't wrap until they *really* need 1219 * to, i.e. when the character *after* the last one to fit on a 1220 * line is output. But they are too hard to deal with when they 1221 * get in the state where a full screen width of characters 1222 * have been output but the cursor is sitting on the right edge 1223 * instead of at the start of the next line. 1224 * So we nudge them into wrapping by outputting a space 1225 * character plus a backspace. But do this only if moving 1226 * forward; if we're moving backward and drawing this line at 1227 * the top of the screen, the space would overwrite the first 1228 * char on the next line. We don't need to do this "nudge" 1229 * at the top of the screen anyway. 1230 */ 1231 add_linebuf(' ', AT_NORMAL, 1); 1232 add_linebuf('\b', AT_NORMAL, -1); 1233 } 1234 set_linebuf(linebuf.end, '\0', AT_NORMAL); 1235 } 1236 1237 /* 1238 * 1239 */ 1240 public void 1241 set_status_col(c, attr) 1242 int c; 1243 int attr; 1244 { 1245 set_pfx(0, c, attr); 1246 } 1247 1248 /* 1249 * Get a character from the current line. 1250 * Return the character as the function return value, 1251 * and the character attribute in *ap. 1252 */ 1253 public int 1254 gline(i, ap) 1255 int i; 1256 int *ap; 1257 { 1258 if (is_null_line) 1259 { 1260 /* 1261 * If there is no current line, we pretend the line is 1262 * either "~" or "", depending on the "twiddle" flag. 1263 */ 1264 if (twiddle) 1265 { 1266 if (i == 0) 1267 { 1268 *ap = AT_BOLD; 1269 return '~'; 1270 } 1271 --i; 1272 } 1273 /* Make sure we're back to AT_NORMAL before the '\n'. */ 1274 *ap = AT_NORMAL; 1275 return i ? '\0' : '\n'; 1276 } 1277 1278 if (i < linebuf.pfx_end) 1279 { 1280 *ap = linebuf.pfx_attr[i]; 1281 return linebuf.pfx[i]; 1282 } 1283 i += linebuf.print - linebuf.pfx_end; 1284 *ap = linebuf.attr[i]; 1285 return (linebuf.buf[i] & 0xFF); 1286 } 1287 1288 /* 1289 * Indicate that there is no current line. 1290 */ 1291 public void 1292 null_line(VOID_PARAM) 1293 { 1294 is_null_line = 1; 1295 cshift = 0; 1296 } 1297 1298 /* 1299 * Analogous to forw_line(), but deals with "raw lines": 1300 * lines which are not split for screen width. 1301 * {{ This is supposed to be more efficient than forw_line(). }} 1302 */ 1303 public POSITION 1304 forw_raw_line(curr_pos, linep, line_lenp) 1305 POSITION curr_pos; 1306 char **linep; 1307 int *line_lenp; 1308 { 1309 int n; 1310 int c; 1311 POSITION new_pos; 1312 1313 if (curr_pos == NULL_POSITION || ch_seek(curr_pos) || 1314 (c = ch_forw_get()) == EOI) 1315 return (NULL_POSITION); 1316 1317 n = 0; 1318 for (;;) 1319 { 1320 if (c == '\n' || c == EOI || ABORT_SIGS()) 1321 { 1322 new_pos = ch_tell(); 1323 break; 1324 } 1325 if (n >= size_linebuf-1) 1326 { 1327 if (expand_linebuf()) 1328 { 1329 /* 1330 * Overflowed the input buffer. 1331 * Pretend the line ended here. 1332 */ 1333 new_pos = ch_tell() - 1; 1334 break; 1335 } 1336 } 1337 linebuf.buf[n++] = c; 1338 c = ch_forw_get(); 1339 } 1340 linebuf.buf[n] = '\0'; 1341 if (linep != NULL) 1342 *linep = linebuf.buf; 1343 if (line_lenp != NULL) 1344 *line_lenp = n; 1345 return (new_pos); 1346 } 1347 1348 /* 1349 * Analogous to back_line(), but deals with "raw lines". 1350 * {{ This is supposed to be more efficient than back_line(). }} 1351 */ 1352 public POSITION 1353 back_raw_line(curr_pos, linep, line_lenp) 1354 POSITION curr_pos; 1355 char **linep; 1356 int *line_lenp; 1357 { 1358 int n; 1359 int c; 1360 POSITION new_pos; 1361 1362 if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() || 1363 ch_seek(curr_pos-1)) 1364 return (NULL_POSITION); 1365 1366 n = size_linebuf; 1367 linebuf.buf[--n] = '\0'; 1368 for (;;) 1369 { 1370 c = ch_back_get(); 1371 if (c == '\n' || ABORT_SIGS()) 1372 { 1373 /* 1374 * This is the newline ending the previous line. 1375 * We have hit the beginning of the line. 1376 */ 1377 new_pos = ch_tell() + 1; 1378 break; 1379 } 1380 if (c == EOI) 1381 { 1382 /* 1383 * We have hit the beginning of the file. 1384 * This must be the first line in the file. 1385 * This must, of course, be the beginning of the line. 1386 */ 1387 new_pos = ch_zero(); 1388 break; 1389 } 1390 if (n <= 0) 1391 { 1392 int old_size_linebuf = size_linebuf; 1393 char *fm; 1394 char *to; 1395 if (expand_linebuf()) 1396 { 1397 /* 1398 * Overflowed the input buffer. 1399 * Pretend the line ended here. 1400 */ 1401 new_pos = ch_tell() + 1; 1402 break; 1403 } 1404 /* 1405 * Shift the data to the end of the new linebuf. 1406 */ 1407 for (fm = linebuf.buf + old_size_linebuf - 1, 1408 to = linebuf.buf + size_linebuf - 1; 1409 fm >= linebuf.buf; fm--, to--) 1410 *to = *fm; 1411 n = size_linebuf - old_size_linebuf; 1412 } 1413 linebuf.buf[--n] = c; 1414 } 1415 if (linep != NULL) 1416 *linep = &linebuf.buf[n]; 1417 if (line_lenp != NULL) 1418 *line_lenp = size_linebuf - 1 - n; 1419 return (new_pos); 1420 } 1421 1422 /* 1423 * Find the shift necessary to show the end of the longest displayed line. 1424 */ 1425 public int 1426 rrshift(VOID_PARAM) 1427 { 1428 POSITION pos; 1429 int save_width; 1430 int line; 1431 int longest = 0; 1432 1433 save_width = sc_width; 1434 sc_width = INT_MAX; 1435 pos = position(TOP); 1436 for (line = 0; line < sc_height && pos != NULL_POSITION; line++) 1437 { 1438 pos = forw_line(pos); 1439 if (end_column > longest) 1440 longest = end_column; 1441 } 1442 sc_width = save_width; 1443 if (longest < sc_width) 1444 return 0; 1445 return longest - sc_width; 1446 } 1447 1448 /* 1449 * Get the color_map index associated with a given attribute. 1450 */ 1451 static int 1452 color_index(attr) 1453 int attr; 1454 { 1455 if (use_color) 1456 { 1457 switch (attr & AT_COLOR) 1458 { 1459 case AT_COLOR_ATTN: return 0; 1460 case AT_COLOR_BIN: return 1; 1461 case AT_COLOR_CTRL: return 2; 1462 case AT_COLOR_ERROR: return 3; 1463 case AT_COLOR_LINENUM: return 4; 1464 case AT_COLOR_MARK: return 5; 1465 case AT_COLOR_PROMPT: return 6; 1466 case AT_COLOR_RSCROLL: return 7; 1467 case AT_COLOR_SEARCH: return 8; 1468 } 1469 } 1470 if (attr & AT_UNDERLINE) 1471 return 9; 1472 if (attr & AT_BOLD) 1473 return 10; 1474 if (attr & AT_BLINK) 1475 return 11; 1476 if (attr & AT_STANDOUT) 1477 return 12; 1478 return -1; 1479 } 1480 1481 /* 1482 * Set the color string to use for a given attribute. 1483 */ 1484 public int 1485 set_color_map(attr, colorstr) 1486 int attr; 1487 char *colorstr; 1488 { 1489 int cx = color_index(attr); 1490 if (cx < 0) 1491 return -1; 1492 if (strlen(colorstr)+1 > sizeof(color_map[cx])) 1493 return -1; 1494 if (*colorstr != '\0' && parse_color(colorstr, NULL, NULL) == CT_NULL) 1495 return -1; 1496 strcpy(color_map[cx], colorstr); 1497 return 0; 1498 } 1499 1500 /* 1501 * Get the color string to use for a given attribute. 1502 */ 1503 public char * 1504 get_color_map(attr) 1505 int attr; 1506 { 1507 int cx = color_index(attr); 1508 if (cx < 0) 1509 return NULL; 1510 return color_map[cx]; 1511 } 1512