LESS

Section: Misc. Reference Manual Pages (l)
Index Return to Main Contents
 

NAME

less - opposite of more  

SYNOPSIS

less [-dstwcCeEmMqQuU] [-hN] [-b[fp]N] [-xN] [-[z]N]
[-P[mM]string] [-[lL]logfile] [+cmd] [filename]...  

DESCRIPTION

Less is a program similar to more (1), but which allows backwards movement in the file as well as forward movement. Also, less does not have to read the entire input file before starting, so with large input files it starts up faster than text editors like vi (1). Less uses termcap, so it can run on a variety of terminals. There is even limited support for hardcopy terminals. (On a hardcopy terminal, lines which should be printed at the top of the screen are prefixed with an up-arrow.)

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.

 

COMMANDS

In the following descriptions, ^X means control-X.
H
Help: display a summary of these commands. If you forget all the other commands, remember this one.

SPACE
Scroll forward N lines, default one window (see option -z below). If N is more than the screen size, only the final screenful is displayed.

f or ^F
Same as SPACE.

b or ^B
Scroll backward N lines, default one window (see option -z below). If N is more than the screen size, only the final screenful is displayed.

RETURN
Scroll forward N lines, default 1. The entire N lines are displayed, even if N is more than the screen size.

e or ^E
Same as RETURN.

j or ^J
Also the same as RETURN.

y or ^Y
Scroll backward N lines, default 1. The entire N lines are displayed, even if N is more than the screen size.
k or ^K
Same as y.

d or ^D
Scroll forward N lines, default 10. If N is specified, it becomes the new default for subsequent d and u commands.

u or ^U
Scroll backward N lines, default 10. If N is specified, it becomes the new default for subsequent d and u commands.

r or ^R or ^L
Repaint the screen.

R
Repaint the screen, discarding any buffered input. Useful if the file is changing while it is being viewed.

g
Go to line N in the file, default 1 (beginning of file). (Warning: this may be slow if N is large.)

G
Go to line N in the file, default the end of the file. (Warning: this may be slow if standard input, rather than a file, is being read.)

p
Go to a position N percent into the file. N should be between 0 and 100. (This is possible if standard input is being read, but only if less has already read to the end of the file. It is always fast, but not always useful.)

%
Same as p.

m
Followed by any lowercase letter, marks the current position with that letter.

'
(Single quote.) Followed by any lowercase letter, returns to the position which was previously marked with that letter. Followed by another single quote, returns to the postion at which the last "large" movement command was executed. All marks are lost when a new file is examined.

/pattern
Search forward in the file for the N-th line containing the pattern. N defaults to 1. The pattern is a regular expression, as recognized by ed. The search starts at the second line displayed (but see the -t option, which changes this).

?pattern
Search backward in the file for the N-th line containing the pattern. The search starts at the line immediately before the top line displayed.

n
Repeat previous search, for N-th line containing the last pattern.

E
Examine a new file. If the filename is missing, the "current" file (see the N and P commands below) from the list of files in the command line is re-examined. If the filename is a pound sign (#), the previously examined file is re-examined.

N
Examine the next file (from the list of files given in the command line). If a number N is specified (not to be confused with the command N), the N-th next file is examined.

P
Examine the previous file. If a number N is specified, the N-th previous file is examined.

= or ^G
Prints some information about the file being viewed, including its name and the byte offset of the bottom line being displayed. If possible, it also prints the length of the file and the percent of the file above the last displayed line.

-
Followed by one of the command line option letters (see below), this will toggle the setting of that option and print a message describing the new setting.

+cmd
Causes the specified cmd to be executed each time a new file is examined. For example, +G causes less to initially display each file starting at the end rather than the beginning.

V
Prints the version number of less being run.

q
Exits less.

The following two commands may or may not be valid, depending on your particular installation.

v
Invokes an editor to edit the current file being viewed. The editor is taken from the environment variable EDITOR, or defaults to "vi".

! shell-command
Invokes a shell to run the shell-command given. A percent sign in the command is replaced by the name of the current file. "!!" repeats the last shell command.

 

OPTIONS

Command line options are described below. Most options may be changed while less is running, via the "-" command.

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.

-b
The -bn option tells less to use a non-standard buffer size. There are two standard (default) buffer sizes, one is used when a file is being read and the other when a pipe (standard input) is being read. The current defaults are 5 buffers for files and 12 for pipes. (Buffers are 1024 bytes.) The number n specifies a different number of buffers to use. The -b may be followed by "f", in which case only the file default is changed, or by "p" in which case only the pipe default is changed. Otherwise, both are changed.
-c
Normally, less will repaint the screen by scrolling from the bottom of the screen. If the -c option is set, when less needs to change the entire display, it will paint from the top line down.
-C
The -C option is like -c, but the screen is cleared before it is repainted.
-d
Normally, less will complain if the terminal is dumb; that is, lacks some important capability, such as the ability to clear the screen or scroll backwards. The -d option suppresses this complaint (but does not otherwise change the behavior of the program on a dumb terminal).
-e
Normally the only way to exit less is via the "q" command. The -e option tells less to automatically exit the second time it reaches end-of-file.
-E
The -E flag causes less to exit the first time it reaches end-of-file.
-h
Normally, less will scroll backwards when backwards movement is necessary. The -h option specifies a maximum number of lines to scroll backwards. If it is necessary to move backwards more than this many lines, the screen is repainted in a forward direction. (If the terminal does not have the ability to scroll backwards, -h0 is implied.)
-l
The -l option, followed immediately by a filename, will cause less to copy its input to the named file as it is being viewed. This applies only when the input file is a pipe, not an ordinary file. If the file already exists, less will ask for confirmation before overwriting it.
-L
The -L option is like -l, but it will overwrite an existing file without asking for confirmation.
-m
Normally, less prompts with a colon. The -m option causes less to prompt verbosely (like more), with the percent into the file.
-M
The -M option causes less to prompt even more verbosely than more.
-P
The -P option provides a way to tailor the three prompt styles to your own preference. You would normally put this option in your LESS environment variable, rather than type it in with each less command. Such an option must either be the last option in the LESS variable, or be terminated by a dollar sign. -P followed by a string changes the default (short) prompt to that string. -Pm changes the medium (-m) prompt to the string, and -PM changes the long (-M) prompt. The string consists of a sequence of letters which are replaced with certain predefined strings, as follows:
       F       file name

       f       file name, only once

       O       file n of n

       o       file n of n, only once

       b       byte offset

       p       percent into file

       P       percent if known, else byte offset

Angle brackets, < and >, may be used to surround a literal string to be included in the prompt. The defaults are "fo" for the short prompt, "foP" for the medium prompt, and "Fobp" for the long prompt.
Example: Setting your LESS variable to "PmFOP$PMFObp" would change the medium and long prompts to always include the file name and "file n of n" message.
Another example: Setting your LESS variable to
"mPm<--Less-->FoPe" would change the medium prompt to the string "--Less--" followed by the file name and percent into the file. It also selects the medium prompt as the default prompt (because of the first "m").
-q
Normally, if an attempt is made to scroll past the end of the file or before the beginning of the file, the terminal bell is rung to indicate this fact. The -q option tells less not to ring the bell at such times. If the terminal has a "visual bell", it is used instead.
-Q
Even if -q is given, less will ring the bell on certain other errors, such as typing an invalid character. The -Q option tells less to be quiet all the time; that is, never ring the terminal bell. If the terminal has a "visual bell", it is used instead.
-s
The -s option causes consecutive blank lines to be squeezed into a single blank line. This is useful when viewing nroff output.
-t
Normally, forward searches start just after the top displayed line (that is, at the second displayed line). Thus forward searches include the currently displayed screen. The -t option causes forward searches to start just after the bottom line displayed, thus skipping the currently displayed screen.
-u
If the -u option is given, backspaces are treated as printable characters; that is, they are sent to the terminal when they appear in the input.
-U
If the -U option is given, backspaces are printed as the two character sequence "^H".

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.

-w
Normally, less uses a tilde character to represent lines past the end of the file. The -w option causes blank lines to be used instead.
-x
The -xn option sets tab stops every n positions. The default for n is 8.
-[z]
When given a backwards or forwards window command, less will by default scroll backwards or forwards one screenful of lines. The -zn option changes the default scrolling window size to n lines. If n is greater than the screen size, the scrolling window size will be set to one screenful. Note that the "z" is optional for compatibility with more.
+
If a command line option begins with +, the remainder of that option is taken to be an initial command to less. For example, +G tells less to start at the end of the file rather than the beginning, and +/xyz tells it to start at the first occurence of "xyz" in the file. As a special case, +<number> acts like +<number>g; that is, it starts the display at the specified line number (however, see the caveat under the "g" command above). If the option starts with ++, the initial command applies to every file being viewed, not just the first one. The + command described previously may also be used to set (or change) an initial command for every file.

 

BUGS

When used on standard input (rather than a file), you can move backwards only a finite amount, corresponding to that portion of the file which is still buffered. The -b option may be used to expand the buffer space. _SHAR_EOF_

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


 

Index

NAME
SYNOPSIS
DESCRIPTION
COMMANDS
OPTIONS
BUGS

This document was created by man2html, using the manual pages.
Time: 21:40:48 GMT, February 02, 2023