1 /* 2 * Copyright (c) 1988 Mark Nudleman 3 * Copyright (c) 1988 Regents of the University of California. 4 * All rights reserved. 5 * 6 * %sccs.include.redist.c% 7 */ 8 9 #ifndef lint 10 static char sccsid[] = "@(#)prim.c 5.9 (Berkeley) 02/11/93"; 11 #endif /* not lint */ 12 13 /* 14 * Primitives for displaying the file on the screen. 15 */ 16 17 #include <sys/types.h> 18 #include <stdio.h> 19 #include <ctype.h> 20 #include <less.h> 21 22 int back_scroll = -1; 23 int hit_eof; /* keeps track of how many times we hit end of file */ 24 int screen_trashed; 25 26 static int squished; 27 28 extern int sigs; 29 extern int top_scroll; 30 extern int sc_width, sc_height; 31 extern int caseless; 32 extern int linenums; 33 extern int tagoption; 34 extern char *line; 35 extern int retain_below; 36 37 off_t position(), forw_line(), back_line(), forw_raw_line(), back_raw_line(); 38 off_t ch_length(), ch_tell(); 39 40 /* 41 * Check to see if the end of file is currently "displayed". 42 */ 43 eof_check() 44 { 45 off_t pos; 46 47 if (sigs) 48 return; 49 /* 50 * If the bottom line is empty, we are at EOF. 51 * If the bottom line ends at the file length, 52 * we must be just at EOF. 53 */ 54 pos = position(BOTTOM_PLUS_ONE); 55 if (pos == NULL_POSITION || pos == ch_length()) 56 hit_eof++; 57 } 58 59 /* 60 * If the screen is "squished", repaint it. 61 * "Squished" means the first displayed line is not at the top 62 * of the screen; this can happen when we display a short file 63 * for the first time. 64 */ 65 squish_check() 66 { 67 if (squished) { 68 squished = 0; 69 repaint(); 70 } 71 } 72 73 /* 74 * Display n lines, scrolling forward, starting at position pos in the 75 * input file. "only_last" means display only the last screenful if 76 * n > screen size. 77 */ 78 forw(n, pos, only_last) 79 register int n; 80 off_t pos; 81 int only_last; 82 { 83 extern int short_file; 84 static int first_time = 1; 85 int eof = 0, do_repaint; 86 87 squish_check(); 88 89 /* 90 * do_repaint tells us not to display anything till the end, 91 * then just repaint the entire screen. 92 */ 93 do_repaint = (only_last && n > sc_height-1); 94 95 if (!do_repaint) { 96 if (top_scroll && n >= sc_height - 1) { 97 /* 98 * Start a new screen. 99 * {{ This is not really desirable if we happen 100 * to hit eof in the middle of this screen, 101 * but we don't yet know if that will happen. }} 102 */ 103 clear(); 104 home(); 105 } else { 106 lower_left(); 107 clear_eol(); 108 } 109 110 /* 111 * This is not contiguous with what is currently displayed. 112 * Clear the screen image (position table) and start a new 113 * screen. 114 */ 115 if (pos != position(BOTTOM_PLUS_ONE)) { 116 pos_clear(); 117 add_forw_pos(pos); 118 if (top_scroll) { 119 clear(); 120 home(); 121 } else if (!first_time) 122 putstr("...skipping...\n"); 123 } 124 } 125 126 for (short_file = 0; --n >= 0;) { 127 /* 128 * Read the next line of input. 129 */ 130 pos = forw_line(pos); 131 if (pos == NULL_POSITION) { 132 /* 133 * end of file; copy the table if the file was 134 * too small for an entire screen. 135 */ 136 eof = 1; 137 if (position(TOP) == NULL_POSITION) { 138 copytable(); 139 if (!position(TOP)) 140 short_file = 1; 141 } 142 break; 143 } 144 /* 145 * Add the position of the next line to the position table. 146 * Display the current line on the screen. 147 */ 148 add_forw_pos(pos); 149 if (do_repaint) 150 continue; 151 /* 152 * If this is the first screen displayed and we hit an early 153 * EOF (i.e. before the requested number of lines), we 154 * "squish" the display down at the bottom of the screen. 155 * But don't do this if a -t option was given; it can cause 156 * us to start the display after the beginning of the file, 157 * and it is not appropriate to squish in that case. 158 */ 159 if (first_time && line == NULL && !top_scroll && !tagoption) { 160 squished = 1; 161 continue; 162 } 163 put_line(); 164 } 165 166 if (eof && !sigs) 167 hit_eof++; 168 else 169 eof_check(); 170 if (do_repaint) 171 repaint(); 172 first_time = 0; 173 (void) currline(BOTTOM); 174 } 175 176 /* 177 * Display n lines, scrolling backward. 178 */ 179 back(n, pos, only_last) 180 register int n; 181 off_t pos; 182 int only_last; 183 { 184 int do_repaint; 185 186 squish_check(); 187 do_repaint = (n > get_back_scroll() || (only_last && n > sc_height-1)); 188 hit_eof = 0; 189 while (--n >= 0) 190 { 191 /* 192 * Get the previous line of input. 193 */ 194 pos = back_line(pos); 195 if (pos == NULL_POSITION) 196 break; 197 /* 198 * Add the position of the previous line to the position table. 199 * Display the line on the screen. 200 */ 201 add_back_pos(pos); 202 if (!do_repaint) 203 { 204 if (retain_below) 205 { 206 lower_left(); 207 clear_eol(); 208 } 209 home(); 210 add_line(); 211 put_line(); 212 } 213 } 214 215 eof_check(); 216 if (do_repaint) 217 repaint(); 218 (void) currline(BOTTOM); 219 } 220 221 /* 222 * Display n more lines, forward. 223 * Start just after the line currently displayed at the bottom of the screen. 224 */ 225 forward(n, only_last) 226 int n; 227 int only_last; 228 { 229 off_t pos; 230 231 if (hit_eof) { 232 /* 233 * If we're trying to go forward from end-of-file, 234 * go on to the next file. 235 */ 236 next_file(1); 237 return; 238 } 239 240 pos = position(BOTTOM_PLUS_ONE); 241 if (pos == NULL_POSITION) 242 { 243 hit_eof++; 244 return; 245 } 246 forw(n, pos, only_last); 247 } 248 249 /* 250 * Display n more lines, backward. 251 * Start just before the line currently displayed at the top of the screen. 252 */ 253 backward(n, only_last) 254 int n; 255 int only_last; 256 { 257 off_t pos; 258 259 pos = position(TOP); 260 /* 261 * This will almost never happen, because the top line is almost 262 * never empty. 263 */ 264 if (pos == NULL_POSITION) 265 return; 266 back(n, pos, only_last); 267 } 268 269 /* 270 * Repaint the screen, starting from a specified position. 271 */ 272 prepaint(pos) 273 off_t pos; 274 { 275 hit_eof = 0; 276 forw(sc_height-1, pos, 0); 277 screen_trashed = 0; 278 } 279 280 /* 281 * Repaint the screen. 282 */ 283 repaint() 284 { 285 /* 286 * Start at the line currently at the top of the screen 287 * and redisplay the screen. 288 */ 289 prepaint(position(TOP)); 290 } 291 292 /* 293 * Jump to the end of the file. 294 * It is more convenient to paint the screen backward, 295 * from the end of the file toward the beginning. 296 */ 297 jump_forw() 298 { 299 off_t pos; 300 301 if (ch_end_seek()) 302 { 303 error("Cannot seek to end of file"); 304 return; 305 } 306 lastmark(); 307 pos = ch_tell(); 308 clear(); 309 pos_clear(); 310 add_back_pos(pos); 311 back(sc_height - 1, pos, 0); 312 } 313 314 /* 315 * Jump to line n in the file. 316 */ 317 jump_back(n) 318 register int n; 319 { 320 register int c, nlines; 321 322 /* 323 * This is done the slow way, by starting at the beginning 324 * of the file and counting newlines. 325 * 326 * {{ Now that we have line numbering (in linenum.c), 327 * we could improve on this by starting at the 328 * nearest known line rather than at the beginning. }} 329 */ 330 if (ch_seek((off_t)0)) { 331 /* 332 * Probably a pipe with beginning of file no longer buffered. 333 * If he wants to go to line 1, we do the best we can, 334 * by going to the first line which is still buffered. 335 */ 336 if (n <= 1 && ch_beg_seek() == 0) 337 jump_loc(ch_tell()); 338 error("Cannot get to beginning of file"); 339 return; 340 } 341 342 /* 343 * Start counting lines. 344 */ 345 for (nlines = 1; nlines < n; nlines++) 346 while ((c = ch_forw_get()) != '\n') 347 if (c == EOI) { 348 char message[40]; 349 (void)sprintf(message, "File has only %d lines", 350 nlines - 1); 351 error(message); 352 return; 353 } 354 jump_loc(ch_tell()); 355 } 356 357 /* 358 * Jump to a specified percentage into the file. 359 * This is a poor compensation for not being able to 360 * quickly jump to a specific line number. 361 */ 362 jump_percent(percent) 363 int percent; 364 { 365 off_t pos, len, ch_length(); 366 register int c; 367 368 /* 369 * Determine the position in the file 370 * (the specified percentage of the file's length). 371 */ 372 if ((len = ch_length()) == NULL_POSITION) 373 { 374 error("Don't know length of file"); 375 return; 376 } 377 pos = (percent * len) / 100; 378 379 /* 380 * Back up to the beginning of the line. 381 */ 382 if (ch_seek(pos) == 0) 383 { 384 while ((c = ch_back_get()) != '\n' && c != EOI) 385 ; 386 if (c == '\n') 387 (void) ch_forw_get(); 388 pos = ch_tell(); 389 } 390 jump_loc(pos); 391 } 392 393 /* 394 * Jump to a specified position in the file. 395 */ 396 jump_loc(pos) 397 off_t pos; 398 { 399 register int nline; 400 off_t tpos; 401 402 if ((nline = onscreen(pos)) >= 0) { 403 /* 404 * The line is currently displayed. 405 * Just scroll there. 406 */ 407 forw(nline, position(BOTTOM_PLUS_ONE), 0); 408 return; 409 } 410 411 /* 412 * Line is not on screen. 413 * Seek to the desired location. 414 */ 415 if (ch_seek(pos)) { 416 error("Cannot seek to that position"); 417 return; 418 } 419 420 /* 421 * See if the desired line is BEFORE the currently displayed screen. 422 * If so, then move forward far enough so the line we're on will be 423 * at the bottom of the screen, in order to be able to call back() 424 * to make the screen scroll backwards & put the line at the top of 425 * the screen. 426 * {{ This seems inefficient, but it's not so bad, 427 * since we can never move forward more than a 428 * screenful before we stop to redraw the screen. }} 429 */ 430 tpos = position(TOP); 431 if (tpos != NULL_POSITION && pos < tpos) { 432 off_t npos = pos; 433 /* 434 * Note that we can't forw_line() past tpos here, 435 * so there should be no EOI at this stage. 436 */ 437 for (nline = 0; npos < tpos && nline < sc_height - 1; nline++) 438 npos = forw_line(npos); 439 440 if (npos < tpos) { 441 /* 442 * More than a screenful back. 443 */ 444 lastmark(); 445 clear(); 446 pos_clear(); 447 add_back_pos(npos); 448 } 449 450 /* 451 * Note that back() will repaint() if nline > back_scroll. 452 */ 453 back(nline, npos, 0); 454 return; 455 } 456 /* 457 * Remember where we were; clear and paint the screen. 458 */ 459 lastmark(); 460 prepaint(pos); 461 } 462 463 /* 464 * The table of marks. 465 * A mark is simply a position in the file. 466 */ 467 #define NMARKS (27) /* 26 for a-z plus one for quote */ 468 #define LASTMARK (NMARKS-1) /* For quote */ 469 static off_t marks[NMARKS]; 470 471 /* 472 * Initialize the mark table to show no marks are set. 473 */ 474 init_mark() 475 { 476 int i; 477 478 for (i = 0; i < NMARKS; i++) 479 marks[i] = NULL_POSITION; 480 } 481 482 /* 483 * See if a mark letter is valid (between a and z). 484 */ 485 static int 486 badmark(c) 487 int c; 488 { 489 if (c < 'a' || c > 'z') 490 { 491 error("Choose a letter between 'a' and 'z'"); 492 return (1); 493 } 494 return (0); 495 } 496 497 /* 498 * Set a mark. 499 */ 500 setmark(c) 501 int c; 502 { 503 if (badmark(c)) 504 return; 505 marks[c-'a'] = position(TOP); 506 } 507 508 lastmark() 509 { 510 marks[LASTMARK] = position(TOP); 511 } 512 513 /* 514 * Go to a previously set mark. 515 */ 516 gomark(c) 517 int c; 518 { 519 off_t pos; 520 521 if (c == '\'') { 522 pos = marks[LASTMARK]; 523 if (pos == NULL_POSITION) 524 pos = 0; 525 } 526 else { 527 if (badmark(c)) 528 return; 529 pos = marks[c-'a']; 530 if (pos == NULL_POSITION) { 531 error("mark not set"); 532 return; 533 } 534 } 535 jump_loc(pos); 536 } 537 538 /* 539 * Get the backwards scroll limit. 540 * Must call this function instead of just using the value of 541 * back_scroll, because the default case depends on sc_height and 542 * top_scroll, as well as back_scroll. 543 */ 544 get_back_scroll() 545 { 546 if (back_scroll >= 0) 547 return (back_scroll); 548 if (top_scroll) 549 return (sc_height - 2); 550 return (sc_height - 1); 551 } 552 553 /* 554 * Search for the n-th occurence of a specified pattern, 555 * either forward or backward. 556 */ 557 search(search_forward, pattern, n, wantmatch) 558 register int search_forward; 559 register char *pattern; 560 register int n; 561 int wantmatch; 562 { 563 off_t pos, linepos; 564 register char *p; 565 register char *q; 566 int linenum; 567 int linematch; 568 #ifdef RECOMP 569 char *re_comp(); 570 char *errmsg; 571 #else 572 #ifdef REGCMP 573 char *regcmp(); 574 static char *cpattern = NULL; 575 #else 576 static char lpbuf[100]; 577 static char *last_pattern = NULL; 578 char *strcpy(); 579 #endif 580 #endif 581 582 /* 583 * For a caseless search, convert any uppercase in the pattern to 584 * lowercase. 585 */ 586 if (caseless && pattern != NULL) 587 for (p = pattern; *p; p++) 588 if (isupper(*p)) 589 *p = tolower(*p); 590 #ifdef RECOMP 591 592 /* 593 * (re_comp handles a null pattern internally, 594 * so there is no need to check for a null pattern here.) 595 */ 596 if ((errmsg = re_comp(pattern)) != NULL) 597 { 598 error(errmsg); 599 return(0); 600 } 601 #else 602 #ifdef REGCMP 603 if (pattern == NULL || *pattern == '\0') 604 { 605 /* 606 * A null pattern means use the previous pattern. 607 * The compiled previous pattern is in cpattern, so just use it. 608 */ 609 if (cpattern == NULL) 610 { 611 error("No previous regular expression"); 612 return(0); 613 } 614 } else 615 { 616 /* 617 * Otherwise compile the given pattern. 618 */ 619 char *s; 620 if ((s = regcmp(pattern, 0)) == NULL) 621 { 622 error("Invalid pattern"); 623 return(0); 624 } 625 if (cpattern != NULL) 626 free(cpattern); 627 cpattern = s; 628 } 629 #else 630 if (pattern == NULL || *pattern == '\0') 631 { 632 /* 633 * Null pattern means use the previous pattern. 634 */ 635 if (last_pattern == NULL) 636 { 637 error("No previous regular expression"); 638 return(0); 639 } 640 pattern = last_pattern; 641 } else 642 { 643 (void)strcpy(lpbuf, pattern); 644 last_pattern = lpbuf; 645 } 646 #endif 647 #endif 648 649 /* 650 * Figure out where to start the search. 651 */ 652 653 if (position(TOP) == NULL_POSITION) { 654 /* 655 * Nothing is currently displayed. Start at the beginning 656 * of the file. (This case is mainly for searches from the 657 * command line. 658 */ 659 pos = (off_t)0; 660 } else if (!search_forward) { 661 /* 662 * Backward search: start just before the top line 663 * displayed on the screen. 664 */ 665 pos = position(TOP); 666 } else { 667 /* 668 * Start at the second screen line displayed on the screen. 669 */ 670 pos = position(TOP_PLUS_ONE); 671 } 672 673 if (pos == NULL_POSITION) 674 { 675 /* 676 * Can't find anyplace to start searching from. 677 */ 678 error("Nothing to search"); 679 return(0); 680 } 681 682 linenum = find_linenum(pos); 683 for (;;) 684 { 685 /* 686 * Get lines until we find a matching one or 687 * until we hit end-of-file (or beginning-of-file 688 * if we're going backwards). 689 */ 690 if (sigs) 691 /* 692 * A signal aborts the search. 693 */ 694 return(0); 695 696 if (search_forward) 697 { 698 /* 699 * Read the next line, and save the 700 * starting position of that line in linepos. 701 */ 702 linepos = pos; 703 pos = forw_raw_line(pos); 704 if (linenum != 0) 705 linenum++; 706 } else 707 { 708 /* 709 * Read the previous line and save the 710 * starting position of that line in linepos. 711 */ 712 pos = back_raw_line(pos); 713 linepos = pos; 714 if (linenum != 0) 715 linenum--; 716 } 717 718 if (pos == NULL_POSITION) 719 { 720 /* 721 * We hit EOF/BOF without a match. 722 */ 723 error("Pattern not found"); 724 return(0); 725 } 726 727 /* 728 * If we're using line numbers, we might as well 729 * remember the information we have now (the position 730 * and line number of the current line). 731 */ 732 if (linenums) 733 add_lnum(linenum, pos); 734 735 /* 736 * If this is a caseless search, convert uppercase in the 737 * input line to lowercase. 738 */ 739 if (caseless) 740 for (p = q = line; *p; p++, q++) 741 *q = isupper(*p) ? tolower(*p) : *p; 742 743 /* 744 * Remove any backspaces along with the preceeding char. 745 * This allows us to match text which is underlined or 746 * overstruck. 747 */ 748 for (p = q = line; *p; p++, q++) 749 if (q > line && *p == '\b') 750 /* Delete BS and preceeding char. */ 751 q -= 2; 752 else 753 /* Otherwise, just copy. */ 754 *q = *p; 755 756 /* 757 * Test the next line to see if we have a match. 758 * This is done in a variety of ways, depending 759 * on what pattern matching functions are available. 760 */ 761 #ifdef REGCMP 762 linematch = (regex(cpattern, line) != NULL); 763 #else 764 #ifdef RECOMP 765 linematch = (re_exec(line) == 1); 766 #else 767 linematch = match(pattern, line); 768 #endif 769 #endif 770 /* 771 * We are successful if wantmatch and linematch are 772 * both true (want a match and got it), 773 * or both false (want a non-match and got it). 774 */ 775 if (((wantmatch && linematch) || (!wantmatch && !linematch)) && 776 --n <= 0) 777 /* 778 * Found the line. 779 */ 780 break; 781 } 782 jump_loc(linepos); 783 return(1); 784 } 785 786 #if !defined(REGCMP) && !defined(RECOMP) 787 /* 788 * We have neither regcmp() nor re_comp(). 789 * We use this function to do simple pattern matching. 790 * It supports no metacharacters like *, etc. 791 */ 792 static 793 match(pattern, buf) 794 char *pattern, *buf; 795 { 796 register char *pp, *lp; 797 798 for ( ; *buf != '\0'; buf++) 799 { 800 for (pp = pattern, lp = buf; *pp == *lp; pp++, lp++) 801 if (*pp == '\0' || *lp == '\0') 802 break; 803 if (*pp == '\0') 804 return (1); 805 } 806 return (0); 807 } 808 #endif 809