freebsd-dev/usr.bin/more/main.c
Tim Vanderhoek 20d6e5fe6e Thorough revamp of how input commands are processed. This allows customization
of user keys (documentation pending).  The only key whose semantics have
changed is the capital 'N' key, which now performs a repeat-search in the
opposite direction (just like in vi).

This commit is a little bulkier than what I had originally planned.  I'm not
completely happy with the direction it went, but it's definately an
improvement, and the alternative is to continue becoming irrelevant compared
to GNU less.  (Does anyone even _use_ /usr/bin/more these days?)
1999-09-03 22:31:21 +00:00

540 lines
12 KiB
C

/*
* Copyright (c) 1988 Mark Nudleman
* Copyright (c) 1988, 1993
* 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
char copyright[] =
"@(#) Copyright (c) 1988 Mark Nudleman.\n\
@(#) Copyright (c) 1988, 1993
Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
#ifndef lint
static char sccsid[] = "@(#)main.c 8.1 (Berkeley) 6/7/93";
#endif /* not lint */
#ifndef lint
static const char rcsid[] =
"$FreeBSD$";
#endif /* not lint */
/*
* Entry point, initialization, miscellaneous routines.
*/
#include <sys/file.h>
#include <sys/param.h>
#include <sys/types.h>
#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define DEFINEGLOBALS
#include "defrc.h"
#include "less.h"
#include "pathnames.h"
int ispipe;
char *current_file, *previous_file, *current_name, *next_name;
int any_display;
int ac;
char **av;
int curr_ac;
extern int file;
extern int cbufs;
extern int errmsgs;
extern char *tagfile;
extern int tagoption;
/*
* Edit a new file.
* Filename "-" means standard input.
* No filename means the "current" file, from the command line. If called
* with the same filename in succession, filename will be closed and reopened.
*/
edit(filename)
register char *filename;
{
register int f;
register char *m;
off_t initial_pos, prev_pos, position();
static int didpipe;
char message[MAXPATHLEN + 50], *p;
char *rindex(), *strerror(), *save(), *bad_file();
initial_pos = NULL_POSITION;
if (filename == NULL || *filename == '\0') {
if (curr_ac >= ac) {
error("No current file");
return(0);
}
filename = save(av[curr_ac]);
}
else if (strcmp(filename, "#") == 0) {
if (*previous_file == '\0') {
error("no previous file");
return(0);
}
filename = save(previous_file);
initial_pos = prev_pos;
} else
filename = save(filename);
/* use standard input. */
if (!strcmp(filename, "-")) {
if (didpipe) {
error("can view standard input only once");
return(0);
}
f = 0;
}
else if ((m = bad_file(filename, message, sizeof(message))) != NULL) {
error(m);
free(filename);
return(0);
}
else if ((f = open(filename, O_RDONLY, 0)) < 0) {
(void)snprintf(message, sizeof(message),
"%s: %s", filename, strerror(errno));
error(message);
free(filename);
return(0);
}
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)
(void)close(f);
(void)free(filename);
return(0);
}
/*
* 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)
(void)close(file);
if (previous_file != NULL)
free(previous_file);
previous_file = current_file;
current_file = filename;
pos_clear();
prev_pos = position(TOP);
ispipe = (f == 0);
if (ispipe) {
didpipe = 1;
current_name = "stdin";
} else
current_name = (p = rindex(filename, '/')) ? p + 1 : filename;
if (curr_ac >= ac)
next_name = NULL;
else
next_name = av[curr_ac + 1];
file = f;
ch_init(cbufs, 0);
init_mark();
if (isatty(STDOUT_FILENO)) {
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.
*/
if (initial_pos != NULL_POSITION)
jump_loc(initial_pos);
clr_linenum();
}
return(1);
}
/*
* Edit the next file in the command line list.
*/
next_file(n)
int n;
{
extern int quit_at_eof;
off_t position();
if (curr_ac + n >= ac) {
if (quit_at_eof || position(TOP) == NULL_POSITION)
quit();
error("No (N-th) next file");
}
else
(void)edit(av[curr_ac += n]);
}
/*
* Edit the previous file in the command line list.
*/
prev_file(n)
int n;
{
if (curr_ac - n < 0)
error("No (N-th) previous file");
else
(void)edit(av[curr_ac -= n]);
}
/*
* copy a file directly to standard output; used if stdout is not a tty.
* the only processing is to squeeze multiple blank input lines.
*/
static
cat_file()
{
extern int squeeze;
register int c, empty;
if (squeeze) {
empty = 0;
while ((c = ch_forw_get()) != EOI)
if (c != '\n') {
putchr(c);
empty = 0;
}
else if (empty < 2) {
putchr(c);
++empty;
}
}
else while ((c = ch_forw_get()) != EOI)
putchr(c);
flush();
}
main(argc, argv)
int argc;
char **argv;
{
int envargc, argcnt;
char *envargv[3];
(void) setlocale(LC_ALL, "");
/*
* Process command line arguments and MORE environment arguments.
* Command line arguments override environment arguments.
*/
if (envargv[1] = getenv("MORE")) {
envargc = 2;
envargv[0] = "more";
envargv[2] = NULL;
(void)option(envargc, envargv);
}
argcnt = option(argc, argv);
argv += argcnt;
argc -= argcnt;
/*
* Set up list of files to be examined.
*/
ac = argc;
av = argv;
curr_ac = 0;
/*
* Set up terminal, etc.
*/
if (!isatty(STDOUT_FILENO)) {
/*
* Output is not a tty.
* Just copy the input file(s) to output.
*/
if (ac < 1) {
(void)edit("-");
if (file >= 0)
cat_file();
} else {
do {
(void)edit((char *)NULL);
if (file >= 0)
cat_file();
} while (++curr_ac < ac);
}
exit(0);
}
raw_mode(1);
get_term();
open_getchr();
init();
init_signals(1);
/* select the first file to examine. */
if (tagoption) {
/*
* A -t option was given; edit the file selected by the
* "tags" search, and search for the proper line in the file.
*/
if (!tagfile || !edit(tagfile) || tagsearch())
quit();
}
else if (ac < 1)
(void)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 {
(void)edit((char *)NULL);
} while (file < 0 && ++curr_ac < ac);
}
if (file >= 0) {
/*
* Don't call rcfiles() until now so that people who put
* wierd things (like "forw_scroll") in their rc file don't
* cause us to SEGV.
*/
rcfiles();
commands();
}
quit();
/*NOTREACHED*/
}
/*
* Copy a string to a "safe" place
* (that is, to a buffer allocated by malloc).
*/
char *
save(s)
char *s;
{
char *p, *strcpy();
p = malloc((u_int)strlen(s)+1);
if (p == NULL)
{
error("cannot allocate memory");
quit();
}
return(strcpy(p, s));
}
/*
* Exit the program.
*/
quit()
{
/*
* Put cursor at bottom left corner, clear the line,
* reset the terminal modes, and exit.
*/
lower_left();
clear_eol();
deinit();
flush();
raw_mode(0);
exit(0);
}
/*
* Read in from each of the three rc files - default, system, user.
* Calls handle_error() directly to report errors.
*/
rcfiles()
{
FILE *fd;
char fbuf[MAXPATHLEN + 1];
char *c;
int readrc();
int savederrno;
static int str_read();
/* The default builtin rc file */
if ((c = getenv("HOME")) &&
strlen(c) + strlen(_PATH_DEFRC) + 1 < MAXPATHLEN) {
sprintf(fbuf, "%s/%s", c, _PATH_DEFRC);
fd = fopen(fbuf, "r");
savederrno = errno;
if (!fd) {
if (!access(_PATH_SYSMORERC, F_OK)) {
SETERRSTR(E_EXTERN, "unable to read %s: %s",
_PATH_SYSMORERC, strerror(savederrno));
handle_error();
}
/* We'd better have some type of default keys!! */
goto use_builtin_defrc;
} else {
readrc(fd);
fclose(fd);
}
} else {
use_builtin_defrc:
fd = fropen(DEFRC, str_read);
readrc(fd);
fclose(fd);
}
/* The system rc file */
fd = fopen(_PATH_SYSMORERC, "r");
savederrno = errno;
if (!fd) {
if (!access(_PATH_SYSMORERC, F_OK)) {
SETERRSTR(E_EXTERN, "unable to read %s: %s",
_PATH_SYSMORERC, strerror(savederrno));
handle_error();
} else
; /* non-existant => non-error */
} else {
readrc(fd);
fclose(fd);
}
/* The user rc file */
if ((c = getenv("HOME")) &&
strlen(c) + strlen(_PATH_RC) + 1 < MAXPATHLEN) {
sprintf(fbuf, "%s/%s", c, _PATH_RC);
fd = fopen(fbuf, "r");
savederrno = errno;
if (!fd) {
if (!access(fbuf, F_OK)) {
SETERRSTR(E_EXTERN,
"unable to read %s: %s", fbuf,
strerror(savederrno));
handle_error();
} else
; /* non-existant => non-error */
} else {
readrc(fd);
fclose(fd);
}
}
}
/*
* Read-in an rc file from a fd. Calls handle_error() directly to handle
* errors.
*
* This really belongs in ncommand.c, but that file is already 33292 bytes
* long.
*/
readrc(fd)
FILE *fd;
{
char *bufptr, *buf;
size_t len;
int strlenbuf;
buf = NULL;
strlenbuf = 0;
while (bufptr = fgetln(fd, &len)) {
if (!len)
continue; /* ??? */
if (*bufptr == '#')
continue; /* skip comments */
if (!(buf = reallocf(buf, strlenbuf + len + 1))) {
SETERR(E_MALLOC);
handle_error();
if (strlenbuf + len < 1024)
return; /* major memory shortage... */
continue;
}
memcpy (buf + strlenbuf, bufptr, len);
buf[len + strlenbuf] = '\0';
if (len > 1 && buf[strlenbuf + len - 2] == '\\') {
/* line continuation */
buf[strlenbuf + len - 2] = '\0';
strlenbuf = strlen(buf);
continue;
}
if (buf[len + strlenbuf - 1] == '\n')
buf[len + strlenbuf - 1] = '\0';
if (command(buf))
handle_error();
free(buf);
buf = NULL;
strlenbuf = 0;
}
}
/*
* Read from the NUL-terminated cookie. Non-reentrant: keeps a static pointer
* to the current position in the cookie. Used for funopen().
*/
static int
str_read(cookie, buf, len)
void *cookie;
char *buf;
size_t len;
{
static char *curpos;
static int cooklen;
static char *lastcook;
if (lastcook != cookie) {
/* begin working on a new cookie */
curpos = cookie;
lastcook = cookie;
cooklen = strlen(cookie);
}
if (curpos + len > lastcook + cooklen) {
ssize_t r;
memcpy(buf, curpos, r = (cooklen - (curpos - lastcook)));
curpos = cookie + cooklen;
return (int) r;
} else {
memcpy(buf, curpos, len);
curpos += len;
return (int) len;
}
}