/* @(#)edit.c 1.1 */ /* * edit.c - common routines for vi and emacs one line editors in shell * * David Korn P.D. Sullivan * AT&T Bell Laboratories AT&T Bell Laboratories * Room 5D-112 Room 1E253 * Murray Hill, N. J. 07974 Columbus, OH 43213 * Tel. x7975 Tel. x 2655 * * Coded April 1983. */ #include #ifdef KSHELL #include "shtype.h" #include "flags.h" #include "defs.h" #include "io.h" #include "name.h" #include "sym.h" #include "stak.h" #include "mode.h" #include "builtins.h" #include "brkincr.h" #else #include #include #include #include #endif /* KSHELL */ #include "history.h" #include "edit.h" #define BAD -1 #define GOOD 0 #define TRUE (-1) #define FALSE 0 #define SYSERR -1 #ifdef VENIX #define RT 1 #endif /* VENIX */ void e_crlf(); void e_flush(); int e_getchar(); void e_putchar(); void e_ringbell(); void e_setup(); int e_virt_to_phys(); int e_window(); void setcooked(); void ungetchar(); #if BSD || RT #include # ifdef BSD_4_2 # include # endif /* BSD_4_2 */ static struct sgttyb ttyparm; /* initial tty parameters */ static struct sgttyb nttyparm; /* raw tty parameters */ # ifdef BSD extern int tty_speeds[]; static int delay; static int l_mask; static struct tchars l_ttychars; static struct ltchars l_chars; static char l_changed; /* set if mode bits changed */ #define L_CHARS 4 #define T_CHARS 2 #define L_MASK 1 # endif /* BSD */ #else # ifdef XENIX /* avoid symbol table overflows */ # define NCC 8 struct termio { unsigned short c_iflag; /* input modes */ unsigned short c_oflag; /* output modes */ unsigned short c_cflag; /* control modes */ unsigned short c_lflag; /* line discipline modes */ char c_line; /* line discipline */ unsigned char c_cc[NCC]; /* control chars */ char c_res; /* padding, AFTER the array */ }; #define TIOC ('T'<<8) #define BRKINT 0000002 #define CBAUD 0000017 #define ECHO 0000010 #define ECHOE 0000020 #define ECHOK 0000040 #define ECHONL 0000100 #define ICANON 0000002 #define ICRNL 0000400 #define IGNCR 0000200 #define IGNPAR 0000004 #define INLCR 0000100 #define PARMRK 0000010 #define TCGETA (TIOC|1) #define TCSETAW (TIOC|3) #define TCXONC (TIOC|6) #define VEOF 4 #define VERASE 2 #define VKILL 3 #define VMIN 4 #define VTIME 5 #define TM_CECHO 0010 # else # include # endif /* XENIX */ static struct termio ttyparm; /* initial tty parameters */ static struct termio nttyparm; /* raw tty parameters */ #endif /* RT */ #define lookahead editb.e_index #define env editb.e_env #define previous editb.e_lbuf #define fildes editb.e_fd #ifdef KSHELL extern struct Amemory *alias; extern char **arg_build(); extern void failed(); extern void fault(); extern struct Namnod *findnod(); extern int f_complete(); extern void gsort(); extern char *movstr(); extern void p_flush(); extern void p_setout(); extern char *simple(); extern char *tilde(); extern char *valup(); static char macro[] = "_?"; #else static char badcooked[] = "cannot reset tty to cooked mode"; struct edit editb; char trapnote; extern char trapnote; extern int errno; #define p_flush() fflush(stderr) #define p_setout(s) fflush(stdout) #define error(s) failed(s,NULL) #define SIGSLOW 1 #define output stderr #endif /* KSHELL */ extern char *strrchr(); extern char *strcpy(); static char bellchr[] = "\7"; /* bell char */ static int control(); /*{ SETCOOKED( fd ) * * This routine will set the tty in cooked mode. * It is also called by error.done(). * }*/ void setcooked(fd) register int fd; { /*** don't do ioctl unless ttyparm has valid data ***/ /* or in raw mode */ if(editb.e_ttyspeed==0 || editb.e_raw==0) return; #ifdef BSD if(editb.e_raw==RAWMODE && ioctl(fd, TIOCSETN, &ttyparm) == SYSERR ) { if(errno!=EBADF && errno!=ENOTTY) error(badcooked); return; } /* restore flags */ if(l_changed&L_MASK) ioctl(fd,TIOCLSET,&l_mask); if(l_changed&T_CHARS) /* restore alternate break character */ ioctl(fd,TIOCSETC,&l_ttychars); if(l_changed&L_CHARS) /* restore alternate break character */ ioctl(fd,TIOCSLTC,&l_chars); l_changed = 0; #else # ifdef RT if (stty(fd,&ttyparm) == SYSERR) # else if( editb.e_raw && control(fd, TCSETAW, &ttyparm) == SYSERR ) # endif /* RT */ { if(errno!=EBADF && errno!=ENOTTY) error(badcooked); return; } #endif /* BSD */ editb.e_raw = 0; return; } /*{ SETRAW( fd ) * * This routine will set the tty in raw mode. * }*/ setraw(fd) register int fd; { #ifdef BSD struct ltchars lchars; #endif /* BSD */ if(editb.e_raw==RAWMODE) return(GOOD); /* characters are echoed on standard error */ p_setout(stderr); #if BSD || RT # ifdef BSD if((ioctl(fd,TIOCGETP,&ttyparm) == SYSERR) # else if ((gtty(fd,&ttyparm) == SYSERR) # endif /* BSD */ || !(ttyparm.sg_flags & ECHO ) || (ttyparm.sg_flags & LCASE )) { return(BAD); } nttyparm = ttyparm; nttyparm.sg_flags &= ~(ECHO | TBDELAY); # ifdef BSD nttyparm.sg_flags |= CBREAK; # else nttyparm.sg_flags |= RAW; # endif /* BSD */ editb.e_erase = ttyparm.sg_erase; editb.e_kill = ttyparm.sg_kill; editb.e_eof = cntl(D); # ifdef BSD if( ioctl(fd, TIOCSETN, &nttyparm) == SYSERR ) # else if( stty(fd, &nttyparm) == SYSERR ) # endif /* BSD */ { return(BAD); } editb.e_ttyspeed = (ttyparm.sg_ospeed>=B1200?FAST:SLOW); #ifdef BSD delay = tty_speeds[ttyparm.sg_ospeed]; /* try to remove effect of ^V and ^Y */ if(ioctl(fd,TIOCGLTC,&l_chars) != SYSERR) { lchars = l_chars; lchars.t_lnextc = -1; lchars.t_dsuspc = -1; /* no delayed stop process signal */ if(ioctl(fd,TIOCSLTC,&lchars) != SYSERR) l_changed |= L_CHARS; } #endif /* BSD */ #else # ifndef RAWONLY if(editb.e_raw != ALTMODE) # endif /* RAWONLY */ if( (ioctl(fd, TCGETA, &ttyparm) == SYSERR) || (!(ttyparm.c_lflag & ECHO ))) { return(BAD); } nttyparm = ttyparm; #ifndef u370 nttyparm.c_iflag &= ~(IGNPAR|PARMRK|INLCR|IGNCR|ICRNL); nttyparm.c_iflag |= BRKINT; nttyparm.c_lflag &= ~(ICANON|ECHO); #else nttyparm.c_iflag &= ~(IGNBRK|PARMRK|INLCR|IGNCR|ICRNL|INPCK); nttyparm.c_iflag |= (BRKINT|IGNPAR); nttyparm.c_lflag &= ~(ICANON|ECHO); #endif /* u370 */ nttyparm.c_cc[VTIME] = 0; nttyparm.c_cc[VMIN] = 1; editb.e_eof = ttyparm.c_cc[VEOF]; editb.e_erase = ttyparm.c_cc[VERASE]; editb.e_kill = ttyparm.c_cc[VKILL]; #ifndef u370 if( control(fd, TCSETAW, &nttyparm) == SYSERR ) #else /* delays are too long, don't wait for output to drain */ if( control(fd, TCSETA, &nttyparm) == SYSERR ) #endif /* u370 */ { return(BAD); } control(fd,TCXONC,1); editb.e_ttyspeed = ttyparm.c_cflag & CBAUD; #endif editb.e_raw = RAWMODE; return(GOOD); } #ifndef BSD /* * give two tries for ioctl * interrupts are ignored */ static int control(fd,request,arg) { register int i; register int k = 2; errno = 0; while(k--) { if((i = ioctl(fd,request,arg)) != SYSERR) return(i); if(errno == EINTR) { errno = 0; k++; } } return(SYSERR); } #endif /* BSD */ #ifndef RAWONLY /* SET_TTY( fd ) * * Get tty parameters and make ESC and '\r' wakeup characters. * */ #ifdef BSD setalt(fd) register int fd; { int mask; struct tchars ttychars; if(editb.e_raw==ALTMODE) return(GOOD); if(editb.e_raw==RAWMODE) setcooked(fd); l_changed = 0; if( editb.e_ttyspeed == 0) { if((ioctl(fd,TIOCGETP,&ttyparm) != SYSERR)) editb.e_ttyspeed = (ttyparm.sg_ospeed>=B1200?FAST:SLOW); } if(ioctl(fd,TIOCGETC,&l_ttychars) == SYSERR) return(BAD); if(ioctl(fd,TIOCLGET,&l_mask)==SYSERR) return(BAD); ttychars = l_ttychars; mask = LCRTBS|LCRTERA|LCTLECH|LPENDIN|LCRTKIL; if((l_mask|mask) != l_mask) l_changed = L_MASK; if(ioctl(fd,TIOCLBIS,&mask)==SYSERR) return(BAD); ttychars.t_brkc = ESC; l_changed |= T_CHARS; if(ioctl(fd,TIOCSETC,&ttychars) == SYSERR) return(BAD); editb.e_raw = ALTMODE; return(GOOD); } #else setalt(fd) register int fd; { if(editb.e_raw==ALTMODE) return(GOOD); if(editb.e_raw==RAWMODE) setcooked(fd); if( (control(fd, TCGETA, &ttyparm) == SYSERR) || (!(ttyparm.c_lflag & ECHO ))) { return(BAD); } nttyparm = ttyparm; nttyparm.c_iflag &= ~(IGNCR|ICRNL); nttyparm.c_iflag |= INLCR; nttyparm.c_lflag |= (ECHOE|ECHOK); nttyparm.c_cc[VEOF] = ESC; /* make ESC the eof char */ nttyparm.c_cc[VEOL] = '\r'; /* make CR an eol char */ editb.e_eof = ttyparm.c_cc[VEOF]; nttyparm.c_cc[VEOL2] = editb.e_eof; /* make EOF an eol char */ editb.e_erase = ttyparm.c_cc[VERASE]; editb.e_kill = ttyparm.c_cc[VKILL]; if( control(fd, TCSETAW, &nttyparm) == SYSERR ) { return(BAD); } editb.e_ttyspeed = ((ttyparm.c_cflag&CBAUD)>=B1200?FAST:SLOW); editb.e_raw = ALTMODE; return(GOOD); } #endif /* BSD */ #endif /* RAWONLY */ /* * E_WINDOW() * * return the window size */ int e_window() { register int n = DFLTWINDOW-1; register char *cp = valup(COLUMNS); if(cp) { n = atoi(cp)-1; if(n < MINWINDOW) n = MINWINDOW; if(n > MAXWINDOW) n = MAXWINDOW; } return(n); } /* E_FLUSH() * * Flush the output buffer. * */ void e_flush() { register unsigned char *buf = (unsigned char*)output->_base; register int n = editb.e_outptr-buf; register int fd = fileno(output); if(n<=0) return; write(fd,(char*)buf,n); editb.e_outptr = buf; #ifdef BSD # ifdef BSD_4_2 if(delay && n > delay/100) { /* delay until output drains */ struct timeval timeloc; n *= 10; timeloc.tv_sec = n/delay; timeloc.tv_usec = (1000000*(n%delay))/delay; select(0,0,0,0,&timeloc); } # endif /* BSD_4_2 */ #else # ifndef RT if(editb.e_raw==RAWMODE && n > 16) ioctl(fd, TCSETAW, &nttyparm); # endif /* RT */ #endif /* BSD */ } /* * send the bell character ^G to the terminal */ void e_ringbell() { write(fileno(output),bellchr,1); } /* * send a carriage return line feed to the terminal */ void e_crlf() { #ifdef u370 e_putchar('\r'); #endif /* u370 */ #ifdef VENIX e_putchar('\r'); #endif /* VENIX */ e_putchar('\n'); e_flush(); } /* E_SETUP( max_prompt_size ) * * This routine sets up the prompt string */ void e_setup(fd,PRSIZE) { register char *last; register char *pp = (char*)(output->_base); char *ppmax; editb.e_fd = fd; if(fc_fix) { register struct fixcmd *fp = fc_fix; editb.e_hismax = fp->fixind; editb.e_hloff = fp->fixline; editb.e_hismin = fp->fixind-fp->fixmax; if(editb.e_hismin<0) editb.e_hismin = 0; } else { editb.e_hismax = editb.e_hismin = editb.e_hloff = 0; } editb.e_hline = editb.e_hismax; editb.e_wsize = e_window()-2; editb.e_outptr = (unsigned char*)pp; editb.e_crlf = YES; *(output->_ptr) = 0; if((last=strrchr(pp,'\n'))==NULL) { if(*(last=pp)==0) editb.e_crlf = NO; } else last++; pp = editb.e_prompt; ppmax = pp+PRSIZE-1; *pp++ = '\r'; { register int c; while(c = *last++) { /* cut out bells */ if(c!=BELL) { if(pp < ppmax) *pp++ = c; if(!isprint(c)) editb.e_crlf = NO; } } } editb.e_plen = pp - editb.e_prompt - 1; *pp = 0; #ifdef u370 if (editb.e_raw == RAWMODE) u370fflush(output); else #endif /* u370 */ p_flush(); } #ifdef u370 /* The u370 does not \r before \n in raw mode (known bug). To get around this we will insert a \r before each \n in the output buffer. */ u370fflush(file) FILE *file; { unsigned char *base,*ptr,buffer[BUFSIZ*2]; int icnt,ocnt; ptr = buffer; icnt = file->_ptr - file->_base; ocnt = icnt; base = file->_base ; if (icnt <= 0) return; while (base < file->_ptr) { if (*base == '\n' ) { *ptr++ = '\r'; ocnt++; } *ptr++ = *base++; } base = file->_base; ptr = buffer; while (ocnt>0) { for(icnt=0;icnt_ptr = base; p_flush(); base = file->_base; } } #endif /* u370 */ #ifdef KSHELL /* * look for edit macro named _i * if found, puts the macro definition into lookahead buffer and returns 1 */ e_macro(i) register int i; { register char *out; struct Namnod *np; genchar buff[LOOKAHEAD+1]; if(i != '@') macro[1] = i; if (isalnum(i)&&(np=findnod(macro,alias,0))&&(out=valup(np))) { #ifdef MULTIBYTE /* copy to buff in internal representation */ int c = out[LOOKAHEAD]; out[LOOKAHEAD] = 0; i = e_internal(out,buff); out[LOOKAHEAD] = c; #else strncpy((char*)buff,out,LOOKAHEAD); i = strlen((char*)buff); #endif /* MULTIBYTE */ while(i-- > 0) ungetchar(buff[i]); return(1); } return(0); } /* * file name generation for edit modes * non-zero exit for error, <0 ring bell * don't search back past character of the buffer * mode is '*' for inline expansion, otherwise files are listed in select format */ q_expand(outbuff,cur,eol,start,mode) char outbuff[]; int *cur; int *eol; int start; int mode; { STKPTR staksav = stakbot; COMPTR comptr = (COMPTR)getstak(COMTYPE); ARGPTR ap = (ARGPTR)locstak(); register char *out; char *outmin = outbuff + start; char *begin; char *last; int rval = 0; int strip; optflag savflags = flags; #ifdef MULTIBYTE { register int c = *cur; register genchar *cp; /* adjust cur */ cp = (genchar *)outbuff + *cur; c = *cp; *cp = 0; *cur = e_external((genchar*)outbuff,(char*)stakbot); *cp = c; *eol = e_external((genchar*)outbuff,outbuff); } #endif /* MULTIBYTE */ out = outbuff + *cur; comptr->comtyp = COMSCAN; comptr->comarg = ap; ap->argflag = (A_MAC|A_EXP); ap->argnxt = 0; { register int c; register char *ptr = ap->argval; int chktilde = 0; int flag; char *cp; if(out>outmin) { /* go to beginning of word */ do { out--; c = *(unsigned char*)out; } while(out>outmin && !isqmeta(c)); /* copy word into arg */ if(isqmeta(c)) out++; } else out = outmin; begin = out; flag = '*'; strip = TRUE; /* copy word to arg and do ~ expansion */ do { c = *(unsigned char*)out++; if(isexp(c)) flag = 0; if ((c == '/') && (flag == 0)) strip = FALSE; *ptr++ = c; if(chktilde==0 && (c==0 || c == '/')) { chktilde++; if(cp=tilde(begin)) { ptr = movstr(cp,ap->argval); *ptr++ = c; } } } while (c && !isqmeta(c)); out--; *(ptr-1) = flag; endstak(ptr); last = ptr-1; } if(mode!='*') on_option(MARKDIR); { register char **com; int narg; register int size; while(1) { com = arg_build(&narg,comptr); /* match? */ if (narg > 1 || !eq(ap->argval,*com)) break; if (*last == 0) *last = '*'; else { rval = -1; goto done; } } if(mode!='*') { if (strip) { register char **ptrcom; for(ptrcom=com;*ptrcom;ptrcom++) /* trim directory prefix */ *ptrcom = simple (*ptrcom); } p_setout(stderr); newline(); p_list(narg,com); p_flush(); goto done; } /* see if there is enough room */ size = *eol - (out-begin); size += narg; { char **savcom = com; while (*com) size += strlen(*com++); com = savcom; } /* see if room for expansion */ if(outbuff+size >= &outbuff[MAXLINE]) { com[0] = ap->argval; com[1] = NULL; } /* save remainder of the buffer */ strcpy(stakbot,out); out = begin; while (*com) { out = movstr(*com,out); if (*++com) *out++ = ' '; } *cur = (out-outbuff); /* restore rest of buffer */ out = movstr(stakbot,out); *eol = (out-outbuff); } done: tdystak(staksav); flags = savflags; #ifdef MULTIBYTE { register int c; /* first re-adjust cur */ out = outbuff + *cur; c = *out; *out = 0; *cur = e_internal(outbuff,(genchar*)stakbot); *out = c; outbuff[*eol+1] = 0; *eol = e_internal(outbuff,(genchar*)outbuff); } #endif /* MULTIBYTE */ return(rval); } #endif /* KSHELL */ /* * routine to perform read from terminal for vi and emacs mode */ int e_getchar() { register int i; register int c; register int maxtry = 100; int nchar; /* number of characters to read at a time */ #ifdef MULTIBYTE static int curchar; static int cursize; #endif /* MULTIBYTE */ char readin[LOOKAHEAD] ; if (lookahead) { c = previous[--lookahead]; /*** map '\r' to '\n' ***/ if(c == '\r') c = '\n'; return(c); } e_flush() ; /* you can't chance read ahead at the end of line */ nchar = (editb.e_cur>=editb.e_eol?1:READAHEAD); /* Set 'i' to indicate read failed, in case intr set */ retry: i = -1; errno = 0; editb.e_inmacro = 0; while((trapnote&SIGSLOW)==0 && maxtry--) { errno=0; if ((i = read(fildes,readin, nchar)) != -1) break; } #ifdef MULTIBYTE lookahead = maxtry = i; i = 0; while (i < maxtry) { c = readin[i++] & STRIP; next: if(cursize-- > 0) { curchar = (curchar<<7) | (c&~HIGHBIT); if(cursize==0) { c = curchar; goto gotit; } else if(i>=maxtry) goto retry; continue; } else if(curchar = echarset(c)) { cursize = in_csize(curchar); if(curchar != 1) c = 0; curchar <<= 7*(ESS_MAXCHAR-cursize); if(c) goto next; else if(i>=maxtry) goto retry; continue; } gotit: previous[--lookahead] = c; #else while (i > 0) { c = readin[--i] & STRIP; previous[lookahead++] = c; #endif /* MULTIBYTE */ #ifndef BSD if( c == '\0' ) { /*** user break key ***/ lookahead = 0; # ifdef KSHELL fault(SIGINT); longjmp(env, UINTR); # endif /* KSHELL */ } #endif /* !BSD */ } #ifdef MULTIBYTE /* shift lookahead buffer if necessary */ if(lookahead) { for(i=lookahead;i < maxtry;i++) previous[i-lookahead] = previous[i]; } lookahead = maxtry-lookahead; #endif /* MULTIBYTE */ if (lookahead > 0) return(e_getchar(1)); longjmp(env,(i==0?UEOF:UINTR)); /* What a mess! Give up */ /* NOTREACHED */ } void ungetchar(c) register int c; { if (lookahead < LOOKAHEAD) previous[lookahead++] = c; return; } /* * put a character into the output buffer */ void e_putchar(c) register int c; { register unsigned char *dp = editb.e_outptr; #ifdef MULTIBYTE register int d; /* check for place holder */ if(c == MARKER) return; if(d = icharset(c)) { if(d == 2) *dp++ = ESS2; else if(d == 3) *dp++ = ESS3; d = in_csize(d); while(--d>0) *dp++ = HIGHBIT|(c>>(7*d)); c |= HIGHBIT; } #endif /* MULTIBYTE */ if (c == '_') { *dp++ = ' '; *dp++ = '\b'; } *dp++ = c; *dp = '\0'; if ((dp - (unsigned char*)(output->_base))>=(BUFSIZ-3)) e_flush(); else editb.e_outptr = dp; } /* * copy virtual to physical and return the index for cursor in physical buffer */ e_virt_to_phys(virt,phys,cur,voff,poff) genchar *virt; genchar *phys; int cur; { register genchar *sp = virt; register genchar *dp = phys; register int c; genchar *curp = sp + cur; genchar *dpmax = phys+MAXLINE; int r = 0; #ifdef MULTIBYTE int d; #endif /* MULTIBYTE */ sp += voff; dp += poff; for(r=poff;c= *sp;sp++) { if(curp == sp) r = dp - phys; #ifdef MULTIBYTE d = out_csize(icharset(c)); if(d>1) { /* multiple width character put in place holders */ *dp++ = c; while(--d >0) *dp++ = MARKER; /* in vi mode the cursor is at the last character */ if(dp>=dpmax) break; continue; } else #endif /* MULTIBYTE */ if(!isprint(c)) { if(c=='\t') { c = dp-phys; c = ((c+8)&~07) - c; while(--c>0) *dp++ = ' '; c = ' '; } else { *dp++ = '^'; c ^= TO_PRINT; } /* in vi mode the cursor is at the last character */ if(curp == sp && is_option(EDITVI)) r = dp - phys; } *dp++ = c; if(dp>=dpmax) break; } *dp = 0; return(r); } #ifdef MULTIBYTE /* * convert external representation to an array of genchars * and can be the same * returns number of chars in dest */ int e_internal(src,dest) register unsigned char *src; genchar *dest; { register int c; register genchar *dp = dest; register int d; register int size; if((unsigned char*)dest == src) { genchar buffer[MAXLINE]; c = e_internal(src,buffer); e_gencpy(dp,buffer); return(c); } while(c = *src++) { if(size = echarset(c)) { d = (size==1?c:0); c = size; size = in_csize(c); c <<= 7*(ESS_MAXCHAR-size); if(d) { size--; c = (c<<7) | (d&~HIGHBIT); } while(size-- >0) c = (c<<7) | ((*src++)&~HIGHBIT); } *dp++ = c; } *dp = 0; return(dp-dest); } /* * convert internal representation into character array . * The and may be the same. * returns number of chars in dest. */ int e_external(src,dest) genchar *src; char *dest; { register int c; register char *dp = dest; register int d; char *dpmax = dp+sizeof(genchar)*MAXLINE-2; if((char*)src == dp) { char buffer[MAXLINE*sizeof(genchar)]; c = e_external(src,buffer); strcpy(dest,buffer); return(c); } while((c = *src++) && dp0) *dp++ = HIGHBIT|(c>>(7*d)); c |= HIGHBIT; } *dp++ = c; } *dp = 0; return(dp-dest); } /* * copy to */ int e_gencpy(dp,sp) register genchar *dp; register genchar *sp; { while(*dp++ = *sp++); } /* * copy at most items from to */ int e_genncpy(dp,sp, n) register genchar *dp; register genchar *sp; register int n; { while(n-->0 && (*dp++ = *sp++)); } /* * find the string length of */ int e_genlen(str) register genchar *str; { register genchar *sp = str; while(*sp++); return(sp-str-1); } #endif /* MULTIBYTE */