tw.comp.c revision 100616
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
35RCSID("$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 */
42struct varent completions;
43
44static int 	 	  tw_result	__P((Char *, Char **));
45static Char		**tw_find	__P((Char *, struct varent *, int));
46static Char 		 *tw_tok	__P((Char *));
47static bool	 	  tw_pos	__P((Char *, int));
48static void	  	  tw_pr		__P((Char **));
49static int	  	  tw_match	__P((Char *, Char *));
50static void	 	  tw_prlist	__P((struct varent *));
51static 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*/
58void
59docomplete(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*/
85void
86douncomplete(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 */
98static void
99tw_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;
114x:
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 */
138static void
139tw_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 */
165static Char **
166tw_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 */
193static bool
194tw_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 */
224static Char *
225tw_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 */
254static int
255tw_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 */
273static int
274tw_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 */
410static Char *
411tw_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 */
462int
463tw_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