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