home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume13 / e / e.c < prev    next >
Encoding:
C/C++ Source or Header  |  1988-01-31  |  34.3 KB  |  1,430 lines

  1.  
  2. /******************************************************************************
  3.  #                                                                            #
  4.  #                                                                            #
  5.  # e - Command line preprocessor for vi.  Version 1.2 - November 1987.        #
  6.  # ======================================================================     #
  7.  #                                                                            #
  8.  #                                                                            #
  9.  # Terry Jones                                                                #
  10.  # Department of Computer Science                                             #
  11.  # University of Waterloo                                                     #
  12.  # Waterloo, Ontario, Canada. N2L 3G1                                         #
  13.  #                                                                            #
  14.  # {ihnp4,allegra,decvax,utzoo,utcsri,clyde}!watmath!watdragon!tcjones        #
  15.  # tcjones@dragon.waterloo.{cdn,edu} tcjones@WATER.bitnet                     #
  16.  # tcjones%watdragon@waterloo.csnet                                           #
  17.  #                                                                            #
  18.  #*****************************************************************************/
  19.  
  20.  
  21. #include "e.h"
  22.  
  23.  
  24. /*
  25.  * Globals. All in the name of ease and perhaps speed.
  26.  *
  27.  */
  28.  
  29. char history[HIST_CHARS];
  30. char arg[ARG_CHARS];
  31. char *hist[HIST_LINES];
  32. char temp[HIST_CHARS];
  33. char *tmp_file=".e_tmpXXXXXX";
  34. char erase;
  35. char *myname;
  36.  
  37.  
  38. clean_up()
  39. {
  40.     /* 
  41.      * This is where we come when an interrupt is received.
  42.      * Just get out after making sure things are tidy.
  43.      *
  44.      */
  45.  
  46.     e_error("\n%s","Interrupt.");
  47. }
  48.  
  49.  
  50.  
  51. main(c,v)
  52. int c;
  53. char **v;
  54. {
  55.     myname=v[0];
  56.     terminal(TERM_RECORD);
  57.  
  58.     /* handle SIGINT */
  59.     if (signal(SIGINT,SIG_IGN)!=SIG_IGN){
  60.         signal(SIGINT,clean_up);
  61.     }
  62.  
  63.  
  64.  
  65.     /*
  66.      * Process the command line. This gets a little messy as there are so
  67.      * many ways e can be invoked. They are listed below and there is an
  68.      * example provided in each case statement to illustrate the 
  69.      * particular case we are trying to handle.
  70.      *
  71.      * The idea in most cases is to get the arguments that will be passed
  72.      * to vi into a character array (arg), and pass it to do_vi(). do_vi()
  73.      * splits up the arguments and execs vi. Occasionally it is simpler and
  74.      * do_vi() can be called as do_vi("fred").
  75.      *
  76.      *
  77.      * Command Line Options.
  78.      * =====================
  79.      *
  80.      * No arguments.
  81.      *
  82.      * (1) "e"
  83.      *
  84.      * One argument.
  85.      *
  86.      * (3) "e -"
  87.      * (2) "e -n"                   Where n is some valid history item.
  88.      * (4) "e -t"                   If they do this then they are in error!
  89.      * (5) "e -r"
  90.      * (6) "e -pat"
  91.      * (7) "e +100"
  92.      * (8) "e ."
  93.      * (9) "e fred"
  94.      *
  95.      * Multiple arguments.
  96.      *
  97.      * (10) "e fred harry joe"      Also handles "e -t tag", "e -r file" etc.
  98.      *
  99.      */
  100.  
  101.  
  102.     switch (c){
  103.         case 1: {
  104.  
  105.             /* 
  106.              * Command line option (1).
  107.              * Example: "e"
  108.              *
  109.              * Just go and vi the last file that was e'ed.
  110.              *
  111.              */
  112.  
  113.             last_file();
  114.             do_vi(arg);
  115.             break;
  116.         }
  117.         
  118.         case 2:{
  119.             switch ((*++v)[0]){
  120.  
  121.                 case '-':{
  122.  
  123.                     if ((c=(*v)[1])=='\0'){
  124.  
  125.                         /* 
  126.                          * Command line option (2).
  127.                          * Example: "e -"
  128.                          *
  129.                          * This is a select from history, ask what they want.
  130.                          *
  131.                          */
  132.  
  133.                         ask_hist();
  134.                         do_vi(arg);
  135.                     }
  136.                     else if (isdigit(c)){
  137.  
  138.                         /* 
  139.                          * Command line option (3).
  140.                          * Example: "e -3"
  141.                          *
  142.                          * Get the nth last file from the history and vi it.
  143.                          *
  144.                          */
  145.  
  146.                         nth_hist(c-'0');
  147.                         do_vi(arg);
  148.                     }
  149.                     else if (c=='t'&&(*v)[2]=='\0'){
  150.  
  151.                         /* 
  152.                          * Command line option (4).
  153.                          * Example: "e -t"
  154.                          *
  155.                          * This is an empty tag - ignore it.
  156.                          * They have made a mistake, but let vi tell them.
  157.                          *
  158.                          */ 
  159.  
  160.                         do_vi(*v);
  161.                     }
  162.                     else if (c=='r'&&(*v)[2]=='\0'){
  163.  
  164.                         /* 
  165.                          * Command line option (5).
  166.                          * Example: "e -r"
  167.                          *
  168.                          * A recover, just pass it to vi and don't interfere.
  169.                          *
  170.                          */
  171.  
  172.                         do_vi(*v);
  173.                     }
  174.                     else{
  175.  
  176.                         /* 
  177.                          * Command line option (6).
  178.                          * Example: "e -pat"
  179.                          *
  180.                          * This is a pattern - try to match it.
  181.                          *
  182.                          */
  183.  
  184.                         find_match(++*v);
  185.                         do_vi(arg);
  186.                     }
  187.                     break;
  188.                 }
  189.  
  190.                 case '+':{
  191.  
  192.                     /* 
  193.                      * Command line option (7).
  194.                      * Example: "e +100"
  195.                      *
  196.                      * A command, put it before the last file name.
  197.                      *
  198.                      */
  199.  
  200.                     insert_command(*v);
  201.                     do_vi(arg);
  202.                     break;
  203.                 }
  204.  
  205.                 case '.':{
  206.  
  207.                     /* 
  208.                      * Command line option (8).
  209.                      * Example: "e ."
  210.                      * Example: "e .login"  (falls through to option (9)).
  211.                      *
  212.                      * Just give a history list if there is only a dot.
  213.                      * Otherwise fall through as it must be a filename.
  214.                      *
  215.                      */
  216.  
  217.                     if ((*v)[1]=='\0'){
  218.                         register ct;
  219.                         register i;
  220.  
  221.                         read_hist();
  222.                         ct=split_hist();
  223.  
  224.                         for (i=0;i<ct;i++){
  225.                             fprintf(stderr,"\t[%d]: %s\n",ct-i-1,hist[ct-i-1]);
  226.                         }
  227.                         exit(0);
  228.                     }
  229.                     /* 
  230.                      * WARNING!
  231.                      * The switch falls through in the case where there is a
  232.                      * filename that starts with a period.
  233.                      *
  234.                      */
  235.                 }
  236.  
  237.                 default :{
  238.  
  239.                     /* 
  240.                      * Command line option (9).
  241.                      * Example: "e fred"
  242.                      * Example: "e .login"  (fell through from option (8)).
  243.                      *
  244.                      * Looks like it's just a plain old file name. vi it!
  245.                      *
  246.                      */
  247.  
  248.                     normal(*v);
  249.                     do_vi(arg);
  250.                     break;
  251.                 }
  252.             }
  253.         }
  254.  
  255.         default:{
  256.  
  257.             /* 
  258.              * Command line option (10).
  259.              * Example: "e fred harry joe"
  260.              *
  261.              * A bunch of arguments, fix the history & vi them all as normal.
  262.              *
  263.              */
  264.  
  265.             multiple(c,v,ARG_CHARS);
  266.             do_vi(arg);
  267.             break;
  268.         }
  269.     }
  270. }
  271.  
  272.  
  273.  
  274. do_vi(thing)
  275. char *thing;
  276. {
  277.     /* 
  278.      * Split the arguments (if any) in 'thing' up and exec vi on them.
  279.      * The arguments must be space separated.
  280.      *
  281.      */
  282.     char *args[MAX_ARGS];
  283.     char *this,*next;
  284.     register i;
  285.  
  286.     args[0]="vi";
  287.     args[1]=thing;
  288.  
  289.     i=1;
  290.     while (*thing!='\0'&&(thing=index(thing,' '))!=NULL){
  291.         *thing++='\0';
  292.         if (*thing!='\0'){
  293.             args[++i]=thing;
  294.         }
  295.     }
  296.     args[++i]=NULL;
  297.         
  298.     if (execvp(VI,args)==-1){
  299.         e_error("%s %s","Could not execvp",VI);
  300.     }
  301. }
  302.  
  303.  
  304.  
  305. read_hist()
  306. {
  307.     /*
  308.        Read the history file and break it up into lines in the global variable
  309.        'history'. Do the appropriate checks to see that it exists.
  310.     */
  311.  
  312.     register vh;
  313.     register bytes;
  314.     register offset;
  315.     struct stat buf;
  316.  
  317.     /* 
  318.      * If there is no history file then say so and get out of here - they 
  319.      * had no business asking for access to the history.
  320.      *
  321.      */
  322.  
  323.     if ((vh=open(HIST,O_RDONLY))==-1){
  324.         e_error("%s %s","Could not open",HIST);
  325.     }
  326.  
  327.     /* Stat it */
  328.     if (fstat(vh,&buf)==-1){
  329.         e_error("%s %s","Could not stat",HIST);
  330.     }
  331.  
  332.     /* 
  333.      * Set 'offset' so that we can read the last portion of the history
  334.      * file only. If there are less than HIST_CHARS characters in the
  335.      * file then we will start reading at 0, otherwise at HIST_CHARS
  336.      * characters before the end of the file.
  337.      *
  338.      */
  339.  
  340.     offset=(int)buf.st_size-HIST_CHARS<0 ? 0 : buf.st_size-HIST_CHARS;
  341.  
  342.     /* Move (if it's non zero) to that place in the file. */
  343.  
  344.     if (offset&&lseek(vh,(long)offset,L_SET)==-1){
  345.         e_error("%s %s","Could not lseek in",HIST);
  346.     }
  347.  
  348.     /* And read. */
  349.     if ((bytes=read(vh,history,HIST_CHARS))==-1){
  350.         e_error("%s %s","read: Could not read",HIST);
  351.     }
  352.  
  353.     /* If we didn't come up with ANYTHING we may as well leave. */
  354.     if (!bytes){
  355.         e_error("%s %s %s","Empty",HIST,"file.");
  356.     }
  357.  
  358.     /* Zap the newline (which *should* be there) for now. */
  359.     if (history[--bytes]=='\n'){
  360.         history[bytes]='\0';    
  361.     }
  362.  
  363.     /* And get out of here. */
  364.     return(bytes);
  365. }
  366.  
  367.  
  368.  
  369. last_file()
  370. {
  371.     /*
  372.      * Get the last name from the 'history' array and put it into 'arg'.
  373.      *
  374.      */
  375.  
  376.     read_hist();
  377.     if (index(history,'\n')==NULL){
  378.         if (*history=='\0'){
  379.             e_error("%s %s",HIST,"has a line with no newline character.");
  380.         }
  381.         else{
  382.             sprintf(arg,"%s",history);
  383.         }
  384.     }
  385.     else{
  386.         sprintf(arg,"%s",rindex(history,'\n')+1);
  387.     }
  388. }
  389.  
  390.  
  391.  
  392. split_hist()
  393. {
  394.     /*
  395.      * Set the array of pointers in 'hist' to point to the succesive names
  396.      * in the 'history' array. These are delimited (presumably) by newlines
  397.      * and so they're easy to catch.
  398.  
  399.      * What in fact is done is that the history array is copied and we set
  400.      * a pointer (in "hist") to the start of each new item and set the 
  401.      * newline characters to be NULLs. This way we don't mess up the history 
  402.      * array as we will want it intact later on (maybe).
  403.      *
  404.      */
  405.  
  406.     char *tmp;
  407.     register count;
  408.  
  409.     /* Copy it. */
  410.     sprintf(temp,"%s",history);
  411.  
  412.     /* 
  413.      * Now run through breaking it up, setting pointers and return the number 
  414.      * of lines we found.
  415.      *
  416.      */
  417.  
  418.     for (count=0;count<HIST_LINES;count++){
  419.         if ((tmp=hist[count]=rindex(temp,'\n'))==NULL){
  420.             break;
  421.         }
  422.         *tmp='\0';
  423.         hist[count]++;
  424.     }
  425.     if (count<HIST_LINES){
  426.         hist[count++]=temp;
  427.     }
  428.     return(count);
  429. }
  430.  
  431.  
  432.  
  433. nth_hist(n)
  434. int n;
  435. {
  436.     /*
  437.      * Get the nth last filename from the list. Make use (of course) of
  438.      * read_hist and split_hist.
  439.      *
  440.      */
  441.  
  442.     register count;
  443.     register i;
  444.  
  445.     read_hist();
  446.     count=split_hist();
  447.     if (n>count-1){
  448.         if (count>1){
  449.             e_error("%s %d %s","Only",count,"history items exist.");
  450.         }
  451.         else{
  452.             e_error("%s","Only one history item exists.");
  453.         }
  454.     }
  455.     sprintf(arg,"%s",hist[n]);
  456.  
  457.     /* Rebuild the history with the selected name at the bottom. */
  458.  
  459.     reconstruct(n,count);
  460. }
  461.  
  462.  
  463.  
  464. ask_hist()
  465. {
  466.     /*
  467.      * Ask the outside world which of the files in the history is wanted.
  468.      * set the terminal to cbreak.
  469.      *
  470.      */
  471.  
  472.     register i;
  473.     register count;
  474.     char *last;
  475.     register option;
  476.     struct sgttyb blk;
  477.  
  478.     /* Read and split the history file. */
  479.     read_hist();
  480.     count=split_hist();
  481.  
  482.     /* Print the history. */
  483.     for (i=0;i<count;i++){
  484.         fprintf(stderr,"\t[%d]: %s\n",count-i-1,hist[count-i-1]);
  485.     }
  486.  
  487.     /* Give them a prompt (of sorts). */
  488.     fprintf(stderr,"select -> ");
  489.  
  490.     /* Set the terminal up. */
  491.     terminal(TERM_SET);
  492.  
  493.     /* Get their response. */
  494.     option=getc(stdin);
  495.  
  496.     /* Make the terminal 'safe' again. */
  497.     terminal(TERM_RESET);
  498.  
  499.     /* 
  500.      * Process the option and put the appropriate file name into the 
  501.      * arg variable.
  502.      *
  503.      */
  504.  
  505.     if (option=='\n'){
  506.         /* They want the last file of the list. */
  507.         fprintf(stderr,"%s\n",hist[0]);
  508.         sprintf(arg,"%s",hist[0]);
  509.         return;
  510.     }
  511.     else if (option==(int)erase){
  512.         /* They want to leave. */
  513.         fprintf(stderr,"\n");
  514.         exit(1);
  515.     }
  516.     else if (option>='0'&&option<='0'+count-1){
  517.         /* They have requested a file by its number. */
  518.         option=option-'0';
  519.         fprintf(stderr,"%s\n",hist[option]);
  520.         sprintf(arg,"%s",hist[option]);
  521.         reconstruct(option,count);
  522.         return;
  523.     }
  524.     else{
  525.         /* 
  526.          * Looks like they want to name a specific file. Echo the 
  527.          * character back to the screen.
  528.          *
  529.          */
  530.  
  531.         fprintf(stderr,"%c",option);
  532.         arg[0]=option;
  533.         i=1;
  534.         while ((arg[i]=getc(stdin))!='\n'){
  535.             i++;
  536.         }
  537.         arg[i]='\0';
  538.  
  539.         /* Seeing as they typed in the name, try and help with spelling. */
  540.         if (!spell_help()){
  541.             find();
  542.         }
  543.  
  544.         /* If it is in the history then reconstruct and return. */
  545.         for (i=0;i<count;i++){
  546.             if (!strcmp(hist[i],arg)){
  547.                 reconstruct(i,count);
  548.                 return;
  549.             }
  550.         }
  551.  
  552.         /* Otherwise reconstruct, leaving out the oldest name. */
  553.         reconstruct(count-1,count);
  554.     }
  555. }
  556.  
  557.  
  558.  
  559. FILE *
  560. get_temp()
  561. {
  562.     /* Get ourselves a temporary file for the reconstructed history. */
  563.     FILE *fp,*fopen();
  564.  
  565.     mktemp(tmp_file);
  566.     if ((fp=fopen(tmp_file,"w"))==NULL){
  567.         e_error("%s %s","Could not open temporary file",tmp_file);
  568.     }
  569.     return(fp);
  570. }
  571.  
  572.  
  573.  
  574. close_temp(fp)
  575. FILE *fp;
  576. {
  577.     /* Move the temporary file to be the new history. */
  578.     FILE *fclose();
  579.  
  580.     if (fclose(fp)==(FILE *)EOF){
  581.         e_error("%s %s","Could not close",tmp_file);
  582.     }
  583.  
  584.     if (rename(tmp_file,HIST)!=0){
  585.         e_error("%s %s %s %s","Could not rename",tmp_file,"to",HIST);
  586.     }
  587. }
  588.  
  589.  
  590.  
  591. terminal(what)
  592. int what;
  593. {
  594.     /*
  595.      * Handles the terminal. Must be first called as 
  596.      *
  597.      * terminal(TERM_RECORD)
  598.      *
  599.      * which remembers the initial terminal charcteristics and sets up the
  600.      * "erase" variable. Thereafter can be called as
  601.      *
  602.      * terminal(TERM_SET)  --  to turn on CBREAK and ECHO off.
  603.      * terminal(TERM_RESET)  --  to set the terminal to its original state.
  604.      *
  605.      */
  606.  
  607.      register i;
  608.  
  609.  
  610. #ifdef sysV
  611.     static struct termio initial_blk;
  612.     static struct termio set_blk;
  613. #else
  614.     static struct sgttyb initial_blk;
  615.     static struct sgttyb set_blk;
  616. #endif sysV
  617.  
  618.  
  619.  
  620.     switch(what){
  621.  
  622.         case TERM_RECORD:{
  623.  
  624. #ifdef sysV
  625.             if (ioctl(0, TCGETA, &initial_blk)==-1){
  626.                 e_error("%s","Could not ioctl stdin.");
  627.             }
  628.  
  629. #ifdef STRUCT_ASST
  630.             /* Copy the structure in one hit. */
  631.             set_blk=initial_blk;
  632. #else
  633.             /* Copy the structure field by field. */
  634.             set_blk.c_iflag=initial_blk.c_iflag;
  635.             set_blk.c_oflag=initial_blk.c_oflag;
  636.             set_blk.c_cflag=initial_blk.c_cflag;
  637.             set_blk.c_line=initial_blk.c_line;
  638.  
  639.             for (i=0;i<NCC;i++){
  640.                 set_blk.c_cc[i]=initial_blk.c_cc[i];
  641.             }
  642. #endif STRUCT_ASST
  643.  
  644.             /* And now set up the set_blk. */
  645.             set_blk.c_lflag=(initial_blk.c_lflag &= ~(ICANON|ECHO|ECHONL));
  646.             erase=set_blk.c_cc[VERASE];
  647.             set_blk.c_cc[VMIN]=1;
  648.             set_blk.c_cc[VTIME]=0;
  649. #else
  650.             if (ioctl(0, TIOCGETP, &initial_blk)==-1){
  651.                 e_error("%s","Could not ioctl stdin.");
  652.             }
  653.  
  654. #ifdef STRUCT_ASST
  655.             /* Copy the structure in one hit. */
  656.             set_blk=initial_blk;
  657. #else
  658.             /* Copy the structure field by field. */
  659.             set_blk.sg_ispeed=initial_blk.sg_ispeed;
  660.             set_blk.sg_ospeed = initial_blk.sg_ospeed;
  661.             set_blk.sg_erase = initial_blk.sg_erase;
  662.             set_blk.sg_kill = initial_blk.sg_kill;
  663.             set_blk.sg_flags = initial_blk.sg_flags;
  664. #endif STRUCT_ASST
  665.  
  666.  
  667.             /* And now set up the set_blk. */
  668.             erase = set_blk.sg_erase;
  669.  
  670.             /* Go into CBREAK mode or stay that way if we are already. */
  671.             set_blk.sg_flags |= CBREAK;
  672.  
  673.             /* Turn off echo. */
  674.             set_blk.sg_flags &= ~ECHO;
  675.  
  676. #endif sysV
  677.  
  678.             break;
  679.         }
  680.  
  681.  
  682.  
  683.         case TERM_SET:{
  684.  
  685. #ifdef sysV
  686.             if (ioctl(0, TCSETA, &set_blk)==-1){
  687.                 e_error("%s","Could not ioctl stdin");
  688.             }
  689. #else
  690.             if (ioctl(0, TIOCSETP, &set_blk)==-1){
  691.                 e_error("%s","Could not ioctl stdin");
  692.             };
  693. #endif sysV
  694.  
  695.             break;
  696.         }
  697.  
  698.  
  699.  
  700.  
  701.  
  702.         case TERM_RESET:{
  703.  
  704. #ifdef sysV
  705.             if (ioctl(0, TCSETA, &initial_blk)==-1){
  706.                 e_error("%s","Could not ioctl stdin");
  707.             }
  708. #else
  709.             if (ioctl(0, TIOCSETP, &initial_blk)==-1){
  710.                 e_error("%s","Could not ioctl stdin");
  711.             }
  712. #endif sysV
  713.  
  714.             break;
  715.         }
  716.  
  717.  
  718.         default:{
  719.             /* Look! - no ifdefs here. */
  720.             e_error("%s %d","terminal() called with unknown parameter",what);
  721.         }
  722.     }
  723. }
  724.  
  725.  
  726.  
  727. match(argument,pattern)
  728. char    *argument;
  729. char    *pattern;
  730. {
  731.     /*
  732.      * Boneheaded but easy pattern matcher. Just see if the 'pattern'
  733.      * exists anywhere in the 'argument'. Boyer-Moore who?
  734.      * In general our patterns will be so short that it wouldn't be
  735.      * worth the effort to set up a better algorithm.
  736.      *
  737.      */
  738.  
  739.     register length=strlen(pattern);
  740.  
  741.     while (strlen(argument)>=length){
  742.         if (!strncmp(argument++,pattern,length)){
  743.             return(1);
  744.         }
  745.     }
  746.     return(0);
  747. }
  748.  
  749.  
  750.  
  751. find_match(pattern)
  752. char *pattern;
  753. {
  754.     /*
  755.      * Find the name in the history list that contains the 'pattern'.
  756.      * if it exists then put it into the 'arg' variable and otherwise
  757.      * announce that a match couldn't be found and leave.
  758.      *
  759.      */
  760.  
  761.     register count;
  762.     register i;
  763.  
  764.     /* Read and split the history file. */
  765.     read_hist();
  766.     count=split_hist();
  767.  
  768.     /* 
  769.      * Try for a match with each file in turn (note that we are working
  770.      * from most-recently-used backwards - probably a good thing).
  771.      *
  772.      */
  773.  
  774.     for (i=0;i<count;i++){
  775.         if (match(hist[i],pattern)){
  776.             sprintf(arg,"%s",hist[i]);
  777.             reconstruct(i,count);
  778.             return;
  779.         }
  780.     }
  781.  
  782.     /* We couldn't match so get out of here. */
  783.     e_error("%s \"%s\".","Unable to match with",pattern);
  784. }
  785.  
  786.  
  787.  
  788. insert_command(command)
  789. char *command;
  790. {
  791.     /*
  792.      * They want the last file in the history but want to preceed it
  793.      * this time with a command - no problems here.
  794.      *
  795.      */
  796.  
  797.     register count;
  798.     char *place;
  799.  
  800.     /* read and split the history. */
  801.     read_hist();
  802.     count=split_hist();
  803.     
  804.     /* 
  805.      * If there was already a command there (indicated by a '+') then we
  806.      * want to get rid of it. If there is a '+' but no ' ' (somewhere) after 
  807.      * it then the history file is in disarray and we will not try to recover.
  808.      *
  809.      */
  810.  
  811.     if (*hist[0]=='+'){
  812.         if ((place=index(hist[0],' '))==NULL){
  813.             e_error("%s %s",HIST,"file corrupted, + but no following space");
  814.         }
  815.         /* Move over white space - if there is any. */
  816.         while (*place==' '||*place=='\t'){
  817.             place++;
  818.         }
  819.     }
  820.     else{
  821.         /* There was no command preceeding the last file in the history. */
  822.         place=hist[0];
  823.     }
  824.  
  825.     /* Put the new command and the filename into 'arg' */
  826.     sprintf(arg,"%s %s",command,place);
  827.  
  828.     /* Rebuild the history with the selected command and name at the bottom. */
  829.     reconstruct(0,count);
  830. }
  831.  
  832.  
  833.  
  834. reconstruct(except,count)
  835. int except;
  836. int count;
  837. {
  838.     /* 
  839.      * Reconstruct history file excepting the 'except' last.
  840.      * So just copy all lines but the 'except'th last and then put in 'arg'
  841.      * which contains the new line for the history.
  842.      *
  843.      */
  844.  
  845.     register i;
  846.     FILE *tv,*get_temp();
  847.  
  848.     /* Get a temporary file. */
  849.     tv=get_temp();
  850.  
  851.     /* Put in the lines we still want. */
  852.     for (i=count-1;i>=0;i--){
  853.         if (i!=except){
  854.             fprintf(tv,"%s\n",hist[i]);
  855.         }
  856.     }
  857.  
  858.     /* Put in the new line from 'arg'. */
  859.     fprintf(tv,"%s\n",arg);
  860.  
  861.     /* Rename the temporary to be the new history file. */
  862.     close_temp(tv);
  863. }
  864.  
  865.  
  866.  
  867. normal(string)
  868. char *string;
  869. {
  870.     /* 
  871.      * A normal filename was found, put it into arg. First of all if there
  872.      * is a history and the file is already in it (which means they could
  873.      * have gotten to this file in other ways), then reconstruct the history
  874.      * as though they had. Also offer spelling help.
  875.      *
  876.      */
  877.  
  878.     register count;
  879.     register i;
  880.  
  881.     /* Put it into 'arg'. */
  882.     sprintf(arg,"%s",string);
  883.  
  884.     /* If there is a history file. */
  885.     if (got_vi()){
  886.  
  887.         /* Read it and split it up. */
  888.         read_hist();
  889.         count=split_hist();
  890.  
  891.         /* If it is in the history then reconstruct and return. */
  892.         for (i=0;i<count;i++){
  893.             if (!strcmp(hist[i],arg)){
  894.                 reconstruct(i,count);
  895.                 return;
  896.             }
  897.         }
  898.  
  899.         /* It's not in the history, help with spelling then reconstruct. */
  900.         if (!spell_help()){
  901.             find();
  902.         }
  903.  
  904.         /* If it is in the history then reconstruct and return. */
  905.         for (i=0;i<count;i++){
  906.             if (!strcmp(hist[i],arg)){
  907.                 reconstruct(i,count);
  908.                 return;
  909.             }
  910.         }
  911.  
  912.         reconstruct(HIST_LINES,count);
  913.     }
  914.     else{
  915.  
  916.         /* 
  917.          * There is no history around so help with spelling and set up a 
  918.          * history for next time.
  919.          *
  920.          */
  921.  
  922.         if (!spell_help()){
  923.             find();
  924.         }
  925.         new_vi();
  926.     }
  927.  
  928. }
  929.  
  930.  
  931.  
  932. multiple(number,args,size)
  933. int number;
  934. char **args;
  935. {
  936.     /*
  937.      * There were several names on the command line so we just strcat them
  938.      * into the 'arg' array. Check to see that the length of all the args
  939.      * will not be greater than "size" or else we will overflow arg.
  940.      *
  941.      * The total argument length must be at most size-1 characters, including
  942.      * spaces. arg needs to have a trailing '\0' so that do_vi() wont break.
  943.      *
  944.      */
  945.  
  946.     register count;
  947.     register i;
  948.     register total=0;
  949.  
  950.     *arg='\0';
  951.     while (--number){
  952.         if ((total+=strlen(*(args+1)))>=size){
  953.  
  954.             /*
  955.              * If you are running e and you find that this condition occurs,
  956.              * the solution is to simply increase the value of the #define
  957.              * line for ARG_CHARS in e.h.
  958.              *
  959.              */
  960.  
  961.             fprintf(stderr,
  962.             "%c%c%cWarning! Argument list too long, truncated after \"%s\".\n",
  963.                 BELL,BELL,BELL,*args);      
  964.             sleep(2);   /* Give them some chance to see what happened. */
  965.             break;
  966.         }
  967.  
  968.         strcat(arg,*++args);
  969.         if (number>1){
  970.             strcat(arg," ");
  971.  
  972.             /* 
  973.              * Add one to total for the space. There's no need to check for
  974.              * overflow here as we know there is another argument since
  975.              * number > 1 still. Thus if this overflows arg, then it is going
  976.              * to be caught anyway in the test at the top of the while loop.
  977.              *
  978.              */
  979.  
  980.             total++;                
  981.         }
  982.     }
  983.  
  984.     /*
  985.      * Now if there is a history file and we can find an identical line
  986.      * then reconstruct with that line at the bottom.
  987.      *
  988.      */
  989.  
  990.     if (got_vi()){
  991.         read_hist();
  992.         count=split_hist();
  993.         for (i=0;i<count;i++){
  994.             if (!strcmp(hist[i],arg)){
  995.                 reconstruct(i,count);
  996.                 return;
  997.             }
  998.         }
  999.  
  1000.         /* 
  1001.          * Rebuild, including everything but the counth last (i.e. make
  1002.          * a new history by omitting the oldest file in the current one and
  1003.          * putting 'arg' on the end.
  1004.          *
  1005.          */
  1006.  
  1007.         reconstruct(HIST_LINES,count);
  1008.     }
  1009.     else{
  1010.         /* There was no history file so try to give them one for next time. */
  1011.         new_vi();
  1012.     }
  1013. }
  1014.  
  1015.  
  1016.  
  1017. got_vi()
  1018. {
  1019.     /* Indicate if there is a history file that they own or otherwise. */
  1020.     struct stat buf;
  1021.  
  1022.     if (stat(HIST,&buf)==-1){
  1023.         return(0);
  1024.     }
  1025.     else{
  1026.         return(getuid()==buf.st_uid);
  1027.     }
  1028. }
  1029.  
  1030.  
  1031.  
  1032. new_vi()
  1033. {
  1034.     /* 
  1035.      * Attempt to make a new history file.
  1036.      * During which several things could go wrong.
  1037.      *
  1038.      */
  1039.  
  1040.     FILE *vh,*fopen(),*fclose();
  1041.     struct stat buf;
  1042.  
  1043.     /* If you can't read the current directory, get out. */
  1044.     if (stat(".",&buf)==-1){
  1045.         e_error("%s \".\"","Could not stat");
  1046.     }
  1047.  
  1048.     /* If you own the directory (you can't get a history in /tmp). */
  1049.     if (getuid()==buf.st_uid){
  1050.  
  1051.         /* If we can't make a history, get out. */
  1052.         if ((vh=fopen(HIST,"w"))==NULL){
  1053.             e_error("%s %s %s","Could not open",HIST,"for writing.");
  1054.         }
  1055.  
  1056.         /* Put in the 'arg' that we will be vi'ing in a second. */
  1057.         fprintf(vh,"%s\n",arg);
  1058.  
  1059.         /* Close the history. */
  1060.         if (fclose(vh)==(FILE *)EOF){
  1061.             e_error("%s %s","Could not close",HIST);
  1062.         }
  1063.  
  1064.         /* Give the history some protection - for those who want it! */
  1065.         if (chmod(HIST,E_MODE)==-1){
  1066.             e_error("%s %s","Could not chmod",HIST);
  1067.         }
  1068.     }
  1069. }
  1070.  
  1071.  
  1072.  
  1073.  
  1074. spell_help()
  1075. {
  1076.     /*
  1077.        Unashamedly stolen (and modified) from "The UNIX Programming
  1078.        Environment" - Kernighan and Pike.
  1079.  
  1080.        Read the directory and if the file they want (in 'arg') does not
  1081.        exist then see if there is one that does that has similar spelling
  1082.        to what they requested. Offer the change and handle the reply.
  1083.     */
  1084.  
  1085.     register dist;
  1086.     DIR *dp, *opendir();
  1087.     struct direct *readdir();
  1088.     struct direct *entry;
  1089.     register len;
  1090.     struct stat buf;
  1091.     int ch;
  1092.  
  1093.     /* If the file already exists just return - they don't need help. */
  1094.     if (stat(arg,&buf)==0){
  1095.         return(1);
  1096.     }
  1097.  
  1098.     /* If the current directory can't be read then return. */
  1099.     if ((dp=opendir("."))==NULL){
  1100.         return(0);
  1101.     }
  1102.     
  1103.     /* Get the length of what we are seeking to cut down on strcmping time. */
  1104.     len=strlen(arg);
  1105.  
  1106.     for (entry=readdir(dp);entry!=NULL;entry=readdir(dp)){
  1107.  
  1108.         register int dlen=entry->d_namlen;
  1109.  
  1110.         /* Try to stat the entry. */
  1111.         if (stat(entry->d_name,&buf)==-1){
  1112.             continue;
  1113.         }
  1114.  
  1115.         /* If it's not a regular file then continue. */
  1116.         if ((buf.st_mode&S_IFMT)!=S_IFREG){
  1117.             continue;
  1118.         }
  1119.  
  1120.         /* 
  1121.          * If this entry has 
  1122.          *
  1123.          *      length == sought length +/- 1 
  1124.          *
  1125.          * then it should be checked.
  1126.          *
  1127.          */
  1128.  
  1129.         if (entry->d_ino && dlen>=len-1 && dlen<=len+1){
  1130.  
  1131.             /* 
  1132.              * If the distance between this name and the one the user enetered
  1133.              * is too great then just continue.
  1134.              *
  1135.              */
  1136.  
  1137.             if (sp_dist(entry->d_name,arg)==3) continue;
  1138.  
  1139.  
  1140.             /* Otherwise offer them this one. */
  1141.             terminal(TERM_SET);
  1142.             fprintf(stderr,"correct to %s [y]? ",entry->d_name);
  1143.  
  1144.             /* Get and process the reply. */
  1145.  
  1146.             ch=getc(stdin);
  1147.             terminal(TERM_RESET);
  1148.  
  1149.             if (ch=='N'){
  1150.  
  1151.                 /* No, and they mean it. Offer no more help. */
  1152.                 fprintf(stderr,"No!\n");
  1153.                 break;
  1154.             }
  1155.  
  1156.             else if (ch=='n'){
  1157.  
  1158.                 /* No, but they'd like more help. */
  1159.                 fprintf(stderr,"no\n");
  1160.                 continue;
  1161.             }
  1162.  
  1163.             else if (ch=='q'||ch=='Q'||ch==(int)erase){
  1164.  
  1165.                 /* Quit. */
  1166.                 fprintf(stderr,"quit\n");
  1167.                 closedir(dp);
  1168.                 exit(0);
  1169.             }
  1170.  
  1171.             else{
  1172.  
  1173.                 /* Yes. */
  1174.                 fprintf(stderr,"yes\n");
  1175.                 closedir(dp);
  1176.                 strcpy(arg,entry->d_name);
  1177.                 return(1);
  1178.             }
  1179.         }
  1180.     }
  1181.  
  1182.     closedir(dp);
  1183.     return(0);
  1184. }
  1185.  
  1186.  
  1187.  
  1188. sp_dist(s,t)
  1189. char *s;
  1190. char *t;
  1191. {
  1192.     /* 
  1193.      * Stolen from the same place as spell_help() above.
  1194.  
  1195.      * Work out the distance between the strings 's' and 't' according
  1196.      * to the rough metric that
  1197.      * 
  1198.      *     Identical = 0
  1199.      *     Interchanged characters = 1
  1200.      *     Wrong character/extra character/missing character = 2
  1201.      *     Forget it = 3
  1202.      *
  1203.      */
  1204.  
  1205.     while (*s++==*t){
  1206.         if (*t++=='\0'){
  1207.             /* identical */
  1208.             return(0);
  1209.         }
  1210.     }
  1211.  
  1212.     if (*--s){
  1213.         if (*t){
  1214.             if (s[1]&&t[1]&&*s==t[1]&&*t==s[1]&&!strcmp(s+2,t+2)){
  1215.                 /* Interchanged chars. */
  1216.                 return(1);
  1217.             }
  1218.             if (!strcmp(s+1,t+1)){
  1219.                 /* Wrong char. */
  1220.                 return(2);
  1221.             }
  1222.         }
  1223.         if (!strcmp(s+1,t)){
  1224.             /* Extra char in 't'. */
  1225.             return(2);
  1226.         }
  1227.     }
  1228.     if (!strcmp(s,t+1)){
  1229.         /* Extra char in 's'. */
  1230.         return(2);
  1231.     }
  1232.  
  1233.     /* Forget it. */
  1234.     return(3);
  1235. }
  1236.  
  1237.  
  1238.  
  1239.  
  1240. find()
  1241. {
  1242.     /*
  1243.      * This takes the environment variable which is #defined as PATH and 
  1244.      * extracts the directory names from it. They may be separated by 
  1245.      * arbitrary numbers of delimiter characters (currently "\n", "\t", " " 
  1246.      * and ":"). Each directory is then checked to see if it contains the 
  1247.      * desired filename (with a call to check). Spelling corrections are 
  1248.      * not attempted.
  1249.      *
  1250.      */
  1251.  
  1252.     extern char *getwd();
  1253.     extern char *getenv();
  1254.     char *p;
  1255.     char path[MAX_PATH];
  1256.     char *dir;
  1257.     char *space;
  1258.     char *current_dir;
  1259.     char wd[MAXPATHLEN];
  1260.     char what[ARG_CHARS];
  1261.  
  1262.     if (!(p=getenv(E_PATH))) return;
  1263.  
  1264.     if (strlen(p)>=MAX_PATH){
  1265.         e_error("%s %s %s %d.","Length of",E_PATH,"variable exceeds",MAX_PATH);
  1266.     }
  1267.  
  1268.     strcpy(path,p);
  1269.     strcpy(what,arg);
  1270.  
  1271.     if (!(current_dir=getwd(wd))){
  1272.         e_error("%s","Could not get working directory.");
  1273.     }
  1274.  
  1275.     dir=path;
  1276.  
  1277.     /* Skip initial delimiters in the PATH variable. */
  1278.     while (*dir && is_delim(dir)) dir++;
  1279.  
  1280.     if (!*dir) return(0);   /* There was nothing there but delimiters! */
  1281.  
  1282.     space=dir+1;
  1283.  
  1284.     while (*space){
  1285.  
  1286.         /* Move "space" along to the first non delimiter. */
  1287.         while (*space && !is_delim(space)) space++;
  1288.  
  1289.         if (*space){
  1290.             *space='\0';
  1291.             space++;
  1292.         }
  1293.  
  1294.         /* Skip any white space between directory names. */
  1295.         while (*space && is_delim(space)) space++;
  1296.  
  1297.         /* Check the directory "dir" for the filename "what". */
  1298.         if (check(what,dir)){
  1299.  
  1300.             /* Offer them dir/what. */
  1301.             terminal(TERM_SET);
  1302.             fprintf(stderr,"%s/%s [y]? ",dir,what);
  1303.  
  1304.             /* Process the reply. */
  1305.             switch (getc(stdin)){
  1306.                 case 'N':
  1307.                 case 'n':{
  1308.                     fprintf(stderr,"no\n");
  1309.                     terminal(TERM_RESET);
  1310.                     break;
  1311.                 }
  1312.  
  1313.                 case 'q':
  1314.                 case 'Q':{
  1315.                     fprintf(stderr,"quit\n");
  1316.                     terminal(TERM_RESET);
  1317.                     exit(0);
  1318.                     break;
  1319.                 }
  1320.  
  1321.                 default :{
  1322.                     fprintf(stderr,"yes\n");
  1323.                     terminal(TERM_RESET);
  1324.                     if (chdir(current_dir)==-1){
  1325.                         e_error("%s %s","Could not chdir to",current_dir);
  1326.                     }
  1327.                     sprintf(arg,"%s/%s",dir,what);
  1328.                     return(1);
  1329.                 }
  1330.             }
  1331.         }
  1332.         dir=space;
  1333.     }
  1334.  
  1335.     /* Go back to the original directory. */
  1336.     if (chdir(current_dir)==-1){
  1337.         e_error("%s %s","Could not chdir to",current_dir);
  1338.     }
  1339.     return(0);
  1340. }
  1341.  
  1342.  
  1343.  
  1344. check(target,dir)
  1345. char *target;
  1346. char *dir;
  1347. {
  1348.     /*
  1349.      * Checks to see if the name "target" can be found in the directory "dir".
  1350.      * 
  1351.      */
  1352.  
  1353.     DIR *dp, *opendir();
  1354.     struct direct *readdir();
  1355.     struct direct *entry;
  1356.     struct stat buf;
  1357.  
  1358.     if ((dp=opendir(dir))==NULL){
  1359.         fprintf(stderr,"Cannot open \"%s\"\n",dir);
  1360.         return(0);
  1361.     }
  1362.     
  1363.     for (entry=readdir(dp);entry!=NULL;entry=readdir(dp)){
  1364.         if (!strcmp(entry->d_name,target)){
  1365.             if (chdir(dir)==-1){
  1366.                 perror("chdir");
  1367.                 return(0);
  1368.             }
  1369.  
  1370.             if (stat(entry->d_name,&buf)==-1){
  1371.                 /*
  1372.                  * At this point I used to have perror() give a message and
  1373.                  * the function return. Then one day e ran across an unresolved
  1374.                  * symbolic link at this point. The filename existed in the
  1375.                  * search directory, but it could not be stat'd as the thing
  1376.                  * it was supposedly linked to had been removed.
  1377.                  *
  1378.                  * The easiest thing (I think) to do is to ignore it. 
  1379.                  */
  1380.  
  1381.                 fprintf(stderr,
  1382.                     "%c%c%cWarning: Suspected unresolved symbolic link %s/%s\n",
  1383.                     BELL,BELL,BELL,dir,entry->d_name);
  1384.                 sleep(2);
  1385.                 continue;
  1386.             }
  1387.  
  1388.             /* 
  1389.                 If it is not a directory and EITHER you own it and can
  1390.                 read it OR you don't own it and it is readable by others, 
  1391.                 OR you are in the group of the owner and it's group readable
  1392.                     - then this is it.
  1393.             */
  1394.  
  1395.             if (    ((buf.st_mode&S_IFMT)==S_IFREG)  &&  
  1396.                     (
  1397.                         (buf.st_uid==getuid() && buf.st_mode&S_IREAD)
  1398.                         ||
  1399.                         (buf.st_gid==getgid() && buf.st_mode&G_READ)
  1400.                         ||
  1401.                         (buf.st_uid!=getuid() && buf.st_mode&O_READ)
  1402.                     )
  1403.                 )
  1404.             {
  1405.                 return(1);
  1406.             }
  1407.         }
  1408.     }
  1409.     return(0);
  1410. }
  1411.  
  1412.  
  1413.  
  1414. /* VARARGS1 */
  1415. e_error(a,b,c,d,e,f)
  1416. char *a;
  1417. {
  1418.     /*
  1419.      * Print the error message, clean up and get out.
  1420.      *
  1421.      */
  1422.  
  1423.     fprintf(stderr,"%s: ",myname);
  1424.     fprintf(stderr,a,b,c,d,e,f);
  1425.     fputc('\n',stderr);
  1426.     terminal(TERM_RESET);
  1427.     unlink(tmp_file);
  1428.     exit(1);
  1429. }
  1430.