1/* $NetBSD: prim.c,v 1.9 2003/10/13 14:34:25 agc 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 36static char sccsid[] = "@(#)prim.c 8.1 (Berkeley) 6/6/93"; 37#else 38__RCSID("$NetBSD: prim.c,v 1.9 2003/10/13 14:34:25 agc 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 54int back_scroll = -1; 55int hit_eof; /* keeps track of how many times we hit end of file */ 56int screen_trashed; 57 58static int squished; 59 60 61static int match(char *, char *); 62static int badmark __P((int)); 63 64/* 65 * Check to see if the end of file is currently "displayed". 66 */ 67void 68eof_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 */ 91void 92squish_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 */ 106void 107forw(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 */ 205void 206back(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 */ 253void 254forward(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 */ 283void 284backward(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 */ 304void 305prepaint(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 */ 317void 318repaint() 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 */ 333void 334jump_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 */ 355void 356jump_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 */ 402void 403jump_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 */ 438void 439jump_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 */ 513static off_t marks[NMARKS]; 514 515/* 516 * Initialize the mark table to show no marks are set. 517 */ 518void 519init_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 */ 531static int 532badmark(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 */ 546void 547setmark(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 556void 557lastmark() 558/*###547 [cc] conflicting types for `lastmark'%%%*/ 559{ 560 marks[LASTMARK] = position(TOP); 561} 562 563/* 564 * Go to a previously set mark. 565 */ 566void 567gomark(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 */ 596int 597get_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 occurence of a specified pattern, 608 * either forward or backward. 609 */ 610int 611search(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 */ 846static int 847match(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