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