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