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