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