[comp.sources.unix] v19i010: A command-line editor

rsalz@uunet.uu.net (Rich Salz) (06/01/89)

Submitted-by: ka@june.cs.washington.edu (Kenneth Almquist)
Posting-number: Volume 19, Issue 10
Archive-name: atty/part01

Atty is an alternative to tty(4).
Atty provides a set of EMACS-like editing commands which allow
you to move around the input line and make corrections where ever you want.
Atty allows you to bring lines that you previously typed into the input
buffer so that you can resend them, possible after modifying them.  Atty
also handles line wrapping and does a better job than tty of keeping input
and output from getting intermingled.  Not to mention its abbreviation
feature...  Try it, you'll like it!

Atty runs entirely in user mode and requires no special privileges to run
or install.  It's a hack, but it works.  I developed the code under Ultrix,
a 4.2 BSD derivative.  It has also been tested on a couple of other 4.2 BSD
derivatives.  The code does not run under System V, and would not be easy
to port.

# This is part 1 of atty.  To unpack, feed it into the shell (not csh).
# The atty distribution consists of four pieces.  After you unpack everyting,
# read the file README.
mkdir atty ; cd atty
echo extracting README
cat > README <<\EOF
			    Welcome to ATTY

Atty is an alternative to tty(4).  To correct a mistake using tty(4), you
must erase all the characters between the mistake and the end of the line.
In contrast, atty provides a set of EMACS-like editing commands which allow
you to move around the input line and make corrections where ever you want.
Atty allows you to bring lines that you previously typed into the input
buffer so that you can resend them, possible after modifying them.  Atty
also handles line wrapping and does a better job than tty of keeping input
and output from getting intermingled.  Not to mention its abbreviation
feature...  Try it, you'll like it!

Atty runs entirely in user mode and requires no special privileges to run
or install.  It's a hack, but it works.  I developed the code under Ultrix,
a 4.2 BSD derivative.  It has also been tested on a couple of other 4.2 BSD
derivatives.  The code does not run under System V, and would not be easy
to port.


			     INSTALLATION

To compile atty, just type "make".  Three programs should be created:

	atty - the atty program
	kbind - program to generate key binding files
	fmatch - file name matching program

These should be installed in a bin directory somewhere.  Three additional
files are generated:

	atty.bindc - default binding file
	dftbind.c - name of default binding file
	atty.1 - atty manual page

Atty.bindc is the default key binding file, which is created from the
file atty.bind.  The latter two files include the absolution path name
of atty.bindc.  The makefile will create these files correctly if you
just leave atty.bindc in the source directory.  If you install atty.bindc
somewhere else, you will have to edit these files.

The makefile used cc.  The Berkeley header files are incompatible with
ANSI C, so if you compile with gcc or some other ANSI compiler, you will
get incorrect code.  You can use an ANSI compatable version of the header
files, or you can use the -traditional flag of gcc.

Having compiled atty, you are now ready to run it.  However, you may want
to change some the bindings first.  The end of the "Getting Started"
section of the atty manual page provides a tutorial introduction to
doing this; if you really want to know what's going on read the kbind
manual page.  Some users will probably want set up their bindings to act
like vi rather than emacs.  The file vi.bind illustrates how to set up
separate insert and command modes, like vi has.  However, I have not
implemented the vi commands, and you can't have a real vi emulation
without them.


Copyright (c) 1989 by Kenneth Almquist
EOF
if test `wc -c < README` -ne 2593
then	echo 'README is the wrong size'
fi
echo extracting LICENSE
cat > LICENSE <<\EOF
		    ATTY GENERAL PUBLIC LICENSE

  Copyright 1989 by Kenneth Almquist.
  Everyone is permitted to copy and distribute verbatim copies of this
  license, but changing it is not allowed.  You can also use this wording
  to make the terms for other programs.  This license agreement uses
  wording taken from the GNU Emacs General Public License, which is
  copyright 1985, 1987, 1988 by Richard M. Stallman.  Richard M. Stallman
  is not responsible for the contents of this license.


Atty is copyrighted.  To encourage maximum use of this software, I,
Kenneth Almquist, hereby grant all recipients of atty the right to
reproduce and modify atty under the terms specified in this license.


			COPYING POLICIES

  1. You may copy and distribute verbatim copies of atty source code
as you receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy a valid copyright notice "Copyright
1989 by Kenneth Almquist." (or with whatever year is appropriate); keep
intact the notices on all files that refer to this License Agreement
and to the absence of any warranty; and give any other recipients of
the atty program a copy of this License Agreement along with the program.
You may charge a distribution fee for the physical act of transferring
a copy.

  2. You may modify your copy or copies of atty source code or
any portion of it, and copy and distribute such modifications under
the terms of Paragraph 1 above, provided that you also do the following:

    a) cause the modified files to carry prominent notices stating
    that you changed the files and the date of any change; and

    b) cause the whole of any work that you distribute or publish,
    that in whole or in part contains or is a derivative of atty
    or any part thereof, to be licensed at no charge to all third
    parties on terms identical to those contained in this License
    Agreement (except that you may choose to grant more extensive
    warranty protection to some or all third parties, at your option).

    c) You may charge a distribution fee for the physical act of
    transferring a copy, and you may at your option offer warranty
    protection in exchange for a fee.

Mere aggregation of another unrelated program with this program (or its
derivative) on a volume of a storage or distribution medium does not bring
the other program under the scope of these terms.

  3. You may copy and distribute atty (or a portion or derivative of it,
under Paragraph 2) in object code or executable form under the terms of
Paragraphs 1 and 2 above provided that you also do one of the following:

    a) accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of
    Paragraphs 1 and 2 above; or,

    b) accompany it with a written offer, valid for at least three
    years, to give any third party free (except for a nominal
    shipping charge) a complete machine-readable copy of the
    corresponding source code, to be distributed under the terms of
    Paragraphs 1 and 2 above; or,

    c) accompany it with the information you received as to where the
    corresponding source code may be obtained.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form alone.)

For an executable file, complete source code means all the source code for
all modules it contains; but, as a special exception, it need not include
source code for modules which are standard libraries that accompany the
operating system on which the executable file runs.

  4. The atty distribution contains two files, regex.h and regex.c,
which are copyrighted by the Free Software Foundation, Inc.  These files
are not covered by this license agreement, and may only be used or copied
in accordance with the license agreements that appear at the top of those
files.  The compilation process for the atty program links it with regex.c.
Therefore binary versions of the atty program are covered by both this
license and the license appearing at the top of regex.c.

  5. You may not copy, sublicense, distribute or transfer atty except as
expressly provided under this License Agreement.  Any attempt otherwise
to copy, sublicense, distribute or transfer atty is void and your rights
to use atty under this License agreement shall be automatically terminated.
However, parties who have received computer software programs from you
with this License Agreement will not have their licenses terminated so
long as such parties remain in full compliance.


			   NO WARRANTY

  Because atty is licensed free of charge, I provide absolutely no
warranty, to the extent permitted by applicable state law.  Except
when otherwise stated in writing, Kenneth Almquist and/or other
parties provide atty "as is" without warranty of any kind, either
expressed or implied, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose.
The entire risk as to the quality and performance of the program is
with you.  Should the atty program prove defective, you assume the cost
of all necessary servicing, repair or correction.

 In no event unless required by applicable law will Kenneth Almquist
and/or any other party who may modify and redistribute atty as permitted
above, be liable to you for damages, including any lost profits, lost
monies, or other special, incidental or consequential damages arising
out of the use or inability to use (including but not limited to loss
of data or data being rendered inaccurate or losses sustained by third
parties or a failure of the program to operate with programs provided
by other parties) the program, even if you have been advised of the
possibility of such damages, or for any claim by any other party.
EOF
if test `wc -c < LICENSE` -ne 5831
then	echo 'LICENSE is the wrong size'
fi
echo extracting atty.1.mk
cat > atty.1.mk <<\EOF
echo '.TH ATTY 1'
echo ".ds d `pwd`/atty.bind"
cat <<\!
.SH NAME
atty \- alternative tty driver
.SH SYNOPSYS
.B atty
.B -ls
.B -c
.I command
[
.I bindings-file
]
.SH COPYRIGHT
.if n Copyright (C) 1989 by Kenneth Almquist.
.if t Copyright \(co 1989 by Kenneth Almquist.  
.SH DESCRIPTION
.I Atty
is an alternative to
.IR tty (4).
It puts the terminal in
.I cbreak
mode to suppress the line editing normally done by
.IR tty (4)
.PP
Most of the standard
.IR tty (4)
features (except for the ones relating to input line editing) are supported.
Skip to the the section titled
.I "Getting Started"
if you just want an introduction to
.IR atty .
.PP
When
.I atty
starts up, it runs the shell specified by the environment variable
SHELL.  It sets the environment variable ATTY to inform programs that
they are running under \fIatty\fR.
If the
.B -l
option is given, the shell is made a login shell (by prepending a minus
sign to arg zero).
Normally suspending the shell will cause
.I atty
to suspend itself.  The
.B -s
option causes
.I atty
to restart a suspended shell instead.
The
.B -c
option causes the shell to run the specified command before reading commands
from the standard input.  This only works with
.IR ash (1).
(I use it to make the prompt of my top level shell different from the
default ``@\ '' \fIwithout\fR passing PS1 as an environment variable,
which would change the prompt for subshells as well as the top level shell.)
.PP
The optional
.I bindings-file
controls the key bindings used by the editor (see
.IR kbind (1)).
If
.I bindings-file
is omitted,
.I atty
use the file "$HOME/.bindc" as the bindings file if it exists;
otherwise it uses the system default binding file ``\*dc''.
.PP
Several escape sequences are handled specially by
.IR atty .
``ESC ] P''
introduces a prompt.  The remainder of the line consists of a sequence
of digits giving the program ID, a semicolon, and the text of the
prompt.
``ESC ] D newline'' deletes the current prompt (if any).
These two escape sequences can be used by shells (such as
.IR ash (1))
that know about
.IR atty .
``ESC ] I'' inserts text in the input buffer.  The remainder of the
line contains the text to be inserted.  This is used by the
.B -c
option of
.IR fmatch (1).
.PP
Clearing the ODDP bit in the sgtty structure (``stty -odd'') turns off
the special input editing features of
.I atty
and causes the normal
.IR tty (4)
input editing to be done.
.PP
When the prompt is set (via ``ESC ] P''),
.I atty
assumes that it can run the
.IR fmatch (1)
program by inputing a command line preceded by an FS character (octal 34).
.sp 2
.B "Getting Started"
.PP
This section gives an overview of the editiing features of
.I atty
when the default binding file is used.  For detailed descriptions of the
commands, see
.IR kbind (1).
.PP
Each command can be given a numeric prefix argument.  One way to do this
is to type ESC followed by the number.  Another is to type ^U.
^U multiplies the preceding prefix argument by 4 if one is given,
so a single ^U sets the prefix argument to 4, ^U ^U sets the prefix
argument to 16, and so on.  Typically, the prefix argument specifies the
number of times that a command is to be repeated.  Most commands will do
something sensible with a negative argument.
.PP
Printing characters are bound to the
.I self-insert
command, so to enter then you just type them.
Control characters are used for commands, but you can enter them by
preceding them with ^V.
.PP
Several commands correspond to functions normally provided by
.IR tty (4).
DEL (^?) sends an interrupt signal, FS (^\e) sends a quit signal, and
^Z sends a stop signal.  The first two of these normally flush
.IR atty 's
input buffer.  ^D generate an end of file when typed on an empty line.
(Otherwise it deletes the following character.)  These keys are the ones
that you are most likely to want to re-bind; see below.
.PP
There are several commands for moving around on a line.  ^F moves forward
one character and ^B moves backward one character.  ESC f and ESC b move
forward and backword by words rather than characters.  ESC a and ESC e
move to the beginning and the end of the line, respectively.
.PP
The commands that delete text generally save the deleted text in the
.IR "kill buffer" .
You can then get the text back by typing ^Y.  This feature is often
used to move text around.  Also, if you delete text by accident, you
can get it back by typing ^Y.
.PP
The most important deletion command is BACKSPACE, which deletes the
preceding character.  It only saves the deleted text to the kill buffer
if you gave an explicit prefix argument.  ^D is similar to BACKSPACE,
but deletes forwards rather than backwards.  (As a special case, ^D
generates an end of file when typed on an empty line.)  There are also
commands for deleting words:  ESC BACKSPACE deletes the preceding word and
ESC d deletes the following word.  Finally, ^W deletes everything between
the cursor and a location in the line known as the
.IR mark .
You can set the location of the mark by typing ^@.
.PP
One final text modification command worth mentioning is ^T, which transposes
the two preceding characters.
.PP
.I Atty
mantains a history of lines typed.  ^P moves to the preceding history line
and ^N moves to the next history line.  Both ^P and ^N skip blank lines.
^R does a reverse search through the history file for a specified regular
expression (which you type in, followed by a carriage return).  When
you go to a line, it is loaded into the input buffer where you can then
edit it.  (Editing it does not affect the contents of the actual history
file.)
.PP
When you type carriage return to send a line,
.I atty
moves to the end of the history file.  You can also send a line using
^J (NEWLINE); in this case
.I atty
will advance to the next line of the history file rather than moving to
the end.  ^J is useful for resending a series of lines.
ESC . inserts the last word of the last history line.  This
is useful for entering sequences like:
.in +1i
.nf
mkdir directory
cd directory
.fi
.in -1i
Finally, ``ESC ,'' inserts the last line of output.
.sp 2
.B "Changing Bindings"
.PP
If you want to change a few bindings, the best thing to do is to copy
the system binding file \*d to the file .bind in your home directory,
and then edit it.  As an example, suppose you are a closet VMS fanatic
and want to make CONTROL-C be your interrupt character.  You will find
that two of the lines in the .bind file read
.sp
.nf
	b	^C	upcase-char
and
	b	\e177	tty-intr
.fi
.sp
The first line binds ^C to upcase-char, a function that converts a
character to upper case.  The second line binds DELETE (written here
as \e177; it could also have been written as ^?) to tty-intr.  You
want CONTROL C to do what DELETE does in the standard binding file,
so change the ``upcase-char'' to ``tty-intr''.  Now both CONTROL C
and DELETE will generate an interrupt, which is perfectly legal.  In
practice you probably want to make DELETE delete the preceding character,
which you can do by binding it to delete-backward-char.  The resulting
lines look like:
.sp
.nf
	b	^C	tty-intr
and
	b	\e177	delete-backward-char
.fi
.PP
After you have set up the .bind file to your satisfaction, type
.sp
	kbind .bind
.sp
This will create a file named .bindc which
.I atty
will read instead of the default binding file.
\fIIf you omit this step, atty will ignore the \fR.bind\fI file and will
continue to use the default binding file.\fR  Atty doesn't look for a
\&.bind file, only a .bindc file.
.SH AUTHORS
Kenneth Almquist
.SH FILES
\*dc\h'0.5i'default binding file
.SH "SEE ALSO"
.ta 2i
\fIkbind\fR(1)
.SH BUGS
Atty could be more intelligent about updating the screen in some cases.
(The slowest terminals on the systems it was developed on were 9600 baud.)
.PP
The program does not create a /etc/utmp entry for the pseudo-terminal that
it uses.  This confuses
.IR talk (1).
.PP
Changing the line discipline will cause problems.  There are also a number
of race conditions.  This program really needs more operating system support.
(Stream pipes would help, but an acknowledgement should really be sent back
when data is read to allow prompts and input lines to be associated correctly.)
!
EOF
if test `wc -c < atty.1.mk` -ne 8197
then	echo 'atty.1.mk is the wrong size'
fi
echo extracting atty.bind
cat > atty.bind <<\EOF
# This is the default key binding file for atty.

mode 0

default		self-insert
b	^@	set-mark
b	^A	beginning-of-line
b	^B	backward-char
b	^C	upcase-char
#b	^D	delete-char
b	^D	eof-or-delete-char
b	^E	end-of-line
b	^F	forward-char
b	^G	undefined
b	^H	delete-backward-char
b	\t	self-insert
b	^J	newline-and-insert
b	^K	kill-line
b	^L	undefined
b	\r	newline
b	^N	next-history
b	^O	undefined
b	^P	previous-history
b	^Q	quoted-insert
b	^R	re-search-backward
b	^S	re-search-forward
b	^T	gosling-transpose-chars
b	^U	universal-argument
b	^V	quoted-insert
b	^W	kill-region
b	^X	kill-input
b	^Y	yank
b	^Z	tty-susp
b	\034	tty-quit
b	\035	undefined
b	\036	undefined
#b	\037	end-of-file
b	\177	tty-intr

b	\e^H	backward-kill-word
b	\e\s	set-mark
b	\e<	beginning-of-history
b	\e>	end-of-history
b	\e-	negative-argument
b	\e0	digit-argument
b	\e1	digit-argument
b	\e2	digit-argument
b	\e3	digit-argument
b	\e4	digit-argument
b	\e5	digit-argument
b	\e6	digit-argument
b	\e7	digit-argument
b	\e8	digit-argument
b	\e9	digit-argument
b	\eb	backward-word
b	\eB	backward-word
b	\ed	kill-word
b	\eD	kill-word
b	\ef	forward-word
b	\eF	forward-word
b	\eh	backward-kill-word
b	\eH	backward-kill-word
#b	\em	insert "/usr/spool/mail/ka"
b	\et	transpose-words
b	\eT	transpose-words
b	\eu	upcase-word
b	\eU	upcase-region
b	\ew	copy-region-as-kill
b	\eW	copy-region-as-kill
b	\ex	exchange-point-and-mark
b	\eX	exchange-point-and-mark
b	\e\e	file-complete
b	\e=	list-file-completions
b	\e,	last-output-line
b	\e.	get-history-word
b	\e\177	backward-kill-word

# arrow keys
b	\e[C	forward-char
b	\e[D	backward-char
b	\e[A	previous-history
b	\e[B	next-history

syntax word	"a-zA-Z0-9$%"
syntax filename	"^ \t\n&();<>|"
syntax abbrev	"^ \t\n&();<>|/"

#abbrev	"~"	"$HOME"
EOF
if test `wc -c < atty.bind` -ne 1737
then	echo 'atty.bind is the wrong size'
fi
echo extracting atty.h
cat > atty.h <<\EOF
/*
 * Copyright (C) 1989 by Kenneth Almquist.  All rights reserved.
 * This file is part of atty, which is distributed under the terms specified
 * by the Atty General Public License.  See the file named LICENSE.
 */

struct tty {			/* structure containing terminal modes */
      int ldisc;		/* line discipline */
      struct sgttyb sgtty;	/* stty modes */
      int lmode;		/* local mode */
      struct tchars tchars;	/* special characters */
      struct ltchars ltchars;	/* more special characters */
      /* window size omitted */
};
EOF
if test `wc -c < atty.h` -ne 542
then	echo 'atty.h is the wrong size'
fi
echo extracting atty.c
cat > atty.c <<\EOF
/*
 * Atty - tty(4) replacement.
 *
 * Copyright (C) 1989 by Kenneth Almquist.  All rights reserved.
 * This file is part of atty, which is distributed under the terms specified
 * by the Atty General Public License.  See the file named LICENSE.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <signal.h>
#include <sgtty.h>
#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include "atty.h"
#include "attyed.h"


#ifndef __STDC__
#define const
#define volatile
#endif


#define ECHO_NL 1		/* echo input line terminated by a newline */
#define ECHO_EOF 2		/* echo input line terminated by EOF */
#define ECHO_SIG 3		/* echo interrupt character */


struct indata {
      struct indata *next;	/* next structure on queue */
      short nchars;		/* number of characters */
      short nwrt;		/* number of characters already written */
      char data[4];		/* characters; should be "char data[nchars]" */
};


struct tty origtty;		/* original tty modes */
struct tty realtty;		/* tty modes of real tty */
struct tty ptymodes;		/* modes of pty */
int realfcntl;			/* flags for real tty file descriptor */
int ptyfd;			/* file descriptor of pty */
char ptydevice[12];		/* pty device */
int childpid;			/* pid of child process */
jmp_buf sigjmp;			/* where to jump on signal */
volatile int canjump;		/* set if should do longjmp on signal */
volatile int gotsigchild;	/* child is dead */
volatile int gotsigwinch;	/* window size has changed */
volatile int childstatus;	/* status of child */
struct indata *inputq;		/* queue of data to be sent to pty */
struct indata *inqlast;		/* last entry in inputq */
struct mode ttymode;		/* tty characters for editor */
int remoteon;			/* true if TIOCREMOTE mdoe is on */
int needclearin;		/* true if we must clear the tty input */
int echoflag;			/* set if output echoed */
char sigchar;			/* signal character to be echoed */
int noedit;			/* true if not using input editor */
int logall;			/* save all output data in the trace file */
int loginshell;			/* true if should start up login shell */
int nosuspend;			/* don't permit atty to be suspended */
char *shellcmd;			/* prompt to be used by shell */
short ospeed;			/* output speed, for termcap */
int gottty;			/* true if we have read the tty modes */

const struct ltchars real_ltchars = { -1, -1, -1, -1, -1, -1 };

/* C library routines */
#ifdef __STDC__
int ioctl(int, int, char *);
int isatty(int);
int getpid(void);
char *getenv(char *);
#else
int ioctl();
int isatty();
int getpid();
char *getenv();
#endif


#ifdef __STDC__
int openpty(void);
void spawnshell(void);
void runtty(void);
void ttyoutc(int);
void outreal(char *, int);
void writereal(void);
void senddata(char *, int);
void writepty(void);
int stripecho(int, char *, int);
void flushptyin(void);
void onchild();
void onwinch();
void indata(char *, int);
void newttymodes();
void fillttymode(void);
void sendsig(int);
void flushoutput(void);
void fatal(char *);
void done(int);
void cleanup(void);
int getttymodes(int, struct tty *);
int setttymodes(int, struct tty *);
int copyttysize(void);
#else
int openpty();
void spawnshell();
void runtty();
void ttyoutc();
void outreal();
void writereal();
void senddata();
void writepty();
int stripecho();
void flushptyin();
void onchild();
void onwinch();
void indata();
void newttymodes();
void fillttymode();
void sendsig();
void flushoutput();
void fatal();
void done();
void cleanup();
int getttymodes();
int setttymodes();
int copyttysize();
#endif

#define scopy(s1, s2)	strcpy(s2, s1)	/*TEMPORARY*/


main(argc, argv)
      char **argv;
      {
      int one = 1;
      int i;
      char **ap;
      char *p;

      if (! isatty(2) || ! isatty(0)) {
	    fputs("Atty can only be run from a terminal\n", stderr);
	    exit(2);
      }
      ap = argv;
      if (argc > 0) {
	    ap++;
	    for (;;) {
		  if ((p = *ap) == NULL || *p++ != '-')
			break;
		  ap++;
		  if (p[0] == '-' && p[1] == '\0')	/* "--" */
			break;
		  while (*p) {
			switch (*p++) {
			case 'l':
			      loginshell++;
			      break;
			case 's':
			      nosuspend++;
			      break;
			case 'c':
			      if (*p == '\0' && (p = *ap++) == NULL) {
				    fputs("atty:  no arg for -c option\n");
				    exit(2);
			      }
			      shellcmd = p;
			      p = "";
			      break;
			default:
			      fprintf(stderr, "atty: Illegal option -%c\n", p[-1]);
			      exit(2);
			}
		  }
	    }
      }
      signal(SIGCHLD, onchild);
      ptyfd = openpty();
      if (getttymodes(2, &origtty) < 0)
	    fatal("getttymodes");
      realfcntl = fcntl(2, F_GETFL, 0);
      gottty = 1;
      ospeed = origtty.sgtty.sg_ospeed;
      edinit(*ap);
      ptymodes = origtty;
      ptymodes.ldisc = NTTYDISC;	/* we must have literal next char */
      ptymodes.sgtty.sg_flags |= EVENP|ODDP;	/* no parity checking */
      realtty = origtty;
      realtty.tchars.t_intrc = -1;
      realtty.tchars.t_quitc = -1;
      realtty.ltchars = real_ltchars;
      realtty.sgtty.sg_flags |= CBREAK;
      realtty.sgtty.sg_flags &=~ (CRMOD|ECHO|LCASE);
      if ((realtty.sgtty.sg_flags & TBDELAY) == XTABS)
	    realtty.sgtty.sg_flags &=~ TBDELAY;
      if (setttymodes(2, &realtty) < 0) {
	    perror("setttymodes");
	    exit(2);
      }
      /* copyttysize(); */
      realfcntl |= FNDELAY;
      fcntl(2, F_SETFL, realfcntl);
      i = fcntl(ptyfd, F_GETFL, 0);
      i |= FNDELAY;
      fcntl(ptyfd, F_SETFL, i);
#ifdef REMOTE
      if (ioctl(ptyfd, TIOCREMOTE, (char *)&one) < 0)
	    fatal("TIOCREMOTE");
      remoteon = 1;
#endif
      if (ioctl(ptyfd, TIOCPKT, (char *)&one) < 0)
	    fatal("TIOCPKT");
      if (setttymodes(ptyfd, &ptymodes) < 0)
	    fatal("set pty modes");
      fillttymode();
      copyttysize();
      if ((childpid = fork()) == -1) {
	    fatal("fork");
      }
      if (childpid == 0)
	    spawnshell();
      else {
	    signal(SIGTTOU, SIG_IGN);
	    signal(SIGWINCH, onwinch);
	    runtty();
      }
      /*NOTREACHED*/
}
      

/*
 * Search for an unused pty.  It returns a file descriptor for the master
 * and leaves the name of the slave in the global variable ptydevice.
 * This code starts at the beginning; a more efficient version might start
 * at a randomly chosen point in the middle.
 */

int
openpty() {
      char c;
      int i;
      int fd;

      scopy("/dev/ptyXX", ptydevice);
      for (c = 'p'; c <= 's'; c++) {
            for (i = 0 ; i < 16 ; i++) {
                  ptydevice[8] = c;
                  ptydevice[9] = "0123456789abcdef"[i];
                  fd = open(ptydevice, 2);
                  if (fd >= 0) {
			ptydevice[5] = 't';	/* change "pty" to "tty" */
                        return fd;		/* success */
                  }
                  if (errno == ENOENT)
                        goto failure;
            }
      }
failure:
      fprintf(stderr, "Out of pty's\n");
      exit(2);
}


/*
 * This routine exec's the shell.
 */

void
spawnshell() {
      int fd;
      int mypid = getpid();
      char *shell;
      char **env;
      char **ep;
      extern char **environ;
      char arg0[16];
      char *p;
      char *basename;
      char *argv[4];

      if ((shell = getenv("SHELL")) == NULL)
	    shell = "/bin/sh";
      for (ep = environ ; *ep ; ep++);
      if ((env = (char **)malloc((char *)(ep + 2) - (char *)environ)) == NULL)
	    fatal("Malloc failed");
      for (ep = env ; *environ ; environ++) {
	    if (strncmp(*environ, "ATTY=", 5) != 0)
		  *ep++ = *environ;
      }
      *ep++ = "ATTY=";
      *ep = NULL;
      close(0);
      close(1);
      close(ptyfd);
      fd = open("/dev/tty", O_RDWR);
      if (fd >= 0) {
	    ioctl(fd, TIOCNOTTY, (char *)0);
	    close(fd);
      }
      setpgrp(mypid, 0);
      if (open(ptydevice, O_RDWR) != 0) {
	    perror(ptydevice);
	    exit(2);
      }
      setpgrp(mypid, mypid);
      close(2);
      dup(0);				/* file descriptor 1 = dup of 0 */
      dup(0);				/* file descriptor 2 = dup of 0 */
      if (ioctl(2, TIOCSPGRP, (char *)&mypid) < 0)
	    printf("TIOCSPGRP failed, errno=%d\n", errno);
      basename = shell;
      for (p = shell ; *p ; p++) {
	    if (*p == '/')
		  basename = p + 1;
      }
      if (loginshell) {
	    arg0[0] = '-';
	    for (p = arg0 + 1 ; p < arg0 + 15 && *basename ; *p++ = *basename++);
	    *p = '\0';
	    basename = arg0;
      }
      argv[0] = basename;
      argv[1] = NULL;
      if (shellcmd) {
	    argv[1] = "-sc";
	    argv[2] = shellcmd;
	    argv[3] = NULL;
      }
      execve(shell, argv, env);
      perror(shell);
      exit(2);
}


#define OUTRSIZE 512

char outrbuf[OUTRSIZE];
char *outrp = outrbuf;
char *outrnext = outrbuf;
int outrsize;


/*
 * This routine contains the main loop which is run after initialization.
 */

void
runtty() {
      volatile int ptybit = 1 << ptyfd;
      volatile int realbit = 1 << 2;
      volatile int nfds = ptyfd + 1;
      volatile int inbits, outbits, exceptbits;
      volatile int sel;
      int i, j;
      char buf[260];

      if (setjmp(sigjmp))
	    goto gotsig;
      for (;;) {
	    inbits = realbit;
	    outbits = 0;
	    if (outrsize == 0)
		  inbits |= ptybit;
	    else
		  outbits = realbit;
	    if (inputq != NULL)
		  outbits |= ptybit;
	    exceptbits = 0;
	    canjump = 1;
	    if (gotsigchild || gotsigwinch)
		  goto gotsig;
	    sel = select(nfds, &inbits, &outbits, &exceptbits, (struct timeval *)NULL);
	    canjump = 0;
	    if (0) {	/* if interrupted by signal */
gotsig:
		  if (gotsigchild) {
			gotsigchild = 0;
			i = childstatus;
			if ((i & 0xFF) == 0177
			 && (i >> 8 == SIGTSTP || i >> 8 == SIGSTOP)) {
			      if (! nosuspend) {
				    setttymodes(2, &origtty);
				    fcntl(2, F_SETFL, realfcntl &~ FNDELAY);
				    kill(getpid(), SIGTSTP);
				    setttymodes(2, &realtty);
				    fcntl(2, F_SETFL, realfcntl);
			      }
			      killpg(childpid, SIGCONT);
			} else {
			      if ((i & 0xFF) == 0)
				    i >>= 8;
			      else
				    i = i & 0xFF | 0x80;
			      done(i);
			}
		  }
		  if (gotsigwinch) {
			gotsigwinch = 0;
			newscrnwidth(copyttysize());
		  }
	    } else if (sel < 0) {
		  if (errno != EINTR)
			fatal("select");
	    } else {
		  if (inbits & realbit) {
			if ((i = read(2, buf, sizeof buf)) < 0)
			      fatal("tty read");
			if (i == 0)
			      fatal("tty eof");
			indata(buf, i);
		  }
		  if (inbits & ptybit) {
			if ((i = read(ptyfd, buf, sizeof buf)) < 0) {
			      /*
			       * Probably the problem is that the child
			       * process has terminated, which causes the
			       * ptc device to return EIO.
			       */
			      if (gotsigchild)
				    goto gotsig;
			      if (errno == EIO) {
				    sleep(1);
				    errno = EIO;
				    if (gotsigchild)
					  goto gotsig;
			      }
			      fatal("pty read");
			}
			if (i == 0)
			      fatal("pty eof");
#ifndef TIOCPKT_IOCTL
			newttymodes();
#endif
			if (buf[0] == TIOCPKT_DATA) {
			      j = 0;
			      if (echoflag) {
				    j = stripecho(echoflag, buf + 1, i - 1);
				    echoflag = 0;
			      }
			      j++;
#ifdef notdef
			      if (logall)
				    fwrite(buf + 1, i - 1, 1, trace);
#endif
			      outchars(buf + j, i - j);
			      writereal();
			      if (noedit == 0)
				    refresh();
			} else {
#ifdef TIOCPKT_IOCTL
			      if (buf[0] & TIOCPKT_IOCTL)
				    newttymodes();
#endif
			      if (buf[0] & TIOCPKT_FLUSHREAD) {
				    clearinput();
			      }
			}
		  }
		  if (outbits & realbit) {
			writereal();
		  }
		  if (outbits & ptybit) {
			writepty();
		  }
	    }
      }
}


/*
 * Output a character to the real tty device.
 */

void
ttyoutc(c)
      char c;
      {
      outreal(&c, 1);
}


/*
 * Output a block of characters to the real tty device.
 */

void
outreal(p, size)
      char *p;
      {
      int n = &outrbuf[OUTRSIZE] - outrnext;

      if (size + outrsize > OUTRSIZE)
	    fatal("output overflow");
      if (n > size)
	    n = size;
      bcopy(p, outrnext, n);
      outrnext += n;
      if (outrnext == &outrbuf[OUTRSIZE])
	    outrnext = outrbuf;
      if (n < size) {
	    p += n;
	    n = size - n;
	    bcopy(p, outrnext, n);
	    outrnext += n;
      }
      outrsize += size;
}


/*
 * Write as much data as possible to the real tty, until we block.
 */

void
writereal() {
      int i;
      int n;

      while (outrsize > 0) {
	    n = &outrbuf[OUTRSIZE] - outrp;
	    if (n > outrsize)
		  n = outrsize;
	    if ((i = write(2, outrp, n)) < 0) {
		  if (errno == EWOULDBLOCK)
			break;
		  else
			fatal("tty write");
	    }
	    outrsize -= i;
	    outrp += i;
	    if (outrp == &outrbuf[OUTRSIZE])
		  outrp = outrbuf;
      }
}


/*
 * Send data to the pty.
 */

#define INDATAHDR ((char *)((struct indata *)0)->data - (char *)0)

void
senddata(p, size)
      char *p;
      {
      struct indata *ip;

      if ((ip = (struct indata *)malloc(INDATAHDR + size)) == NULL)
	    fatal("malloc pty data");
      ip->nchars = size;
      ip->nwrt = 0;
      bcopy(p, ip->data, size);
      ip->next = NULL;
      if (inputq == NULL)
	    inputq = ip;
      else
	    inqlast->next = ip;
      inqlast = ip;
}



/*
 * Like writereal, but for pty.
 */

void
writepty() {
#ifdef REMOTE
      int i;
      struct indata *ip;
      int one = 1;

      if ((ip = inputq) == NULL)
	    return;
      if (! remoteon) {
	    if (ioctl(ptyfd, TIOCREMOTE, (char *)&one) < 0)
		  fatal("TIOCREMOTE");
	    remoteon = 1;
      }
      if ((i = write(ptyfd, ip->data, ip->nchars)) < 0) {
	    if (errno != EWOULDBLOCK)
		  fatal("pty write");
	    return;
      } else if (i != ip->nchars) {
	    fatal("partial pty write");
      }
      inputq = ip->next;
      free((char *)ip);
#else
      int i;
      struct indata *ip;
      char buf[512];
      int buflen;
      char c;
      char *p;
      int echotype;

      if ((ip = inputq) == NULL)
	    return;
      if (noedit) {
	    i = write(ptyfd, ip->data + ip->nwrt, ip->nchars - ip->nwrt);
	    if (i < 0)
		  fatal("pty write");
	    if ((ip->nwrt += i) >= ip->nchars) {
		  inputq = ip->next;
		  free((char *)ip);
	    }
      } else {	  
	    newttymodes();	/* just in case */
	    p = buf;
	    for (i = ip->nwrt ; i < ip->nchars ; i++) {
		  c = ip->data[i];
		  if (c == ptymodes.sgtty.sg_erase
		   || c == ptymodes.sgtty.sg_kill
		   || c == ptymodes.tchars.t_intrc
		   || c == ptymodes.tchars.t_quitc
		   || c == ptymodes.tchars.t_startc
		   || c == ptymodes.tchars.t_stopc
		   || c == ptymodes.tchars.t_eofc
		   || c == ptymodes.tchars.t_brkc
		   || c == ptymodes.ltchars.t_suspc
		   || c == ptymodes.ltchars.t_dsuspc
		   || c == ptymodes.ltchars.t_rprntc
		   || c == ptymodes.ltchars.t_flushc
		   || c == ptymodes.ltchars.t_werasc
		   || c == ptymodes.ltchars.t_lnextc)
			*p++ = ptymodes.ltchars.t_lnextc;
		  *p++ = c;
	    }
	    echotype = ECHO_NL;
	    if (p == buf || p[-1] != '\n') {
		  *p++ = ptymodes.tchars.t_eofc;
		  echotype = ECHO_EOF;
	    }
	    buflen = p - buf;
	    i = write(ptyfd, buf, buflen);
	    if (i < 0) {
		  if (errno != EWOULDBLOCK)
			fatal("pty write");
	    } else if (i < buflen) {
		  for (p = buf ; p < buf + buflen ; p++) {
			if (*p == ptymodes.ltchars.t_lnextc)
			      p++;
			ip->nwrt++;
		  }
	    } else {
		  inputq = ip->next;
		  free((char *)ip);
	    }
	    if (ptymodes.sgtty.sg_flags & ECHO) {
		  echoflag = echotype;
	    }
      }
#endif
}



/*
 * Locate the end of the echoed characters.
 */

int
stripecho(type, p, n)
      char *p;
      {
      register char *q;

      q = p;
      if (type == ECHO_NL) {
	    do {
		  if (--n < 0)
			return 0;
	    } while (*q++ != '\n');
      } else if (type == ECHO_EOF) {
	    do {
		  if (--n <= 0)
			return 0;
	    } while (*q++ != '\b' || *q != '\b');
	    q++;
      } else {
	    if (n < 2 || p[0] != '^' || p[1] != (sigchar ^ 0100))
		  return 0;
	    q += 2;
      }
      return q - p;
}



/*
 * Clear pty input queue.  The input queue is the one we write to.
 */

void
flushptyin() {
      struct indata *ip;

      while (inputq != NULL) {
	    ip = inputq;
	    inputq = ip->next;
	    free((char *)ip);
      }
      /* ioctl(ptyfd, TIOCFLUSH, (char *)&mode); */
}


/*
 * Signal handler invoked when a child dies or is stopped.
 */

void
onchild() {
      int pid;
      int status;
      int e = errno;

      if ((pid = wait3(&status, WNOHANG|WUNTRACED, (struct rusage *)0)) == childpid) {
	    childstatus = status;
	    gotsigchild = 1;
	    if (canjump)
		  longjmp(sigjmp, 1);
      }
      errno = e;
}


/*
 * Signal handler called when the terminal changes size.
 */

void
onwinch() {
      gotsigwinch = 1;
      if (canjump)
	    longjmp(sigjmp, 1);
}


/*
 * Called to process data received from the real tty.
 */

void
indata(p, n)
      char *p;
      {
      if (noedit) {
	    senddata(p, n);
#ifdef REMOTE
	    if (ptymodes.sgtty.sg_flags & ECHO) {
		  outchars(p, n);
		  writereal();
	    }
#endif
      } else {
	    while (--n >= 0)
		  inchar(*p++);
	    refresh();
	    if (needclearin) {
		  clearinput();
		  needclearin = 0;
	    }
	    if (outrsize)
		  writereal();
      }
}


clearinput() {
      edflush();
      flushptyin();
}


/*
 * Send a signal to the user process.
 */

void
sendsig(signo) {
#ifdef REMOTE
      int pgrp;
      int zero = 0;

      if (ioctl(ptyfd, TIOCFLUSH, (char *)&zero) < 0)
	    fatal("TIOCFLUSH");
      needclearin++;
      if (ioctl(ptyfd, TIOCGPGRP, (char *)&pgrp) < 0)
	    fatal("TIOCGPRGP");
      killpg(pgrp, signo);
#else
      char c;

      switch (signo) {
	case SIGINT:	c = ttymode.intr;  break;
	case SIGQUIT:	c = ttymode.quit;  break;
	case SIGTSTP:	c = ttymode.susp;  break;
	default:	fatal("sendsig arg");
      }      
      if ((ptymodes.lmode & LNOFLSH) == 0 && signo != SIGTSTP)
	    flushoutput();
      echoflag = ECHO_SIG;
      sigchar = c;
      if (write(ptyfd, &sigchar, 1) != 1)
	    fatal("sendsig write");
#endif
}


void
flushoutput() {
      int two = 2;

      outrp = outrnext = outrbuf;
      outrsize = 0;
      ioctl(2, TIOCFLUSH, (char *)&two);
}


puttty(c)
      char c;
      {
      write(2, &c, 1);
}


puttstr(s)
      char *s;
      {
      write(2, s, strlen(s));
}


/*
 * Called when the tty modes of the pty are changed by the user.
 */

#define SGCOPY	(ALLDELAY|RAW)	/* bits to copy from pty to real tty */

void
newttymodes() {
      char startc, stopc;
      int changereal;
      int flags;

      if (getttymodes(ptyfd, &ptymodes) < 0)
	    fatal("get pty modes");
      noedit = 0;
#ifdef notdef
      if (ptymodes.sgtty.sg_flags & (RAW|CBREAK))
#else
      if ((ptymodes.sgtty.sg_flags & (RAW|CBREAK|ODDP)) != ODDP)
#endif
	    noedit = 1;
      changereal = 0;
      startc = ptymodes.tchars.t_startc;
      stopc = ptymodes.tchars.t_stopc;
#ifdef notdef
      if (ptymodes.sgtty.sg_flags & RAW)
	    startc = stopc = -1;
#endif
      if (realtty.tchars.t_startc != startc
       || realtty.tchars.t_stopc != stopc) {
	    realtty.tchars.t_startc = startc;
	    realtty.tchars.t_stopc = stopc;
	    changereal = 1;
      }
      flags = ptymodes.sgtty.sg_flags & SGCOPY;
      if ((flags & TBDELAY) == XTABS)
	    flags &=~ TBDELAY;
      if ((realtty.sgtty.sg_flags & SGCOPY) !=  flags) {
	    realtty.sgtty.sg_flags &=~ SGCOPY;
	    realtty.sgtty.sg_flags |= flags;
	    changereal = 1;
      }
      if ((realtty.lmode & LDECCTQ) != (ptymodes.lmode & DECCTQ)) {
	    realtty.lmode &=~ LDECCTQ;
	    if (ptymodes.lmode & DECCTQ)
		  realtty.lmode |= LDECCTQ;
	    changereal = 1;
      }
      if (changereal) {
	    if (setttymodes(2, &realtty) < 0)
		  fatal("change real");
      }
      if (ttymode.intr != ptymodes.tchars.t_intrc
       || ttymode.quit != ptymodes.tchars.t_quitc
       || ttymode.erase!= ptymodes.sgtty.sg_erase
       || ttymode.kill != ptymodes.sgtty.sg_kill
       || ttymode.eof  != ptymodes.tchars.t_eofc
       || ttymode.susp != ptymodes.ltchars.t_suspc
       || ttymode.echo != (ptymodes.sgtty.sg_flags & ECHO)) {
	    fillttymode();
	    newttychars();
      }
}


void
fillttymode() {
      ttymode.intr = ptymodes.tchars.t_intrc;
      ttymode.quit = ptymodes.tchars.t_quitc;
      ttymode.erase= ptymodes.sgtty.sg_erase;
      ttymode.kill = ptymodes.sgtty.sg_kill;
      ttymode.eof  = ptymodes.tchars.t_eofc;
      ttymode.susp = ptymodes.ltchars.t_suspc;
      ttymode.echo = ptymodes.sgtty.sg_flags & ECHO;
}


/*
 * A unexpected error occurred.
 */

void
fatal(msg)
      char *msg;
      {
      int e = errno;

      cleanup();
      errno = e;
      perror(msg);
      fflush(stderr);
      abort();
}


/*
 * Called if initialization fails.
 */

void
badinit(msg)
      char *msg;
      {
      cleanup();
      fputs(msg, stderr);
      putc('\n', stderr);
      exit(2);
}


/*
 * Exit the program, resetting the terminal modes first.  Status is the
 * exit status.
 */

void
done(status) {
      cleanup();
      exit(status);
}


void
cleanup() {
      if (gottty) {
	    setttymodes(2, &origtty);
	    realfcntl &=~ FNDELAY;
	    fcntl(2, F_SETFL, realfcntl);
      }
}



int
getttymodes(fd, tty)
      int fd;
      struct tty *tty;
      {
      if (ioctl(fd, TIOCGETD, (char *)&tty->ldisc) < 0
       || ioctl(fd, TIOCGETP, (char *)&tty->sgtty) < 0
       || ioctl(fd, TIOCGETC, (char *)&tty->tchars) < 0
       || ioctl(fd, TIOCLGET, (char *)&tty->lmode) < 0
       || ioctl(fd, TIOCGLTC, (char *)&tty->ltchars) < 0)
	    return -1;
      return 0;
}



int
setttymodes(fd, tty)
      int fd;
      struct tty *tty;
      {
      if (ioctl(fd, TIOCSETD, (char *)&tty->ldisc) < 0
       || ioctl(fd, TIOCSETP, (char *)&tty->sgtty) < 0
       || ioctl(fd, TIOCSETC, (char *)&tty->tchars) < 0
       || ioctl(fd, TIOCLSET, (char *)&tty->lmode) < 0
       || ioctl(fd, TIOCSLTC, (char *)&tty->ltchars) < 0)
	    return -1;
      return 0;
}



/*
 * Copy the size of the tty from the real device to the pty.
 * Returns the number of columns.
 */

int
copyttysize() {
#ifdef TIOCGWINSZ
      struct winsize size;

      if (ioctl(2, TIOCGWINSZ, (char *)&size) >= 0)
	    ioctl(ptyfd, TIOCSWINSZ, (char *)&size);
      return size.ws_col;
#else
      return 0;
#endif
}
EOF
if test `wc -c < atty.c` -ne 22147
then	echo 'atty.c is the wrong size'
fi
echo extracting attyed.h
cat > attyed.h <<\EOF
/*
 * Copyright (C) 1989 by Kenneth Almquist.  All rights reserved.
 * This file is part of atty, which is distributed under the terms specified
 * by the Atty General Public License.  See the file named LICENSE.
 *
 * This file defines the interface between the editor and the rest of
 * atty.  First come things defined in atty and exported to the editor.
 */

#define MAXLLEN 254		/* maximum line length (excluding newline) */
#define COLUMNS 132		/* maximum screen width we expect */


struct mode {
      int intr;			/* interrupt character */
      int quit;			/* quit character */
      int erase;		/* erase character */
      int kill;			/* kill character */
      int eof;			/* eof character */
      int susp;			/* suspend character */
      int echo;			/* set if echo turned on */
};


extern struct mode ttymode;	/* various info about the tty */
extern short ospeed;		/* output speed (as returned by gtty) */
extern int promptset;		/* true if prompt specified by the user */
extern char prompt[COLUMNS];	/* the prompt, if any */
extern int column;		/* column for writing output to (see below) */
extern char outline[COLUMNS];	/* current output line */
extern char lastoutline[COLUMNS]; /* last complete line of output */
extern int lastoutlinelen;	/* lenght of lastoutline */


#ifdef __STDC__
void ttyoutc(int);		/* write a character to the tty */
void outreal(char *, int);	/* write a block of characters to the tty */
void senddata(char *, int);	/* send a line to the process */
void badinit(char *);		/* edinit failed; print message and exit */
#else
void ttyoutc();
void outreal();
void senddata();
void badinit();
#endif



/*
 * Now routines provide by the editor.
 */

#ifdef __STDC__
void edinit(char *);		/* initialize the editor */
void newttychars(void);		/* called when ttymode changes */
void newscrnwidth(int);		/* called when the screen width changes */
void inchar(int);		/* called to input a character */
void edflush(void);		/* discard any input */
void refresh(void);		/* turn display on and update it */
void dispoff(int);		/* turn display off */
void freezedisp(int);		/* freeze the editor display */
void insertchars(char *, int);	/* insert characters into input buffer */
#else
void edinit();
void newttychars();
void newscrnwidth();
void inchar();
void edflush();
void refresh();
void dispoff();
void freezedisp();
void insertchars();
#endif
EOF
if test `wc -c < attyed.h` -ne 2378
then	echo 'attyed.h is the wrong size'
fi
echo extracting bind.h
cat > bind.h <<\EOF
/*
 * Copyright (C) 1989 by Kenneth Almquist.  All rights reserved.
 * This file is part of atty, which is distributed under the terms specified
 * by the Atty General Public License.  See the file named LICENSE.
 */

#define BINDMAGIC 300		/* magic number at start of .bindc file */

#define C_FUNC 0		/* start of functions */
#define C_PFXTBL 1		/* prefix table */
#define C_INSERT 2		/* insert string */
#define C_UNDEF 3		/* not defined */

#define NSYNTAX 3		/* number of syntax classes */
EOF
if test `wc -c < bind.h` -ne 495
then	echo 'bind.h is the wrong size'
fi
echo extracting bindc.5
cat > bindc.5 <<\EOF
.TH BINDC 5
.SH NAME
bindc \- compiled bindings file
.SH COPYRIGHT
.if n Copyright (C) 1989 by Kenneth Almquist.
.if t Copyright \(co 1989 by Kenneth Almquist.  
.SH DESCRIPTION
The manual page describes the format of the compiled binding files
produced by
.IR kbind (1)
and used by
.IR atty (1).
The format assumes eight bit characters.  Short integers are stored in
two bytes, with the low order byte first.
.PP
The overall format of the file is:

.in +1i
.nf
Magic number (300) (2 bytes)
Version number of the bindc format (1 byte)
Number of keymaps (1 bytes)
Size of string area (2 bytes)
Size of abbreviations (2 bytes)
Keymap
...
Word syntax bitmap (16 bytes)
File name syntax bitmap (16 bytes)
Abbrev syntax bitmap (16 bytes)
String area
Abbreviations area
.fi
.in -1i
.PP
A keymap consists of two 128 byte arrays, which are indexed by the
input character.  The first array specifies the type of operation
to be performed when that key is typed.  C_FUNC specifies that a
function is to be performed.  The number of the function is given
by the second array.  C_PFXTBL specifies that the next key is to
be interpreted using the keymap whose number is given by the
second array.  (This is used to implement multicharacter commands.)  \
C_INSERT causes a string in the string area to be inserted.  The
number of the string appears in the second array.  C_UNDEF indicates
that this character is undefined.
.PP
The string table consists of a series of strings laid end to end.
Each string is preceded by a single byte giving the length of the
string.  The length byte is used instead of a nul terminator to allow
nul characters to be included in strings.  The abbreviations area
contains pairs of strings laid end to end.  Each string is preceded by
a byte giving the length of the string.  The first string in each pair
is the name of an abbreviation; the second is the string that the
abbreviation expands to.
.SH AUTHORS
Kenneth Almquist
.SH "SEE ALSO"
.IR kbind (1),
.IR atty (1).
EOF
if test `wc -c < bindc.5` -ne 1987
then	echo 'bindc.5 is the wrong size'
fi
echo extracting ed.h
cat > ed.h <<\EOF
/*
 * Stuff shared between ed.c and update.c.
 *
 * Copyright (C) 1989 by Kenneth Almquist.  All rights reserved.
 * This file is part of atty, which is distributed under the terms specified
 * by the Atty General Public License.  See the file named LICENSE.
 */

extern char curline[MAXLLEN+1];	/* line being edited */
extern char *point;		/* cursor location within line */
extern char *endcurline;	/* end of current line */
extern int needbeep;		/* set to request beep */
extern char editprompt;		/* prompt for recursive edit */


#ifdef __STDC__
void gettermcap(void);
void movetoend(void);
#else
void gettermcap();
void movetoend();
#endif
EOF
if test `wc -c < ed.h` -ne 644
then	echo 'ed.h is the wrong size'
fi
echo Archive 1 unpacked
exit
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.