/*********************************************************************** * * * This software is part of the ast package * * Copyright (c) 1982-2009 AT&T Intellectual Property * * and is licensed under the * * Common Public License, Version 1.0 * * by AT&T Intellectual Property * * * * A copy of the License is available at * * http://www.opensource.org/licenses/cpl1.0.txt * * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * * * * Information and Software Systems Research * * AT&T Research * * Florham Park NJ * * * * David Korn * * * ***********************************************************************/ #pragma prototyped /* * completion.c - command and file completion for shell editors * */ #include "defs.h" #include #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; while((c=mbchar(cp)),(c>UCHAR_MAX)||(n=state[c])==0); if(n==S_EOF) return((char*)string); offset = staktell(); stakwrite(string,--cp-string); while(c=mbchar(cp)) { if(state[c]) stakputc('\\'); stakputc(c); } 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 to common prefix of and * if is equal to returns +strlen()+1 * otherwise returns +strlen() */ 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 && c!= '}') { *type='$'; return(++xp); } } 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); 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&&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(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(sh.pathlist,cp,'/')) && (np=nv_search(begin,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 [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,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(!sh.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(sh.hist_ptr->histfp,(char*)ep->e_inbuf,ep->e_eol+1); sh_onstate(SH_HISTORY); hist_flush(sh.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); }