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