1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1982-2007 AT&T Knowledge Ventures * 5 * and is licensed under the * 6 * Common Public License, Version 1.0 * 7 * by AT&T Knowledge Ventures * 8 * * 9 * A copy of the License is available at * 10 * http://www.opensource.org/licenses/cpl1.0.txt * 11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 12 * * 13 * Information and Software Systems Research * 14 * AT&T Research * 15 * Florham Park NJ * 16 * * 17 * David Korn <dgk@research.att.com> * 18 * * 19 ***********************************************************************/ 20 #pragma prototyped 21 /* 22 * completion.c - command and file completion for shell editors 23 * 24 */ 25 26 #include "defs.h" 27 #include <ctype.h> 28 #include <ast_wchar.h> 29 #include "lexstates.h" 30 #include "path.h" 31 #include "io.h" 32 #include "edit.h" 33 #include "history.h" 34 35 static int charcmp(int a, int b, int nocase) 36 { 37 if(nocase) 38 { 39 if(isupper(a)) 40 a = tolower(a); 41 if(isupper(b)) 42 b = tolower(b); 43 } 44 return(a==b); 45 } 46 47 /* 48 * overwrites <str> to common prefix of <str> and <newstr> 49 * if <str> is equal to <newstr> returns <str>+strlen(<str>)+1 50 * otherwise returns <str>+strlen(<str>) 51 */ 52 static char *overlaid(register char *str,register const char *newstr,int nocase) 53 { 54 register int c,d; 55 while((c= *(unsigned char *)str) && ((d= *(unsigned char*)newstr++),charcmp(c,d,nocase))) 56 str++; 57 if(*str) 58 *str = 0; 59 else if(*newstr==0) 60 str++; 61 return(str); 62 } 63 64 65 /* 66 * returns pointer to beginning of expansion and sets type of expansion 67 */ 68 static char *find_begin(char outbuff[], char *last, int endchar, int *type) 69 { 70 register char *cp=outbuff, *bp, *xp; 71 register int c,inquote = 0; 72 bp = outbuff; 73 *type = 0; 74 while(cp < last) 75 { 76 xp = cp; 77 switch(c= mbchar(cp)) 78 { 79 case '\'': case '"': 80 if(!inquote) 81 { 82 inquote = c; 83 bp = xp; 84 break; 85 } 86 if(inquote==c) 87 inquote = 0; 88 break; 89 case '\\': 90 if(inquote != '\'') 91 mbchar(cp); 92 break; 93 case '$': 94 if(inquote == '\'') 95 break; 96 c = *(unsigned char*)cp; 97 if(isaletter(c) || c=='{') 98 { 99 int dot = '.'; 100 if(c=='{') 101 { 102 xp = cp; 103 mbchar(cp); 104 c = *(unsigned char*)cp; 105 if(c!='.' && !isaletter(c)) 106 break; 107 } 108 else 109 dot = 'a'; 110 while(cp < last) 111 { 112 if((c= mbchar(cp)) , c!=dot && !isaname(c)) 113 break; 114 } 115 if(cp>=last) 116 { 117 *type='$'; 118 return(++xp); 119 } 120 } 121 else if(c=='(') 122 { 123 xp = find_begin(cp,last,')',type); 124 if(*(cp=xp)!=')') 125 bp = xp; 126 else 127 cp++; 128 } 129 break; 130 case '=': 131 if(!inquote) 132 bp = cp; 133 break; 134 case '~': 135 if(*cp=='(') 136 break; 137 /* fall through */ 138 default: 139 if(c && c==endchar) 140 return(xp); 141 if(!inquote && ismeta(c)) 142 bp = cp; 143 break; 144 } 145 } 146 if(inquote && *bp==inquote) 147 *type = *bp++; 148 return(bp); 149 } 150 151 /* 152 * file name generation for edit modes 153 * non-zero exit for error, <0 ring bell 154 * don't search back past beginning of the buffer 155 * mode is '*' for inline expansion, 156 * mode is '\' for filename completion 157 * mode is '=' cause files to be listed in select format 158 */ 159 160 int ed_expand(Edit_t *ep, char outbuff[],int *cur,int *eol,int mode, int count) 161 { 162 struct comnod *comptr; 163 struct argnod *ap; 164 register char *out; 165 char *av[2], *begin , *dir=0; 166 int addstar=0, rval=0, var=0, strip=1; 167 int nomarkdirs = !sh_isoption(SH_MARKDIRS); 168 sh_onstate(SH_FCOMPLETE); 169 if(ep->e_nlist) 170 { 171 if(mode=='=' && count>0) 172 { 173 if(count> ep->e_nlist) 174 return(-1); 175 mode = '*'; 176 av[0] = ep->e_clist[count-1]; 177 av[1] = 0; 178 } 179 else 180 { 181 stakset(ep->e_stkptr,ep->e_stkoff); 182 ep->e_nlist = 0; 183 } 184 } 185 comptr = (struct comnod*)stakalloc(sizeof(struct comnod)); 186 ap = (struct argnod*)stakseek(ARGVAL); 187 #if SHOPT_MULTIBYTE 188 { 189 register int c = *cur; 190 register genchar *cp; 191 /* adjust cur */ 192 cp = (genchar *)outbuff + *cur; 193 c = *cp; 194 *cp = 0; 195 *cur = ed_external((genchar*)outbuff,(char*)stakptr(0)); 196 *cp = c; 197 *eol = ed_external((genchar*)outbuff,outbuff); 198 } 199 #endif /* SHOPT_MULTIBYTE */ 200 out = outbuff + *cur + (sh_isoption(SH_VI)!=0); 201 comptr->comtyp = COMSCAN; 202 comptr->comarg = ap; 203 ap->argflag = (ARG_MAC|ARG_EXP); 204 ap->argnxt.ap = 0; 205 ap->argchn.cp = 0; 206 { 207 register int c; 208 char *last = out; 209 c = *(unsigned char*)out; 210 begin = out = find_begin(outbuff,last,0,&var); 211 /* addstar set to zero if * should not be added */ 212 if(var=='$') 213 { 214 stakputs("${!"); 215 stakwrite(out,last-out); 216 stakputs("@}"); 217 out = last; 218 } 219 else 220 { 221 addstar = '*'; 222 while(out < last) 223 { 224 c = *(unsigned char*)out; 225 if(isexp(c)) 226 addstar = 0; 227 if (c == '/') 228 { 229 if(addstar == 0) 230 strip = 0; 231 dir = out+1; 232 } 233 stakputc(c); 234 out++; 235 } 236 } 237 if(var!='$' && mode=='\\' && out[-1]!='*') 238 addstar = '*'; 239 if(*begin=='~' && !strchr(begin,'/')) 240 addstar = 0; 241 stakputc(addstar); 242 ap = (struct argnod*)stakfreeze(1); 243 } 244 if(mode!='*') 245 sh_onoption(SH_MARKDIRS); 246 { 247 register char **com; 248 char *cp=begin, *left=0, *saveout="."; 249 int nocase=0,narg,cmd_completion=0; 250 register int size='x'; 251 while(cp>outbuff && ((size=cp[-1])==' ' || size=='\t')) 252 cp--; 253 if(!var && !strchr(ap->argval,'/') && (((cp==outbuff&&sh.nextprompt==1) || (strchr(";&|(",size)) && (cp==outbuff+1||size=='('||cp[-2]!='>') && *begin!='~' ))) 254 { 255 cmd_completion=1; 256 sh_onstate(SH_COMPLETE); 257 } 258 if(ep->e_nlist) 259 { 260 narg = 1; 261 com = av; 262 if(dir) 263 begin += (dir-begin); 264 } 265 else 266 { 267 com = sh_argbuild(&narg,comptr,0); 268 /* special handling for leading quotes */ 269 if(begin>outbuff && (begin[-1]=='"' || begin[-1]=='\'')) 270 begin--; 271 } 272 sh_offstate(SH_COMPLETE); 273 /* allow a search to be aborted */ 274 if(sh.trapnote&SH_SIGSET) 275 { 276 rval = -1; 277 goto done; 278 } 279 /* match? */ 280 if (*com==0 || (narg <= 1 && (strcmp(ap->argval,*com)==0) || (addstar && com[0][strlen(*com)-1]=='*'))) 281 { 282 rval = -1; 283 goto done; 284 } 285 if(mode=='=') 286 { 287 if (strip && !cmd_completion) 288 { 289 register char **ptrcom; 290 for(ptrcom=com;*ptrcom;ptrcom++) 291 /* trim directory prefix */ 292 *ptrcom = path_basename(*ptrcom); 293 } 294 sfputc(sfstderr,'\n'); 295 sh_menu(sfstderr,narg,com); 296 sfsync(sfstderr); 297 ep->e_nlist = narg; 298 ep->e_clist = com; 299 goto done; 300 } 301 /* see if there is enough room */ 302 size = *eol - (out-begin); 303 if(mode=='\\') 304 { 305 int c; 306 if(dir) 307 { 308 c = *dir; 309 *dir = 0; 310 saveout = begin; 311 } 312 if(saveout=astconf("PATH_ATTRIBUTES",saveout,(char*)0)) 313 nocase = (strchr(saveout,'c')!=0); 314 if(dir) 315 *dir = c; 316 /* just expand until name is unique */ 317 size += strlen(*com); 318 } 319 else 320 { 321 size += narg; 322 { 323 char **savcom = com; 324 while (*com) 325 size += strlen(cp=sh_fmtq(*com++)); 326 com = savcom; 327 } 328 } 329 /* see if room for expansion */ 330 if(outbuff+size >= &outbuff[MAXLINE]) 331 { 332 com[0] = ap->argval; 333 com[1] = 0; 334 } 335 /* save remainder of the buffer */ 336 if(*out) 337 left=stakcopy(out); 338 if(cmd_completion && mode=='\\') 339 out = strcopy(begin,path_basename(cp= *com++)); 340 else if(mode=='*') 341 { 342 if(ep->e_nlist && dir && var) 343 { 344 if(*cp==var) 345 cp++; 346 else 347 *begin++ = var; 348 out = strcopy(begin,cp); 349 var = 0; 350 } 351 else 352 out = strcopy(begin,sh_fmtq(*com)); 353 com++; 354 } 355 else 356 out = strcopy(begin,*com++); 357 if(mode=='\\') 358 { 359 saveout= ++out; 360 while (*com && *begin) 361 { 362 if(cmd_completion) 363 out = overlaid(begin,path_basename(*com++),nocase); 364 else 365 out = overlaid(begin,*com++,nocase); 366 } 367 mode = (out==saveout); 368 if(out[-1]==0) 369 out--; 370 if(mode && out[-1]!='/') 371 { 372 if(cmd_completion) 373 { 374 Namval_t *np; 375 /* add as tracked alias */ 376 #ifdef PATH_BFPATH 377 Pathcomp_t *pp; 378 if(*cp=='/' && (pp=path_dirfind(sh.pathlist,cp,'/')) && (np=nv_search(begin,sh.track_tree,NV_ADD))) 379 path_alias(np,pp); 380 #else 381 if(*cp=='/' && (np=nv_search(begin,sh.track_tree,NV_ADD))) 382 path_alias(np,cp); 383 #endif 384 out = strcopy(begin,cp); 385 } 386 /* add quotes if necessary */ 387 if((cp=sh_fmtq(begin))!=begin) 388 out = strcopy(begin,cp); 389 if(var=='$' && begin[-1]=='{') 390 *out = '}'; 391 else 392 *out = ' '; 393 *++out = 0; 394 } 395 else if(out[-1]=='/' && (cp=sh_fmtq(begin))!=begin) 396 { 397 out = strcopy(begin,cp); 398 if(out[-1] =='"' || out[-1]=='\'') 399 *--out = 0;; 400 } 401 if(*begin==0) 402 ed_ringbell(); 403 } 404 else 405 { 406 while (*com) 407 { 408 *out++ = ' '; 409 out = strcopy(out,sh_fmtq(*com++)); 410 } 411 } 412 if(ep->e_nlist) 413 { 414 cp = com[-1]; 415 if(cp[strlen(cp)-1]!='/') 416 { 417 if(var=='$' && begin[-1]=='{') 418 *out = '}'; 419 else 420 *out = ' '; 421 out++; 422 } 423 else if(out[-1] =='"' || out[-1]=='\'') 424 out--; 425 *out = 0; 426 } 427 *cur = (out-outbuff); 428 /* restore rest of buffer */ 429 if(left) 430 out = strcopy(out,left); 431 *eol = (out-outbuff); 432 } 433 done: 434 sh_offstate(SH_FCOMPLETE); 435 if(!ep->e_nlist) 436 stakset(ep->e_stkptr,ep->e_stkoff); 437 if(nomarkdirs) 438 sh_offoption(SH_MARKDIRS); 439 #if SHOPT_MULTIBYTE 440 { 441 register int c,n=0; 442 /* first re-adjust cur */ 443 c = outbuff[*cur]; 444 outbuff[*cur] = 0; 445 for(out=outbuff; *out;n++) 446 mbchar(out); 447 outbuff[*cur] = c; 448 *cur = n; 449 outbuff[*eol+1] = 0; 450 *eol = ed_internal(outbuff,(genchar*)outbuff); 451 } 452 #endif /* SHOPT_MULTIBYTE */ 453 return(rval); 454 } 455 456 /* 457 * look for edit macro named _i 458 * if found, puts the macro definition into lookahead buffer and returns 1 459 */ 460 int ed_macro(Edit_t *ep, register int i) 461 { 462 register char *out; 463 Namval_t *np; 464 genchar buff[LOOKAHEAD+1]; 465 if(i != '@') 466 ep->e_macro[1] = i; 467 /* undocumented feature, macros of the form <ESC>[c evoke alias __c */ 468 if(i=='_') 469 ep->e_macro[2] = ed_getchar(ep,1); 470 else 471 ep->e_macro[2] = 0; 472 if (isalnum(i)&&(np=nv_search(ep->e_macro,sh.alias_tree,HASH_SCOPE))&&(out=nv_getval(np))) 473 { 474 #if SHOPT_MULTIBYTE 475 /* copy to buff in internal representation */ 476 int c = 0; 477 if( strlen(out) > LOOKAHEAD ) 478 { 479 c = out[LOOKAHEAD]; 480 out[LOOKAHEAD] = 0; 481 } 482 i = ed_internal(out,buff); 483 if(c) 484 out[LOOKAHEAD] = c; 485 #else 486 strncpy((char*)buff,out,LOOKAHEAD); 487 buff[LOOKAHEAD] = 0; 488 i = strlen((char*)buff); 489 #endif /* SHOPT_MULTIBYTE */ 490 while(i-- > 0) 491 ed_ungetchar(ep,buff[i]); 492 return(1); 493 } 494 return(0); 495 } 496 497 /* 498 * Enter the fc command on the current history line 499 */ 500 int ed_fulledit(Edit_t *ep) 501 { 502 register char *cp; 503 if(!sh.hist_ptr) 504 return(-1); 505 /* use EDITOR on current command */ 506 if(ep->e_hline == ep->e_hismax) 507 { 508 if(ep->e_eol<0) 509 return(-1); 510 #if SHOPT_MULTIBYTE 511 ep->e_inbuf[ep->e_eol+1] = 0; 512 ed_external(ep->e_inbuf, (char *)ep->e_inbuf); 513 #endif /* SHOPT_MULTIBYTE */ 514 sfwrite(sh.hist_ptr->histfp,(char*)ep->e_inbuf,ep->e_eol+1); 515 sh_onstate(SH_HISTORY); 516 hist_flush(sh.hist_ptr); 517 } 518 cp = strcopy((char*)ep->e_inbuf,e_runvi); 519 cp = strcopy(cp, fmtbase((long)ep->e_hline,10,0)); 520 ep->e_eol = ((unsigned char*)cp - (unsigned char*)ep->e_inbuf)-(sh_isoption(SH_VI)!=0); 521 return(0); 522 } 523