1 /* $Header: /src/pub/tcsh/tw.comp.c,v 1.33 2002/06/25 19:02:11 christos Exp $ */ 2 /* 3 * tw.comp.c: File completion builtin 4 */ 5 /*- 6 * Copyright (c) 1980, 1991 The Regents of the University of California. 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 #include "sh.h" 34 35 RCSID("$Id: tw.comp.c,v 1.33 2002/06/25 19:02:11 christos Exp $") 36 37 #include "tw.h" 38 #include "ed.h" 39 #include "tc.h" 40 41 /* #define TDEBUG */ 42 struct varent completions; 43 44 static int tw_result __P((Char *, Char **)); 45 static Char **tw_find __P((Char *, struct varent *, int)); 46 static Char *tw_tok __P((Char *)); 47 static bool tw_pos __P((Char *, int)); 48 static void tw_pr __P((Char **)); 49 static int tw_match __P((Char *, Char *)); 50 static void tw_prlist __P((struct varent *)); 51 static Char *tw_dollar __P((Char *,Char **, int, Char *, 52 int, const char *)); 53 54 /* docomplete(): 55 * Add or list completions in the completion list 56 */ 57 /*ARGSUSED*/ 58 void 59 docomplete(v, t) 60 Char **v; 61 struct command *t; 62 { 63 register struct varent *vp; 64 register Char *p; 65 66 USE(t); 67 v++; 68 p = *v++; 69 if (p == 0) 70 tw_prlist(&completions); 71 else if (*v == 0) { 72 vp = adrof1(strip(p), &completions); 73 if (vp && vp->vec) 74 tw_pr(vp->vec), xputchar('\n'); 75 } 76 else 77 set1(strip(p), saveblk(v), &completions, VAR_READWRITE); 78 } /* end docomplete */ 79 80 81 /* douncomplete(): 82 * Remove completions from the completion list 83 */ 84 /*ARGSUSED*/ 85 void 86 douncomplete(v, t) 87 Char **v; 88 struct command *t; 89 { 90 USE(t); 91 unset1(v, &completions); 92 } /* end douncomplete */ 93 94 95 /* tw_prlist(): 96 * Pretty print a list of variables 97 */ 98 static void 99 tw_prlist(p) 100 struct varent *p; 101 { 102 register struct varent *c; 103 104 if (setintr) 105 #ifdef BSDSIGS 106 (void) sigsetmask(sigblock((sigmask_t) 0) & ~sigmask(SIGINT)); 107 #else /* BSDSIGS */ 108 (void) sigrelse(SIGINT); 109 #endif /* BSDSIGS */ 110 111 for (;;) { 112 while (p->v_left) 113 p = p->v_left; 114 x: 115 if (p->v_parent == 0) /* is it the header? */ 116 return; 117 xprintf("%s\t", short2str(p->v_name)); 118 if (p->vec) 119 tw_pr(p->vec); 120 xputchar('\n'); 121 if (p->v_right) { 122 p = p->v_right; 123 continue; 124 } 125 do { 126 c = p; 127 p = p->v_parent; 128 } while (p->v_right == c); 129 goto x; 130 } 131 } /* end tw_prlist */ 132 133 134 /* tw_pr(): 135 * Pretty print a completion, adding single quotes around 136 * a completion argument and collapsing multiple spaces to one. 137 */ 138 static void 139 tw_pr(cmp) 140 Char **cmp; 141 { 142 bool sp, osp; 143 Char *ptr; 144 145 for (; *cmp; cmp++) { 146 xputchar('\''); 147 for (osp = 0, ptr = *cmp; *ptr; ptr++) { 148 sp = Isspace(*ptr); 149 if (sp && osp) 150 continue; 151 xputchar(*ptr); 152 osp = sp; 153 } 154 xputchar('\''); 155 if (cmp[1]) 156 xputchar(' '); 157 } 158 } /* end tw_pr */ 159 160 161 /* tw_find(): 162 * Find the first matching completion. 163 * For commands we only look at names that start with - 164 */ 165 static Char ** 166 tw_find(nam, vp, cmd) 167 Char *nam; 168 register struct varent *vp; 169 int cmd; 170 { 171 register Char **rv; 172 173 for (vp = vp->v_left; vp; vp = vp->v_right) { 174 if (vp->v_left && (rv = tw_find(nam, vp, cmd)) != NULL) 175 return rv; 176 if (cmd) { 177 if (vp->v_name[0] != '-') 178 continue; 179 if (Gmatch(nam, &vp->v_name[1]) && vp->vec != NULL) 180 return vp->vec; 181 } 182 else 183 if (Gmatch(nam, vp->v_name) && vp->vec != NULL) 184 return vp->vec; 185 } 186 return NULL; 187 } /* end tw_find */ 188 189 190 /* tw_pos(): 191 * Return true if the position is within the specified range 192 */ 193 static bool 194 tw_pos(ran, wno) 195 Char *ran; 196 int wno; 197 { 198 Char *p; 199 200 if (ran[0] == '*' && ran[1] == '\0') 201 return 1; 202 203 for (p = ran; *p && *p != '-'; p++) 204 continue; 205 206 if (*p == '\0') /* range == <number> */ 207 return wno == getn(ran); 208 209 if (ran == p) /* range = - <number> */ 210 return wno <= getn(&ran[1]); 211 *p++ = '\0'; 212 213 if (*p == '\0') /* range = <number> - */ 214 return getn(ran) <= wno; 215 else /* range = <number> - <number> */ 216 return (getn(ran) <= wno) && (wno <= getn(p)); 217 218 } /* end tw_pos */ 219 220 221 /* tw_tok(): 222 * Return the next word from string, unquoteing it. 223 */ 224 static Char * 225 tw_tok(str) 226 Char *str; 227 { 228 static Char *bf = NULL; 229 230 if (str != NULL) 231 bf = str; 232 233 /* skip leading spaces */ 234 for (; *bf && Isspace(*bf); bf++) 235 continue; 236 237 for (str = bf; *bf && !Isspace(*bf); bf++) { 238 if (ismeta(*bf)) 239 return INVPTR; 240 *bf = *bf & ~QUOTE; 241 } 242 if (*bf != '\0') 243 *bf++ = '\0'; 244 245 return *str ? str : NULL; 246 } /* end tw_tok */ 247 248 249 /* tw_match(): 250 * Match a string against the pattern given. 251 * and return the number of matched characters 252 * in a prefix of the string. 253 */ 254 static int 255 tw_match(str, pat) 256 Char *str, *pat; 257 { 258 Char *estr; 259 int rv = Gnmatch(str, pat, &estr); 260 #ifdef TDEBUG 261 xprintf("Gnmatch(%s, ", short2str(str)); 262 xprintf("%s, ", short2str(pat)); 263 xprintf("%s) = %d [%d]\n", short2str(estr), rv, estr - str); 264 #endif /* TDEBUG */ 265 return (int) (rv ? estr - str : -1); 266 } 267 268 269 /* tw_result(): 270 * Return what the completion action should be depending on the 271 * string 272 */ 273 static int 274 tw_result(act, pat) 275 Char *act, **pat; 276 { 277 int looking; 278 static Char* res = NULL; 279 280 if (res != NULL) 281 xfree((ptr_t) res), res = NULL; 282 283 switch (act[0] & ~QUOTE) { 284 case 'X': 285 looking = TW_COMPLETION; 286 break; 287 case 'S': 288 looking = TW_SIGNAL; 289 break; 290 case 'a': 291 looking = TW_ALIAS; 292 break; 293 case 'b': 294 looking = TW_BINDING; 295 break; 296 case 'c': 297 looking = TW_COMMAND; 298 break; 299 case 'C': 300 looking = TW_PATH | TW_COMMAND; 301 break; 302 case 'd': 303 looking = TW_DIRECTORY; 304 break; 305 case 'D': 306 looking = TW_PATH | TW_DIRECTORY; 307 break; 308 case 'e': 309 looking = TW_ENVVAR; 310 break; 311 case 'f': 312 looking = TW_FILE; 313 break; 314 #ifdef COMPAT 315 case 'p': 316 #endif /* COMPAT */ 317 case 'F': 318 looking = TW_PATH | TW_FILE; 319 break; 320 case 'g': 321 looking = TW_GRPNAME; 322 break; 323 case 'j': 324 looking = TW_JOB; 325 break; 326 case 'l': 327 looking = TW_LIMIT; 328 break; 329 case 'n': 330 looking = TW_NONE; 331 break; 332 case 's': 333 looking = TW_SHELLVAR; 334 break; 335 case 't': 336 looking = TW_TEXT; 337 break; 338 case 'T': 339 looking = TW_PATH | TW_TEXT; 340 break; 341 case 'v': 342 looking = TW_VARIABLE; 343 break; 344 case 'u': 345 looking = TW_USER; 346 break; 347 case 'x': 348 looking = TW_EXPLAIN; 349 break; 350 351 case '$': 352 *pat = res = Strsave(&act[1]); 353 (void) strip(res); 354 return(TW_VARLIST); 355 356 case '(': 357 *pat = res = Strsave(&act[1]); 358 if ((act = Strchr(res, ')')) != NULL) 359 *act = '\0'; 360 (void) strip(res); 361 return TW_WORDLIST; 362 363 case '`': 364 res = Strsave(act); 365 if ((act = Strchr(&res[1], '`')) != NULL) 366 *++act = '\0'; 367 368 if (didfds == 0) { 369 /* 370 * Make sure that we have some file descriptors to 371 * play with, so that the processes have at least 0, 1, 2 372 * open 373 */ 374 (void) dcopy(SHIN, 0); 375 (void) dcopy(SHOUT, 1); 376 (void) dcopy(SHDIAG, 2); 377 } 378 if ((act = globone(res, G_APPEND)) != NULL) { 379 xfree((ptr_t) res), res = NULL; 380 *pat = res = Strsave(act); 381 xfree((ptr_t) act); 382 return TW_WORDLIST; 383 } 384 return TW_ZERO; 385 386 default: 387 stderror(ERR_COMPCOM, short2str(act)); 388 return TW_ZERO; 389 } 390 391 switch (act[1] & ~QUOTE) { 392 case '\0': 393 return looking; 394 395 case ':': 396 *pat = res = Strsave(&act[2]); 397 (void) strip(res); 398 return looking; 399 400 default: 401 stderror(ERR_COMPCOM, short2str(act)); 402 return TW_ZERO; 403 } 404 } /* end tw_result */ 405 406 407 /* tw_dollar(): 408 * Expand $<n> args in buffer 409 */ 410 static Char * 411 tw_dollar(str, wl, nwl, buffer, sep, msg) 412 Char *str, **wl; 413 int nwl; 414 Char *buffer; 415 int sep; 416 const char *msg; 417 { 418 Char *sp, *bp = buffer, *ebp = &buffer[MAXPATHLEN]; 419 420 for (sp = str; *sp && *sp != sep && bp < ebp;) 421 if (sp[0] == '$' && sp[1] == ':' && Isdigit(sp[sp[2] == '-' ? 3 : 2])) { 422 int num, neg = 0; 423 sp += 2; 424 if (*sp == '-') { 425 neg = 1; 426 sp++; 427 } 428 for (num = *sp++ - '0'; Isdigit(*sp); num += 10 * num + *sp++ - '0') 429 continue; 430 if (neg) 431 num = nwl - num - 1; 432 if (num >= 0 && num < nwl) { 433 Char *ptr; 434 for (ptr = wl[num]; *ptr && bp < ebp - 1; *bp++ = *ptr++) 435 continue; 436 437 } 438 } 439 else 440 *bp++ = *sp++; 441 442 *bp = '\0'; 443 444 if (*sp++ == sep) 445 return sp; 446 447 stderror(ERR_COMPMIS, sep, msg, short2str(str)); 448 return --sp; 449 } /* end tw_dollar */ 450 451 452 /* tw_complete(): 453 * Return the appropriate completion for the command 454 * 455 * valid completion strings are: 456 * p/<range>/<completion>/[<suffix>/] positional 457 * c/<pattern>/<completion>/[<suffix>/] current word ignore pattern 458 * C/<pattern>/<completion>/[<suffix>/] current word with pattern 459 * n/<pattern>/<completion>/[<suffix>/] next word 460 * N/<pattern>/<completion>/[<suffix>/] next-next word 461 */ 462 int 463 tw_complete(line, word, pat, looking, suf) 464 Char *line, **word, **pat; 465 int looking, *suf; 466 { 467 Char buf[MAXPATHLEN + 1], **vec, *ptr; 468 Char *wl[MAXPATHLEN/6]; 469 static Char nomatch[2] = { (Char) ~0, 0x00 }; 470 int wordno, n; 471 472 copyn(buf, line, MAXPATHLEN); 473 474 /* find the command */ 475 if ((wl[0] = tw_tok(buf)) == NULL || wl[0] == INVPTR) 476 return TW_ZERO; 477 478 /* 479 * look for hardwired command completions using a globbing 480 * search and for arguments using a normal search. 481 */ 482 if ((vec = tw_find(wl[0], &completions, (looking == TW_COMMAND))) == NULL) 483 return looking; 484 485 /* tokenize the line one more time :-( */ 486 for (wordno = 1; (wl[wordno] = tw_tok(NULL)) != NULL && 487 wl[wordno] != INVPTR; wordno++) 488 continue; 489 490 if (wl[wordno] == INVPTR) /* Found a meta character */ 491 return TW_ZERO; /* de-activate completions */ 492 #ifdef TDEBUG 493 { 494 int i; 495 for (i = 0; i < wordno; i++) 496 xprintf("'%s' ", short2str(wl[i])); 497 xprintf("\n"); 498 } 499 #endif /* TDEBUG */ 500 501 /* if the current word is empty move the last word to the next */ 502 if (**word == '\0') { 503 wl[wordno] = *word; 504 wordno++; 505 } 506 wl[wordno] = NULL; 507 508 509 #ifdef TDEBUG 510 xprintf("\r\n"); 511 xprintf(" w#: %d\n", wordno); 512 xprintf("line: %s\n", short2str(line)); 513 xprintf(" cmd: %s\n", short2str(wl[0])); 514 xprintf("word: %s\n", short2str(*word)); 515 xprintf("last: %s\n", wordno - 2 >= 0 ? short2str(wl[wordno-2]) : "n/a"); 516 xprintf("this: %s\n", wordno - 1 >= 0 ? short2str(wl[wordno-1]) : "n/a"); 517 #endif /* TDEBUG */ 518 519 for (;vec != NULL && (ptr = vec[0]) != NULL; vec++) { 520 Char ran[MAXPATHLEN+1],/* The pattern or range X/<range>/XXXX/ */ 521 com[MAXPATHLEN+1],/* The completion X/XXXXX/<completion>/ */ 522 *pos = NULL; /* scratch pointer */ 523 int cmd, sep; /* the command and separator characters */ 524 525 if (ptr[0] == '\0') 526 continue; 527 528 #ifdef TDEBUG 529 xprintf("match %s\n", short2str(ptr)); 530 #endif /* TDEBUG */ 531 532 switch (cmd = ptr[0]) { 533 case 'N': 534 pos = (wordno - 3 < 0) ? nomatch : wl[wordno - 3]; 535 break; 536 case 'n': 537 pos = (wordno - 2 < 0) ? nomatch : wl[wordno - 2]; 538 break; 539 case 'c': 540 case 'C': 541 pos = (wordno - 1 < 0) ? nomatch : wl[wordno - 1]; 542 break; 543 case 'p': 544 break; 545 default: 546 stderror(ERR_COMPINV, CGETS(27, 1, "command"), cmd); 547 return TW_ZERO; 548 } 549 550 sep = ptr[1]; 551 if (!Ispunct(sep)) { 552 stderror(ERR_COMPINV, CGETS(27, 2, "separator"), sep); 553 return TW_ZERO; 554 } 555 556 ptr = tw_dollar(&ptr[2], wl, wordno, ran, sep, 557 CGETS(27, 3, "pattern")); 558 if (ran[0] == '\0') /* check for empty pattern (disallowed) */ 559 { 560 stderror(ERR_COMPINC, cmd == 'p' ? CGETS(27, 4, "range") : 561 CGETS(27, 3, "pattern"), ""); 562 return TW_ZERO; 563 } 564 565 ptr = tw_dollar(ptr, wl, wordno, com, sep, CGETS(27, 5, "completion")); 566 567 if (*ptr != '\0') { 568 if (*ptr == sep) 569 *suf = ~0; 570 else 571 *suf = *ptr; 572 } 573 else 574 *suf = '\0'; 575 576 #ifdef TDEBUG 577 xprintf("command: %c\nseparator: %c\n", cmd, sep); 578 xprintf("pattern: %s\n", short2str(ran)); 579 xprintf("completion: %s\n", short2str(com)); 580 xprintf("suffix: "); 581 switch (*suf) { 582 case 0: 583 xprintf("*auto suffix*\n"); 584 break; 585 case ~0: 586 xprintf("*no suffix*\n"); 587 break; 588 default: 589 xprintf("%c\n", *suf); 590 break; 591 } 592 #endif /* TDEBUG */ 593 594 switch (cmd) { 595 case 'p': /* positional completion */ 596 #ifdef TDEBUG 597 xprintf("p: tw_pos(%s, %d) = ", short2str(ran), wordno - 1); 598 xprintf("%d\n", tw_pos(ran, wordno - 1)); 599 #endif /* TDEBUG */ 600 if (!tw_pos(ran, wordno - 1)) 601 continue; 602 return tw_result(com, pat); 603 604 case 'N': /* match with the next-next word */ 605 case 'n': /* match with the next word */ 606 case 'c': /* match with the current word */ 607 case 'C': 608 #ifdef TDEBUG 609 xprintf("%c: ", cmd); 610 #endif /* TDEBUG */ 611 if ((n = tw_match(pos, ran)) < 0) 612 continue; 613 if (cmd == 'c') 614 *word += n; 615 return tw_result(com, pat); 616 617 default: 618 return TW_ZERO; /* Cannot happen */ 619 } 620 } 621 *suf = '\0'; 622 return TW_ZERO; 623 } /* end tw_complete */ 624