Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Posting-Version: version B 2.10.2 9/5/84; site nsc-pdc.UUCP Path: utzoo!watmath!clyde!burl!ulysses!allegra!mit-eddie!genrad!decvax!tektronix!reed!nsc-pdc!mark From: mark@nsc-pdc.UUCP (Mark Nudelman) Newsgroups: net.sources Subject: less, part 1 of 2 Message-ID: <228@nsc-pdc.UUCP> Date: Wed, 10-Jul-85 00:08:59 EDT Article-I.D.: nsc-pdc.228 Posted: Wed Jul 10 00:08:59 1985 Date-Received: Fri, 12-Jul-85 03:31:12 EDT Reply-To: mark@nsc-pdc.UUCP (Mark Nudelman) Organization: NSC Portland Development Center, Portland Oregon Lines: 2212 This is a new version of the program "less" which I posted about a month ago. It is a pagination program similar to more or pg. This distribution is in 2 parts to avoid truncation problems in transmission; this is part 1. This version has some bug fixes and some new features. The major new feature is better handling of underlines and backspaces. It should also be somewhat more portable; in particular, I think all symbols are now unique in 6 characters (yuch). To install, delete everything down to the cut line below, put the file in an empty directory, and run sh on the file. Then read the file called INSTALLATION. Problems, complaints, praise, bug fixes to the author. Mark Nudelman nsc!nsc-pdc!mark National Semiconductor tektronix!reed!nsc-pdc!mark #!/bin/sh-----cut here-----cut here-----cut here-----cut here----- # shar: Shell Archiver # Run the following text with /bin/sh to create: # INSTALLATION # less.l # makefile.bsd41 # makefile.bsd42 # makefile.sys5 # makefile.xen # ch.c # command.c # help.c # input.c # line.c cat - << \SHAR_EOF > INSTALLATION This is the distribution of "less", a paginator similar to "more" or "pg". The manual page is in less.l. INSTALLATION: 1. Move the distributed source to its own directory and unpack it by running "sh" on the distribution file, if you have not already done so. 2. If your system is System V: cp makefile.sys5 makefile If your system is Berkeley 4.2bsd: cp makefile.bsd42 makefile If your system is Berkeley 4.1bsd: cp makefile.bsd41 makefile If your system is Xenix 3.0: cp makefile.xen makefile Otherwise, edit the makefile to make the system parameters match your system. There is one features which is selectable at compile time: shell escapes. If you want to have shell escapes, edit the makefile appropriately. 3. Type "make" and watch the fun. 4. If the make succeeds, it will generate a program "less" in your current directory. Test the generated program. 5. When satisfied that it works, if you wish to install it in a public place, edit the makefile so that INSTALL_LESS and INSTALL_MAN are the proper filenames. Then type "make install". If you have any problems building or running "less", you may mail to the author via USENET at: ...!tektronix!reed!nsc-pdc!mark or ...!nsc!nsc-pdc!mark Note to hackers: comments noting possible improvements are enclosed in double curly brackets {{ like this }}. SHAR_EOF cat - << \SHAR_EOF > less.l .TH LESS l .SH NAME less \- opposite of more .SH SYNOPSIS .B "less [-cdtmMqQuU] [-b[fp]\fIn\fB] [+\fIcommand\fB] [\fIname\fB] ..." .SH DESCRIPTION .I Less is a program similar to .I more (1), but which allows backwards movement in the file as well as forward movement. Also, .I 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 .I vi (1). .I 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.) .PP Commands are based on both .I more and .I vi. Commands may be preceeded by a decimal number, called N in the descriptions below. The number is used by some commands, as indicated. .IP h Help: display a summary of these commands. If you forget all the other commands, remember this one. .PP .IP SPACE Scroll forward one screen. .PP .IP f Same as SPACE. .PP .IP b Scroll backward one screen. .PP .IP RETURN Scroll forward N lines, default 1. .PP .IP e Same as RETURN. .PP .IP j Also the same as RETURN. .PP .IP y Scroll backward N lines, default 1. .IP k Same as y. .PP .IP d Scroll forward N lines, default 10. If N is specified, it becomes the new default for all d and u commands. .PP .IP u Scroll backward N lines, default 10. If N is specified, it becomes the new default for all d and u commands. .PP .IP r Repaint the screen. .PP .IP R Repaint the screen, discarding any buffered input. Useful if the file is changing while it is being viewed. .PP .IP g Go to line N in the file, default 1 (beginning of file). (Warning: this may be slow if N is large.) .PP .IP 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.) .PP .IP p Go to a position N percent into the file. N should be between 0 and 100. (This is possible only if a file, rather than standard input, is being read. It is always fast, but not always useful.) .PP .IP /pattern Search forward in the file for the N-th occurence of the pattern. N defaults to 1. The pattern is a regular expression, as recognized by .I ed. The search starts at the second line displayed (but see the -t option, which changes this). .PP .IP ?pattern Search backward in the file for the N-th occurence of the pattern. The search starts at the line immediately before the top line displayed. .PP .IP n Repeat previous search, for N-th occurence of the last pattern. .PP .IP E [filename] 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. .PP .IP 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. .PP .IP P Examine the previous file. If a number N is specified, the N-th previous file is examined. .PP .IP = Prints the name of the file being viewed 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. .PP .IP \- 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. .PP .IP v Prints the version number of .I less being run. .PP .IP q Exits .I less. .PP Command line options are described below. Options are also taken from the environment variable "LESS". (The environment variable is parsed before the command line, so command line options override the LESS environment variable. Options may be changed while .I less is running via the "\-" command.) For example, if you like more-style prompting, to avoid typing "less -m ..." each time .I less is invoked, you might tell .I csh: .sp setenv LESS m .sp or if you use .I sh: .sp LESS=m; export LESS .PP 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 command line option causes forward searches to start just after the bottom line displayed, thus skipping the currently displayed screen. .PP Normally, .I less prompts with a colon. The -m command line option causes .I less to prompt verbosely like .I more, printing the file name and percent into the file. .PP The -M command line option causes .I less to prompt even more verbosely than .I more. .PP 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 command line option tells .I less not to ring the bell at such times. If the terminal has a "visual bell", it is used instead. .PP Even if -q is given, less will ring the bell on certain other errors, such as typing an invalid character. The -Q command line option tells .I 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. .PP Normally, underlined text is displayed using the terminal's hardware underlining capability. If the terminal does not provide this, underlined text is displayed in reverse video, bold, or some other distinctive video attribute. (Underlining, by the way, is defined as overstriking an underscore character. One character overstrikes another if a backspace is between the two.) Overstrikes other than underlines are displayed with real backspaces. If the -u command line option is given, underlining is treated as any other overstrike; no standout mode is used. If the -U command line option is given, backspaces are printed as the two character sequence "^H". .PP Normally, .I 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 flag suppresses this complaint (but does not otherwise change the behavior of the program on a dumb terminal). .PP The -b\fIn\fR command line option tells .I 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 \fIn\fR 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. .PP Normally, when data is read by .I less, it is scanned to ensure that bit 7 (the high order bit) is turned off in each byte read, and to ensure that there are no null (zero) bytes in the data (null bytes are turned into "@" characters). If the data is known to be "clean", the -c command line option will tell .I less to skip this checking. (However, if the data is not "clean", unpredicatable results may occur.) .PP If a command line option begins with \fB+\fR, the remainder of that option is taken to be an initial command to .I less. For example, +G tells .I 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, + acts like +g; that is, it starts the display at the specified line number (however, see the caveat under the "g" command above). .SH 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. .PP Searches will not find a string which has been split due to line folding. .sp Tabs in the input are translated into the appropriate number of spaces, so a search pattern which includes a tab character will always fail. Use the pattern "\ *" instead. SHAR_EOF cat - << \SHAR_EOF > makefile.bsd41 # Makefile for "less" # # Invoked as: # make all # or make install # Plain "make" is equivalent to "make all". # # If you add or delete functions, remake funcs.h by doing: # make newfuncs # This depends on the coding convention of function headers looking like: # " \t public \n ( ... ) " # # Also provided: # make lint # Runs "lint" on all the sources. # make clean # Removes "less" and the .o files. # make clobber # Pretty much the same as make "clean". ########################################################################## # System-specific parameters ########################################################################## # Define XENIX if running under XENIX 3.0 XENIX = 0 # VOID is 1 if your C compiler supports the "void" type, # 0 if it does not. VOID = 1 # off_t is the type which lseek() returns. # It is also the type of lseek()'s second argument. off_t = long # TERMIO is 1 if your system has /usr/include/termio.h. # This is normally the case for System 5. # If TERMIO is 0 your system must have /usr/include/sgtty.h. # This is normally the case for BSD. TERMIO = 0 # SIGSETMASK is 1 if your system has the sigsetmask() call. # This is normally the case only for BSD 4.2, # not for BSD 4.1 or System 5. SIGSETMASK = 0 ########################################################################## # Optional and semi-optional features ########################################################################## # REGCMP is 1 if your system has the regcmp() function. # This is normally the case for System 5. # RECOMP is 1 if your system has the re_comp() function. # This is normally the case for BSD. # If neither is 1, pattern matching is supported, but without metacharacters. REGCMP = 0 RECOMP = 1 # SHELL_ESCAPE is 1 if you wish to allow shell escapes. # (This is possible only if your system supplies the system() function.) SHELL_ESCAPE = 0 ########################################################################## # Compilation environment. ########################################################################## # LIBS is the list of libraries needed. LIBS = -ltermcap # INSTALL_LESS is a list of the public versions of less. # INSTALL_MAN is a list of the public versions of the manual page. INSTALL_LESS = /usr/local/less INSTALL_MAN = /usr/man/manl/less.l # OPTIM is passed to the compiler and the loader. # It is normally "-O" but may be, for example, "-g". OPTIM = -O ########################################################################## # Files ########################################################################## SRC = main.c prim.c ch.c position.c input.c output.c screen.c \ line.c signal.c help.c ttyin.c command.c version.c OBJ = main.o prim.o ch.o position.o input.o output.o screen.o \ line.o signal.o help.o ttyin.o command.o version.o ########################################################################## # Rules ########################################################################## DEFS = "-DTERMIO=$(TERMIO)" \ "-DSIGSETMASK=$(SIGSETMASK)" \ "-Doff_t=$(off_t)" "-DVOID=$(VOID)" \ "-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \ "-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \ "-DXENIX=$(XENIX)" CFLAGS = $(OPTIM) $(DEFS) all: less less: $(OBJ) cc $(OPTIM) -o less $(OBJ) $(LIBS) install: install_man install_less install_man: less.l for f in $(INSTALL_MAN); do rm -f $$f; cp less.l $$f; done touch install_man install_less: less for f in $(INSTALL_LESS); do rm -f $$f; cp less $$f; done touch install_less $(OBJ): less.h funcs.h lint: lint -hp $(DEFS) $(SRC) newfuncs: mv funcs.h funcs.h.OLD awk -f mkfuncs.awk $(SRC) >funcs.h clean: rm -f $(OBJ) less clobber: rm -f *.o less install_less install_man shar: shar INSTALLATION less.l makefile.* *.c *.h *.awk > less.shar SHAR_EOF cat - << \SHAR_EOF > makefile.bsd42 # Makefile for "less" # # Invoked as: # make all # or make install # Plain "make" is equivalent to "make all". # # If you add or delete functions, remake funcs.h by doing: # make newfuncs # This depends on the coding convention of function headers looking like: # " \t public \n ( ... ) " # # Also provided: # make lint # Runs "lint" on all the sources. # make clean # Removes "less" and the .o files. # make clobber # Pretty much the same as make "clean". ########################################################################## # System-specific parameters ########################################################################## # Define XENIX if running under XENIX 3.0 XENIX = 0 # VOID is 1 if your C compiler supports the "void" type, # 0 if it does not. VOID = 1 # off_t is the type which lseek() returns. # It is also the type of lseek()'s second argument. off_t = long # TERMIO is 1 if your system has /usr/include/termio.h. # This is normally the case for System 5. # If TERMIO is 0 your system must have /usr/include/sgtty.h. # This is normally the case for BSD. TERMIO = 0 # SIGSETMASK is 1 if your system has the sigsetmask() call. # This is normally the case only for BSD 4.2, # not for BSD 4.1 or System 5. SIGSETMASK = 1 ########################################################################## # Optional and semi-optional features ########################################################################## # REGCMP is 1 if your system has the regcmp() function. # This is normally the case for System 5. # RECOMP is 1 if your system has the re_comp() function. # This is normally the case for BSD. # If neither is 1, pattern matching is supported, but without metacharacters. REGCMP = 0 RECOMP = 1 # SHELL_ESCAPE is 1 if you wish to allow shell escapes. # (This is possible only if your system supplies the system() function.) SHELL_ESCAPE = 0 ########################################################################## # Compilation environment. ########################################################################## # LIBS is the list of libraries needed. LIBS = -ltermcap # INSTALL_LESS is a list of the public versions of less. # INSTALL_MAN is a list of the public versions of the manual page. INSTALL_LESS = /usr/local/less INSTALL_MAN = /usr/man/manl/less.l # OPTIM is passed to the compiler and the loader. # It is normally "-O" but may be, for example, "-g". OPTIM = -O ########################################################################## # Files ########################################################################## SRC = main.c prim.c ch.c position.c input.c output.c screen.c \ line.c signal.c help.c ttyin.c command.c version.c OBJ = main.o prim.o ch.o position.o input.o output.o screen.o \ line.o signal.o help.o ttyin.o command.o version.o ########################################################################## # Rules ########################################################################## DEFS = "-DTERMIO=$(TERMIO)" \ "-DSIGSETMASK=$(SIGSETMASK)" \ "-Doff_t=$(off_t)" "-DVOID=$(VOID)" \ "-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \ "-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \ "-DXENIX=$(XENIX)" CFLAGS = $(OPTIM) $(DEFS) all: less less: $(OBJ) cc $(OPTIM) -o less $(OBJ) $(LIBS) install: install_man install_less install_man: less.l for f in $(INSTALL_MAN); do rm -f $$f; cp less.l $$f; done touch install_man install_less: less for f in $(INSTALL_LESS); do rm -f $$f; cp less $$f; done touch install_less $(OBJ): less.h funcs.h lint: lint -hp $(DEFS) $(SRC) newfuncs: mv funcs.h funcs.h.OLD awk -f mkfuncs.awk $(SRC) >funcs.h clean: rm -f $(OBJ) less clobber: rm -f *.o less install_less install_man shar: shar INSTALLATION less.l makefile.* *.c *.h *.awk > less.shar SHAR_EOF cat - << \SHAR_EOF > makefile.sys5 # Makefile for "less" # # Invoked as: # make all # or make install # Plain "make" is equivalent to "make all". # # If you add or delete functions, remake funcs.h by doing: # make newfuncs # This depends on the coding convention of function headers looking like: # " \t public \n ( ... ) " # # Also provided: # make lint # Runs "lint" on all the sources. # make clean # Removes "less" and the .o files. # make clobber # Pretty much the same as make "clean". ########################################################################## # System-specific parameters ########################################################################## # Define XENIX if running under XENIX 3.0 XENIX = 0 # VOID is 1 if your C compiler supports the "void" type, # 0 if it does not. VOID = 1 # off_t is the type which lseek() returns. # It is also the type of lseek()'s second argument. off_t = long # TERMIO is 1 if your system has /usr/include/termio.h. # This is normally the case for System 5. # If TERMIO is 0 your system must have /usr/include/sgtty.h. # This is normally the case for BSD. TERMIO = 1 # SIGSETMASK is 1 if your system has the sigsetmask() call. # This is normally the case only for BSD 4.2, # not for BSD 4.1 or System 5. SIGSETMASK = 0 ########################################################################## # Optional and semi-optional features ########################################################################## # REGCMP is 1 if your system has the regcmp() function. # This is normally the case for System 5. # RECOMP is 1 if your system has the re_comp() function. # This is normally the case for BSD. # If neither is 1, pattern matching is supported, but without metacharacters. REGCMP = 1 RECOMP = 0 # SHELL_ESCAPE is 1 if you wish to allow shell escapes. # (This is possible only if your system supplies the system() function.) SHELL_ESCAPE = 0 ########################################################################## # Compilation environment. ########################################################################## # LIBS is the list of libraries needed. LIBS = -lcurses -lPW # INSTALL_LESS is a list of the public versions of less. # INSTALL_MAN is a list of the public versions of the manual page. INSTALL_LESS = /usr/lbin/less INSTALL_MAN = /usr/man/manl/less.l # OPTIM is passed to the compiler and the loader. # It is normally "-O" but may be, for example, "-g". OPTIM = -O ########################################################################## # Files ########################################################################## SRC = main.c prim.c ch.c position.c input.c output.c screen.c \ line.c signal.c help.c ttyin.c command.c version.c OBJ = main.o prim.o ch.o position.o input.o output.o screen.o \ line.o signal.o help.o ttyin.o command.o version.o ########################################################################## # Rules ########################################################################## DEFS = "-DTERMIO=$(TERMIO)" \ "-DSIGSETMASK=$(SIGSETMASK)" \ "-Doff_t=$(off_t)" "-DVOID=$(VOID)" \ "-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \ "-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \ "-DXENIX=$(XENIX)" CFLAGS = $(OPTIM) $(DEFS) all: less less: $(OBJ) cc $(OPTIM) -o less $(OBJ) $(LIBS) install: install_man install_less install_man: less.l for f in $(INSTALL_MAN); do rm -f $$f; cp less.l $$f; done touch install_man install_less: less for f in $(INSTALL_LESS); do rm -f $$f; cp less $$f; done touch install_less $(OBJ): less.h funcs.h lint: lint -hp $(DEFS) $(SRC) newfuncs: mv funcs.h funcs.h.OLD awk -f mkfuncs.awk $(SRC) >funcs.h clean: rm -f $(OBJ) less clobber: rm -f *.o less install_less install_man shar: shar INSTALLATION less.l makefile.* *.c *.h *.awk > less.shar SHAR_EOF cat - << \SHAR_EOF > makefile.xen # Makefile for "less" # # Invoked as: # make all # or make install # Plain "make" is equivalent to "make all". # # If you add or delete functions, remake funcs.h by doing: # make newfuncs # This depends on the coding convention of function headers looking like: # " \t public \n ( ... ) " # # Also provided: # make lint # Runs "lint" on all the sources. # make clean # Removes "less" and the .o files. # make clobber # Pretty much the same as make "clean". ########################################################################## # System-specific parameters ########################################################################## # Define XENIX if running under XENIX 3.0 XENIX = 1 # VOID is 1 if your C compiler supports the "void" type, # 0 if it does not. VOID = 1 # off_t is the type which lseek() returns. # It is also the type of lseek()'s second argument. off_t = long # TERMIO is 1 if your system has /usr/include/termio.h. # This is normally the case for System 5. # If TERMIO is 0 your system must have /usr/include/sgtty.h. # This is normally the case for BSD. TERMIO = 1 # SIGSETMASK is 1 if your system has the sigsetmask() call. # This is normally the case only for BSD 4.2, # not for BSD 4.1 or System 5. SIGSETMASK = 0 ########################################################################## # Optional and semi-optional features ########################################################################## # REGCMP is 1 if your system has the regcmp() function. # This is normally the case for System 5. # RECOMP is 1 if your system has the re_comp() function. # This is normally the case for BSD. # If neither is 1, pattern matching is supported, but without metacharacters. REGCMP = 1 RECOMP = 0 # SHELL_ESCAPE is 1 if you wish to allow shell escapes. # (This is possible only if your system supplies the system() function.) SHELL_ESCAPE = 0 ########################################################################## # Compilation environment. ########################################################################## # LIBS is the list of libraries needed. LIBS = -lcurses -ltermlib # INSTALL_LESS is a list of the public versions of less. # INSTALL_MAN is a list of the public versions of the manual page. INSTALL_LESS = /usr/lbin/less INSTALL_MAN = /usr/man/manl/less.l # OPTIM is passed to the compiler and the loader. # It is normally "-O" but may be, for example, "-g". OPTIM = -O ########################################################################## # Files ########################################################################## SRC = main.c prim.c ch.c position.c input.c output.c screen.c \ line.c signal.c help.c ttyin.c command.c version.c OBJ = main.o prim.o ch.o position.o input.o output.o screen.o \ line.o signal.o help.o ttyin.o command.o version.o ########################################################################## # Rules ########################################################################## DEFS = "-DTERMIO=$(TERMIO)" \ "-DSIGSETMASK=$(SIGSETMASK)" \ "-Doff_t=$(off_t)" "-DVOID=$(VOID)" \ "-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \ "-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \ "-DXENIX=$(XENIX)" CFLAGS = $(OPTIM) $(DEFS) all: less less: $(OBJ) cc $(OPTIM) -o less $(OBJ) $(LIBS) install: install_man install_less install_man: less.l for f in $(INSTALL_MAN); do rm -f $$f; cp less.l $$f; done touch install_man install_less: less for f in $(INSTALL_LESS); do rm -f $$f; cp less $$f; done touch install_less $(OBJ): less.h funcs.h lint: lint -hp $(DEFS) $(SRC) newfuncs: mv funcs.h funcs.h.OLD awk -f mkfuncs.awk $(SRC) >funcs.h clean: rm -f $(OBJ) less clobber: rm -f *.o less install_less install_man shar: shar INSTALLATION less.l makefile.* *.c *.h *.awk > less.shar SHAR_EOF cat - << \SHAR_EOF > ch.c /* * 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 static 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 pipe; /* * Current position in file. * Stored as a block number and an offset into the block. */ static long ch_block; static int ch_offset; /* * 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; /* * 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; if (pipe) { /* * 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 %d, want %d\n", last_piped_block-1, ch_block); error(message); quit(); } } else lseek(file, ch_block * BUFSIZ, 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(); } /* * 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) 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]); } /* * 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 (!pipe || 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 (pipe) { /* * Do it the slow way: read till end of data. */ while (ch_forw_get() != EOF) ; } else { (void) ch_seek((POSITION)(lseek(file, (off_t)0, 2))); } return (0); } /* * Return the length of the file, if known. */ public POSITION ch_length() { if (pipe) return (NULL_POSITION); return ((POSITION)(lseek(file, (off_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 || (pipe && !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.\n", 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; (void) ch_seek((POSITION)0); /* ? */ } SHAR_EOF cat - << \SHAR_EOF > command.c /* * User-level command processor. */ #include "less.h" extern int erase_char, kill_char; extern int pr_type; extern int new_file; extern int sigs; extern int sc_width, sc_height; extern char *first_cmd; extern char version[]; static char cmdbuf[90]; /* Buffer for holding a multi-char command */ static char *cp; /* Pointer into cmdbuf */ static int cmd_col; /* Current column of the multi-char command */ static char mcc; /* The multi-char command letter (e.g. '/') */ /* * Reset command buffer (to empty). */ cmd_reset() { cp = cmdbuf; } /* * Backspace in command buffer. */ static int cmd_erase() { if (cp == cmdbuf) /* * Backspace past beginning of the string: * this usually means abort the command. */ return (1); if (control_char(*--cp)) { /* * Erase an extra character, for the carat. */ backspace(); cmd_col--; } backspace(); cmd_col--; return (0); } /* * Set up the display to start a new multi-character command. */ start_mcc() { lower_left(); clear_eol(); putc(mcc); cmd_col = 1; } /* * Process a single character of a multi-character command, such as * a number, or the pattern of a search command. */ static int cmd_char(c) int c; { if (c == erase_char) { if (cmd_erase()) return (1); } else if (c == kill_char) { /* {{ Could do this faster, but who cares? }} */ while (cmd_erase() == 0) ; } else { /* * Append the character to the string, * if there is room in the buffer and on the screen. */ if (cp < &cmdbuf[sizeof(cmdbuf)-1] && cmd_col < sc_width-3) { *cp++ = c; if (control_char(c)) { putc('^'); cmd_col++; c = carat_char(c); } putc(c); cmd_col++; } else bell(); } return (0); } /* * Return the number currently in the command buffer. */ static int cmd_int() { *cp = '\0'; return (atoi(cmdbuf)); } /* * Move the cursor to lower left before executing a command. * This looks nicer if the command takes a long time before * updating the screen. */ static void cmd_exec() { lower_left(); flush(); } /* * Display the appropriate prompt. */ static void prompt() { int what; if (first_cmd != NULL && *first_cmd != '\0') /* * No prompt necessary if commands are from first_cmd * rather than from the user. */ return; /* * Select the proper prompt and display it. */ if (pr_type == PR_LONG) { what = MNAME|MOF|MBYTE|MPCT; } else if (new_file) { what = MNAME|MOF|MPCT; new_file = 0; } else if (pr_type == PR_MEDIUM) { what = MNAME|MPCT; } else /* (pr_type == PR_SHORT) */ { putc(':'); return; } so_enter(); puts(eq_message(sc_width-1, what)); so_exit(); } /* * Get command character. * The character normally comes from the keyboard, * but may come from the "first_cmd" string. */ static int getcc() { if (first_cmd == NULL) return (getc()); if (*first_cmd == '\0') { /* * Reached end of first_cmd input. */ first_cmd = NULL; if (cp > cmdbuf) { /* * Command is incomplete, so try to complete it. * There are only two cases: * 1. We have "/string" but no newline. Add the \n. * 2. We have a number but no command. Treat as #g. * (This is all pretty hokey.) */ if (mcc != ':') return ('\n'); else return ('g'); } return (getc()); } return (*first_cmd++); } /* * Main command processor. * Accept and execute commands until a quit command, then return. */ public void commands() { register int c; register int n; register int scroll = 10; register int last_mcc = 0; mcc = 0; for (;;) { /* * Display prompt and accept a character. */ psignals(); /* See if any signals need processing */ cmd_reset(); lower_left(); clear_eol(); prompt(); c = getcc(); again: if (sigs) continue; if (mcc) { /* * We are in a multi-character command. * All chars until newline go into the command buffer. * (Note that mcc == ':' is a special case that * means a number is being entered.) */ if (mcc != ':' && (c == '\n' || c == '\r')) { /* * Execute the command. */ *cp = '\0'; cmd_exec(); if (mcc == 'E') { char *p; /* * Ignore leading spaces * in the filename. */ for (p = cmdbuf; *p == ' '; p++) ; edit(p); #if SHELL_ESCAPE } else if (mcc == '!') { lower_left(); putc('\n'); flush(); raw_mode(0); system(cmdbuf); raw_mode(1); error("!done"); first_cmd = "r"; /* Repaint */ #endif } else search(mcc, cmdbuf, n); mcc = 0; } else { if (mcc == ':' && (c < '0' || c > '9') && c != erase_char && c != kill_char) { /* * This is not part of the number * we were entering. Process * it as a regular character. */ mcc = 0; goto again; } /* * Append the char to the command buffer. */ if (cmd_char(c)) { /* Abort the multi-char command. */ mcc = 0; continue; } c = getcc(); goto again; } } else switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* * First digit of a number. */ mcc = ':'; start_mcc(); goto again; case 'f': case ' ': /* * Forward one screen. */ lower_left(); clear_eol(); forward(sc_height - 1); break; case 'b': /* * Backward one screen. */ lower_left(); clear_eol(); backward(sc_height - 1); break; case 'e': case 'j': case '\r': case '\n': /* * Forward N (default 1) line. */ n = cmd_int(); if (n <= 0) n = 1; lower_left(); clear_eol(); forward(n); break; case 'y': case 'k': /* * Backward N (default 1) line. */ n = cmd_int(); if (n <= 0) n = 1; lower_left(); clear_eol(); backward(n); break; case 'd': /* * Forward N lines * (default same as last 'd' or 'u' command). */ n = cmd_int(); if (n > 0) scroll = n; lower_left(); clear_eol(); forward(scroll); break; case 'u': /* * Forward N lines * (default same as last 'd' or 'u' command). */ n = cmd_int(); if (n > 0) scroll = n; lower_left(); clear_eol(); backward(scroll); break; case 'R': /* * Flush buffers, then repaint screen. */ ch_init(0); /* Fall thru */ case 'r': /* * Repaint screen. */ repaint(); break; case 'g': /* * Go to line N, default beginning of file. */ n = cmd_int(); if (n <= 0) n = 1; cmd_exec(); jump_back(n); break; case 'p': /* * Go to a specified percentage into the file. */ n = cmd_int(); if (n < 0) n = 0; if (n > 100) n = 100; cmd_exec(); jump_percent(n); break; case 'G': /* * Go to line N, default end of file. */ n = cmd_int(); cmd_exec(); if (n <= 0) jump_forw(); else jump_back(n); break; case '=': /* * Print file name, etc. */ error(eq_message(error_width(), MNAME|MOF|MBYTE|MPCT)); break; case 'v': /* * Print version number, without the "@(#)". */ error(version+4); break; case 'q': /* * Exit. */ return; case '/': case '?': /* * Search for a pattern. * Accept chars of the pattern until \n. */ n = cmd_int(); if (n <= 0) n = 1; mcc = last_mcc = c; start_mcc(); c = getcc(); goto again; case 'n': /* * Repeat previous search. */ n = cmd_int(); if (n <= 0) n = 1; mcc = last_mcc; start_mcc(); cmd_exec(); search(mcc, (char *)NULL, n); mcc = 0; break; case 'h': /* * Help. */ help(); repaint(); break; case 'E': /* * Edit a new file. Get the filename. */ cmd_reset(); mcc = 'E'; start_mcc(); putc(' '); /* This looks nicer */ cmd_col++; c = getcc(); goto again; #if SHELL_ESCAPE case '!': /* * Shell escape. */ cmd_reset(); mcc = '!'; start_mcc(); c = getcc(); goto again; #endif case 'N': /* * Examine next file. */ n = cmd_int(); if (n <= 0) n = 1; next_file(n); break; case 'P': /* * Examine previous file. */ n = cmd_int(); if (n <= 0) n = 1; prev_file(n); break; case '-': /* * Toggle a flag setting. */ mcc = '-'; start_mcc(); c = getcc(); mcc = 0; if (c == erase_char || c == kill_char) /* Abort the "-" command. */ break; toggle_flag(c); break; default: bell(); break; } } } SHAR_EOF cat - << \SHAR_EOF > help.c #include "less.h" /* * Display some help. * Help is in two pages. */ static void help0() { puts("f, SPACE Forward one screen.\n"); puts("b Backward one screen.\n"); puts("e, j, CR * Forward N lines, default 1.\n"); puts("y, k * Backward N lines, default 1.\n"); puts("d * Forward N lines, default 10 or last N to d or u command.\n"); puts("u * Backward N lines, default 10 or last N to d or u command.\n"); puts("r Repaint screen.\n"); puts("g * Go to line N, default 1.\n"); puts("G * Like g, but default is last line in file.\n"); puts("= Print current file name\n"); puts("/pattern * Search forward for N-th occurence of pattern.\n"); puts("?pattern * Search backward for N-th occurence of pattern.\n"); puts("n * Repeat previous search (for N-th occurence).\n"); puts("q Exit.\n"); error("More help..."); } static void help1() { puts("R Repaint screen, discarding buffered input.\n"); puts("p * Position to N percent into the file.\n"); puts("-x Toggle a flag (x may be b,c,d,t,m,M,u,U,q,Q).\n"); puts("E [file] Examine a new file.\n"); puts("N Examine the next file (from the command line).\n"); puts("P Examine the previous file (from the command line).\n"); puts("v Print version number.\n"); error(""); } public void help() { register int i; for (i = 0; i < 2; i++) { clear(); puts("Commands marked with * may be preceeded by a number, N.\n\n"); switch (i) { case 0: help0(); break; case 1: help1(); break; } } } SHAR_EOF cat - << \SHAR_EOF > input.c /* * High level routines dealing with getting input from the file being viewed. * When we speak of "lines" here, we mean PRINTABLE lines. */ #include "less.h" extern int do_bs; 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)) { line = NULL; return (NULL_POSITION); } c = ch_forw_get(); if (c == EOF) { line = NULL; return (NULL_POSITION); } prewind(); for (;;) { if (c == '\n' || 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('\0'); 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)) { line = NULL; return (NULL_POSITION); } /* * Scan backwards until we hit the beginning of the line. */ for (;;) { c = ch_back_get(); if (c == '\n') { /* * 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 = (POSITION)0; 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)) { line = NULL; return (NULL_POSITION); } loop: begin_new_pos = new_pos; prewind(); do { c = ch_forw_get(); new_pos++; if (c == '\n') 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('\0'); (void) ch_back_get(); new_pos--; goto loop; } } while (new_pos < curr_pos); (void) pappend('\0'); return (begin_new_pos); } SHAR_EOF cat - << \SHAR_EOF > line.c /* * Routines to manipulate the "line buffer". * The line buffer holds a line of output as it is being built * in preparation for output to the screen. * We keep track of the PRINTABLE length of the line as it is being built. */ #include "less.h" static char linebuf[1024]; /* Buffer which holds the current output line */ static char *curr; /* Pointer into linebuf */ static int column; /* Printable length, accounting for backspaces, etc. */ /* * A ridiculously complex state machine takes care of backspaces * when in BS_UNDERLINE mode. The complexity arises from the attempt * to deal with all cases, especially involving long lines with underlining. * There are still some cases which will break it. * * There are four states: * UL_NORMAL is the normal state (not in underline mode). * UL_YES means we are in underline mode. We expect to get * either a sequence like "_\bX" or "X\b_" to continue * underline mode, or just some ordinary characters * (no backspaces) to end underline mode. * UL_X means we are one character after UL_YES * (we have gotten the '_' in "_\bX" or the 'X' in "X\b_"). * UL_XB means we are one character after UL_X * (we have gotten the backspace in "_\bX" or "X\b_"; * we expect one more ordinary character, * which will put us back in state UL_YES). */ static int ul_state; /* Currently in underline mode? */ #define UL_NORMAL 0 /* Not in underline mode */ #define UL_YES 1 /* In underline, need next char */ #define UL_X 2 /* In underline, got char, need \b */ #define UL_XB 3 /* In underline, got char & \b, need one more */ public char *line; /* Pointer to the current line. Usually points to linebuf. */ extern int bs_mode; extern int ul_width, ue_width; extern int sc_width, sc_height; /* * Rewind the line buffer. */ public void prewind() { line = curr = linebuf; ul_state = UL_NORMAL; column = 0; } /* * Append a character to the line buffer. * Expand tabs into spaces, handle underlining. * Returns 0 if ok, 1 if couldn't fit in buffer. */ #define NEW_COLUMN(newcol) if ((newcol) + ((ul_state)?ue_width:0) > sc_width) \ return (1); else column = (newcol) public int pappend(c) int c; { if (c == '\0') { /* * Terminate underline mode, if necessary. * Append a '\0' to the end of the line. */ switch (ul_state) { case UL_X: curr[0] = curr[-1]; curr[-1] = UE_CHAR; curr++; break; case UL_XB: case UL_YES: *curr++ = UE_CHAR; break; } ul_state = UL_NORMAL; *curr = '\0'; return (0); } if (curr > linebuf + sizeof(linebuf) - 12) /* * Almost out of room in the line buffer. * Don't take any chances. * {{ Linebuf is supposed to be big enough that this * will never happen, but may need to be made * bigger for wide screens or lots of backspaces. }} */ return (1); if (bs_mode == BS_UNDERLINE) { /* * Advance the state machine. */ switch (ul_state) { case UL_NORMAL: if (curr <= linebuf + 1 || curr[-1] != '\b' || (c != '_' && curr[-2] != '_')) break; /* * We have either "_\bX" or "X\b_" (including * the current char). Switch into underline mode. */ if (column + ul_width + ue_width + 1 >= sc_width) /* * Not enough room left on the screen to * enter and exit underline mode. */ return (1); curr[-1] = curr[-2]; curr[-2] = UL_CHAR; column += ul_width; curr++; /* Fall thru */ case UL_XB: /* * Termination of a sequnce "_\bX" or "X\b_". */ if (c == '_') c = curr[-2]; curr -= 2; ul_state = UL_YES; break; case UL_YES: if (column + ue_width + 1 >= sc_width) /* * We have just barely enough room to * exit underline mode. */ return (1); ul_state = UL_X; break; case UL_X: if (c == '\b') ul_state = UL_XB; else { /* * Exit underline mode. * We have to shuffle the chars a bit * to make this work. */ curr[0] = curr[-1]; curr[-1] = UE_CHAR; curr++; column += ue_width; ul_state = UL_NORMAL; } break; } } if (c == '\t') { /* * Expand a tab into spaces. */ do { NEW_COLUMN(column+1); *curr++ = ' '; } while ((column % 8) != 0); return (0); } if (c == '\b') { if (bs_mode == BS_CONTROL) { /* * Treat backspace as a control char: output "^H". */ NEW_COLUMN(column+2); *curr++ = ('H' | 0200); } else { /* * Output a real backspace. */ column--; *curr++ = '\b'; } return (0); } if (control_char(c)) { /* * Put a "^X" into the buffer. * The 0200 bit is used to tell put_line() to prefix * the char with a ^. We don't actually put the ^ * in the buffer because we sometimes need to move * chars around, and such movement might separate * the ^ from its following character. */ NEW_COLUMN(column+2); *curr++ = (carat_char(c) | 0200); return (0); } /* * Ordinary character. Just put it in the buffer. */ NEW_COLUMN(column+1); *curr++ = c; return (0); } SHAR_EOF