Merge from vendor branch BSDTAR:
[dragonfly.git] / contrib / tcsh / tw.comp.c
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 */