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