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