home *** CD-ROM | disk | FTP | other *** search
- /*
- * 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);
- }
-