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