/* * Copyright (c) 1988 Mark Nudleman * Copyright (c) 1988 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static char sccsid[] = "@(#)prim.c 5.8 (Berkeley) 6/1/90"; #endif /* not lint */ /* * Primitives for displaying the file on the screen. */ #include #include #include #include #ifdef REGEX #include #endif int back_scroll = -1; int hit_eof; /* keeps track of how many times we hit end of file */ int screen_trashed; static int squished; extern int sigs; extern int top_scroll; extern int sc_width, sc_height; extern int caseless; extern int linenums; extern int tagoption; extern char *line; off_t position(), forw_line(), back_line(), forw_raw_line(), back_raw_line(); off_t ch_length(), ch_tell(); /* * Check to see if the end of file is currently "displayed". */ eof_check() { off_t pos; if (sigs) return; /* * If the bottom line is empty, we are at EOF. * If the bottom line ends at the file length, * we must be just at EOF. */ pos = position(BOTTOM_PLUS_ONE); if (pos == NULL_POSITION || pos == ch_length()) hit_eof++; } /* * If the screen is "squished", repaint it. * "Squished" means the first displayed line is not at the top * of the screen; this can happen when we display a short file * for the first time. */ squish_check() { if (squished) { squished = 0; repaint(); } } /* * Display n lines, scrolling forward, starting at position pos in the * input file. "only_last" means display only the last screenful if * n > screen size. */ forw(n, pos, only_last) register int n; off_t pos; int only_last; { extern int short_file; static int first_time = 1; int eof = 0, do_repaint; squish_check(); /* * do_repaint tells us not to display anything till the end, * then just repaint the entire screen. */ do_repaint = (only_last && n > sc_height-1); if (!do_repaint) { if (top_scroll && n >= sc_height - 1) { /* * Start a new screen. * {{ This is not really desirable if we happen * to hit eof in the middle of this screen, * but we don't yet know if that will happen. }} */ clear(); home(); } else { lower_left(); clear_eol(); } /* * This is not contiguous with what is currently displayed. * Clear the screen image (position table) and start a new * screen. */ if (pos != position(BOTTOM_PLUS_ONE)) { pos_clear(); add_forw_pos(pos); if (top_scroll) { clear(); home(); } else if (!first_time) putstr("...skipping...\n"); } } for (short_file = 0; --n >= 0;) { /* * Read the next line of input. */ pos = forw_line(pos); if (pos == NULL_POSITION) { /* * end of file; copy the table if the file was * too small for an entire screen. */ eof = 1; if (position(TOP) == NULL_POSITION) { copytable(); if (!position(TOP)) short_file = 1; } break; } /* * Add the position of the next line to the position table. * Display the current line on the screen. */ add_forw_pos(pos); if (do_repaint) continue; /* * If this is the first screen displayed and we hit an early * EOF (i.e. before the requested number of lines), we * "squish" the display down at the bottom of the screen. * But don't do this if a -t option was given; it can cause * us to start the display after the beginning of the file, * and it is not appropriate to squish in that case. */ if (first_time && line == NULL && !top_scroll && !tagoption) { squished = 1; continue; } put_line(); } if (eof && !sigs) hit_eof++; else eof_check(); if (do_repaint) repaint(); first_time = 0; (void) currline(BOTTOM); } /* * Display n lines, scrolling backward. */ back(n, pos, only_last) register int n; off_t pos; int only_last; { int do_repaint; squish_check(); do_repaint = (n > get_back_scroll() || (only_last && n > sc_height-1)); hit_eof = 0; while (--n >= 0) { /* * Get the previous line of input. */ pos = back_line(pos); if (pos == NULL_POSITION) break; /* * Add the position of the previous line to the position table. * Display the line on the screen. */ add_back_pos(pos); if (!do_repaint) { home(); add_line(); put_line(); } } eof_check(); if (do_repaint) repaint(); (void) currline(BOTTOM); } /* * Display n more lines, forward. * Start just after the line currently displayed at the bottom of the screen. */ forward(n, only_last) int n; int only_last; { off_t pos; if (hit_eof) { /* * If we're trying to go forward from end-of-file, * go on to the next file. */ next_file(1); return; } pos = position(BOTTOM_PLUS_ONE); if (pos == NULL_POSITION) { hit_eof++; return; } forw(n, pos, only_last); } /* * Display n more lines, backward. * Start just before the line currently displayed at the top of the screen. */ backward(n, only_last) int n; int only_last; { off_t pos; pos = position(TOP); /* * This will almost never happen, because the top line is almost * never empty. */ if (pos == NULL_POSITION) return; back(n, pos, only_last); } /* * Repaint the screen, starting from a specified position. */ prepaint(pos) off_t pos; { hit_eof = 0; forw(sc_height-1, pos, 0); screen_trashed = 0; } /* * Repaint the screen. */ repaint() { /* * Start at the line currently at the top of the screen * and redisplay the screen. */ prepaint(position(TOP)); } /* * Jump to the end of the file. * It is more convenient to paint the screen backward, * from the end of the file toward the beginning. */ jump_forw() { off_t pos; if (ch_end_seek()) { error("Cannot seek to end of file"); return; } lastmark(); pos = ch_tell(); clear(); pos_clear(); add_back_pos(pos); back(sc_height - 1, pos, 0); } /* * Jump to line n in the file. */ jump_back(n) register int n; { register int c, nlines; /* * This is done the slow way, by starting at the beginning * of the file and counting newlines. * * {{ Now that we have line numbering (in linenum.c), * we could improve on this by starting at the * nearest known line rather than at the beginning. }} */ if (ch_seek((off_t)0)) { /* * Probably a pipe with beginning of file no longer buffered. * If he wants to go to line 1, we do the best we can, * by going to the first line which is still buffered. */ if (n <= 1 && ch_beg_seek() == 0) jump_loc(ch_tell()); error("Cannot get to beginning of file"); return; } /* * Start counting lines. */ for (nlines = 1; nlines < n; nlines++) while ((c = ch_forw_get()) != '\n') if (c == EOI) { char message[40]; (void)sprintf(message, "File has only %d lines", nlines - 1); error(message); return; } jump_loc(ch_tell()); } /* * Jump to a specified percentage into the file. * This is a poor compensation for not being able to * quickly jump to a specific line number. */ jump_percent(percent) int percent; { off_t pos, len, ch_length(); register int c; /* * Determine the position in the file * (the specified percentage of the file's length). */ if ((len = ch_length()) == NULL_POSITION) { error("Don't know length of file"); return; } pos = (percent * len) / 100; /* * Back up to the beginning of the line. */ if (ch_seek(pos) == 0) { while ((c = ch_back_get()) != '\n' && c != EOI) ; if (c == '\n') (void) ch_forw_get(); pos = ch_tell(); } jump_loc(pos); } /* * Jump to a specified position in the file. */ jump_loc(pos) off_t pos; { register int nline; off_t tpos; if ((nline = onscreen(pos)) >= 0) { /* * The line is currently displayed. * Just scroll there. */ forw(nline, position(BOTTOM_PLUS_ONE), 0); return; } /* * Line is not on screen. * Seek to the desired location. */ if (ch_seek(pos)) { error("Cannot seek to that position"); return; } /* * See if the desired line is BEFORE the currently displayed screen. * If so, then move forward far enough so the line we're on will be * at the bottom of the screen, in order to be able to call back() * to make the screen scroll backwards & put the line at the top of * the screen. * {{ This seems inefficient, but it's not so bad, * since we can never move forward more than a * screenful before we stop to redraw the screen. }} */ tpos = position(TOP); if (tpos != NULL_POSITION && pos < tpos) { off_t npos = pos; /* * Note that we can't forw_line() past tpos here, * so there should be no EOI at this stage. */ for (nline = 0; npos < tpos && nline < sc_height - 1; nline++) npos = forw_line(npos); if (npos < tpos) { /* * More than a screenful back. */ lastmark(); clear(); pos_clear(); add_back_pos(npos); } /* * Note that back() will repaint() if nline > back_scroll. */ back(nline, npos, 0); return; } /* * Remember where we were; clear and paint the screen. */ lastmark(); prepaint(pos); } /* * The table of marks. * A mark is simply a position in the file. */ #define NMARKS (27) /* 26 for a-z plus one for quote */ #define LASTMARK (NMARKS-1) /* For quote */ static off_t marks[NMARKS]; /* * Initialize the mark table to show no marks are set. */ init_mark() { int i; for (i = 0; i < NMARKS; i++) marks[i] = NULL_POSITION; } /* * See if a mark letter is valid (between a and z). */ static int badmark(c) int c; { if (c < 'a' || c > 'z') { error("Choose a letter between 'a' and 'z'"); return (1); } return (0); } /* * Set a mark. */ setmark(c) int c; { if (badmark(c)) return; marks[c-'a'] = position(TOP); } lastmark() { marks[LASTMARK] = position(TOP); } /* * Go to a previously set mark. */ gomark(c) int c; { off_t pos; if (c == '\'') { pos = marks[LASTMARK]; if (pos == NULL_POSITION) pos = 0; } else { if (badmark(c)) return; pos = marks[c-'a']; if (pos == NULL_POSITION) { error("mark not set"); return; } } jump_loc(pos); } /* * Get the backwards scroll limit. * Must call this function instead of just using the value of * back_scroll, because the default case depends on sc_height and * top_scroll, as well as back_scroll. */ get_back_scroll() { if (back_scroll >= 0) return (back_scroll); if (top_scroll) return (sc_height - 2); return (sc_height - 1); } /* * Search for the n-th occurence of a specified pattern, * either forward or backward. */ search(search_forward, pattern, n, wantmatch) register int search_forward; register char *pattern; register int n; int wantmatch; { off_t pos, linepos; register char *p; register char *q; int linenum; int linematch; #ifdef REGEX static regex_t *cpattern = NULL; #else #ifdef RECOMP char *re_comp(); char *errmsg; #else #ifdef REGCMP char *regcmp(); static char *cpattern = NULL; #else static char lpbuf[100]; static char *last_pattern = NULL; char *strcpy(); #endif #endif #endif /*REGEX */ /* * For a caseless search, convert any uppercase in the pattern to * lowercase. */ if (caseless && pattern != NULL) for (p = pattern; *p; p++) if (isupper(*p)) *p = tolower(*p); #ifdef REGEX if (pattern == NULL || *pattern == '\0') { /* * A null pattern means use the previous pattern. * The compiled previous pattern is in cpattern, so just use it. */ if (cpattern == NULL) { error("No previous regular expression"); return(0); } } else { /* * Otherwise compile the given pattern. */ if (cpattern == NULL && (cpattern = (regex_t *) malloc(sizeof(regex_t))) == NULL) { error("cannot allocate memory"); quit(); } else regfree(cpattern); if (regcomp(cpattern, pattern, 0)) { error("Invalid pattern"); return(0); } } #else #ifdef RECOMP /* * (re_comp handles a null pattern internally, * so there is no need to check for a null pattern here.) */ if ((errmsg = re_comp(pattern)) != NULL) { error(errmsg); return(0); } #else #ifdef REGCMP if (pattern == NULL || *pattern == '\0') { /* * A null pattern means use the previous pattern. * The compiled previous pattern is in cpattern, so just use it. */ if (cpattern == NULL) { error("No previous regular expression"); return(0); } } else { /* * Otherwise compile the given pattern. */ char *s; if ((s = regcmp(pattern, 0)) == NULL) { error("Invalid pattern"); return(0); } if (cpattern != NULL) free(cpattern); cpattern = s; } #else if (pattern == NULL || *pattern == '\0') { /* * Null pattern means use the previous pattern. */ if (last_pattern == NULL) { error("No previous regular expression"); return(0); } pattern = last_pattern; } else { (void)strcpy(lpbuf, pattern); last_pattern = lpbuf; } #endif #endif #endif /* REGEX */ /* * Figure out where to start the search. */ if (position(TOP) == NULL_POSITION) { /* * Nothing is currently displayed. Start at the beginning * of the file. (This case is mainly for searches from the * command line. */ pos = (off_t)0; } else if (!search_forward) { /* * Backward search: start just before the top line * displayed on the screen. */ pos = position(TOP); } else { /* * Start at the second screen line displayed on the screen. */ pos = position(TOP_PLUS_ONE); } if (pos == NULL_POSITION) { /* * Can't find anyplace to start searching from. */ error("Nothing to search"); return(0); } linenum = find_linenum(pos); for (;;) { /* * Get lines until we find a matching one or * until we hit end-of-file (or beginning-of-file * if we're going backwards). */ if (sigs) /* * A signal aborts the search. */ return(0); if (search_forward) { /* * Read the next line, and save the * starting position of that line in linepos. */ linepos = pos; pos = forw_raw_line(pos); if (linenum != 0) linenum++; } else { /* * Read the previous line and save the * starting position of that line in linepos. */ pos = back_raw_line(pos); linepos = pos; if (linenum != 0) linenum--; } if (pos == NULL_POSITION) { /* * We hit EOF/BOF without a match. */ error("Pattern not found"); return(0); } /* * If we're using line numbers, we might as well * remember the information we have now (the position * and line number of the current line). */ if (linenums) add_lnum(linenum, pos); /* * If this is a caseless search, convert uppercase in the * input line to lowercase. */ if (caseless) for (p = q = line; *p; p++, q++) *q = isupper(*p) ? tolower(*p) : *p; /* * Remove any backspaces along with the preceeding char. * This allows us to match text which is underlined or * overstruck. */ for (p = q = line; *p; p++, q++) if (q > line && *p == '\b') /* Delete BS and preceeding char. */ q -= 2; else /* Otherwise, just copy. */ *q = *p; /* * Test the next line to see if we have a match. * This is done in a variety of ways, depending * on what pattern matching functions are available. */ #ifdef REGEX linematch = !regexec(cpattern, line, 0, NULL, 0); #else #ifdef REGCMP linematch = (regex(cpattern, line) != NULL); #else #ifdef RECOMP linematch = (re_exec(line) == 1); #else linematch = match(pattern, line); #endif #endif #endif /* REGEX */ /* * We are successful if wantmatch and linematch are * both true (want a match and got it), * or both false (want a non-match and got it). */ if (((wantmatch && linematch) || (!wantmatch && !linematch)) && --n <= 0) /* * Found the line. */ break; } jump_loc(linepos); return(1); } #if !defined(REGCMP) && !defined(RECOMP) && !defined(RECOMP) /* * We have neither regcmp() nor re_comp(). * We use this function to do simple pattern matching. * It supports no metacharacters like *, etc. */ static match(pattern, buf) char *pattern, *buf; { register char *pp, *lp; for ( ; *buf != '\0'; buf++) { for (pp = pattern, lp = buf; *pp == *lp; pp++, lp++) if (*pp == '\0' || *lp == '\0') break; if (*pp == '\0') return (1); } return (0); } #endif