1 /* 2 * Copyright (C) 1984-2012 Mark Nudelman 3 * Modified for use with illumos by Garrett D'Amore. 4 * Copyright 2014 Garrett D'Amore <garrett@damore.org> 5 * 6 * You may distribute under the terms of either the GNU General Public 7 * License or the Less License, as specified in the README file. 8 * 9 * For more information, see the README file. 10 */ 11 12 /* 13 * Routines to manipulate the "line buffer". 14 * The line buffer holds a line of output as it is being built 15 * in preparation for output to the screen. 16 */ 17 18 #include <wchar.h> 19 #include <wctype.h> 20 21 #include "charset.h" 22 #include "less.h" 23 24 static char *linebuf = NULL; /* Buffer which holds the current output line */ 25 static char *attr = NULL; /* Extension of linebuf to hold attributes */ 26 int size_linebuf = 0; /* Size of line buffer (and attr buffer) */ 27 28 static int cshift; /* Current left-shift of output line buffer */ 29 int hshift; /* Desired left-shift of output line buffer */ 30 int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */ 31 int ntabstops = 1; /* Number of tabstops */ 32 int tabdefault = 8; /* Default repeated tabstops */ 33 off_t highest_hilite; /* Pos of last hilite in file found so far */ 34 35 static int curr; /* Total number of bytes in linebuf */ 36 static int column; /* Display columns needed to show linebuf */ 37 static int overstrike; /* Next char should overstrike previous char */ 38 static int is_null_line; /* There is no current line */ 39 static int lmargin; /* Index in linebuf of start of content */ 40 static char pendc; 41 static off_t pendpos; 42 static char *end_ansi_chars; 43 static char *mid_ansi_chars; 44 45 static int attr_swidth(int); 46 static int attr_ewidth(int); 47 static int do_append(LWCHAR, char *, off_t); 48 49 extern volatile sig_atomic_t sigs; 50 extern int bs_mode; 51 extern int linenums; 52 extern int ctldisp; 53 extern int twiddle; 54 extern int binattr; 55 extern int status_col; 56 extern int auto_wrap, ignaw; 57 extern int bo_s_width, bo_e_width; 58 extern int ul_s_width, ul_e_width; 59 extern int bl_s_width, bl_e_width; 60 extern int so_s_width, so_e_width; 61 extern int sc_width, sc_height; 62 extern int utf_mode; 63 extern off_t start_attnpos; 64 extern off_t end_attnpos; 65 66 static char mbc_buf[MAX_UTF_CHAR_LEN]; 67 static int mbc_buf_index = 0; 68 static off_t mbc_pos; 69 70 /* 71 * Initialize from environment variables. 72 */ 73 void 74 init_line(void) 75 { 76 end_ansi_chars = lgetenv("LESSANSIENDCHARS"); 77 if (end_ansi_chars == NULL || *end_ansi_chars == '\0') 78 end_ansi_chars = "m"; 79 80 mid_ansi_chars = lgetenv("LESSANSIMIDCHARS"); 81 if (mid_ansi_chars == NULL || *mid_ansi_chars == '\0') 82 mid_ansi_chars = "0123456789;[?!\"'#%()*+ "; 83 84 linebuf = ecalloc(LINEBUF_SIZE, sizeof (char)); 85 attr = ecalloc(LINEBUF_SIZE, sizeof (char)); 86 size_linebuf = LINEBUF_SIZE; 87 } 88 89 /* 90 * Expand the line buffer. 91 */ 92 static int 93 expand_linebuf(void) 94 { 95 /* Double the size of the line buffer. */ 96 int new_size = size_linebuf * 2; 97 98 /* Just realloc to expand the buffer, if we can. */ 99 char *new_buf = recallocarray(linebuf, size_linebuf, new_size, 1); 100 char *new_attr = recallocarray(attr, size_linebuf, new_size, 1); 101 if (new_buf == NULL || new_attr == NULL) { 102 free(new_attr); 103 free(new_buf); 104 return (1); 105 } 106 linebuf = new_buf; 107 attr = new_attr; 108 size_linebuf = new_size; 109 return (0); 110 } 111 112 /* 113 * Is a character ASCII? 114 */ 115 static int 116 is_ascii_char(LWCHAR ch) 117 { 118 return (ch <= 0x7F); 119 } 120 121 /* 122 * Rewind the line buffer. 123 */ 124 void 125 prewind(void) 126 { 127 curr = 0; 128 column = 0; 129 cshift = 0; 130 overstrike = 0; 131 is_null_line = 0; 132 pendc = '\0'; 133 lmargin = 0; 134 if (status_col) 135 lmargin += 1; 136 } 137 138 /* 139 * Insert the line number (of the given position) into the line buffer. 140 */ 141 void 142 plinenum(off_t pos) 143 { 144 off_t linenum = 0; 145 int i; 146 147 if (linenums == OPT_ONPLUS) { 148 /* 149 * Get the line number and put it in the current line. 150 * {{ Note: since find_linenum calls forw_raw_line, 151 * it may seek in the input file, requiring the caller 152 * of plinenum to re-seek if necessary. }} 153 * {{ Since forw_raw_line modifies linebuf, we must 154 * do this first, before storing anything in linebuf. }} 155 */ 156 linenum = find_linenum(pos); 157 } 158 159 /* 160 * Display a status column if the -J option is set. 161 */ 162 if (status_col) { 163 linebuf[curr] = ' '; 164 if (start_attnpos != -1 && 165 pos >= start_attnpos && pos < end_attnpos) 166 attr[curr] = AT_NORMAL|AT_HILITE; 167 else 168 attr[curr] = AT_NORMAL; 169 curr++; 170 column++; 171 } 172 /* 173 * Display the line number at the start of each line 174 * if the -N option is set. 175 */ 176 if (linenums == OPT_ONPLUS) { 177 char buf[23]; 178 int n; 179 180 postoa(linenum, buf, sizeof(buf)); 181 n = strlen(buf); 182 if (n < MIN_LINENUM_WIDTH) 183 n = MIN_LINENUM_WIDTH; 184 snprintf(linebuf+curr, size_linebuf-curr, "%*s ", n, buf); 185 n++; /* One space after the line number. */ 186 for (i = 0; i < n; i++) 187 attr[curr+i] = AT_NORMAL; 188 curr += n; 189 column += n; 190 lmargin += n; 191 } 192 193 /* 194 * Append enough spaces to bring us to the lmargin. 195 */ 196 while (column < lmargin) { 197 linebuf[curr] = ' '; 198 attr[curr++] = AT_NORMAL; 199 column++; 200 } 201 } 202 203 /* 204 * Shift the input line left. 205 * Starting at lmargin, some bytes are discarded from the linebuf, 206 * until the number of display columns needed to show these bytes 207 * would exceed the argument. 208 */ 209 static void 210 pshift(int shift) 211 { 212 int shifted = 0; /* Number of display columns already discarded. */ 213 int from; /* Index in linebuf of the current character. */ 214 int to; /* Index in linebuf to move this character to. */ 215 int len; /* Number of bytes in this character. */ 216 int width = 0; /* Display columns needed for this character. */ 217 int prev_attr; /* Attributes of the preceding character. */ 218 int next_attr; /* Attributes of the following character. */ 219 unsigned char c; /* First byte of current character. */ 220 221 if (shift > column - lmargin) 222 shift = column - lmargin; 223 if (shift > curr - lmargin) 224 shift = curr - lmargin; 225 226 to = from = lmargin; 227 /* 228 * We keep on going when shifted == shift 229 * to get all combining chars. 230 */ 231 while (shifted <= shift && from < curr) { 232 c = linebuf[from]; 233 if (ctldisp == OPT_ONPLUS && c == ESC) { 234 /* Keep cumulative effect. */ 235 linebuf[to] = c; 236 attr[to++] = attr[from++]; 237 while (from < curr && linebuf[from]) { 238 linebuf[to] = linebuf[from]; 239 attr[to++] = attr[from]; 240 if (!is_ansi_middle(linebuf[from++])) 241 break; 242 } 243 continue; 244 } 245 if (utf_mode && !isascii(c)) { 246 wchar_t ch; 247 /* 248 * Before this point, UTF-8 validity was already 249 * checked, but for additional safety, treat 250 * invalid bytes as single-width characters 251 * if they ever make it here. Similarly, treat 252 * non-printable characters as width 1. 253 */ 254 len = mbtowc(&ch, linebuf + from, curr - from); 255 if (len == -1) 256 len = width = 1; 257 else if ((width = wcwidth(ch)) == -1) 258 width = 1; 259 } else { 260 len = 1; 261 if (c == '\b') 262 /* XXX - Incorrect if several '\b' in a row. */ 263 width = width > 0 ? -width : -1; 264 else 265 width = iscntrl(c) ? 0 : 1; 266 } 267 268 if (width == 2 && shift - shifted == 1) { 269 /* 270 * Move the first half of a double-width character 271 * off screen. Print a space instead of the second 272 * half. This should never happen when called 273 * by pshift_all(). 274 */ 275 attr[to] = attr[from]; 276 linebuf[to++] = ' '; 277 from += len; 278 shifted++; 279 break; 280 } 281 282 /* Adjust width for magic cookies. */ 283 prev_attr = (to > 0) ? attr[to-1] : AT_NORMAL; 284 next_attr = (from + len < curr) ? attr[from + len] : prev_attr; 285 if (!is_at_equiv(attr[from], prev_attr) && 286 !is_at_equiv(attr[from], next_attr)) { 287 width += attr_swidth(attr[from]); 288 if (from + len < curr) 289 width += attr_ewidth(attr[from]); 290 if (is_at_equiv(prev_attr, next_attr)) { 291 width += attr_ewidth(prev_attr); 292 if (from + len < curr) 293 width += attr_swidth(next_attr); 294 } 295 } 296 297 if (shift - shifted < width) 298 break; 299 from += len; 300 shifted += width; 301 if (shifted < 0) 302 shifted = 0; 303 } 304 while (from < curr) { 305 linebuf[to] = linebuf[from]; 306 attr[to++] = attr[from++]; 307 } 308 curr = to; 309 column -= shifted; 310 cshift += shifted; 311 } 312 313 /* 314 * 315 */ 316 void 317 pshift_all(void) 318 { 319 pshift(column); 320 } 321 322 /* 323 * Return the printing width of the start (enter) sequence 324 * for a given character attribute. 325 */ 326 static int 327 attr_swidth(int a) 328 { 329 int w = 0; 330 331 a = apply_at_specials(a); 332 333 if (a & AT_UNDERLINE) 334 w += ul_s_width; 335 if (a & AT_BOLD) 336 w += bo_s_width; 337 if (a & AT_BLINK) 338 w += bl_s_width; 339 if (a & AT_STANDOUT) 340 w += so_s_width; 341 342 return (w); 343 } 344 345 /* 346 * Return the printing width of the end (exit) sequence 347 * for a given character attribute. 348 */ 349 static int 350 attr_ewidth(int a) 351 { 352 int w = 0; 353 354 a = apply_at_specials(a); 355 356 if (a & AT_UNDERLINE) 357 w += ul_e_width; 358 if (a & AT_BOLD) 359 w += bo_e_width; 360 if (a & AT_BLINK) 361 w += bl_e_width; 362 if (a & AT_STANDOUT) 363 w += so_e_width; 364 365 return (w); 366 } 367 368 /* 369 * Return the printing width of a given character and attribute, 370 * if the character were added to the current position in the line buffer. 371 * Adding a character with a given attribute may cause an enter or exit 372 * attribute sequence to be inserted, so this must be taken into account. 373 */ 374 static int 375 pwidth(wchar_t ch, int a, wchar_t prev_ch) 376 { 377 int w; 378 379 /* 380 * In case of a backspace, back up by the width of the previous 381 * character. If that is non-printable (for example another 382 * backspace) or zero width (for example a combining accent), 383 * the terminal may actually back up to a character even further 384 * back, but we no longer know how wide that may have been. 385 * The best guess possible at this point is that it was 386 * hopefully width one. 387 */ 388 if (ch == L'\b') { 389 w = wcwidth(prev_ch); 390 if (w <= 0) 391 w = 1; 392 return (-w); 393 } 394 395 w = wcwidth(ch); 396 397 /* 398 * Non-printable characters can get here if the -r flag is in 399 * effect, and possibly in some other situations (XXX check that!). 400 * Treat them as zero width. 401 * That may not always match their actual behaviour, 402 * but there is no reasonable way to be more exact. 403 */ 404 if (w == -1) 405 w = 0; 406 407 /* 408 * Combining accents take up no space. 409 * Some terminals, upon failure to compose them with the 410 * characters that precede them, will actually take up one column 411 * for the combining accent; there isn't much we could do short 412 * of testing the (complex) composition process ourselves and 413 * printing a binary representation when it fails. 414 */ 415 if (w == 0) 416 return (0); 417 418 /* 419 * Other characters take one or two columns, 420 * plus the width of any attribute enter/exit sequence. 421 */ 422 if (curr > 0 && !is_at_equiv(attr[curr-1], a)) 423 w += attr_ewidth(attr[curr-1]); 424 if ((apply_at_specials(a) != AT_NORMAL) && 425 (curr == 0 || !is_at_equiv(attr[curr-1], a))) 426 w += attr_swidth(a); 427 return (w); 428 } 429 430 /* 431 * Delete to the previous base character in the line buffer. 432 * Return 1 if one is found. 433 */ 434 static int 435 backc(void) 436 { 437 wchar_t ch, prev_ch; 438 int i, len, width; 439 440 i = curr - 1; 441 if (utf_mode) { 442 while (i >= lmargin && IS_UTF8_TRAIL(linebuf[i])) 443 i--; 444 } 445 if (i < lmargin) 446 return (0); 447 if (utf_mode) { 448 len = mbtowc(&ch, linebuf + i, curr - i); 449 if (len == -1 || i + len < curr) { 450 (void)mbtowc(NULL, NULL, MB_CUR_MAX); 451 return (0); 452 } 453 } else 454 ch = linebuf[i]; 455 456 /* This assumes that there is no '\b' in linebuf. */ 457 while (curr > lmargin && column > lmargin && 458 (!(attr[curr - 1] & (AT_ANSI|AT_BINARY)))) { 459 curr = i--; 460 if (utf_mode) { 461 while (i >= lmargin && IS_UTF8_TRAIL(linebuf[i])) 462 i--; 463 } 464 if (i < lmargin) 465 prev_ch = L'\0'; 466 else if (utf_mode) { 467 len = mbtowc(&prev_ch, linebuf + i, curr - i); 468 if (len == -1 || i + len < curr) { 469 (void)mbtowc(NULL, NULL, MB_CUR_MAX); 470 prev_ch = L'\0'; 471 } 472 } else 473 prev_ch = linebuf[i]; 474 width = pwidth(ch, attr[curr], prev_ch); 475 column -= width; 476 if (width > 0) 477 return (1); 478 if (prev_ch == L'\0') 479 return (0); 480 ch = prev_ch; 481 } 482 return (0); 483 } 484 485 /* 486 * Is a character the end of an ANSI escape sequence? 487 */ 488 static int 489 is_ansi_end(LWCHAR ch) 490 { 491 if (!is_ascii_char(ch)) 492 return (0); 493 return (strchr(end_ansi_chars, (char)ch) != NULL); 494 } 495 496 /* 497 * 498 */ 499 int 500 is_ansi_middle(LWCHAR ch) 501 { 502 if (!is_ascii_char(ch)) 503 return (0); 504 if (is_ansi_end(ch)) 505 return (0); 506 return (strchr(mid_ansi_chars, (char)ch) != NULL); 507 } 508 509 /* 510 * Append a character and attribute to the line buffer. 511 */ 512 static int 513 store_char(LWCHAR ch, char a, char *rep, off_t pos) 514 { 515 int i; 516 int w; 517 int replen; 518 char cs; 519 int matches; 520 521 if (is_hilited(pos, pos+1, 0, &matches)) { 522 /* 523 * This character should be highlighted. 524 * Override the attribute passed in. 525 */ 526 if (a != AT_ANSI) { 527 if (highest_hilite != -1 && pos > highest_hilite) 528 highest_hilite = pos; 529 a |= AT_HILITE; 530 } 531 } 532 533 w = -1; 534 if (ctldisp == OPT_ONPLUS) { 535 /* 536 * Set i to the beginning of an ANSI escape sequence 537 * that was begun and not yet ended, or to -1 otherwise. 538 */ 539 for (i = curr - 1; i >= 0; i--) { 540 if (linebuf[i] == ESC) 541 break; 542 if (!is_ansi_middle(linebuf[i])) 543 i = 0; 544 } 545 if (i >= 0 && !is_ansi_end(ch) && !is_ansi_middle(ch)) { 546 /* Remove whole unrecognized sequence. */ 547 curr = i; 548 return (0); 549 } 550 if (i >= 0 || ch == ESC) { 551 a = AT_ANSI; /* Will force re-AT_'ing around it. */ 552 w = 0; 553 } 554 } 555 if (w == -1) { 556 wchar_t prev_ch; 557 558 if (utf_mode) { 559 for (i = curr - 1; i >= 0; i--) 560 if (!IS_UTF8_TRAIL(linebuf[i])) 561 break; 562 if (i >= 0) { 563 w = mbtowc(&prev_ch, linebuf + i, curr - i); 564 if (w == -1 || i + w < curr) { 565 (void)mbtowc(NULL, NULL, MB_CUR_MAX); 566 prev_ch = L' '; 567 } 568 } else 569 prev_ch = L' '; 570 } else 571 prev_ch = curr > 0 ? linebuf[curr - 1] : L' '; 572 w = pwidth(ch, a, prev_ch); 573 } 574 575 if (ctldisp != OPT_ON && column + w + attr_ewidth(a) > sc_width) 576 /* 577 * Won't fit on screen. 578 */ 579 return (1); 580 581 if (rep == NULL) { 582 cs = (char)ch; 583 rep = &cs; 584 replen = 1; 585 } else { 586 replen = utf_len(rep[0]); 587 } 588 if (curr + replen >= size_linebuf-6) { 589 /* 590 * Won't fit in line buffer. 591 * Try to expand it. 592 */ 593 if (expand_linebuf()) 594 return (1); 595 } 596 597 while (replen-- > 0) { 598 linebuf[curr] = *rep++; 599 attr[curr] = a; 600 curr++; 601 } 602 column += w; 603 return (0); 604 } 605 606 /* 607 * Append a tab to the line buffer. 608 * Store spaces to represent the tab. 609 */ 610 static int 611 store_tab(int attr, off_t pos) 612 { 613 int to_tab = column + cshift - lmargin; 614 int i; 615 616 if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1]) 617 to_tab = tabdefault - 618 ((to_tab - tabstops[ntabstops-1]) % tabdefault); 619 else { 620 for (i = ntabstops - 2; i >= 0; i--) 621 if (to_tab >= tabstops[i]) 622 break; 623 to_tab = tabstops[i+1] - to_tab; 624 } 625 626 if (column + to_tab - 1 + pwidth(' ', attr, 0) + 627 attr_ewidth(attr) > sc_width) 628 return (1); 629 630 do { 631 if (store_char(' ', attr, " ", pos)) 632 return (1); 633 } while (--to_tab > 0); 634 return (0); 635 } 636 637 static int 638 store_prchar(char c, off_t pos) 639 { 640 char *s; 641 642 /* 643 * Convert to printable representation. 644 */ 645 s = prchar(c); 646 647 /* 648 * Make sure we can get the entire representation 649 * of the character on this line. 650 */ 651 if (column + (int)strlen(s) - 1 + 652 pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width) 653 return (1); 654 655 for (; *s != 0; s++) { 656 if (store_char(*s, AT_BINARY, NULL, pos)) 657 return (1); 658 } 659 return (0); 660 } 661 662 static int 663 flush_mbc_buf(off_t pos) 664 { 665 int i; 666 667 for (i = 0; i < mbc_buf_index; i++) { 668 if (store_prchar(mbc_buf[i], pos)) 669 return (mbc_buf_index - i); 670 } 671 return (0); 672 } 673 674 /* 675 * Append a character to the line buffer. 676 * Expand tabs into spaces, handle underlining, boldfacing, etc. 677 * Returns 0 if ok, 1 if couldn't fit in buffer. 678 */ 679 int 680 pappend(char c, off_t pos) 681 { 682 mbstate_t mbs; 683 size_t sz; 684 wchar_t ch; 685 int r; 686 687 if (pendc) { 688 if (do_append(pendc, NULL, pendpos)) 689 /* 690 * Oops. We've probably lost the char which 691 * was in pendc, since caller won't back up. 692 */ 693 return (1); 694 pendc = '\0'; 695 } 696 697 if (c == '\r' && bs_mode == BS_SPECIAL) { 698 if (mbc_buf_index > 0) /* utf_mode must be on. */ { 699 /* Flush incomplete (truncated) sequence. */ 700 r = flush_mbc_buf(mbc_pos); 701 mbc_buf_index = 0; 702 if (r) 703 return (r + 1); 704 } 705 706 /* 707 * Don't put the CR into the buffer until we see 708 * the next char. If the next char is a newline, 709 * discard the CR. 710 */ 711 pendc = c; 712 pendpos = pos; 713 return (0); 714 } 715 716 if (!utf_mode) { 717 r = do_append((LWCHAR) c, NULL, pos); 718 } else { 719 for (;;) { 720 if (mbc_buf_index == 0) 721 mbc_pos = pos; 722 mbc_buf[mbc_buf_index++] = c; 723 memset(&mbs, 0, sizeof(mbs)); 724 sz = mbrtowc(&ch, mbc_buf, mbc_buf_index, &mbs); 725 726 /* Incomplete UTF-8: wait for more bytes. */ 727 if (sz == (size_t)-2) 728 return (0); 729 730 /* Valid UTF-8: use the character. */ 731 if (sz != (size_t)-1) { 732 r = do_append(ch, mbc_buf, mbc_pos) ? 733 mbc_buf_index : 0; 734 break; 735 } 736 737 /* Invalid start byte: encode it. */ 738 if (mbc_buf_index == 1) { 739 r = store_prchar(c, pos); 740 break; 741 } 742 743 /* 744 * Invalid continuation. 745 * Encode the preceding bytes. 746 * If they fit, handle the interrupting byte. 747 * Otherwise, tell the caller to back up 748 * by the number of bytes that do not fit, 749 * plus one for the new byte. 750 */ 751 mbc_buf_index--; 752 if ((r = flush_mbc_buf(mbc_pos) + 1) == 1) 753 mbc_buf_index = 0; 754 else 755 break; 756 } 757 } 758 759 /* 760 * If we need to shift the line, do it. 761 * But wait until we get to at least the middle of the screen, 762 * so shifting it doesn't affect the chars we're currently 763 * pappending. (Bold & underline can get messed up otherwise.) 764 */ 765 if (cshift < hshift && column > sc_width / 2) { 766 linebuf[curr] = '\0'; 767 pshift(hshift - cshift); 768 } 769 mbc_buf_index = 0; 770 return (r); 771 } 772 773 static int 774 do_append(LWCHAR ch, char *rep, off_t pos) 775 { 776 wchar_t prev_ch; 777 int a; 778 779 a = AT_NORMAL; 780 781 if (ch == '\b') { 782 if (bs_mode == BS_CONTROL) 783 goto do_control_char; 784 785 /* 786 * A better test is needed here so we don't 787 * backspace over part of the printed 788 * representation of a binary character. 789 */ 790 if (curr <= lmargin || 791 column <= lmargin || 792 (attr[curr - 1] & (AT_ANSI|AT_BINARY))) { 793 if (store_prchar('\b', pos)) 794 return (1); 795 } else if (bs_mode == BS_NORMAL) { 796 if (store_char(ch, AT_NORMAL, NULL, pos)) 797 return (1); 798 } else if (bs_mode == BS_SPECIAL) { 799 overstrike = backc(); 800 } 801 802 return (0); 803 } 804 805 if (overstrike > 0) { 806 /* 807 * Overstrike the character at the current position 808 * in the line buffer. This will cause either 809 * underline (if a "_" is overstruck), 810 * bold (if an identical character is overstruck), 811 * or just deletion of the character in the buffer. 812 */ 813 overstrike = utf_mode ? -1 : 0; 814 /* To be correct, this must be a base character. */ 815 if (mbtowc(&prev_ch, linebuf + curr, MB_CUR_MAX) == -1) { 816 (void)mbtowc(NULL, NULL, MB_CUR_MAX); 817 prev_ch = L'\0'; 818 } 819 a = attr[curr]; 820 if (ch == prev_ch) { 821 /* 822 * Overstriking a char with itself means make it bold. 823 * But overstriking an underscore with itself is 824 * ambiguous. It could mean make it bold, or 825 * it could mean make it underlined. 826 * Use the previous overstrike to resolve it. 827 */ 828 if (ch == '_') { 829 if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL) 830 a |= (AT_BOLD|AT_UNDERLINE); 831 else if (curr > 0 && attr[curr - 1] & AT_UNDERLINE) 832 a |= AT_UNDERLINE; 833 else if (curr > 0 && attr[curr - 1] & AT_BOLD) 834 a |= AT_BOLD; 835 else 836 a |= AT_INDET; 837 } else { 838 a |= AT_BOLD; 839 } 840 } else if (ch == '_' && prev_ch != L'\0') { 841 a |= AT_UNDERLINE; 842 ch = prev_ch; 843 rep = linebuf + curr; 844 } else if (prev_ch == '_') { 845 a |= AT_UNDERLINE; 846 } 847 /* Else we replace prev_ch, but we keep its attributes. */ 848 } else if (overstrike < 0) { 849 if (wcwidth(ch) == 0) { 850 /* Continuation of the same overstrike. */ 851 if (curr > 0) 852 a = attr[curr - 1] & (AT_UNDERLINE | AT_BOLD); 853 else 854 a = AT_NORMAL; 855 } else 856 overstrike = 0; 857 } 858 859 if (ch == '\t') { 860 /* 861 * Expand a tab into spaces. 862 */ 863 switch (bs_mode) { 864 case BS_CONTROL: 865 goto do_control_char; 866 case BS_NORMAL: 867 case BS_SPECIAL: 868 if (store_tab(a, pos)) 869 return (1); 870 break; 871 } 872 } else if ((!utf_mode || is_ascii_char(ch)) && 873 !isprint((unsigned char)ch)) { 874 do_control_char: 875 if (ctldisp == OPT_ON || 876 (ctldisp == OPT_ONPLUS && ch == ESC)) { 877 /* 878 * Output as a normal character. 879 */ 880 if (store_char(ch, AT_NORMAL, rep, pos)) 881 return (1); 882 } else { 883 if (store_prchar(ch, pos)) 884 return (1); 885 } 886 } else if (utf_mode && ctldisp != OPT_ON && !iswprint(ch)) { 887 char *s; 888 889 s = prutfchar(ch); 890 891 if (column + (int)strlen(s) - 1 + 892 pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width) 893 return (1); 894 895 for (; *s != 0; s++) { 896 if (store_char(*s, AT_BINARY, NULL, pos)) 897 return (1); 898 } 899 } else { 900 if (store_char(ch, a, rep, pos)) 901 return (1); 902 } 903 return (0); 904 } 905 906 /* 907 * 908 */ 909 int 910 pflushmbc(void) 911 { 912 int r = 0; 913 914 if (mbc_buf_index > 0) { 915 /* Flush incomplete (truncated) sequence. */ 916 r = flush_mbc_buf(mbc_pos); 917 mbc_buf_index = 0; 918 } 919 return (r); 920 } 921 922 /* 923 * Terminate the line in the line buffer. 924 */ 925 void 926 pdone(int endline, int forw) 927 { 928 int i; 929 930 (void) pflushmbc(); 931 932 if (pendc && (pendc != '\r' || !endline)) 933 /* 934 * If we had a pending character, put it in the buffer. 935 * But discard a pending CR if we are at end of line 936 * (that is, discard the CR in a CR/LF sequence). 937 */ 938 (void) do_append(pendc, NULL, pendpos); 939 940 for (i = curr - 1; i >= 0; i--) { 941 if (attr[i] & AT_INDET) { 942 attr[i] &= ~AT_INDET; 943 if (i < curr - 1 && attr[i + 1] & AT_BOLD) 944 attr[i] |= AT_BOLD; 945 else 946 attr[i] |= AT_UNDERLINE; 947 } 948 } 949 950 /* 951 * Make sure we've shifted the line, if we need to. 952 */ 953 if (cshift < hshift) 954 pshift(hshift - cshift); 955 956 if (ctldisp == OPT_ONPLUS && is_ansi_end('m')) { 957 /* Switch to normal attribute at end of line. */ 958 char *p = "\033[m"; 959 for (; *p != '\0'; p++) { 960 linebuf[curr] = *p; 961 attr[curr++] = AT_ANSI; 962 } 963 } 964 965 /* 966 * Add a newline if necessary, 967 * and append a '\0' to the end of the line. 968 * We output a newline if we're not at the right edge of the screen, 969 * or if the terminal doesn't auto wrap, 970 * or if this is really the end of the line AND the terminal ignores 971 * a newline at the right edge. 972 * (In the last case we don't want to output a newline if the terminal 973 * doesn't ignore it since that would produce an extra blank line. 974 * But we do want to output a newline if the terminal ignores it in case 975 * the next line is blank. In that case the single newline output for 976 * that blank line would be ignored!) 977 */ 978 if (column < sc_width || !auto_wrap || (endline && ignaw) || 979 ctldisp == OPT_ON) { 980 linebuf[curr] = '\n'; 981 attr[curr] = AT_NORMAL; 982 curr++; 983 } else if (ignaw && column >= sc_width && forw) { 984 /* 985 * Terminals with "ignaw" don't wrap until they *really* need 986 * to, i.e. when the character *after* the last one to fit on a 987 * line is output. But they are too hard to deal with when they 988 * get in the state where a full screen width of characters 989 * have been output but the cursor is sitting on the right edge 990 * instead of at the start of the next line. 991 * So we nudge them into wrapping by outputting a space 992 * character plus a backspace. But do this only if moving 993 * forward; if we're moving backward and drawing this line at 994 * the top of the screen, the space would overwrite the first 995 * char on the next line. We don't need to do this "nudge" 996 * at the top of the screen anyway. 997 */ 998 linebuf[curr] = ' '; 999 attr[curr++] = AT_NORMAL; 1000 linebuf[curr] = '\b'; 1001 attr[curr++] = AT_NORMAL; 1002 } 1003 linebuf[curr] = '\0'; 1004 attr[curr] = AT_NORMAL; 1005 } 1006 1007 /* 1008 * 1009 */ 1010 void 1011 set_status_col(char c) 1012 { 1013 linebuf[0] = c; 1014 attr[0] = AT_NORMAL|AT_HILITE; 1015 } 1016 1017 /* 1018 * Get a character from the current line. 1019 * Return the character as the function return value, 1020 * and the character attribute in *ap. 1021 */ 1022 int 1023 gline(int i, int *ap) 1024 { 1025 if (is_null_line) { 1026 /* 1027 * If there is no current line, we pretend the line is 1028 * either "~" or "", depending on the "twiddle" flag. 1029 */ 1030 if (twiddle) { 1031 if (i == 0) { 1032 *ap = AT_BOLD; 1033 return ('~'); 1034 } 1035 --i; 1036 } 1037 /* Make sure we're back to AT_NORMAL before the '\n'. */ 1038 *ap = AT_NORMAL; 1039 return (i ? '\0' : '\n'); 1040 } 1041 1042 *ap = attr[i]; 1043 return (linebuf[i] & 0xFF); 1044 } 1045 1046 /* 1047 * Indicate that there is no current line. 1048 */ 1049 void 1050 null_line(void) 1051 { 1052 is_null_line = 1; 1053 cshift = 0; 1054 } 1055 1056 /* 1057 * Analogous to forw_line(), but deals with "raw lines": 1058 * lines which are not split for screen width. 1059 * {{ This is supposed to be more efficient than forw_line(). }} 1060 */ 1061 off_t 1062 forw_raw_line(off_t curr_pos, char **linep, int *line_lenp) 1063 { 1064 int n; 1065 int c; 1066 off_t new_pos; 1067 1068 if (curr_pos == -1 || ch_seek(curr_pos) || 1069 (c = ch_forw_get()) == EOI) 1070 return (-1); 1071 1072 n = 0; 1073 for (;;) { 1074 if (c == '\n' || c == EOI || ABORT_SIGS()) { 1075 new_pos = ch_tell(); 1076 break; 1077 } 1078 if (n >= size_linebuf-1) { 1079 if (expand_linebuf()) { 1080 /* 1081 * Overflowed the input buffer. 1082 * Pretend the line ended here. 1083 */ 1084 new_pos = ch_tell() - 1; 1085 break; 1086 } 1087 } 1088 linebuf[n++] = (char)c; 1089 c = ch_forw_get(); 1090 } 1091 linebuf[n] = '\0'; 1092 if (linep != NULL) 1093 *linep = linebuf; 1094 if (line_lenp != NULL) 1095 *line_lenp = n; 1096 return (new_pos); 1097 } 1098 1099 /* 1100 * Analogous to back_line(), but deals with "raw lines". 1101 * {{ This is supposed to be more efficient than back_line(). }} 1102 */ 1103 off_t 1104 back_raw_line(off_t curr_pos, char **linep, int *line_lenp) 1105 { 1106 int n; 1107 int c; 1108 off_t new_pos; 1109 1110 if (curr_pos == -1 || curr_pos <= ch_zero() || ch_seek(curr_pos - 1)) 1111 return (-1); 1112 1113 n = size_linebuf; 1114 linebuf[--n] = '\0'; 1115 for (;;) { 1116 c = ch_back_get(); 1117 if (c == '\n' || ABORT_SIGS()) { 1118 /* 1119 * This is the newline ending the previous line. 1120 * We have hit the beginning of the line. 1121 */ 1122 new_pos = ch_tell() + 1; 1123 break; 1124 } 1125 if (c == EOI) { 1126 /* 1127 * We have hit the beginning of the file. 1128 * This must be the first line in the file. 1129 * This must, of course, be the beginning of the line. 1130 */ 1131 new_pos = ch_zero(); 1132 break; 1133 } 1134 if (n <= 0) { 1135 int old_size_linebuf = size_linebuf; 1136 if (expand_linebuf()) { 1137 /* 1138 * Overflowed the input buffer. 1139 * Pretend the line ended here. 1140 */ 1141 new_pos = ch_tell() + 1; 1142 break; 1143 } 1144 /* 1145 * Shift the data to the end of the new linebuf. 1146 */ 1147 n = size_linebuf - old_size_linebuf; 1148 memmove(linebuf + n, linebuf, old_size_linebuf); 1149 } 1150 linebuf[--n] = c; 1151 } 1152 if (linep != NULL) 1153 *linep = &linebuf[n]; 1154 if (line_lenp != NULL) 1155 *line_lenp = size_linebuf - 1 - n; 1156 return (new_pos); 1157 } 1158