Commands are based on both more and vi. Commands may be preceeded by a decimal number, called N in the descriptions below. The number is used by some commands, as indicated.
The following two commands may or may not be valid, depending on your particular installation.
Options are also taken from the environment variable "LESS". For example, if you like more-style prompting, to avoid typing "less -m ..." each time less is invoked, you might tell csh:
setenv LESS m
or if you use sh:
LESS=m; export LESS
The environment variable is parsed before the command line, so command line options override the LESS environment variable. A dollar sign ($) may be used to signal the end of an option string. This is important only for options like -P which take a following string.
If neither -u nor -U is given, backspaces which appear adjacent to an underscore character are treated specially: the underlined text is displayed using the terminal's hardware underlining capability. Also, backspaces which appear between two identical characters are treated specially: the overstruck text is printed using the terminal's hardware boldface capability. Other backspaces are deleted, along with the preceeding character.
echo main.c
cat >main.c <<'_SHAR_EOF_'
/*
* Entry point, initialization, miscellaneous routines.
*/
#include "less.h" #include "position.h" #include <setjmp.h>
public int ispipe;
public jmp_buf main_loop;
public char * first_cmd;
public char * every_first_cmd;
public int new_file;
public int is_tty;
public char current_file[FILENAME];
public char previous_file[FILENAME];
public POSITION prev_pos;
public int any_display;
public int ac;
public char ** av;
public int curr_ac;
#if LOGFILE
public int logfile = -1;
public int force_logfile = 0;
public char * namelogfile = NULL;
#endif
#if EDITOR
public char * editor;
#endif
extern int file; extern int nbufs; extern int sigs; extern int quit_at_eof; extern int p_nbufs, f_nbufs; extern int back_scroll; extern int top_scroll; extern int sc_height; extern int errmsgs;
/*
* Edit a new file.
* Filename "-" means standard input.
* No filename means the "current" file, from the command line.
*/
public void
edit(filename)
register char *filename;
{
register int f;
register char *m;
POSITION initial_pos;
char message[100];
char tempfile[FILENAME];
static int didpipe;
initial_pos = NULL_POSITION;
if (filename == NULL || *filename == ' ')
{
if (curr_ac >= ac)
{
error("No current file");
return;
}
filename = av[curr_ac];
}
if (strcmp(filename, "#") == 0)
{
if (*previous_file == ' ')
{
error("no previous file");
return;
}
strtcpy(tempfile, previous_file, sizeof(tempfile));
filename = tempfile;
initial_pos = prev_pos;
}
if (strcmp(filename, "-") == 0)
{
/*
* Use standard input.
*/
if (didpipe)
{
error("Can view standard input only once");
return;
}
f = 0;
} else if ((m = bad_file(filename, message, sizeof(message))) != NULL)
{
error(m);
return;
} else if ((f = open(filename, 0)) < 0)
{
error(errno_message(filename, message, sizeof(message)));
return;
}
if (isatty(f))
{
/*
* Not really necessary to call this an error,
* but if the control terminal (for commands)
* and the input file (for data) are the same,
* we get weird results at best.
*/
error("Can't take input from a terminal");
if (f > 0)
close(f);
return;
}
#if LOGFILE
/*
* If he asked for a log file and we have opened standard input,
* create the log file.
* We take care not to blindly overwrite an existing file.
*/
end_logfile();
if (f == 0 && namelogfile != NULL && is_tty)
{
int exists;
int answer;
/*
* {{ We could use access() here. }}
*/
exists = open(namelogfile, 0);
close(exists);
exists = (exists >= 0);
if (exists && !force_logfile)
{
static char w[] = "WARNING: log file exists: ";
strcpy(message, w);
strtcpy(message+sizeof(w)-1, namelogfile,
sizeof(message)-sizeof(w));
error(message);
answer = 'X'; /* Ask the user what to do */
} else
answer = 'O'; /* Create the log file */
loop:
switch (answer)
{
case 'O': case 'o':
logfile = creat(namelogfile, 0644);
break;
case 'A': case 'a':
logfile = open(namelogfile, 1);
if (lseek(logfile, (offset_t)0, 2) < 0)
{
close(logfile);
logfile = -1;
}
break;
case 'D': case 'd':
answer = 0; /* Don't print an error message */
break;
case 'q':
quit();
default:
putstr(" Overwrite, Append, or Don't log? ");
answer = getchr();
putstr(");
flush();
goto loop;
}
if (logfile < 0 && answer != 0)
{
sprintf(message, "Cannot write to namelogfile);
error(message);
}
}
#endif
/*
* We are now committed to using the new file.
* Close the current input file and set up to use the new one.
*/
if (file > 0)
close(file);
new_file = 1;
strtcpy(previous_file, current_file, sizeof(previous_file));
strtcpy(current_file, filename, sizeof(current_file));
prev_pos = position(TOP);
ispipe = (f == 0);
if (ispipe)
didpipe = 1;
file = f;
ch_init( (ispipe) ? p_nbufs : f_nbufs );
init_mark();
if (every_first_cmd != NULL)
first_cmd = every_first_cmd;
if (is_tty)
{
int no_display = !any_display;
any_display = 1;
if (no_display && errmsgs > 0)
{
/*
* We displayed some messages on error output
* (file descriptor 2; see error() function).
* Before erasing the screen contents,
* display the file name and wait for a keystroke.
*/
error(filename);
}
/*
* Indicate there is nothing displayed yet.
*/
pos_clear();
if (initial_pos != NULL_POSITION)
jump_loc(initial_pos);
}
}
/*
* Edit the next file in the command line list.
*/
public void
next_file(n)
int n;
{
if (curr_ac + n >= ac)
{
if (quit_at_eof)
quit();
error("No (N-th) next file");
} else
edit(av[curr_ac += n]);
}
/*
* Edit the previous file in the command line list.
*/
public void
prev_file(n)
int n;
{
if (curr_ac - n < 0)
error("No (N-th) previous file");
else
edit(av[curr_ac -= n]);
}
/*
* Copy a file directly to standard output.
* Used if standard output is not a tty.
*/
static void
cat_file()
{
register int c;
while ((c = ch_forw_get()) != EOF)
putchr(c);
flush();
}
/*
* Entry point.
*/
main(argc, argv)
int argc;
char *argv[];
{
char *getenv();
/*
* Process command line arguments and LESS environment arguments.
* Command line arguments override environment arguments.
*/
init_option();
scan_option(getenv("LESS"));
argv++;
while ( (--argc > 0) &&
(argv[0][0] == '-' || argv[0][0] == '+') &&
argv[0][1] != ' ')
scan_option(*argv++);
#if EDITOR
editor = getenv("EDITOR");
if (editor == NULL || *editor == ' ')
editor = EDIT_PGM;
#endif
/*
* Set up list of files to be examined.
*/
ac = argc;
av = argv;
curr_ac = 0;
/*
* Set up terminal, etc.
*/
is_tty = isatty(1);
if (!is_tty)
{
/*
* Output is not a tty.
* Just copy the input file(s) to output.
*/
if (ac < 1)
{
edit("-");
cat_file();
} else
{
do
{
edit((char *)NULL);
if (file >= 0)
cat_file();
} while (++curr_ac < ac);
}
exit(0);
}
raw_mode(1);
get_term();
open_getchr();
init();
if (setjmp(main_loop))
quit();
init_signals();
/*
* Select the first file to examine.
*/
if (ac < 1)
edit("-"); /* Standard input */
else
{
/*
* Try all the files named as command arguments.
* We are simply looking for one which can be
* opened without error.
*/
do
{
edit((char *)NULL);
} while (file < 0 && ++curr_ac < ac);
}
if (file >= 0)
commands();
quit();
/*NOTREACHED*/
}
/*
* Copy a string, truncating to the specified length if necessary.
* Unlike strncpy(), the resulting string is guaranteed to be null-terminated.
*/
strtcpy(to, from, len)
char *to;
char *from;
int len;
{
strncpy(to, from, len);
to[len-1] = ' ';
}
/*
* Exit the program.
*/
public void
quit()
{
/*
* Put cursor at bottom left corner, clear the line,
* reset the terminal modes, and exit.
*/
#if LOGFILE
end_logfile();
#endif
lower_left();
clear_eol();
deinit();
flush();
raw_mode(0);
exit(0);
}
_SHAR_EOF_
echo option.c
cat >option.c <<'_SHAR_EOF_'
/*
* Process command line options.
* Each option is a single letter which controls a program variable.
* The options have defaults which may be changed via
* the command line option, or toggled via the "-" command.
*/
#include "less.h"
#define toupper(c) ((c)-'a'+'A')
#define END_OPTION_STRING ('$')
/*
* Types of options.
*/
#define BOOL 01 /* Boolean option: 0 or 1 */
#define TRIPLE 02 /* Triple-valued option: 0, 1 or 2 */
#define NUMBER 04 /* Numeric option */
#define REPAINT 040 /* Repaint screen after toggling option */
#define NO_TOGGLE 0100 /* Option cannot be toggled with "-" cmd */
/*
* Variables controlled by command line options.
*/
public int p_nbufs, f_nbufs; /* Number of buffers. There are two values,
one used for input from a pipe and
the other for input from a file. */
public int clean_data; /* Can we assume the data is "clean"?
(That is, free of nulls, etc) */
public int quiet; /* Should we suppress the audible bell? */
public int top_search; /* Should forward searches start at the top
of the screen? (alternative is bottom) */
public int top_scroll; /* Repaint screen from top?
(alternative is scroll from bottom) */
public int pr_type; /* Type of prompt (short, medium, long) */
public int bs_mode; /* How to process backspaces */
public int know_dumb; /* Don't complain about dumb terminals */
public int quit_at_eof; /* Quit after hitting end of file twice */
public int squeeze; /* Squeeze multiple blank lines into one */
public int tabstop; /* Tab settings */
public int back_scroll; /* Repaint screen on backwards movement */
public int twiddle; /* Display "~" for lines after EOF */
extern char *prproto[]; extern int nbufs; extern int sc_window; extern char *first_cmd; extern char *every_first_cmd; #if LOGFILE extern char *namelogfile; extern int force_logfile; #endif
#define DEF_F_NBUFS 5 /* Default for f_nbufs */
#define DEF_P_NBUFS 12 /* Default for p_nbufs */
static struct option
{
char oletter; /* The controlling letter (a-z) */
char otype; /* Type of the option */
int odefault; /* Default value */
int *ovar; /* Pointer to the associated variable */
char *odesc[3]; /* Description of each value */
} option[] =
{
{ 'c', TRIPLE, 0, &top_scroll,
{ "Repaint by scrolling from bottom of screen",
"Repaint by clearing each line",
"Repaint by painting from top of screen"
}
},
{ 'd', BOOL|NO_TOGGLE, 0, &know_dumb,
{ NULL, NULL, NULL}
},
{ 'e', TRIPLE, 0, &quit_at_eof,
{ "Don't quit at end-of-file",
"Quit at end-of-file",
"Quit immediately at end-of-file"
}
},
{ 'f', BOOL, 0, &clean_data,
{ "Don't assume data is clean",
"Assume data is clean",
NULL
}
},
{ 'h', NUMBER, -1, &back_scroll,
{ "Backwards scroll limit is %d lines",
NULL, NULL
}
},
{ 'm', TRIPLE, 0, &pr_type,
{ "Short prompt",
"Medium prompt",
"Long prompt"
}
},
{ 'q', TRIPLE, 0, &quiet,
{ "Ring the bell for errors AND at eof/bof",
"Ring the bell for errors but not at eof/bof",
"Never ring the bell"
}
},
{ 'u', TRIPLE|REPAINT, 0, &bs_mode,
{ "Underlined text displayed in underline mode",
"Backspaces cause overstrike",
"Backspaces print as ^H"
}
},
{ 's', BOOL|REPAINT, 0, &squeeze,
{ "Don't squeeze multiple blank lines",
"Squeeze multiple blank lines",
NULL
}
},
{ 't', BOOL, 1, &top_search,
{ "Forward search starts from bottom of screen",
"Forward search starts from top of screen",
NULL
}
},
{ 'w', BOOL|REPAINT, 1, &twiddle,
{ "Display nothing for lines after end-of-file",
"Display ~ for lines after end-of-file",
NULL
}
},
{ 'x', NUMBER|REPAINT, 8, &tabstop,
{ "Tab stops every %d spaces",
NULL, NULL
}
},
{ 'z', NUMBER|REPAINT, -1, &sc_window,
{ "Scroll window size is %d lines",
NULL, NULL
}
},
{ ' ' }
};
public char all_options[64]; /* List of all valid options */
/*
* Initialize each option to its default value.
*/
public void
init_option()
{
register struct option *o;
register char *p;
/*
* First do special cases, not in option table.
*/
first_cmd = every_first_cmd = NULL;
f_nbufs = DEF_F_NBUFS; /* -bf */
p_nbufs = DEF_P_NBUFS; /* -bp */
p = all_options;
*p++ = 'b';
for (o = option; o->oletter != ' '; o++)
{
/*
* Set each variable to its default.
* Also make a list of all options, in "all_options".
*/
*(o->ovar) = o->odefault;
*p++ = o->oletter;
if (o->otype & TRIPLE)
*p++ = toupper(o->oletter);
}
*p = ' ';
}
/*
* Toggle command line flags from within the program.
* Used by the "-" command.
*/
public void
toggle_option(s)
char *s;
{
int c;
register struct option *o;
char *msg;
int n;
int dorepaint;
char message[100];
char buf[5];
c = *s++;
/*
* First check for special cases not handled by the option table.
*/
switch (c)
{
case 'b':
sprintf(message, "%d buffers", nbufs);
error(message);
return;
}
msg = NULL;
for (o = option; o->oletter != ' '; o++)
{
if (o->otype & NO_TOGGLE)
continue;
dorepaint = (o->otype & REPAINT);
if ((o->otype & BOOL) && (o->oletter == c))
{
/*
* Boolean option:
* just toggle it.
*/
*(o->ovar) = ! *(o->ovar);
} else if ((o->otype & TRIPLE) && (o->oletter == c))
{
/*
* Triple-valued option with lower case letter:
* make it 1 unless already 1, then make it 0.
*/
*(o->ovar) = (*(o->ovar) == 1) ? 0 : 1;
} else if ((o->otype & TRIPLE) && (toupper(o->oletter) == c))
{
/*
* Triple-valued option with upper case letter:
* make it 2 unless already 2, then make it 0.
*/
*(o->ovar) = (*(o->ovar) == 2) ? 0 : 2;
} else if ((o->otype & NUMBER) && (o->oletter == c))
{
n = getnum(&s, ' ');
if (n < 0)
{
/*
* No number; just a query.
* No need to repaint screen.
*/
dorepaint = 0;
} else
{
/*
* Number follows the option letter.
* Set the variable to that number.
*/
*(o->ovar) = n;
}
sprintf(message, o->odesc[0],
(o->ovar == &back_scroll) ?
get_back_scroll() : *(o->ovar));
msg = message;
} else
continue;
if (dorepaint)
repaint();
if (msg == NULL)
msg = o->odesc[*(o->ovar)];
error(msg);
return;
}
if (control_char(c))
sprintf(buf, "^%c", carat_char(c));
else
sprintf(buf, "%c", c);
sprintf(message, " buf, all_options);
error(message);
}
/*
* Scan to end of string or to an END_OPTION_STRING character.
* In the latter case, replace the char with a null char.
* Return a pointer to the remainder of the string, if any.
*/
static char *
optstring(s)
char *s;
{
register char *p;
for (p = s; *p != ' '; p++)
if (*p == END_OPTION_STRING)
{
*p = ' ';
return (p+1);
}
return (p);
}
/*
* Scan an argument (either from command line or from LESS environment
* variable) and process it.
*/
public void
scan_option(s)
char *s;
{
register struct option *o;
register int c;
char message[80];
if (s == NULL)
return;
next:
if (*s == ' ')
return;
switch (c = *s++)
{
case '-':
case ' ':
case ' ':
case END_OPTION_STRING:
goto next;
case '+':
if (*s == '+')
every_first_cmd = ++s;
first_cmd = s;
s = optstring(s);
goto next;
case 'P':
switch (*s)
{
case 'm': prproto[PR_MEDIUM] = ++s; break;
case 'M': prproto[PR_LONG] = ++s; break;
default: prproto[PR_SHORT] = s; break;
}
s = optstring(s);
goto next;
#if LOGFILE
case 'L':
force_logfile = 1;
/* fall thru */
case 'l':
namelogfile = s;
s = optstring(s);
goto next;
#endif
case 'b':
switch (*s)
{
case 'f':
s++;
f_nbufs = getnum(&s, 'b');
break;
case 'p':
s++;
p_nbufs = getnum(&s, 'b');
break;
default:
f_nbufs = p_nbufs = getnum(&s, 'b');
break;
}
goto next;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
{
/*
* Handle special "more" compatibility form "-number"
* to set the scrolling window size.
*/
s--;
sc_window = getnum(&s, '-');
goto next;
}
}
for (o = option; o->oletter != ' '; o++)
{
if ((o->otype & BOOL) && (o->oletter == c))
{
*(o->ovar) = ! o->odefault;
goto next;
} else if ((o->otype & TRIPLE) && (o->oletter == c))
{
*(o->ovar) = (o->odefault == 1) ? 0 : 1;
goto next;
} else if ((o->otype & TRIPLE) && (toupper(o->oletter) == c))
{
*(o->ovar) = (o->odefault == 2) ? 0 : 2;
goto next;
} else if ((o->otype & NUMBER) && (o->oletter == c))
{
*(o->ovar) = getnum(&s, c);
goto next;
}
}
sprintf(message, " error(message);
exit(1);
}
/*
* Translate a string into a number.
* Like atoi(), but takes a pointer to a char *, and updates
* the char * to point after the translated number.
*/
static int
getnum(sp, c)
char **sp;
int c;
{
register char *s;
register int n;
char message[80];
s = *sp;
if (*s < '0' || *s > '9')
{
if (c == ' ')
return (-1);
sprintf(message, "number is required after -%c", c);
error(message);
exit(1);
}
n = 0;
while (*s >= '0' && *s <= '9')
n = 10 * n + *s++ - '0';
*sp = s;
return (n);
}
_SHAR_EOF_
echo prim.c
cat >prim.c <<'_SHAR_EOF_'
/*
* 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 */
extern int quiet; extern int top_search; extern int top_scroll; extern int back_scroll; extern int sc_width, sc_height; extern int sigs; extern int quit_at_eof; extern int ac; extern char *line; extern char *first_cmd;
/*
* 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 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++;
}
/*
* 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;
/*
* 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...);
}
}
}
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 ||
(first_time && line == NULL && !top_scroll))
continue;
if (top_scroll == 1)
clear_eol();
put_line();
}
if (eof)
hit_eof++;
else
eof_check();
if (nlines == 0)
eof_bell();
else if (do_repaint)
repaint();
if (first_time && hit_eof && quit_at_eof && ac <= 1)
quit();
first_time = 0;
}
/*
* 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;
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();
}
/*
* 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;
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);
}
/*
* 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.
*/
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()) != ')
if (c == EOF)
{
char message[40];
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()) != ' && c != EOF)
;
if (c == ')
(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;
/*
* See if the desired line is BEFORE the currently
* displayed screen. If so, see if it is close enough
* to scroll backwards to it.
* {{ This can be expensive if he has specified a very
* large back_scroll count. Perhaps we should put
* some sanity limit on the loop count here. }}
*/
tpos = position(TOP);
if (tpos != NULL_POSITION && pos < tpos)
{
int bs = get_back_scroll();
for (nline = 1; nline <= bs; nline++)
{
tpos = back_line(tpos);
if (tpos == NULL_POSITION)
break;
if (tpos <= pos)
{
back(nline, position(TOP), 1, 0);
return;
}
}
} else 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.
* Remember where we were; clear and paint the screen.
*/
if (ch_seek(pos))
{
error("Cannot seek to that position");
return;
}
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 (direction == '/'), or backwards (direction == '?').
*/
public void
search(direction, pattern, n)
int direction;
char *pattern;
register int n;
{
register int search_forward = (direction == '/');
POSITION pos, linepos;
#if RECOMP
char *re_comp();
char *errmsg;
/*
* (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
char *regcmp();
static char *cpattern = NULL;
if (pattern == NULL || *pattern == ' ')
{
/*
* 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
static char lpbuf[100];
static char *last_pattern = NULL;
if (pattern == NULL || *pattern == ' ')
{
/*
* Null pattern means use the previous pattern.
*/
if (last_pattern == NULL)
{
error("No previous regular expression");
return;
}
pattern = last_pattern;
} else
{
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 (top_search)
{
/*
* Forward search and "start from top".
* Start at the second line displayed on the screen.
*/
pos = position(TOP_PLUS_ONE);
} else
{
/*
* Forward search but don't "start from top".
* Start just after the bottom line displayed on the screen.
*/
pos = position(BOTTOM_PLUS_ONE);
}
if (pos == NULL_POSITION)
{
/*
* Can't find anyplace to start searching from.
*/
error("Nothing to search");
return;
}
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);
} else
{
/*
* Read the previous line and save the
* starting position of that line in linepos.
*/
pos = back_raw_line(pos);
linepos = pos;
}
if (pos == NULL_POSITION)
{
/*
* We hit EOF/BOF without a match.
*/
error("Pattern not found");
return;
}
/*
* 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
if ( (regex(cpattern, line) != NULL)
#else
#if RECOMP
if ( (re_exec(line) == 1)
#else
if ( (match(pattern, line))
#endif
#endif
&& (--n <= 0) )
/*
* Found the matching 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 != ' '; buf++)
{
for (pp = pattern, lp = buf; *pp == *lp; pp++, lp++)
if (*pp == ' ' || *lp == ' ')
break;
if (*pp == ' ')
return (1);
}
return (0);
}
#endif
_SHAR_EOF_
echo ch.c
cat >ch.c <<'_SHAR_EOF_'
/*
* Low level character input from the input file.
* We use these special purpose routines which optimize moving
* both forward and backward from the current read pointer.
*/
#include "less.h"
public int file = -1; /* File descriptor of the input file */
/*
* Pool of buffers holding the most recently used blocks of the input file.
*/
#define BUFSIZ 1024
struct buf {
struct buf *next, *prev;
long block;
char data[BUFSIZ];
};
static struct buf *bufs = NULL;
public int nbufs;
/*
* The buffer pool is kept as a doubly-linked circular list,
* in order from most- to least-recently used.
* The circular list is anchored by buf_anchor.
*/
static struct {
struct buf *next, *prev;
} buf_anchor;
#define END_OF_CHAIN ((struct buf *)&buf_anchor)
#define buf_head buf_anchor.next
#define buf_tail buf_anchor.prev
/*
* If we fail to allocate enough memory for buffers, we try to limp
* along with a minimum number of buffers.
*/
#define DEF_NBUFS 2 /* Minimum number of buffers */
extern int clean_data; extern int ispipe; extern int sigs;
#if LOGFILE extern int logfile; #endif
/*
* Current position in file.
* Stored as a block number and an offset into the block.
*/
static long ch_block;
static int ch_offset;
/*
* Length of file, needed if input is a pipe.
*/
static POSITION ch_fsize;
/*
* Largest block number read if input is standard input (a pipe).
*/
static long last_piped_block;
/*
* Get the character pointed to by the read pointer.
* ch_get() is a macro which is more efficient to call
* than fch_get (the function), in the usual case
* that the block desired is at the head of the chain.
*/
#define ch_get() ((buf_head->block == ch_block) ? buf_head->data[ch_offset] : fch_get())
static int
fch_get()
{
register struct buf *bp;
register int n;
register int end;
POSITION pos;
/*
* Look for a buffer holding the desired block.
*/
for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next)
if (bp->block == ch_block)
goto found;
/*
* Block is not in a buffer.
* Take the least recently used buffer
* and read the desired block into it.
*/
bp = buf_tail;
bp->block = ch_block;
pos = ch_block * BUFSIZ;
if (ispipe)
{
/*
* The block requested should be one more than
* the last block read.
*/
if (ch_block != ++last_piped_block)
{
/* This "should not happen". */
char message[80];
sprintf(message, "Pipe error: last %ld, want %ld,
(long)last_piped_block-1, (long)ch_block);
error(message);
quit();
}
} else
lseek(file, pos, 0);
/*
* Read the block. This may take several reads if the input
* is coming from standard input, due to the nature of pipes.
*/
end = 0;
while ((n = read(file, &bp->data[end], BUFSIZ-end)) > 0)
if ((end += n) >= BUFSIZ)
break;
if (n < 0)
{
error("read error");
quit();
}
#if LOGFILE
/*
* If we have a log file, write this block to it.
*/
if (logfile >= 0 && end > 0)
write(logfile, bp->data, end);
#endif
/*
* Set an EOF marker in the buffered data itself.
* Then ensure the data is "clean": there are no
* extra EOF chars in the data and that the "meta"
* bit (the 0200 bit) is reset in each char.
*/
if (end < BUFSIZ)
{
ch_fsize = pos + end;
bp->data[end] = EOF;
}
if (!clean_data)
while (--end >= 0)
{
bp->data[end] &= 0177;
if (bp->data[end] == EOF)
bp->data[end] = '@';
}
found:
/* if (buf_head != bp) {this is guaranteed by the ch_get macro} */
{
/*
* Move the buffer to the head of the buffer chain.
* This orders the buffer chain, most- to least-recently used.
*/
bp->next->prev = bp->prev;
bp->prev->next = bp->next;
bp->next = buf_head;
bp->prev = END_OF_CHAIN;
buf_head->prev = bp;
buf_head = bp;
}
return (bp->data[ch_offset]);
}
#if LOGFILE
/*
* Close the logfile.
* If we haven't read all of standard input into it, do that now.
*/
public void
end_logfile()
{
static int tried;
if (logfile < 0)
return;
if (!tried && ch_fsize == NULL_POSITION)
{
tried = 1;
lower_left();
clear_eol();
so_enter();
putstr("finishing logfile... (interrupt to abort)");
so_exit();
flush();
while (sigs == 0 && ch_forw_get() != EOF)
;
}
close(logfile);
logfile = -1;
}
#endif
/*
* Determine if a specific block is currently in one of the buffers.
*/
static int
buffered(block)
long block;
{
register struct buf *bp;
for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next)
if (bp->block == block)
return (1);
return (0);
}
/*
* Seek to a specified position in the file.
* Return 0 if successful, non-zero if can't seek there.
*/
public int
ch_seek(pos)
register POSITION pos;
{
long new_block;
new_block = pos / BUFSIZ;
if (!ispipe || new_block == last_piped_block + 1 || buffered(new_block))
{
/*
* Set read pointer.
*/
ch_block = new_block;
ch_offset = pos % BUFSIZ;
return (0);
}
return (1);
}
/*
* Seek to the end of the file.
*/
public int
ch_end_seek()
{
if (ispipe)
{
/*
* Do it the slow way: read till end of data.
*/
while (ch_forw_get() != EOF)
;
} else
{
(void) ch_seek((POSITION)(lseek(file, (offset_t)0, 2)));
}
return (0);
}
/*
* Seek to the beginning of the file, or as close to it as we can get.
* We may not be able to seek there if input is a pipe and the
* beginning of the pipe is no longer buffered.
*/
public int
ch_beg_seek()
{
register struct buf *bp, *firstbp;
/*
* Try a plain ch_seek first.
*/
if (ch_seek((POSITION)0) == 0)
return (0);
/*
* Can't get to position 0.
* Look thru the buffers for the one closest to position 0.
*/
firstbp = bp = buf_head;
if (bp == END_OF_CHAIN)
return (1);
while ((bp = bp->next) != END_OF_CHAIN)
if (bp->block < firstbp->block)
firstbp = bp;
ch_block = firstbp->block;
ch_offset = 0;
return (0);
}
/*
* Return the length of the file, if known.
*/
public POSITION
ch_length()
{
if (ispipe)
return (ch_fsize);
return ((POSITION)(lseek(file, (offset_t)0, 2)));
}
/*
* Return the current position in the file.
*/
public POSITION
ch_tell()
{
return (ch_block * BUFSIZ + ch_offset);
}
/*
* Get the current char and post-increment the read pointer.
*/
public int
ch_forw_get()
{
register int c;
c = ch_get();
if (c != EOF && ++ch_offset >= BUFSIZ)
{
ch_offset = 0;
ch_block ++;
}
return (c);
}
/*
* Pre-decrement the read pointer and get the new current char.
*/
public int
ch_back_get()
{
register int c;
if (--ch_offset < 0)
{
if (ch_block <= 0 || (ispipe && !buffered(ch_block-1)))
{
ch_offset = 0;
return (EOF);
}
ch_offset = BUFSIZ - 1;
ch_block--;
}
c = ch_get();
return (c);
}
/*
* Initialize the buffer pool to all empty.
* Caller suggests that we use want_nbufs buffers.
*/
public void
ch_init(want_nbufs)
int want_nbufs;
{
register struct buf *bp;
char *calloc();
if (nbufs < want_nbufs)
{
/*
* We don't have enough buffers.
* Free what we have (if any) and allocate some new ones.
*/
if (bufs != NULL)
free((char *)bufs);
bufs = (struct buf *) calloc(want_nbufs, sizeof(struct buf));
nbufs = want_nbufs;
if (bufs == NULL)
{
/*
* Couldn't get that many.
* Try for a small default number of buffers.
*/
char message[80];
sprintf(message,
"Cannot allocate %d buffers. Using %d buffers.",
nbufs, DEF_NBUFS);
error(message);
bufs = (struct buf *) calloc(DEF_NBUFS, sizeof(struct buf));
nbufs = DEF_NBUFS;
if (bufs == NULL)
{
/*
* Couldn't even get the smaller number of bufs.
* Something is wrong here, don't continue.
*/
sprintf(message,
"Cannot even allocate %d buffers! Quitting.",
DEF_NBUFS);
error(message);
quit();
/*NOTREACHED*/
}
}
}
/*
* Initialize the buffers to empty.
* Set up the circular list.
*/
for (bp = &bufs[0]; bp < &bufs[nbufs]; bp++)
{
bp->next = bp + 1;
bp->prev = bp - 1;
bp->block = (long)(-1);
}
bufs[0].prev = bufs[nbufs-1].next = END_OF_CHAIN;
buf_head = &bufs[0];
buf_tail = &bufs[nbufs-1];
last_piped_block = -1;
ch_fsize = NULL_POSITION;
(void) ch_seek((POSITION)0);
}
_SHAR_EOF_
echo position.c
cat >position.c <<'_SHAR_EOF_'
/*
* Routines dealing with the "position" table.
* This is a table which tells the position (in the input file) of the
* first char on each currently displayed line.
*
* {{ The position table is scrolled by moving all the entries.
* Would be better to have a circular table
* and just change a couple of pointers. }}
*/
#include "less.h" #include "position.h"
#define NPOS 100 /* {{ sc_height must be less than NPOS }} */
static POSITION table[NPOS]; /* The position table */
extern int sc_width, sc_height;
/*
* Return the starting file position of a line displayed on the screen.
* The line may be specified as a line number relative to the top
* of the screen, but is usually one of these special cases:
* the top (first) line on the screen
* the second line on the screen
* the bottom line on the screen
* the line after the bottom line on the screen
*/
public POSITION
position(where)
int where;
{
switch (where)
{
case BOTTOM:
where = sc_height - 2;
break;
case BOTTOM_PLUS_ONE:
where = sc_height - 1;
break;
}
return (table[where]);
}
/*
* Add a new file position to the bottom of the position table.
*/
public void
add_forw_pos(pos)
POSITION pos;
{
register int i;
/*
* Scroll the position table up.
*/
for (i = 1; i < sc_height; i++)
table[i-1] = table[i];
table[sc_height - 1] = pos;
}
/*
* Add a new file position to the top of the position table.
*/
public void
add_back_pos(pos)
POSITION pos;
{
register int i;
/*
* Scroll the position table down.
*/
for (i = sc_height - 1; i > 0; i--)
table[i] = table[i-1];
table[0] = pos;
}
/*
* Initialize the position table, done whenever we clear the screen.
*/
public void
pos_clear()
{
register int i;
for (i = 0; i < sc_height; i++)
table[i] = NULL_POSITION;
}
/*
* See if the byte at a specified position is currently on the screen.
* Check the position table to see if the position falls within its range.
* Return the position table entry if found, -1 if not.
*/
public int
onscreen(pos)
POSITION pos;
{
register int i;
if (pos < table[0])
return (-1);
for (i = 1; i < sc_height; i++)
if (pos < table[i])
return (i-1);
return (-1);
}
_SHAR_EOF_
echo input.c
cat >input.c <<'_SHAR_EOF_'
/*
* High level routines dealing with getting lines of input
* from the file being viewed.
*
* When we speak of "lines" here, we mean PRINTABLE lines;
* lines processed with respect to the screen width.
* We use the term "raw line" to refer to lines simply
* delimited by newlines; not processed with respect to screen width.
*/
#include "less.h"
extern int squeeze; extern char *line;
/*
* Get the next line.
* A "current" position is passed and a "new" position is returned.
* The current position is the position of the first character of
* a line. The new position is the position of the first character
* of the NEXT line. The line obtained is the line starting at curr_pos.
*/
public POSITION
forw_line(curr_pos)
POSITION curr_pos;
{
POSITION new_pos;
register int c;
if (curr_pos == NULL_POSITION || ch_seek(curr_pos))
return (NULL_POSITION);
c = ch_forw_get();
if (c == EOF)
return (NULL_POSITION);
prewind();
for (;;)
{
if (c == ' || c == EOF)
{
/*
* End of the line.
*/
new_pos = ch_tell();
break;
}
/*
* Append the char to the line and get the next char.
*/
if (pappend(c))
{
/*
* The char won't fit in the line; the line
* is too long to print in the screen width.
* End the line here.
*/
new_pos = ch_tell() - 1;
break;
}
c = ch_forw_get();
}
(void) pappend(' ');
if (squeeze && *line == ' ')
{
/*
* This line is blank.
* Skip down to the last contiguous blank line
* and pretend it is the one which we are returning.
*/
while ((c = ch_forw_get()) == ')
;
if (c != EOF)
(void) ch_back_get();
new_pos = ch_tell();
}
return (new_pos);
}
/*
* Get the previous line.
* A "current" position is passed and a "new" position is returned.
* The current position is the position of the first character of
* a line. The new position is the position of the first character
* of the PREVIOUS line. The line obtained is the one starting at new_pos.
*/
public POSITION
back_line(curr_pos)
POSITION curr_pos;
{
POSITION new_pos, begin_new_pos;
int c;
if (curr_pos == NULL_POSITION || curr_pos <= (POSITION)0 ||
ch_seek(curr_pos-1))
return (NULL_POSITION);
if (squeeze)
{
/*
* Find out if the "current" line was blank.
*/
(void) ch_forw_get(); /* Skip the newline */
c = ch_forw_get(); /* First char of "current" line */
(void) ch_back_get(); /* Restore our position */
(void) ch_back_get();
if (c == ')
{
/*
* The "current" line was blank.
* Skip over any preceeding blank lines,
* since we skipped them in forw_line().
*/
while ((c = ch_back_get()) == ')
;
if (c == EOF)
return (NULL_POSITION);
(void) ch_forw_get();
}
}
/*
* Scan backwards until we hit the beginning of the line.
*/
for (;;)
{
c = ch_back_get();
if (c == ')
{
/*
* This is the newline ending the previous line.
* We have hit the beginning of the line.
*/
new_pos = ch_tell() + 1;
break;
}
if (c == EOF)
{
/*
* We have hit the beginning of the file.
* This must be the first line in the file.
* This must, of course, be the beginning of the line.
*/
new_pos = ch_tell();
break;
}
}
/*
* Now scan forwards from the beginning of this line.
* We keep discarding "printable lines" (based on screen width)
* until we reach the curr_pos.
*
* {{ This algorithm is pretty inefficient if the lines
* are much longer than the screen width,
* but I don't know of any better way. }}
*/
if (ch_seek(new_pos))
return (NULL_POSITION);
loop:
begin_new_pos = new_pos;
prewind();
do
{
c = ch_forw_get();
new_pos++;
if (c == ')
break;
if (pappend(c))
{
/*
* Got a full printable line, but we haven't
* reached our curr_pos yet. Discard the line
* and start a new one.
*/
(void) pappend(' ');
(void) ch_back_get();
new_pos--;
goto loop;
}
} while (new_pos < curr_pos);
(void) pappend(' ');
return (begin_new_pos);
}
_SHAR_EOF_
echo output.c
cat >output.c <<'_SHAR_EOF_'
/*
* High level routines dealing with the output to the screen.
*/
#include "less.h"
public int errmsgs; /* Count of messages displayed by error() */
extern int sigs; extern int sc_width, sc_height; extern int ul_width, ue_width; extern int so_width, se_width; extern int bo_width, be_width; extern int tabstop; extern int twiddle; extern int any_display; extern char *line; extern char *first_cmd;
/*
* Display the line which is in the line buffer.
*/
public void
put_line()
{
register char *p;
register int c;
register int column;
extern int auto_wrap, ignaw;
if (sigs)
/*
* Don't output if a signal is pending.
*/
return;
if (line == NULL)
line = (twiddle) ? "~" : "";
column = 0;
for (p = line; *p != ' '; p++)
{
switch (c = *p)
{
case UL_CHAR:
ul_enter();
column += ul_width;
break;
case UE_CHAR:
ul_exit();
column += ue_width;
break;
case BO_CHAR:
bo_enter();
column += bo_width;
break;
case BE_CHAR:
bo_exit();
column += be_width;
break;
case ' ':
do
{
putchr(' ');
column++;
} while ((column % tabstop) != 0);
break;
case '^');
putchr(c & 0177);
column += 2;
} else
{
putchr(c);
column++;
}
}
}
if (column < sc_width || !auto_wrap || ignaw)
putchr(');
}
/*
* Is a given character a "control" character?
* {{ ASCII DEPENDENT }}
*/
public int
control_char(c)
int c;
{
return (c < ' ' || c == '177');
}
/*
* Return the printable character used to identify a control character
* (printed after a carat; e.g. '3' => "^C").
* {{ ASCII DEPENDENT }}
*/
public int
carat_char(c)
int c;
{
return ((c == '177') ? '?' : (c | 0100));
}
static char obuf[1024]; static char *ob = obuf;
/*
* Flush buffered output.
*/
public void
flush()
{
write(1, obuf, ob-obuf);
ob = obuf;
}
/*
* Discard buffered output.
*/
public void
dropout()
{
ob = obuf;
}
/*
* Output a character.
*/
public void
putchr(c)
int c;
{
if (ob >= &obuf[sizeof(obuf)])
flush();
*ob++ = c;
}
/*
* Output a string.
*/
public void
putstr(s)
register char *s;
{
while (*s != ' ')
putchr(*s++);
}
/*
* Output a message in the lower left corner of the screen
* and wait for carriage return.
*/
static char return_to_continue[] = " (press RETURN)";
public void
error(s)
char *s;
{
register int c;
static char buf[2];
errmsgs++;
if (!any_display)
{
/*
* Nothing has been displayed yet.
* Output this message on error output (file
* descriptor 2) and don't wait for a keystroke
* to continue.
*
* This has the desirable effect of producing all
* error messages on error output if standard output
* is directed to a file. It also does the same if
* we never produce any real output; for example, if
* the input file(s) cannot be opened. If we do
* eventually produce output, code in edit() makes
* sure these messages can be seen before they are
* overwritten or scrolled away.
*/
write(2, s, strlen(s));
write(2, ", 1);
return;
}
lower_left();
clear_eol();
so_enter();
putstr(s);
putstr(return_to_continue);
so_exit();
#if ONLY_RETURN
while ((c = getchr()) != ' && c != '')
bell();
#else
c = getchr();
if (c != ' && c != '' && c != ' ')
{
buf[0] = c;
first_cmd = buf;
}
#endif
lower_left();
if (strlen(s) + sizeof(return_to_continue) +
so_width + se_width + 1 > sc_width)
/*
* Printing the message has probably scrolled the screen.
* {{ Unless the terminal doesn't have auto margins,
* in which case we just hammered on the right margin. }}
*/
repaint();
flush();
}
_SHAR_EOF_
--
Rich $alz
Cronus Project, BBN Labs rsalz@bbn.com
Moderator, comp.sources.unix sources@uunet.uu.net