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