1 /* $Header: /p/tcsh/cvsroot/tcsh/tw.comp.c,v 1.42 2007/10/01 21:52:00 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("$tcsh: tw.comp.c,v 1.42 2007/10/01 21:52:00 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 (const Char *, Char **); 45 static Char **tw_find (Char *, struct varent *, int); 46 static Char *tw_tok (Char *); 47 static int tw_pos (Char *, int); 48 static void tw_pr (Char **); 49 static int tw_match (const Char *, const Char *); 50 static void tw_prlist (struct varent *); 51 static const Char *tw_dollar (const Char *,Char **, size_t, Char **, 52 Char, const char *); 53 54 /* docomplete(): 55 * Add or list completions in the completion list 56 */ 57 /*ARGSUSED*/ 58 void 59 docomplete(Char **v, struct command *t) 60 { 61 struct varent *vp; 62 Char *p; 63 Char **pp; 64 65 USE(t); 66 v++; 67 p = *v++; 68 if (p == 0) 69 tw_prlist(&completions); 70 else if (*v == 0) { 71 vp = adrof1(strip(p), &completions); 72 if (vp && vp->vec) 73 tw_pr(vp->vec), xputchar('\n'); 74 else 75 { 76 #ifdef TDEBUG 77 xprintf("tw_find(%s) \n", short2str(strip(p))); 78 #endif /* TDEBUG */ 79 pp = tw_find(strip(p), &completions, FALSE); 80 if (pp) 81 tw_pr(pp), xputchar('\n'); 82 } 83 } 84 else 85 set1(strip(p), saveblk(v), &completions, VAR_READWRITE); 86 } /* end docomplete */ 87 88 89 /* douncomplete(): 90 * Remove completions from the completion list 91 */ 92 /*ARGSUSED*/ 93 void 94 douncomplete(Char **v, struct command *t) 95 { 96 USE(t); 97 unset1(v, &completions); 98 } /* end douncomplete */ 99 100 101 /* tw_prlist(): 102 * Pretty print a list of variables 103 */ 104 static void 105 tw_prlist(struct varent *p) 106 { 107 struct varent *c; 108 109 for (;;) { 110 while (p->v_left) 111 p = p->v_left; 112 x: 113 if (p->v_parent == 0) /* is it the header? */ 114 break; 115 if (setintr) { 116 int old_pintr_disabled; 117 118 pintr_push_enable(&old_pintr_disabled); 119 cleanup_until(&old_pintr_disabled); 120 } 121 xprintf("%s\t", short2str(p->v_name)); 122 if (p->vec) 123 tw_pr(p->vec); 124 xputchar('\n'); 125 if (p->v_right) { 126 p = p->v_right; 127 continue; 128 } 129 do { 130 c = p; 131 p = p->v_parent; 132 } while (p->v_right == c); 133 goto x; 134 } 135 } /* end tw_prlist */ 136 137 138 /* tw_pr(): 139 * Pretty print a completion, adding single quotes around 140 * a completion argument and collapsing multiple spaces to one. 141 */ 142 static void 143 tw_pr(Char **cmp) 144 { 145 int sp, osp; 146 Char *ptr; 147 148 for (; *cmp; cmp++) { 149 xputchar('\''); 150 for (osp = 0, ptr = *cmp; *ptr; ptr++) { 151 sp = Isspace(*ptr); 152 if (sp && osp) 153 continue; 154 xputwchar(*ptr); 155 osp = sp; 156 } 157 xputchar('\''); 158 if (cmp[1]) 159 xputchar(' '); 160 } 161 } /* end tw_pr */ 162 163 164 /* tw_find(): 165 * Find the first matching completion. 166 * For commands we only look at names that start with - 167 */ 168 static Char ** 169 tw_find(Char *nam, struct varent *vp, int cmd) 170 { 171 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 int 194 tw_pos(Char *ran, int wno) 195 { 196 Char *p; 197 198 if (ran[0] == '*' && ran[1] == '\0') 199 return 1; 200 201 for (p = ran; *p && *p != '-'; p++) 202 continue; 203 204 if (*p == '\0') /* range == <number> */ 205 return wno == getn(ran); 206 207 if (ran == p) /* range = - <number> */ 208 return wno <= getn(&ran[1]); 209 *p++ = '\0'; 210 211 if (*p == '\0') /* range = <number> - */ 212 return getn(ran) <= wno; 213 else /* range = <number> - <number> */ 214 return (getn(ran) <= wno) && (wno <= getn(p)); 215 } /* end tw_pos */ 216 217 218 /* tw_tok(): 219 * Return the next word from string, unquoteing it. 220 */ 221 static Char * 222 tw_tok(Char *str) 223 { 224 static Char *bf = NULL; 225 226 if (str != NULL) 227 bf = str; 228 229 /* skip leading spaces */ 230 for (; *bf && Isspace(*bf); bf++) 231 continue; 232 233 for (str = bf; *bf && !Isspace(*bf); bf++) { 234 if (ismetahash(*bf)) 235 return INVPTR; 236 *bf = *bf & ~QUOTE; 237 } 238 if (*bf != '\0') 239 *bf++ = '\0'; 240 241 return *str ? str : NULL; 242 } /* end tw_tok */ 243 244 245 /* tw_match(): 246 * Match a string against the pattern given. 247 * and return the number of matched characters 248 * in a prefix of the string. 249 */ 250 static int 251 tw_match(const Char *str, const Char *pat) 252 { 253 const Char *estr; 254 int rv = Gnmatch(str, pat, &estr); 255 #ifdef TDEBUG 256 xprintf("Gnmatch(%s, ", short2str(str)); 257 xprintf("%s, ", short2str(pat)); 258 xprintf("%s) = %d [%d]\n", short2str(estr), rv, estr - str); 259 #endif /* TDEBUG */ 260 return (int) (rv ? estr - str : -1); 261 } 262 263 264 /* tw_result(): 265 * Return what the completion action should be depending on the 266 * string 267 */ 268 static int 269 tw_result(const Char *act, Char **pat) 270 { 271 int looking; 272 static Char* res = NULL; 273 Char *p; 274 275 if (res != NULL) 276 xfree(res), res = NULL; 277 278 switch (act[0] & ~QUOTE) { 279 case 'X': 280 looking = TW_COMPLETION; 281 break; 282 case 'S': 283 looking = TW_SIGNAL; 284 break; 285 case 'a': 286 looking = TW_ALIAS; 287 break; 288 case 'b': 289 looking = TW_BINDING; 290 break; 291 case 'c': 292 looking = TW_COMMAND; 293 break; 294 case 'C': 295 looking = TW_PATH | TW_COMMAND; 296 break; 297 case 'd': 298 looking = TW_DIRECTORY; 299 break; 300 case 'D': 301 looking = TW_PATH | TW_DIRECTORY; 302 break; 303 case 'e': 304 looking = TW_ENVVAR; 305 break; 306 case 'f': 307 looking = TW_FILE; 308 break; 309 #ifdef COMPAT 310 case 'p': 311 #endif /* COMPAT */ 312 case 'F': 313 looking = TW_PATH | TW_FILE; 314 break; 315 case 'g': 316 looking = TW_GRPNAME; 317 break; 318 case 'j': 319 looking = TW_JOB; 320 break; 321 case 'l': 322 looking = TW_LIMIT; 323 break; 324 case 'n': 325 looking = TW_NONE; 326 break; 327 case 's': 328 looking = TW_SHELLVAR; 329 break; 330 case 't': 331 looking = TW_TEXT; 332 break; 333 case 'T': 334 looking = TW_PATH | TW_TEXT; 335 break; 336 case 'v': 337 looking = TW_VARIABLE; 338 break; 339 case 'u': 340 looking = TW_USER; 341 break; 342 case 'x': 343 looking = TW_EXPLAIN; 344 break; 345 346 case '$': 347 *pat = res = Strsave(&act[1]); 348 (void) strip(res); 349 return(TW_VARLIST); 350 351 case '(': 352 *pat = res = Strsave(&act[1]); 353 if ((p = Strchr(res, ')')) != NULL) 354 *p = '\0'; 355 (void) strip(res); 356 return TW_WORDLIST; 357 358 case '`': 359 res = Strsave(act); 360 if ((p = Strchr(&res[1], '`')) != NULL) 361 *++p = '\0'; 362 363 if (didfds == 0) { 364 /* 365 * Make sure that we have some file descriptors to 366 * play with, so that the processes have at least 0, 1, 2 367 * open 368 */ 369 (void) dcopy(SHIN, 0); 370 (void) dcopy(SHOUT, 1); 371 (void) dcopy(SHDIAG, 2); 372 } 373 if ((p = globone(res, G_APPEND)) != NULL) { 374 xfree(res), res = NULL; 375 *pat = res = Strsave(p); 376 xfree(p); 377 return TW_WORDLIST; 378 } 379 return TW_ZERO; 380 381 default: 382 stderror(ERR_COMPCOM, short2str(act)); 383 return TW_ZERO; 384 } 385 386 switch (act[1] & ~QUOTE) { 387 case '\0': 388 return looking; 389 390 case ':': 391 *pat = res = Strsave(&act[2]); 392 (void) strip(res); 393 return looking; 394 395 default: 396 stderror(ERR_COMPCOM, short2str(act)); 397 return TW_ZERO; 398 } 399 } /* end tw_result */ 400 401 402 /* tw_dollar(): 403 * Expand $<n> args in buffer 404 */ 405 static const Char * 406 tw_dollar(const Char *str, Char **wl, size_t nwl, Char **result, Char sep, 407 const char *msg) 408 { 409 struct Strbuf buf = Strbuf_INIT; 410 Char *res; 411 const Char *sp; 412 413 for (sp = str; *sp && *sp != sep;) 414 if (sp[0] == '$' && sp[1] == ':' && Isdigit(sp[sp[2] == '-' ? 3 : 2])) { 415 int num, neg = 0; 416 sp += 2; 417 if (*sp == '-') { 418 neg = 1; 419 sp++; 420 } 421 for (num = *sp++ - '0'; Isdigit(*sp); num += 10 * num + *sp++ - '0') 422 continue; 423 if (neg) 424 num = nwl - num - 1; 425 if (num >= 0 && (size_t)num < nwl) 426 Strbuf_append(&buf, wl[num]); 427 } 428 else 429 Strbuf_append1(&buf, *sp++); 430 431 res = Strbuf_finish(&buf); 432 433 if (*sp++ == sep) { 434 *result = res; 435 return sp; 436 } 437 438 xfree(res); 439 /* Truncates data if WIDE_STRINGS */ 440 stderror(ERR_COMPMIS, (int)sep, msg, short2str(str)); 441 return --sp; 442 } /* end tw_dollar */ 443 444 445 /* tw_complete(): 446 * Return the appropriate completion for the command 447 * 448 * valid completion strings are: 449 * p/<range>/<completion>/[<suffix>/] positional 450 * c/<pattern>/<completion>/[<suffix>/] current word ignore pattern 451 * C/<pattern>/<completion>/[<suffix>/] current word with pattern 452 * n/<pattern>/<completion>/[<suffix>/] next word 453 * N/<pattern>/<completion>/[<suffix>/] next-next word 454 */ 455 int 456 tw_complete(const Char *line, Char **word, Char **pat, int looking, eChar *suf) 457 { 458 Char *buf, **vec, **wl; 459 static Char nomatch[2] = { (Char) ~0, 0x00 }; 460 const Char *ptr; 461 size_t wordno; 462 int n; 463 464 buf = Strsave(line); 465 cleanup_push(buf, xfree); 466 /* Single-character words, empty current word, terminating NULL */ 467 wl = xmalloc(((Strlen(line) + 1) / 2 + 2) * sizeof (*wl)); 468 cleanup_push(wl, xfree); 469 470 /* find the command */ 471 if ((wl[0] = tw_tok(buf)) == NULL || wl[0] == INVPTR) { 472 cleanup_until(buf); 473 return TW_ZERO; 474 } 475 476 /* 477 * look for hardwired command completions using a globbing 478 * search and for arguments using a normal search. 479 */ 480 if ((vec = tw_find(wl[0], &completions, (looking == TW_COMMAND))) 481 == NULL) { 482 cleanup_until(buf); 483 return looking; 484 } 485 486 /* tokenize the line one more time :-( */ 487 for (wordno = 1; (wl[wordno] = tw_tok(NULL)) != NULL && 488 wl[wordno] != INVPTR; wordno++) 489 continue; 490 491 if (wl[wordno] == INVPTR) { /* Found a meta character */ 492 cleanup_until(buf); 493 return TW_ZERO; /* de-activate completions */ 494 } 495 #ifdef TDEBUG 496 { 497 size_t i; 498 for (i = 0; i < wordno; i++) 499 xprintf("'%s' ", short2str(wl[i])); 500 xprintf("\n"); 501 } 502 #endif /* TDEBUG */ 503 504 /* if the current word is empty move the last word to the next */ 505 if (**word == '\0') { 506 wl[wordno] = *word; 507 wordno++; 508 } 509 wl[wordno] = NULL; 510 511 512 #ifdef TDEBUG 513 xprintf("\r\n"); 514 xprintf(" w#: %lu\n", (unsigned long)wordno); 515 xprintf("line: %s\n", short2str(line)); 516 xprintf(" cmd: %s\n", short2str(wl[0])); 517 xprintf("word: %s\n", short2str(*word)); 518 xprintf("last: %s\n", wordno >= 2 ? short2str(wl[wordno-2]) : "n/a"); 519 xprintf("this: %s\n", wordno >= 1 ? short2str(wl[wordno-1]) : "n/a"); 520 #endif /* TDEBUG */ 521 522 for (;vec != NULL && (ptr = vec[0]) != NULL; vec++) { 523 Char *ran, /* The pattern or range X/<range>/XXXX/ */ 524 *com, /* The completion X/XXXXX/<completion>/ */ 525 *pos = NULL; /* scratch pointer */ 526 int cmd, res; 527 Char sep; /* the command and separator characters */ 528 529 if (ptr[0] == '\0') 530 continue; 531 532 #ifdef TDEBUG 533 xprintf("match %s\n", short2str(ptr)); 534 #endif /* TDEBUG */ 535 536 switch (cmd = ptr[0]) { 537 case 'N': 538 pos = (wordno < 3) ? nomatch : wl[wordno - 3]; 539 break; 540 case 'n': 541 pos = (wordno < 2) ? nomatch : wl[wordno - 2]; 542 break; 543 case 'c': 544 case 'C': 545 pos = (wordno < 1) ? nomatch : wl[wordno - 1]; 546 break; 547 case 'p': 548 break; 549 default: 550 stderror(ERR_COMPINV, CGETS(27, 1, "command"), cmd); 551 return TW_ZERO; 552 } 553 554 sep = ptr[1]; 555 if (!Ispunct(sep)) { 556 /* Truncates data if WIDE_STRINGS */ 557 stderror(ERR_COMPINV, CGETS(27, 2, "separator"), (int)sep); 558 return TW_ZERO; 559 } 560 561 ptr = tw_dollar(&ptr[2], wl, wordno, &ran, sep, 562 CGETS(27, 3, "pattern")); 563 cleanup_push(ran, xfree); 564 if (ran[0] == '\0') /* check for empty pattern (disallowed) */ 565 { 566 stderror(ERR_COMPINC, cmd == 'p' ? CGETS(27, 4, "range") : 567 CGETS(27, 3, "pattern"), ""); 568 return TW_ZERO; 569 } 570 571 ptr = tw_dollar(ptr, wl, wordno, &com, sep, 572 CGETS(27, 5, "completion")); 573 cleanup_push(com, xfree); 574 575 if (*ptr != '\0') { 576 if (*ptr == sep) 577 *suf = CHAR_ERR; 578 else 579 *suf = *ptr; 580 } 581 else 582 *suf = '\0'; 583 584 #ifdef TDEBUG 585 xprintf("command: %c\nseparator: %c\n", cmd, (int)sep); 586 xprintf("pattern: %s\n", short2str(ran)); 587 xprintf("completion: %s\n", short2str(com)); 588 xprintf("suffix: "); 589 switch (*suf) { 590 case 0: 591 xprintf("*auto suffix*\n"); 592 break; 593 case CHAR_ERR: 594 xprintf("*no suffix*\n"); 595 break; 596 default: 597 xprintf("%c\n", (int)*suf); 598 break; 599 } 600 #endif /* TDEBUG */ 601 602 switch (cmd) { 603 case 'p': /* positional completion */ 604 #ifdef TDEBUG 605 xprintf("p: tw_pos(%s, %lu) = ", short2str(ran), 606 (unsigned long)wordno - 1); 607 xprintf("%d\n", tw_pos(ran, wordno - 1)); 608 #endif /* TDEBUG */ 609 if (!tw_pos(ran, wordno - 1)) { 610 cleanup_until(ran); 611 continue; 612 } 613 break; 614 615 case 'N': /* match with the next-next word */ 616 case 'n': /* match with the next word */ 617 case 'c': /* match with the current word */ 618 case 'C': 619 #ifdef TDEBUG 620 xprintf("%c: ", cmd); 621 #endif /* TDEBUG */ 622 if ((n = tw_match(pos, ran)) < 0) { 623 cleanup_until(ran); 624 continue; 625 } 626 if (cmd == 'c') 627 *word += n; 628 break; 629 630 default: 631 abort(); /* Cannot happen */ 632 } 633 tsetenv(STRCOMMAND_LINE, line); 634 res = tw_result(com, pat); 635 Unsetenv(STRCOMMAND_LINE); 636 cleanup_until(buf); 637 return res; 638 } 639 cleanup_until(buf); 640 *suf = '\0'; 641 return TW_ZERO; 642 } /* end tw_complete */ 643