diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2012-06-24 22:28:35 +0000 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2012-06-24 22:28:35 +0000 |
commit | 3950ffe2a485479f6561c27364d3d7df5a21d124 (patch) | |
tree | 468c6e14449d1b1e279222ec32f676b0311917d2 /src/cmd/ksh93/edit | |
download | ksh-upstream.tar.gz |
Imported Upstream version 93u+upstream
Diffstat (limited to 'src/cmd/ksh93/edit')
-rw-r--r-- | src/cmd/ksh93/edit/completion.c | 572 | ||||
-rw-r--r-- | src/cmd/ksh93/edit/edit.c | 1806 | ||||
-rw-r--r-- | src/cmd/ksh93/edit/emacs.c | 1571 | ||||
-rw-r--r-- | src/cmd/ksh93/edit/hexpand.c | 734 | ||||
-rw-r--r-- | src/cmd/ksh93/edit/history.c | 1222 | ||||
-rw-r--r-- | src/cmd/ksh93/edit/vi.c | 2753 |
6 files changed, 8658 insertions, 0 deletions
diff --git a/src/cmd/ksh93/edit/completion.c b/src/cmd/ksh93/edit/completion.c new file mode 100644 index 0000000..da81f94 --- /dev/null +++ b/src/cmd/ksh93/edit/completion.c @@ -0,0 +1,572 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2011 AT&T Intellectual Property * +* and is licensed under the * +* Eclipse Public License, Version 1.0 * +* by AT&T Intellectual Property * +* * +* A copy of the License is available at * +* http://www.eclipse.org/org/documents/epl-v10.html * +* (with md5 checksum b35adb5213ca9657e911e9befb180842) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * completion.c - command and file completion for shell editors + * + */ + +#include "defs.h" +#include <ast_wchar.h> +#include "lexstates.h" +#include "path.h" +#include "io.h" +#include "edit.h" +#include "history.h" + +#if !SHOPT_MULTIBYTE +#define mbchar(p) (*(unsigned char*)p++) +#endif + +static char *fmtx(const char *string) +{ + register const char *cp = string; + register int n,c; + unsigned char *state = (unsigned char*)sh_lexstates[2]; + int offset = staktell(); + if(*cp=='#' || *cp=='~') + stakputc('\\'); + while((c=mbchar(cp)),(c>UCHAR_MAX)||(n=state[c])==0 || n==S_EPAT); + if(n==S_EOF && *string!='#') + return((char*)string); + stakwrite(string,--cp-string); + for(string=cp;c=mbchar(cp);string=cp) + { + if((n=cp-string)==1) + { + if((n=state[c]) && n!=S_EPAT) + stakputc('\\'); + stakputc(c); + } + else + stakwrite(string,n); + } + stakputc(0); + return(stakptr(offset)); +} + +static int charcmp(int a, int b, int nocase) +{ + if(nocase) + { + if(isupper(a)) + a = tolower(a); + if(isupper(b)) + b = tolower(b); + } + return(a==b); +} + +/* + * overwrites <str> to common prefix of <str> and <newstr> + * if <str> is equal to <newstr> returns <str>+strlen(<str>)+1 + * otherwise returns <str>+strlen(<str>) + */ +static char *overlaid(register char *str,register const char *newstr,int nocase) +{ + register int c,d; + while((c= *(unsigned char *)str) && ((d= *(unsigned char*)newstr++),charcmp(c,d,nocase))) + str++; + if(*str) + *str = 0; + else if(*newstr==0) + str++; + return(str); +} + + +/* + * returns pointer to beginning of expansion and sets type of expansion + */ +static char *find_begin(char outbuff[], char *last, int endchar, int *type) +{ + register char *cp=outbuff, *bp, *xp; + register int c,inquote = 0, inassign=0; + int mode=*type; + bp = outbuff; + *type = 0; + while(cp < last) + { + xp = cp; + switch(c= mbchar(cp)) + { + case '\'': case '"': + if(!inquote) + { + inquote = c; + bp = xp; + break; + } + if(inquote==c) + inquote = 0; + break; + case '\\': + if(inquote != '\'') + mbchar(cp); + break; + case '$': + if(inquote == '\'') + break; + c = *(unsigned char*)cp; + if(mode!='*' && (isaletter(c) || c=='{')) + { + int dot = '.'; + if(c=='{') + { + xp = cp; + mbchar(cp); + c = *(unsigned char*)cp; + if(c!='.' && !isaletter(c)) + break; + } + else + dot = 'a'; + while(cp < last) + { + if((c= mbchar(cp)) , c!=dot && !isaname(c)) + break; + } + if(cp>=last) + { + if(c==dot || isaname(c)) + { + *type='$'; + return(++xp); + } + if(c!='}') + bp = cp; + } + } + else if(c=='(') + { + *type = mode; + xp = find_begin(cp,last,')',type); + if(*(cp=xp)!=')') + bp = xp; + else + cp++; + } + break; + case '=': + if(!inquote) + { + bp = cp; + inassign = 1; + } + break; + case ':': + if(!inquote && inassign) + bp = cp; + break; + case '~': + if(*cp=='(') + break; + /* fall through */ + default: + if(c && c==endchar) + return(xp); + if(!inquote && ismeta(c)) + { + bp = cp; + inassign = 0; + } + break; + } + } + if(inquote && *bp==inquote) + *type = *bp++; + return(bp); +} + +/* + * file name generation for edit modes + * non-zero exit for error, <0 ring bell + * don't search back past beginning of the buffer + * mode is '*' for inline expansion, + * mode is '\' for filename completion + * mode is '=' cause files to be listed in select format + */ + +int ed_expand(Edit_t *ep, char outbuff[],int *cur,int *eol,int mode, int count) +{ + struct comnod *comptr; + struct argnod *ap; + register char *out; + char *av[2], *begin , *dir=0; + int addstar=0, rval=0, var=0, strip=1; + int nomarkdirs = !sh_isoption(SH_MARKDIRS); + sh_onstate(SH_FCOMPLETE); + if(ep->e_nlist) + { + if(mode=='=' && count>0) + { + if(count> ep->e_nlist) + return(-1); + mode = '?'; + av[0] = ep->e_clist[count-1]; + av[1] = 0; + } + else + { + stakset(ep->e_stkptr,ep->e_stkoff); + ep->e_nlist = 0; + } + } + comptr = (struct comnod*)stakalloc(sizeof(struct comnod)); + ap = (struct argnod*)stakseek(ARGVAL); +#if SHOPT_MULTIBYTE + { + register int c = *cur; + register genchar *cp; + /* adjust cur */ + cp = (genchar *)outbuff + *cur; + c = *cp; + *cp = 0; + *cur = ed_external((genchar*)outbuff,(char*)stakptr(0)); + *cp = c; + *eol = ed_external((genchar*)outbuff,outbuff); + } +#endif /* SHOPT_MULTIBYTE */ + out = outbuff + *cur + (sh_isoption(SH_VI)!=0); + if(out[-1]=='"' || out[-1]=='\'') + { + rval = -(sh_isoption(SH_VI)!=0); + goto done; + } + comptr->comtyp = COMSCAN; + comptr->comarg = ap; + ap->argflag = (ARG_MAC|ARG_EXP); + ap->argnxt.ap = 0; + ap->argchn.cp = 0; + { + register int c; + char *last = out; + c = *(unsigned char*)out; + var = mode; + begin = out = find_begin(outbuff,last,0,&var); + /* addstar set to zero if * should not be added */ + if(var=='$') + { + stakputs("${!"); + stakwrite(out,last-out); + stakputs("@}"); + out = last; + } + else + { + addstar = '*'; + while(out < last) + { + c = *(unsigned char*)out; + if(isexp(c)) + addstar = 0; + if (c == '/') + { + if(addstar == 0) + strip = 0; + dir = out+1; + } + stakputc(c); + out++; + } + } + if(mode=='?') + mode = '*'; + if(var!='$' && mode=='\\' && out[-1]!='*') + addstar = '*'; + if(*begin=='~' && !strchr(begin,'/')) + addstar = 0; + stakputc(addstar); + ap = (struct argnod*)stakfreeze(1); + } + if(mode!='*') + sh_onoption(SH_MARKDIRS); + { + register char **com; + char *cp=begin, *left=0, *saveout="."; + int nocase=0,narg,cmd_completion=0; + register int size='x'; + while(cp>outbuff && ((size=cp[-1])==' ' || size=='\t')) + cp--; + if(!var && !strchr(ap->argval,'/') && (((cp==outbuff&&ep->sh->nextprompt==1) || (strchr(";&|(",size)) && (cp==outbuff+1||size=='('||cp[-2]!='>') && *begin!='~' ))) + { + cmd_completion=1; + sh_onstate(SH_COMPLETE); + } + if(ep->e_nlist) + { + narg = 1; + com = av; + if(dir) + begin += (dir-begin); + } + else + { + com = sh_argbuild(ep->sh,&narg,comptr,0); + /* special handling for leading quotes */ + if(begin>outbuff && (begin[-1]=='"' || begin[-1]=='\'')) + begin--; + } + sh_offstate(SH_COMPLETE); + /* allow a search to be aborted */ + if(ep->sh->trapnote&SH_SIGSET) + { + rval = -1; + goto done; + } + /* match? */ + if (*com==0 || (narg <= 1 && (strcmp(ap->argval,*com)==0) || (addstar && com[0][strlen(*com)-1]=='*'))) + { + rval = -1; + goto done; + } + if(mode=='=') + { + if (strip && !cmd_completion) + { + register char **ptrcom; + for(ptrcom=com;*ptrcom;ptrcom++) + /* trim directory prefix */ + *ptrcom = path_basename(*ptrcom); + } + sfputc(sfstderr,'\n'); + sh_menu(sfstderr,narg,com); + sfsync(sfstderr); + ep->e_nlist = narg; + ep->e_clist = com; + goto done; + } + /* see if there is enough room */ + size = *eol - (out-begin); + if(mode=='\\') + { + int c; + if(dir) + { + c = *dir; + *dir = 0; + saveout = begin; + } + if(saveout=astconf("PATH_ATTRIBUTES",saveout,(char*)0)) + nocase = (strchr(saveout,'c')!=0); + if(dir) + *dir = c; + /* just expand until name is unique */ + size += strlen(*com); + } + else + { + size += narg; + { + char **savcom = com; + while (*com) + size += strlen(cp=fmtx(*com++)); + com = savcom; + } + } + /* see if room for expansion */ + if(outbuff+size >= &outbuff[MAXLINE]) + { + com[0] = ap->argval; + com[1] = 0; + } + /* save remainder of the buffer */ + if(*out) + left=stakcopy(out); + if(cmd_completion && mode=='\\') + out = strcopy(begin,path_basename(cp= *com++)); + else if(mode=='*') + { + if(ep->e_nlist && dir && var) + { + if(*cp==var) + cp++; + else + *begin++ = var; + out = strcopy(begin,cp); + var = 0; + } + else + out = strcopy(begin,fmtx(*com)); + com++; + } + else + out = strcopy(begin,*com++); + if(mode=='\\') + { + saveout= ++out; + while (*com && *begin) + { + if(cmd_completion) + out = overlaid(begin,path_basename(*com++),nocase); + else + out = overlaid(begin,*com++,nocase); + } + mode = (out==saveout); + if(out[-1]==0) + out--; + if(mode && out[-1]!='/') + { + if(cmd_completion) + { + Namval_t *np; + /* add as tracked alias */ + Pathcomp_t *pp; + if(*cp=='/' && (pp=path_dirfind(ep->sh->pathlist,cp,'/')) && (np=nv_search(begin,ep->sh->track_tree,NV_ADD))) + path_alias(np,pp); + out = strcopy(begin,cp); + } + /* add quotes if necessary */ + if((cp=fmtx(begin))!=begin) + out = strcopy(begin,cp); + if(var=='$' && begin[-1]=='{') + *out = '}'; + else + *out = ' '; + *++out = 0; + } + else if((cp=fmtx(begin))!=begin) + { + out = strcopy(begin,cp); + if(out[-1] =='"' || out[-1]=='\'') + *--out = 0; + } + if(*begin==0) + ed_ringbell(); + } + else + { + while (*com) + { + *out++ = ' '; + out = strcopy(out,fmtx(*com++)); + } + } + if(ep->e_nlist) + { + cp = com[-1]; + if(cp[strlen(cp)-1]!='/') + { + if(var=='$' && begin[-1]=='{') + *out = '}'; + else + *out = ' '; + out++; + } + else if(out[-1] =='"' || out[-1]=='\'') + out--; + *out = 0; + } + *cur = (out-outbuff); + /* restore rest of buffer */ + if(left) + out = strcopy(out,left); + *eol = (out-outbuff); + } + done: + sh_offstate(SH_FCOMPLETE); + if(!ep->e_nlist) + stakset(ep->e_stkptr,ep->e_stkoff); + if(nomarkdirs) + sh_offoption(SH_MARKDIRS); +#if SHOPT_MULTIBYTE + { + register int c,n=0; + /* first re-adjust cur */ + c = outbuff[*cur]; + outbuff[*cur] = 0; + for(out=outbuff; *out;n++) + mbchar(out); + outbuff[*cur] = c; + *cur = n; + outbuff[*eol+1] = 0; + *eol = ed_internal(outbuff,(genchar*)outbuff); + } +#endif /* SHOPT_MULTIBYTE */ + return(rval); +} + +/* + * look for edit macro named _i + * if found, puts the macro definition into lookahead buffer and returns 1 + */ +int ed_macro(Edit_t *ep, register int i) +{ + register char *out; + Namval_t *np; + genchar buff[LOOKAHEAD+1]; + if(i != '@') + ep->e_macro[1] = i; + /* undocumented feature, macros of the form <ESC>[c evoke alias __c */ + if(i=='_') + ep->e_macro[2] = ed_getchar(ep,1); + else + ep->e_macro[2] = 0; + if (isalnum(i)&&(np=nv_search(ep->e_macro,ep->sh->alias_tree,HASH_SCOPE))&&(out=nv_getval(np))) + { +#if SHOPT_MULTIBYTE + /* copy to buff in internal representation */ + int c = 0; + if( strlen(out) > LOOKAHEAD ) + { + c = out[LOOKAHEAD]; + out[LOOKAHEAD] = 0; + } + i = ed_internal(out,buff); + if(c) + out[LOOKAHEAD] = c; +#else + strncpy((char*)buff,out,LOOKAHEAD); + buff[LOOKAHEAD] = 0; + i = strlen((char*)buff); +#endif /* SHOPT_MULTIBYTE */ + while(i-- > 0) + ed_ungetchar(ep,buff[i]); + return(1); + } + return(0); +} + +/* + * Enter the fc command on the current history line + */ +int ed_fulledit(Edit_t *ep) +{ + register char *cp; + if(!shgd->hist_ptr) + return(-1); + /* use EDITOR on current command */ + if(ep->e_hline == ep->e_hismax) + { + if(ep->e_eol<0) + return(-1); +#if SHOPT_MULTIBYTE + ep->e_inbuf[ep->e_eol+1] = 0; + ed_external(ep->e_inbuf, (char *)ep->e_inbuf); +#endif /* SHOPT_MULTIBYTE */ + sfwrite(shgd->hist_ptr->histfp,(char*)ep->e_inbuf,ep->e_eol+1); + sh_onstate(SH_HISTORY); + hist_flush(shgd->hist_ptr); + } + cp = strcopy((char*)ep->e_inbuf,e_runvi); + cp = strcopy(cp, fmtbase((long)ep->e_hline,10,0)); + ep->e_eol = ((unsigned char*)cp - (unsigned char*)ep->e_inbuf)-(sh_isoption(SH_VI)!=0); + return(0); +} diff --git a/src/cmd/ksh93/edit/edit.c b/src/cmd/ksh93/edit/edit.c new file mode 100644 index 0000000..b050f60 --- /dev/null +++ b/src/cmd/ksh93/edit/edit.c @@ -0,0 +1,1806 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2012 AT&T Intellectual Property * +* and is licensed under the * +* Eclipse Public License, Version 1.0 * +* by AT&T Intellectual Property * +* * +* A copy of the License is available at * +* http://www.eclipse.org/org/documents/epl-v10.html * +* (with md5 checksum b35adb5213ca9657e911e9befb180842) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * edit.c - common routines for vi and emacs one line editors in shell + * + * David Korn P.D. Sullivan + * AT&T Labs + * + * Coded April 1983. + */ + +#include <ast.h> +#include <errno.h> +#include <ccode.h> +#include "FEATURE/options" +#include "FEATURE/time" +#include "FEATURE/cmds" +#ifdef _hdr_utime +# include <utime.h> +# include <ls.h> +#endif + +#if KSHELL +# include "defs.h" +# include "variables.h" +#else +# include <ctype.h> + extern char ed_errbuf[]; + char e_version[] = "\n@(#)$Id: Editlib version 1993-12-28 r $\0\n"; +#endif /* KSHELL */ +#include "io.h" +#include "terminal.h" +#include "history.h" +#include "edit.h" + +static char CURSOR_UP[20] = { ESC, '[', 'A', 0 }; +static char KILL_LINE[20] = { ESC, '[', 'J', 0 }; + + + +#if SHOPT_MULTIBYTE +# define is_cntrl(c) ((c<=STRIP) && iscntrl(c)) +# define is_print(c) ((c&~STRIP) || isprint(c)) +#else +# define is_cntrl(c) iscntrl(c) +# define is_print(c) isprint(c) +#endif + +#if (CC_NATIVE == CC_ASCII) +# define printchar(c) ((c) ^ ('A'-cntl('A'))) +#else + static int printchar(int c) + { + switch(c) + { + + case cntl('A'): return('A'); + case cntl('B'): return('B'); + case cntl('C'): return('C'); + case cntl('D'): return('D'); + case cntl('E'): return('E'); + case cntl('F'): return('F'); + case cntl('G'): return('G'); + case cntl('H'): return('H'); + case cntl('I'): return('I'); + case cntl('J'): return('J'); + case cntl('K'): return('K'); + case cntl('L'): return('L'); + case cntl('M'): return('M'); + case cntl('N'): return('N'); + case cntl('O'): return('O'); + case cntl('P'): return('P'); + case cntl('Q'): return('Q'); + case cntl('R'): return('R'); + case cntl('S'): return('S'); + case cntl('T'): return('T'); + case cntl('U'): return('U'); + case cntl('V'): return('V'); + case cntl('W'): return('W'); + case cntl('X'): return('X'); + case cntl('Y'): return('Y'); + case cntl('Z'): return('Z'); + case cntl(']'): return(']'); + case cntl('['): return('['); + } + return('?'); + } +#endif +#define MINWINDOW 15 /* minimum width window */ +#define DFLTWINDOW 80 /* default window width */ +#define RAWMODE 1 +#define ALTMODE 2 +#define ECHOMODE 3 +#define SYSERR -1 + +#if SHOPT_OLDTERMIO +# undef tcgetattr +# undef tcsetattr +#endif /* SHOPT_OLDTERMIO */ + +#ifdef RT +# define VENIX 1 +#endif /* RT */ + + +#ifdef _hdr_sgtty +# ifdef TIOCGETP + 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 /* TIOCGETP */ +#endif /* _hdr_sgtty */ + +#if KSHELL + static int keytrap(Edit_t *,char*, int, int, int); +#else + Edit_t editb; +#endif /* KSHELL */ + + +#ifndef _POSIX_DISABLE +# define _POSIX_DISABLE 0 +#endif + +#ifdef future + static int compare(const char*, const char*, int); +#endif /* future */ +#if SHOPT_VSH || SHOPT_ESH +# define ttyparm (ep->e_ttyparm) +# define nttyparm (ep->e_nttyparm) + static const char bellchr[] = "\a"; /* bell char */ +#endif /* SHOPT_VSH || SHOPT_ESH */ + + +/* + * This routine returns true if fd refers to a terminal + * This should be equivalent to isatty + */ +int tty_check(int fd) +{ + register Edit_t *ep = (Edit_t*)(shgd->ed_context); + struct termios tty; + ep->e_savefd = -1; + return(tty_get(fd,&tty)==0); +} + +/* + * Get the current terminal attributes + * This routine remembers the attributes and just returns them if it + * is called again without an intervening tty_set() + */ + +int tty_get(register int fd, register struct termios *tty) +{ + register Edit_t *ep = (Edit_t*)(shgd->ed_context); + if(fd == ep->e_savefd) + *tty = ep->e_savetty; + else + { + while(tcgetattr(fd,tty) == SYSERR) + { + if(errno !=EINTR) + return(SYSERR); + errno = 0; + } + /* save terminal settings if in cannonical state */ + if(ep->e_raw==0) + { + ep->e_savetty = *tty; + ep->e_savefd = fd; + } + } + return(0); +} + +/* + * Set the terminal attributes + * If fd<0, then current attributes are invalidated + */ + +int tty_set(int fd, int action, struct termios *tty) +{ + register Edit_t *ep = (Edit_t*)(shgd->ed_context); + if(fd >=0) + { +#ifdef future + if(ep->e_savefd>=0 && compare(&ep->e_savetty,tty,sizeof(struct termios))) + return(0); +#endif + while(tcsetattr(fd, action, tty) == SYSERR) + { + if(errno !=EINTR) + return(SYSERR); + errno = 0; + } + ep->e_savetty = *tty; + } + ep->e_savefd = fd; + return(0); +} + +#if SHOPT_ESH || SHOPT_VSH +/*{ TTY_COOKED( fd ) + * + * This routine will set the tty in cooked mode. + * It is also called by error.done(). + * +}*/ + +void tty_cooked(register int fd) +{ + register Edit_t *ep = (Edit_t*)(shgd->ed_context); + ep->e_keytrap = 0; + if(ep->e_raw==0) + return; + if(fd < 0) + fd = ep->e_savefd; +#ifdef L_MASK + /* 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; +#endif /* L_MASK */ + /*** don't do tty_set unless ttyparm has valid data ***/ + if(tty_set(fd, TCSANOW, &ttyparm) == SYSERR) + return; + ep->e_raw = 0; + return; +} + +/*{ TTY_RAW( fd ) + * + * This routine will set the tty in raw mode. + * +}*/ + +int tty_raw(register int fd, int echomode) +{ + int echo = echomode; +#ifdef L_MASK + struct ltchars lchars; +#endif /* L_MASK */ + register Edit_t *ep = (Edit_t*)(shgd->ed_context); + if(ep->e_raw==RAWMODE) + return(echo?-1:0); + else if(ep->e_raw==ECHOMODE) + return(echo?0:-1); +#if !SHOPT_RAWONLY + if(ep->e_raw != ALTMODE) +#endif /* SHOPT_RAWONLY */ + { + if(tty_get(fd,&ttyparm) == SYSERR) + return(-1); + } +#if L_MASK || VENIX + if(ttyparm.sg_flags&LCASE) + return(-1); + if(!(ttyparm.sg_flags&ECHO)) + { + if(!echomode) + return(-1); + echo = 0; + } + nttyparm = ttyparm; + if(!echo) + nttyparm.sg_flags &= ~(ECHO | TBDELAY); +# ifdef CBREAK + nttyparm.sg_flags |= CBREAK; +# else + nttyparm.sg_flags |= RAW; +# endif /* CBREAK */ + ep->e_erase = ttyparm.sg_erase; + ep->e_kill = ttyparm.sg_kill; + ep->e_eof = cntl('D'); + ep->e_werase = cntl('W'); + ep->e_lnext = cntl('V'); + if( tty_set(fd, TCSADRAIN, &nttyparm) == SYSERR ) + return(-1); + ep->e_ttyspeed = (ttyparm.sg_ospeed>=B1200?FAST:SLOW); +# ifdef TIOCGLTC + /* try to remove effect of ^V and ^Y and ^O */ + if(ioctl(fd,TIOCGLTC,&l_chars) != SYSERR) + { + lchars = l_chars; + lchars.t_lnextc = -1; + lchars.t_flushc = -1; + lchars.t_dsuspc = -1; /* no delayed stop process signal */ + if(ioctl(fd,TIOCSLTC,&lchars) != SYSERR) + l_changed |= L_CHARS; + } +# endif /* TIOCGLTC */ +#else + if (!(ttyparm.c_lflag & ECHO )) + { + if(!echomode) + return(-1); + echo = 0; + } +# ifdef FLUSHO + ttyparm.c_lflag &= ~FLUSHO; +# endif /* FLUSHO */ + nttyparm = ttyparm; +# ifndef u370 + nttyparm.c_iflag &= ~(IGNPAR|PARMRK|INLCR|IGNCR|ICRNL); + nttyparm.c_iflag |= BRKINT; +# else + nttyparm.c_iflag &= + ~(IGNBRK|PARMRK|INLCR|IGNCR|ICRNL|INPCK); + nttyparm.c_iflag |= (BRKINT|IGNPAR); +# endif /* u370 */ + if(echo) + nttyparm.c_lflag &= ~(ICANON|ISIG); + else + nttyparm.c_lflag &= ~(ICANON|ISIG|ECHO|ECHOK); + nttyparm.c_cc[VTIME] = 0; + nttyparm.c_cc[VMIN] = 1; +# ifdef VREPRINT + nttyparm.c_cc[VREPRINT] = _POSIX_DISABLE; +# endif /* VREPRINT */ +# ifdef VDISCARD + nttyparm.c_cc[VDISCARD] = _POSIX_DISABLE; +# endif /* VDISCARD */ +# ifdef VDSUSP + nttyparm.c_cc[VDSUSP] = _POSIX_DISABLE; +# endif /* VDSUSP */ +# ifdef VWERASE + if(ttyparm.c_cc[VWERASE] == _POSIX_DISABLE) + ep->e_werase = cntl('W'); + else + ep->e_werase = nttyparm.c_cc[VWERASE]; + nttyparm.c_cc[VWERASE] = _POSIX_DISABLE; +# else + ep->e_werase = cntl('W'); +# endif /* VWERASE */ +# ifdef VLNEXT + if(ttyparm.c_cc[VLNEXT] == _POSIX_DISABLE ) + ep->e_lnext = cntl('V'); + else + ep->e_lnext = nttyparm.c_cc[VLNEXT]; + nttyparm.c_cc[VLNEXT] = _POSIX_DISABLE; +# else + ep->e_lnext = cntl('V'); +# endif /* VLNEXT */ + ep->e_intr = ttyparm.c_cc[VINTR]; + ep->e_eof = ttyparm.c_cc[VEOF]; + ep->e_erase = ttyparm.c_cc[VERASE]; + ep->e_kill = ttyparm.c_cc[VKILL]; + if( tty_set(fd, TCSADRAIN, &nttyparm) == SYSERR ) + return(-1); + ep->e_ttyspeed = (cfgetospeed(&ttyparm)>=B1200?FAST:SLOW); +#endif + ep->e_raw = (echomode?ECHOMODE:RAWMODE); + return(0); +} + +#if !SHOPT_RAWONLY + +/* + * + * Get tty parameters and make ESC and '\r' wakeup characters. + * + */ + +# ifdef TIOCGETC +int tty_alt(register int fd) +{ + register Edit_t *ep = (Edit_t*)(shgd->ed_context); + int mask; + struct tchars ttychars; + switch(ep->e_raw) + { + case ECHOMODE: + return(-1); + case ALTMODE: + return(0); + case RAWMODE: + tty_cooked(fd); + } + l_changed = 0; + if( ep->e_ttyspeed == 0) + { + if((tty_get(fd,&ttyparm) != SYSERR)) + ep->e_ttyspeed = (ttyparm.sg_ospeed>=B1200?FAST:SLOW); + ep->e_raw = ALTMODE; + } + if(ioctl(fd,TIOCGETC,&l_ttychars) == SYSERR) + return(-1); + if(ioctl(fd,TIOCLGET,&l_mask)==SYSERR) + return(-1); + 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(-1); + if(ttychars.t_brkc!=ESC) + { + ttychars.t_brkc = ESC; + l_changed |= T_CHARS; + if(ioctl(fd,TIOCSETC,&ttychars) == SYSERR) + return(-1); + } + return(0); +} +# else +# ifndef PENDIN +# define PENDIN 0 +# endif /* PENDIN */ +# ifndef IEXTEN +# define IEXTEN 0 +# endif /* IEXTEN */ + +int tty_alt(register int fd) +{ + register Edit_t *ep = (Edit_t*)(shgd->ed_context); + switch(ep->e_raw) + { + case ECHOMODE: + return(-1); + case ALTMODE: + return(0); + case RAWMODE: + tty_cooked(fd); + } + if((tty_get(fd, &ttyparm)==SYSERR) || (!(ttyparm.c_lflag&ECHO))) + return(-1); +# ifdef FLUSHO + ttyparm.c_lflag &= ~FLUSHO; +# endif /* FLUSHO */ + nttyparm = ttyparm; + ep->e_eof = ttyparm.c_cc[VEOF]; +# ifdef ECHOCTL + /* escape character echos as ^[ */ + nttyparm.c_lflag |= (ECHOE|ECHOK|ECHOCTL|PENDIN|IEXTEN); + nttyparm.c_cc[VEOL] = ESC; +# else + /* switch VEOL2 and EOF, since EOF isn't echo'd by driver */ + nttyparm.c_lflag |= (ECHOE|ECHOK); + nttyparm.c_cc[VEOF] = ESC; /* make ESC the eof char */ +# ifdef VEOL2 + nttyparm.c_iflag &= ~(IGNCR|ICRNL); + nttyparm.c_iflag |= INLCR; + nttyparm.c_cc[VEOL] = '\r'; /* make CR an eol char */ + nttyparm.c_cc[VEOL2] = ep->e_eof; /* make EOF an eol char */ +# else + nttyparm.c_cc[VEOL] = ep->e_eof; /* make EOF an eol char */ +# endif /* VEOL2 */ +# endif /* ECHOCTL */ +# ifdef VREPRINT + nttyparm.c_cc[VREPRINT] = _POSIX_DISABLE; +# endif /* VREPRINT */ +# ifdef VDISCARD + nttyparm.c_cc[VDISCARD] = _POSIX_DISABLE; +# endif /* VDISCARD */ +# ifdef VWERASE + if(ttyparm.c_cc[VWERASE] == _POSIX_DISABLE) + nttyparm.c_cc[VWERASE] = cntl('W'); + ep->e_werase = nttyparm.c_cc[VWERASE]; +# else + ep->e_werase = cntl('W'); +# endif /* VWERASE */ +# ifdef VLNEXT + if(ttyparm.c_cc[VLNEXT] == _POSIX_DISABLE ) + nttyparm.c_cc[VLNEXT] = cntl('V'); + ep->e_lnext = nttyparm.c_cc[VLNEXT]; +# else + ep->e_lnext = cntl('V'); +# endif /* VLNEXT */ + ep->e_erase = ttyparm.c_cc[VERASE]; + ep->e_kill = ttyparm.c_cc[VKILL]; + if( tty_set(fd, TCSADRAIN, &nttyparm) == SYSERR ) + return(-1); + ep->e_ttyspeed = (cfgetospeed(&ttyparm)>=B1200?FAST:SLOW); + ep->e_raw = ALTMODE; + return(0); +} + +# endif /* TIOCGETC */ +#endif /* SHOPT_RAWONLY */ + +/* + * ED_WINDOW() + * + * return the window size + */ +int ed_window(void) +{ + int rows,cols; + register char *cp = nv_getval(COLUMNS); + if(cp) + cols = (int)strtol(cp, (char**)0, 10)-1; + else + { + astwinsize(2,&rows,&cols); + if(--cols <0) + cols = DFLTWINDOW-1; + } + if(cols < MINWINDOW) + cols = MINWINDOW; + else if(cols > MAXWINDOW) + cols = MAXWINDOW; + return(cols); +} + +/* E_FLUSH() + * + * Flush the output buffer. + * + */ + +void ed_flush(Edit_t *ep) +{ + register int n = ep->e_outptr-ep->e_outbase; + register int fd = ERRIO; + if(n<=0) + return; + write(fd,ep->e_outbase,(unsigned)n); + ep->e_outptr = ep->e_outbase; +} + +/* + * send the bell character ^G to the terminal + */ + +void ed_ringbell(void) +{ + write(ERRIO,bellchr,1); +} + +/* + * send a carriage return line feed to the terminal + */ + +void ed_crlf(register Edit_t *ep) +{ +#ifdef cray + ed_putchar(ep,'\r'); +#endif /* cray */ +#ifdef u370 + ed_putchar(ep,'\r'); +#endif /* u370 */ +#ifdef VENIX + ed_putchar(ep,'\r'); +#endif /* VENIX */ + ed_putchar(ep,'\n'); + ed_flush(ep); +} + +/* ED_SETUP( max_prompt_size ) + * + * This routine sets up the prompt string + * The following is an unadvertised feature. + * Escape sequences in the prompt can be excluded from the calculated + * prompt length. This is accomplished as follows: + * - if the prompt string starts with "%\r, or contains \r%\r", where % + * represents any char, then % is taken to be the quote character. + * - strings enclosed by this quote character, and the quote character, + * are not counted as part of the prompt length. + */ + +void ed_setup(register Edit_t *ep, int fd, int reedit) +{ + Shell_t *shp = ep->sh; + register char *pp; + register char *last, *prev; + char *ppmax; + int myquote = 0, n; + register int qlen = 1, qwid; + char inquote = 0; + ep->e_fd = fd; + ep->e_multiline = sh_isoption(SH_MULTILINE)!=0; +#ifdef SIGWINCH + if(!(shp->sigflag[SIGWINCH]&SH_SIGFAULT)) + { + signal(SIGWINCH,sh_fault); + shp->sigflag[SIGWINCH] |= SH_SIGFAULT; + } + pp = shp->st.trapcom[SIGWINCH]; + shp->st.trapcom[SIGWINCH] = 0; + sh_fault(SIGWINCH); + shp->st.trapcom[SIGWINCH] = pp; + ep->sh->winch = 0; +#endif +#if SHOPT_EDPREDICT + ep->hlist = 0; + ep->nhlist = 0; + ep->hoff = 0; +#endif /* SHOPT_EDPREDICT */ +#if KSHELL + ep->e_stkptr = stakptr(0); + ep->e_stkoff = staktell(); + if(!(last = shp->prompt)) + last = ""; + shp->prompt = 0; +#else + last = ep->e_prbuff; +#endif /* KSHELL */ + if(shp->gd->hist_ptr) + { + register History_t *hp = shp->gd->hist_ptr; + ep->e_hismax = hist_max(hp); + ep->e_hismin = hist_min(hp); + } + else + { + ep->e_hismax = ep->e_hismin = ep->e_hloff = 0; + } + ep->e_hline = ep->e_hismax; + if(!sh_isoption(SH_VI) && !sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS)) + ep->e_wsize = MAXLINE; + else + ep->e_wsize = ed_window()-2; + ep->e_winsz = ep->e_wsize+2; + ep->e_crlf = 1; + ep->e_plen = 0; + pp = ep->e_prompt; + ppmax = pp+PRSIZE-1; + *pp++ = '\r'; + { + register int c; + while(prev = last, c = mbchar(last)) switch(c) + { + case ESC: + { + int skip=0; + ep->e_crlf = 0; + *pp++ = c; + for(n=1; c = *last++; n++) + { + if(pp < ppmax) + *pp++ = c; + if(c=='\a' || c==ESC || c=='\r') + break; + if(skip || (c>='0' && c<='9')) + { + skip = 0; + continue; + } + if(n>1 && c==';') + skip = 1; + else if(n>2 || (c!= '[' && c!= ']')) + break; + } + if(c==0 || c==ESC || c=='\r') + last--; + qlen += (n+1); + break; + } + case '\b': + if(pp>ep->e_prompt+1) + pp--; + break; + case '\r': + if(pp == (ep->e_prompt+2)) /* quote char */ + myquote = *(pp-1); + /*FALLTHROUGH*/ + + case '\n': + /* start again */ + ep->e_crlf = 1; + qlen = 1; + inquote = 0; + pp = ep->e_prompt+1; + break; + + case '\t': + /* expand tabs */ + while((pp-ep->e_prompt)%TABSIZE) + { + if(pp >= ppmax) + break; + *pp++ = ' '; + } + break; + + case '\a': + /* cut out bells */ + break; + + default: + if(c==myquote) + { + qlen += inquote; + inquote ^= 1; + } + if(pp < ppmax) + { + if(inquote) + qlen++; + else if(!is_print(c)) + ep->e_crlf = 0; + if((qwid = last - prev) > 1) + qlen += qwid - mbwidth(c); + while(prev < last && pp < ppmax) + *pp++ = *prev++; + } + break; + } + } + if(pp-ep->e_prompt > qlen) + ep->e_plen = pp - ep->e_prompt - qlen; + *pp = 0; + if(!ep->e_multiline && (ep->e_wsize -= ep->e_plen) < 7) + { + register int shift = 7-ep->e_wsize; + ep->e_wsize = 7; + pp = ep->e_prompt+1; + strcpy(pp,pp+shift); + ep->e_plen -= shift; + last[-ep->e_plen-2] = '\r'; + } + sfsync(sfstderr); + if(fd == sffileno(sfstderr)) + { + /* can't use output buffer when reading from stderr */ + static char *buff; + if(!buff) + buff = (char*)malloc(MAXLINE); + ep->e_outbase = ep->e_outptr = buff; + ep->e_outlast = ep->e_outptr + MAXLINE; + return; + } + qlen = sfset(sfstderr,SF_READ,0); + /* make sure SF_READ not on */ + ep->e_outbase = ep->e_outptr = (char*)sfreserve(sfstderr,SF_UNBOUND,SF_LOCKR); + ep->e_outlast = ep->e_outptr + sfvalue(sfstderr); + if(qlen) + sfset(sfstderr,SF_READ,1); + sfwrite(sfstderr,ep->e_outptr,0); + ep->e_eol = reedit; + if(ep->e_multiline) + { +#ifdef _cmd_tput + char *term; + if(!ep->e_term) + ep->e_term = nv_search("TERM",shp->var_tree,0); + if(ep->e_term && (term=nv_getval(ep->e_term)) && strlen(term)<sizeof(ep->e_termname) && strcmp(term,ep->e_termname)) + { + sh_trap(".sh.subscript=$(tput cuu1 2>/dev/null)",0); + if(pp=nv_getval(SH_SUBSCRNOD)) + strncpy(CURSOR_UP,pp,sizeof(CURSOR_UP)-1); + nv_unset(SH_SUBSCRNOD); + strcpy(ep->e_termname,term); + } +#endif + ep->e_wsize = MAXLINE - (ep->e_plen+1); + } + if(ep->e_default && (pp = nv_getval(ep->e_default))) + { + n = strlen(pp); + if(n > LOOKAHEAD) + n = LOOKAHEAD; + ep->e_lookahead = n; + while(n-- > 0) + ep->e_lbuf[n] = *pp++; + ep->e_default = 0; + } +} + +static void ed_putstring(register Edit_t *ep, const char *str) +{ + register int c; + while(c = *str++) + ed_putchar(ep,c); +} + +static void ed_nputchar(register Edit_t *ep, int n, int c) +{ + while(n-->0) + ed_putchar(ep,c); +} + +/* + * Do read, restart on interrupt unless SH_SIGSET or SH_SIGTRAP is set + * Use sfpkrd() to poll() or select() to wait for input if possible + * Unfortunately, systems that get interrupted from slow reads update + * this access time for for the terminal (in violation of POSIX). + * The fixtime() macro, resets the time to the time at entry in + * this case. This is not necessary for systems that can handle + * sfpkrd() correctly (i,e., those that support poll() or select() + */ +int ed_read(void *context, int fd, char *buff, int size, int reedit) +{ + register Edit_t *ep = (Edit_t*)context; + register int rv= -1; + register int delim = (ep->e_raw==RAWMODE?'\r':'\n'); + Shell_t *shp = ep->sh; + int mode = -1; + int (*waitevent)(int,long,int) = shp->gd->waitevent; + if(ep->e_raw==ALTMODE) + mode = 1; + if(size < 0) + { + mode = 1; + size = -size; + } + sh_onstate(SH_TTYWAIT); + errno = EINTR; + shp->gd->waitevent = 0; + while(rv<0 && errno==EINTR) + { + if(shp->trapnote&(SH_SIGSET|SH_SIGTRAP)) + goto done; + if(ep->sh->winch && sh_isstate(SH_INTERACTIVE) && (sh_isoption(SH_VI) || sh_isoption(SH_EMACS))) + { + Edpos_t lastpos; + int n, rows, newsize; + /* move cursor to start of first line */ + ed_putchar(ep,'\r'); + ed_flush(ep); + astwinsize(2,&rows,&newsize); + n = (ep->e_plen+ep->e_cur)/++ep->e_winsz; + while(n--) + ed_putstring(ep,CURSOR_UP); + if(ep->e_multiline && newsize>ep->e_winsz && (lastpos.line=(ep->e_plen+ep->e_peol)/ep->e_winsz)) + { + /* clear the current command line */ + n = lastpos.line; + while(lastpos.line--) + { + ed_nputchar(ep,ep->e_winsz,' '); + ed_putchar(ep,'\n'); + } + ed_nputchar(ep,ep->e_winsz,' '); + while(n--) + ed_putstring(ep,CURSOR_UP); + } + ep->sh->winch = 0; + ed_flush(ep); + sh_delay(.05); + astwinsize(2,&rows,&newsize); + ep->e_winsz = newsize-1; + if(ep->e_winsz < MINWINDOW) + ep->e_winsz = MINWINDOW; + if(!ep->e_multiline && ep->e_wsize < MAXLINE) + ep->e_wsize = ep->e_winsz-2; + ep->e_nocrnl=1; + if(*ep->e_vi_insert) + { + buff[0] = ESC; + buff[1] = cntl('L'); + buff[2] = 'a'; + return(3); + } + if(sh_isoption(SH_EMACS) || sh_isoption(SH_VI)) + buff[0] = cntl('L'); + return(1); + } + else + ep->sh->winch = 0; + /* an interrupt that should be ignored */ + errno = 0; + if(!waitevent || (rv=(*waitevent)(fd,-1L,0))>=0) + rv = sfpkrd(fd,buff,size,delim,-1L,mode); + } + if(rv < 0) + { +#ifdef _hdr_utime +# define fixtime() if(isdevtty)utime(ep->e_tty,&utimes) + int isdevtty=0; + struct stat statb; + struct utimbuf utimes; + if(errno==0 && !ep->e_tty) + { + if((ep->e_tty=ttyname(fd)) && stat(ep->e_tty,&statb)>=0) + { + ep->e_tty_ino = statb.st_ino; + ep->e_tty_dev = statb.st_dev; + } + } + if(ep->e_tty_ino && fstat(fd,&statb)>=0 && statb.st_ino==ep->e_tty_ino && statb.st_dev==ep->e_tty_dev) + { + utimes.actime = statb.st_atime; + utimes.modtime = statb.st_mtime; + isdevtty=1; + } +#else +# define fixtime() +#endif /* _hdr_utime */ + while(1) + { + rv = read(fd,buff,size); + if(rv>=0 || errno!=EINTR) + break; + if(shp->trapnote&(SH_SIGSET|SH_SIGTRAP)) + goto done; + /* an interrupt that should be ignored */ + fixtime(); + } + } + else if(rv>=0 && mode>0) + rv = read(fd,buff,rv>0?rv:1); +done: + shp->gd->waitevent = waitevent; + sh_offstate(SH_TTYWAIT); + return(rv); +} + + +/* + * put <string> of length <nbyte> onto lookahead stack + * if <type> is non-zero, the negation of the character is put + * onto the stack so that it can be checked for KEYTRAP + * putstack() returns 1 except when in the middle of a multi-byte char + */ +static int putstack(Edit_t *ep,char string[], register int nbyte, int type) +{ + register int c; +#if SHOPT_MULTIBYTE + char *endp, *p=string; + int size, offset = ep->e_lookahead + nbyte; + *(endp = &p[nbyte]) = 0; + endp = &p[nbyte]; + do + { + c = (int)((*p) & STRIP); + if(c< 0x80 && c!='<') + { + if (type) + c = -c; +# ifndef CBREAK + if(c == '\0') + { + /*** user break key ***/ + ep->e_lookahead = 0; +# if KSHELL + sh_fault(SIGINT); + siglongjmp(ep->e_env, UINTR); +# endif /* KSHELL */ + } +# endif /* CBREAK */ + + } + else + { + again: + if((c=mbchar(p)) >=0) + { + p--; /* incremented below */ + if(type) + c = -c; + } +#ifdef EILSEQ + else if(errno == EILSEQ) + errno = 0; +#endif + else if((endp-p) < mbmax()) + { + if ((c=ed_read(ep,ep->e_fd,endp, 1,0)) == 1) + { + *++endp = 0; + goto again; + } + return(c); + } + else + { + ed_ringbell(); + c = -(int)((*p) & STRIP); + offset += mbmax()-1; + } + } + ep->e_lbuf[--offset] = c; + p++; + } + while (p < endp); + /* shift lookahead buffer if necessary */ + if(offset -= ep->e_lookahead) + { + for(size=offset;size < nbyte;size++) + ep->e_lbuf[ep->e_lookahead+size-offset] = ep->e_lbuf[ep->e_lookahead+size]; + } + ep->e_lookahead += nbyte-offset; +#else + while (nbyte > 0) + { + c = string[--nbyte] & STRIP; + ep->e_lbuf[ep->e_lookahead++] = (type?-c:c); +# ifndef CBREAK + if( c == '\0' ) + { + /*** user break key ***/ + ep->e_lookahead = 0; +# if KSHELL + sh_fault(SIGINT); + siglongjmp(ep->e_env, UINTR); +# endif /* KSHELL */ + } +# endif /* CBREAK */ + } +#endif /* SHOPT_MULTIBYTE */ + return(1); +} + +/* + * routine to perform read from terminal for vi and emacs mode + * <mode> can be one of the following: + * -2 vi insert mode - key binding is in effect + * -1 vi control mode - key binding is in effect + * 0 normal command mode - key binding is in effect + * 1 edit keys not mapped + * 2 Next key is literal + */ +int ed_getchar(register Edit_t *ep,int mode) +{ + register int n, c; + char readin[LOOKAHEAD+1]; + if(!ep->e_lookahead) + { + ed_flush(ep); + ep->e_inmacro = 0; + /* The while is necessary for reads of partial multbyte chars */ + *ep->e_vi_insert = (mode==-2); + if((n=ed_read(ep,ep->e_fd,readin,-LOOKAHEAD,0)) > 0) + n = putstack(ep,readin,n,1); + *ep->e_vi_insert = 0; + } + if(ep->e_lookahead) + { + /* check for possible key mapping */ + if((c = ep->e_lbuf[--ep->e_lookahead]) < 0) + { + if(mode<=0 && -c == ep->e_intr) + { + sh_fault(SIGINT); + siglongjmp(ep->e_env, UINTR); + } + if(mode<=0 && ep->sh->st.trap[SH_KEYTRAP]) + { + ep->e_keytrap = 1; + n=1; + if((readin[0]= -c) == ESC) + { + while(1) + { + if(!ep->e_lookahead) + { + if((c=sfpkrd(ep->e_fd,readin+n,1,'\r',(mode?400L:-1L),0))>0) + putstack(ep,readin+n,c,1); + } + if(!ep->e_lookahead) + break; + if((c=ep->e_lbuf[--ep->e_lookahead])>=0) + { + ep->e_lookahead++; + break; + } + c = -c; + readin[n++] = c; + if(c>='0' && c<='9' && n>2) + continue; + if(n>2 || (c!= '[' && c!= 'O')) + break; + } + } + if(n=keytrap(ep,readin,n,LOOKAHEAD-n,mode)) + { + putstack(ep,readin,n,0); + c = ep->e_lbuf[--ep->e_lookahead]; + } + else + c = ed_getchar(ep,mode); + ep->e_keytrap = 0; + } + else + c = -c; + } + /*** map '\r' to '\n' ***/ + if(c == '\r' && mode!=2) + c = '\n'; + if(ep->e_tabcount && !(c=='\t'||c==ESC || c=='\\' || c=='=' || c==cntl('L') || isdigit(c))) + ep->e_tabcount = 0; + } + else + siglongjmp(ep->e_env,(n==0?UEOF:UINTR)); + return(c); +} + +void ed_ungetchar(Edit_t *ep,register int c) +{ + if (ep->e_lookahead < LOOKAHEAD) + ep->e_lbuf[ep->e_lookahead++] = c; + return; +} + +/* + * put a character into the output buffer + */ + +void ed_putchar(register Edit_t *ep,register int c) +{ + char buf[8]; + register char *dp = ep->e_outptr; + register int i,size=1; + if(!dp) + return; + buf[0] = c; +#if SHOPT_MULTIBYTE + /* check for place holder */ + if(c == MARKER) + return; + if((size = mbconv(buf, (wchar_t)c)) > 1) + { + for (i = 0; i < (size-1); i++) + *dp++ = buf[i]; + c = buf[i]; + } + else + { + buf[0] = c; + size = 1; + } +#endif /* SHOPT_MULTIBYTE */ + if (buf[0] == '_' && size==1) + { + *dp++ = ' '; + *dp++ = '\b'; + } + *dp++ = c; + *dp = '\0'; + if(dp >= ep->e_outlast) + ed_flush(ep); + else + ep->e_outptr = dp; +} + +/* + * returns the line and column corresponding to offset <off> in the physical buffer + * if <cur> is non-zero and <= <off>, then correspodning <curpos> will start the search + */ +Edpos_t ed_curpos(Edit_t *ep,genchar *phys, int off, int cur, Edpos_t curpos) +{ + register genchar *sp=phys; + register int c=1, col=ep->e_plen; + Edpos_t pos; +#if SHOPT_MULTIBYTE + char p[16]; +#endif /* SHOPT_MULTIBYTE */ + if(cur && off>=cur) + { + sp += cur; + off -= cur; + pos = curpos; + col = pos.col; + } + else + { + pos.line = 0; + while(col > ep->e_winsz) + { + pos.line++; + col -= (ep->e_winsz+1); + } + } + while(off-->0) + { + if(c) + c = *sp++; +#if SHOPT_MULTIBYTE + if(c && (mbconv(p, (wchar_t)c))==1 && p[0]=='\n') +#else + if(c=='\n') +#endif /* SHOPT_MULTIBYTE */ + col = 0; + else + col++; + if(col > ep->e_winsz) + col = 0; + if(col==0) + pos.line++; + } + pos.col = col; + return(pos); +} + +int ed_setcursor(register Edit_t *ep,genchar *physical,register int old,register int new,int first) +{ + static int oldline; + register int delta; + int clear = 0; + Edpos_t newpos; + + delta = new - old; + if(first < 0) + { + first = 0; + clear = 1; + } + if( delta == 0 && !clear) + return(new); + if(ep->e_multiline) + { + ep->e_curpos = ed_curpos(ep, physical, old,0,ep->e_curpos); + if(clear && old>=ep->e_peol && (clear=ep->e_winsz-ep->e_curpos.col)>0) + { + ed_nputchar(ep,clear,' '); + ed_nputchar(ep,clear,'\b'); + return(new); + } + newpos = ed_curpos(ep, physical, new,old,ep->e_curpos); + if(ep->e_curpos.col==0 && ep->e_curpos.line>0 && oldline<ep->e_curpos.line && delta<0) + ed_putstring(ep,"\r\n"); + oldline = newpos.line; + if(ep->e_curpos.line > newpos.line) + { + int n,pline,plen=ep->e_plen; + for(;ep->e_curpos.line > newpos.line; ep->e_curpos.line--) + ed_putstring(ep,CURSOR_UP); + pline = plen/(ep->e_winsz+1); + if(newpos.line <= pline) + plen -= pline*(ep->e_winsz+1); + else + plen = 0; + if((n=plen- ep->e_curpos.col)>0) + { + ep->e_curpos.col += n; + ed_putchar(ep,'\r'); + if(!ep->e_crlf && pline==0) + ed_putstring(ep,ep->e_prompt); + else + { + int m = ep->e_winsz+1-plen; + ed_putchar(ep,'\n'); + n = plen; + if(m < ed_genlen(physical)) + { + while(physical[m] && n-->0) + ed_putchar(ep,physical[m++]); + } + ed_nputchar(ep,n,' '); + ed_putstring(ep,CURSOR_UP); + } + } + } + else if(ep->e_curpos.line < newpos.line) + { + ed_nputchar(ep, newpos.line-ep->e_curpos.line,'\n'); + ep->e_curpos.line = newpos.line; + ed_putchar(ep,'\r'); + ep->e_curpos.col = 0; + } + delta = newpos.col - ep->e_curpos.col; + old = new - delta; + } + else + newpos.line=0; + if(delta<0) + { + int bs= newpos.line && ep->e_plen>ep->e_winsz; + /*** move to left ***/ + delta = -delta; + /*** attempt to optimize cursor movement ***/ + if(!ep->e_crlf || bs || (2*delta <= ((old-first)+(newpos.line?0:ep->e_plen))) ) + { + ed_nputchar(ep,delta,'\b'); + delta = 0; + } + else + { + if(newpos.line==0) + ed_putstring(ep,ep->e_prompt); + else + { + first = 1+(newpos.line*ep->e_winsz - ep->e_plen); + ed_putchar(ep,'\r'); + } + old = first; + delta = new-first; + } + } + while(delta-->0) + ed_putchar(ep,physical[old++]); + return(new); +} + +/* + * copy virtual to physical and return the index for cursor in physical buffer + */ +int ed_virt_to_phys(Edit_t *ep,genchar *virt,genchar *phys,int cur,int voff,int poff) +{ + register genchar *sp = virt; + register genchar *dp = phys; + register int c; + genchar *curp = sp + cur; + genchar *dpmax = phys+MAXLINE; + int d, r; + sp += voff; + dp += poff; + for(r=poff;c= *sp;sp++) + { + if(curp == sp) + r = dp - phys; +#if SHOPT_MULTIBYTE + d = mbwidth((wchar_t)c); + if(d==1 && is_cntrl(c)) + d = -1; + 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 +#else + d = (is_cntrl(c)?-1:1); +#endif /* SHOPT_MULTIBYTE */ + if(d<0) + { + if(c=='\t') + { + c = dp-phys; + if(sh_isoption(SH_VI)) + c += ep->e_plen; + c = TABSIZE - c%TABSIZE; + while(--c>0) + *dp++ = ' '; + c = ' '; + } + else + { + *dp++ = '^'; + c = printchar(c); + } + /* in vi mode the cursor is at the last character */ + if(curp == sp && sh_isoption(SH_VI)) + r = dp - phys; + } + *dp++ = c; + if(dp>=dpmax) + break; + } + *dp = 0; + ep->e_peol = dp-phys; + return(r); +} + +#if SHOPT_MULTIBYTE +/* + * convert external representation <src> to an array of genchars <dest> + * <src> and <dest> can be the same + * returns number of chars in dest + */ + +int ed_internal(const char *src, genchar *dest) +{ + register const unsigned char *cp = (unsigned char *)src; + register int c; + register wchar_t *dp = (wchar_t*)dest; + if(dest == (genchar*)roundof(cp-(unsigned char*)0,sizeof(genchar))) + { + genchar buffer[MAXLINE]; + c = ed_internal(src,buffer); + ed_gencpy((genchar*)dp,buffer); + return(c); + } + while(*cp) + *dp++ = mbchar(cp); + *dp = 0; + return(dp-(wchar_t*)dest); +} + +/* + * convert internal representation <src> into character array <dest>. + * The <src> and <dest> may be the same. + * returns number of chars in dest. + */ + +int ed_external(const genchar *src, char *dest) +{ + register genchar wc; + register int c,size; + register char *dp = dest; + char *dpmax = dp+sizeof(genchar)*MAXLINE-2; + if((char*)src == dp) + { + char buffer[MAXLINE*sizeof(genchar)]; + c = ed_external(src,buffer); + +#ifdef _lib_wcscpy + wcscpy((wchar_t *)dest,(const wchar_t *)buffer); +#else + strcpy(dest,buffer); +#endif + return(c); + } + while((wc = *src++) && dp<dpmax) + { + if((size = mbconv(dp, wc)) < 0) + { + /* copy the character as is */ + size = 1; + *dp = wc; + } + dp += size; + } + *dp = 0; + return(dp-dest); +} + +/* + * copy <sp> to <dp> + */ + +void ed_gencpy(genchar *dp,const genchar *sp) +{ + dp = (genchar*)roundof((char*)dp-(char*)0,sizeof(genchar)); + sp = (const genchar*)roundof((char*)sp-(char*)0,sizeof(genchar)); + while(*dp++ = *sp++); +} + +/* + * copy at most <n> items from <sp> to <dp> + */ + +void ed_genncpy(register genchar *dp,register const genchar *sp, int n) +{ + dp = (genchar*)roundof((char*)dp-(char*)0,sizeof(genchar)); + sp = (const genchar*)roundof((char*)sp-(char*)0,sizeof(genchar)); + while(n-->0 && (*dp++ = *sp++)); +} + +#endif /* SHOPT_MULTIBYTE */ +/* + * find the string length of <str> + */ + +int ed_genlen(register const genchar *str) +{ + register const genchar *sp = str; + sp = (const genchar*)roundof((char*)sp-(char*)0,sizeof(genchar)); + while(*sp++); + return(sp-str-1); +} +#endif /* SHOPT_ESH || SHOPT_VSH */ + +#ifdef future +/* + * returns 1 when <n> bytes starting at <a> and <b> are equal + */ +static int compare(register const char *a,register const char *b,register int n) +{ + while(n-->0) + { + if(*a++ != *b++) + return(0); + } + return(1); +} +#endif + +#if SHOPT_OLDTERMIO + +# include <sys/termio.h> + +#ifndef ECHOCTL +# define ECHOCTL 0 +#endif /* !ECHOCTL */ +#define ott ep->e_ott + +/* + * For backward compatibility only + * This version will use termios when possible, otherwise termio + */ + +int tcgetattr(int fd, struct termios *tt) +{ + register Edit_t *ep = (Edit_t*)(shgd->ed_context); + register int r,i; + ep->e_tcgeta = 0; + ep->e_echoctl = (ECHOCTL!=0); + if((r=ioctl(fd,TCGETS,tt))>=0 || errno!=EINVAL) + return(r); + if((r=ioctl(fd,TCGETA,&ott)) >= 0) + { + tt->c_lflag = ott.c_lflag; + tt->c_oflag = ott.c_oflag; + tt->c_iflag = ott.c_iflag; + tt->c_cflag = ott.c_cflag; + for(i=0; i<NCC; i++) + tt->c_cc[i] = ott.c_cc[i]; + ep->e_tcgeta++; + ep->e_echoctl = 0; + } + return(r); +} + +int tcsetattr(int fd,int mode,struct termios *tt) +{ + register Edit_t *ep = (Edit_t*)(shgd->ed_context); + register int r; + if(ep->e_tcgeta) + { + register int i; + ott.c_lflag = tt->c_lflag; + ott.c_oflag = tt->c_oflag; + ott.c_iflag = tt->c_iflag; + ott.c_cflag = tt->c_cflag; + for(i=0; i<NCC; i++) + ott.c_cc[i] = tt->c_cc[i]; + if(tt->c_lflag&ECHOCTL) + { + ott.c_lflag &= ~(ECHOCTL|IEXTEN); + ott.c_iflag &= ~(IGNCR|ICRNL); + ott.c_iflag |= INLCR; + ott.c_cc[VEOF]= ESC; /* ESC -> eof char */ + ott.c_cc[VEOL] = '\r'; /* CR -> eol char */ + ott.c_cc[VEOL2] = tt->c_cc[VEOF]; /* EOF -> eol char */ + } + switch(mode) + { + case TCSANOW: + mode = TCSETA; + break; + case TCSADRAIN: + mode = TCSETAW; + break; + case TCSAFLUSH: + mode = TCSETAF; + } + return(ioctl(fd,mode,&ott)); + } + return(ioctl(fd,mode,tt)); +} +#endif /* SHOPT_OLDTERMIO */ + +#if KSHELL +/* + * Execute keyboard trap on given buffer <inbuff> of given size <isize> + * <mode> < 0 for vi insert mode + */ +static int keytrap(Edit_t *ep,char *inbuff,register int insize, int bufsize, int mode) +{ + register char *cp; + int savexit; + Shell_t *shp = ep->sh; +#if SHOPT_MULTIBYTE + char buff[MAXLINE]; + ed_external(ep->e_inbuf,cp=buff); +#else + cp = ep->e_inbuf; +#endif /* SHOPT_MULTIBYTE */ + inbuff[insize] = 0; + ep->e_col = ep->e_cur; + if(mode== -2) + { + ep->e_col++; + *ep->e_vi_insert = ESC; + } + else + *ep->e_vi_insert = 0; + nv_putval(ED_CHRNOD,inbuff,NV_NOFREE); + nv_putval(ED_COLNOD,(char*)&ep->e_col,NV_NOFREE|NV_INTEGER); + nv_putval(ED_TXTNOD,(char*)cp,NV_NOFREE); + nv_putval(ED_MODENOD,ep->e_vi_insert,NV_NOFREE); + savexit = shp->savexit; + sh_trap(shp->st.trap[SH_KEYTRAP],0); + shp->savexit = savexit; + if((cp = nv_getval(ED_CHRNOD)) == inbuff) + nv_unset(ED_CHRNOD); + else if(bufsize>0) + { + strncpy(inbuff,cp,bufsize); + inbuff[bufsize-1]='\0'; + insize = strlen(inbuff); + } + else + insize = 0; + nv_unset(ED_TXTNOD); + return(insize); +} +#endif /* KSHELL */ + +#if SHOPT_EDPREDICT +static int ed_sortdata(const char *s1, const char *s2) +{ + Histmatch_t *m1 = (Histmatch_t*)s1; + Histmatch_t *m2 = (Histmatch_t*)s2; + return(strcmp(m1->data,m2->data)); +} + +static int ed_sortindex(const char *s1, const char *s2) +{ + Histmatch_t *m1 = (Histmatch_t*)s1; + Histmatch_t *m2 = (Histmatch_t*)s2; + return(m2->index-m1->index); +} + +static int ed_histlencopy(const char *cp, char *dp) +{ + int c,n=1,col=1; + const char *oldcp=cp; + for(n=0;c = mbchar(cp);oldcp=cp,col++) + { + if(c=='\n' && *cp) + { + n += 2; + if(dp) + { + *dp++ = '^'; + *dp++ = 'J'; + col +=2; + } + } + else if(c=='\t') + { + n++; + if(dp) + *dp++ = ' '; + } + else + { + n += cp-oldcp; + if(dp) + { + while(oldcp < cp) + *dp++ = *oldcp++; + } + } + + } + return(n); +} + +int ed_histgen(Edit_t *ep,const char *pattern) +{ + Histmatch_t *mp,*mplast=0; + History_t *hp; + off_t offset; + int ac=0,l,m,n,index1,index2; + char *cp, **argv, **av, **ar; + if(!(hp=ep->sh->gd->hist_ptr)) + return(0); + if(*pattern=='#') + pattern++; + cp = stakalloc(m=strlen(pattern)+6); + sfsprintf(cp,m,"@(%s)*%c",pattern,0); + if(ep->hlist) + { + m = strlen(ep->hpat)-4; + if(memcmp(pattern,ep->hpat+2,m)==0) + { + n = strcmp(cp,ep->hpat)==0; + for(argv=av=(char**)ep->hlist,mp=ep->hfirst; mp;mp= mp->next) + { + if(n || strmatch(mp->data,cp)) + *av++ = (char*)mp; + } + *av = 0; + return(ep->hmax=av-argv); + } + stakset(ep->e_stkptr,ep->e_stkoff); + } + pattern = ep->hpat = cp; + index1 = (int)hp->histind; + for(index2=index1-hp->histsize; index1>index2; index1--) + { + offset = hist_tell(hp,index1); + sfseek(hp->histfp,offset,SEEK_SET); + if(!(cp = sfgetr(hp->histfp,0,0))) + continue; + if(*cp=='#') + continue; + if(strmatch(cp,pattern)) + { + l = ed_histlencopy(cp,(char*)0); + mp = (Histmatch_t*)stakalloc(sizeof(Histmatch_t)+l); + mp->next = mplast; + mplast = mp; + mp->len = l; + ed_histlencopy(cp,mp->data); + mp->count = 1; + mp->data[l] = 0; + mp->index = index1; + ac++; + } + } + if(ac>0) + { + l = ac; + argv = av = (char**)stakalloc((ac+1)*sizeof(char*)); + for(mplast=0; l>=0 && (*av= (char*)mp); mplast=mp,mp=mp->next,av++) + { + l--; + } + *av = 0; + strsort(argv,ac,ed_sortdata); + mplast = (Histmatch_t*)argv[0]; + for(ar= av= &argv[1]; mp=(Histmatch_t*)*av; av++) + { + if(strcmp(mp->data,mplast->data)==0) + { + mplast->count++; + if(mp->index> mplast->index) + mplast->index = mp->index; + continue; + } + *ar++ = (char*)(mplast=mp); + } + *ar = 0; + mplast->next = 0; + ac = ar-argv; + strsort(argv,ac,ed_sortindex); + mplast = (Histmatch_t*)argv[0]; + for(av= &argv[1]; mp=(Histmatch_t*)*av; av++, mplast=mp) + mplast->next = mp; + mplast->next = 0; + } + ep->hlist = (Histmatch_t**)argv; + ep->hfirst = ep->hlist?ep->hlist[0]:0; + return(ep->hmax=ac); +} + +void ed_histlist(Edit_t *ep,int n) +{ + Histmatch_t *mp,**mpp = ep->hlist+ep->hoff; + int i,last=0,save[2]; + if(n) + { + /* don't bother updating the screen if there is typeahead */ + if(!ep->e_lookahead && sfpkrd(ep->e_fd,save,1,'\r',200L,-1)>0) + ed_ungetchar(ep,save[0]); + if(ep->e_lookahead) + return; + ed_putchar(ep,'\n'); + ed_putchar(ep,'\r'); + } + else + { + stakset(ep->e_stkptr,ep->e_stkoff); + ep->hlist = 0; + ep->nhlist = 0; + } + ed_putstring(ep,KILL_LINE); + if(n) + { + for(i=1; (mp= *mpp) && i <= 16 ; i++,mpp++) + { + last = 0; + if(mp->len >= ep->e_winsz-4) + { + last = ep->e_winsz-4; + save[0] = mp->data[last-1]; + save[1] = mp->data[last]; + mp->data[last-1] = '\n'; + mp->data[last] = 0; + } + ed_putchar(ep,i<10?' ':'1'); + ed_putchar(ep,i<10?'0'+i:'0'+i-10); + ed_putchar(ep,')'); + ed_putchar(ep,' '); + ed_putstring(ep,mp->data); + if(last) + { + mp->data[last-1] = save[0]; + mp->data[last] = save[1]; + } + ep->nhlist = i; + } + last = i-1; + while(i-->0) + ed_putstring(ep,CURSOR_UP); + } + ed_flush(ep); +} +#endif /* SHOPT_EDPREDICT */ + +void *ed_open(Shell_t *shp) +{ + Edit_t *ed = newof(0,Edit_t,1,0); + ed->sh = shp; + strcpy(ed->e_macro,"_??"); + return((void*)ed); +} diff --git a/src/cmd/ksh93/edit/emacs.c b/src/cmd/ksh93/edit/emacs.c new file mode 100644 index 0000000..cb686b7 --- /dev/null +++ b/src/cmd/ksh93/edit/emacs.c @@ -0,0 +1,1571 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2012 AT&T Intellectual Property * +* and is licensed under the * +* Eclipse Public License, Version 1.0 * +* by AT&T Intellectual Property * +* * +* A copy of the License is available at * +* http://www.eclipse.org/org/documents/epl-v10.html * +* (with md5 checksum b35adb5213ca9657e911e9befb180842) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* Original version by Michael T. Veach + * Adapted for ksh by David Korn */ +/* EMACS_MODES: c tabstop=4 + +One line screen editor for any program + +*/ + + +/* The following is provided by: + * + * Matthijs N. Melchior + * AT&T Network Systems International + * APT Nederland + * HV BZ335 x2962 + * hvlpb!mmelchio + * + * These are now on by default + * + * ESH_NFIRST + * - A ^N as first history related command after the prompt will move + * to the next command relative to the last known history position. + * It will not start at the position where the last command was entered + * as is done by the ^P command. Every history related command will + * set both the current and last position. Executing a command will + * only set the current position. + * + * ESH_KAPPEND + * - Successive kill and delete commands will accumulate their data + * in the kill buffer, by appending or prepending as appropriate. + * This mode will be reset by any command not adding something to the + * kill buffer. + * + * ESH_BETTER + * - Some enhancements: + * - argument for a macro is passed to its replacement + * - ^X^H command to find out about history position (debugging) + * - ^X^D command to show any debugging info + * + * I do not pretend these for changes are completely independent, + * but you can use them to seperate features. + */ + +#include <ast.h> +#include "FEATURE/cmds" +#if KSHELL +# include "defs.h" +#else +# include <ctype.h> +#endif /* KSHELL */ +#include "io.h" + +#include "history.h" +#include "edit.h" +#include "terminal.h" + +#define ESH_NFIRST +#define ESH_KAPPEND +#define ESH_BETTER + +#undef putchar +#define putchar(ed,c) ed_putchar(ed,c) +#define beep() ed_ringbell() + + +#if SHOPT_MULTIBYTE +# define gencpy(a,b) ed_gencpy(a,b) +# define genncpy(a,b,n) ed_genncpy(a,b,n) +# define genlen(str) ed_genlen(str) + static int print(int); + static int _isword(int); +# define isword(c) _isword(out[c]) + +#else +# define gencpy(a,b) strcpy((char*)(a),(char*)(b)) +# define genncpy(a,b,n) strncpy((char*)(a),(char*)(b),n) +# define genlen(str) strlen(str) +# define print(c) isprint(c) +# define isword(c) (isalnum(out[c]) || (out[c]=='_')) +#endif /*SHOPT_MULTIBYTE */ + +typedef struct _emacs_ +{ + genchar *screen; /* pointer to window buffer */ + genchar *cursor; /* Cursor in real screen */ + int mark; + int in_mult; + char cr_ok; + char CntrlO; + char overflow; /* Screen overflow flag set */ + char scvalid; /* Screen is up to date */ + char lastdraw; /* last update type */ + int offset; /* Screen offset */ + enum + { + CRT=0, /* Crt terminal */ + PAPER /* Paper terminal */ + } terminal; + Histloc_t _location; + int prevdirection; + Edit_t *ed; /* pointer to edit data */ +} Emacs_t; + +#define editb (*ep->ed) +#define eol editb.e_eol +#define cur editb.e_cur +#define hline editb.e_hline +#define hloff editb.e_hloff +#define hismin editb.e_hismin +#define usrkill editb.e_kill +#define usrlnext editb.e_lnext +#define usreof editb.e_eof +#define usrerase editb.e_erase +#define crallowed editb.e_crlf +#define Prompt editb.e_prompt +#define plen editb.e_plen +#define kstack editb.e_killbuf +#define lstring editb.e_search +#define lookahead editb.e_lookahead +#define env editb.e_env +#define raw editb.e_raw +#define histlines editb.e_hismax +#define w_size editb.e_wsize +#define drawbuff editb.e_inbuf +#define killing editb.e_mode +#define location ep->_location + +#define LBUF 100 +#define KILLCHAR UKILL +#define ERASECHAR UERASE +#define EOFCHAR UEOF +#define LNEXTCHAR ULNEXT +#define DELETE ('a'==97?0177:7) + +/********************** +A large lookahead helps when the user is inserting +characters in the middle of the line. +************************/ + + +typedef enum +{ + FIRST, /* First time thru for logical line, prompt on screen */ + REFRESH, /* Redraw entire screen */ + APPEND, /* Append char before cursor to screen */ + UPDATE, /* Update the screen as need be */ + FINAL /* Update screen even if pending look ahead */ +} Draw_t; + +static void draw(Emacs_t*,Draw_t); +static int escape(Emacs_t*,genchar*, int); +static void putstring(Emacs_t*,char*); +static void search(Emacs_t*,genchar*,int); +static void setcursor(Emacs_t*,int, int); +static void show_info(Emacs_t*,const char*); +static void xcommands(Emacs_t*,int); + +int ed_emacsread(void *context, int fd,char *buff,int scend, int reedit) +{ + Edit_t *ed = (Edit_t*)context; + register int c; + register int i; + register genchar *out; + register int count; + register Emacs_t *ep = ed->e_emacs; + int adjust,oadjust; + char backslash; + genchar *kptr; + char prompt[PRSIZE]; + genchar Screen[MAXLINE]; + memset(Screen,0,sizeof(Screen)); + if(!ep) + { + ep = ed->e_emacs = newof(0,Emacs_t,1,0); + ep->ed = ed; + ep->prevdirection = 1; + location.hist_command = -5; + } + Prompt = prompt; + ep->screen = Screen; + ep->lastdraw = FINAL; + if(tty_raw(ERRIO,0) < 0) + { + return(reedit?reedit:ed_read(context, fd,buff,scend,0)); + } + raw = 1; + /* This mess in case the read system call fails */ + + ed_setup(ep->ed,fd,reedit); + out = (genchar*)buff; +#if SHOPT_MULTIBYTE + out = (genchar*)roundof(buff-(char*)0,sizeof(genchar)); + if(reedit) + ed_internal(buff,out); +#endif /* SHOPT_MULTIBYTE */ + if(!kstack) + { + kstack = (genchar*)malloc(CHARSIZE*MAXLINE); + kstack[0] = '\0'; + } + drawbuff = out; +#ifdef ESH_NFIRST + if (location.hist_command == -5) /* to be initialized */ + { + kstack[0] = '\0'; /* also clear kstack... */ + location.hist_command = hline; + location.hist_line = hloff; + } + if (location.hist_command <= hismin) /* don't start below minimum */ + { + location.hist_command = hismin + 1; + location.hist_line = 0; + } + ep->in_mult = hloff; /* save pos in last command */ +#endif /* ESH_NFIRST */ + i = sigsetjmp(env,0); + if (i !=0) + { + if(ep->ed->e_multiline) + { + cur = eol; + draw(ep,FINAL); + ed_flush(ep->ed); + } + tty_cooked(ERRIO); + if (i == UEOF) + { + return(0); /* EOF */ + } + return(-1); /* some other error */ + } + out[reedit] = 0; + if(scend+plen > (MAXLINE-2)) + scend = (MAXLINE-2)-plen; + ep->mark = 0; + cur = eol; + draw(ep,reedit?REFRESH:FIRST); + adjust = -1; + backslash = 0; + if (ep->CntrlO) + { +#ifdef ESH_NFIRST + ed_ungetchar(ep->ed,cntl('N')); +#else + location = hist_locate(shgd->hist_ptr,location.hist_command,location.hist_line,1); + if (location.hist_command < histlines) + { + hline = location.hist_command; + hloff = location.hist_line; + hist_copy((char*)kstack,MAXLINE, hline,hloff); +# if SHOPT_MULTIBYTE + ed_internal((char*)kstack,kstack); +# endif /* SHOPT_MULTIBYTE */ + ed_ungetchar(ep->ed,cntl('Y')); + } +#endif /* ESH_NFIRST */ + } + ep->CntrlO = 0; + while ((c = ed_getchar(ep->ed,0)) != (-1)) + { + if (backslash) + { + backslash = 0; + if (c==usrerase||c==usrkill||(!print(c) && + (c!='\r'&&c!='\n'))) + { + /* accept a backslashed character */ + cur--; + out[cur++] = c; + out[eol] = '\0'; + draw(ep,APPEND); + continue; + } + } + if (c == usrkill) + { + c = KILLCHAR ; + } + else if (c == usrerase) + { + c = ERASECHAR ; + } + else if (c == usrlnext) + { + c = LNEXTCHAR ; + } + else if ((c == usreof)&&(eol == 0)) + { + c = EOFCHAR; + } +#ifdef ESH_KAPPEND + if (--killing <= 0) /* reset killing flag */ + killing = 0; +#endif + oadjust = count = adjust; + if(count<0) + count = 1; + adjust = -1; + i = cur; + switch(c) + { + case LNEXTCHAR: + c = ed_getchar(ep->ed,2); + goto do_default_processing; + case cntl('V'): + show_info(ep,fmtident(e_version)); + continue; + case '\0': + ep->mark = i; + continue; + case cntl('X'): + xcommands(ep,count); + continue; + case EOFCHAR: + ed_flush(ep->ed); + tty_cooked(ERRIO); + return(0); +#ifdef u370 + case cntl('S') : + case cntl('Q') : + continue; +#endif /* u370 */ + case '\t': + if(cur>0 && ep->ed->sh->nextprompt) + { + if(ep->ed->e_tabcount==0) + { + ep->ed->e_tabcount=1; + ed_ungetchar(ep->ed,ESC); + goto do_escape; + } + else if(ep->ed->e_tabcount==1) + { + ed_ungetchar(ep->ed,'='); + goto do_escape; + } + ep->ed->e_tabcount = 0; + } + do_default_processing: + default: + + if ((eol+1) >= (scend)) /* will not fit on line */ + { + ed_ungetchar(ep->ed,c); /* save character for next line */ + goto process; + } + for(i= ++eol; i>cur; i--) + out[i] = out[i-1]; + backslash = (c == '\\'); + out[cur++] = c; + draw(ep,APPEND); + continue; + case cntl('Y') : + { + c = genlen(kstack); + if ((c + eol) > scend) + { + beep(); + continue; + } + ep->mark = i; + for(i=eol;i>=cur;i--) + out[c+i] = out[i]; + kptr=kstack; + while (i = *kptr++) + out[cur++] = i; + draw(ep,UPDATE); + eol = genlen(out); + continue; + } + case '\n': + case '\r': + c = '\n'; + goto process; + + case DELETE: /* delete char 0x7f */ + case '\b': /* backspace, ^h */ + case ERASECHAR : + if (count > i) + count = i; +#ifdef ESH_KAPPEND + kptr = &kstack[count]; /* move old contents here */ + if (killing) /* prepend to killbuf */ + { + c = genlen(kstack) + CHARSIZE; /* include '\0' */ + while(c--) /* copy stuff */ + kptr[c] = kstack[c]; + } + else + *kptr = 0; /* this is end of data */ + killing = 2; /* we are killing */ + i -= count; + eol -= count; + genncpy(kstack,out+i,cur-i); +#else + while ((count--)&&(i>0)) + { + i--; + eol--; + } + genncpy(kstack,out+i,cur-i); + kstack[cur-i] = 0; +#endif /* ESH_KAPPEND */ + gencpy(out+i,out+cur); + ep->mark = i; + goto update; + case cntl('W') : +#ifdef ESH_KAPPEND + ++killing; /* keep killing flag */ +#endif + if (ep->mark > eol ) + ep->mark = eol; + if (ep->mark == i) + continue; + if (ep->mark > i) + { + adjust = ep->mark - i; + ed_ungetchar(ep->ed,cntl('D')); + continue; + } + adjust = i - ep->mark; + ed_ungetchar(ep->ed,usrerase); + continue; + case cntl('D') : + ep->mark = i; +#ifdef ESH_KAPPEND + if (killing) + kptr = &kstack[genlen(kstack)]; /* append here */ + else + kptr = kstack; + killing = 2; /* we are now killing */ +#else + kptr = kstack; +#endif /* ESH_KAPPEND */ + while ((count--)&&(eol>0)&&(i<eol)) + { + *kptr++ = out[i]; + eol--; + while(1) + { + if ((out[i] = out[(i+1)])==0) + break; + i++; + } + i = cur; + } + *kptr = '\0'; + goto update; + case cntl('C') : + case cntl('F') : + { + int cntlC = (c==cntl('C')); + while (count-- && eol>i) + { + if (cntlC) + { + c = out[i]; +#if SHOPT_MULTIBYTE + if((c&~STRIP)==0 && islower(c)) +#else + if(islower(c)) +#endif /* SHOPT_MULTIBYTE */ + { + c += 'A' - 'a'; + out[i] = c; + } + } + i++; + } + goto update; + } + case cntl(']') : + c = ed_getchar(ep->ed,1); + if ((count == 0) || (count > eol)) + { + beep(); + continue; + } + if (out[i]) + i++; + while (i < eol) + { + if (out[i] == c && --count==0) + goto update; + i++; + } + i = 0; + while (i < cur) + { + if (out[i] == c && --count==0) + break; + i++; + }; + +update: + cur = i; + draw(ep,UPDATE); + continue; + + case cntl('B') : + if (count > i) + count = i; + i -= count; + goto update; + case cntl('T') : + if ((sh_isoption(SH_EMACS))&& (eol!=i)) + i++; + if (i >= 2) + { + c = out[i - 1]; + out[i-1] = out[i-2]; + out[i-2] = c; + } + else + { + if(sh_isoption(SH_EMACS)) + i--; + beep(); + continue; + } + goto update; + case cntl('A') : + i = 0; + goto update; + case cntl('E') : + i = eol; + goto update; + case cntl('U') : + adjust = 4*count; + continue; + case KILLCHAR : + cur = 0; + oadjust = -1; + case cntl('K') : + if(oadjust >= 0) + { +#ifdef ESH_KAPPEND + killing = 2; /* set killing signal */ +#endif + ep->mark = count; + ed_ungetchar(ep->ed,cntl('W')); + continue; + } + i = cur; + eol = i; + ep->mark = i; +#ifdef ESH_KAPPEND + if (killing) /* append to kill buffer */ + gencpy(&kstack[genlen(kstack)], &out[i]); + else + gencpy(kstack,&out[i]); + killing = 2; /* set killing signal */ +#else + gencpy(kstack,&out[i]); +#endif /* ESH_KAPPEND */ + out[i] = 0; + draw(ep,UPDATE); + if (c == KILLCHAR) + { + if (ep->terminal == PAPER) + { + putchar(ep->ed,'\n'); + putstring(ep,Prompt); + } + c = ed_getchar(ep->ed,0); + if (c != usrkill) + { + ed_ungetchar(ep->ed,c); + continue; + } + if (ep->terminal == PAPER) + ep->terminal = CRT; + else + { + ep->terminal = PAPER; + putchar(ep->ed,'\n'); + putstring(ep,Prompt); + } + } + continue; + case cntl('L'): + if(!ep->ed->e_nocrnl) + ed_crlf(ep->ed); + draw(ep,REFRESH); + ep->ed->e_nocrnl = 0; + continue; + case cntl('[') : + do_escape: + adjust = escape(ep,out,oadjust); + continue; + case cntl('R') : + search(ep,out,count); + goto drawline; + case cntl('P') : +#if SHOPT_EDPREDICT + if(ep->ed->hlist) + { + if(ep->ed->hoff == 0) + { + beep(); + continue; + } + ep->ed->hoff--; + goto hupdate; + } +#endif /* SHOPT_EDPREDICT */ + if (count <= hloff) + hloff -= count; + else + { + hline -= count - hloff; + hloff = 0; + } +#ifdef ESH_NFIRST + if (hline <= hismin) +#else + if (hline < hismin) +#endif /* ESH_NFIRST */ + { + hline = hismin+1; + beep(); +#ifndef ESH_NFIRST + continue; +#endif + } + goto common; + + case cntl('O') : + location.hist_command = hline; + location.hist_line = hloff; + ep->CntrlO = 1; + c = '\n'; + goto process; + case cntl('N') : +#if SHOPT_EDPREDICT + if(ep->ed->hlist) + { + if(ep->ed->hoff >= ep->ed->hmax) + { + beep(); + continue; + } + ep->ed->hoff++; + hupdate: + ed_histlist(ep->ed,*ep->ed->hlist!=0); + draw(ep,REFRESH); + continue; + } +#endif /* SHOPT_EDPREDICT */ +#ifdef ESH_NFIRST + hline = location.hist_command; /* start at saved position */ + hloff = location.hist_line; +#endif /* ESH_NFIRST */ + location = hist_locate(shgd->hist_ptr,hline,hloff,count); + if (location.hist_command > histlines) + { + beep(); +#ifdef ESH_NFIRST + location.hist_command = histlines; + location.hist_line = ep->in_mult; +#else + continue; +#endif /* ESH_NFIRST */ + } + hline = location.hist_command; + hloff = location.hist_line; + common: +#ifdef ESH_NFIRST + location.hist_command = hline; /* save current position */ + location.hist_line = hloff; +#endif + cur = 0; + draw(ep,UPDATE); + hist_copy((char*)out,MAXLINE, hline,hloff); +#if SHOPT_MULTIBYTE + ed_internal((char*)(out),out); +#endif /* SHOPT_MULTIBYTE */ + drawline: + eol = genlen(out); + cur = eol; + draw(ep,UPDATE); + continue; + } + + } + +process: + + if (c == (-1)) + { + lookahead = 0; + beep(); + *out = '\0'; + } + draw(ep,FINAL); + tty_cooked(ERRIO); + if(ed->e_nlist) + { + ed->e_nlist = 0; + stakset(ed->e_stkptr,ed->e_stkoff); + } + if(c == '\n') + { + out[eol++] = '\n'; + out[eol] = '\0'; + ed_crlf(ep->ed); + } +#if SHOPT_MULTIBYTE + ed_external(out,buff); +#endif /* SHOPT_MULTIBYTE */ + i = strlen(buff); + if (i) + return(i); + return(-1); +} + +static void show_info(Emacs_t *ep,const char *str) +{ + register genchar *out = drawbuff; + register int c; + genchar string[LBUF]; + int sav_cur = cur; + /* save current line */ + genncpy(string,out,sizeof(string)/sizeof(*string)); + *out = 0; + cur = 0; +#if SHOPT_MULTIBYTE + ed_internal(str,out); +#else + gencpy(out,str); +#endif /* SHOPT_MULTIBYTE */ + draw(ep,UPDATE); + c = ed_getchar(ep->ed,0); + if(c!=' ') + ed_ungetchar(ep->ed,c); + /* restore line */ + cur = sav_cur; + genncpy(out,string,sizeof(string)/sizeof(*string)); + draw(ep,UPDATE); +} + +static void putstring(Emacs_t* ep,register char *sp) +{ + register int c; + while (c= *sp++) + putchar(ep->ed,c); +} + + +static int escape(register Emacs_t* ep,register genchar *out,int count) +{ + register int i,value; + int digit,ch; + digit = 0; + value = 0; + while ((i=ed_getchar(ep->ed,0)),isdigit(i)) + { + value *= 10; + value += (i - '0'); + digit = 1; + } + if (digit) + { + ed_ungetchar(ep->ed,i) ; +#ifdef ESH_KAPPEND + ++killing; /* don't modify killing signal */ +#endif + return(value); + } + value = count; + if(value<0) + value = 1; + switch(ch=i) + { + case cntl('V'): + show_info(ep,fmtident(e_version)); + return(-1); + case ' ': + ep->mark = cur; + return(-1); + +#ifdef ESH_KAPPEND + case '+': /* M-+ = append next kill */ + killing = 2; + return -1; /* no argument for next command */ +#endif + + case 'p': /* M-p == ^W^Y (copy stack == kill & yank) */ + ed_ungetchar(ep->ed,cntl('Y')); + ed_ungetchar(ep->ed,cntl('W')); +#ifdef ESH_KAPPEND + killing = 0; /* start fresh */ +#endif + return(-1); + + case 'l': /* M-l == lower-case */ + case 'd': + case 'c': + case 'f': + { + i = cur; + while(value-- && i<eol) + { + while ((out[i])&&(!isword(i))) + i++; + while ((out[i])&&(isword(i))) + i++; + } + if(ch=='l') + { + value = i-cur; + while (value-- > 0) + { + i = out[cur]; +#if SHOPT_MULTIBYTE + if((i&~STRIP)==0 && isupper(i)) +#else + if(isupper(i)) +#endif /* SHOPT_MULTIBYTE */ + { + i += 'a' - 'A'; + out[cur] = i; + } + cur++; + } + draw(ep,UPDATE); + return(-1); + } + + else if(ch=='f') + goto update; + else if(ch=='c') + { + ed_ungetchar(ep->ed,cntl('C')); + return(i-cur); + } + else + { + if (i-cur) + { + ed_ungetchar(ep->ed,cntl('D')); +#ifdef ESH_KAPPEND + ++killing; /* keep killing signal */ +#endif + return(i-cur); + } + beep(); + return(-1); + } + } + + + case 'b': + case DELETE : + case '\b': + case 'h': + { + i = cur; + while(value-- && i>0) + { + i--; + while ((i>0)&&(!isword(i))) + i--; + while ((i>0)&&(isword(i-1))) + i--; + } + if(ch=='b') + goto update; + else + { + ed_ungetchar(ep->ed,usrerase); +#ifdef ESH_KAPPEND + ++killing; +#endif + return(cur-i); + } + } + + case '>': + ed_ungetchar(ep->ed,cntl('N')); +#ifdef ESH_NFIRST + if (ep->in_mult) + { + location.hist_command = histlines; + location.hist_line = ep->in_mult - 1; + } + else + { + location.hist_command = histlines - 1; + location.hist_line = 0; + } +#else + hline = histlines-1; + hloff = 0; +#endif /* ESH_NFIRST */ + return(0); + + case '<': + ed_ungetchar(ep->ed,cntl('P')); + hloff = 0; +#ifdef ESH_NFIRST + hline = hismin + 1; + return 0; +#else + return(hline-hismin); +#endif /* ESH_NFIRST */ + + + case '#': + ed_ungetchar(ep->ed,'\n'); + ed_ungetchar(ep->ed,(out[0]=='#')?cntl('D'):'#'); + ed_ungetchar(ep->ed,cntl('A')); + return(-1); + case '_' : + case '.' : + { + genchar name[MAXLINE]; + char buf[MAXLINE]; + char *ptr; + ptr = hist_word(buf,MAXLINE,(count?count:-1)); + if(ptr==0) + { + beep(); + break; + } + if ((eol - cur) >= sizeof(name)) + { + beep(); + return(-1); + } + ep->mark = cur; + gencpy(name,&out[cur]); + while(*ptr) + { + out[cur++] = *ptr++; + eol++; + } + gencpy(&out[cur],name); + draw(ep,UPDATE); + return(-1); + } +#if KSHELL + +#if SHOPT_EDPREDICT + case '\n': case '\t': + if(!ep->ed->hlist) + { + beep(); + break; + } + if(ch=='\n') + ed_ungetchar(ep->ed,'\n'); +#endif /* SHOPT_EDPREDICT */ + /* file name expansion */ + case cntl('[') : /* filename completion */ +#if SHOPT_EDPREDICT + if(ep->ed->hlist) + { + value += ep->ed->hoff; + if(value > ep->ed->nhlist) + beep(); + else + { + value = histlines - ep->ed->hlist[value-1]->index; + ed_histlist(ep->ed,0); + ed_ungetchar(ep->ed,cntl('P')); + return(value); + } + } +#endif /* SHOPT_EDPREDICT */ + i = '\\'; + case '*': /* filename expansion */ + case '=': /* escape = - list all matching file names */ + ep->mark = cur; + if(ed_expand(ep->ed,(char*)out,&cur,&eol,i,count) < 0) + { + if(ep->ed->e_tabcount==1) + { + ep->ed->e_tabcount=2; + ed_ungetchar(ep->ed,cntl('\t')); + return(-1); + } + beep(); + } + else if(i=='=') + { + draw(ep,REFRESH); + if(count>0) + ep->ed->e_tabcount=0; + else + { + i=ed_getchar(ep->ed,0); + ed_ungetchar(ep->ed,i); + if(isdigit(i)) + ed_ungetchar(ep->ed,ESC); + } + } + else + { + if(i=='\\' && cur>ep->mark && (out[cur-1]=='/' || out[cur-1]==' ')) + ep->ed->e_tabcount=0; + draw(ep,UPDATE); + } + return(-1); + + /* search back for character */ + case cntl(']'): /* feature not in book */ + { + int c = ed_getchar(ep->ed,1); + if ((value == 0) || (value > eol)) + { + beep(); + return(-1); + } + i = cur; + if (i > 0) + i--; + while (i >= 0) + { + if (out[i] == c && --value==0) + goto update; + i--; + } + i = eol; + while (i > cur) + { + if (out[i] == c && --value==0) + break; + i--; + }; + + } + update: + cur = i; + draw(ep,UPDATE); + return(-1); + +#ifdef _cmd_tput + case cntl('L'): /* clear screen */ + sh_trap("tput clear", 0); + draw(ep,REFRESH); + return(-1); +#endif + case '[': /* feature not in book */ + switch(i=ed_getchar(ep->ed,1)) + { + case 'A': +#if SHOPT_EDPREDICT + if(!ep->ed->hlist && cur>0 && eol==cur && (cur<(SEARCHSIZE-2) || ep->prevdirection == -2)) +#else + if(cur>0 && eol==cur && (cur<(SEARCHSIZE-2) || ep->prevdirection == -2)) +#endif /* SHOPT_EDPREDICT */ + { + if(ep->lastdraw==APPEND && ep->prevdirection != -2) + { + out[cur] = 0; + gencpy((genchar*)lstring+1,out); +#if SHOPT_MULTIBYTE + ed_external((genchar*)lstring+1,lstring+1); +#endif /* SHOPT_MULTIBYTE */ + *lstring = '^'; + ep->prevdirection = -2; + } + if(*lstring) + { + ed_ungetchar(ep->ed,'\r'); + ed_ungetchar(ep->ed,cntl('R')); + return(-1); + } + } + *lstring = 0; + ed_ungetchar(ep->ed,cntl('P')); + return(-1); + case 'B': + ed_ungetchar(ep->ed,cntl('N')); + return(-1); + case 'C': + ed_ungetchar(ep->ed,cntl('F')); + return(-1); + case 'D': + ed_ungetchar(ep->ed,cntl('B')); + return(-1); + case 'H': + ed_ungetchar(ep->ed,cntl('A')); + return(-1); + case 'Y': + ed_ungetchar(ep->ed,cntl('E')); + return(-1); + default: + ed_ungetchar(ep->ed,i); + } + i = '_'; + + default: + /* look for user defined macro definitions */ + if(ed_macro(ep->ed,i)) +# ifdef ESH_BETTER + return(count); /* pass argument to macro */ +# else + return(-1); +# endif /* ESH_BETTER */ +#else + update: + cur = i; + draw(ep,UPDATE); + return(-1); + + default: +#endif /* KSHELL */ + beep(); + return(-1); + } + return(-1); +} + + +/* + * This routine process all commands starting with ^X + */ + +static void xcommands(register Emacs_t *ep,int count) +{ + register int i = ed_getchar(ep->ed,0); + NOT_USED(count); + switch(i) + { + case cntl('X'): /* exchange dot and mark */ + if (ep->mark > eol) + ep->mark = eol; + i = ep->mark; + ep->mark = cur; + cur = i; + draw(ep,UPDATE); + return; + +#if KSHELL +# ifdef ESH_BETTER + case cntl('E'): /* invoke emacs on current command */ + if(ed_fulledit(ep->ed)==-1) + beep(); + else + { +#if SHOPT_MULTIBYTE + ed_internal((char*)drawbuff,drawbuff); +#endif /* SHOPT_MULTIBYTE */ + ed_ungetchar(ep->ed,'\n'); + } + return; + +# define itos(i) fmtbase((long)(i),0,0)/* want signed conversion */ + + case cntl('H'): /* ^X^H show history info */ + { + char hbuf[MAXLINE]; + + strcpy(hbuf, "Current command "); + strcat(hbuf, itos(hline)); + if (hloff) + { + strcat(hbuf, " (line "); + strcat(hbuf, itos(hloff+1)); + strcat(hbuf, ")"); + } + if ((hline != location.hist_command) || + (hloff != location.hist_line)) + { + strcat(hbuf, "; Previous command "); + strcat(hbuf, itos(location.hist_command)); + if (location.hist_line) + { + strcat(hbuf, " (line "); + strcat(hbuf, itos(location.hist_line+1)); + strcat(hbuf, ")"); + } + } + show_info(ep,hbuf); + return; + } +# if 0 /* debugging, modify as required */ + case cntl('D'): /* ^X^D show debugging info */ + { + char debugbuf[MAXLINE]; + + strcpy(debugbuf, "count="); + strcat(debugbuf, itos(count)); + strcat(debugbuf, " eol="); + strcat(debugbuf, itos(eol)); + strcat(debugbuf, " cur="); + strcat(debugbuf, itos(cur)); + strcat(debugbuf, " crallowed="); + strcat(debugbuf, itos(crallowed)); + strcat(debugbuf, " plen="); + strcat(debugbuf, itos(plen)); + strcat(debugbuf, " w_size="); + strcat(debugbuf, itos(w_size)); + + show_info(ep,debugbuf); + return; + } +# endif /* debugging code */ +# endif /* ESH_BETTER */ +#endif /* KSHELL */ + + default: + beep(); + return; + } +} + +static void search(Emacs_t* ep,genchar *out,int direction) +{ +#ifndef ESH_NFIRST + Histloc_t location; +#endif + register int i,sl; + genchar str_buff[LBUF]; + register genchar *string = drawbuff; + /* save current line */ + int sav_cur = cur; + genncpy(str_buff,string,sizeof(str_buff)/sizeof(*str_buff)); + string[0] = '^'; + string[1] = 'R'; + string[2] = '\0'; + sl = 2; + cur = sl; + draw(ep,UPDATE); + while ((i = ed_getchar(ep->ed,1))&&(i != '\r')&&(i != '\n')) + { + if (i==usrerase || i==DELETE || i=='\b' || i==ERASECHAR) + { + if (sl > 2) + { + string[--sl] = '\0'; + cur = sl; + draw(ep,UPDATE); + } + else + goto restore; + continue; + } + if(i == ep->ed->e_intr) + goto restore; + if (i==usrkill) + { + beep(); + goto restore; + } + if (i == '\\') + { + string[sl++] = '\\'; + string[sl] = '\0'; + cur = sl; + draw(ep,APPEND); + i = ed_getchar(ep->ed,1); + string[--sl] = '\0'; + } + string[sl++] = i; + string[sl] = '\0'; + cur = sl; + draw(ep,APPEND); + } + i = genlen(string); + + if(ep->prevdirection == -2 && i!=2 || direction!=1) + ep->prevdirection = -1; + if (direction < 1) + { + ep->prevdirection = -ep->prevdirection; + direction = 1; + } + else + direction = -1; + if (i != 2) + { +#if SHOPT_MULTIBYTE + ed_external(string,(char*)string); +#endif /* SHOPT_MULTIBYTE */ + strncpy(lstring,((char*)string)+2,SEARCHSIZE); + lstring[SEARCHSIZE-1] = 0; + ep->prevdirection = direction; + } + else + direction = ep->prevdirection ; + location = hist_find(shgd->hist_ptr,(char*)lstring,hline,1,direction); + i = location.hist_command; + if(i>0) + { + hline = i; +#ifdef ESH_NFIRST + hloff = location.hist_line = 0; /* display first line of multi line command */ +#else + hloff = location.hist_line; +#endif /* ESH_NFIRST */ + hist_copy((char*)out,MAXLINE, hline,hloff); +#if SHOPT_MULTIBYTE + ed_internal((char*)out,out); +#endif /* SHOPT_MULTIBYTE */ + return; + } + if (i < 0) + { + beep(); +#ifdef ESH_NFIRST + location.hist_command = hline; + location.hist_line = hloff; +#else + hloff = 0; + hline = histlines; +#endif /* ESH_NFIRST */ + } +restore: + genncpy(string,str_buff,sizeof(str_buff)/sizeof(*str_buff)); + cur = sav_cur; + return; +} + + +/* Adjust screen to agree with inputs: logical line and cursor */ +/* If 'first' assume screen is blank */ +/* Prompt is always kept on the screen */ + +static void draw(register Emacs_t *ep,Draw_t option) +{ +#define NORMAL ' ' +#define LOWER '<' +#define BOTH '*' +#define UPPER '>' + + register genchar *sptr; /* Pointer within screen */ + genchar nscreen[2*MAXLINE]; /* New entire screen */ + genchar *ncursor; /* New cursor */ + register genchar *nptr; /* Pointer to New screen */ + char longline; /* Line overflow */ + genchar *logcursor; + genchar *nscend; /* end of logical screen */ + register int i; + + nptr = nscreen; + sptr = drawbuff; + logcursor = sptr + cur; + longline = NORMAL; + ep->lastdraw = option; + + if (option == FIRST || option == REFRESH) + { + ep->overflow = NORMAL; + ep->cursor = ep->screen; + ep->offset = 0; + ep->cr_ok = crallowed; + if (option == FIRST) + { + ep->scvalid = 1; + return; + } + *ep->cursor = '\0'; + putstring(ep,Prompt); /* start with prompt */ + } + + /********************* + Do not update screen if pending characters + **********************/ + + if ((lookahead)&&(option != FINAL)) + { + + ep->scvalid = 0; /* Screen is out of date, APPEND will not work */ + + return; + } + + /*************************************** + If in append mode, cursor at end of line, screen up to date, + the previous character was a 'normal' character, + and the window has room for another character. + Then output the character and adjust the screen only. + *****************************************/ + + + i = *(logcursor-1); /* last character inserted */ +#if SHOPT_EDPREDICT + if(option==FINAL) + { + if(ep->ed->hlist) + ed_histlist(ep->ed,0); + } + else if((option==UPDATE||option==APPEND) && drawbuff[0]=='#' && cur>1 && cur==eol && drawbuff[cur-1]!='*') + { + int n; + drawbuff[cur+1]=0; +# if SHOPT_MULTIBYTE + ed_external(drawbuff,(char*)drawbuff); +# endif /*SHOPT_MULTIBYTE */ + n = ed_histgen(ep->ed,(char*)drawbuff); +# if SHOPT_MULTIBYTE + ed_internal((char*)drawbuff,drawbuff); +# endif /*SHOPT_MULTIBYTE */ + if(ep->ed->hlist) + { + ed_histlist(ep->ed,n); + putstring(ep,Prompt); + ed_setcursor(ep->ed,ep->screen,0,ep->cursor-ep->screen, 0); + } + else + ed_ringbell(); + + } +#endif /* SHOPT_EDPREDICT */ + + if ((option == APPEND)&&(ep->scvalid)&&(*logcursor == '\0')&& + print(i)&&((ep->cursor-ep->screen)<(w_size-1))) + { + putchar(ep->ed,i); + *ep->cursor++ = i; + *ep->cursor = '\0'; + return; + } + + /* copy the line */ + ncursor = nptr + ed_virt_to_phys(ep->ed,sptr,nptr,cur,0,0); + nptr += genlen(nptr); + sptr += genlen(sptr); + nscend = nptr - 1; + if(sptr == logcursor) + ncursor = nptr; + + /********************* + Does ncursor appear on the screen? + If not, adjust the screen offset so it does. + **********************/ + + i = ncursor - nscreen; + + if ((ep->offset && i<=ep->offset)||(i >= (ep->offset+w_size))) + { + /* Center the cursor on the screen */ + ep->offset = i - (w_size>>1); + if (--ep->offset < 0) + ep->offset = 0; + } + + /********************* + Is the range of screen[0] thru screen[w_size] up-to-date + with nscreen[offset] thru nscreen[offset+w_size] ? + If not, update as need be. + ***********************/ + + nptr = &nscreen[ep->offset]; + sptr = ep->screen; + + i = w_size; + + while (i-- > 0) + { + + if (*nptr == '\0') + { + *(nptr + 1) = '\0'; + *nptr = ' '; + } + if (*sptr == '\0') + { + *(sptr + 1) = '\0'; + *sptr = ' '; + } + if (*nptr == *sptr) + { + nptr++; + sptr++; + continue; + } + setcursor(ep,sptr-ep->screen,*nptr); + *sptr++ = *nptr++; +#if SHOPT_MULTIBYTE + while(*nptr==MARKER) + { + if(*sptr=='\0') + *(sptr + 1) = '\0'; + *sptr++ = *nptr++; + i--; + ep->cursor++; + } +#endif /* SHOPT_MULTIBYTE */ + } + if(ep->ed->e_multiline && option == REFRESH && ep->ed->e_nocrnl==0) + ed_setcursor(ep->ed, ep->screen, ep->cursor-ep->screen, ep->ed->e_peol, -1); + + + /****************** + + Screen overflow checks + + ********************/ + + if (nscend >= &nscreen[ep->offset+w_size]) + { + if (ep->offset > 0) + longline = BOTH; + else + longline = UPPER; + } + else + { + if (ep->offset > 0) + longline = LOWER; + } + + /* Update screen overflow indicator if need be */ + + if (longline != ep->overflow) + { + setcursor(ep,w_size,longline); + ep->overflow = longline; + } + i = (ncursor-nscreen) - ep->offset; + setcursor(ep,i,0); + if(option==FINAL && ep->ed->e_multiline) + setcursor(ep,nscend+1-nscreen,0); + ep->scvalid = 1; + return; +} + +/* + * put the cursor to the <newp> position within screen buffer + * if <c> is non-zero then output this character + * cursor is set to reflect the change + */ + +static void setcursor(register Emacs_t *ep,register int newp,int c) +{ + register int oldp = ep->cursor - ep->screen; + newp = ed_setcursor(ep->ed, ep->screen, oldp, newp, 0); + if(c) + { + putchar(ep->ed,c); + newp++; + } + ep->cursor = ep->screen+newp; + return; +} + +#if SHOPT_MULTIBYTE +static int print(register int c) +{ + return((c&~STRIP)==0 && isprint(c)); +} + +static int _isword(register int c) +{ + return((c&~STRIP) || isalnum(c) || c=='_'); +} +#endif /* SHOPT_MULTIBYTE */ diff --git a/src/cmd/ksh93/edit/hexpand.c b/src/cmd/ksh93/edit/hexpand.c new file mode 100644 index 0000000..3e2d396 --- /dev/null +++ b/src/cmd/ksh93/edit/hexpand.c @@ -0,0 +1,734 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2011 AT&T Intellectual Property * +* and is licensed under the * +* Eclipse Public License, Version 1.0 * +* by AT&T Intellectual Property * +* * +* A copy of the License is available at * +* http://www.eclipse.org/org/documents/epl-v10.html * +* (with md5 checksum b35adb5213ca9657e911e9befb180842) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * bash style history expansion + * + * Author: + * Karsten Fleischer + * Omnium Software Engineering + * An der Luisenburg 7 + * D-51379 Leverkusen + * Germany + * + * <K.Fleischer@omnium.de> + */ + + +#include "defs.h" +#include "edit.h" + +#if ! SHOPT_HISTEXPAND + +NoN(hexpand) + +#else + +static char *modifiers = "htrepqxs&"; +static int mod_flags[] = { 0, 0, 0, 0, HIST_PRINT, HIST_QUOTE, HIST_QUOTE|HIST_QUOTE_BR, 0, 0 }; + +#define DONE() {flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;} + +struct subst +{ + char *str[2]; /* [0] is "old", [1] is "new" string */ +}; + + +/* + * parse an /old/new/ string, delimiter expected as first char. + * if "old" not specified, keep sb->str[0] + * if "new" not specified, set sb->str[1] to empty string + * read up to third delimeter char, \n or \0, whichever comes first. + * return adress is one past the last valid char in s: + * - the address containing \n or \0 or + * - one char beyond the third delimiter + */ + +static char *parse_subst(const char *s, struct subst *sb) +{ + char *cp,del; + int off,n = 0; + + /* build the strings on the stack, mainly for '&' substition in "new" */ + off = staktell(); + + /* init "new" with empty string */ + if(sb->str[1]) + free(sb->str[1]); + sb->str[1] = strdup(""); + + /* get delimiter */ + del = *s; + + cp = (char*) s + 1; + + while(n < 2) + { + if(*cp == del || *cp == '\n' || *cp == '\0') + { + /* delimiter or EOL */ + if(staktell() != off) + { + /* dupe string on stack and rewind stack */ + stakputc('\0'); + if(sb->str[n]) + free(sb->str[n]); + sb->str[n] = strdup(stakptr(off)); + stakseek(off); + } + n++; + + /* if not delimiter, we've reached EOL. Get outta here. */ + if(*cp != del) + break; + } + else if(*cp == '\\') + { + if(*(cp+1) == del) /* quote delimiter */ + { + stakputc(del); + cp++; + } + else if(*(cp+1) == '&' && n == 1) + { /* quote '&' only in "new" */ + stakputc('&'); + cp++; + } + else + stakputc('\\'); + } + else if(*cp == '&' && n == 1 && sb->str[0]) + /* substitute '&' with "old" in "new" */ + stakputs(sb->str[0]); + else + stakputc(*cp); + cp++; + } + + /* rewind stack */ + stakseek(off); + + return cp; +} + +/* + * history expansion main routine + */ + +int hist_expand(const char *ln, char **xp) +{ + int off, /* stack offset */ + q, /* quotation flags */ + p, /* flag */ + c, /* current char */ + flag=0; /* HIST_* flags */ + Sfoff_t n, /* history line number, counter, etc. */ + i, /* counter */ + w[2]; /* word range */ + char *sp, /* stack pointer */ + *cp, /* current char in ln */ + *str, /* search string */ + *evp, /* event/word designator string, for error msgs */ + *cc=0, /* copy of current line up to cp; temp ptr */ + hc[3], /* default histchars */ + *qc="\'\"`"; /* quote characters */ + Sfio_t *ref=0, /* line referenced by event designator */ + *tmp=0, /* temporary line buffer */ + *tmp2=0;/* temporary line buffer */ + Histloc_t hl; /* history location */ + static Namval_t *np = 0; /* histchars variable */ + static struct subst sb = {0,0}; /* substition strings */ + static Sfio_t *wm=0; /* word match from !?string? event designator */ + + if(!wm) + wm = sfopen(NULL, NULL, "swr"); + + hc[0] = '!'; + hc[1] = '^'; + hc[2] = 0; + if((np = nv_open("histchars",sh.var_tree,0)) && (cp = nv_getval(np))) + { + if(cp[0]) + { + hc[0] = cp[0]; + if(cp[1]) + { + hc[1] = cp[1]; + if(cp[2]) + hc[2] = cp[2]; + } + } + } + + /* save shell stack */ + if(off = staktell()) + sp = stakfreeze(0); + + cp = (char*)ln; + + while(cp && *cp) + { + /* read until event/quick substitution/comment designator */ + if((*cp != hc[0] && *cp != hc[1] && *cp != hc[2]) + || (*cp == hc[1] && cp != ln)) + { + if(*cp == '\\') /* skip escaped designators */ + stakputc(*cp++); + else if(*cp == '\'') /* skip quoted designators */ + { + do + stakputc(*cp); + while(*++cp && *cp != '\''); + } + stakputc(*cp++); + continue; + } + + if(hc[2] && *cp == hc[2]) /* history comment designator, skip rest of line */ + { + stakputc(*cp++); + stakputs(cp); + DONE(); + } + + n = -1; + str = 0; + flag &= HIST_EVENT; /* save event flag for returning later */ + evp = cp; + ref = 0; + + if(*cp == hc[1]) /* shortcut substitution */ + { + flag |= HIST_QUICKSUBST; + goto getline; + } + + if(*cp == hc[0] && *(cp+1) == hc[0]) /* refer to line -1 */ + { + cp += 2; + goto getline; + } + + switch(c = *++cp) { + case ' ': + case '\t': + case '\n': + case '\0': + case '=': + case '(': + stakputc(hc[0]); + continue; + case '#': /* the line up to current position */ + flag |= HIST_HASH; + cp++; + n = staktell(); /* terminate string and dup */ + stakputc('\0'); + cc = strdup(stakptr(0)); + stakseek(n); /* remove null byte again */ + ref = sfopen(ref, cc, "s"); /* open as file */ + n = 0; /* skip history file referencing */ + break; + case '-': /* back reference by number */ + if(!isdigit(*(cp+1))) + goto string_event; + cp++; + case '0': /* reference by number */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + n = 0; + while(isdigit(*cp)) + n = n * 10 + (*cp++) - '0'; + if(c == '-') + n = -n; + break; + case '$': + n = -1; + case ':': + break; + case '?': + cp++; + flag |= HIST_QUESTION; + string_event: + default: + /* read until end of string or word designator/modifier */ + str = cp; + while(*cp) + { + cp++; + if((!(flag&HIST_QUESTION) && + (*cp == ':' || isspace(*cp) + || *cp == '^' || *cp == '$' + || *cp == '*' || *cp == '-' + || *cp == '%') + ) + || ((flag&HIST_QUESTION) && (*cp == '?' || *cp == '\n'))) + { + c = *cp; + *cp = '\0'; + } + } + break; + } + +getline: + flag |= HIST_EVENT; + if(str) /* !string or !?string? event designator */ + { + + /* search history for string */ + hl = hist_find(shgd->hist_ptr, str, + shgd->hist_ptr->histind, + flag&HIST_QUESTION, -1); + if((n = hl.hist_command) == -1) + n = 0; /* not found */ + } + if(n) + { + if(n < 0) /* determine index for backref */ + n = shgd->hist_ptr->histind + n; + /* search and use history file if found */ + if(n > 0 && hist_seek(shgd->hist_ptr, n) != -1) + ref = shgd->hist_ptr->histfp; + + } + if(!ref) + { + /* string not found or command # out of range */ + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, "%s: event not found", evp); + *cp = c; + DONE(); + } + + if(str) /* string search: restore orig. line */ + { + if(flag&HIST_QUESTION) + *cp++ = c; /* skip second question mark */ + else + *cp = c; + } + + /* colon introduces either word designators or modifiers */ + if(*(evp = cp) == ':') + cp++; + + w[0] = 0; /* -1 means last word, -2 means match from !?string? */ + w[1] = -1; /* -1 means last word, -2 means suppress last word */ + + if(flag & HIST_QUICKSUBST) /* shortcut substitution */ + goto getsel; + + n = 0; + while(n < 2) + { + switch(c = *cp++) { + case '^': /* first word */ + if(n == 0) + { + w[0] = w[1] = 1; + goto skip; + } + else + goto skip2; + case '$': /* last word */ + w[n] = -1; + goto skip; + case '%': /* match from !?string? event designator */ + if(n == 0) + { + if(!str) + { + w[0] = 0; + w[1] = -1; + ref = wm; + } + else + { + w[0] = -2; + w[1] = sftell(ref) + hl.hist_char; + } + sfseek(wm, 0, SEEK_SET); + goto skip; + } + default: + skip2: + cp--; + n = 2; + break; + case '*': /* until last word */ + if(n == 0) + w[0] = 1; + w[1] = -1; + skip: + flag |= HIST_WORDDSGN; + n = 2; + break; + case '-': /* until last word or specified index */ + w[1] = -2; + flag |= HIST_WORDDSGN; + n = 1; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': /* specify index */ + if((*evp == ':') || w[1] == -2) + { + w[n] = c - '0'; + while(isdigit(c=*cp++)) + w[n] = w[n] * 10 + c - '0'; + flag |= HIST_WORDDSGN; + if(n == 0) + w[1] = w[0]; + n++; + } + else + n = 2; + cp--; + break; + } + } + + if(w[0] != -2 && w[1] > 0 && w[0] > w[1]) + { + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp); + *cp = c; + DONE(); + } + + /* no valid word designator after colon, rewind */ + if(!(flag & HIST_WORDDSGN) && (*evp == ':')) + cp = evp; + +getsel: + /* open temp buffer, let sfio do the (re)allocation */ + tmp = sfopen(NULL, NULL, "swr"); + + /* push selected words into buffer, squash + whitespace into single blank or a newline */ + n = i = q = 0; + + while((c = sfgetc(ref)) > 0) + { + if(isspace(c)) + { + flag |= (c == '\n' ? HIST_NEWLINE : 0); + continue; + } + + if(n >= w[0] && ((w[0] != -2) ? (w[1] < 0 || n <= w[1]) : 1)) + { + if(w[0] < 0) + sfseek(tmp, 0, SEEK_SET); + else + i = sftell(tmp); + + if(i > 0) + sfputc(tmp, flag & HIST_NEWLINE ? '\n' : ' '); + + flag &= ~HIST_NEWLINE; + p = 1; + } + else + p = 0; + + do + { + cc = strchr(qc, c); + q ^= cc ? 1<<(int)(cc - qc) : 0; + if(p) + sfputc(tmp, c); + } + while((c = sfgetc(ref)) > 0 && (!isspace(c) || q)); + + if(w[0] == -2 && sftell(ref) > w[1]) + break; + + flag |= (c == '\n' ? HIST_NEWLINE : 0); + n++; + } + if(w[0] != -2 && w[1] >= 0 && w[1] >= n) + { + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp); + *cp = c; + DONE(); + } + else if(w[1] == -2) /* skip last word */ + sfseek(tmp, i, SEEK_SET); + + /* remove trailing newline */ + if(sftell(tmp)) + { + sfseek(tmp, -1, SEEK_CUR); + if(sfgetc(tmp) == '\n') + sfungetc(tmp, '\n'); + } + + sfputc(tmp, '\0'); + + if(str) + { + if(wm) + sfclose(wm); + wm = tmp; + } + + if(cc && (flag&HIST_HASH)) + { + /* close !# temp file */ + sfclose(ref); + flag &= ~HIST_HASH; + free(cc); + cc = 0; + } + + evp = cp; + + /* selected line/words are now in buffer, now go for the modifiers */ + while(*cp == ':' || (flag & HIST_QUICKSUBST)) + { + if(flag & HIST_QUICKSUBST) + { + flag &= ~HIST_QUICKSUBST; + c = 's'; + cp--; + } + else + c = *++cp; + + sfseek(tmp, 0, SEEK_SET); + tmp2 = sfopen(tmp2, NULL, "swr"); + + if(c == 'g') /* global substitution */ + { + flag |= HIST_GLOBALSUBST; + c = *++cp; + } + + if(cc = strchr(modifiers, c)) + flag |= mod_flags[cc - modifiers]; + else + { + errormsg(SH_DICT, ERROR_ERROR, "%c: unrecognized history modifier", c); + DONE(); + } + + if(c == 'h' || c == 'r') /* head or base */ + { + n = -1; + while((c = sfgetc(tmp)) > 0) + { /* remember position of / or . */ + if((c == '/' && *cp == 'h') || (c == '.' && *cp == 'r')) + n = sftell(tmp2); + sfputc(tmp2, c); + } + if(n > 0) + { /* rewind to last / or . */ + sfseek(tmp2, n, SEEK_SET); + /* end string there */ + sfputc(tmp2, '\0'); + } + } + else if(c == 't' || c == 'e') /* tail or suffix */ + { + n = 0; + while((c = sfgetc(tmp)) > 0) + { /* remember position of / or . */ + if((c == '/' && *cp == 't') || (c == '.' && *cp == 'e')) + n = sftell(tmp); + } + /* rewind to last / or . */ + sfseek(tmp, n, SEEK_SET); + /* copy from there on */ + while((c = sfgetc(tmp)) > 0) + sfputc(tmp2, c); + } + else if(c == 's' || c == '&') + { + cp++; + + if(c == 's') + { + /* preset old with match from !?string? */ + if(!sb.str[0] && wm) + sb.str[0] = strdup(sfsetbuf(wm, (Void_t*)1, 0)); + cp = parse_subst(cp, &sb); + } + + if(!sb.str[0] || !sb.str[1]) + { + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, + "%s%s: no previous substitution", + (flag & HIST_QUICKSUBST) ? ":s" : "", + evp); + *cp = c; + DONE(); + } + + /* need pointer for strstr() */ + str = sfsetbuf(tmp, (Void_t*)1, 0); + + flag |= HIST_SUBSTITUTE; + while(flag & HIST_SUBSTITUTE) + { + /* find string */ + if(cc = strstr(str, sb.str[0])) + { /* replace it */ + c = *cc; + *cc = '\0'; + sfputr(tmp2, str, -1); + sfputr(tmp2, sb.str[1], -1); + *cc = c; + str = cc + strlen(sb.str[0]); + } + else if(!sftell(tmp2)) + { /* not successfull */ + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, + "%s%s: substitution failed", + (flag & HIST_QUICKSUBST) ? ":s" : "", + evp); + *cp = c; + DONE(); + } + /* loop if g modifier specified */ + if(!cc || !(flag & HIST_GLOBALSUBST)) + flag &= ~HIST_SUBSTITUTE; + } + /* output rest of line */ + sfputr(tmp2, str, -1); + if(*cp) + cp--; + } + + if(sftell(tmp2)) + { /* if any substitions done, swap buffers */ + if(wm != tmp) + sfclose(tmp); + tmp = tmp2; + tmp2 = 0; + } + cc = 0; + if(*cp) + cp++; + } + + /* flush temporary buffer to stack */ + if(tmp) + { + sfseek(tmp, 0, SEEK_SET); + + if(flag & HIST_QUOTE) + stakputc('\''); + + while((c = sfgetc(tmp)) > 0) + { + if(isspace(c)) + { + flag = flag & ~HIST_NEWLINE; + + /* squash white space to either a + blank or a newline */ + do + flag |= (c == '\n' ? HIST_NEWLINE : 0); + while((c = sfgetc(tmp)) > 0 && isspace(c)); + + sfungetc(tmp, c); + + c = (flag & HIST_NEWLINE) ? '\n' : ' '; + + if(flag & HIST_QUOTE_BR) + { + stakputc('\''); + stakputc(c); + stakputc('\''); + } + else + stakputc(c); + } + else if((c == '\'') && (flag & HIST_QUOTE)) + { + stakputc('\''); + stakputc('\\'); + stakputc(c); + stakputc('\''); + } + else + stakputc(c); + } + if(flag & HIST_QUOTE) + stakputc('\''); + } + } + + stakputc('\0'); + +done: + if(cc && (flag&HIST_HASH)) + { + /* close !# temp file */ + sfclose(ref); + free(cc); + cc = 0; + } + + /* error? */ + if(staktell() && !(flag & HIST_ERROR)) + *xp = strdup(stakfreeze(1)); + + /* restore shell stack */ + if(off) + stakset(sp,off); + else + stakseek(0); + + /* drop temporary files */ + + if(tmp && tmp != wm) + sfclose(tmp); + if(tmp2) + sfclose(tmp2); + + return (flag & HIST_ERROR ? HIST_ERROR : flag & HIST_FLAG_RETURN_MASK); +} + +#endif diff --git a/src/cmd/ksh93/edit/history.c b/src/cmd/ksh93/edit/history.c new file mode 100644 index 0000000..99fc01d --- /dev/null +++ b/src/cmd/ksh93/edit/history.c @@ -0,0 +1,1222 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2011 AT&T Intellectual Property * +* and is licensed under the * +* Eclipse Public License, Version 1.0 * +* by AT&T Intellectual Property * +* * +* A copy of the License is available at * +* http://www.eclipse.org/org/documents/epl-v10.html * +* (with md5 checksum b35adb5213ca9657e911e9befb180842) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * History file manipulation routines + * + * David Korn + * AT&T Labs + * + */ + +/* + * Each command in the history file starts on an even byte is null terminated. + * The first byte must contain the special character HIST_UNDO and the second + * byte is the version number. The sequence HIST_UNDO 0, following a command, + * nullifies the previous command. A six byte sequence starting with + * HIST_CMDNO is used to store the command number so that it is not necessary + * to read the file from beginning to end to get to the last block of + * commands. This format of this sequence is different in version 1 + * then in version 0. Version 1 allows commands to use the full 8 bit + * character set. It can understand version 0 format files. + */ + + +#define HIST_MAX (sizeof(int)*HIST_BSIZE) +#define HIST_BIG (0100000-1024) /* 1K less than maximum short */ +#define HIST_LINE 32 /* typical length for history line */ +#define HIST_MARKSZ 6 +#define HIST_RECENT 600 +#define HIST_UNDO 0201 /* invalidate previous command */ +#define HIST_CMDNO 0202 /* next 3 bytes give command number */ +#define HIST_BSIZE 4096 /* size of history file buffer */ +#define HIST_DFLT 512 /* default size of history list */ + +#if SHOPT_AUDIT +# define _HIST_AUDIT Sfio_t *auditfp; \ + char *tty; \ + int auditmask; +#else +# define _HIST_AUDIT +#endif + +#define _HIST_PRIVATE \ + void *histshell; \ + off_t histcnt; /* offset into history file */\ + off_t histmarker; /* offset of last command marker */ \ + int histflush; /* set if flushed outside of hflush() */\ + int histmask; /* power of two mask for histcnt */ \ + char histbuff[HIST_BSIZE+1]; /* history file buffer */ \ + int histwfail; \ + _HIST_AUDIT \ + off_t histcmds[2]; /* offset for recent commands, must be last */ + +#define hist_ind(hp,c) ((int)((c)&(hp)->histmask)) + +#include <ast.h> +#include <sfio.h> +#include "FEATURE/time" +#include <error.h> +#include <ls.h> +#if KSHELL +# include "defs.h" +# include "variables.h" +# include "path.h" +# include "builtins.h" +# include "io.h" +#else +# include <ctype.h> +#endif /* KSHELL */ +#include "history.h" + +#if !KSHELL +# define new_of(type,x) ((type*)malloc((unsigned)sizeof(type)+(x))) +# define NIL(type) ((type)0) +# define path_relative(s,x) (s,x) +# ifdef __STDC__ +# define nv_getval(s) getenv(#s) +# else +# define nv_getval(s) getenv("s") +# endif /* __STDC__ */ +# define e_unknown "unknown" +# define sh_translate(x) (x) + char login_sh = 0; + char hist_fname[] = "/.history"; +#endif /* KSHELL */ + +#ifndef O_BINARY +# define O_BINARY 0 +#endif /* O_BINARY */ + +int _Hist = 0; +static void hist_marker(char*,long); +static History_t* hist_trim(History_t*, int); +static int hist_nearend(History_t*,Sfio_t*, off_t); +static int hist_check(int); +static int hist_clean(int); +#ifdef SF_BUFCONST + static ssize_t hist_write(Sfio_t*, const void*, size_t, Sfdisc_t*); + static int hist_exceptf(Sfio_t*, int, void*, Sfdisc_t*); +#else + static int hist_write(Sfio_t*, const void*, int, Sfdisc_t*); + static int hist_exceptf(Sfio_t*, int, Sfdisc_t*); +#endif + + +static int histinit; +static mode_t histmode; +static History_t *wasopen; +static History_t *hist_ptr; + +#if SHOPT_ACCTFILE + static int acctfd; + static char *logname; +# include <pwd.h> + + static int acctinit(History_t *hp) + { + register char *cp, *acctfile; + Namval_t *np = nv_search("ACCTFILE",((Shell_t*)hp->histshell)->var_tree,0); + + if(!np || !(acctfile=nv_getval(np))) + return(0); + if(!(cp = getlogin())) + { + struct passwd *userinfo = getpwuid(getuid()); + if(userinfo) + cp = userinfo->pw_name; + else + cp = "unknown"; + } + logname = strdup(cp); + if((acctfd=sh_open(acctfile, + O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 && + (unsigned)acctfd < 10) + { + int n; + if((n = fcntl(acctfd, F_DUPFD, 10)) >= 0) + { + close(acctfd); + acctfd = n; + } + } + if(acctfd < 0) + { + acctfd = 0; + return(0); + } + if(sh_isdevfd(acctfile)) + { + char newfile[16]; + sfsprintf(newfile,sizeof(newfile),"%.8s%d\0",e_devfdNN,acctfd); + nv_putval(np,newfile,NV_RDONLY); + } + else + fcntl(acctfd,F_SETFD,FD_CLOEXEC); + return(1); + } +#endif /* SHOPT_ACCTFILE */ + +#if SHOPT_AUDIT +static int sh_checkaudit(History_t *hp, const char *name, char *logbuf, size_t len) +{ + char *buff, *cp, *last; + int id1, id2, r=0, n, fd; + if((fd=open(name, O_RDONLY)) < 0) + return(0); + if((n = read(fd, logbuf,len-1)) < 0) + goto done; + while(logbuf[n-1]=='\n') + n--; + logbuf[n] = 0; + if(!(cp=strchr(logbuf,';')) && !(cp=strchr(logbuf,' '))) + goto done; + *cp = 0; + do + { + cp++; + id1 = id2 = strtol(cp,&last,10); + if(*last=='-') + id1 = strtol(last+1,&last,10); + if(shgd->euserid >=id1 && shgd->euserid <= id2) + r |= 1; + if(shgd->userid >=id1 && shgd->userid <= id2) + r |= 2; + cp = last; + } + while(*cp==';' || *cp==' '); +done: + close(fd); + return(r); + +} +#endif /*SHOPT_AUDIT*/ + +static const unsigned char hist_stamp[2] = { HIST_UNDO, HIST_VERSION }; +static const Sfdisc_t hist_disc = { NULL, hist_write, NULL, hist_exceptf, NULL}; + +static void hist_touch(void *handle) +{ + touch((char*)handle, (time_t)0, (time_t)0, 0); +} + +/* + * open the history file + * if HISTNAME is not given and userid==0 then no history file. + * if login_sh and HISTFILE is longer than HIST_MAX bytes then it is + * cleaned up. + * hist_open() returns 1, if history file is open + */ +int sh_histinit(void *sh_context) +{ + Shell_t *shp = (Shell_t*)sh_context; + register int fd; + register History_t *hp; + register char *histname; + char *fname=0; + int histmask, maxlines, hist_start=0; + register char *cp; + register off_t hsize = 0; + + if(shgd->hist_ptr=hist_ptr) + return(1); + if(!(histname = nv_getval(HISTFILE))) + { + int offset = staktell(); + if(cp=nv_getval(HOME)) + stakputs(cp); + stakputs(hist_fname); + stakputc(0); + stakseek(offset); + histname = stakptr(offset); + } +#ifdef future + if(hp=wasopen) + { + /* reuse history file if same name */ + wasopen = 0; + shgd->hist_ptr = hist_ptr = hp; + if(strcmp(histname,hp->histname)==0) + return(1); + else + hist_free(); + } +#endif +retry: + cp = path_relative(shp,histname); + if(!histinit) + histmode = S_IRUSR|S_IWUSR; + if((fd=open(cp,O_BINARY|O_APPEND|O_RDWR|O_CREAT,histmode))>=0) + { + hsize=lseek(fd,(off_t)0,SEEK_END); + } + if((unsigned)fd <=2) + { + int n; + if((n=fcntl(fd,F_DUPFD,10))>=0) + { + close(fd); + fd=n; + } + } + /* make sure that file has history file format */ + if(hsize && hist_check(fd)) + { + close(fd); + hsize = 0; + if(unlink(cp)>=0) + goto retry; + fd = -1; + } + if(fd < 0) + { +#if KSHELL + /* don't allow root a history_file in /tmp */ + if(shgd->userid) +#endif /* KSHELL */ + { + if(!(fname = pathtmp(NIL(char*),0,0,NIL(int*)))) + return(0); + fd = open(fname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR); + } + } + if(fd<0) + return(0); + /* set the file to close-on-exec */ + fcntl(fd,F_SETFD,FD_CLOEXEC); + if(cp=nv_getval(HISTSIZE)) + maxlines = (unsigned)strtol(cp, (char**)0, 10); + else + maxlines = HIST_DFLT; + for(histmask=16;histmask <= maxlines; histmask <<=1 ); + if(!(hp=new_of(History_t,(--histmask)*sizeof(off_t)))) + { + close(fd); + return(0); + } + shgd->hist_ptr = hist_ptr = hp; + hp->histshell = (void*)shp; + hp->histsize = maxlines; + hp->histmask = histmask; + hp->histfp= sfnew(NIL(Sfio_t*),hp->histbuff,HIST_BSIZE,fd,SF_READ|SF_WRITE|SF_APPENDWR|SF_SHARE); + memset((char*)hp->histcmds,0,sizeof(off_t)*(hp->histmask+1)); + hp->histind = 1; + hp->histcmds[1] = 2; + hp->histcnt = 2; + hp->histname = strdup(histname); + hp->histdisc = hist_disc; + if(hsize==0) + { + /* put special characters at front of file */ + sfwrite(hp->histfp,(char*)hist_stamp,2); + sfsync(hp->histfp); + } + /* initialize history list */ + else + { + int first,last; + off_t mark,size = (HIST_MAX/4)+maxlines*HIST_LINE; + hp->histind = first = hist_nearend(hp,hp->histfp,hsize-size); + histinit = 1; + hist_eof(hp); /* this sets histind to last command */ + if((hist_start = (last=(int)hp->histind)-maxlines) <=0) + hist_start = 1; + mark = hp->histmarker; + while(first > hist_start) + { + size += size; + first = hist_nearend(hp,hp->histfp,hsize-size); + hp->histind = first; + } + histinit = hist_start; + hist_eof(hp); + if(!histinit) + { + sfseek(hp->histfp,hp->histcnt=hsize,SEEK_SET); + hp->histind = last; + hp->histmarker = mark; + } + histinit = 0; + } + if(fname) + { + unlink(fname); + free((void*)fname); + } + if(hist_clean(fd) && hist_start>1 && hsize > HIST_MAX) + { +#ifdef DEBUG + sfprintf(sfstderr,"%d: hist_trim hsize=%d\n",getpid(),hsize); + sfsync(sfstderr); +#endif /* DEBUG */ + hp = hist_trim(hp,(int)hp->histind-maxlines); + } + sfdisc(hp->histfp,&hp->histdisc); +#if KSHELL + (HISTCUR)->nvalue.lp = (&hp->histind); +#endif /* KSHELL */ + sh_timeradd(1000L*(HIST_RECENT-30), 1, hist_touch, (void*)hp->histname); +#if SHOPT_ACCTFILE + if(sh_isstate(SH_INTERACTIVE)) + acctinit(hp); +#endif /* SHOPT_ACCTFILE */ +#if SHOPT_AUDIT + { + char buff[SF_BUFSIZE]; + hp->auditfp = 0; + if(sh_isstate(SH_INTERACTIVE) && (hp->auditmask=sh_checkaudit(hp,SHOPT_AUDITFILE, buff, sizeof(buff)))) + { + if((fd=sh_open(buff,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 && fd < 10) + { + int n; + if((n = sh_fcntl(fd,F_DUPFD, 10)) >= 0) + { + sh_close(fd); + fd = n; + } + } + if(fd>=0) + { + fcntl(fd,F_SETFD,FD_CLOEXEC); + hp->tty = strdup(ttyname(2)); + hp->auditfp = sfnew((Sfio_t*)0,NULL,-1,fd,SF_WRITE); + } + } + } +#endif + return(1); +} + +/* + * close the history file and free the space + */ + +void hist_close(register History_t *hp) +{ + sfclose(hp->histfp); +#if SHOPT_AUDIT + if(hp->auditfp) + { + if(hp->tty) + free((void*)hp->tty); + sfclose(hp->auditfp); + } +#endif /* SHOPT_AUDIT */ + free((char*)hp); + hist_ptr = 0; + shgd->hist_ptr = 0; +#if SHOPT_ACCTFILE + if(acctfd) + { + close(acctfd); + acctfd = 0; + } +#endif /* SHOPT_ACCTFILE */ +} + +/* + * check history file format to see if it begins with special byte + */ +static int hist_check(register int fd) +{ + unsigned char magic[2]; + lseek(fd,(off_t)0,SEEK_SET); + if((read(fd,(char*)magic,2)!=2) || (magic[0]!=HIST_UNDO)) + return(1); + return(0); +} + +/* + * clean out history file OK if not modified in HIST_RECENT seconds + */ +static int hist_clean(int fd) +{ + struct stat statb; + return(fstat(fd,&statb)>=0 && (time((time_t*)0)-statb.st_mtime) >= HIST_RECENT); +} + +/* + * Copy the last <n> commands to a new file and make this the history file + */ + +static History_t* hist_trim(History_t *hp, int n) +{ + register char *cp; + register int incmd=1, c=0; + register History_t *hist_new, *hist_old = hp; + char *buff, *endbuff, *tmpname=0; + off_t oldp,newp; + struct stat statb; + unlink(hist_old->histname); + if(access(hist_old->histname,F_OK) >= 0) + { + /* The unlink can fail on windows 95 */ + int fd; + char *last, *name=hist_old->histname; + close(sffileno(hist_old->histfp)); + tmpname = (char*)malloc(strlen(name)+14); + if(last = strrchr(name,'/')) + { + *last = 0; + pathtmp(tmpname,name,"hist",NIL(int*)); + *last = '/'; + } + else + pathtmp(tmpname,".","hist",NIL(int*)); + if(rename(name,tmpname) < 0) + { + free(tmpname); + tmpname = name; + } + fd = open(tmpname,O_RDONLY); + sfsetfd(hist_old->histfp,fd); + if(tmpname==name) + tmpname = 0; + } + hist_ptr = 0; + if(fstat(sffileno(hist_old->histfp),&statb)>=0) + { + histinit = 1; + histmode = statb.st_mode; + } + if(!sh_histinit(hp->histshell)) + { + /* use the old history file */ + return hist_ptr = hist_old; + } + hist_new = hist_ptr; + hist_ptr = hist_old; + if(--n < 0) + n = 0; + newp = hist_seek(hist_old,++n); + while(1) + { + if(!incmd) + { + c = hist_ind(hist_new,++hist_new->histind); + hist_new->histcmds[c] = hist_new->histcnt; + if(hist_new->histcnt > hist_new->histmarker+HIST_BSIZE/2) + { + char locbuff[HIST_MARKSZ]; + hist_marker(locbuff,hist_new->histind); + sfwrite(hist_new->histfp,locbuff,HIST_MARKSZ); + hist_new->histcnt += HIST_MARKSZ; + hist_new->histmarker = hist_new->histcmds[hist_ind(hist_new,c)] = hist_new->histcnt; + } + oldp = newp; + newp = hist_seek(hist_old,++n); + if(newp <=oldp) + break; + } + if(!(buff=(char*)sfreserve(hist_old->histfp,SF_UNBOUND,0))) + break; + *(endbuff=(cp=buff)+sfvalue(hist_old->histfp)) = 0; + /* copy to null byte */ + incmd = 0; + while(*cp++); + if(cp > endbuff) + incmd = 1; + else if(*cp==0) + cp++; + if(cp > endbuff) + cp = endbuff; + c = cp-buff; + hist_new->histcnt += c; + sfwrite(hist_new->histfp,buff,c); + } + hist_cancel(hist_new); + sfclose(hist_old->histfp); + if(tmpname) + { + unlink(tmpname); + free(tmpname); + } + free((char*)hist_old); + return hist_ptr = hist_new; +} + +/* + * position history file at size and find next command number + */ +static int hist_nearend(History_t *hp, Sfio_t *iop, register off_t size) +{ + register unsigned char *cp, *endbuff; + register int n, incmd=1; + unsigned char *buff, marker[4]; + if(size <= 2L || sfseek(iop,size,SEEK_SET)<0) + goto begin; + /* skip to marker command and return the number */ + /* numbering commands occur after a null and begin with HIST_CMDNO */ + while(cp=buff=(unsigned char*)sfreserve(iop,SF_UNBOUND,SF_LOCKR)) + { + n = sfvalue(iop); + *(endbuff=cp+n) = 0; + while(1) + { + /* check for marker */ + if(!incmd && *cp++==HIST_CMDNO && *cp==0) + { + n = cp+1 - buff; + incmd = -1; + break; + } + incmd = 0; + while(*cp++); + if(cp>endbuff) + { + incmd = 1; + break; + } + if(*cp==0 && ++cp>endbuff) + break; + } + size += n; + sfread(iop,(char*)buff,n); + if(incmd < 0) + { + if((n=sfread(iop,(char*)marker,4))==4) + { + n = (marker[0]<<16)|(marker[1]<<8)|marker[2]; + if(n < size/2) + { + hp->histmarker = hp->histcnt = size+4; + return(n); + } + n=4; + } + if(n >0) + size += n; + incmd = 0; + } + } +begin: + sfseek(iop,(off_t)2,SEEK_SET); + hp->histmarker = hp->histcnt = 2L; + return(1); +} + +/* + * This routine reads the history file from the present position + * to the end-of-file and puts the information in the in-core + * history table + * Note that HIST_CMDNO is only recognized at the beginning of a command + * and that HIST_UNDO as the first character of a command is skipped + * unless it is followed by 0. If followed by 0 then it cancels + * the previous command. + */ + +void hist_eof(register History_t *hp) +{ + register char *cp,*first,*endbuff; + register int incmd = 0; + register off_t count = hp->histcnt; + int oldind,n,skip=0; + off_t last = sfseek(hp->histfp,(off_t)0,SEEK_END); + if(last < count) + { + last = -1; + count = 2+HIST_MARKSZ; + oldind = hp->histind; + if((hp->histind -= hp->histsize) < 0) + hp->histind = 1; + } +again: + sfseek(hp->histfp,count,SEEK_SET); + while(cp=(char*)sfreserve(hp->histfp,SF_UNBOUND,0)) + { + n = sfvalue(hp->histfp); + *(endbuff = cp+n) = 0; + first = cp += skip; + while(1) + { + while(!incmd) + { + if(cp>first) + { + count += (cp-first); + n = hist_ind(hp, ++hp->histind); +#ifdef future + if(count==hp->histcmds[n]) + { + sfprintf(sfstderr,"count match n=%d\n",n); + if(histinit) + { + histinit = 0; + return; + } + } + else if(n>=histinit) +#endif + hp->histcmds[n] = count; + first = cp; + } + switch(*((unsigned char*)(cp++))) + { + case HIST_CMDNO: + if(*cp==0) + { + hp->histmarker=count+2; + cp += (HIST_MARKSZ-1); + hp->histind--; + if(!histinit && (cp <= endbuff)) + { + unsigned char *marker = (unsigned char*)(cp-4); + hp->histind = ((marker[0]<<16)|(marker[1]<<8)|marker[2] -1); + } + } + break; + case HIST_UNDO: + if(*cp==0) + { + cp+=1; + hp->histind-=2; + } + break; + default: + cp--; + incmd = 1; + } + if(cp > endbuff) + { + cp++; + goto refill; + } + } + first = cp; + while(*cp++); + if(cp > endbuff) + break; + incmd = 0; + while(*cp==0) + { + if(++cp > endbuff) + goto refill; + } + } + refill: + count += (--cp-first); + skip = (cp-endbuff); + if(!incmd && !skip) + hp->histcmds[hist_ind(hp,++hp->histind)] = count; + } + hp->histcnt = count; + if(incmd && last) + { + sfputc(hp->histfp,0); + hist_cancel(hp); + count = 2; + skip = 0; + oldind -= hp->histind; + hp->histind = hp->histind-hp->histsize + oldind +2; + if(hp->histind<0) + hp->histind = 1; + if(last<0) + { + char buff[HIST_MARKSZ]; + int fd = open(hp->histname,O_RDWR); + if(fd>=0) + { + hist_marker(buff,hp->histind); + write(fd,(char*)hist_stamp,2); + write(fd,buff,HIST_MARKSZ); + close(fd); + } + } + last = 0; + goto again; + } +} + +/* + * This routine will cause the previous command to be cancelled + */ + +void hist_cancel(register History_t *hp) +{ + register int c; + if(!hp) + return; + sfputc(hp->histfp,HIST_UNDO); + sfputc(hp->histfp,0); + sfsync(hp->histfp); + hp->histcnt += 2; + c = hist_ind(hp,--hp->histind); + hp->histcmds[c] = hp->histcnt; +} + +/* + * flush the current history command + */ + +void hist_flush(register History_t *hp) +{ + register char *buff; + if(hp) + { + if(buff=(char*)sfreserve(hp->histfp,0,SF_LOCKR)) + { + hp->histflush = sfvalue(hp->histfp)+1; + sfwrite(hp->histfp,buff,0); + } + else + hp->histflush=0; + if(sfsync(hp->histfp)<0) + { + hist_close(hp); + if(!sh_histinit(hp->histshell)) + sh_offoption(SH_HISTORY); + } + hp->histflush = 0; + } +} + +/* + * This is the write discipline for the history file + * When called from hist_flush(), trailing newlines are deleted and + * a zero byte. Line sequencing is added as required + */ + +#ifdef SF_BUFCONST +static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle) +#else +static int hist_write(Sfio_t *iop,const void *buff,register int insize,Sfdisc_t* handle) +#endif +{ + register History_t *hp = (History_t*)handle; + register char *bufptr = ((char*)buff)+insize; + register int c,size = insize; + register off_t cur; + int saved=0; + char saveptr[HIST_MARKSZ]; + if(!hp->histflush) + return(write(sffileno(iop),(char*)buff,size)); + if((cur = lseek(sffileno(iop),(off_t)0,SEEK_END)) <0) + { + errormsg(SH_DICT,2,"hist_flush: EOF seek failed errno=%d",errno); + return(-1); + } + hp->histcnt = cur; + /* remove whitespace from end of commands */ + while(--bufptr >= (char*)buff) + { + c= *bufptr; + if(!isspace(c)) + { + if(c=='\\' && *(bufptr+1)!='\n') + bufptr++; + break; + } + } + /* don't count empty lines */ + if(++bufptr <= (char*)buff) + return(insize); + *bufptr++ = '\n'; + *bufptr++ = 0; + size = bufptr - (char*)buff; +#if SHOPT_AUDIT + if(hp->auditfp) + { + time_t t=time((time_t*)0); + sfprintf(hp->auditfp,"%u;%u;%s;%*s%c",sh_isoption(SH_PRIVILEGED)?shgd->euserid:shgd->userid,t,hp->tty,size,buff,0); + sfsync(hp->auditfp); + } +#endif /* SHOPT_AUDIT */ +#if SHOPT_ACCTFILE + if(acctfd) + { + int timechars, offset; + offset = staktell(); + stakputs(buff); + stakseek(staktell() - 1); + timechars = sfprintf(staksp, "\t%s\t%x\n",logname,time(NIL(long *))); + lseek(acctfd, (off_t)0, SEEK_END); + write(acctfd, stakptr(offset), size - 2 + timechars); + stakseek(offset); + + } +#endif /* SHOPT_ACCTFILE */ + if(size&01) + { + size++; + *bufptr++ = 0; + } + hp->histcnt += size; + c = hist_ind(hp,++hp->histind); + hp->histcmds[c] = hp->histcnt; + if(hp->histflush>HIST_MARKSZ && hp->histcnt > hp->histmarker+HIST_BSIZE/2) + { + memcpy((void*)saveptr,(void*)bufptr,HIST_MARKSZ); + saved=1; + hp->histcnt += HIST_MARKSZ; + hist_marker(bufptr,hp->histind); + hp->histmarker = hp->histcmds[hist_ind(hp,c)] = hp->histcnt; + size += HIST_MARKSZ; + } + errno = 0; + size = write(sffileno(iop),(char*)buff,size); + if(saved) + memcpy((void*)bufptr,(void*)saveptr,HIST_MARKSZ); + if(size>=0) + { + hp->histwfail = 0; + return(insize); + } + return(-1); +} + +/* + * Put history sequence number <n> into buffer <buff> + * The buffer must be large enough to hold HIST_MARKSZ chars + */ + +static void hist_marker(register char *buff,register long cmdno) +{ + *buff++ = HIST_CMDNO; + *buff++ = 0; + *buff++ = (cmdno>>16); + *buff++ = (cmdno>>8); + *buff++ = cmdno; + *buff++ = 0; +} + +/* + * return byte offset in history file for command <n> + */ +off_t hist_tell(register History_t *hp, int n) +{ + return(hp->histcmds[hist_ind(hp,n)]); +} + +/* + * seek to the position of command <n> + */ +off_t hist_seek(register History_t *hp, int n) +{ + return(sfseek(hp->histfp,hp->histcmds[hist_ind(hp,n)],SEEK_SET)); +} + +/* + * write the command starting at offset <offset> onto file <outfile>. + * if character <last> appears before newline it is deleted + * each new-line character is replaced with string <nl>. + */ + +void hist_list(register History_t *hp,Sfio_t *outfile, off_t offset,int last, char *nl) +{ + register int oldc=0; + register int c; + if(offset<0 || !hp) + { + sfputr(outfile,sh_translate(e_unknown),'\n'); + return; + } + sfseek(hp->histfp,offset,SEEK_SET); + while((c = sfgetc(hp->histfp)) != EOF) + { + if(c && oldc=='\n') + sfputr(outfile,nl,-1); + else if(last && (c==0 || (c=='\n' && oldc==last))) + return; + else if(oldc) + sfputc(outfile,oldc); + oldc = c; + if(c==0) + return; + } + return; +} + +/* + * find index for last line with given string + * If flag==0 then line must begin with string + * direction < 1 for backwards search +*/ + +Histloc_t hist_find(register History_t*hp,char *string,register int index1,int flag,int direction) +{ + register int index2; + off_t offset; + int *coffset=0; + Histloc_t location; + location.hist_command = -1; + location.hist_char = 0; + location.hist_line = 0; + if(!hp) + return(location); + /* leading ^ means beginning of line unless escaped */ + if(flag) + { + index2 = *string; + if(index2=='\\') + string++; + else if(index2=='^') + { + flag=0; + string++; + } + } + if(flag) + coffset = &location.hist_char; + index2 = (int)hp->histind; + if(direction<0) + { + index2 -= hp->histsize; + if(index2<1) + index2 = 1; + if(index1 <= index2) + return(location); + } + else if(index1 >= index2) + return(location); + while(index1!=index2) + { + direction>0?++index1:--index1; + offset = hist_tell(hp,index1); + if((location.hist_line=hist_match(hp,offset,string,coffset))>=0) + { + location.hist_command = index1; + return(location); + } +#if KSHELL + /* allow a search to be aborted */ + if(((Shell_t*)hp->histshell)->trapnote&SH_SIGSET) + break; +#endif /* KSHELL */ + } + return(location); +} + +/* + * search for <string> in history file starting at location <offset> + * If coffset==0 then line must begin with string + * returns the line number of the match if successful, otherwise -1 + */ + +int hist_match(register History_t *hp,off_t offset,char *string,int *coffset) +{ + register unsigned char *first, *cp; + register int m,n,c=1,line=0; +#if SHOPT_MULTIBYTE + mbinit(); +#endif /* SHOPT_MULTIBYTE */ + sfseek(hp->histfp,offset,SEEK_SET); + if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0))) + return(-1); + m = sfvalue(hp->histfp); + n = strlen(string); + while(m > n) + { + if(*cp==*string && memcmp(cp,string,n)==0) + { + if(coffset) + *coffset = (cp-first); + return(line); + } + if(!coffset) + break; + if(*cp=='\n') + line++; +#if SHOPT_MULTIBYTE + if((c=mbsize(cp)) < 0) + c = 1; +#endif /* SHOPT_MULTIBYTE */ + cp += c; + m -= c; + } + return(-1); +} + + +#if SHOPT_ESH || SHOPT_VSH +/* + * copy command <command> from history file to s1 + * at most <size> characters copied + * if s1==0 the number of lines for the command is returned + * line=linenumber for emacs copy and only this line of command will be copied + * line < 0 for full command copy + * -1 returned if there is no history file + */ + +int hist_copy(char *s1,int size,int command,int line) +{ + register int c; + register History_t *hp = shgd->hist_ptr; + register int count = 0; + register char *s1max = s1+size; + if(!hp) + return(-1); + hist_seek(hp,command); + while ((c = sfgetc(hp->histfp)) && c!=EOF) + { + if(c=='\n') + { + if(count++ ==line) + break; + else if(line >= 0) + continue; + } + if(s1 && (line<0 || line==count)) + { + if(s1 >= s1max) + { + *--s1 = 0; + break; + } + *s1++ = c; + } + + } + sfseek(hp->histfp,(off_t)0,SEEK_END); + if(s1==0) + return(count); + if(count && (c= *(s1-1)) == '\n') + s1--; + *s1 = '\0'; + return(count); +} + +/* + * return word number <word> from command number <command> + */ + +char *hist_word(char *string,int size,int word) +{ + register int c; + register char *s1 = string; + register unsigned char *cp = (unsigned char*)s1; + register int flag = 0; + History_t *hp = hist_ptr; + if(!hp) + return(NIL(char*)); + hist_copy(string,size,(int)hp->histind-1,-1); + for(;c = *cp;cp++) + { + c = isspace(c); + if(c && flag) + { + *cp = 0; + if(--word==0) + break; + flag = 0; + } + else if(c==0 && flag==0) + { + s1 = (char*)cp; + flag++; + } + } + *cp = 0; + if(s1 != string) + strcpy(string,s1); + return(string); +} + +#endif /* SHOPT_ESH */ + +#if SHOPT_ESH +/* + * given the current command and line number, + * and number of lines back or foward, + * compute the new command and line number. + */ + +Histloc_t hist_locate(History_t *hp,register int command,register int line,int lines) +{ + Histloc_t next; + line += lines; + if(!hp) + { + command = -1; + goto done; + } + if(lines > 0) + { + register int count; + while(command <= hp->histind) + { + count = hist_copy(NIL(char*),0, command,-1); + if(count > line) + goto done; + line -= count; + command++; + } + } + else + { + register int least = (int)hp->histind-hp->histsize; + while(1) + { + if(line >=0) + goto done; + if(--command < least) + break; + line += hist_copy(NIL(char*),0, command,-1); + } + command = -1; + } +done: + next.hist_line = line; + next.hist_command = command; + return(next); +} +#endif /* SHOPT_ESH */ + + +/* + * Handle history file exceptions + */ +#ifdef SF_BUFCONST +static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle) +#else +static int hist_exceptf(Sfio_t* fp, int type, Sfdisc_t *handle) +#endif +{ + register int newfd,oldfd; + History_t *hp = (History_t*)handle; + if(type==SF_WRITE) + { + if(errno==ENOSPC || hp->histwfail++ >= 10) + return(0); + /* write failure could be NFS problem, try to re-open */ + close(oldfd=sffileno(fp)); + if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) >= 0) + { + if(fcntl(newfd, F_DUPFD, oldfd) !=oldfd) + return(-1); + fcntl(oldfd,F_SETFD,FD_CLOEXEC); + close(newfd); + if(lseek(oldfd,(off_t)0,SEEK_END) < hp->histcnt) + { + register int index = hp->histind; + lseek(oldfd,(off_t)2,SEEK_SET); + hp->histcnt = 2; + hp->histind = 1; + hp->histcmds[1] = 2; + hist_eof(hp); + hp->histmarker = hp->histcnt; + hp->histind = index; + } + return(1); + } + errormsg(SH_DICT,2,"History file write error-%d %s: file unrecoverable",errno,hp->histname); + return(-1); + } + return(0); +} diff --git a/src/cmd/ksh93/edit/vi.c b/src/cmd/ksh93/edit/vi.c new file mode 100644 index 0000000..376e349 --- /dev/null +++ b/src/cmd/ksh93/edit/vi.c @@ -0,0 +1,2753 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2011 AT&T Intellectual Property * +* and is licensed under the * +* Eclipse Public License, Version 1.0 * +* by AT&T Intellectual Property * +* * +* A copy of the License is available at * +* http://www.eclipse.org/org/documents/epl-v10.html * +* (with md5 checksum b35adb5213ca9657e911e9befb180842) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* Adapted for ksh by David Korn */ +/*+ VI.C P.D. Sullivan + * + * One line editor for the shell based on the vi editor. + * + * Questions to: + * P.D. Sullivan + * cbosgd!pds +-*/ + + +#if KSHELL +# include "defs.h" +#else +# include <ast.h> +# include "FEATURE/options" +# include <ctype.h> +#endif /* KSHELL */ +#include "io.h" + +#include "history.h" +#include "edit.h" +#include "terminal.h" +#include "FEATURE/time" + +#if SHOPT_OLDTERMIO +# undef ECHOCTL +# define echoctl (vp->ed->e_echoctl) +#else +# ifdef ECHOCTL +# define echoctl ECHOCTL +# else +# define echoctl 0 +# endif /* ECHOCTL */ +#endif /*SHOPT_OLDTERMIO */ + +#ifndef FIORDCHK +# define NTICKS 5 /* number of ticks for typeahead */ +#endif /* FIORDCHK */ + +#define MAXCHAR MAXLINE-2 /* max char per line */ + +#if SHOPT_MULTIBYTE +# include "lexstates.h" +# define gencpy(a,b) ed_gencpy(a,b) +# define genncpy(a,b,n) ed_genncpy(a,b,n) +# define genlen(str) ed_genlen(str) +# define digit(c) ((c&~STRIP)==0 && isdigit(c)) +# define is_print(c) ((c&~STRIP) || isprint(c)) +# if !_lib_iswprint && !defined(iswprint) +# define iswprint(c) ((c&~0177) || isprint(c)) +# endif + static int _isalph(int); + static int _ismetach(int); + static int _isblank(int); +# undef isblank +# define isblank(v) _isblank(virtual[v]) +# define isalph(v) _isalph(virtual[v]) +# define ismetach(v) _ismetach(virtual[v]) +#else + static genchar _c; +# define gencpy(a,b) strcpy((char*)(a),(char*)(b)) +# define genncpy(a,b,n) strncpy((char*)(a),(char*)(b),n) +# define genlen(str) strlen(str) +# define isalph(v) ((_c=virtual[v])=='_'||isalnum(_c)) +# undef isblank +# define isblank(v) isspace(virtual[v]) +# define ismetach(v) ismeta(virtual[v]) +# define digit(c) isdigit(c) +# define is_print(c) isprint(c) +#endif /* SHOPT_MULTIBYTE */ + +#if ( 'a' == 97) /* ASCII? */ +# define fold(c) ((c)&~040) /* lower and uppercase equivalent */ +#else +# define fold(c) ((c)|0100) /* lower and uppercase equivalent */ +#endif + +#ifndef iswascii +#define iswascii(c) (!((c)&(~0177))) +#endif + +typedef struct _vi_ +{ + int direction; + int lastmacro; + char addnl; /* boolean - add newline flag */ + char last_find; /* last find command */ + char last_cmd; /* last command */ + char repeat_set; + char nonewline; + int findchar; /* last find char */ + genchar *lastline; + int first_wind; /* first column of window */ + int last_wind; /* last column in window */ + int lastmotion; /* last motion */ + int long_char; /* line bigger than window */ + int long_line; /* line bigger than window */ + int ocur_phys; /* old current physical position */ + int ocur_virt; /* old last virtual position */ + int ofirst_wind; /* old window first col */ + int o_v_char; /* prev virtual[ocur_virt] */ + int repeat; /* repeat count for motion cmds */ + int lastrepeat; /* last repeat count for motion cmds */ + int u_column; /* undo current column */ + int U_saved; /* original virtual saved */ + genchar *U_space; /* used for U command */ + genchar *u_space; /* used for u command */ +#ifdef FIORDCHK + clock_t typeahead; /* typeahead occurred */ +#else + int typeahead; /* typeahead occurred */ +#endif /* FIORDCHK */ +#if SHOPT_MULTIBYTE + int bigvi; +#endif + Edit_t *ed; /* pointer to edit data */ +} Vi_t; + +#define editb (*vp->ed) + +#undef putchar +#define putchar(c) ed_putchar(vp->ed,c) + +#define crallowed editb.e_crlf +#define cur_virt editb.e_cur /* current virtual column */ +#define cur_phys editb.e_pcur /* current phys column cursor is at */ +#define curhline editb.e_hline /* current history line */ +#define first_virt editb.e_fcol /* first allowable column */ +#define globals editb.e_globals /* local global variables */ +#define histmin editb.e_hismin +#define histmax editb.e_hismax +#define last_phys editb.e_peol /* last column in physical */ +#define last_virt editb.e_eol /* last column */ +#define lsearch editb.e_search /* last search string */ +#define lookahead editb.e_lookahead /* characters in buffer */ +#define previous editb.e_lbuf /* lookahead buffer */ +#define max_col editb.e_llimit /* maximum column */ +#define Prompt editb.e_prompt /* pointer to prompt */ +#define plen editb.e_plen /* length of prompt */ +#define physical editb.e_physbuf /* physical image */ +#define usreof editb.e_eof /* user defined eof char */ +#define usrerase editb.e_erase /* user defined erase char */ +#define usrlnext editb.e_lnext /* user defined next literal */ +#define usrkill editb.e_kill /* user defined kill char */ +#define virtual editb.e_inbuf /* pointer to virtual image buffer */ +#define window editb.e_window /* window buffer */ +#define w_size editb.e_wsize /* window size */ +#define inmacro editb.e_inmacro /* true when in macro */ +#define yankbuf editb.e_killbuf /* yank/delete buffer */ + + +#define ABORT -2 /* user abort */ +#define APPEND -10 /* append chars */ +#define BAD -1 /* failure flag */ +#define BIGVI -15 /* user wants real vi */ +#define CONTROL -20 /* control mode */ +#define ENTER -25 /* enter flag */ +#define GOOD 0 /* success flag */ +#define INPUT -30 /* input mode */ +#define INSERT -35 /* insert mode */ +#define REPLACE -40 /* replace chars */ +#define SEARCH -45 /* search flag */ +#define TRANSLATE -50 /* translate virt to phys only */ + +#define INVALID (-1) /* invalid column */ + +static const char paren_chars[] = "([{)]}"; /* for % command */ + +static void cursor(Vi_t*, int); +static void del_line(Vi_t*,int); +static int getcount(Vi_t*,int); +static void getline(Vi_t*,int); +static int getrchar(Vi_t*); +static int mvcursor(Vi_t*,int); +static void pr_string(Vi_t*,const char*); +static void putstring(Vi_t*,int, int); +static void refresh(Vi_t*,int); +static void replace(Vi_t*,int, int); +static void restore_v(Vi_t*); +static void save_last(Vi_t*); +static void save_v(Vi_t*); +static int search(Vi_t*,int); +static void sync_cursor(Vi_t*); +static int textmod(Vi_t*,int,int); + +/*+ VI_READ( fd, shbuf, nchar ) + * + * This routine implements a one line version of vi and is + * called by _filbuf.c + * +-*/ + +/* + * if reedit is non-zero, initialize edit buffer with reedit chars + */ +int ed_viread(void *context, int fd, register char *shbuf, int nchar, int reedit) +{ + Edit_t *ed = (Edit_t*)context; + register int i; /* general variable */ + register int term_char=0; /* read() termination character */ + register Vi_t *vp = ed->e_vi; + char prompt[PRSIZE+2]; /* prompt */ + genchar Physical[2*MAXLINE]; /* physical image */ + genchar Ubuf[MAXLINE]; /* used for U command */ + genchar ubuf[MAXLINE]; /* used for u command */ + genchar Window[MAXLINE]; /* window image */ + int Globals[9]; /* local global variables */ + int esc_or_hang=0; /* <ESC> or hangup */ + char cntl_char=0; /* TRUE if control character present */ +#if SHOPT_RAWONLY +# define viraw 1 +#else + int viraw = (sh_isoption(SH_VIRAW) || ed->sh->st.trap[SH_KEYTRAP]); +# ifndef FIORDCHK + clock_t oldtime, newtime; + struct tms dummy; +# endif /* FIORDCHK */ +#endif /* SHOPT_RAWONLY */ + if(!vp) + { + ed->e_vi = vp = newof(0,Vi_t,1,0); + vp->lastline = (genchar*)malloc(MAXLINE*CHARSIZE); + vp->direction = -1; + vp->ed = ed; + } + + /*** setup prompt ***/ + + Prompt = prompt; + ed_setup(vp->ed,fd, reedit); + shbuf[reedit] = 0; + +#if !SHOPT_RAWONLY + if(!viraw) + { + /*** Change the eol characters to '\r' and eof ***/ + /* in addition to '\n' and make eof an ESC */ + if(tty_alt(ERRIO) < 0) + return(reexit?reedit:ed_read(context, fd, shbuf, nchar,0)); + +#ifdef FIORDCHK + ioctl(fd,FIORDCHK,&vp->typeahead); +#else + /* time the current line to determine typeahead */ + oldtime = times(&dummy); +#endif /* FIORDCHK */ +#if KSHELL + /* abort of interrupt has occurred */ + if(ed->sh->trapnote&SH_SIGSET) + i = -1; + else +#endif /* KSHELL */ + /*** Read the line ***/ + i = ed_read(context, fd, shbuf, nchar, 0); +#ifndef FIORDCHK + newtime = times(&dummy); + vp->typeahead = ((newtime-oldtime) < NTICKS); +#endif /* FIORDCHK */ + if(echoctl) + { + if( i <= 0 ) + { + /*** read error or eof typed ***/ + tty_cooked(ERRIO); + return(i); + } + term_char = shbuf[--i]; + if( term_char == '\r' ) + term_char = '\n'; + if( term_char=='\n' || term_char==ESC ) + shbuf[i--] = '\0'; + else + shbuf[i+1] = '\0'; + } + else + { + register int c = shbuf[0]; + + /*** Save and remove the last character if its an eol, ***/ + /* changing '\r' to '\n' */ + + if( i == 0 ) + { + /*** ESC was typed as first char of line ***/ + esc_or_hang = 1; + term_char = ESC; + shbuf[i--] = '\0'; /* null terminate line */ + } + else if( i<0 || c==usreof ) + { + /*** read error or eof typed ***/ + tty_cooked(ERRIO); + if( c == usreof ) + i = 0; + return(i); + } + else + { + term_char = shbuf[--i]; + if( term_char == '\r' ) + term_char = '\n'; +#if !defined(VEOL2) && !defined(ECHOCTL) + if(term_char=='\n') + { + tty_cooked(ERRIO); + return(i+1); + } +#endif + if( term_char=='\n' || term_char==usreof ) + { + /*** remove terminator & null terminate ***/ + shbuf[i--] = '\0'; + } + else + { + /** terminator was ESC, which is not xmitted **/ + term_char = ESC; + shbuf[i+1] = '\0'; + } + } + } + } + else +#endif /* SHOPT_RAWONLY */ + { + /*** Set raw mode ***/ + +#if !SHOPT_RAWONLY + if( editb.e_ttyspeed == 0 ) + { + /*** never did TCGETA, so do it ***/ + /* avoids problem if user does 'sh -o viraw' */ + tty_alt(ERRIO); + } +#endif /* SHOPT_RAWONLY */ + if(tty_raw(ERRIO,0) < 0 ) + return(reedit?reedit:ed_read(context, fd, shbuf, nchar,0)); + i = last_virt-1; + } + + /*** Initialize some things ***/ + + virtual = (genchar*)shbuf; +#if SHOPT_MULTIBYTE + virtual = (genchar*)roundof((char*)virtual-(char*)0,sizeof(genchar)); + shbuf[i+1] = 0; + i = ed_internal(shbuf,virtual)-1; +#endif /* SHOPT_MULTIBYTE */ + globals = Globals; + cur_phys = i + 1; + cur_virt = i; + first_virt = 0; + vp->first_wind = 0; + last_virt = i; + last_phys = i; + vp->last_wind = i; + vp->long_line = ' '; + vp->long_char = ' '; + vp->o_v_char = '\0'; + vp->ocur_phys = 0; + vp->ocur_virt = MAXCHAR; + vp->ofirst_wind = 0; + physical = Physical; + vp->u_column = INVALID - 1; + vp->U_space = Ubuf; + vp->u_space = ubuf; + window = Window; + window[0] = '\0'; + + if(!yankbuf) + yankbuf = (genchar*)malloc(MAXLINE*CHARSIZE); + if( vp->last_cmd == '\0' ) + { + /*** first time for this shell ***/ + + vp->last_cmd = 'i'; + vp->findchar = INVALID; + vp->lastmotion = '\0'; + vp->lastrepeat = 1; + vp->repeat = 1; + *yankbuf = 0; + } + + /*** fiddle around with prompt length ***/ + if( nchar+plen > MAXCHAR ) + nchar = MAXCHAR - plen; + max_col = nchar - 2; + + if( !viraw ) + { + int kill_erase = 0; + for(i=(echoctl?last_virt:0); i<last_virt; ++i ) + { + /*** change \r to \n, check for control characters, ***/ + /* delete appropriate ^Vs, */ + /* and estimate last physical column */ + + if( virtual[i] == '\r' ) + virtual[i] = '\n'; + if(!echoctl) + { + register int c = virtual[i]; + if( c<=usrerase) + { + /*** user typed escaped erase or kill char ***/ + cntl_char = 1; + if(is_print(c)) + kill_erase++; + } + else if( !is_print(c) ) + { + cntl_char = 1; + + if( c == usrlnext ) + { + if( i == last_virt ) + { + /*** eol/eof was escaped ***/ + /* so replace ^V with it */ + virtual[i] = term_char; + break; + } + + /*** delete ^V ***/ + gencpy((&virtual[i]), (&virtual[i+1])); + --cur_virt; + --last_virt; + } + } + } + } + + /*** copy virtual image to window ***/ + if(last_virt > 0) + last_phys = ed_virt_to_phys(vp->ed,virtual,physical,last_virt,0,0); + if( last_phys >= w_size ) + { + /*** line longer than window ***/ + vp->last_wind = w_size - 1; + } + else + vp->last_wind = last_phys; + genncpy(window, virtual, vp->last_wind+1); + + if( term_char!=ESC && (last_virt==INVALID + || virtual[last_virt]!=term_char) ) + { + /*** Line not terminated with ESC or escaped (^V) ***/ + /* eol, so return after doing a total update */ + /* if( (speed is greater or equal to 1200 */ + /* and something was typed) and */ + /* (control character present */ + /* or typeahead occurred) ) */ + + tty_cooked(ERRIO); + if( editb.e_ttyspeed==FAST && last_virt!=INVALID + && (vp->typeahead || cntl_char) ) + { + refresh(vp,TRANSLATE); + pr_string(vp,Prompt); + putstring(vp,0, last_phys+1); + if(echoctl) + ed_crlf(vp->ed); + else + while(kill_erase-- > 0) + putchar(' '); + } + + if( term_char=='\n' ) + { + if(!echoctl) + ed_crlf(vp->ed); + virtual[++last_virt] = '\n'; + } + vp->last_cmd = 'i'; + save_last(vp); +#if SHOPT_MULTIBYTE + virtual[last_virt+1] = 0; + last_virt = ed_external(virtual,shbuf); + return(last_virt); +#else + return(++last_virt); +#endif /* SHOPT_MULTIBYTE */ + } + + /*** Line terminated with escape, or escaped eol/eof, ***/ + /* so set raw mode */ + + if( tty_raw(ERRIO,0) < 0 ) + { + tty_cooked(ERRIO); + /* + * The following prevents drivers that return 0 on + * causing an infinite loop + */ + if(esc_or_hang) + return(-1); + virtual[++last_virt] = '\n'; +#if SHOPT_MULTIBYTE + virtual[last_virt+1] = 0; + last_virt = ed_external(virtual,shbuf); + return(last_virt); +#else + return(++last_virt); +#endif /* SHOPT_MULTIBYTE */ + } + + if(echoctl) /*** for cntl-echo erase the ^[ ***/ + pr_string(vp,"\b\b\b\b \b\b"); + + + if(crallowed) + { + /*** start over since there may be ***/ + /*** a control char, or cursor might not ***/ + /*** be at left margin (this lets us know ***/ + /*** where we are ***/ + cur_phys = 0; + window[0] = '\0'; + pr_string(vp,Prompt); + if( term_char==ESC && (last_virt<0 || virtual[last_virt]!=ESC)) + refresh(vp,CONTROL); + else + refresh(vp,INPUT); + } + else + { + /*** just update everything internally ***/ + refresh(vp,TRANSLATE); + } + } + + /*** Handle usrintr, usrquit, or EOF ***/ + + i = sigsetjmp(editb.e_env,0); + if( i != 0 ) + { + if(vp->ed->e_multiline) + { + cur_virt = last_virt; + sync_cursor(vp); + } + virtual[0] = '\0'; + tty_cooked(ERRIO); + + switch(i) + { + case UEOF: + /*** EOF ***/ + return(0); + + case UINTR: + /** interrupt **/ + return(-1); + } + return(-1); + } + + /*** Get a line from the terminal ***/ + + vp->U_saved = 0; + if(reedit) + { + cur_phys = vp->first_wind; + vp->ofirst_wind = INVALID; + refresh(vp,INPUT); + } + if(viraw) + getline(vp,APPEND); + else if(last_virt>=0 && virtual[last_virt]==term_char) + getline(vp,APPEND); + else + getline(vp,ESC); + if(vp->ed->e_multiline) + cursor(vp, last_phys); + /*** add a new line if user typed unescaped \n ***/ + /* to cause the shell to process the line */ + tty_cooked(ERRIO); + if(ed->e_nlist) + { + ed->e_nlist = 0; + stakset(ed->e_stkptr,ed->e_stkoff); + } + if( vp->addnl ) + { + virtual[++last_virt] = '\n'; + ed_crlf(vp->ed); + } + if( ++last_virt >= 0 ) + { +#if SHOPT_MULTIBYTE + if(vp->bigvi) + { + vp->bigvi = 0; + shbuf[last_virt-1] = '\n'; + } + else + { + virtual[last_virt] = 0; + last_virt = ed_external(virtual,shbuf); + } +#endif /* SHOPT_MULTIBYTE */ +#if SHOPT_EDPREDICT + if(vp->ed->nhlist) + ed_histlist(vp->ed,0); +#endif /* SHOPT_EDPREDICT */ + return(last_virt); + } + else + return(-1); +} + + +/*{ APPEND( char, mode ) + * + * This routine will append char after cur_virt in the virtual image. + * mode = APPEND, shift chars right before appending + * REPLACE, replace char if possible + * +}*/ + +static void append(Vi_t *vp,int c, int mode) +{ + register int i,j; + + if( last_virt<max_col && last_phys<max_col ) + { + if( mode==APPEND || (cur_virt==last_virt && last_virt>=0)) + { + j = (cur_virt>=0?cur_virt:0); + for(i = ++last_virt; i > j; --i) + virtual[i] = virtual[i-1]; + } + virtual[++cur_virt] = c; + } + else + ed_ringbell(); + return; +} + +/*{ BACKWORD( nwords, cmd ) + * + * This routine will position cur_virt at the nth previous word. + * +}*/ + +static void backword(Vi_t *vp,int nwords, register int cmd) +{ + register int tcur_virt = cur_virt; + while( nwords-- && tcur_virt > first_virt ) + { + if( !isblank(tcur_virt) && isblank(tcur_virt-1) + && tcur_virt>first_virt ) + --tcur_virt; + else if(cmd != 'B') + { + register int last = isalph(tcur_virt-1); + register int cur = isalph(tcur_virt); + if((!cur && last) || (cur && !last)) + --tcur_virt; + } + while( isblank(tcur_virt) && tcur_virt>=first_virt ) + --tcur_virt; + if( cmd == 'B' ) + { + while( !isblank(tcur_virt) && tcur_virt>=first_virt ) + --tcur_virt; + } + else + { + if(isalph(tcur_virt)) + while( isalph(tcur_virt) && tcur_virt>=first_virt ) + --tcur_virt; + else + while( !isalph(tcur_virt) && !isblank(tcur_virt) + && tcur_virt>=first_virt ) + --tcur_virt; + } + cur_virt = ++tcur_virt; + } + return; +} + +/*{ CNTLMODE() + * + * This routine implements the vi command subset. + * The cursor will always be positioned at the char of interest. + * +}*/ + +static int cntlmode(Vi_t *vp) +{ + register int c; + register int i; + genchar tmp_u_space[MAXLINE]; /* temporary u_space */ + genchar *real_u_space; /* points to real u_space */ + int tmp_u_column = INVALID; /* temporary u_column */ + int was_inmacro; + + if(!vp->U_saved) + { + /*** save virtual image if never done before ***/ + virtual[last_virt+1] = '\0'; + gencpy(vp->U_space, virtual); + vp->U_saved = 1; + } + + save_last(vp); + + real_u_space = vp->u_space; + curhline = histmax; + first_virt = 0; + vp->repeat = 1; + if( cur_virt > INVALID ) + { + /*** make sure cursor is at the last char ***/ + sync_cursor(vp); + } + + /*** Read control char until something happens to cause a ***/ + /* return to APPEND/REPLACE mode */ + + while( c=ed_getchar(vp->ed,-1) ) + { + vp->repeat_set = 0; + was_inmacro = inmacro; + if( c == '0' ) + { + /*** move to leftmost column ***/ + cur_virt = 0; + sync_cursor(vp); + continue; + } + + if( digit(c) ) + { + c = getcount(vp,c); + if( c == '.' ) + vp->lastrepeat = vp->repeat; + } + + /*** see if it's a move cursor command ***/ + + if(mvcursor(vp,c)) + { + sync_cursor(vp); + vp->repeat = 1; + continue; + } + + /*** see if it's a repeat of the last command ***/ + + if( c == '.' ) + { + c = vp->last_cmd; + vp->repeat = vp->lastrepeat; + i = textmod(vp,c, c); + } + else + { + i = textmod(vp,c, 0); + } + + /*** see if it's a text modification command ***/ + + switch(i) + { + case BAD: + break; + + default: /** input mode **/ + if(!was_inmacro) + { + vp->last_cmd = c; + vp->lastrepeat = vp->repeat; + } + vp->repeat = 1; + if( i == GOOD ) + continue; + return(i); + } + + switch( c ) + { + /***** Other stuff *****/ + + case cntl('L'): /** Redraw line **/ + /*** print the prompt and ***/ + /* force a total refresh */ + if(vp->nonewline==0 && !vp->ed->e_nocrnl) + putchar('\n'); + vp->nonewline = 0; + pr_string(vp,Prompt); + window[0] = '\0'; + cur_phys = vp->first_wind; + vp->ofirst_wind = INVALID; + vp->long_line = ' '; + break; + + case cntl('V'): + { + register const char *p = fmtident(e_version); + save_v(vp); + del_line(vp,BAD); + while(c = *p++) + append(vp,c,APPEND); + refresh(vp,CONTROL); + ed_getchar(vp->ed,-1); + restore_v(vp); + break; + } + + case '/': /** Search **/ + case '?': + case 'N': + case 'n': + save_v(vp); + switch( search(vp,c) ) + { + case GOOD: + /*** force a total refresh ***/ + window[0] = '\0'; + goto newhist; + + case BAD: + /*** no match ***/ + ed_ringbell(); + + default: + if( vp->u_column == INVALID ) + del_line(vp,BAD); + else + restore_v(vp); + break; + } + break; + + case 'j': /** get next command **/ + case '+': /** get next command **/ +#if SHOPT_EDPREDICT + if(vp->ed->hlist) + { + if(vp->ed->hoff >= vp->ed->hmax) + goto ringbell; + vp->ed->hoff++; + goto hupdate; + } +#endif /* SHOPT_EDPREDICT */ + curhline += vp->repeat; + if( curhline > histmax ) + { + curhline = histmax; + goto ringbell; + } + else if(curhline==histmax && tmp_u_column!=INVALID ) + { + vp->u_space = tmp_u_space; + vp->u_column = tmp_u_column; + restore_v(vp); + vp->u_space = real_u_space; + break; + } + save_v(vp); + cur_virt = INVALID; + goto newhist; + + case 'k': /** get previous command **/ + case '-': /** get previous command **/ +#if SHOPT_EDPREDICT + if(vp->ed->hlist) + { + if(vp->ed->hoff == 0) + goto ringbell; + vp->ed->hoff--; + hupdate: + ed_histlist(vp->ed,*vp->ed->hlist!=0); + vp->nonewline++; + ed_ungetchar(vp->ed,cntl('L')); + continue; + } +#endif /* SHOPT_EDPREDICT */ + if( curhline == histmax ) + { + vp->u_space = tmp_u_space; + i = vp->u_column; + save_v(vp); + vp->u_space = real_u_space; + tmp_u_column = vp->u_column; + vp->u_column = i; + } + + curhline -= vp->repeat; + if( curhline <= histmin ) + { + curhline += vp->repeat; + goto ringbell; + } + save_v(vp); + cur_virt = INVALID; + newhist: + if(curhline!=histmax || cur_virt==INVALID) + hist_copy((char*)virtual, MAXLINE, curhline,-1); + else + { + strcpy((char*)virtual,(char*)vp->u_space); +#if SHOPT_MULTIBYTE + ed_internal((char*)vp->u_space,vp->u_space); +#endif /* SHOPT_MULTIBYTE */ + } +#if SHOPT_MULTIBYTE + ed_internal((char*)virtual,virtual); +#endif /* SHOPT_MULTIBYTE */ + if((last_virt=genlen(virtual)-1) >= 0 && cur_virt == INVALID) + cur_virt = 0; +#if SHOPT_EDPREDICT + if(vp->ed->hlist) + { + ed_histlist(vp->ed,0); + if(c=='\n') + ed_ungetchar(vp->ed,c); + ed_ungetchar(vp->ed,cntl('L')); + vp->nonewline = 1; + cur_virt = 0; + } +#endif /*SHOPT_EDPREDICT */ + break; + + + case 'u': /** undo the last thing done **/ + restore_v(vp); + break; + + case 'U': /** Undo everything **/ + save_v(vp); + if( virtual[0] == '\0' ) + goto ringbell; + else + { + gencpy(virtual, vp->U_space); + last_virt = genlen(vp->U_space) - 1; + cur_virt = 0; + } + break; + +#if KSHELL + case 'v': + if(vp->repeat_set==0) + goto vcommand; +#endif /* KSHELL */ + + case 'G': /** goto command repeat **/ + if(vp->repeat_set==0) + vp->repeat = histmin+1; + if( vp->repeat <= histmin || vp->repeat > histmax ) + { + goto ringbell; + } + curhline = vp->repeat; + save_v(vp); + if(c == 'G') + { + cur_virt = INVALID; + goto newhist; + } + +#if KSHELL + vcommand: + if(ed_fulledit(vp->ed)==GOOD) + return(BIGVI); + else + goto ringbell; +#endif /* KSHELL */ + + case '#': /** insert(delete) # to (no)comment command **/ + if( cur_virt != INVALID ) + { + register genchar *p = &virtual[last_virt+1]; + *p = 0; + /*** see whether first char is comment char ***/ + c = (virtual[0]=='#'); + while(p-- >= virtual) + { + if(*p=='\n' || p<virtual) + { + if(c) /* delete '#' */ + { + if(p[1]=='#') + { + last_virt--; + gencpy(p+1,p+2); + } + } + else + { + cur_virt = p-virtual; + append(vp,'#', APPEND); + } + } + } + if(c) + { + curhline = histmax; + cur_virt = 0; + break; + } + refresh(vp,INPUT); + } + + case '\n': /** send to shell **/ +#if SHOPT_EDPREDICT + if(!vp->ed->hlist) + return(ENTER); + case '\t': /** bring choice to edit **/ + if(vp->ed->hlist) + { + if(vp->repeat > vp->ed->nhlist-vp->ed->hoff) + goto ringbell; + curhline = vp->ed->hlist[vp->repeat+vp->ed->hoff-1]->index; + goto newhist; + } + goto ringbell; +#else + return(ENTER); +#endif /* SHOPT_EDPREDICT */ + case ESC: + /* don't ring bell if next char is '[' */ + if(!lookahead) + { + char x; + if(sfpkrd(editb.e_fd,&x,1,'\r',400L,-1)>0) + ed_ungetchar(vp->ed,x); + } + if(lookahead) + { + ed_ungetchar(vp->ed,c=ed_getchar(vp->ed,1)); + if(c=='[') + { + vp->repeat = 1; + continue; + } + } + default: + ringbell: + ed_ringbell(); + vp->repeat = 1; + continue; + } + + refresh(vp,CONTROL); + vp->repeat = 1; + } +/* NOTREACHED */ + return(0); +} + +/*{ CURSOR( new_current_physical ) + * + * This routine will position the virtual cursor at + * physical column x in the window. + * +}*/ + +static void cursor(Vi_t *vp,register int x) +{ +#if SHOPT_MULTIBYTE + while(physical[x]==MARKER) + x++; +#endif /* SHOPT_MULTIBYTE */ + cur_phys = ed_setcursor(vp->ed, physical, cur_phys,x,vp->first_wind); +} + +/*{ DELETE( nchars, mode ) + * + * Delete nchars from the virtual space and leave cur_virt positioned + * at cur_virt-1. + * + * If mode = 'c', do not save the characters deleted + * = 'd', save them in yankbuf and delete. + * = 'y', save them in yankbuf but do not delete. + * +}*/ + +static void cdelete(Vi_t *vp,register int nchars, int mode) +{ + register int i; + register genchar *cp; + + if( cur_virt < first_virt ) + { + ed_ringbell(); + return; + } + if( nchars > 0 ) + { + cp = virtual+cur_virt; + vp->o_v_char = cp[0]; + if( (cur_virt-- + nchars) > last_virt ) + { + /*** set nchars to number actually deleted ***/ + nchars = last_virt - cur_virt; + } + + /*** save characters to be deleted ***/ + + if( mode != 'c' ) + { + i = cp[nchars]; + cp[nchars] = 0; + gencpy(yankbuf,cp); + cp[nchars] = i; + } + + /*** now delete these characters ***/ + + if( mode != 'y' ) + { + gencpy(cp,cp+nchars); + last_virt -= nchars; + } + } + return; +} + +/*{ DEL_LINE( mode ) + * + * This routine will delete the line. + * mode = GOOD, do a save_v() + * +}*/ +static void del_line(register Vi_t *vp, int mode) +{ + if( last_virt == INVALID ) + return; + + if( mode == GOOD ) + save_v(vp); + + cur_virt = 0; + first_virt = 0; + cdelete(vp,last_virt+1, BAD); + refresh(vp,CONTROL); + + cur_virt = INVALID; + cur_phys = 0; + vp->findchar = INVALID; + last_phys = INVALID; + last_virt = INVALID; + vp->last_wind = INVALID; + vp->first_wind = 0; + vp->o_v_char = '\0'; + vp->ocur_phys = 0; + vp->ocur_virt = MAXCHAR; + vp->ofirst_wind = 0; + window[0] = '\0'; + return; +} + +/*{ DELMOTION( motion, mode ) + * + * Delete thru motion. + * + * mode = 'd', save deleted characters, delete + * = 'c', do not save characters, change + * = 'y', save characters, yank + * + * Returns 1 if operation successful; else 0. + * +}*/ + +static int delmotion(Vi_t *vp,int motion, int mode) +{ + register int begin, end, delta; + /* the following saves a register */ + + if( cur_virt == INVALID ) + return(0); + if( mode != 'y' ) + save_v(vp); + begin = cur_virt; + + /*** fake out the motion routines by appending a blank ***/ + + virtual[++last_virt] = ' '; + end = mvcursor(vp,motion); + virtual[last_virt--] = 0; + if(!end) + return(0); + + end = cur_virt; + if( mode=='c' && end>begin && strchr("wW", motion) ) + { + /*** called by change operation, user really expects ***/ + /* the effect of the eE commands, so back up to end of word */ + while( end>begin && isblank(end-1) ) + --end; + if( end == begin ) + ++end; + } + + delta = end - begin; + if( delta >= 0 ) + { + cur_virt = begin; + if( strchr("eE;,TtFf%", motion) ) + ++delta; + } + else + { + delta = -delta + (motion=='%'); + } + + cdelete(vp,delta, mode); + if( mode == 'y' ) + cur_virt = begin; + return(1); +} + + +/*{ ENDWORD( nwords, cmd ) + * + * This routine will move cur_virt to the end of the nth word. + * +}*/ + +static void endword(Vi_t *vp, int nwords, register int cmd) +{ + register int tcur_virt = cur_virt; + while( nwords-- ) + { + if( !isblank(tcur_virt) && tcur_virt<=last_virt ) + ++tcur_virt; + while( isblank(tcur_virt) && tcur_virt<=last_virt ) + ++tcur_virt; + if( cmd == 'E' ) + { + while( !isblank(tcur_virt) && tcur_virt<=last_virt ) + ++tcur_virt; + } + else + { + if( isalph(tcur_virt) ) + while( isalph(tcur_virt) && tcur_virt<=last_virt ) + ++tcur_virt; + else + while( !isalph(tcur_virt) && !isblank(tcur_virt) + && tcur_virt<=last_virt ) + ++tcur_virt; + } + if( tcur_virt > first_virt ) + tcur_virt--; + } + cur_virt = tcur_virt; + return; +} + +/*{ FORWARD( nwords, cmd ) + * + * This routine will move cur_virt forward to the next nth word. + * +}*/ + +static void forward(Vi_t *vp,register int nwords, int cmd) +{ + register int tcur_virt = cur_virt; + while( nwords-- ) + { + if( cmd == 'W' ) + { + while( !isblank(tcur_virt) && tcur_virt < last_virt ) + ++tcur_virt; + } + else + { + if( isalph(tcur_virt) ) + { + while( isalph(tcur_virt) && tcur_virt<last_virt ) + ++tcur_virt; + } + else + { + while( !isalph(tcur_virt) && !isblank(tcur_virt) + && tcur_virt < last_virt ) + ++tcur_virt; + } + } + while( isblank(tcur_virt) && tcur_virt < last_virt ) + ++tcur_virt; + } + cur_virt = tcur_virt; + return; +} + + + +/*{ GETCOUNT(c) + * + * Set repeat to the user typed number and return the terminating + * character. + * +}*/ + +static int getcount(register Vi_t *vp,register int c) +{ + register int i; + + /*** get any repeat count ***/ + + if( c == '0' ) + return(c); + + vp->repeat_set++; + i = 0; + while( digit(c) ) + { + i = i*10 + c - '0'; + c = ed_getchar(vp->ed,-1); + } + + if( i > 0 ) + vp->repeat *= i; + return(c); +} + + +/*{ GETLINE( mode ) + * + * This routine will fetch a line. + * mode = APPEND, allow escape to cntlmode subroutine + * appending characters. + * = REPLACE, allow escape to cntlmode subroutine + * replacing characters. + * = SEARCH, no escape allowed + * = ESC, enter control mode immediately + * + * The cursor will always be positioned after the last + * char printed. + * + * This routine returns when cr, nl, or (eof in column 0) is + * received (column 0 is the first char position). + * +}*/ + +static void getline(register Vi_t* vp,register int mode) +{ + register int c; + register int tmp; + int max_virt=0, last_save=0; + genchar saveline[MAXLINE]; + vp->addnl = 1; + + if( mode == ESC ) + { + /*** go directly to control mode ***/ + goto escape; + } + + for(;;) + { + if( (c=ed_getchar(vp->ed,mode==SEARCH?1:-2)) == usreof ) + c = UEOF; + else if( c == usrerase ) + c = UERASE; + else if( c == usrkill ) + c = UKILL; + else if( c == editb.e_werase ) + c = UWERASE; + else if( c == usrlnext ) + c = ULNEXT; + else if(mode==SEARCH && c==editb.e_intr) + c = UINTR; + + if( c == ULNEXT) + { + /*** implement ^V to escape next char ***/ + c = ed_getchar(vp->ed,2); + append(vp,c, mode); + refresh(vp,INPUT); + continue; + } + + switch( c ) + { + case ESC: /** enter control mode **/ + if(!sh_isoption(SH_VI)) + { + append(vp,c, mode); + break; + } + if( mode == SEARCH ) + { + ed_ringbell(); + continue; + } + else + { + escape: + if( mode == REPLACE ) + { + c = max_virt-cur_virt; + if(c > 0 && last_save>=cur_virt) + { + genncpy((&virtual[cur_virt]),&saveline[cur_virt],c); + if(last_virt>=last_save) + last_virt=last_save-1; + refresh(vp,INPUT); + } + --cur_virt; + } + tmp = cntlmode(vp); + if( tmp == ENTER || tmp == BIGVI ) + { +#if SHOPT_MULTIBYTE + vp->bigvi = (tmp==BIGVI); +#endif /* SHOPT_MULTIBYTE */ + return; + } + if( tmp == INSERT ) + { + mode = APPEND; + continue; + } + mode = tmp; + if(mode==REPLACE) + { + c = last_save = last_virt+1; + if(c >= MAXLINE) + c = MAXLINE-1; + genncpy(saveline, virtual, c); + } + } + break; + + case UINTR: + first_virt = 0; + cdelete(vp,cur_virt+1, BAD); + cur_virt = -1; + return; + case UERASE: /** user erase char **/ + /*** treat as backspace ***/ + + case '\b': /** backspace **/ + if( virtual[cur_virt] == '\\' ) + { + cdelete(vp,1, BAD); + append(vp,usrerase, mode); + } + else + { + if( mode==SEARCH && cur_virt==0 ) + { + first_virt = 0; + cdelete(vp,1, BAD); + return; + } + if(mode==REPLACE || (last_save>0 && last_virt<=last_save)) + { + if(cur_virt<=first_virt) + ed_ringbell(); + else if(mode==REPLACE) + --cur_virt; + mode = REPLACE; + sync_cursor(vp); + continue; + } + else + cdelete(vp,1, BAD); + } + break; + + case UWERASE: /** delete back word **/ + if( cur_virt > first_virt && + !isblank(cur_virt) && + !ispunct(virtual[cur_virt]) && + isblank(cur_virt-1) ) + { + cdelete(vp,1, BAD); + } + else + { + tmp = cur_virt; + backword(vp,1, 'W'); + cdelete(vp,tmp - cur_virt + 1, BAD); + } + break; + + case UKILL: /** user kill line char **/ + if( virtual[cur_virt] == '\\' ) + { + cdelete(vp,1, BAD); + append(vp,usrkill, mode); + } + else + { + if( mode == SEARCH ) + { + cur_virt = 1; + delmotion(vp, '$', BAD); + } + else if(first_virt) + { + tmp = cur_virt; + cur_virt = first_virt; + cdelete(vp,tmp - cur_virt + 1, BAD); + } + else + del_line(vp,GOOD); + } + break; + + case UEOF: /** eof char **/ + if( cur_virt != INVALID ) + continue; + vp->addnl = 0; + + case '\n': /** newline or return **/ + if( mode != SEARCH ) + save_last(vp); + refresh(vp,INPUT); + last_phys++; + return; + + case '\t': /** command completion **/ + if(mode!=SEARCH && last_virt>=0 && (vp->ed->e_tabcount|| !isblank(cur_virt)) && vp->ed->sh->nextprompt) + { + if(virtual[cur_virt]=='\\') + { + virtual[cur_virt] = '\t'; + break; + } + if(vp->ed->e_tabcount==0) + { + ed_ungetchar(vp->ed,'\\'); + vp->ed->e_tabcount=1; + goto escape; + } + else if(vp->ed->e_tabcount==1) + { + ed_ungetchar(vp->ed,'='); + goto escape; + } + vp->ed->e_tabcount = 0; + } + /* FALL THRU*/ + default: + if( mode == REPLACE ) + { + if( cur_virt < last_virt ) + { + replace(vp,c, 1); + if(cur_virt>max_virt) + max_virt = cur_virt; + continue; + } + cdelete(vp,1, BAD); + mode = APPEND; + max_virt = last_virt+3; + } + append(vp,c, mode); + break; + } + refresh(vp,INPUT); + + } +} + +/*{ MVCURSOR( motion ) + * + * This routine will move the virtual cursor according to motion + * for repeat times. + * + * It returns GOOD if successful; else BAD. + * +}*/ + +static int mvcursor(register Vi_t* vp,register int motion) +{ + register int count; + register int tcur_virt; + register int incr = -1; + register int bound = 0; + + switch(motion) + { + /***** Cursor move commands *****/ + + case '0': /** First column **/ + tcur_virt = 0; + break; + + case '^': /** First nonblank character **/ + tcur_virt = first_virt; + while( isblank(tcur_virt) && tcur_virt < last_virt ) + ++tcur_virt; + break; + + case '|': + tcur_virt = vp->repeat-1; + if(tcur_virt <= last_virt) + break; + /* fall through */ + + case '$': /** End of line **/ + tcur_virt = last_virt; + break; + + case '[': + switch(motion=getcount(vp,ed_getchar(vp->ed,-1))) + { + case 'A': +#if SHOPT_EDPREDICT + if(!vp->ed->hlist && cur_virt>=0 && cur_virt<(SEARCHSIZE-2) && cur_virt == last_virt) +#else + if(cur_virt>=0 && cur_virt<(SEARCHSIZE-2) && cur_virt == last_virt) +#endif /* SHOPT_EDPREDICT */ + { + virtual[last_virt + 1] = '\0'; +#if SHOPT_MULTIBYTE + ed_external(virtual,lsearch+1); +#else + strcpy(lsearch+1,virtual); +#endif /* SHOPT_MULTIBYTE */ + *lsearch = '^'; + vp->direction = -2; + ed_ungetchar(vp->ed,'n'); + } + else if(cur_virt==0 && vp->direction == -2) + ed_ungetchar(vp->ed,'n'); + else + ed_ungetchar(vp->ed,'k'); + return(1); + case 'B': + ed_ungetchar(vp->ed,'j'); + return(1); + case 'C': + motion = last_virt; + incr = 1; + goto walk; + case 'D': + motion = first_virt; + goto walk; + case 'H': + tcur_virt = 0; + break; + case 'Y': + tcur_virt = last_virt; + break; + default: + ed_ungetchar(vp->ed,motion); + return(0); + } + break; + + case 'h': /** Left one **/ + case '\b': + motion = first_virt; + goto walk; + + case ' ': + case 'l': /** Right one **/ + motion = last_virt; + incr = 1; + walk: + tcur_virt = cur_virt; + if( incr*tcur_virt < motion) + { + tcur_virt += vp->repeat*incr; + if( incr*tcur_virt > motion) + tcur_virt = motion; + } + else + return(0); + break; + + case 'B': + case 'b': /** back word **/ + tcur_virt = cur_virt; + backword(vp,vp->repeat, motion); + if( cur_virt == tcur_virt ) + return(0); + return(1); + + case 'E': + case 'e': /** end of word **/ + tcur_virt = cur_virt; + if(tcur_virt >=0) + endword(vp, vp->repeat, motion); + if( cur_virt == tcur_virt ) + return(0); + return(1); + + case ',': /** reverse find old char **/ + case ';': /** find old char **/ + switch(vp->last_find) + { + case 't': + case 'f': + if(motion==';') + { + bound = last_virt; + incr = 1; + } + goto find_b; + + case 'T': + case 'F': + if(motion==',') + { + bound = last_virt; + incr = 1; + } + goto find_b; + + default: + return(0); + } + + + case 't': /** find up to new char forward **/ + case 'f': /** find new char forward **/ + bound = last_virt; + incr = 1; + + case 'T': /** find up to new char backward **/ + case 'F': /** find new char backward **/ + vp->last_find = motion; + if((vp->findchar=getrchar(vp))==ESC) + return(1); +find_b: + tcur_virt = cur_virt; + count = vp->repeat; + while( count-- ) + { + while( incr*(tcur_virt+=incr) <= bound + && virtual[tcur_virt] != vp->findchar ); + if( incr*tcur_virt > bound ) + { + return(0); + } + } + if( fold(vp->last_find) == 'T' ) + tcur_virt -= incr; + break; + + case '%': + { + int nextmotion; + int nextc; + tcur_virt = cur_virt; + while( tcur_virt <= last_virt + && strchr(paren_chars,virtual[tcur_virt])==(char*)0) + tcur_virt++; + if(tcur_virt > last_virt ) + return(0); + nextc = virtual[tcur_virt]; + count = strchr(paren_chars,nextc)-paren_chars; + if(count < 3) + { + incr = 1; + bound = last_virt; + nextmotion = paren_chars[count+3]; + } + else + nextmotion = paren_chars[count-3]; + count = 1; + while(count >0 && incr*(tcur_virt+=incr) <= bound) + { + if(virtual[tcur_virt] == nextmotion) + count--; + else if(virtual[tcur_virt]==nextc) + count++; + } + if(count) + return(0); + break; + } + + case 'W': + case 'w': /** forward word **/ + tcur_virt = cur_virt; + forward(vp,vp->repeat, motion); + if( tcur_virt == cur_virt ) + return(0); + return(1); + + default: + return(0); + } + cur_virt = tcur_virt; + + return(1); +} + +/* + * print a string + */ + +static void pr_string(register Vi_t *vp, register const char *sp) +{ + /*** copy string sp ***/ + register char *ptr = editb.e_outptr; + while(*sp) + *ptr++ = *sp++; + editb.e_outptr = ptr; + return; +} + +/*{ PUTSTRING( column, nchars ) + * + * Put nchars starting at column of physical into the workspace + * to be printed. + * +}*/ + +static void putstring(register Vi_t *vp,register int col, register int nchars) +{ + while( nchars-- ) + putchar(physical[col++]); + return; +} + +/*{ REFRESH( mode ) + * + * This routine will refresh the crt so the physical image matches + * the virtual image and display the proper window. + * + * mode = CONTROL, refresh in control mode, ie. leave cursor + * positioned at last char printed. + * = INPUT, refresh in input mode; leave cursor positioned + * after last char printed. + * = TRANSLATE, perform virtual to physical translation + * and adjust left margin only. + * + * +-------------------------------+ + * | | | virtual | | | + * +-------------------------------+ + * cur_virt last_virt + * + * +-----------------------------------------------+ + * | | | physical | | | + * +-----------------------------------------------+ + * cur_phys last_phys + * + * 0 w_size - 1 + * +-----------------------+ + * | | | window | + * +-----------------------+ + * cur_window = cur_phys - first_wind +}*/ + +static void refresh(register Vi_t* vp, int mode) +{ + register int p; + register int v; + register int first_w = vp->first_wind; + int p_differ; + int new_lw; + int ncur_phys; + int opflag; /* search optimize flag */ + +# define w v + + /*** find out if it's necessary to start translating at beginning ***/ + + if(lookahead>0) + { + p = previous[lookahead-1]; + if(p != ESC && p != '\n' && p != '\r') + mode = TRANSLATE; + } + v = cur_virt; +#if SHOPT_EDPREDICT + if(mode==INPUT && v>0 && virtual[0]=='#' && v==last_virt && virtual[v]!='*' && sh_isoption(SH_VI)) + { + int n; + virtual[last_virt+1] = 0; +# if SHOPT_MULTIBYTE + ed_external(virtual,(char*)virtual); +# endif /* SHOPT_MULTIBYTE */ + n = ed_histgen(vp->ed,(char*)virtual); +# if SHOPT_MULTIBYTE + ed_internal((char*)virtual,virtual); +# endif /* SHOPT_MULTIBYTE */ + if(vp->ed->hlist) + { + ed_histlist(vp->ed,n); + pr_string(vp,Prompt); + vp->ocur_virt = INVALID; + ed_setcursor(vp->ed,physical,0,cur_phys,0); + } + else + ed_ringbell(); + } + else if(mode==INPUT && v<=1 && vp->ed->hlist) + ed_histlist(vp->ed,0); +#endif /* SHOPT_EDPREDICT */ + if( v<vp->ocur_virt || vp->ocur_virt==INVALID + || ( v==vp->ocur_virt + && (!is_print(virtual[v]) || !is_print(vp->o_v_char))) ) + { + opflag = 0; + p = 0; + v = 0; + } + else + { + opflag = 1; + p = vp->ocur_phys; + v = vp->ocur_virt; + if( !is_print(virtual[v]) ) + { + /*** avoid double ^'s ***/ + ++p; + ++v; + } + } + virtual[last_virt+1] = 0; + ncur_phys = ed_virt_to_phys(vp->ed,virtual,physical,cur_virt,v,p); + p = genlen(physical); + if( --p < 0 ) + last_phys = 0; + else + last_phys = p; + + /*** see if this was a translate only ***/ + + if( mode == TRANSLATE ) + return; + + /*** adjust left margin if necessary ***/ + + if( ncur_phys<first_w || ncur_phys>=(first_w + w_size) ) + { + cursor(vp,first_w); + first_w = ncur_phys - (w_size>>1); + if( first_w < 0 ) + first_w = 0; + vp->first_wind = cur_phys = first_w; + } + + /*** attempt to optimize search somewhat to find ***/ + /*** out where physical and window images differ ***/ + + if( first_w==vp->ofirst_wind && ncur_phys>=vp->ocur_phys && opflag==1 ) + { + p = vp->ocur_phys; + w = p - first_w; + } + else + { + p = first_w; + w = 0; + } + + for(; (p<=last_phys && w<=vp->last_wind); ++p, ++w) + { + if( window[w] != physical[p] ) + break; + } + p_differ = p; + + if( (p>last_phys || p>=first_w+w_size) && w>vp->last_wind + && cur_virt==vp->ocur_virt ) + { + /*** images are identical ***/ + return; + } + + /*** copy the physical image to the window image ***/ + + if( last_virt != INVALID ) + { + while( p <= last_phys && w < w_size ) + window[w++] = physical[p++]; + } + new_lw = w; + + /*** erase trailing characters if needed ***/ + + while( w <= vp->last_wind ) + window[w++] = ' '; + vp->last_wind = --w; + + p = p_differ; + + /*** move cursor to start of difference ***/ + + cursor(vp,p); + + /*** and output difference ***/ + + w = p - first_w; + while( w <= vp->last_wind ) + putchar(window[w++]); + + cur_phys = w + first_w; + vp->last_wind = --new_lw; + + if( last_phys >= w_size ) + { + if( first_w == 0 ) + vp->long_char = '>'; + else if( last_phys < (first_w+w_size) ) + vp->long_char = '<'; + else + vp->long_char = '*'; + } + else + vp->long_char = ' '; + + if( vp->long_line != vp->long_char ) + { + /*** indicate lines longer than window ***/ + while( w++ < w_size ) + { + putchar(' '); + ++cur_phys; + } + putchar(vp->long_char); + ++cur_phys; + vp->long_line = vp->long_char; + } + + if(vp->ed->e_multiline && vp->ofirst_wind==INVALID && !vp->ed->e_nocrnl) + ed_setcursor(vp->ed, physical, last_phys+1, last_phys+1, -1); + vp->ed->e_nocrnl = 0; + vp->ocur_phys = ncur_phys; + vp->ocur_virt = cur_virt; + vp->ofirst_wind = first_w; + + if( mode==INPUT && cur_virt>INVALID ) + ++ncur_phys; + + cursor(vp,ncur_phys); + ed_flush(vp->ed); + return; +} + +/*{ REPLACE( char, increment ) + * + * Replace the cur_virt character with char. This routine attempts + * to avoid using refresh(). + * + * increment = 1, increment cur_virt after replacement. + * = 0, leave cur_virt where it is. + * +}*/ + +static void replace(register Vi_t *vp, register int c, register int increment) +{ + register int cur_window; + + if( cur_virt == INVALID ) + { + /*** can't replace invalid cursor ***/ + ed_ringbell(); + return; + } + cur_window = cur_phys - vp->first_wind; + if( vp->ocur_virt == INVALID || !is_print(c) + || !is_print(virtual[cur_virt]) + || !is_print(vp->o_v_char) +#if SHOPT_MULTIBYTE + || !iswascii(c) || mbwidth(vp->o_v_char)>1 + || !iswascii(virtual[cur_virt]) +#endif /* SHOPT_MULTIBYTE */ + || (increment && (cur_window==w_size-1) + || !is_print(virtual[cur_virt+1])) ) + { + /*** must use standard refresh routine ***/ + + cdelete(vp,1, BAD); + append(vp,c, APPEND); + if( increment && cur_virt<last_virt ) + ++cur_virt; + refresh(vp,CONTROL); + } + else + { + virtual[cur_virt] = c; + physical[cur_phys] = c; + window[cur_window] = c; + putchar(c); + if(increment) + { + c = virtual[++cur_virt]; + ++cur_phys; + } + else + { + putchar('\b'); + } + vp->o_v_char = c; + ed_flush(vp->ed); + } + return; +} + +/*{ RESTORE_V() + * + * Restore the contents of virtual space from u_space. + * +}*/ + +static void restore_v(register Vi_t *vp) +{ + register int tmpcol; + genchar tmpspace[MAXLINE]; + + if( vp->u_column == INVALID-1 ) + { + /*** never saved anything ***/ + ed_ringbell(); + return; + } + gencpy(tmpspace, vp->u_space); + tmpcol = vp->u_column; + save_v(vp); + gencpy(virtual, tmpspace); + cur_virt = tmpcol; + last_virt = genlen(tmpspace) - 1; + vp->ocur_virt = MAXCHAR; /** invalidate refresh optimization **/ + return; +} + +/*{ SAVE_LAST() + * + * If the user has typed something, save it in last line. + * +}*/ + +static void save_last(register Vi_t* vp) +{ + register int i; + + if( (i = cur_virt - first_virt + 1) > 0 ) + { + /*** save last thing user typed ***/ + if(i >= MAXLINE) + i = MAXLINE-1; + genncpy(vp->lastline, (&virtual[first_virt]), i); + vp->lastline[i] = '\0'; + } + return; +} + +/*{ SAVE_V() + * + * This routine will save the contents of virtual in u_space. + * +}*/ + +static void save_v(register Vi_t *vp) +{ + if(!inmacro) + { + virtual[last_virt + 1] = '\0'; + gencpy(vp->u_space, virtual); + vp->u_column = cur_virt; + } + return; +} + +/*{ SEARCH( mode ) + * + * Search history file for regular expression. + * + * mode = '/' require search string and search new to old + * mode = '?' require search string and search old to new + * mode = 'N' repeat last search in reverse direction + * mode = 'n' repeat last search + * +}*/ + +/* + * search for <string> in the current command + */ +static int curline_search(Vi_t *vp, const char *string) +{ + register int len=strlen(string); + register const char *dp,*cp=string, *dpmax; +#if SHOPT_MULTIBYTE + ed_external(vp->u_space,(char*)vp->u_space); +#endif /* SHOPT_MULTIBYTE */ + for(dp=(char*)vp->u_space,dpmax=dp+strlen(dp)-len; dp<=dpmax; dp++) + { + if(*dp==*cp && memcmp(cp,dp,len)==0) + return(dp-(char*)vp->u_space); + } +#if SHOPT_MULTIBYTE + ed_internal((char*)vp->u_space,vp->u_space); +#endif /* SHOPT_MULTIBYTE */ + return(-1); +} + +static int search(register Vi_t* vp,register int mode) +{ + register int new_direction; + register int oldcurhline; + register int i; + Histloc_t location; + + if( vp->direction == -2 && mode != 'n') + vp->direction = -1; + if( mode == '/' || mode == '?') + { + /*** new search expression ***/ + del_line(vp,BAD); + append(vp,mode, APPEND); + refresh(vp,INPUT); + first_virt = 1; + getline(vp,SEARCH); + first_virt = 0; + virtual[last_virt + 1] = '\0'; /*** make null terminated ***/ + vp->direction = mode=='/' ? -1 : 1; + } + + if( cur_virt == INVALID ) + { + /*** no operation ***/ + return(ABORT); + } + + if( cur_virt==0 || fold(mode)=='N' ) + { + /*** user wants repeat of last search ***/ + del_line(vp,BAD); + strcpy( ((char*)virtual)+1, lsearch); +#if SHOPT_MULTIBYTE + *((char*)virtual) = '/'; + ed_internal((char*)virtual,virtual); +#endif /* SHOPT_MULTIBYTE */ + } + + if( mode == 'N' ) + new_direction = -vp->direction; + else + new_direction = vp->direction; + + + /*** now search ***/ + + oldcurhline = curhline; +#if SHOPT_MULTIBYTE + ed_external(virtual,(char*)virtual); +#endif /* SHOPT_MULTIBYTE */ + if(mode=='?' && (i=curline_search(vp,((char*)virtual)+1))>=0) + { + location.hist_command = curhline; + location.hist_char = i; + } + else + { + i = INVALID; + if( new_direction==1 && curhline >= histmax ) + curhline = histmin + 1; + location = hist_find(shgd->hist_ptr,((char*)virtual)+1, curhline, 1, new_direction); + } + cur_virt = i; + strncpy(lsearch, ((char*)virtual)+1, SEARCHSIZE); + lsearch[SEARCHSIZE-1] = 0; + if( (curhline=location.hist_command) >=0 ) + { + vp->ocur_virt = INVALID; + return(GOOD); + } + + /*** could not find matching line ***/ + + curhline = oldcurhline; + return(BAD); +} + +/*{ SYNC_CURSOR() + * + * This routine will move the physical cursor to the same + * column as the virtual cursor. + * +}*/ + +static void sync_cursor(register Vi_t *vp) +{ + register int p; + register int v; + register int c; + int new_phys; + + if( cur_virt == INVALID ) + return; + + /*** find physical col that corresponds to virtual col ***/ + + new_phys = 0; + if(vp->first_wind==vp->ofirst_wind && cur_virt>vp->ocur_virt && vp->ocur_virt!=INVALID) + { + /*** try to optimize search a little ***/ + p = vp->ocur_phys + 1; +#if SHOPT_MULTIBYTE + while(physical[p]==MARKER) + p++; +#endif /* SHOPT_MULTIBYTE */ + v = vp->ocur_virt + 1; + } + else + { + p = 0; + v = 0; + } + for(; v <= last_virt; ++p, ++v) + { +#if SHOPT_MULTIBYTE + int d; + c = virtual[v]; + if((d = mbwidth(c)) > 1) + { + if( v != cur_virt ) + p += (d-1); + } + else if(!iswprint(c)) +#else + c = virtual[v]; + if(!isprint(c)) +#endif /* SHOPT_MULTIBYTE */ + { + if( c == '\t' ) + { + p -= ((p+editb.e_plen)%TABSIZE); + p += (TABSIZE-1); + } + else + { + ++p; + } + } + if( v == cur_virt ) + { + new_phys = p; + break; + } + } + + if( new_phys < vp->first_wind || new_phys >= vp->first_wind + w_size ) + { + /*** asked to move outside of window ***/ + + window[0] = '\0'; + refresh(vp,CONTROL); + return; + } + + cursor(vp,new_phys); + ed_flush(vp->ed); + vp->ocur_phys = cur_phys; + vp->ocur_virt = cur_virt; + vp->o_v_char = virtual[vp->ocur_virt]; + + return; +} + +/*{ TEXTMOD( command, mode ) + * + * Modify text operations. + * + * mode != 0, repeat previous operation + * +}*/ + +static int textmod(register Vi_t *vp,register int c, int mode) +{ + register int i; + register genchar *p = vp->lastline; + register int trepeat = vp->repeat; + genchar *savep; + + if(mode && (fold(vp->lastmotion)=='F' || fold(vp->lastmotion)=='T')) + vp->lastmotion = ';'; + + if( fold(c) == 'P' ) + { + /*** change p from lastline to yankbuf ***/ + p = yankbuf; + } + +addin: + switch( c ) + { + /***** Input commands *****/ + +#if KSHELL + case '\t': + if(vp->ed->e_tabcount!=1) + return(BAD); + c = '='; + case '*': /** do file name expansion in place **/ + case '\\': /** do file name completion in place **/ + if( cur_virt == INVALID ) + return(BAD); + case '=': /** list file name expansions **/ + save_v(vp); + i = last_virt; + ++last_virt; + mode = cur_virt-1; + virtual[last_virt] = 0; + if(ed_expand(vp->ed,(char*)virtual, &cur_virt, &last_virt, c, vp->repeat_set?vp->repeat:-1)<0) + { + if(vp->ed->e_tabcount) + { + vp->ed->e_tabcount=2; + ed_ungetchar(vp->ed,'\t'); + --last_virt; + return(APPEND); + } + last_virt = i; + ed_ringbell(); + } + else if(c == '=' && !vp->repeat_set) + { + last_virt = i; + vp->nonewline++; + ed_ungetchar(vp->ed,cntl('L')); + return(GOOD); + } + else + { + --cur_virt; + --last_virt; + vp->ocur_virt = MAXCHAR; + if(c=='=' || (mode<cur_virt && (virtual[cur_virt]==' ' || virtual[cur_virt]=='/'))) + vp->ed->e_tabcount = 0; + return(APPEND); + } + break; + + case '@': /** macro expansion **/ + if( mode ) + c = vp->lastmacro; + else + if((c=getrchar(vp))==ESC) + return(GOOD); + if(!inmacro) + vp->lastmacro = c; + if(ed_macro(vp->ed,c)) + { + save_v(vp); + inmacro++; + return(GOOD); + } + ed_ringbell(); + return(BAD); + +#endif /* KSHELL */ + case '_': /** append last argument of prev command **/ + save_v(vp); + { + genchar tmpbuf[MAXLINE]; + if(vp->repeat_set==0) + vp->repeat = -1; + p = (genchar*)hist_word((char*)tmpbuf,MAXLINE,vp->repeat); + if(p==0) + { + ed_ringbell(); + break; + } +#if SHOPT_MULTIBYTE + ed_internal((char*)p,tmpbuf); + p = tmpbuf; +#endif /* SHOPT_MULTIBYTE */ + i = ' '; + do + { + append(vp,i,APPEND); + } + while(i = *p++); + return(APPEND); + } + + case 'A': /** append to end of line **/ + cur_virt = last_virt; + sync_cursor(vp); + + case 'a': /** append **/ + if( fold(mode) == 'A' ) + { + c = 'p'; + goto addin; + } + save_v(vp); + if( cur_virt != INVALID ) + { + first_virt = cur_virt + 1; + cursor(vp,cur_phys + 1); + ed_flush(vp->ed); + } + return(APPEND); + + case 'I': /** insert at beginning of line **/ + cur_virt = first_virt; + sync_cursor(vp); + + case 'i': /** insert **/ + if( fold(mode) == 'I' ) + { + c = 'P'; + goto addin; + } + save_v(vp); + if( cur_virt != INVALID ) + { + vp->o_v_char = virtual[cur_virt]; + first_virt = cur_virt--; + } + return(INSERT); + + case 'C': /** change to eol **/ + c = '$'; + goto chgeol; + + case 'c': /** change **/ + if( mode ) + c = vp->lastmotion; + else + c = getcount(vp,ed_getchar(vp->ed,-1)); +chgeol: + vp->lastmotion = c; + if( c == 'c' ) + { + del_line(vp,GOOD); + return(APPEND); + } + + if(!delmotion(vp, c, 'c')) + return(BAD); + + if( mode == 'c' ) + { + c = 'p'; + trepeat = 1; + goto addin; + } + first_virt = cur_virt + 1; + return(APPEND); + + case 'D': /** delete to eol **/ + c = '$'; + goto deleol; + + case 'd': /** delete **/ + if( mode ) + c = vp->lastmotion; + else + c = getcount(vp,ed_getchar(vp->ed,-1)); +deleol: + vp->lastmotion = c; + if( c == 'd' ) + { + del_line(vp,GOOD); + break; + } + if(!delmotion(vp, c, 'd')) + return(BAD); + if( cur_virt < last_virt ) + ++cur_virt; + break; + + case 'P': + if( p[0] == '\0' ) + return(BAD); + if( cur_virt != INVALID ) + { + i = virtual[cur_virt]; + if(!is_print(i)) + vp->ocur_virt = INVALID; + --cur_virt; + } + + case 'p': /** print **/ + if( p[0] == '\0' ) + return(BAD); + + if( mode != 's' && mode != 'c' ) + { + save_v(vp); + if( c == 'P' ) + { + /*** fix stored cur_virt ***/ + ++vp->u_column; + } + } + if( mode == 'R' ) + mode = REPLACE; + else + mode = APPEND; + savep = p; + for(i=0; i<trepeat; ++i) + { + while(c= *p++) + append(vp,c,mode); + p = savep; + } + break; + + case 'R': /* Replace many chars **/ + if( mode == 'R' ) + { + c = 'P'; + goto addin; + } + save_v(vp); + if( cur_virt != INVALID ) + first_virt = cur_virt; + return(REPLACE); + + case 'r': /** replace **/ + if( mode ) + c = *p; + else + if((c=getrchar(vp))==ESC) + return(GOOD); + *p = c; + save_v(vp); + while(trepeat--) + replace(vp,c, trepeat!=0); + return(GOOD); + + case 'S': /** Substitute line - cc **/ + c = 'c'; + goto chgeol; + + case 's': /** substitute **/ + save_v(vp); + cdelete(vp,vp->repeat, BAD); + if( mode ) + { + c = 'p'; + trepeat = 1; + goto addin; + } + first_virt = cur_virt + 1; + return(APPEND); + + case 'Y': /** Yank to end of line **/ + c = '$'; + goto yankeol; + + case 'y': /** yank thru motion **/ + if( mode ) + c = vp->lastmotion; + else + c = getcount(vp,ed_getchar(vp->ed,-1)); +yankeol: + vp->lastmotion = c; + if( c == 'y' ) + { + gencpy(yankbuf, virtual); + } + else if(!delmotion(vp, c, 'y')) + { + return(BAD); + } + break; + + case 'x': /** delete repeat chars forward - dl **/ + c = 'l'; + goto deleol; + + case 'X': /** delete repeat chars backward - dh **/ + c = 'h'; + goto deleol; + + case '~': /** invert case and advance **/ + if( cur_virt != INVALID ) + { + save_v(vp); + i = INVALID; + while(trepeat-->0 && i!=cur_virt) + { + i = cur_virt; + c = virtual[cur_virt]; +#if SHOPT_MULTIBYTE + if((c&~STRIP)==0) +#endif /* SHOPT_MULTIBYTE */ + if( isupper(c) ) + c = tolower(c); + else if( islower(c) ) + c = toupper(c); + replace(vp,c, 1); + } + return(GOOD); + } + else + return(BAD); + + default: + return(BAD); + } + refresh(vp,CONTROL); + return(GOOD); +} + + +#if SHOPT_MULTIBYTE + static int _isalph(register int v) + { +#ifdef _lib_iswalnum + return(iswalnum(v) || v=='_'); +#else + return((v&~STRIP) || isalnum(v) || v=='_'); +#endif + } + + + static int _isblank(register int v) + { + return((v&~STRIP)==0 && isspace(v)); + } + + static int _ismetach(register int v) + { + return((v&~STRIP)==0 && ismeta(v)); + } + +#endif /* SHOPT_MULTIBYTE */ + +/* + * get a character, after ^V processing + */ +static int getrchar(register Vi_t *vp) +{ + register int c; + if((c=ed_getchar(vp->ed,1))== usrlnext) + c = ed_getchar(vp->ed,2); + return(c); +} |