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