[comp.sources.misc] e - the friendly interface to vi. version 1.1

tcjones@watdragon.waterloo.edu (Crocodile Dundee) (10/21/87)

Here is version 1.1 of e - the friendly interface to vi. 

This version fixes a bug with extra long argument lists, adds a very useful 
capability to do fast cross-directory e'ing using an environment variable 
VIPATH, and will now compile ok on a SUN.
	Terry Jones

#!/bin/sh
# This is a shell archive.  Remove anything before the previous line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by watdragon!tcjones on Sat Oct 17 22:24:29 EDT 1987
# Contents:  README edoc Makefile e.h e.c Manifest
 
echo x - README
sed 's/^@//' > "README" <<'@//E*O*F README//'

Basically e is a little thingamy to let you get into vi with more ease.
I find it useful - even given a shell with history. It keeps a short history
of the files that have been e'ed most recently in each directory. Thus it is 
nice to be able to do

    cd somewhere
    e

and get straight into the file you were last working on in the directory 
"somewhere". It also allows quick cross-directory editing and trys to help 
out with spelling.


There is some documentation and examples in the file edoc.

Before making you should change the 
#define VI "/usr/ucb/vi"
line in e.h if your vi is located elsewhere.
and the DEST line in Makefile.

This will run just fine on BSD4.[23].
It appears to work on a SUN as well, just #define SUN in e.h

Other versions will have little trouble. The readdir/opendir stuff of BSD is 
used for spelling corrections and cross-directory e'ing. I modified e.h to work
on unix v8 in about 15 minutes. I think all that was required was to 
#include <ndir.h> instead of dir.h and to change the modes on the read() calls.

This appears free of major bugs (famous last words) - I'd really like to hear
about anything that goes wrong and/or fixes/enhancements. Comments on what is
wrong and how it could be better/faster are also welcome...


-------------------------------------------------------------------------------
Terry Jones, Department Of Computer Science, University Of Waterloo
             Waterloo Ontario Canada N2L 3G1

{ihnp4,allegra,decvax,utzoo,utcsri,clyde}!watmath!watdragon!tcjones
tcjones@dragon.waterloo.{cdn,edu} tcjones@WATER.bitnet
tcjones%watdragon@waterloo.csnet [from oz, tcjones@dragon.waterloo.cdn@munnari]
-------------------------------------------------------------------------------

@//E*O*F README//
chmod u=rw,g=rx,o=rx README
 
echo x - edoc
sed 's/^@//' > "edoc" <<'@//E*O*F edoc//'


e version 1.1 1/10/87
=====================

About e
=======

e is an interface to vi that maintains a history of the most recently e'ed
files for each directory. Its invocation syntax is (almost) a superset of 
that of vi.

A file called .e is kept in each directory. This contains the history and is 
kept to a small length (<10 lines). Spelling corrections are suggested for 
simple mistakes (omitted character, interchanged characters, extra character). 
The history file is rearranged with each use to place the last e'ed file at
the end of the list. Duplicate entries are removed.

If set, the environment variable VIPATH is read and the directories it contains
are searched for an existing file with the same name before a new file is
created. Thus one can vi across directories with ease - it's nice to type

"e .cshrc" or "e inode.h" etc 

from anywhere - albeit (for those that have it) at the loss of filename 
completion.


Command line syntax
===================

A list of the command line variations is given below. Those options that are
reminiscent of vi syntax have a "*" after their descriptions.
[x] indicates that "x" is optional. "cmd" means a vi command.


e           (Invokes vi on the last file that was e'ed in this directory)

e -         (Prints the history for this directory and allows selection
             of a previous file - or a new one)

e .         (Prints the history for this directory without asking for input)

e -t tag    (Invokes vi -t tag)*

e -r        (Invokes vi -r)*

e -r file   (Invokes vi -r file)*

e -pat      (vi's the last file that was e'ed with the string "pat" on
             the command line)

e +[cmd]    (vi's the last file that was e'ed in this directory but executes
             command "cmd" on the way into vi)

e [+[cmd]] file  
            (vi's the file and adds it to the history list. minor spelling
             corrections are suggested if "file" does not exist but is close 
             (in spelling) to some file that does) *

e [+[cmd]] files 
            (vi's the files and adds them as a single entry to the history) *


"Features".
==========

When using "e -" the terminal is put into cbreak mode. If the first
character typed is a digit (in the acceptable range of history items)
then you will get that history item without further ado. Thus if you
have a file called 4play and you try and "e" it from within a "e -"
then you'll probably end up in the wrong place.

Also the history length must be less than or equal to 9 (the code sets
it to 8 at present (HIST_LINES)). This, I find, is enough but you may want
more. The problem with having more is that with "e -" you go into cbreak
and the first digit entered is taken to mean "I want the nth last file". This
saves the need for hitting return. Do what you will, but I like it this way.
With the history being kept as a most recently used list, 8 file names 
should be enough.

The numbering on "e -" and "e ." is slightly different. The "e -" option
is meant so that you can look at the number next to the file name and hit
it easily. With "e ." you see what files you would get if you did an "e -3" 
(etc) and so it makes sense that "e -1" should give the second last file. 
That way you can pop in and out of two files by repeated "e -1"'s. Once again, 
that's the way it is...


Examples.
=========

With a ".e" file containing 

    fred.c                                          [5]: fred.c
    jane                                            [4]: jane
    alison                  "e ." will give         [3]: alison
    +/main pete.c                                   [2]: +/main pete.c
    bigmac                                          [1]: bigmac
    fries juice                                     [0]: fries juice

"e"             - will get you fries and then juice.
"e -"           - will present the above and ask for a number (RETURN=0).
"e -ali"        - will get you alison.
"e +/ketchup"   - will get you fries and juice, searching for ketchup in fries.
"e bigamc"      - will ask if you want to correct to bigmac. n or N
                    will not do the correction, q or Q will quit, 
                    anything else will go ahead.

VIPATH
======

With an environment variable VIPATH set to "$HOME /usr/include/sys"

"e inode.h" will prompt you with /usr/include/sys/inode.h 
if no file called inode.h exists in the current directory or your home
directory. You can say y, n or q to accept, reject or quit. 
If you accept then the file is put into the history. Spelling corrcetions 
are *not* suggested across directories.

There is (of course) no need to put "." in your VIPATH. Doing so will just slow
things down and cannot possibly be of help.


To be added?
============

* Allow backspacing on a select line.
* Make .exrc files be inherited when e'ing across directories.
* Spelling corrections across directories.
* Plenty of fancy things.
* A man page.
* Keep a single .e file in $HOME and then it's easy to use the history in
   unwriteable directories etc. do this nicely with hashing/binary search etc.
   (suggested by ian! allen)


e version 1.1 changes - 17/10/87
================================

* Added VIPATH environment variable.
* Fixed problem with ARG_LENGTH.
* #ifdefs that made it work on a SUN3/50.
* Moved #defines into e.h

-------------------------------------------------------------------------------
terry jones, department of computer science, university of waterloo
             waterloo ontario canada N2L 3G1

{ihnp4,allegra,decvax,utzoo,utcsri,clyde}!watmath!watdragon!tcjones
tcjones@dragon.waterloo.{cdn,edu} tcjones@WATER.bitnet
tcjones%watdragon@waterloo.csnet [from oz, tcjones@dragon.waterloo.cdn@munnari]
-------------------------------------------------------------------------------
@//E*O*F edoc//
chmod u=rw,g=rx,o=rx edoc
 
echo x - Makefile
sed 's/^@//' > "Makefile" <<'@//E*O*F Makefile//'
CC = /bin/cc
CFLAGS = -O
DEST = /u/tcjones/bin/e
SOURCE = e.c

$(DEST) : $(SOURCE)

$(DEST):
	$(CC) $(CFLAGS) -o $(DEST) $(SOURCE)
	strip $(DEST)
@//E*O*F Makefile//
chmod u=rw,g=rx,o=rx Makefile
 
echo x - e.h
sed 's/^@//' > "e.h" <<'@//E*O*F e.h//'
/*
 * e.h for e version 1.1
 * =====================
 *
 *------------------------------------------------------------------------------
 * terry jones, department of computer science, university of waterloo
 *				waterloo ontario canada N2L 3G1

 * {ihnp4,allegra,decvax,utzoo,utcsri,clyde}!watmath!watdragon!tcjones
 * tcjones@dragon.waterloo.{cdn,edu} tcjones@WATER.bitnet
 * tcjones%watdragon@waterloo.csnet 
 *------------------------------------------------------------------------------
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/dir.h>
#include <sys/param.h>
#include <sgtty.h>
#include <strings.h>
#include <signal.h>
#include <ctype.h>

#ifndef SUN
#include <sys/inode.h>
#endif

#define VI "/usr/ucb/vi"
#define HIST ".e"
#define HIST_LINES 8
#define HIST_CHARS 1024
#define ARG_CHARS 1024
#define MAX_ARGS 100
#define MAX_PATH 1024
#define RUBOUT '\077'
#define BELL '\007'
#define O_READ 00004
#define G_READ 0004

#ifdef SUN
#define IREAD 00400
#define IWRITE 00200
#endif
@//E*O*F e.h//
chmod u=rw,g=rx,o=rx e.h
 
echo x - e.c
sed 's/^@//' > "e.c" <<'@//E*O*F e.c//'
/*
 * e.c - a friendly interface to vi
 * version 1.1
 *
 * Terry Jones
 * Department of Computer Science
 * University of Waterloo
 * Waterloo, Ontario, Canada. N2L 3G1
 *
 * {ihnp4,allegra,decvax,utzoo,utcsri,clyde}!watmath!watdragon!tcjones
 * tcjones@dragon.waterloo.{cdn,edu} tcjones@WATER.bitnet
 * tcjones%watdragon@waterloo.csnet 
 *
 * version 1.1 changes 17/10/87
 *
 *      - Added VIPATH environment variable
 *      - Fixed bug with argument lists that exceeded ARG_CHARS characters.
 *        in multiple().
 *      - Added #ifdef statements to enable compilation on a SUN.
 *      - Put #defines etc into e.h as they were getting in the way.
 */

#include "e.h"

/*
 * globals. all in the name of ease and perhaps speed.
 */

char history[HIST_CHARS];
char arg[ARG_CHARS];
char *hist[HIST_LINES];
char temp[HIST_CHARS];
char *tmp_file="._vihistXXXXXX";

void 
do_vi(thing)
char *thing;
{
    /* split the arguments in 'thing' up and exec vi on them */
    char *args[MAX_ARGS];
    char *this,*next;
    register i;

    args[0]=VI;
    args[1]=thing;

    i=1;
    while (*thing!='\0'&&(thing=index(thing,' '))!=NULL){
        *thing++='\0';
        if (*thing!='\0'){
            args[++i]=thing;
        }
    }
    args[++i]=NULL;
        
    if (execvp(VI,args)==-1){
        perror(VI);
        exit(1);
    }
}

main(c,v)
int c;
char **v;
{
    char *last_file();
    extern clean_up();

    /* make sure we reset the terminal on our way out if we get interrupted */
    if (signal(SIGINT, SIG_IGN) != SIG_IGN){
        signal(SIGINT, clean_up);
    }

    switch (c){
        case 1: 
            /* just go and vi the last file that was vi'ed */
            last_file();
            do_vi(arg);
            break;
        
        case 2:
            switch ((*++v)[0]){
                case '-':
                    if ((c=(*v)[1])=='\0'){
                        /* this is a select from history, ask what they want */
                        ask_hist();
                        do_vi(arg);
                    }
                    else if (isdigit(c)){
                        /* get the nth last file from the history and vi it */
                        nth_hist(c-'0');
                        do_vi(arg);
                    }
                    else if (c=='t'&&(*v)[2]=='\0'){
                        /* this is an empty tag - ignore it */
                        do_vi(*v);
                    }
                    else if (c=='r'&&(*v)[2]=='\0'){
                        /* a recover, just pass it to vi and don't interfere */
                        do_vi(*v);
                    }
                    else{
                        /* this is a pattern - try to match it */
                        find_match(++*v);
                        do_vi(arg);
                    }
                    break;

                case '+':
                    /* a command, put it before the last file name etc */
                    insert_command(*v);
                    do_vi(arg);
                    break;

                case '.':
                    /* just give a history list if there is only a dot */
                    if ((*v)[1]=='\0'){
                        register ct;
                        register i;

                        read_hist();
                        ct=split_hist();

                        for (i=0;i<ct;i++){
                            fprintf(stderr,"\t[%d]: %s\n",ct-i-1,hist[ct-i-1]);
                        }
                        exit(0);
                    }
                    /* 
                       WARNING!
                       the switch falls through in the case where there is a
                       pattern that starts with a period
                    */

                default :
                    /* looks like it's just a plain old file name. vi it! */
                    normal(*v);
                    do_vi(arg);
                    break;
            }
        default:
            /* a bunch of arguments, fix the history & vi them all as normal */
            multiple(c,v,ARG_CHARS);
            do_vi(arg);
            break;
    }
}



read_hist()
{
    /*
       read the history file and break it up into lines in the global variable
       'history'. do the appropriate checks to see that it exists etc...
    */

    register vh;
    register bytes;
    register offset;
    struct stat buf;

    /* 
       if there is no history file then say so and get out of here - they 
       had nobusiness asking for access to the history
    */
    if ((vh=open(HIST,O_RDONLY))==-1){
        perror(HIST);
        exit(1);
    }

    /* stat it */
    if (fstat(vh,&buf)==-1){
        perror(HIST);
        exit(1);
    }

    /* 
       set 'offset' so that we can read the last portion of the history
       file only. if there are less than HIST_CHARS characters in the
       file then we will start reading at 0, otherwise at HIST_CHARS
       characters before the end of the file.
    */
    offset=(int)buf.st_size-HIST_CHARS<0 ? 0 : buf.st_size-HIST_CHARS;

    /* move to that place in the file */
    if (lseek(vh,(long)offset,L_SET)==-1){
        perror(HIST);
        exit(1);
    }

    /* and READ! */
    if ((bytes=read(vh,history,HIST_CHARS))==-1){
        perror(HIST);
        exit(1);
    }

    /* if we didn't come up with ANYTHING we may as well leave */
    if (!bytes){
        fprintf(stderr,"Empty %s file.\n",HIST);
        exit(1);
    }

    /* zap the newline (which should be there) for now */
    if (history[--bytes]=='\n'){
        history[bytes]='\0';    
    }

    /* and get out of here */
    return(bytes);
}

char *
last_file()
{
    /*
       get the last name from the 'history' array and put it into 'arg'
    */

    read_hist();
    if (index(history,'\n')==NULL){
        if (*history=='\0'){
            fprintf(stderr,"%s: badly formatted.\n",HIST);
            exit(1);
        }
        else{
            sprintf(arg,"%s",history);
        }
    }
    else{
        sprintf(arg,"%s",rindex(history,'\n')+1);
    }
}

split_hist()
{
    /*
       set the array of pointers in 'hist' to point to the succesive names
       in the 'history' array. these are delimited (presumably) by newlines
       and so they're easy to catch...

       what in fact is done is that the history array is copied and we set
       the pointers up and set the newlines to be '\0' s. this way we don't
       mess up the history array as we will want it intact later on (maybe).
    */

    char *tmp;
    register count;

    /* copy it */
    sprintf(temp,"%s",history);

    /* 
       now run through breaking it up, setting pointers and return the number 
       of lines we found.
    */
    for (count=0;count<HIST_LINES;count++){
        if ((tmp=hist[count]=rindex(temp,'\n'))==NULL){
            break;
        }
        *tmp='\0';
        hist[count]++;
    }
    if (count<HIST_LINES){
        hist[count++]=temp;
    }
    return(count);
}

nth_hist(n)
int n;
{
    /*
       get the nth last filename from the list. make use (of course) of
       read_hist and split_hist.
    */
    register count;
    register i;

    read_hist();
    count=split_hist();
    if (n>count-1){
        if (count>1){
            fprintf(stderr,"Only %d history items exist.\n",count);
        }
        else{
            fprintf(stderr,"Only one history item exists.\n");
        }
        exit(1);
    }
    sprintf(arg,"%s",hist[n]);

    /* rebuild the history with the selected name at the bottom */
    reconstruct(n,count);
}

ask_hist()
{
    /*
       ask the outside world which of the files in the history is wanted.
       set the terminal to cbreak.
    */
    register i;
    register count;
    char *last;
    register option;
    struct sgttyb blk;

    /* read and split the history file */
    read_hist();
    count=split_hist();

    /* print the history */
    for (i=0;i<count;i++){
        fprintf(stderr,"\t[%d]: %s\n",count-i,hist[count-i-1]);
    }

    /* give them a prompt (of sorts) */
    fprintf(stderr,"select -> ");

    /* set the terminal up */
    set_term();

    /* get their response */
    option=getc(stdin);

    /* make the terminal 'safe' again */
    unset_term();

    /* 
       process the option and put the appropriate file name into the 
       arg variable.
    */
    if (option=='\n'){
        /* they want the last file of the list */
        fprintf(stderr,"%s\n",hist[0]);
        sprintf(arg,"%s",hist[0]);
        return;
    }
    else if (option==RUBOUT){
        /* they want to leave */
        fprintf(stderr,"\n");
        exit(1);
    }
    else if (option>='1'&&option<='0'+count){
        /* they have requested a file by it's number */
        option=option-'0';
        fprintf(stderr,"%s\n",hist[option-1]);
        sprintf(arg,"%s",hist[option-1]);
    }
    else{
        /* 
           looks like they want to name a specific file. echo the 
           characters back to the screen.
        */
        fprintf(stderr,"%c",option);
        arg[0]=option;
        i=1;
        while ((arg[i]=getc(stdin))!='\n'){
            i++;
        }
        arg[i]='\0';
        option=count-1;     /* a kludge for the history re-make to follow */

        /* seeing as they typed in the name, try and help with spelling */
        if (!spell_help()){
            find();
        }

        /* if it is in the history then reconstruct and return */
        for (i=0;i<count;i++){
            if (!strcmp(hist[i],arg)){
                reconstruct(i,count);
                return;
            }
        }
    }

    /* rebuild the history with the selected name at the bottom */
    reconstruct(option-1,count);
}

FILE *
get_temp()
{
    /* get ourselves a temporary file for the reconstructed history */
    FILE *fp,*fopen();

    mktemp(tmp_file);
    if ((fp=fopen(tmp_file,"w"))==NULL){
        perror(tmp_file);
        exit(1);
    }
    return(fp);
}

close_temp(fp)
FILE *fp;
{
    /* move the temporary file to be the new history */
    FILE *fclose();

    if (fclose(fp)==(FILE *)EOF){
        fprintf(stderr,"Could not close %s\n",tmp_file);
        exit(1);
    }

    if (rename(tmp_file,HIST)!=0){
        perror("rename");
        exit(1);
    }
}

set_term()
{
    /* go into cbreak and no echo mode */
    struct sgttyb   blk;

    gtty(0, &blk);
    blk.sg_flags |= CBREAK;
    blk.sg_flags ^= ECHO;
    stty(0, &blk);
}

unset_term()
{
    /* get out of cbreak and no echo */
    struct sgttyb   blk;

    gtty(0, &blk);
    blk.sg_flags &= ~CBREAK;
    blk.sg_flags ^= ECHO;
    stty(0, &blk);
}

match(argument,pattern)
char    *argument;
char    *pattern;
{
    /*
       boneheaded but easy pattern matcher. just see if the 'pattern'
       exists anywhere in the 'argument'. boyer-moore who?
    */
    register length=strlen(pattern);

    while (strlen(argument)>=length){
        if (!strncmp(argument++,pattern,length)){
            return(1);
        }
    }
    return(0);
}

find_match(pattern)
char *pattern;
{
    /*
       find the name in the history list that contains the 'pattern'.
       if it exists then put it into the 'arg' variable and otherwise
       announce that a match couldn't be found and leave.
    */
    register count;
    register i;

    /* read and split the history file */
    read_hist();
    count=split_hist();

    /* try for a match with each file in turn (note that we are working
       from most-recently-used backwards - probably a good thing)
    */
    for (i=0;i<count;i++){
        if (match(hist[i],pattern)){
            sprintf(arg,"%s",hist[i]);
            reconstruct(i,count);
            return;
        }
    }

    /* we couldn't match so get out of here */
    fprintf(stderr,"Unable to match with \"%s\"\n",pattern);
    exit(1);
}

insert_command(command)
char *command;
{
    /*
       they want the last file in the history but want to preceed it
       this time with a command - no problems here.
    */
    register count;
    char *place;

    /* read and split the history */
    read_hist();
    count=split_hist();
    
    /* 
       if there was already a command there (indicated by a '+') then we
       want to get rid of it. if there is a '+' but no ' ' after it then
       the history file is in disarray and we will not try to recover
    */
    if (*hist[0]=='+'){
        if ((place=index(hist[0],' '))==NULL){
            fprintf("Serious weirdenss in insert_command\n");
            exit(1);
        }
        /* move over white space - if there is any */
        while (*place==' '||*place=='\t'){
            place++;
        }
    }
    else{
        /* there was no command preceeding the last file in the history */
        place=hist[0];
    }

    /* put the new command and the filename into 'arg' */
    sprintf(arg,"%s %s",command,place);

    /* rebuild the history with the selected command and name at the bottom */
    reconstruct(0,count);
}

reconstruct(except,count)
int except;
int count;
{
    /* 
       reconstruct history file excepting the 'except' last.
       so just copy all lines but the 'except'th last and then put in 'arg'
       which contains the new line for the history
    */
    register i;
    FILE *tv,*get_temp();

    /* get a temporary file */
    tv=get_temp();

    /* put in the line we still want */
    for (i=count-1;i>=0;i--){
        if (i!=except){
            fprintf(tv,"%s\n",hist[i]);
        }
    }

    /* put in the new line from 'arg' */
    fprintf(tv,"%s\n",arg);

    /* rename the temporary to be the new history file */
    close_temp(tv);
}

normal(string)
char *string;
{
    /* 
       a normal filename was found, put it into arg. first of all if there
       is a history and the file is already in it (which means they could
       have gotten to this file in other ways), then reconstruct the history
       as though they had. also offer spelling help.
    */
    register count;
    register i;

    /* put it into 'arg' */
    sprintf(arg,"%s",string);

    /* if there is a history file */
    if (got_vi()){

        /* read it and split it up */
        read_hist();
        count=split_hist();

        /* if it is in the history then reconstruct and return */
        for (i=0;i<count;i++){
            if (!strcmp(hist[i],arg)){
                reconstruct(i,count);
                return;
            }
        }

        /* it's not in the history, help with spelling then reconstruct */
        if (!spell_help()){
            find();
        }

        /* if it is in the history then reconstruct and return */
        for (i=0;i<count;i++){
            if (!strcmp(hist[i],arg)){
                reconstruct(i,count);
                return;
            }
        }

        reconstruct(HIST_LINES,count);
    }
    else{

        /* 
           there is no history around so help with spelling and set up a 
           history for next time.
        */
        if (!spell_help()){
            find();
        }
        new_vi();
    }

}

multiple(number,args,size)
int number;
char **args;
{
    /*
       there were several names on the command line so we just strcat them
       into the 'arg' array. check to see that the length of all the args
       will not be greater than "size" or else we will overflow arg.
    */
    register count;
    register i;
    register total=0;

    *arg='\0';
    while (--number){
        if ((total+=strlen(*args)+1)>=size){    /* add one for a space */
            /*
             * If you are running e and you find that this condition occurs,
             * the solution is to simply increase the value of the #define
             * line for ARG_CHARS near the top.
             */
            fprintf(stderr,
            "%c%c%cWarning! Argument list too long, truncated after \"%s\".\n",
                BELL,BELL,BELL,*args);      
            sleep(2);   /* give the user some chance to see what happened */
            break;
        }
        strcat(arg,*++args);
        if (number>1){
            strcat(arg," ");
        }
    }

    /*
       now if there is a history file and we can find an identical line
       then reconstruct with that line at the bottom.
    */
    if (got_vi()){
        read_hist();
        count=split_hist();
        for (i=0;i<count;i++){
            if (!strcmp(hist[i],arg)){
                reconstruct(i,count);
                return;
            }
        }
        /* 
           rebuild, including everything but the counth last (i.e. make
           a new history by omitting the oldest file in the current one and
           putting 'arg' on the end.
        */
        reconstruct(HIST_LINES,count);
    }
    else{
        /* there was no history file so try to give them one for next time */
        new_vi();
    }
}

got_vi()
{
    /* indicate if there is a history file that they own or otherwise */
    struct stat buf;

    if (stat(HIST,&buf)==-1){
        return(0);
    }
    else{
        return(getuid()==buf.st_uid);
    }
}

new_vi()
{
    /* attempt to make a new history file - several things could go wrong */
    FILE *vh,*fopen(),*fclose();
    struct stat buf;

    /* if you can't read the current directory, get out */
    if (stat(".",&buf)==-1){
        perror("stat");
        exit(1);
    }

    /* if you own the directory (you can't normally get a history in /tmp) */
    if (getuid()==buf.st_uid){

        /* if we can't make a history, get out */
        if ((vh=fopen(HIST,"w"))==NULL){
            perror(HIST);
            exit(1);
        }

        /* put in the 'arg' that we will be vi'ing in a second */
        fprintf(vh,"%s\n",arg);

        /* close the history */
        if (fclose(vh)==(FILE *)EOF){
            fprintf(stderr,"Could not close %s\n",VI);
            exit(1);
        }

        /* give the history some protection - for those who want it! */
        if (chmod(HIST,IREAD|IWRITE)==-1){
            perror("chmod");
            exit(1);
        }
    }
}

clean_up()
{
    /* just get out after making sure things are tidy */
    fprintf(stderr,"Interrupt.\n");
    unlink(tmp_file);
    unset_term();
    exit(1);
}

spell_help()
{
    /*
       unashamedly stolen (and modified) from "The UNIX Programming
       Environment" - Kernighan and Pike

       read the directory and if the file they want (in 'arg') does not
       exist then see if there is one that does that has similar spelling
       to what they requested. offer the change and handle the reply.
    */

    char new[ARG_CHARS];
    register dist=3;
    register new_dist;
    DIR *dp, *opendir();
    struct direct *readdir();
    struct direct *entry;
    register len;
    struct stat buf;

    /* if the file already exists just return - they don't need help */
    if (stat(arg,&buf)==0){
        return(1);
    }

    /* if the current directory can't be read then return */
    if ((dp=opendir("."))==NULL){
        return(0);
    }
    
    /* get the length of what we are seeking to cut down on strcmping time */
    len=strlen(arg);

    for (entry=readdir(dp);entry!=NULL;entry=readdir(dp)){

        /* if this entry has length = sought length +/- 1 then it may be ok */
        if (entry->d_ino&&entry->d_namlen>=len-1&&entry->d_namlen<=len+1){

            /* get the 'distance' between what we want and this file name */
            new_dist=sp_dist(entry->d_name,arg);

            /* if this name is close enough and better than our current best */
            if (new_dist<=dist&&new_dist!=3){

                if (!new_dist){
                    /* if the dist is 0 then they are identical */
                    closedir(dp);
                    return(1);
                }
                /* remember the new name and distance */
                strcpy(new,entry->d_name);
                dist=new_dist;
            }
        }
    }

    /* close up. if we got no suitable result then simply return */
    closedir(dp);
    if (dist==3){
        return(0);
    }

    /* offer them "new" */
    set_term();
    fprintf(stderr,"correct to %s [y]? ",new);

    /* process the reply */
    switch (getc(stdin)){
        case 'N':
        case 'n':
            fprintf(stderr,"no\n");
            unset_term();
            return(0);
        case 'q':
        case 'Q':
            fprintf(stderr,"quit\n");
            unset_term();
            exit(0);
        default :
            fprintf(stderr,"yes\n");
            unset_term();
            strcpy(arg,new);
            return(1);
    }
}

sp_dist(s,t)
char *s;
char *t;
{
    /* 
       stolen from the same place as spell_help() above.

       work out the distance between the strings 's' and 't' according
       to the rough metric that
       
       identical = 0
       interchanged characters = 1
       wrong character/extra character/missing character = 2
       forget it = 3
    */

    while (*s++==*t){
        if (*t++=='\0'){
            /* identical */
            return(0);
        }
    }

    if (*--s){
        if (*t){
            if (s[1]&&t[1]&&*s==t[1]&&*t==s[1]&&!strcmp(s+2,t+2)){
                /* interchanged chars */
                return(1);
            }
            if (!strcmp(s+1,t+1)){
                /* wrong char */
                return(2);
            }
        }
        if (!strcmp(s+1,t)){
            /* extra char in 't' */
            return(2);
        }
    }
    if (!strcmp(s,t+1)){
        /* extra char in 's' */
        return(2);
    }

    /* forget it */
    return(3);
}


find()
{
    extern char *getenv();
    extern char *getwd();
    char *p;
    char path[MAX_PATH];
    char *dir;
    char *space;
    char *us;
    char wd[MAXPATHLEN];
    char what[ARG_CHARS];

    p=getenv("VIPATH");

    if (!p||!strlen(p)) return;
    
    if (strlen(p)>=MAX_PATH){
        fprintf(stderr,"Could not get VIPATH from environment.\n");
        exit(1);
    }

    strcpy(path,p);
    strcpy(what,arg);

    if (!(us=getwd(wd))){
        fprintf(stderr,"%s\n",wd);
        exit(1);
    }

    dir=path;
    while ((space=index(dir,' '))!=NULL){
        *space='\0';
        if (check(what,dir)){

            /* offer them dir/what */
            set_term();
            fprintf(stderr,"%s/%s [y]? ",dir,what);

            /* process the reply */
            switch (getc(stdin)){
                case 'N':
                case 'n':
                    fprintf(stderr,"no\n");
                    break;
                case 'q':
                case 'Q':
                    fprintf(stderr,"quit\n");
                    unset_term();
                    exit(0);
                    break;
                default :
                    fprintf(stderr,"yes\n");
                    unset_term();
                    if (chdir(us)==-1){
                        perror("chdir");
                        exit(1);
                    }
                    sprintf(arg,"%s/%s",dir,what);
                    return(1);
            }
            unset_term();
        }
        dir=space+1;
    }

    if (check(what,dir)){

        /* offer them dir/what */
        set_term();
        fprintf(stderr,"%s/%s [y]? ",dir,what);

        /* process the reply */
        switch (getc(stdin)){
            case 'N':
            case 'n':
                fprintf(stderr,"no\n");
                break;
            case 'q':
            case 'Q':
                fprintf(stderr,"quit\n");
                unset_term();
                exit(0);
                break;
            default :
                fprintf(stderr,"yes\n");
                unset_term();
                if (chdir(us)==-1){
                    perror("chdir");
                    exit(1);
                }
                sprintf(arg,"%s/%s",dir,what);
                return(1);
        }
        unset_term();
    }

    if (chdir(us)==-1){
        perror("chdir");
        exit(1);
    }
    return(0);
}

check(target,dir)
char *target;
char *dir;
{
    DIR *dp, *opendir();
    struct direct *readdir();
    struct direct *entry;
    struct stat buf;

    if ((dp=opendir(dir))==NULL){
        /* watch here for the chance that they are sitting in someone
           else's directory which may be unreadable - if not, complain. */
        if (strcmp(dir,".")){
            fprintf(stderr,"Cannot open \"%s\"\n",dir);
        }
        return;
    }
    
    for (entry=readdir(dp);entry!=NULL;entry=readdir(dp)){
        if (!strcmp(entry->d_name,target)){
            if (chdir(dir)==-1){
                perror("chdir");
                return;
            }

            if (stat(entry->d_name,&buf)==-1){
                perror("stat");
                return;
            }

            /* 
                if it is not a directory and EITHER you own it and can
                read it OR you don't own it and it is readable by others, 
                OR you are in the group of the owner and it's group readable
                    - then this is it.
            */

            if (    !(buf.st_mode&S_IFDIR)  &&  
                    (
                        (buf.st_uid==getuid() && buf.st_mode&S_IREAD)
                        ||
                        (buf.st_gid==getgid() && buf.st_mode&G_READ)
                        ||
                        (buf.st_uid!=getuid() && buf.st_mode&O_READ)
                    )
                )
            {
                return(1);
            }
        }
    }
    return(0);
}
@//E*O*F e.c//
chmod u=rw,g=rx,o=rx e.c
 
echo x - Manifest
sed 's/^@//' > "Manifest" <<'@//E*O*F Manifest//'
The shar should come with six files

    146     Makefile
    117     Manifest
    1724    README
    20563   e.c
    1049    e.h
    5442    edoc

@//E*O*F Manifest//
chmod u=rw,g=,o= Manifest
 
echo Inspecting for damage in transit...
temp=/tmp/shar$$; dtemp=/tmp/.shar$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
      43     249    1733 README
     152     899    5727 edoc
      10      23     146 Makefile
      45     103    1049 e.h
    1023    2756   25469 e.c
       9      19     148 Manifest
    1282    4049   34272 total
!!!
wc  README edoc Makefile e.h e.c Manifest | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if [ -s $dtemp ]
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0