1 /* 2 * Copyright (C) 1984-2000 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 about less, or for information on how to 8 * contact the author, see the README file. 9 */ 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 "less.h" 19 20 #define IS_CONT(c) (((c) & 0xC0) == 0x80) 21 #define LINENUM_WIDTH 8 /* Chars to use for line number */ 22 23 public char *linebuf = NULL; /* Buffer which holds the current output line */ 24 static char *attr = NULL; /* Extension of linebuf to hold attributes */ 25 public int size_linebuf = 0; /* Size of line buffer (and attr buffer) */ 26 27 public int cshift; /* Current left-shift of output line buffer */ 28 public int hshift; /* Desired left-shift of output line buffer */ 29 public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */ 30 public int ntabstops = 1; /* Number of tabstops */ 31 public int tabdefault = 8; /* Default repeated tabstops */ 32 33 static int curr; /* Index into linebuf */ 34 static int column; /* Printable length, accounting for 35 backspaces, etc. */ 36 static int overstrike; /* Next char should overstrike previous char */ 37 static int is_null_line; /* There is no current line */ 38 static int lmargin; /* Left margin */ 39 static int hilites; /* Number of hilites in this line */ 40 static char pendc; 41 static POSITION pendpos; 42 static char *end_ansi_chars; 43 44 static int do_append(); 45 46 extern int bs_mode; 47 extern int linenums; 48 extern int ctldisp; 49 extern int twiddle; 50 extern int binattr; 51 extern int status_col; 52 extern int auto_wrap, ignaw; 53 extern int bo_s_width, bo_e_width; 54 extern int ul_s_width, ul_e_width; 55 extern int bl_s_width, bl_e_width; 56 extern int so_s_width, so_e_width; 57 extern int sc_width, sc_height; 58 extern int utf_mode; 59 extern POSITION start_attnpos; 60 extern POSITION end_attnpos; 61 62 /* 63 * Initialize from environment variables. 64 */ 65 public void 66 init_line() 67 { 68 end_ansi_chars = lgetenv("LESSANSIENDCHARS"); 69 if (end_ansi_chars == NULL || *end_ansi_chars == '\0') 70 end_ansi_chars = "m"; 71 linebuf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); 72 attr = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); 73 size_linebuf = LINEBUF_SIZE; 74 } 75 76 /* 77 * Expand the line buffer. 78 */ 79 static int 80 expand_linebuf() 81 { 82 int new_size = size_linebuf + LINEBUF_SIZE; 83 char *new_buf = (char *) calloc(new_size, sizeof(char)); 84 char *new_attr = (char *) calloc(new_size, sizeof(char)); 85 if (new_buf == NULL || new_attr == NULL) 86 { 87 if (new_attr != NULL) 88 free(new_attr); 89 if (new_buf != NULL) 90 free(new_buf); 91 return 1; 92 } 93 memcpy(new_buf, linebuf, size_linebuf * sizeof(char)); 94 memcpy(new_attr, attr, size_linebuf * sizeof(char)); 95 linebuf = new_buf; 96 attr = new_attr; 97 size_linebuf = new_size; 98 return 0; 99 } 100 101 /* 102 * Rewind the line buffer. 103 */ 104 public void 105 prewind() 106 { 107 curr = 0; 108 column = 0; 109 overstrike = 0; 110 is_null_line = 0; 111 pendc = '\0'; 112 lmargin = 0; 113 #if HILITE_SEARCH 114 hilites = 0; 115 #endif 116 if (status_col) 117 lmargin += 1; 118 if (linenums == OPT_ONPLUS) 119 lmargin += LINENUM_WIDTH+1; 120 } 121 122 /* 123 * Insert the line number (of the given position) into the line buffer. 124 */ 125 public void 126 plinenum(pos) 127 POSITION pos; 128 { 129 register int lno; 130 register int i; 131 132 if (linenums == OPT_ONPLUS) 133 { 134 /* 135 * Get the line number and put it in the current line. 136 * {{ Note: since find_linenum calls forw_raw_line, 137 * it may seek in the input file, requiring the caller 138 * of plinenum to re-seek if necessary. }} 139 * {{ Since forw_raw_line modifies linebuf, we must 140 * do this first, before storing anything in linebuf. }} 141 */ 142 lno = find_linenum(pos); 143 } 144 145 /* 146 * Display a status column if the -J option is set. 147 */ 148 if (status_col) 149 { 150 linebuf[curr] = ' '; 151 if (start_attnpos != NULL_POSITION && 152 pos >= start_attnpos && pos < end_attnpos) 153 attr[curr] = AT_STANDOUT; 154 else 155 attr[curr] = 0; 156 curr++; 157 column++; 158 } 159 /* 160 * Display the line number at the start of each line 161 * if the -N option is set. 162 */ 163 if (linenums == OPT_ONPLUS) 164 { 165 sprintf(&linebuf[curr], "%*d", LINENUM_WIDTH, lno); 166 column += LINENUM_WIDTH; 167 for (i = 0; i < LINENUM_WIDTH; i++) 168 attr[curr++] = 0; 169 } 170 /* 171 * Append enough spaces to bring us to the lmargin. 172 */ 173 while (column < lmargin) 174 { 175 linebuf[curr] = ' '; 176 attr[curr++] = AT_NORMAL; 177 column++; 178 } 179 } 180 181 /* 182 * Determine how many characters are required to shift N columns. 183 */ 184 static int 185 shift_chars(s, len) 186 char *s; 187 int len; 188 { 189 char *p = s; 190 191 /* 192 * Each char counts for one column, except ANSI color escape 193 * sequences use no columns since they don't move the cursor. 194 */ 195 while (*p != '\0' && len > 0) 196 { 197 if (*p++ != ESC) 198 { 199 len--; 200 } else 201 { 202 while (*p != '\0') 203 { 204 if (is_ansi_end(*p++)) 205 break; 206 } 207 } 208 } 209 return (p - s); 210 } 211 212 /* 213 * Determine how many characters are required to shift N columns (UTF version). 214 * {{ FIXME: what about color escape sequences in UTF mode? }} 215 */ 216 static int 217 utf_shift_chars(s, len) 218 char *s; 219 int len; 220 { 221 int ulen = 0; 222 223 while (*s != '\0' && len > 0) 224 { 225 if (!IS_CONT(*s)) 226 len--; 227 s++; 228 ulen++; 229 } 230 while (IS_CONT(*s)) 231 { 232 s++; 233 ulen++; 234 } 235 return (ulen); 236 } 237 238 /* 239 * Shift the input line left. 240 * This means discarding N printable chars at the start of the buffer. 241 */ 242 static void 243 pshift(shift) 244 int shift; 245 { 246 int i; 247 int nchars; 248 249 if (shift > column - lmargin) 250 shift = column - lmargin; 251 if (shift > curr - lmargin) 252 shift = curr - lmargin; 253 254 if (utf_mode) 255 nchars = utf_shift_chars(linebuf + lmargin, shift); 256 else 257 nchars = shift_chars(linebuf + lmargin, shift); 258 if (nchars > curr) 259 nchars = curr; 260 for (i = 0; i < curr - nchars; i++) 261 { 262 linebuf[lmargin + i] = linebuf[lmargin + i + nchars]; 263 attr[lmargin + i] = attr[lmargin + i + nchars]; 264 } 265 curr -= nchars; 266 column -= shift; 267 cshift += shift; 268 } 269 270 /* 271 * Return the printing width of the start (enter) sequence 272 * for a given character attribute. 273 */ 274 static int 275 attr_swidth(a) 276 int a; 277 { 278 switch (a) 279 { 280 case AT_BOLD: return (bo_s_width); 281 case AT_UNDERLINE: return (ul_s_width); 282 case AT_BLINK: return (bl_s_width); 283 case AT_STANDOUT: return (so_s_width); 284 } 285 return (0); 286 } 287 288 /* 289 * Return the printing width of the end (exit) sequence 290 * for a given character attribute. 291 */ 292 static int 293 attr_ewidth(a) 294 int a; 295 { 296 switch (a) 297 { 298 case AT_BOLD: return (bo_e_width); 299 case AT_UNDERLINE: return (ul_e_width); 300 case AT_BLINK: return (bl_e_width); 301 case AT_STANDOUT: return (so_e_width); 302 } 303 return (0); 304 } 305 306 /* 307 * Return the printing width of a given character and attribute, 308 * if the character were added to the current position in the line buffer. 309 * Adding a character with a given attribute may cause an enter or exit 310 * attribute sequence to be inserted, so this must be taken into account. 311 */ 312 static int 313 pwidth(c, a) 314 int c; 315 int a; 316 { 317 register int w; 318 319 if (utf_mode && IS_CONT(c)) 320 return (0); 321 322 if (c == '\b') 323 /* 324 * Backspace moves backwards one position. 325 */ 326 return (-1); 327 328 if (control_char(c)) 329 /* 330 * Control characters do unpredicatable things, 331 * so we don't even try to guess; say it doesn't move. 332 * This can only happen if the -r flag is in effect. 333 */ 334 return (0); 335 336 /* 337 * Other characters take one space, 338 * plus the width of any attribute enter/exit sequence. 339 */ 340 w = 1; 341 if (curr > 0 && attr[curr-1] != a) 342 w += attr_ewidth(attr[curr-1]); 343 if (a && (curr == 0 || attr[curr-1] != a)) 344 w += attr_swidth(a); 345 return (w); 346 } 347 348 /* 349 * Delete the previous character in the line buffer. 350 */ 351 static void 352 backc() 353 { 354 curr--; 355 column -= pwidth(linebuf[curr], attr[curr]); 356 } 357 358 /* 359 * Are we currently within a recognized ANSI escape sequence? 360 */ 361 static int 362 in_ansi_esc_seq() 363 { 364 int i; 365 366 /* 367 * Search backwards for either an ESC (which means we ARE in a seq); 368 * or an end char (which means we're NOT in a seq). 369 */ 370 for (i = curr-1; i >= 0; i--) 371 { 372 if (linebuf[i] == ESC) 373 return (1); 374 if (is_ansi_end(linebuf[i])) 375 return (0); 376 } 377 return (0); 378 } 379 380 /* 381 * Is a character the end of an ANSI escape sequence? 382 */ 383 public int 384 is_ansi_end(c) 385 char c; 386 { 387 return (strchr(end_ansi_chars, c) != NULL); 388 } 389 390 /* 391 * Append a character and attribute to the line buffer. 392 */ 393 #define STORE_CHAR(c,a,pos) \ 394 do { if (store_char((c),(a),(pos))) return (1); else curr++; } while (0) 395 396 static int 397 store_char(c, a, pos) 398 int c; 399 int a; 400 POSITION pos; 401 { 402 register int w; 403 404 #if HILITE_SEARCH 405 if (is_hilited(pos, pos+1, 0)) 406 { 407 /* 408 * This character should be highlighted. 409 * Override the attribute passed in. 410 */ 411 a = AT_STANDOUT; 412 hilites++; 413 } 414 #endif 415 if (ctldisp == OPT_ONPLUS && in_ansi_esc_seq()) 416 w = 0; 417 else 418 w = pwidth(c, a); 419 if (ctldisp != OPT_ON && column + w + attr_ewidth(a) > sc_width) 420 /* 421 * Won't fit on screen. 422 */ 423 return (1); 424 425 if (curr >= size_linebuf-2) 426 { 427 /* 428 * Won't fit in line buffer. 429 * Try to expand it. 430 */ 431 if (expand_linebuf()) 432 return (1); 433 } 434 435 /* 436 * Special handling for "magic cookie" terminals. 437 * If an attribute enter/exit sequence has a printing width > 0, 438 * and the sequence is adjacent to a space, delete the space. 439 * We just mark the space as invisible, to avoid having too 440 * many spaces deleted. 441 * {{ Note that even if the attribute width is > 1, we 442 * delete only one space. It's not worth trying to do more. 443 * It's hardly worth doing this much. }} 444 */ 445 if (curr > 0 && a != AT_NORMAL && 446 linebuf[curr-1] == ' ' && attr[curr-1] == AT_NORMAL && 447 attr_swidth(a) > 0) 448 { 449 /* 450 * We are about to append an enter-attribute sequence 451 * just after a space. Delete the space. 452 */ 453 attr[curr-1] = AT_INVIS; 454 column--; 455 } else if (curr > 0 && attr[curr-1] != AT_NORMAL && 456 attr[curr-1] != AT_INVIS && c == ' ' && a == AT_NORMAL && 457 attr_ewidth(attr[curr-1]) > 0) 458 { 459 /* 460 * We are about to append a space just after an 461 * exit-attribute sequence. Delete the space. 462 */ 463 a = AT_INVIS; 464 column--; 465 } 466 /* End of magic cookie handling. */ 467 468 linebuf[curr] = c; 469 attr[curr] = a; 470 column += w; 471 return (0); 472 } 473 474 /* 475 * Append a tab to the line buffer. 476 * Store spaces to represent the tab. 477 */ 478 #define STORE_TAB(a,pos) \ 479 do { if (store_tab((a),(pos))) return (1); } while (0) 480 481 static int 482 store_tab(attr, pos) 483 int attr; 484 POSITION pos; 485 { 486 int to_tab = column + cshift - lmargin; 487 int i; 488 489 if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1]) 490 to_tab = tabdefault - 491 ((to_tab - tabstops[ntabstops-1]) % tabdefault); 492 else 493 { 494 for (i = ntabstops - 2; i >= 0; i--) 495 if (to_tab >= tabstops[i]) 496 break; 497 to_tab = tabstops[i+1] - to_tab; 498 } 499 500 do { 501 STORE_CHAR(' ', attr, pos); 502 } while (--to_tab > 0); 503 return 0; 504 } 505 506 /* 507 * Append a character to the line buffer. 508 * Expand tabs into spaces, handle underlining, boldfacing, etc. 509 * Returns 0 if ok, 1 if couldn't fit in buffer. 510 */ 511 public int 512 pappend(c, pos) 513 register int c; 514 POSITION pos; 515 { 516 int r; 517 518 if (pendc) 519 { 520 if (do_append(pendc, pendpos)) 521 /* 522 * Oops. We've probably lost the char which 523 * was in pendc, since caller won't back up. 524 */ 525 return (1); 526 pendc = '\0'; 527 } 528 529 if (c == '\r' && bs_mode == BS_SPECIAL) 530 { 531 /* 532 * Don't put the CR into the buffer until we see 533 * the next char. If the next char is a newline, 534 * discard the CR. 535 */ 536 pendc = c; 537 pendpos = pos; 538 return (0); 539 } 540 541 r = do_append(c, pos); 542 /* 543 * If we need to shift the line, do it. 544 * But wait until we get to at least the middle of the screen, 545 * so shifting it doesn't affect the chars we're currently 546 * pappending. (Bold & underline can get messed up otherwise.) 547 */ 548 if (cshift < hshift && column > sc_width / 2) 549 { 550 linebuf[curr] = '\0'; 551 pshift(hshift - cshift); 552 } 553 return (r); 554 } 555 556 static int 557 do_append(c, pos) 558 int c; 559 POSITION pos; 560 { 561 register char *s; 562 register int a; 563 564 #define STOREC(c,a) \ 565 if ((c) == '\t') STORE_TAB((a),pos); else STORE_CHAR((c),(a),pos) 566 567 if (c == '\b') 568 { 569 switch (bs_mode) 570 { 571 case BS_NORMAL: 572 STORE_CHAR(c, AT_NORMAL, pos); 573 break; 574 case BS_CONTROL: 575 goto do_control_char; 576 case BS_SPECIAL: 577 if (curr == 0) 578 break; 579 backc(); 580 overstrike = 1; 581 break; 582 } 583 } else if (overstrike) 584 { 585 /* 586 * Overstrike the character at the current position 587 * in the line buffer. This will cause either 588 * underline (if a "_" is overstruck), 589 * bold (if an identical character is overstruck), 590 * or just deletion of the character in the buffer. 591 */ 592 overstrike--; 593 if (utf_mode && curr > 1 && (char)c == linebuf[curr-2]) 594 { 595 backc(); 596 backc(); 597 overstrike = 2; 598 } else if (utf_mode && curr > 0 && (char)c == linebuf[curr-1]) 599 { 600 backc(); 601 STORE_CHAR(linebuf[curr], AT_BOLD, pos); 602 overstrike = 1; 603 } else if ((char)c == linebuf[curr]) 604 { 605 STOREC(c, AT_BOLD); 606 } else if (c == '_') 607 { 608 if (utf_mode) 609 { 610 if (curr > 0 && IS_CONT(linebuf[curr])) 611 attr[curr-1] = AT_UNDERLINE; 612 if (curr > 1 && IS_CONT(linebuf[curr-1])) 613 attr[curr-2] = AT_UNDERLINE; 614 if (curr > 2 && IS_CONT(linebuf[curr-2])) 615 attr[curr-3] = AT_UNDERLINE; 616 if (curr > 3 && IS_CONT(linebuf[curr-3])) 617 attr[curr-4] = AT_UNDERLINE; 618 if (curr > 4 && IS_CONT(linebuf[curr-4])) 619 attr[curr-5] = AT_UNDERLINE; 620 } 621 STOREC(linebuf[curr], AT_UNDERLINE); 622 } else if (linebuf[curr] == '_') 623 { 624 STOREC(c, AT_UNDERLINE); 625 } else if (control_char(c)) 626 goto do_control_char; 627 else 628 STOREC(c, AT_NORMAL); 629 } else if (c == '\t') 630 { 631 /* 632 * Expand a tab into spaces. 633 */ 634 switch (bs_mode) 635 { 636 case BS_CONTROL: 637 goto do_control_char; 638 case BS_NORMAL: 639 case BS_SPECIAL: 640 STORE_TAB(AT_NORMAL, pos); 641 break; 642 } 643 } else if (control_char(c)) 644 { 645 do_control_char: 646 if (ctldisp == OPT_ON || (ctldisp == OPT_ONPLUS && c == ESC)) 647 { 648 /* 649 * Output as a normal character. 650 */ 651 STORE_CHAR(c, AT_NORMAL, pos); 652 } else 653 { 654 /* 655 * Convert to printable representation. 656 */ 657 s = prchar(c); 658 a = binattr; 659 660 /* 661 * Make sure we can get the entire representation 662 * of the character on this line. 663 */ 664 if (column + (int) strlen(s) + 665 attr_swidth(a) + attr_ewidth(a) > sc_width) 666 return (1); 667 668 for ( ; *s != 0; s++) 669 STORE_CHAR(*s, a, pos); 670 } 671 } else 672 { 673 STOREC(c, AT_NORMAL); 674 } 675 676 return (0); 677 } 678 679 /* 680 * Terminate the line in the line buffer. 681 */ 682 public void 683 pdone(endline) 684 int endline; 685 { 686 if (pendc && (pendc != '\r' || !endline)) 687 /* 688 * If we had a pending character, put it in the buffer. 689 * But discard a pending CR if we are at end of line 690 * (that is, discard the CR in a CR/LF sequence). 691 */ 692 (void) do_append(pendc, pendpos); 693 694 /* 695 * Make sure we've shifted the line, if we need to. 696 */ 697 if (cshift < hshift) 698 pshift(hshift - cshift); 699 700 /* 701 * Add a newline if necessary, 702 * and append a '\0' to the end of the line. 703 */ 704 if (column < sc_width || !auto_wrap || ignaw || ctldisp == OPT_ON) 705 { 706 linebuf[curr] = '\n'; 707 attr[curr] = AT_NORMAL; 708 curr++; 709 } 710 linebuf[curr] = '\0'; 711 attr[curr] = AT_NORMAL; 712 713 #if HILITE_SEARCH 714 if (status_col && hilites > 0) 715 { 716 linebuf[0] = '*'; 717 attr[0] = AT_STANDOUT; 718 } 719 #endif 720 /* 721 * If we are done with this line, reset the current shift. 722 */ 723 if (endline) 724 cshift = 0; 725 } 726 727 /* 728 * Get a character from the current line. 729 * Return the character as the function return value, 730 * and the character attribute in *ap. 731 */ 732 public int 733 gline(i, ap) 734 register int i; 735 register int *ap; 736 { 737 char *s; 738 739 if (is_null_line) 740 { 741 /* 742 * If there is no current line, we pretend the line is 743 * either "~" or "", depending on the "twiddle" flag. 744 */ 745 *ap = AT_BOLD; 746 s = (twiddle) ? "~\n" : "\n"; 747 return (s[i]); 748 } 749 750 *ap = attr[i]; 751 return (linebuf[i] & 0377); 752 } 753 754 /* 755 * Indicate that there is no current line. 756 */ 757 public void 758 null_line() 759 { 760 is_null_line = 1; 761 cshift = 0; 762 } 763 764 /* 765 * Analogous to forw_line(), but deals with "raw lines": 766 * lines which are not split for screen width. 767 * {{ This is supposed to be more efficient than forw_line(). }} 768 */ 769 public POSITION 770 forw_raw_line(curr_pos, linep) 771 POSITION curr_pos; 772 char **linep; 773 { 774 register int n; 775 register int c; 776 POSITION new_pos; 777 778 if (curr_pos == NULL_POSITION || ch_seek(curr_pos) || 779 (c = ch_forw_get()) == EOI) 780 return (NULL_POSITION); 781 782 n = 0; 783 for (;;) 784 { 785 if (c == '\n' || c == EOI) 786 { 787 new_pos = ch_tell(); 788 break; 789 } 790 if (n >= size_linebuf-1) 791 { 792 if (expand_linebuf()) 793 { 794 /* 795 * Overflowed the input buffer. 796 * Pretend the line ended here. 797 */ 798 new_pos = ch_tell() - 1; 799 break; 800 } 801 } 802 linebuf[n++] = c; 803 c = ch_forw_get(); 804 } 805 linebuf[n] = '\0'; 806 if (linep != NULL) 807 *linep = linebuf; 808 return (new_pos); 809 } 810 811 /* 812 * Analogous to back_line(), but deals with "raw lines". 813 * {{ This is supposed to be more efficient than back_line(). }} 814 */ 815 public POSITION 816 back_raw_line(curr_pos, linep) 817 POSITION curr_pos; 818 char **linep; 819 { 820 register int n; 821 register int c; 822 POSITION new_pos; 823 824 if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() || 825 ch_seek(curr_pos-1)) 826 return (NULL_POSITION); 827 828 n = size_linebuf; 829 linebuf[--n] = '\0'; 830 for (;;) 831 { 832 c = ch_back_get(); 833 if (c == '\n') 834 { 835 /* 836 * This is the newline ending the previous line. 837 * We have hit the beginning of the line. 838 */ 839 new_pos = ch_tell() + 1; 840 break; 841 } 842 if (c == EOI) 843 { 844 /* 845 * We have hit the beginning of the file. 846 * This must be the first line in the file. 847 * This must, of course, be the beginning of the line. 848 */ 849 new_pos = ch_zero(); 850 break; 851 } 852 if (n <= 0) 853 { 854 int old_size_linebuf = size_linebuf; 855 char *fm; 856 char *to; 857 if (expand_linebuf()) 858 { 859 /* 860 * Overflowed the input buffer. 861 * Pretend the line ended here. 862 */ 863 new_pos = ch_tell() + 1; 864 break; 865 } 866 /* 867 * Shift the data to the end of the new linebuf. 868 */ 869 for (fm = linebuf + old_size_linebuf, 870 to = linebuf + size_linebuf; 871 fm >= linebuf; fm--, to--) 872 *to = *fm; 873 n = size_linebuf - old_size_linebuf; 874 } 875 linebuf[--n] = c; 876 } 877 if (linep != NULL) 878 *linep = &linebuf[n]; 879 return (new_pos); 880 } 881