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