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