/*
                          COPYRIGHT 1988
              Evans & Sutherland Computer Corporation
                        Salt Lake City, Utah
                        All Rights Reserved.

     THE INFORMATION  IN  THIS  SOFTWARE  IS  SUBJECT  TO  CHANGE
     WITHOUT  NOTICE  AND SHOULD NOT BE CONSTRUED AS A COMMITMENT
     BY  EVANS  &  SUTHERLAND.   EVANS  &  SUTHERLAND   MAKES  NO
     REPRESENTATIONS  ABOUT  THE SUITABILITY OF THIS SOFTWARE FOR
     ANY PURPOSE.  IT IS SUPPLIED  "AS  IS"  WITHOUT  EXPRESS  OR
     IMPLIED WARRANTY.

     IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING  DERIVATIVE
     COPYRIGHT  RIGHTS,  APPROPRIATE LEGENDS MAY BE PLACED ON THE
     DERIVATIVE WORK IN ADDITION TO THAT SET FORTH ABOVE.

     Permission  to  use,  copy,  modify,  and  distribute   this
     software  and  its documentation for any purpose and without
     fee is hereby granted, provided  that  the  above  copyright
     notice  appear  in  all  copies  and that both the copyright
     notice and this permission notice appear in supporting docu-
     mentation,  and  that  the name of Evans & Sutherland not be
     used in advertising or publicity pertaining to  distribution
     of the software without specific, written prior permission.

Written by:

                        Robert C. Pendleton

Evans & Sutherland, Interactive Systems Division, Salt Lake City, Utah.


$Header: ile.c,v 1.2 88/07/21 14:37:04 tytso Locked $
*/

/*
ile is compiled using:

cc ile.c -o ile -ltermcap
*/

#include <stdio.h>
#include <fcntl.h>
#include <sgtty.h>
#include <signal.h>
#include <string.h>
#include <pwd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/dir.h>
#include <sys/wait.h>

/*------------------------------------------------------------------*/

#define FALSE 0
#define TRUE  1

#define READ  0
#define WRITE 1
#define ERROR 2

#define BUFFER_SIZE 256

#define HISTORY_SIZE 101

#define USER_NAME_SIZE 8

#define EOL (-2)

extern int errno;
int	child_pid, parent_pid;
/*------------------------------------------------------------------*/

/* special characters used by ile */

#define del '\177'

#define CA '\1'
#define CB '\2'
#define CC '\3'
#define CD '\4'
#define CE '\5'
#define CF '\6'
#define bel '\7'
#define bs '\10'
#define CI '\11'
#define nl '\12'
#define CK '\13'
#define CL '\14'
#define cr '\15'
#define CN '\16'
#define CO '\17'
#define CP '\20'
#define CQ '\21'
#define CR '\22'
#define CS '\23'
#define CT '\24'
#define CU '\25'
#define CV '\26'
#define CW '\27'
#define CX '\30'
#define CY '\31'
#define CZ '\32'

#define esc '\33'

/*------------------------------------------------------------------*/
/* areas and varaibles used to get termcap information */

char *getenv();

char *tgetnum();
char *tgetflag();
char *tgetstr();

char termcap_entry[1024];	/* termcap entry for the users terminal */
char term_seqs[1024];		/* area to store control sequences in */
char *where = term_seqs;

char *cle;			/* move cursor left one space */

char *cce;			/* clear to end of line */

char *cbl;			/* audible bell */

char *cnl;			/* new line character */

char *ccr;			/* carriage return */

/*------------------------------------------------------------------*/

/* file descriptors for tty and pty */

int master_pty;
int slave_tty;

/*------------------------------------------------------------------*/

/* the names of the tty and pty opened by getpty */

char ttydev[] = "/dev/ttyxx";
char ptydev[] = "/dev/ptyxx";

/*------------------------------------------------------------------*/

/* tty status flags */
/*
  The original tty status flags are stored so that they can be
  restored when ile exits.
*/

struct sgttyb tty_params;
int tty_mode;
int ldisc;

/*------------------------------------------------------------------*/
/*
The value of $HOME
*/

char *homedir = NULL;

/*------------------------------------------------------------------*/
/*
  getpty opens a pty, storing file descriptors in pty and tty.
  It trys pairs in order until it finds a pair that is not in use.
*/

getpty(pty, tty)
    int *pty;
    int *tty;

{
    int devindex;
    int letter;

    static char ptychar1[] = "pqrstuvwxyz";
    static char ptychar2[] = "0123456789abcdef";

    letter = 0;
    while (letter < 11)
    {
	ttydev[strlen(ttydev) - 2] = ptychar1[letter];
	ptydev[strlen(ptydev) - 2] = ptychar1[letter];
	letter++;

	devindex = 0;
	while (devindex < 16)
	{
	    ttydev[strlen(ttydev) - 1] = ptychar2[devindex];
	    ptydev[strlen(ptydev) - 1] = ptychar2[devindex];
	    devindex++;

	    if ((*pty = open(ptydev, O_RDWR)) >= 0)
	    {
		if ((*tty = open(ttydev, O_RDWR)) >= 0)
		{
		    return;
		}
		else
		{
		    (void) close(*pty);
		}
	    }
	}
    }

    fprintf(stderr, "ile: unable to allocate pty/tty pair\n");
    exit(1);
}

/*------------------------------------------------------------------*/
/*
Termcap entries may have a sequences of digits optionally followed
by a '*' in front of the actual sequence. This routine increments
the pointer past this information.
*/
void
strip(ptr)
    char **ptr;
{
    while (('0' <= **ptr) && (**ptr <= '9'))
    {
	(*ptr)++;
    }

    if (**ptr == '*')
    {
	(*ptr)++;
    }
}
/*------------------------------------------------------------------*/
/*
Set up everything needed to use the control sequences from the
termcap entry for the terminal.
*/
void
get_termcap()
{
    char *terminal_type;	/* type of terminal */

    /* get the terminal name */

    terminal_type = getenv("TERM");

    /* get termcap entry */

    if (tgetent(termcap_entry, terminal_type) < 1)
    {
	fprintf(stderr, "ile: can't find %s\n", terminal_type);
	exit(1);
    }

    /* get the control sequences ile needs */

    if (((cle = tgetstr("le", &where)) == NULL) ||
	((cce = tgetstr("ce", &where)) == NULL) ||
	((cbl = tgetstr("bl", &where)) == NULL) ||
	((cnl = tgetstr("nl", &where)) == NULL) ||
	((ccr = tgetstr("cr", &where)) == NULL))
    {
	fprintf(stderr, "ile: can't run on %s\n", terminal_type);
	exit(1);
    }

    /* strip timing info from strings */

    strip(&cle);
    strip(&cce);
    strip(&cbl);
    strip(&cnl);
    strip(&ccr);
}
/*------------------------------------------------------------------*/
/*
clean up and leave.

This function is bound to the SIGCHLD signal so that when the
child process exits, so does ile. It is also called when an exception
is detected by select() in ile().
*/
void
cleanup()
{
    /* restore terminal status */

    (void) ioctl(READ, TIOCSETP, &tty_params);
    (void) ioctl(READ, TIOCLSET, &tty_mode);

    /* clean up the tty/pty pair */

    (void) close(master_pty);
    (void) close(slave_tty);

    /* make things look nice */

    fputs(cnl, stdout);

    exit(0);
}
/*
 * sig_chld_handler() is now bound to the SIGCHLD signal so that
 * 	the application process can stop it self and not have ile
 * 	die on it.  TYT, 7/14/88
 */
sig_chld_handler()
{
	register int pid;
	union wait w;
	int jobflags;

loop:
	pid = wait3(&w, WNOHANG|WUNTRACED, NULL);
	if (pid <= 0) {
		if (errno == EINTR) {
			errno = 0;
			goto loop;
		}
		return;
	}
	if (pid != child_pid)
		goto loop;
	if (WIFSTOPPED(w)) {
		(void) ioctl(READ, TIOCSETP, &tty_params);
		(void) ioctl(READ, TIOCLSET, &tty_mode);
		kill(parent_pid,w.w_stopsig);
	}
	else if (WIFEXITED(w))
		cleanup();
}
/*
 * sig_cont_handler() is bound to the SIGCONT signal so that we can
 * recover from being stopped.
 */
sig_cont_handler()
{
	ttysetup();		/* Set up the tty for line editing */
	kill(child_pid, SIGCONT); /* Propagate the signal */
}

/*------------------------------------------------------------------*/
/*
The editing routines are called through the edit variable. This allows
the quote and escape commands to be implemented as a straight forward
state machine instead of requiring state flags and complex switch
statements.
*/
/*------------------------------------------------------------------*/

/* line edit buffer */

static char line[BUFFER_SIZE];

static int point;		/* insertion point */
static int length;		/* total chars in buffer */

/* procedure to edit next character */

void (*edit) ();

/* history buffer */

struct
{
    int length;
    char *line;
} hist[HISTORY_SIZE];

int head;			/* insertion point */
int here;			/* current displayed line */

/*------------------------------------------------------------------*/
/*
The delimiter vector is used by the forward, backward, and delete
word operations to decide that a character is a delimiter.
*/
/*------------------------------------------------------------------*/

#define CHAR_SET_SIZE 256

char delimit[CHAR_SET_SIZE];

/*------------------------------------------------------------------*/
/*
The action_table is used to bind sequences of keys to operations or strings.
*/
/*------------------------------------------------------------------*/

typedef enum
{
    is_action, is_string
} action_type;

struct
{
    action_type flag;
    union
    {
	void (*action) ();
	char *string;
    } aors
} action_table[4][CHAR_SET_SIZE];

/*------------------------------------------------------------------*/

void echo();
void echoline();
void cleartoend();
void clearline();
void backspace();
void quote_edit();
void edit_0();
void edit_1();
void edit_2();
void edit_3();
void bell();

/*------------------------------------------------------------------*/
/*
The following routines are action routines that are executed by the
editor to carry out commands. Each routine has a single character
argument. Each routine is invoked with the character that caused it
to be invoked as its argument.

The argument isn't always useful, but it is included to provide a
consistent interface for the routines.
*/
/*------------------------------------------------------------------*/
/*
Given a specific directory and the starting string of a file name,
find the longest partial file name that starts with the substring.
*/
void
complete_file_name(dir, name)
    char *dir;
    char *name;
{
    DIR *dirp;
    struct direct *dp;

    int len;
    int maxlen;
    int oldlen;

    char oldname[MAXNAMLEN + 1];
    char newname[MAXNAMLEN + 1];

    if ((dir != NULL) &&
	(name != NULL) &&
	((oldlen = strlen(name)) != 0) &&
	((dirp = opendir(dir)) != NULL))
    {
	maxlen = oldlen;
	strcpy(oldname, name);
	strcpy(newname, name);

	/* find the longest name starting with name */

	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))
	{
	    if (dp->d_name != NULL)
	    {
		len = strlen(dp->d_name);
		if ((maxlen < len) &&
		    (strncmp(oldname, dp->d_name, oldlen) == 0))
		{
		    maxlen = len;
		    strcpy(newname, dp->d_name);
		}
	    }
	}

	rewinddir(dirp);

	/* find the longest common sub string */

	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))
	{
	    if (dp->d_name != NULL)
	    {
		len = strlen(dp->d_name);
		if ((len <= maxlen) &&
		    (strncmp(oldname, dp->d_name, oldlen) == 0))
		{
		    for (;
			(oldlen < len) &&
			(strncmp(newname, dp->d_name, len) != 0);
			len--);

		    maxlen = len;
		    newname[maxlen] = '\0';
		}
	    }
	}

	if (strlen(name) != strlen(newname))
	{
	    /* return the extended name */

	    strcpy(name, newname);
	}
	else
	{
	    /* no difference so beep */

	    bell('\0');
	}

	closedir(dirp);
    }
}
/*------------------------------------------------------------------*/
/*
List all the files in a given directory that start with the string
passed as name.
*/
void
list_file_names(dir, name)
    char *dir;
    char *name;
{
    DIR *dirp;
    struct direct *dp;

    int namelen;

    int count;

    /* make sure everything is ok */

    if ((dir != NULL) &&
	(name != NULL) &&
	((dirp = opendir(dir)) != NULL))
    {
	namelen = strlen(name);

	/* print all the names starting with name */

	count = 0;
	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))
	{
	    if ((namelen <= dp->d_namlen) &&
		(strncmp(name, dp->d_name, namelen) == 0) &&
		(dp->d_name != NULL))
	    {
		if (80 < (count + dp->d_namlen + 1))
		{

		    fputs(ccr, stdout);
		    fputs(cnl, stdout);
		    count = 0;
		}
		count = count + dp->d_namlen + 1;

		fputs(dp->d_name, stdout);
		fputc(' ', stdout);

		for (; (count % 16) != 0; count++)
		{
		    fputc(' ', stdout);
		}

	    }
	}
    }
}
/*------------------------------------------------------------------*/
/*
Assuming that there is a file name under the cursor return a path
and a file name. If there is no path name return "."
*/
int
get_dir_and_name(dir, name, username, userend, start, middle, tail)
    char *dir;
    char *name;
    char *username;
    int *userend;
    int *start;
    int *middle;
    int *tail;
{
    int dirlen;

    int newstart;

    int punlen;
    char pun[USER_NAME_SIZE + 1];
    struct passwd *userpwd;

    int i;

    /* set the default path and file name */

    dir[0] = '\0';
    name[0] = '\0';
    username[0] = '\0';

    /* search for the start of the file name */

    for ((*start) = point;
	((0 < (*start)) &&
	    (line[(*start) - 1] != ' '));
	(*start)--);

    /* search for the end of the file name */

    for ((*tail) = point - 1;
	(((*tail) < (length - 1)) &&
	    (line[(*tail) + 1] != ' '));
	(*tail)++);

    /* search for the middle of the file name */

    for ((*middle) = (*tail) + 1;
	((0 < (*middle)) &&
	    (line[(*middle) - 1] != '/') &&
	    (line[(*middle) - 1] != ' '));
	(*middle)--);

    /* copy path from line to dir */

    /* what base path */

    newstart = (*start);

    if ((line[newstart] == '~') &&
	((newstart + 1) < length) &&
	(line[newstart + 1] == '/'))
    {
	/* "~/" means use the value of $HOME */

	newstart++;
	strcpy(dir, homedir);
    }
    else if (line[newstart] == '~')
    {
	/* "~username" means use the users login directory */

	/* search for the end of the user name */

	for ((*userend) = newstart,
	    punlen = 0;
	    (((*userend) < (length - 1)) &&
		(line[(*userend) + 1] != ' ') &&
		(line[(*userend) + 1] != '/'));
	    (*userend)++,
	    punlen++);

	/* make middle point to middle */

	if ((*start) == (*middle))
	{
	    (*middle) = (*start) + punlen + 1;
	}

	/* extract partial user name from line */

	strncpy(pun, &line[newstart + 1], punlen);
	pun[punlen] = '\0';

	/* search passwd file for partial match */

	for (userpwd = getpwent();
	    userpwd != NULL;
	    userpwd = getpwent())
	{
	    if ((punlen <= strlen(userpwd->pw_name)) &&
		(strncmp(pun, userpwd->pw_name, punlen) == 0))
	    {

		/* we have a partial match, record it */

		if (strlen(dir) == 0)
		{
		    newstart = (*userend) + 1;
		    strcpy(dir, userpwd->pw_dir);
		    strcpy(username, userpwd->pw_name);
		}
		else
		{
		    /* second partial match, forget the first one. */

		    newstart = (*start);
		    dir[0] = '\0';
		    username[0] = '\0';
		    return (FALSE);
		}

	    }
	}
	setpwent();
    }
    else if ((line[newstart] == '.') &&
	    ((newstart + 1) < length) &&
	(line[newstart + 1] == '/'))
    {
	/* if it's "./" can't deal with it so leave it alone  */

	return (FALSE);
    }
    else if ((line[newstart] == '.') &&
	    ((newstart + 1) < length) &&
	    (line[newstart + 1] == '.') &&
	    ((newstart + 2) < length) &&
	(line[newstart + 2] == '/'))
    {
	/* if it's "../" can't deal with it so leave it alone  */

	return (FALSE);
    }
    else if (line[newstart] != '/')
    {
	/* ile can only handle an absolute path so leave it alone */

	return (FALSE);
    }

    /* add on the rest of the path */

    dirlen = strlen(dir);
    for (i = 0; i < ((*middle) - newstart); i++)
    {
	dir[dirlen + i] = line[newstart + i];
    }
    dir[dirlen + i] = '\0';

    /* copy file name from line to name */

    for (i = 0; i < ((*tail) - (*middle) + 1); i++)
    {
	name[i] = line[(*middle) + i];
    }
    name[i] = '\0';

    return (TRUE);
}
/*------------------------------------------------------------------*/
/*
Perform file name completion. Put the full path and file name in the
line.
*/
void
complete_file_full(ch)
    char ch;
{
    char dir[10 * (MAXNAMLEN + 1)];
    char name[MAXNAMLEN + 1];
    char username[USER_NAME_SIZE + 1];

    char newline[BUFFER_SIZE];
    int newlength;
    int newpoint;

    int userend;
    int start;
    int middle;
    int tail;

    int i;

    /* get the path and file name in the line */

    if (get_dir_and_name(dir, name, username, &userend, &start, &middle, &tail))
    {

	/* complete the file name if possible */

	complete_file_name(dir, name);

	/* create a new line */

	/* start with the line prefix */

	strncpy(newline, line, start);
	newline[start] = '\0';

	/* add in the new path */

	strcat(newline, dir);

	/* stick in the new file name */

	strcat(newline, name);
	newpoint = strlen(newline);

	/* finish with the line postfix */

	strncat(newline, &line[tail + 1], (length - tail - 1));
	newlength = strlen(newline);

	/* display the new line */

	clearline('\0');

	point = newpoint;
	length = newlength;
	strncpy(line, newline, newlength);

	echoline(line, length);

	for (i = point; i < length; i++)
	{
	    backspace(line[i]);
	}
    }
    else
    {
	bell('\0');
    }
}
/*------------------------------------------------------------------*/
/*
Perform file name completion in much the same style as csh.
*/
void
complete_file(ch)
    char ch;
{
    void insert();

    char dir[10 * (MAXNAMLEN + 1)];
    char name[MAXNAMLEN + 1];
    char username[USER_NAME_SIZE + 1];

    int userend;
    int start;
    int middle;
    int tail;

    char newline[BUFFER_SIZE];
    int newlength;
    int newpoint;

    int userlen;
    int len;

    int i;

    /* get the path and file name in the line */

    if (get_dir_and_name(dir, name, username, &userend, &start, &middle, &tail))
    {
	/* how long is the user name */

	userlen = strlen(username);

	/* complete the file name if possible */

	complete_file_name(dir, name);
	/* create a new line */

	/* start with the line prefix */

	strncpy(newline, line, start);
	newline[start] = '\0';

	/* add in the new username */

	if (userlen != 0)
	{
	    /* put in new user name */

	    strcat(newline, "~");
	    strcat(newline, username);
	    len = strlen(newline);

	    /* put in the existing path */

	    strncat(newline, &line[userend + 1], middle - userend - 1);
	    newline[len + (middle - userend - 1)] = '\0';
	}
	else
	{
	    /* put in the existing path */

	    len = strlen(newline);
	    strncat(newline, &line[start], middle - start);
	    newline[len + (middle - start)] = '\0';
	}

	/* stick in the new file name */

	strcat(newline, name);
	newpoint = strlen(newline);

	/* finish with the line postfix */

	strncat(newline, &line[tail + 1], (length - tail - 1));
	newlength = strlen(newline);

	/* display the new line */

	clearline('\0');

	point = newpoint;
	length = newlength;
	strncpy(line, newline, newlength);

	echoline(line, length);

	for (i = point; i < length; i++)
	{
	    backspace(line[i]);
	}
    }
    else
    {
	bell('\0');
    }
}
/*------------------------------------------------------------------*/
/*
List the names of files that start with the directory path and
file name under the cursor.
*/
void
show_files(ch)
    char ch;
{
    static char divider[] = "----------";

    void retype_line();

    char dir[10 * (MAXNAMLEN + 1)];
    char name[MAXNAMLEN + 1];
    char username[USER_NAME_SIZE + 1];

    int userend;
    int start;
    int middle;
    int tail;

    if (get_dir_and_name(dir, name, username, &userend, &start, &middle, &tail))
    {

	fputs(ccr, stdout);
	fputs(cnl, stdout);

	fputs(divider, stdout);

	fputs(ccr, stdout);
	fputs(cnl, stdout);

	list_file_names(dir, name);

	fputs(ccr, stdout);
	fputs(cnl, stdout);

	fputs(divider, stdout);

	fputs(ccr, stdout);
	fputs(cnl, stdout);

	retype_line('\0');
    }
    else
    {
	bell('\0');
    }
}
/*------------------------------------------------------------------*/
/*
Ring the bell on the terminal.
*/
void
bell(ch)
    char ch;
{
    fputs(cbl, stdout);
}
/*------------------------------------------------------------------*/
/*
Pass characters to the slave. Don't mess with them at all.
*/
void
pass(ch)
    char ch;
{
    int cc;

    cc = write(master_pty, &ch, 1);
}
/*------------------------------------------------------------------*/
/*
Insert a character at point in the line buffer. While we are at it
update the display to show the insertion.
*/
void
insert(ch)
    char ch;
{
    int i;

    if (length < (BUFFER_SIZE - 2))
    {

	/* display the character */

	echo(ch);

	/* redisplay the rest of the line */

	echoline(&line[point], (length - point));

	/* move the characters in the line buffer */
	/* and put the cursor back at point */

	for (i = length; i > point; i--)
	{
	    line[i] = line[i - 1];
	    backspace(line[i]);
	}

	/* add the character to the line buffer */
	/* and increment point and length */

	line[point] = ch;
	length++;
	point++;
    }
    else
    {
	bell('\0');
    }
}
/*------------------------------------------------------------------*/
/*
Transpose the letter under the cursor and the letter immediately to
the left of the cursor.
*/
void
transpose_chars(ch)
    char ch;
{
    char tch;

    if ((0 < point) && (point < length))
    {
	/* first, update the display */

	backspace(line[point]);

	echo(line[point]);
	echo(line[point - 1]);

	/* now swap the chars in the line buffer */

	tch = line[point];
	line[point] = line[point - 1];
	line[point - 1] = tch;

	/* point moved forward one char */

	point++;
    }
}
/*------------------------------------------------------------------*/
/*
Delete a character at point in the line buffer. While we are at it
update the display to reflect the deletion.
*/
void
delete_char_under(ch)
    char ch;
{
    int i;

    if (point < length)
    {

	/* clear to the end of the line */

	cleartoend();

	/* retype the rest of the line */

	echoline(&line[point + 1], (length - point - 1));

	/* build the new line */

	for (i = point + 1; i < length; i++)
	{
	    line[i - 1] = line[i];
	    backspace(line[i]);
	}

	length--;

	if (point > length)
	{
	    point = length;
	}
    }

}
/*------------------------------------------------------------------*/
/*
Delete the character to the left of point in the line buffer. While we
are at it update the display to reflect the deletion.
*/
void
delete_char(ch)
    char ch;
{
    int i;

    if (point > 0)
    {
	/* move the cursor left one character */

	backspace(line[point - 1]);

	/* clear to the end of the line */

	cleartoend();

	/* retype the rest of the line */

	echoline(&line[point], (length - point));

	/* build the new line */

	for (i = point; i < length; i++)
	{
	    line[i - 1] = line[i];
	    backspace(line[i]);
	}

	length--;
	point--;
    }

}
/*------------------------------------------------------------------*/
/*
Bind the edit vector to quote_edit so that the next character
will be placed in the line buffer.
*/
void
quote(ch)
    char ch;
{
    edit = quote_edit;
}
/*------------------------------------------------------------------*/
/*
The next character will select an action from action_table[1]
*/
void
escape_1(ch)
    char ch;
{
    edit = edit_1;
}
/*------------------------------------------------------------------*/
/*
The next character will select an action from action_table[2]
*/
void
escape_2(ch)
    char ch;
{
    edit = edit_2;
}
/*------------------------------------------------------------------*/
/*
The next character will select an action from action_table[3]
*/
void
escape_3(ch)
    char ch;
{
    edit = edit_3;
}
/*------------------------------------------------------------------*/
/*
Delete the word to the left of the cursor.
*/
void
delete_word(ch)
    char ch;
{
    int i;
    int old;

    if (length > 0)
    {
	/* find the new deletion point */

	old = point;

	/* first skip over any delimiters */

	for (; (point > 0) && (delimit[line[point - 1]]); point--)
	{
	    backspace(line[point - 1]);
	}

	/* now delete until we find a delimiter */

	for (; (point > 0) && (!delimit[line[point - 1]]); point--)
	{
	    backspace(line[point - 1]);
	}

	/* clear to the end of the line */

	cleartoend();

	/* retype the rest of the line */

	echoline(&line[old], (length - old));

	/* construct the new line */

	for (i = 0; i < (length - old); i++)
	{
	    line[point + i] = line[old + i];
	    backspace(line[point + i]);
	}

	/* update the length */

	length = length - (old - point);
    }
}
/*------------------------------------------------------------------*/
/*
Go forward one word.
*/
void
forward_word(ch)
    char ch;
{
    if (length > 0)
    {
	/* first skip any delimiters */

	for (; (point < length) && (delimit[line[point]]); point++)
	{
	    echo(line[point]);
	}

	/* now skip until we find a delimiter */

	for (; (point < length) && (!delimit[line[point]]); point++)
	{
	    echo(line[point]);
	}
    }

}
/*------------------------------------------------------------------*/
/*
Lower case the word.
*/
void
lower_word(ch)
    char ch;
{
    if (length > 0)
    {
	/* first skip any delimiters */

	for (; (point < length) && (delimit[line[point]]); point++)
	{
	    echo(line[point]);
	}

	/* now skip until we find a delimiter */

	for (; (point < length) && (!delimit[line[point]]); point++)
	{
	    if ((line[point] >= 'A') && (line[point] <= 'Z'))
	    {
		line[point] = line[point] - 'A' + 'a';
		echo(line[point]);
	    }
	    else
	    {
		echo(line[point]);
	    }
	}
    }

}
/*------------------------------------------------------------------*/
/*
Upper case the word.
*/
void
upper_word(ch)
    char ch;
{
    if (length > 0)
    {
	/* first skip any delimiters */

	for (; (point < length) && (delimit[line[point]]); point++)
	{
	    echo(line[point]);
	}

	/* now skip until we find a delimiter */

	for (; (point < length) && (!delimit[line[point]]); point++)
	{
	    if ((line[point] >= 'a') && (line[point] <= 'z'))
	    {
		line[point] = line[point] - 'a' + 'A';
		echo(line[point]);
	    }
	    else
	    {
		echo(line[point]);
	    }
	}
    }

}
/*------------------------------------------------------------------*/
/*
Capitalize the word.
*/
void
capitalize_word(ch)
    char ch;
{
    if (length > 0)
    {
	/* first skip any delimiters */

	for (; (point < length) && (delimit[line[point]]); point++)
	{
	    echo(line[point]);
	}

	/* now skip until we find a delimiter */

	if ((point < length) && (!delimit[line[point]]))
	{
	    if ((line[point] >= 'a') && (line[point] <= 'z'))
	    {
		line[point] = line[point] - 'a' + 'A';
		echo(line[point]);
	    }
	    else
	    {
		echo(line[point]);
	    }
	}
	point++;

	for (; (point < length) && (!delimit[line[point]]); point++)
	{
	    if ((line[point] >= 'A') && (line[point] <= 'Z'))
	    {
		line[point] = line[point] - 'A' + 'a';
		echo(line[point]);
	    }
	    else
	    {
		echo(line[point]);
	    }
	}
    }

}
/*------------------------------------------------------------------*/
/*
Go backward one word.
*/
void
backward_word(ch)
    char ch;
{
    if (length > 0)
    {
	/* first backspace over any delimiters */

	for (; (point > 0) && (delimit[line[point - 1]]); point--)
	{
	    backspace(line[point - 1]);
	}

	/* now backspace until we find a delimiter */

	for (; (point > 0) && (!delimit[line[point - 1]]); point--)
	{
	    backspace(line[point - 1]);
	}
    }

}
/*------------------------------------------------------------------*/
/*
Move the cursor to the start of the line.
*/
void
start_of_line(ch)
    char ch;
{
    int i;

    if (length > 0)
    {
	for (i = 0; i < point; i++)
	{
	    backspace(line[i]);
	}
	point = 0;
    }
}
/*------------------------------------------------------------------*/
/*
Move the cursor one character to the left.
*/
void
backward_char(ch)
    char ch;
{
    if ((length > 0) && (point > 0))
    {
	backspace(line[point - 1]);
	point--;
    }
}
/*------------------------------------------------------------------*/
/*
Move the cursor to the right of the last character on the line.
*/
void
end_of_line(ch)
    char ch;
{
    if ((length > 0) && (point < length))
    {
	echoline(&line[point], (length - point));
	point = length;
    }
}
/*------------------------------------------------------------------*/
/*
Move the cursor one character to the right.
*/
void
forward_char(ch)
    char ch;
{
    if ((length > 0) && (point < length))
    {
	echo(line[point]);
	point++;
    }
}
/*------------------------------------------------------------------*/
/*
Add a line to the history buffer and pass it to the child process
as input.
*/
void
add_to_history(ch)
    char ch;
{
    /* Put the line in the history buffer. Make here point to the current
     * line. And increment head to point to the next history slot. */

    /* If the current line is identical to the current history line, don't
     * add it. */

    /* don't save blank lines */

    int prev;
    int cc;

    if ((head - 1) < 0)
    {
	prev = HISTORY_SIZE - 1;
    }
    else
    {
	prev = head - 1;
    }

    if ((length != 0) &&
	((length != hist[prev].length) ||
	    (strncmp(hist[prev].line, line, length) != 0)))
    {
	/* set the length of the entry */

	hist[head].length = length;

	/* make sure there is enough storage for the new line */

	if (hist[head].line == NULL)
	{
	    if ((hist[head].line = (char *) malloc(length)) == NULL)
	    {
		perror("ile");
	    }
	}
	else
	{
	    if ((hist[head].line =
		    (char *) realloc(hist[head].line, length))
		== NULL)
	    {
		perror("ile");
	    }
	}

	(void) strncpy(hist[head].line, line, length);

	head = (head + 1) % HISTORY_SIZE;

	if (hist[head].line != NULL)
	{
	    free(hist[head].line);
	    hist[head].length = 0;
	    hist[head].line = NULL;
	}
    }

    /* reset here */

    here = head;

    /* Echo a carriage return or a newline as a cr-nl sequence. Then send the
     * line to the child process. Finally, clear the buffer for later use. */

    fputs(ccr, stdout);
    fputs(cnl, stdout);

    line[length] = nl;
    length++;

    cc = write(master_pty, line, length);

    point = 0;
    length = 0;

}
/*------------------------------------------------------------------*/
/*
Erase the entire line.
*/
void
erase_line(ch)
    char ch;
{
    /* remove any text from the display */

    clearline(ch);

    /* nothing in the line buffer */

    point = 0;
    length = 0;

    /* reset here */

    here = head;

}
/*------------------------------------------------------------------*/
/*
Erase from the current cursor position to the end of the line.
*/
void
erase_to_end_of_line(ch)
    char ch;
{
    if ((length > 0) && (point < length))
    {
	cleartoend();
	length = point;
    }

}
/*------------------------------------------------------------------*/
/*
Retype the current contents of the edit buffer.
*/
void
retype_line(ch)
    char ch;
{
    int i;

    fputs(ccr, stdout);
    fputs(cnl, stdout);

    echoline(line, length);

    for (i = point; i < length; i++)
    {
	backspace(line[i]);
    }
}
/*------------------------------------------------------------------*/
/*
Go to the the next entry in the history buffer and display it.
If we are past the last history entry, then beep.
*/
void
forward_history(ch)
    char ch;
{
    if (here != head)
    {
	clearline(ch);

	here = (here + 1) % HISTORY_SIZE;
	length = hist[here].length;
	point = length;

	strncpy(line, hist[here].line, length);
	echoline(line, length);
    }
    else
    {
	bell('\0');
    }
}
/*------------------------------------------------------------------*/
/*
Go back one entry in the history buffer and display it. If we are
already at the last entry, then beep.
*/
void
backward_history(ch)
    char ch;
{
    int prev;

    prev = here - 1;

    if (prev < 0)
    {
	prev = HISTORY_SIZE - 1;
    }

    if (hist[prev].line != NULL)
    {
	clearline(ch);

	here = prev;
	length = hist[here].length;
	point = length;

	strncpy(line, hist[here].line, length);
	echoline(line, length);
    }
    else
    {
	bell('\0');
    }
}
/*------------------------------------------------------------------*/
/*
The following routines are utility routines used by the editing
routines.
*/
/*------------------------------------------------------------------*/
/*
Clear to the end of the current input line.
*/
void
cleartoend()
{
    /* send the clear character */

    fputs(cce, stdout);

    /* send somes nulls for padding */

    fputs("\0\0\0\0", stdout);
}
/*------------------------------------------------------------------*/
/*
Clear the input line. Backspace to the start of the line. Then clear
to the end of the line.
*/
void
clearline(ch)
    char ch;
{
    int i;

    for (i = 0; i < point; i++)
    {
	backspace(line[i]);
    }

    cleartoend();
}
/*------------------------------------------------------------------*/
/*
Echo a character. Not all characters are created equal. Control characters
are echoed in ^X form. So they take up two character positions instead of
the normal 1 character position.
*/
void
echo(ch)
    char ch;
{
    /* how should we echo the char? */

    if (ch < ' ')
    {
	fputc('^', stdout);
	fputc('@' + ch, stdout);
    }
    else
    {
	fputc(ch, stdout);
    }
}
/*------------------------------------------------------------------*/
/*
Echo a line. Print a whole line with control characters printed in
^X form.
*/
void
echoline(line, length)
    char *line;
    int length;
{
    int i;

    for (i = 0; i < length; i++)
    {
	echo(*line++);
    }

}
/*------------------------------------------------------------------*/
/*
Backspace over a character. Generate enough bs characters to backspace
over any character.
*/
void
backspace(ch)
    char ch;
{
    if (ch < ' ')
    {
	fputs(cle, stdout);
	fputs(cle, stdout);
    }
    else
    {
	fputs(cle, stdout);
    }
}
/*------------------------------------------------------------------*/
/*
Add any character to the line buffer.
*/
void
quote_edit(ch)
    char ch;
{
    insert(ch);

    edit = edit_0;
}
/*------------------------------------------------------------------*/
/*
Given a character and an action table number either execute the
action or pass the string to (*edit)(ch)
*/
void
dispatch(table, ch)
    int table;
    char ch;
{
    char *cptr;

    switch (action_table[table][ch].flag)
    {
    case is_action:

	(*(action_table[table][ch].aors.action)) (ch);

	break;

    case is_string:

	cptr = action_table[table][ch].aors.string;
	while ((*cptr) != '\0')
	{
	    (*edit) (*cptr);
	    cptr++;
	}

	break;
    }
}
/*------------------------------------------------------------------*/
/*
Select an action from action_table[3] and execute it.
*/
void
edit_3(ch)
    char ch;
{
    /* reset so that next input is handled by edit_0 unless over ridden by
     * the action. */

    edit = edit_0;
    dispatch(3, ch);
    fflush(stdout);
}
/*------------------------------------------------------------------*/
/*
Select an action from action_table[2] and execute it.
*/
void
edit_2(ch)
    char ch;
{
    /* reset so that next input is handled by edit_0 unless over ridden by
     * the action. */

    edit = edit_0;
    dispatch(2, ch);
    fflush(stdout);
}
/*------------------------------------------------------------------*/
/*
Select an action from action_table[1] and execute it.
*/
void
edit_1(ch)
    char ch;
{
    /* reset so that next input is handled by edit_0 unless over ridden by
     * the action. */

    edit = edit_0;
    dispatch(1, ch);
    fflush(stdout);
}
/*------------------------------------------------------------------*/
/*
Select an action from action_table[0] and execute it.
*/
void
edit_0(ch)
    char ch;
{
    dispatch(0, ch);
    fflush(stdout);
}
/*------------------------------------------------------------------*/

/*
 * ttysetup - sets up the modes on the (real) tty to be what we want.
 * 	assumes that tty_modes and tty_params are already initialized.
 */
ttysetup()
{
	struct sgttyb params;
	int mode;

	/* set raw mode */
	params = tty_params;
	params.sg_flags = EVENP | ODDP | RAW;
	(void) ioctl(READ, TIOCSETP, &params);

	/* set new mode on tty */
	mode = LNOFLSH | LDECCTQ | LLITOUT;
	(void) ioctl(READ, TIOCLSET, &mode);

}
/*
Input line editor.

Initialize the world. Then loop forever using select to wait for
characters to be available from either stdin or from master_pty.
When characters are available, pass them on after doing any needed
editing.
*/
void
ile()
{
    /* general purpose integer variable */

    int i;

    /* arguments for read and write calls */

    char buffer[BUFFER_SIZE];
    int cc;

    /* current slave_tty parameters */

    struct sgttyb slave_params;

    /* Arguments for select call */

    int nfds;
    int width;
    int readfds;

    /* what to do if the child dies */

    (void) signal(SIGCHLD, sig_chld_handler);
    (void) signal(SIGSEGV, cleanup);
    (void) signal(SIGBUS, cleanup);
    
    /* initialize tty status flags */

    /* get current params */

    (void) ioctl(READ, TIOCGETP, &tty_params);

    /* set slave params */
    /* the slave must not echo characters */

    {
	struct sgttyb params;

	params = tty_params;
	params.sg_flags &= ~ECHO;
	(void) ioctl(slave_tty, TIOCSETP, &params);
    }

    /* get current mode */

    (void) ioctl(READ, TIOCLGET, &tty_mode);

    /* set it on slave */

    (void) ioctl(slave_tty, TIOCLSET, &tty_mode);

    (void) ttysetup();		/* Set up (real) tty modes */
    
    (void) signal(SIGCONT, sig_cont_handler); /* Restore tty modes
					        on restart */

    /* set new line discipline */
    
    ldisc = NTTYDISC;
    (void) ioctl(slave_tty, TIOCSETD, &ldisc);

    /* get descriptor table size */

    width = getdtablesize();
    if (width > 32)
    {
	width = 32;
    }

    /* set initial edit function */

    edit = edit_0;

    /* initialize line buffer */

    point = 0;
    length = 0;

    /* initialize history buffer */

    head = 0;
    here = 0;

    for (i = 0; i < HISTORY_SIZE; i++)
    {
	hist[i].length = 0;
	hist[i].line = NULL;
    }

    for (;;)
    {
	readfds = (1 << READ) | (1 << master_pty);

	/* wait for input from stdin or master_pty */

	nfds = select(width, &readfds, NULL, NULL, NULL);

	if (nfds == -1)		/* an exception has occured */
	{
	    if (errno == EINTR)
		    continue;
	    perror("ile");
	    cleanup();
	}
	else if ((nfds > 0) && (readfds != 0))	/* something to read */
	{
	    if ((readfds & (1 << master_pty)) != 0)
	    {
		cc = read(master_pty, buffer, BUFFER_SIZE);
		cc = write(WRITE, buffer, cc);
	    }

	    if ((readfds & (1 << READ)) != 0)
	    {
		/* read the pending characters. */

		cc = read(READ, buffer, BUFFER_SIZE);

		(void) ioctl(slave_tty, TIOCGETP, &slave_params);

		/* if the slave has set ECHO, then we must clear it */

		if ((slave_params.sg_flags & ECHO) != 0)
		{
		    slave_params.sg_flags &= ~ECHO;
		    (void) ioctl(slave_tty, TIOCSETP, &slave_params);

		}

		/* if the slave is in RAW or CBREAK mode then we should not
		 * mess with its input characters */

		if ((slave_params.sg_flags & (RAW | CBREAK)) != 0)
		{
			(void) write(master_pty, buffer, cc);
		} else
			/* decide what to do with the characters. */
			for (i = 0; i < cc; i++)
				(*edit) (buffer[i]);
	    }

	}
    }

}
/*------------------------------------------------------------------*/
/*
The child process.

Make the pty the processes controling terminal. Bind the pty to
stdin, stdout, and stderr. Then exec the users program.
*/
void
child(argv)
    char *argv[];
{
    /* close all file descriptors */

    (void) close(READ);
    (void) close(WRITE);
    (void) close(ERROR);
    (void) close(slave_tty);

    /* get rid of controlling terminal */

    {
	int tty;

	if ((tty = open("/dev/tty", O_RDWR) == -1) ||
	    (ioctl(0, TIOCNOTTY, 0) == -1) ||
	    (close(tty) == -1))
	{
	    perror("ile");
	}
    }

    /* open the tty again */
    /* this makes the pty the controlling terminal */

    if ((slave_tty = open(ttydev, O_RDWR)) == -1)
    {
	perror("ile");
    }

    /* slave_tty is now stdin */

    /* bind slave_tty to stdout */

    (void) dup2(slave_tty, WRITE);

    /* bind slave_tty to stderr */

    (void) dup2(slave_tty, ERROR);

    /* close master_pty descriptor */

    (void) close(master_pty);

    /* Fire up application program. If no program name is given then fire up
     * csh. */

    if (argv[1] == NULL)
    {
	execlp("csh", "csh", 0);
    }
    else if (*argv[1] == '-')
    {
	if (argv[2] == NULL)
	{
	    execlp("csh", "csh", 0);
	}
	else
	{
	    execvp(argv[2], &argv[2]);
	}
    }
    else
    {
	execvp(argv[1], &argv[1]);
    }

    /* this executes if exec fails */

    perror("ile");
    exit(1);
}
/*------------------------------------------------------------------*/
/*
Set up default key bindings and delimeters.
*/
void
default_bindings()
{
    int i;

    /* clear delimiter vector and the action table */

    for (i = 0; i < CHAR_SET_SIZE; i++)
    {
	delimit[i] = FALSE;

	action_table[0][i].aors.action = insert;
	action_table[1][i].aors.action = bell;
	action_table[2][i].aors.action = bell;
	action_table[3][i].aors.action = bell;

	action_table[0][i].flag = is_action;
	action_table[1][i].flag = is_action;
	action_table[2][i].flag = is_action;
	action_table[3][i].flag = is_action;
    }

    /* default delimiters */

    delimit[' '] = TRUE;	/* blank */
    delimit['/'] = TRUE;	/* slash */
    delimit['.'] = TRUE;	/* dot */
    delimit['-'] = TRUE;	/* dash */

    /* default action_table[0] */

    action_table[0][CA].aors.action = start_of_line;
    action_table[0][CB].aors.action = backward_char;
    action_table[0][CE].aors.action = end_of_line;
    action_table[0][CF].aors.action = forward_char;
    action_table[0][CK].aors.action = erase_to_end_of_line;
    action_table[0][CU].aors.action = erase_line;
    action_table[0][CL].aors.action = retype_line;
    action_table[0][CN].aors.action = forward_history;
    action_table[0][CP].aors.action = backward_history;
    action_table[0][CT].aors.action = transpose_chars;
    action_table[0][CV].aors.action = quote;
    action_table[0][del].aors.action = delete_char;
    action_table[0][esc].aors.action = escape_1;
    action_table[0][cr].aors.action = add_to_history;
    action_table[0][nl].aors.action = add_to_history;
    action_table[0][CX].aors.action = delete_char_under;

    action_table[0][CC].aors.action = pass;
    action_table[0][CD].aors.action = pass;
    action_table[0][CQ].aors.action = pass;
    action_table[0][CS].aors.action = pass;
    action_table[0][CZ].aors.action = pass;

    /* default action_table[1] ^[ c */

    action_table[1]['b'].aors.action = backward_word;
    action_table[1]['f'].aors.action = forward_word;
    action_table[1][del].aors.action = delete_word;
    action_table[1]['u'].aors.action = upper_word;
    action_table[1]['l'].aors.action = lower_word;
    action_table[1]['c'].aors.action = capitalize_word;
    action_table[1]['['].aors.action = escape_2;
    action_table[1][esc].aors.action = complete_file;
    action_table[1]['s'].aors.action = complete_file_full;
    action_table[1]['d'].aors.action = show_files;

    /* default action_table[2] ^[ [ */

    action_table[2]['A'].aors.action = backward_history;
    action_table[2]['B'].aors.action = forward_history;
    action_table[2]['C'].aors.action = forward_char;
    action_table[2]['D'].aors.action = backward_char;

}
/*------------------------------------------------------------------*/
/*
Return a character or EOF. This routine reads characters from input
and converts them into a character using the following rules.

The character may be a single character, a control
character indicated by ^x, an octal number starting with \, or an
escaped character indictated by \x.
*/
int
scan_char(input)
    FILE *input;
{
    char ch;
    int value;

    ch = fgetc(input);
    switch (ch)
    {
    case '^':

	/* it is a control character */

	for (ch = fgetc(input); '@' <= ch; ch = ch - '@');

	break;

    case '\\':

	/* octal or an escaped character? */

	ch = fgetc(input);
	if (('0' <= ch) && (ch <= '7'))
	{

	    /* its an octal number */

	    value = 0;
	    while (('0' <= ch) && (ch <= '7'))
	    {
		value = (value * 8) + (ch - '0');
		ch = fgetc(input);
	    }
	    ungetc(ch, input);

	    ch = value & 0177;	/* make sure it is in range */
	}
	else
	{
	    /* its an escaped character */

	    ch = fgetc(input);
	}

	break;

    case '\n':

	/* the real end of the line */

	ch = EOL;

	break;

    default:

	/* it is just itself */

	break;
    }

    return (ch);

}
/*------------------------------------------------------------------*/
/*
Set key bindings and delimiters from the users file.
*/
void
user_bindings(file)
    FILE *file;
{

#define NAME_SIZE 40

    static struct action_name_table
    {
	char *name;
	void (*action) ();
    } action_name_table[] =
    {
	{
	    "complete_file_full", complete_file_full
	},
	{
	    "complete_file", complete_file
	},
	{
	    "show_files", show_files
	},
	{
	    "bell", bell
	},
	{
	    "pass", pass
	},
	{
	    "insert", insert
	},
	{
	    "transpose_chars", transpose_chars
	},
	{
	    "delete_char", delete_char
	},
	{
	    "delete_char_under", delete_char_under
	},
	{
	    "quote", quote
	},
	{
	    "escape_1", escape_1
	},
	{
	    "escape_2", escape_2
	},
	{
	    "escape_3", escape_3
	},
	{
	    "delete_word", delete_word
	},
	{
	    "upper_word", upper_word
	},
	{
	    "lower_word", lower_word
	},
	{
	    "capitalize_word", capitalize_word
	},
	{
	    "forward_word", forward_word
	},
	{
	    "backward_word", backward_word
	},
	{
	    "start_of_line", start_of_line
	},
	{
	    "backward_char", backward_char
	},
	{
	    "end_of_line", end_of_line
	},
	{
	    "forward_char", forward_char
	},
	{
	    "add_to_history", add_to_history
	},
	{
	    "erase_line", erase_line
	},
	{
	    "erase_to_end_of_line", erase_to_end_of_line
	},
	{
	    "retype_line", retype_line
	},
	{
	    "forward_history", forward_history
	},
	{
	    "backward_history", backward_history
	},
	{
	    "", NULL
	}
    };

    char name[NAME_SIZE];

    char ch;
    int i;

    int line;
    int table;
    int entry;

    /* First clear the default delimiters */

    for (i = 0; i < CHAR_SET_SIZE; i++)
    {
	delimit[ch] = FALSE;
    }

    /* Now read the delimiter characters */

    while (((ch = fgetc(file)) != EOF) && (ch != '\n'))
    {
	delimit[ch] = TRUE;
    }

    line = 2;

    /* Now read the character binding pairs */

    while ((ch = fgetc(file)) != EOF)
    {
	switch (ch)
	{
	case '\n':

	    /* skipping a blank line */
	    line++;

	    break;

	case '0':
	case '1':
	case '2':
	case '3':

	    /* which table is this entry directed to? */

	    table = ch - '0';

	    /* get the character code */

	    entry = scan_char(file);

	    /* make sure the '=' is there */

	    ch = fgetc(file);
	    if (ch != '=')
	    {
		fprintf(stderr, "ile: \'=' missing on line %d\n", line);
		exit(1);
	    }

	    /* collect the action name or string */

	    for (ch = scan_char(file), i = 0;
		(ch != EOL) && (i < (NAME_SIZE - 1));
		ch = scan_char(file), i++)
	    {
		name[i] = ch;
		name[i + 1] = '\0';
	    }

	    /* look it up in the action_name_table */

	    for (i = 0;
		(action_name_table[i].action != NULL) &&
		(strcmp(name, action_name_table[i].name) != 0);
		i++);

	    /* if it was found, put it in the action array */

	    if (action_name_table[i].action == NULL)
	    {
		/* must be a string */

		action_table[table][entry].flag = is_string;
		action_table[table][entry].aors.string =
		    (char *) malloc(strlen(name) + 1);
		strcpy(action_table[table][entry].aors.string, name);
	    }
	    else
	    {
		/* its an action */

		action_table[table][entry].flag = is_action;
		action_table[table][entry].aors.action =
		    action_name_table[i].action;
	    }

	    line++;		/* count the line */

	    break;

	default:
	    fprintf(stderr,
		"\nile: error in initialization file on line %d\n",
		line);
	    exit(1);
	    break;
	}
    }

    fclose(file);
}
/*------------------------------------------------------------------*/
/*
Initialize key bindings and delimiters.
*/
void
initialize(argv)
    char *argv[];
{
    FILE *file;
    char name[BUFFER_SIZE];

    /* set up the default bindings */

    default_bindings();

    /* Look for an initialization file. If it's there, load it. */

    name[0] = '\0';
    homedir = getenv("HOME");
    strcpy(name, homedir);
    strcat(name, "/.ilerc");

    if ((argv[1] != NULL) &&
	(*argv[1] == '-') &&
	((file = fopen(argv[1] + 1, "r")) != NULL))
    {
	/* load the users bindings */

	user_bindings(file);
    }
    else if (((file = fopen("./.ilerc", "r")) != NULL) ||
	((file = fopen(name, "r")) != NULL))
    {
	user_bindings(file);
    }
}
/*------------------------------------------------------------------*/
/*
*/
main(argc, argv)
    int argc;
    char *argv[];
{
    /* identify yourself */

    printf("Starting ILE...\n");

    /* create the tty/pty pair */

    getpty(&master_pty, &slave_tty);

    /* get control sequences from termcap */

    get_termcap();

    /* initialize the dispatch vectors */

    initialize(argv);

    /* create the child process */

    child_pid = fork();

    switch (child_pid)
    {
    case 0:			/* child process */

	child(argv);
	break;

    case -1:			/* fork failed */

	perror("ile");
	exit(1);
	break;

    default:			/* parent process */
	parent_pid = getpid();
	ile();
	break;
    }

}
