xref: /freebsd/contrib/tcsh/tw.comp.c (revision d8a0fe102c0cfdfcd5b818f850eff09d8536c9bc)
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