home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume1 / 8710 / 9 < prev    next >
Encoding:
Text File  |  1990-07-13  |  23.4 KB  |  1,042 lines

  1. Path: uunet!husc6!necntc!ncoast!allbery
  2. From: tcjones@watdragon.UUCP (Crocodile Dundee)
  3. Newsgroups: comp.sources.misc
  4. Subject: e - a friendly interface to vi
  5. Message-ID: <4848@ncoast.UUCP>
  6. Date: 13 Oct 87 02:45:52 GMT
  7. Sender: allbery@ncoast.UUCP
  8. Organization: U. of Waterloo, Ontario
  9. Lines: 1029
  10. Approved: allbery@ncoast.UUCP
  11. X-Archive: comp.sources.misc/8710/9
  12.  
  13. here is a little thing i use all the time to get into vi without having
  14. to type a filename terribly often. all in the name of fewer keystrokes.
  15. try it.
  16.  
  17.  - terry.
  18.  
  19.  
  20. # This is a shell archive.  Remove anything before this line,
  21. # then unpack it by saving it in a file and typing "sh file".
  22. # Contents:  README Makefile MANIFEST edoc e.c
  23.  
  24. echo x - README
  25. sed 's/^@//' > "README" <<'@//E*O*F README//'
  26. basically e is a little thingamy to let you get into vi with more ease.
  27. i find it useful - even given a shell with history. it keeps a short history
  28. of the files that have been vi'ed most recently in each directory. thus it is 
  29. nice to be able to do
  30.  
  31. cd somewhere
  32. e
  33.  
  34. and get straight into the file you were last working on in the directory 
  35. somewhere. 
  36.  
  37.  
  38. there is some documentation and examples in the file edoc.
  39. before compiling you should change the 
  40. #define VI "/usr/ucb/vi"
  41. line in e.c if your vi is located elsewhere.
  42. and the DEST line in Makefile.
  43.  
  44. this will run just fine on 4.[23] but i suspect other versions will have 
  45. a little trouble. the readdir/opendir stuff of BSD is used for spelling 
  46. corrections and can be easily rewritten for nonBSD - or you can simply comment 
  47. out the last two functions of e.c [spell_help() and sp_dist()] and all 
  48. references to them at the cost of faster but maybe a less helpful program.
  49.  
  50. this appears free of major bugs (famous last words) - i'd really like to hear
  51. about anything that goes wrong &/| fixes/enhancements. comments on what is
  52. wrong and how it could be better/faster are also welcome...
  53.  
  54. terry jones
  55.  
  56.  
  57. ----------------------here's every email address i can find---------------------
  58. USENET    :     {ihnp4,allegra,decvax,utzoo,utcsri}!watmath!watdragon!tcjones
  59. UUCP      :     {ihnp4,decvax,utzoo}!watmath!watdragon!tcjones
  60. CSNET     :     tcjones%watdragon@waterloo.csnet
  61. ARPA      :     tcjones%watdragon%waterloo.csnet@csnet-relay.arpa
  62. Internet:     tcjones@er.waterloo.edu
  63. Bitnet    :     tcjones@watdragon
  64. CDNnet    :     tcjones@er.waterloo.cdn
  65. from oz    :    tcjones@er.waterloo.cdn@munnari 
  66.  
  67. SNAIL     :     Terry Jones/Department of Computer Science
  68. MAIL        /University of Waterloo/Waterloo/Ontario/Canada/N2L 3G1
  69. PHONENET:    1 519 885-6686 (work?) or 1 519 884-6338 (home)
  70. --------------------------------------------------------------------------------
  71. @//E*O*F README//
  72. chmod u=rw,g=r,o=r README
  73.  
  74. echo x - Makefile
  75. sed 's/^@//' > "Makefile" <<'@//E*O*F Makefile//'
  76. CC = cc
  77. CFLAGS = -O
  78. DEST = /u/tcjones/bin/e
  79. SOURCE = e.c
  80.  
  81. $(DEST) : $(SOURCE)
  82.  
  83. $(DEST):
  84.     $(CC) $(CFLAGS) -o $(DEST) $(SOURCE)
  85.     strip $(DEST)
  86. @//E*O*F Makefile//
  87. chmod u=rw,g=rx,o=rx Makefile
  88.  
  89. echo x - MANIFEST
  90. sed 's/^@//' > "MANIFEST" <<'@//E*O*F MANIFEST//'
  91. edoc
  92. e.c
  93. Makefile
  94. MANIFEST
  95. @//E*O*F MANIFEST//
  96. chmod u=rw,g=r,o=r MANIFEST
  97.  
  98. echo x - edoc
  99. sed 's/^@//' > "edoc" <<'@//E*O*F edoc//'
  100. e is an interface to vi that maintains a history of the most recently e'ed
  101. files for each directory. it is used with exactly the same syntax etc as vi is.
  102. a file called .e is kept in each directory. this contains the history and is 
  103. kept to a small length (<10 lines). spelling corrections are suggested for 
  104. simple mistakes (omitted character, interchanged characters, extra character). 
  105. the history file is rearranged with each use to place the last e'ed file at
  106. the end of the list. duplicate entries are removed (in most situations).
  107. a list of the command line variations is given below.
  108.  
  109.  
  110. e            [vi's the last file that was e'ed in this directory]
  111.  
  112. e -            [prints the history for this directory and allows selection
  113.              of a previous file - or a new one]
  114.  
  115. e .            [prints the history for this directory without asking for input]
  116.  
  117. e -t tag    [the usual tag entry to vi]
  118.  
  119. e -r        [invokes vi -r]
  120.  
  121. e -r file   [invokes vi -r file]
  122.  
  123. e -pat      [vi's the last file that was e'ed with the string 'pat' on
  124.              the command line]
  125.  
  126. e +cmd        [vi's the last file that was e'ed in this directory but executes
  127.              'cmd' on the way into vi]
  128.  
  129. e file        [vi's the file and adds it to the history list. minor spelling
  130.              corrections are suggested if 'file' does not exist but is close 
  131.              (in spelling) to some file that does]
  132.  
  133. e files        [vi's the files and adds them (as a single entry) to the history]
  134.  
  135.  
  136. "features".
  137. ==========
  138. when using "e -" the terminal is put into cbreak mode. if the first
  139. character typed is a digit (in the acceptable range of history items)
  140. then you will get the history item without further ado. thus if you
  141. have a file called 4play and you try and "e" it from within a "e -"
  142. then you'll probably end up it the wrong place.
  143.  
  144. also the history length must be less than or equal to 9 (the code sets
  145. it to 8 at present (HIST_LINES)). this, i find, is enough but you may want
  146. more. the problem with having more is that with "e -" you go into cbreak
  147. and the first digit entered is taken to mean i want the nth last file. this
  148. saves the need for hitting return. do what you will, but i like it this way.
  149. with the history being kept as a most recently used list 8 file names 
  150. should be enough.
  151.  
  152. the numbering on "e -" and "e ." is slightly different. the "e -" option
  153. is meant so that you can look at the number next to the file name and hit
  154. it easily. with "e ." you see what files you would get if you did a "e -3" (etc)
  155. and so it makes sense that "e -1" should give the second last file. that way you
  156. can pop in and out of two files by repeated "e -1"'s (if you don't do ctrl-^).
  157. once again, that's the way it is...
  158.  
  159. examples.
  160. =========
  161. with a ".e" file containing 
  162.  
  163.     fred.c                                [5]: fred.c
  164.     jane                                [4]: jane
  165.     alison            "e ." will give        [3]: alison
  166.     +/main pete.c                        [2]: +/main pete.c
  167.     bigmac                                [1]: bigmac
  168.     fries juice                            [0]: fries juice
  169.  
  170. so "e" will get you fries and then juice.
  171. "e -" will present the above and ask for a number (RETURN=0).
  172. "e -ali" will get you alison.
  173. "e +/ketchup" will get you fries and juice, searching for ketchup in fries.
  174. "e bigamc" will ask if you want to correct to bigmac. n or N
  175.     will do the correction, q or Q will quit, anything else will go ahead.
  176.  
  177.  
  178. to be added?
  179. ============
  180. allow backspacing on a select line.
  181. uncompress and recompress .Z files automatically?
  182. plenty of fancy things
  183. a man page
  184. @//E*O*F edoc//
  185. chmod u=rw,g=rx,o=rx edoc
  186.  
  187. echo x - e.c
  188. sed 's/^@//' > "e.c" <<'@//E*O*F e.c//'
  189. /*
  190.  * e.c - a friendly interface to vi
  191.  *
  192.  * Terry Jones    {ihnp4,allegra,decvax,utzoo,utcsri}!watmath!watdragon!tcjones
  193.  * Department of Computer Science
  194.  * University of Waterloo
  195.  * Waterloo, Ontario, Canada. N2L 3G1
  196.  *
  197.  *
  198.  * best vi'ed with ts=4 sw=4
  199.  * to compile: cc -o e -O e.c
  200.  */
  201.  
  202. #include <stdio.h>
  203. #include <sys/types.h>
  204. #include <sys/stat.h>
  205. #include <sys/file.h>
  206. #include <sys/dir.h>
  207. #include <sys/inode.h>
  208. #include <sgtty.h>
  209. #include <strings.h>
  210. #include <signal.h>
  211. #include <ctype.h>
  212.  
  213. #define VI "/usr/ucb/vi"
  214. #define HIST ".e"
  215. #define HIST_LINES 8
  216. #define HIST_CHARS 512
  217. #define ARG_CHARS 100
  218. #define MAX_ARGS 10
  219. #define RUBOUT '\077'
  220.  
  221. char history[HIST_CHARS];
  222. char arg[ARG_CHARS];
  223. char *hist[HIST_LINES];
  224. char temp[HIST_CHARS];
  225. char *tmp_file="._vihistXXXXXX";
  226.  
  227. void 
  228. do_vi(thing)
  229. char *thing;
  230. {
  231.     /* split the arguments in 'thing' up and exec vi on them */
  232.     char *args[MAX_ARGS];
  233.     char *this,*next;
  234.     register i;
  235.  
  236.     args[0]=VI;
  237.     args[1]=thing;
  238.  
  239.     i=1;
  240.     while (*thing!='\0'&&(thing=index(thing,' '))!=NULL){
  241.         *thing++='\0';
  242.         if (*thing!='\0'){
  243.             args[++i]=thing;
  244.         }
  245.     }
  246.     args[++i]=NULL;
  247.         
  248.     if (execvp(VI,args)==-1){
  249.         perror(VI);
  250.         exit(1);
  251.     }
  252. }
  253.  
  254. main(c,v)
  255. int c;
  256. char **v;
  257. {
  258.     char *last_file();
  259.     extern clean_up();
  260.  
  261.     /* make sure we reset the terminal on our way out if we get interrupted */
  262.     if (signal(SIGINT, SIG_IGN) != SIG_IGN){
  263.         signal(SIGINT, clean_up);
  264.     }
  265.  
  266.     switch (c){
  267.         case 1: 
  268.             /* just go and vi the last file that was vi'ed */
  269.             last_file();
  270.             do_vi(arg);
  271.             break;
  272.         
  273.         case 2:
  274.             switch ((*++v)[0]){
  275.                 case '-':
  276.                     if ((c=(*v)[1])=='\0'){
  277.                         /* this is a select from history, ask what they want */
  278.                         ask_hist();
  279.                         do_vi(arg);
  280.                     }
  281.                     else if (isdigit(c)){
  282.                         /* get the nth last file from the history and vi it */
  283.                         nth_hist(c-'0');
  284.                         do_vi(arg);
  285.                     }
  286.                     else if (c=='t'&&(*v)[2]=='\0'){
  287.                         /* this is an empty tag - ignore it */
  288.                         do_vi(*v);
  289.                     }
  290.                     else if (c=='r'&&(*v)[2]=='\0'){
  291.                         /* a recover, just pass it to vi and don't interfere */
  292.                         do_vi(*v);
  293.                     }
  294.                     else{
  295.                         /* this is a pattern - try to match it */
  296.                         find_match(++*v);
  297.                         do_vi(arg);
  298.                     }
  299.                     break;
  300.  
  301.                 case '+':
  302.                     /* a command, put it before the last file name etc */
  303.                     insert_command(*v);
  304.                     do_vi(arg);
  305.                     break;
  306.  
  307.                 case '.':
  308.                     /* just give a history list if there is only a dot */
  309.                     if ((*v)[1]=='\0'){
  310.                         register ct;
  311.                         register i;
  312.  
  313.                         read_hist();
  314.                         ct=split_hist();
  315.  
  316.                         for (i=0;i<ct;i++){
  317.                             fprintf(stderr,"\t[%d]: %s\n",ct-i-1,hist[ct-i-1]);
  318.                         }
  319.                         exit(0);
  320.                     }
  321.                     /* 
  322.                        WARNING!
  323.                        the switch falls through in the case where there is a
  324.                        pattern that starts with a period
  325.                     */
  326.  
  327.                 default :
  328.                     /* looks like it's just a plain old file name. vi it! */
  329.                     normal(*v);
  330.                     do_vi(arg);
  331.                     break;
  332.             }
  333.         default:
  334.             /* a bunch of arguments, fix the history & vi them all as normal */
  335.             multiple(c,v);
  336.             do_vi(arg);
  337.             break;
  338.     }
  339. }
  340.  
  341.  
  342.  
  343. read_hist()
  344. {
  345.     /*
  346.        read the history file and break it up into lines in the global variable
  347.        'history'. do the appropriate checks to see that it exists etc...
  348.     */
  349.  
  350.     register vh;
  351.     register bytes;
  352.     register offset;
  353.     struct stat buf;
  354.  
  355.     /* 
  356.        if there is no history file then say so and get out of here - they 
  357.        had nobusiness asking for access to the history
  358.     */
  359.     if ((vh=open(HIST,O_RDONLY))==-1){
  360.         perror(HIST);
  361.         exit(1);
  362.     }
  363.  
  364.     /* stat it */
  365.     if (fstat(vh,&buf)==-1){
  366.         perror(HIST);
  367.         exit(1);
  368.     }
  369.  
  370.     /* 
  371.        set 'offset' so that we can read the last portion of the history
  372.        file only. if there are less than HIST_CHARS characters in the
  373.        file then we will start reading at 0, otherwise at HIST_CHARS
  374.        characters before the end of the file.
  375.     */
  376.     offset=(int)buf.st_size-HIST_CHARS<0 ? 0 : buf.st_size-HIST_CHARS;
  377.  
  378.     /* move to that place in the file */
  379.     if (lseek(vh,(long)offset,L_SET)==-1){
  380.         perror(HIST);
  381.         exit(1);
  382.     }
  383.  
  384.     /* and READ! */
  385.     if ((bytes=read(vh,history,HIST_CHARS))==-1){
  386.         perror(HIST);
  387.         exit(1);
  388.     }
  389.  
  390.     /* if we didn't come up with ANYTHING we may as well leave */
  391.     if (!bytes){
  392.         fprintf(stderr,"Empty %s file.\n",HIST);
  393.         exit(1);
  394.     }
  395.  
  396.     /* zap the newline (which should be there) for now */
  397.     if (history[--bytes]=='\n'){
  398.         history[bytes]='\0';    
  399.     }
  400.  
  401.     /* and get out of here */
  402.     return(bytes);
  403. }
  404.  
  405. char *
  406. last_file()
  407. {
  408.     /*
  409.        get the last name from the 'history' array and put it into 'arg'
  410.     */
  411.  
  412.     read_hist();
  413.     if (index(history,'\n')==NULL){
  414.         if (*history=='\0'){
  415.             fprintf(stderr,"%s: badly formatted.\n",HIST);
  416.             exit(1);
  417.         }
  418.         else{
  419.             sprintf(arg,"%s",history);
  420.         }
  421.     }
  422.     else{
  423.         sprintf(arg,"%s",rindex(history,'\n')+1);
  424.     }
  425. }
  426.  
  427. split_hist()
  428. {
  429.     /*
  430.        set the array of pointers in 'hist' to point to the succesive names
  431.        in the 'history' array. these are delimited (presumably) by newlines
  432.        and so they're easy to catch...
  433.  
  434.        what in fact is done is that the history array is copied and we set
  435.        the pointers up and set the newlines to be '\0' s. this way we don't
  436.        mess up the history array as we will want it intact later on (maybe).
  437.     */
  438.  
  439.     char *tmp;
  440.     register count;
  441.  
  442.     /* copy it */
  443.     sprintf(temp,"%s",history);
  444.  
  445.     /* 
  446.        now run through breaking it up, setting pointers and return the number 
  447.        of lines we found.
  448.     */
  449.     for (count=0;count<HIST_LINES;count++){
  450.         if ((tmp=hist[count]=rindex(temp,'\n'))==NULL){
  451.             break;
  452.         }
  453.         *tmp='\0';
  454.         hist[count]++;
  455.     }
  456.     if (count<HIST_LINES){
  457.         hist[count++]=temp;
  458.     }
  459.     return(count);
  460. }
  461.  
  462. nth_hist(n)
  463. int n;
  464. {
  465.     /*
  466.        get the nth last filename from the list. make use (of course) of
  467.        read_hist and split_hist.
  468.     */
  469.     register count;
  470.     register i;
  471.  
  472.     read_hist();
  473.     count=split_hist();
  474.     if (n>count-1){
  475.         if (count>1){
  476.             fprintf(stderr,"Only %d history items exist.\n",count);
  477.         }
  478.         else{
  479.             fprintf(stderr,"Only one history item exists.\n");
  480.         }
  481.         exit(1);
  482.     }
  483.     sprintf(arg,"%s",hist[n]);
  484.  
  485.     /* rebuild the history with the selected name at the bottom */
  486.     reconstruct(n,count);
  487. }
  488.  
  489. ask_hist()
  490. {
  491.     /*
  492.        ask the outside world which of the files in the history is wanted.
  493.        set the terminal to cbreak.
  494.     */
  495.     register i;
  496.     register count;
  497.     char *last;
  498.     register option;
  499.     struct sgttyb blk;
  500.  
  501.     /* read and split the history file */
  502.     read_hist();
  503.     count=split_hist();
  504.  
  505.     /* print the history */
  506.     for (i=0;i<count;i++){
  507.         fprintf(stderr,"\t[%d]: %s\n",count-i,hist[count-i-1]);
  508.     }
  509.  
  510.     /* give them a prompt (of sorts) */
  511.     fprintf(stderr,"select -> ");
  512.  
  513.     /* set the terminal up */
  514.     set_term();
  515.  
  516.     /* get their response */
  517.     option=getc(stdin);
  518.  
  519.     /* make the terminal 'safe' again */
  520.     unset_term();
  521.  
  522.     /* 
  523.        process the option and put the appropriate file name into the 
  524.        arg variable.
  525.     */
  526.     if (option=='\n'){
  527.         /* they want the last file of the list */
  528.         fprintf(stderr,"%s\n",hist[0]);
  529.         sprintf(arg,"%s",hist[0]);
  530.         return;
  531.     }
  532.     else if (option==RUBOUT){
  533.         /* they want to leave */
  534.         fprintf(stderr,"\n");
  535.         exit(1);
  536.     }
  537.     else if (option>='1'&&option<='0'+count){
  538.         /* they have requested a file by it's number */
  539.         option=option-'0';
  540.         fprintf(stderr,"%s\n",hist[option-1]);
  541.         sprintf(arg,"%s",hist[option-1]);
  542.     }
  543.     else{
  544.         /* 
  545.            looks like they want to name a specific file. echo the 
  546.            characters back to the screen.
  547.         */
  548.         fprintf(stderr,"%c",option);
  549.         arg[0]=option;
  550.         i=1;
  551.         while ((arg[i]=getc(stdin))!='\n'){
  552.             i++;
  553.         }
  554.         arg[i]='\0';
  555.         option=count-1;        /* a kludge for the history re-make to follow */
  556.  
  557.         /* seeing as they typed in the name, try and help with spelling */
  558.         spell_help();
  559.     }
  560.  
  561.     /* rebuild the history with the selected name at the bottom */
  562.     reconstruct(option-1,count);
  563. }
  564.  
  565. FILE *
  566. get_temp()
  567. {
  568.     /* get ourselves a temporary file for the reconstructed history */
  569.     FILE *fp,*fopen();
  570.  
  571.     mktemp(tmp_file);
  572.     if ((fp=fopen(tmp_file,"w"))==NULL){
  573.         perror(tmp_file);
  574.         exit(1);
  575.     }
  576.     return(fp);
  577. }
  578.  
  579. close_temp(fp)
  580. FILE *fp;
  581. {
  582.     /* move the temporary file to be the new history */
  583.     FILE *fclose();
  584.  
  585.     if (fclose(fp)==(FILE *)EOF){
  586.         fprintf(stderr,"Could not close %s\n",tmp_file);
  587.         exit(1);
  588.     }
  589.  
  590.     if (rename(tmp_file,HIST)!=0){
  591.         perror("rename");
  592.         exit(1);
  593.     }
  594. }
  595.  
  596. set_term()
  597. {
  598.     /* go into cbreak and no echo mode */
  599.     struct sgttyb    blk;
  600.  
  601.     gtty(0, &blk);
  602.     blk.sg_flags |= CBREAK;
  603.     blk.sg_flags ^= ECHO;
  604.     stty(0, &blk);
  605. }
  606.  
  607. unset_term()
  608. {
  609.     /* get out of cbreak and no echo */
  610.     struct sgttyb    blk;
  611.  
  612.     gtty(0, &blk);
  613.     blk.sg_flags &= ~CBREAK;
  614.     blk.sg_flags ^= ECHO;
  615.     stty(0, &blk);
  616. }
  617.  
  618. match(argument,pattern)
  619. char    *argument;
  620. char    *pattern;
  621. {
  622.     /*
  623.        boneheaded but easy pattern matcher. just see if the 'pattern'
  624.        exists anywhere in the 'argument'. boyer-moore who?
  625.     */
  626.     register length=strlen(pattern);
  627.  
  628.     while (strlen(argument)>=length){
  629.         if (!strncmp(argument++,pattern,length)){
  630.             return(1);
  631.         }
  632.     }
  633.     return(0);
  634. }
  635.  
  636. find_match(pattern)
  637. char *pattern;
  638. {
  639.     /*
  640.        find the name in the history list that contains the 'pattern'.
  641.        if it exists then put it into the 'arg' variable and otherwise
  642.        announce that a match couldn't be found and leave.
  643.     */
  644.     register count;
  645.     register i;
  646.  
  647.     /* read and split the history file */
  648.     read_hist();
  649.     count=split_hist();
  650.  
  651.     /* try for a match with each file in turn (note that we are working
  652.        from most-recently-used backwards - probably a good thing)
  653.     */
  654.     for (i=0;i<count;i++){
  655.         if (match(hist[i],pattern)){
  656.             sprintf(arg,"%s",hist[i]);
  657.             reconstruct(i,count);
  658.             return;
  659.         }
  660.     }
  661.  
  662.     /* we couldn't match so get out of here */
  663.     fprintf(stderr,"Unable to match with \"%s\"\n",pattern);
  664.     exit(1);
  665. }
  666.  
  667. insert_command(command)
  668. char *command;
  669. {
  670.     /*
  671.        they want the last file in the history but want to preceed it
  672.        this time with a command - no problems here.
  673.     */
  674.     register count;
  675.     char *place;
  676.  
  677.     /* read and split the history */
  678.     read_hist();
  679.     count=split_hist();
  680.     
  681.     /* 
  682.        if there was already a command there (indicated by a '+') then we
  683.        want to get rid of it. if there is a '+' but no ' ' after it then
  684.        the history file is in disarray and we will not try to recover
  685.     */
  686.     if (*hist[0]=='+'){
  687.         if ((place=index(hist[0],' '))==NULL){
  688.             fprintf("Serious weirdenss in insert_command\n");
  689.             exit(1);
  690.         }
  691.         /* move over white space - if there is any */
  692.         while (*place==' '||*place=='\t'){
  693.             place++;
  694.         }
  695.     }
  696.     else{
  697.         /* there was no command preceeding the last file in the history */
  698.         place=hist[0];
  699.     }
  700.  
  701.     /* put the new command and the filename into 'arg' */
  702.     sprintf(arg,"%s %s",command,place);
  703.  
  704.     /* rebuild the history with the selected command and name at the bottom */
  705.     reconstruct(0,count);
  706. }
  707.  
  708. reconstruct(except,count)
  709. int    except;
  710. int    count;
  711. {
  712.     /* 
  713.        reconstruct history file excepting the 'except' last.
  714.        so just copy all lines but the 'except'th last and then put in 'arg'
  715.        which contains the new line for the history
  716.     */
  717.     register i;
  718.     FILE *tv,*get_temp();
  719.  
  720.     /* get a temporary file */
  721.     tv=get_temp();
  722.  
  723.     /* put in the line we still want */
  724.     for (i=count-1;i>=0;i--){
  725.         if (i!=except){
  726.             fprintf(tv,"%s\n",hist[i]);
  727.         }
  728.     }
  729.  
  730.     /* put in the new line from 'arg' */
  731.     fprintf(tv,"%s\n",arg);
  732.  
  733.     /* rename the temporary to be the new history file */
  734.     close_temp(tv);
  735. }
  736.  
  737. normal(string)
  738. char *string;
  739. {
  740.     /* 
  741.        a normal filename was found, put it into arg. first of all if there
  742.        is a history and the file is already in it (which means they could
  743.        have gotten to this file in other ways), then reconstruct the history
  744.        as though they had. also offer spelling help.
  745.     */
  746.     register count;
  747.     register i;
  748.  
  749.     /* put it into 'arg' */
  750.     sprintf(arg,"%s",string);
  751.  
  752.     /* if there is a history file */
  753.     if (got_vi()){
  754.  
  755.         /* read it and split it up */
  756.         read_hist();
  757.         count=split_hist();
  758.  
  759.         /* if it is in the history then reconstruct and return */
  760.         for (i=0;i<count;i++){
  761.             if (!strcmp(hist[i],arg)){
  762.                 reconstruct(i,count);
  763.                 return;
  764.             }
  765.         }
  766.  
  767.         /* it's not in the history, help with spelling then reconstruct */
  768.         spell_help();
  769.         reconstruct(HIST_LINES,count);
  770.     }
  771.     else{
  772.  
  773.         /* 
  774.            there is no history around so help with spelling and set up a 
  775.            history for next time.
  776.         */
  777.         spell_help();
  778.         new_vi();
  779.     }
  780.  
  781. }
  782.  
  783. multiple(number, args)
  784. int number;
  785. char **args;
  786. {
  787.     /*
  788.        there were several names on the command line so we just strcat them
  789.        into the 'arg' array.
  790.     */
  791.     register count;
  792.     register i;
  793.  
  794.     *arg='\0';
  795.     while (--number){
  796.         strcat(arg,*++args);
  797.         if (number>1){
  798.             strcat(arg," ");
  799.         }
  800.     }
  801.  
  802.     /*
  803.        now if there is a history file and we can find an identical line
  804.        then reconstruct with that line at the bottom.
  805.     */
  806.     if (got_vi()){
  807.         read_hist();
  808.         count=split_hist();
  809.         for (i=0;i<count;i++){
  810.             if (!strcmp(hist[i],arg)){
  811.                 reconstruct(i,count);
  812.                 return;
  813.             }
  814.         }
  815.         /* 
  816.            rebuild, including everything but the counth last (i.e. make
  817.            a new history by omitting the oldest file in the current one and
  818.            putting 'arg' on the end.
  819.         */
  820.         reconstruct(HIST_LINES,count);
  821.     }
  822.     else{
  823.         /* there was no history file so try to give them one for next time */
  824.         new_vi();
  825.     }
  826. }
  827.  
  828. got_vi()
  829. {
  830.     /* indicate if there is a history file that they own or otherwise */
  831.     struct stat buf;
  832.  
  833.     if (stat(HIST,&buf)==-1){
  834.         return(0);
  835.     }
  836.     else{
  837.         return(getuid()==buf.st_uid);
  838.     }
  839. }
  840.  
  841. new_vi()
  842. {
  843.     /* attempt to make a new history file - several things could go wrong */
  844.     FILE *vh,*fopen(),*fclose();
  845.     struct stat buf;
  846.  
  847.     /* if you can't read the current directory, get out */
  848.     if (stat(".",&buf)==-1){
  849.         perror("stat");
  850.         exit(1);
  851.     }
  852.  
  853.     /* if you own the directory (you can't normally get a history in /tmp) */
  854.     if (getuid()==buf.st_uid){
  855.  
  856.         /* if we can't make a history, get out */
  857.         if ((vh=fopen(HIST,"w"))==NULL){
  858.             perror(HIST);
  859.             exit(1);
  860.         }
  861.  
  862.         /* put in the 'arg' that we will be vi'ing in a second */
  863.         fprintf(vh,"%s\n",arg);
  864.  
  865.         /* close the history */
  866.         if (fclose(vh)==(FILE *)EOF){
  867.             fprintf(stderr,"Could not close %s\n",VI);
  868.             exit(1);
  869.         }
  870.  
  871.         /* give the history some protection - for those who want it! */
  872.         if (chmod(HIST,IREAD|IWRITE)==-1){
  873.             perror("chmod");
  874.             exit(1);
  875.         }
  876.     }
  877. }
  878.  
  879. clean_up()
  880. {
  881.     /* just get out after making sure things are tidy */
  882.     fprintf(stderr,"Interrupt.\n");
  883.     unlink(tmp_file);
  884.     unset_term();
  885.     exit(1);
  886. }
  887.  
  888. spell_help()
  889. {
  890.     /*
  891.        unashamedly stolen (and modified) from "The UNIX Programming
  892.        Environment" - Kernighan and Pike
  893.  
  894.        read the directory and if the file they want (in 'arg') does not
  895.        exist then see if there is one that does that has similar spelling
  896.        to what they requested. offer the change and handle the reply.
  897.     */
  898.  
  899.     char new[ARG_CHARS];
  900.     register dist=3;
  901.     register new_dist;
  902.     DIR *dp, *opendir();
  903.     struct direct *readdir();
  904.     struct direct *entry;
  905.     register len;
  906.     struct stat buf;
  907.  
  908.     /* if the file already exists just return - they don't need help */
  909.     if (stat(arg,&buf)==0){
  910.         return;
  911.     }
  912.  
  913.     /* if the current directory can't be read then return */
  914.     if ((dp=opendir("."))==NULL){
  915.         return;
  916.     }
  917.     
  918.     /* get the length of what we are seeking to cut down on strcmping time */
  919.     len=strlen(arg);
  920.  
  921.     for (entry=readdir(dp);entry!=NULL;entry=readdir(dp)){
  922.  
  923.         /* if this entry has length = sought length +/- 1 then it may be ok */
  924.         if (entry->d_ino&&entry->d_namlen>=len-1&&entry->d_namlen<=len+1){
  925.  
  926.             /* get the 'distance' between what we want and this file name */
  927.             new_dist=sp_dist(entry->d_name,arg);
  928.  
  929.             /* if this name is close enough and better than our current best */
  930.             if (new_dist<=dist&&new_dist!=3){
  931.  
  932.                 if (!new_dist){
  933.                     /* if the dist is 0 then they are identical */
  934.                     closedir(dp);
  935.                     return;
  936.                 }
  937.                 /* remember the new name and distance */
  938.                 strcpy(new,entry->d_name);
  939.                 dist=new_dist;
  940.             }
  941.         }
  942.     }
  943.  
  944.     /* close up. if we got no suitable result then simply return */
  945.     closedir(dp);
  946.     if (dist==3){
  947.         return;
  948.     }
  949.  
  950.     /* offer them "new" */
  951.     set_term();
  952.     fprintf(stderr,"correct to %s [y]? ",new);
  953.  
  954.     /* process the reply */
  955.     switch (getc(stdin)){
  956.         case 'N':
  957.         case 'n':
  958.             fprintf(stderr,"no\n");
  959.             break;
  960.         case 'q':
  961.         case 'Q':
  962.             fprintf(stderr,"quit\n");
  963.             unset_term();
  964.             exit(0);
  965.             break;
  966.         default :
  967.             fprintf(stderr,"yes\n");
  968.             strcpy(arg,new);
  969.     }
  970.     unset_term();
  971. }
  972.  
  973. sp_dist(s,t)
  974. char *s;
  975. char *t;
  976. {
  977.     /* 
  978.        stolen from the same place as spell_help() above.
  979.  
  980.        work out the distance between the strings 's' and 't' according
  981.        to the rough metric that
  982.        
  983.        identical = 0
  984.        interchanged characters = 1
  985.        wrong character/extra character/missing character = 2
  986.        forget it = 3
  987.     */
  988.  
  989.     while (*s++==*t){
  990.         if (*t++=='\0'){
  991.             /* identical */
  992.             return(0);
  993.         }
  994.     }
  995.  
  996.     if (*--s){
  997.         if (*t){
  998.             if (s[1]&&t[1]&&*s==t[1]&&*t==s[1]&&!strcmp(s+2,t+2)){
  999.                 /* interchanged chars */
  1000.                 return(1);
  1001.             }
  1002.             if (!strcmp(s+1,t+1)){
  1003.                 /* wrong char */
  1004.                 return(2);
  1005.             }
  1006.         }
  1007.         if (!strcmp(s+1,t)){
  1008.             /* extra char in 't' */
  1009.             return(2);
  1010.         }
  1011.     }
  1012.     if (!strcmp(s,t+1)){
  1013.         /* extra char in 's' */
  1014.         return(2);
  1015.     }
  1016.  
  1017.     /* forget it */
  1018.     return(3);
  1019. }
  1020. @//E*O*F e.c//
  1021. chmod u=rw,g=rx,o=rx e.c
  1022.  
  1023. echo Inspecting for damage in transit...
  1024. temp=/tmp/shar$$; dtemp=/tmp/.shar$$
  1025. trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
  1026. cat > $temp <<\!!!
  1027.       45     262    1867 README
  1028.       10      23     141 Makefile
  1029.        4       4      27 MANIFEST
  1030.       84     627    3415 edoc
  1031.      831    2311   16691 e.c
  1032.      974    3227   22141 total
  1033. !!!
  1034. wc  README Makefile MANIFEST edoc e.c | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
  1035. if [ -s $dtemp ]
  1036. then echo "Ouch [diff of wc output]:" ; cat $dtemp
  1037. else echo "No problems found."
  1038. fi
  1039. exit 0
  1040.  
  1041. ZZ
  1042.