cleanups, add manual page
[unix-history] / usr / src / usr.bin / more / prim.c
/*
* 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 are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by Mark Nudleman and the University of California, Berkeley. The
* name of Mark Nudleman or the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef lint
static char sccsid[] = "@(#)prim.c 5.4 (Berkeley) %G%";
#endif /* not lint */
/*
* Primitives for displaying the file on the screen.
*/
#include "less.h"
#include "position.h"
public int hit_eof; /* Keeps track of how many times we hit end of file */
public int screen_trashed;
static int squished;
extern int quiet;
extern int sigs;
extern int how_search;
extern int top_scroll;
extern int back_scroll;
extern int sc_width, sc_height;
extern int quit_at_eof;
extern int caseless;
extern int linenums;
extern int plusoption;
extern char *line;
extern char *first_cmd;
extern int tagoption;
/*
* Sound the bell to indicate he is trying to move past end of file.
*/
static void
eof_bell()
{
if (quiet == NOT_QUIET)
bell();
else
vbell();
}
/*
* Check to see if the end of file is currently "displayed".
*/
static void
eof_check()
{
POSITION 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.
*/
static void
squish_check()
{
if (!squished)
return;
squished = 0;
repaint();
}
/*
* Display n lines, scrolling forward,
* starting at position pos in the input file.
* "force" means display the n lines even if we hit end of file.
* "only_last" means display only the last screenful if n > screen size.
*/
static void
forw(n, pos, force, only_last)
register int n;
POSITION pos;
int force;
int only_last;
{
int eof = 0;
int nlines = 0;
int do_repaint;
static int first_time = 1;
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. }}
*/
if (top_scroll == 2)
clear();
home();
force = 1;
} else
{
lower_left();
clear_eol();
}
if (pos != position(BOTTOM_PLUS_ONE))
{
/*
* This is not contiguous with what is
* currently displayed. Clear the screen image
* (position table) and start a new screen.
*/
pos_clear();
add_forw_pos(pos);
force = 1;
if (top_scroll)
{
if (top_scroll == 2)
clear();
home();
} else if (!first_time)
{
putstr("...skipping...\n");
}
}
}
while (--n >= 0)
{
/*
* Read the next line of input.
*/
pos = forw_line(pos);
if (pos == NULL_POSITION)
{
/*
* End of file: stop here unless the top line
* is still empty, or "force" is true.
*/
eof = 1;
if (!force && position(TOP) != NULL_POSITION)
break;
line = NULL;
}
/*
* Add the position of the next line to the position table.
* Display the current line on the screen.
*/
add_forw_pos(pos);
nlines++;
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 + option or a -t option
* was given. These options 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 && !plusoption)
{
squished = 1;
continue;
}
if (top_scroll == 1)
clear_eol();
put_line();
}
if (eof && !sigs)
hit_eof++;
else
eof_check();
if (nlines == 0)
eof_bell();
else if (do_repaint)
repaint();
first_time = 0;
(void) currline(BOTTOM);
}
/*
* Display n lines, scrolling backward.
*/
static void
back(n, pos, force, only_last)
register int n;
POSITION pos;
int force;
int only_last;
{
int nlines = 0;
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)
{
/*
* Beginning of file: stop here unless "force" is true.
*/
if (!force)
break;
line = NULL;
}
/*
* Add the position of the previous line to the position table.
* Display the line on the screen.
*/
add_back_pos(pos);
nlines++;
if (!do_repaint)
{
home();
add_line();
put_line();
}
}
eof_check();
if (nlines == 0)
eof_bell();
else 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.
*/
public void
forward(n, only_last)
int n;
int only_last;
{
POSITION pos;
if (quit_at_eof && hit_eof)
{
/*
* If the -e flag is set and 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)
{
eof_bell();
hit_eof++;
return;
}
forw(n, pos, 0, only_last);
}
/*
* Display n more lines, backward.
* Start just before the line currently displayed at the top of the screen.
*/
public void
backward(n, only_last)
int n;
int only_last;
{
POSITION pos;
pos = position(TOP);
if (pos == NULL_POSITION)
{
/*
* This will almost never happen,
* because the top line is almost never empty.
*/
eof_bell();
return;
}
back(n, pos, 0, only_last);
}
/*
* Repaint the screen, starting from a specified position.
*/
static void
prepaint(pos)
POSITION pos;
{
hit_eof = 0;
forw(sc_height-1, pos, 1, 0);
screen_trashed = 0;
}
/*
* Repaint the screen.
*/
public void
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.
*/
public void
jump_forw()
{
POSITION 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, 0);
}
/*
* Jump to line n in the file.
*/
public void
jump_back(n)
register int n;
{
register int c;
int 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((POSITION)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.
*/
public void
jump_percent(percent)
int percent;
{
POSITION pos, len;
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.
*/
public void
jump_loc(pos)
POSITION pos;
{
register int nline;
POSITION tpos;
if ((nline = onscreen(pos)) >= 0)
{
/*
* The line is currently displayed.
* Just scroll there.
*/
forw(nline, position(BOTTOM_PLUS_ONE), 1, 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)
{
POSITION 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, 1, 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 POSITION marks[NMARKS];
/*
* Initialize the mark table to show no marks are set.
*/
public void
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.
*/
public void
setmark(c)
int c;
{
if (badmark(c))
return;
marks[c-'a'] = position(TOP);
}
public void
lastmark()
{
marks[LASTMARK] = position(TOP);
}
/*
* Go to a previously set mark.
*/
public void
gomark(c)
int c;
{
POSITION pos;
if (c == '\'')
pos = marks[LASTMARK];
else if (badmark(c))
return;
else
pos = marks[c-'a'];
if (pos == NULL_POSITION)
error("mark not set");
else
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.
*/
public int
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.
*/
public void
search(search_forward, pattern, n, wantmatch)
register int search_forward;
register char *pattern;
register int n;
int wantmatch;
{
POSITION pos, linepos;
register char *p;
register char *q;
int linenum;
int linematch;
#if RECOMP
char *re_comp();
char *errmsg;
#else
#if REGCMP
char *regcmp();
static char *cpattern = NULL;
#else
static char lpbuf[100];
static char *last_pattern = NULL;
char *strcpy();
#endif
#endif
if (caseless && pattern != NULL)
{
/*
* For a caseless search, convert any uppercase
* in the pattern to lowercase.
*/
for (p = pattern; *p != '\0'; p++)
if (*p >= 'A' && *p <= 'Z')
*p += 'a' - 'A';
}
#if 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;
}
#else
#if 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;
}
} else
{
/*
* Otherwise compile the given pattern.
*/
char *s;
if ((s = regcmp(pattern, 0)) == NULL)
{
error("Invalid pattern");
return;
}
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;
}
pattern = last_pattern;
} else
{
(void)strcpy(lpbuf, pattern);
last_pattern = lpbuf;
}
#endif
#endif
/*
* 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 first_cmd searches,
* for example, "+/xyz" on the command line.)
*/
pos = (POSITION)0;
} else if (!search_forward)
{
/*
* Backward search: start just before the top line
* displayed on the screen.
*/
pos = position(TOP);
} else if (how_search == 0)
{
/*
* Start at the second real line displayed on the screen.
*/
pos = position(TOP);
do
pos = forw_raw_line(pos);
while (pos < position(TOP+1));
} else if (how_search == 1)
{
/*
* Start just after the bottom line displayed on the screen.
*/
pos = position(BOTTOM_PLUS_ONE);
} 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;
}
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;
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;
}
/*
* 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 (caseless)
{
/*
* If this is a caseless search, convert
* uppercase in the input line to lowercase.
* While we're at it, remove any backspaces
* along with the preceeding char.
* This allows us to match text which is
* underlined or overstruck.
*/
for (p = q = line; *p != '\0'; p++, q++)
{
if (*p >= 'A' && *p <= 'Z')
/* Convert uppercase to lowercase. */
*q = *p + 'a' - 'A';
else 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.
*/
#if REGCMP
linematch = (regex(cpattern, line) != NULL);
#else
#if RECOMP
linematch = (re_exec(line) == 1);
#else
linematch = match(pattern, line);
#endif
#endif
/*
* 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);
}
#if (!REGCMP) && (!RECOMP)
/*
* We have neither regcmp() nor re_comp().
* We use this function to do simple pattern matching.
* It supports no metacharacters like *, etc.
*/
static int
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