1 /* $Header: /p/tcsh/cvsroot/tcsh/tw.comp.c,v 1.45 2015/09/30 13:28:02 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.45 2015/09/30 13:28:02 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 *, int); 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, int exact) 252 { 253 const Char *estr; 254 int rv = exact ? Gmatch(estr = str, pat) : Gnmatch(str, pat, &estr); 255 #ifdef TDEBUG 256 xprintf("G%smatch(%s, ", exact ? "" : "n", short2str(str)); 257 xprintf("%s, ", short2str(pat)); 258 xprintf("%s) = %d [%" TCSH_PTRDIFF_T_FMT "d]\n", short2str(estr), rv, 259 estr - str); 260 #endif /* TDEBUG */ 261 return (int) (rv ? estr - str : -1); 262 } 263 264 265 /* tw_result(): 266 * Return what the completion action should be depending on the 267 * string 268 */ 269 static int 270 tw_result(const Char *act, Char **pat) 271 { 272 int looking; 273 static Char* res = NULL; 274 Char *p; 275 276 if (res != NULL) 277 xfree(res), res = NULL; 278 279 switch (act[0] & ~QUOTE) { 280 case 'X': 281 looking = TW_COMPLETION; 282 break; 283 case 'S': 284 looking = TW_SIGNAL; 285 break; 286 case 'a': 287 looking = TW_ALIAS; 288 break; 289 case 'b': 290 looking = TW_BINDING; 291 break; 292 case 'c': 293 looking = TW_COMMAND; 294 break; 295 case 'C': 296 looking = TW_PATH | TW_COMMAND; 297 break; 298 case 'd': 299 looking = TW_DIRECTORY; 300 break; 301 case 'D': 302 looking = TW_PATH | TW_DIRECTORY; 303 break; 304 case 'e': 305 looking = TW_ENVVAR; 306 break; 307 case 'f': 308 looking = TW_FILE; 309 break; 310 #ifdef COMPAT 311 case 'p': 312 #endif /* COMPAT */ 313 case 'F': 314 looking = TW_PATH | TW_FILE; 315 break; 316 case 'g': 317 looking = TW_GRPNAME; 318 break; 319 case 'j': 320 looking = TW_JOB; 321 break; 322 case 'l': 323 looking = TW_LIMIT; 324 break; 325 case 'n': 326 looking = TW_NONE; 327 break; 328 case 's': 329 looking = TW_SHELLVAR; 330 break; 331 case 't': 332 looking = TW_TEXT; 333 break; 334 case 'T': 335 looking = TW_PATH | TW_TEXT; 336 break; 337 case 'v': 338 looking = TW_VARIABLE; 339 break; 340 case 'u': 341 looking = TW_USER; 342 break; 343 case 'x': 344 looking = TW_EXPLAIN; 345 break; 346 347 case '$': 348 *pat = res = Strsave(&act[1]); 349 (void) strip(res); 350 return(TW_VARLIST); 351 352 case '(': 353 *pat = res = Strsave(&act[1]); 354 if ((p = Strchr(res, ')')) != NULL) 355 *p = '\0'; 356 (void) strip(res); 357 return TW_WORDLIST; 358 359 case '`': 360 res = Strsave(act); 361 if ((p = Strchr(&res[1], '`')) != NULL) 362 *++p = '\0'; 363 364 if (didfds == 0) { 365 /* 366 * Make sure that we have some file descriptors to 367 * play with, so that the processes have at least 0, 1, 2 368 * open 369 */ 370 (void) dcopy(SHIN, 0); 371 (void) dcopy(SHOUT, 1); 372 (void) dcopy(SHDIAG, 2); 373 } 374 if ((p = globone(res, G_APPEND)) != NULL) { 375 xfree(res), res = NULL; 376 *pat = res = Strsave(p); 377 xfree(p); 378 return TW_WORDLIST; 379 } 380 return TW_ZERO; 381 382 default: 383 stderror(ERR_COMPCOM, short2str(act)); 384 return TW_ZERO; 385 } 386 387 switch (act[1] & ~QUOTE) { 388 case '\0': 389 return looking; 390 391 case ':': 392 *pat = res = Strsave(&act[2]); 393 (void) strip(res); 394 return looking; 395 396 default: 397 stderror(ERR_COMPCOM, short2str(act)); 398 return TW_ZERO; 399 } 400 } /* end tw_result */ 401 402 403 /* tw_dollar(): 404 * Expand $<n> args in buffer 405 */ 406 static const Char * 407 tw_dollar(const Char *str, Char **wl, size_t nwl, Char **result, Char sep, 408 const char *msg) 409 { 410 struct Strbuf buf = Strbuf_INIT; 411 Char *res; 412 const Char *sp; 413 414 for (sp = str; *sp && *sp != sep;) 415 if (sp[0] == '$' && sp[1] == ':' && Isdigit(sp[sp[2] == '-' ? 3 : 2])) { 416 int num, neg = 0; 417 sp += 2; 418 if (*sp == '-') { 419 neg = 1; 420 sp++; 421 } 422 for (num = *sp++ - '0'; Isdigit(*sp); num += 10 * num + *sp++ - '0') 423 continue; 424 if (neg) 425 num = nwl - num - 1; 426 if (num >= 0 && (size_t)num < nwl) 427 Strbuf_append(&buf, wl[num]); 428 } 429 else 430 Strbuf_append1(&buf, *sp++); 431 432 res = Strbuf_finish(&buf); 433 434 if (*sp++ == sep) { 435 *result = res; 436 return sp; 437 } 438 439 xfree(res); 440 /* Truncates data if WIDE_STRINGS */ 441 stderror(ERR_COMPMIS, (int)sep, msg, short2str(str)); 442 return --sp; 443 } /* end tw_dollar */ 444 445 446 /* tw_complete(): 447 * Return the appropriate completion for the command 448 * 449 * valid completion strings are: 450 * p/<range>/<completion>/[<suffix>/] positional 451 * c/<pattern>/<completion>/[<suffix>/] current word ignore pattern 452 * C/<pattern>/<completion>/[<suffix>/] current word with pattern 453 * n/<pattern>/<completion>/[<suffix>/] next word 454 * N/<pattern>/<completion>/[<suffix>/] next-next word 455 */ 456 int 457 tw_complete(const Char *line, Char **word, Char **pat, int looking, eChar *suf) 458 { 459 Char *buf, **vec, **wl; 460 static Char nomatch[2] = { (Char) ~0, 0x00 }; 461 const Char *ptr; 462 size_t wordno; 463 int n; 464 465 buf = Strsave(line); 466 cleanup_push(buf, xfree); 467 /* Single-character words, empty current word, terminating NULL */ 468 wl = xmalloc(((Strlen(line) + 1) / 2 + 2) * sizeof (*wl)); 469 cleanup_push(wl, xfree); 470 471 /* find the command */ 472 if ((wl[0] = tw_tok(buf)) == NULL || wl[0] == INVPTR) { 473 cleanup_until(buf); 474 return TW_ZERO; 475 } 476 477 /* 478 * look for hardwired command completions using a globbing 479 * search and for arguments using a normal search. 480 */ 481 if ((vec = tw_find(wl[0], &completions, (looking == TW_COMMAND))) 482 == NULL) { 483 cleanup_until(buf); 484 return looking; 485 } 486 487 /* tokenize the line one more time :-( */ 488 for (wordno = 1; (wl[wordno] = tw_tok(NULL)) != NULL && 489 wl[wordno] != INVPTR; wordno++) 490 continue; 491 492 if (wl[wordno] == INVPTR) { /* Found a meta character */ 493 cleanup_until(buf); 494 return TW_ZERO; /* de-activate completions */ 495 } 496 #ifdef TDEBUG 497 { 498 size_t i; 499 for (i = 0; i < wordno; i++) 500 xprintf("'%s' ", short2str(wl[i])); 501 xprintf("\n"); 502 } 503 #endif /* TDEBUG */ 504 505 /* if the current word is empty move the last word to the next */ 506 if (**word == '\0') { 507 wl[wordno] = *word; 508 wordno++; 509 } 510 wl[wordno] = NULL; 511 512 513 #ifdef TDEBUG 514 xprintf("\r\n"); 515 xprintf(" w#: %lu\n", (unsigned long)wordno); 516 xprintf("line: %s\n", short2str(line)); 517 xprintf(" cmd: %s\n", short2str(wl[0])); 518 xprintf("word: %s\n", short2str(*word)); 519 xprintf("last: %s\n", wordno >= 2 ? short2str(wl[wordno-2]) : "n/a"); 520 xprintf("this: %s\n", wordno >= 1 ? short2str(wl[wordno-1]) : "n/a"); 521 #endif /* TDEBUG */ 522 523 for (;vec != NULL && (ptr = vec[0]) != NULL; vec++) { 524 Char *ran, /* The pattern or range X/<range>/XXXX/ */ 525 *com, /* The completion X/XXXXX/<completion>/ */ 526 *pos = NULL; /* scratch pointer */ 527 int cmd, res; 528 Char sep; /* the command and separator characters */ 529 int exact; 530 531 if (ptr[0] == '\0') 532 continue; 533 534 #ifdef TDEBUG 535 xprintf("match %s\n", short2str(ptr)); 536 #endif /* TDEBUG */ 537 538 switch (cmd = ptr[0]) { 539 case 'N': 540 pos = (wordno < 3) ? nomatch : wl[wordno - 3]; 541 break; 542 case 'n': 543 pos = (wordno < 2) ? nomatch : wl[wordno - 2]; 544 break; 545 case 'c': 546 case 'C': 547 pos = (wordno < 1) ? nomatch : wl[wordno - 1]; 548 break; 549 case 'p': 550 break; 551 default: 552 stderror(ERR_COMPINV, CGETS(27, 1, "command"), cmd); 553 return TW_ZERO; 554 } 555 556 sep = ptr[1]; 557 if (!Ispunct(sep)) { 558 /* Truncates data if WIDE_STRINGS */ 559 stderror(ERR_COMPINV, CGETS(27, 2, "separator"), (int)sep); 560 return TW_ZERO; 561 } 562 563 ptr = tw_dollar(&ptr[2], wl, wordno, &ran, sep, 564 CGETS(27, 3, "pattern")); 565 cleanup_push(ran, xfree); 566 if (ran[0] == '\0') /* check for empty pattern (disallowed) */ 567 { 568 stderror(ERR_COMPINC, cmd == 'p' ? CGETS(27, 4, "range") : 569 CGETS(27, 3, "pattern"), ""); 570 return TW_ZERO; 571 } 572 573 ptr = tw_dollar(ptr, wl, wordno, &com, sep, 574 CGETS(27, 5, "completion")); 575 cleanup_push(com, xfree); 576 577 if (*ptr != '\0') { 578 if (*ptr == sep) 579 *suf = CHAR_ERR; 580 else 581 *suf = *ptr; 582 } 583 else 584 *suf = '\0'; 585 586 #ifdef TDEBUG 587 xprintf("command: %c\nseparator: %c\n", cmd, (int)sep); 588 xprintf("pattern: %s\n", short2str(ran)); 589 xprintf("completion: %s\n", short2str(com)); 590 xprintf("suffix: "); 591 switch (*suf) { 592 case 0: 593 xprintf("*auto suffix*\n"); 594 break; 595 case CHAR_ERR: 596 xprintf("*no suffix*\n"); 597 break; 598 default: 599 xprintf("%c\n", (int)*suf); 600 break; 601 } 602 #endif /* TDEBUG */ 603 604 exact = 0; 605 switch (cmd) { 606 case 'p': /* positional completion */ 607 #ifdef TDEBUG 608 xprintf("p: tw_pos(%s, %lu) = ", short2str(ran), 609 (unsigned long)wordno - 1); 610 xprintf("%d\n", tw_pos(ran, wordno - 1)); 611 #endif /* TDEBUG */ 612 if (!tw_pos(ran, wordno - 1)) { 613 cleanup_until(ran); 614 continue; 615 } 616 break; 617 618 case 'N': /* match with the next-next word */ 619 case 'n': /* match with the next word */ 620 exact = 1; 621 /*FALLTHROUGH*/ 622 case 'c': /* match with the current word */ 623 case 'C': 624 #ifdef TDEBUG 625 xprintf("%c: ", cmd); 626 #endif /* TDEBUG */ 627 if ((n = tw_match(pos, ran, exact)) < 0) { 628 cleanup_until(ran); 629 continue; 630 } 631 if (cmd == 'c') 632 *word += n; 633 break; 634 635 default: 636 abort(); /* Cannot happen */ 637 } 638 tsetenv(STRCOMMAND_LINE, line); 639 res = tw_result(com, pat); 640 Unsetenv(STRCOMMAND_LINE); 641 cleanup_until(buf); 642 return res; 643 } 644 cleanup_until(buf); 645 *suf = '\0'; 646 return TW_ZERO; 647 } /* end tw_complete */ 648