complete.c revision 1.11 1 /* $NetBSD: complete.c,v 1.11 2006/11/28 18:45:32 christos Exp $ */
2
3 /*-
4 * Copyright (c) 1997-2000,2005,2006 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Luke Mewburn.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the NetBSD
21 * Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 * contributors may be used to endorse or promote products derived
24 * from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39 /*
40 * Most of this is derived or copied from src/usr.bin/ftp/complete.c (1.41).
41 */
42
43 #ifdef USE_EDITLINE
44 #undef NO_EDITCOMPLETE
45
46 #include <sys/cdefs.h>
47 #ifndef lint
48 __RCSID("$NetBSD: complete.c,v 1.11 2006/11/28 18:45:32 christos Exp $");
49 #endif /* not lint */
50
51 /*
52 * FTP user program - command and file completion routines
53 */
54
55 #include <assert.h>
56 #include <ctype.h>
57 #include <err.h>
58 #include <dirent.h>
59 #include <glob.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <stringlist.h>
64 #include <util.h>
65
66 #include <sys/param.h>
67 #include <sys/stat.h>
68
69 #include "rcv.h" /* includes "glob.h" */
70 #include "extern.h"
71 #include "complete.h"
72 #ifdef MIME_SUPPORT
73 #include "mime.h"
74 #endif
75 #ifdef THREAD_SUPPORT
76 #include "thread.h"
77 #endif
78
79 #define BELL 0x7
80
81 /*
82 * Global variables
83 */
84 static int doglob = 1; /* glob local file names */
85
86 #define ttyout stdout
87 #define ttywidth screenwidth /* in "glob.h" */
88 #define ttyheight screenheight /* in "glob.h" */
89
90 /************************************************************************/
91 /* from src/usr.bin/ftp/utils.h (1.135) - begin */
92
93 /*
94 * List words in stringlist, vertically arranged
95 */
96 static void
97 list_vertical(StringList *sl)
98 {
99 int i, j, k;
100 int columns, lines;
101 char *p;
102 size_t w, width;
103
104 width = 0;
105
106 for (i = 0; i < sl->sl_cur; i++) {
107 w = strlen(sl->sl_str[i]);
108 if (w > width)
109 width = w;
110 }
111 width = (width + 8) &~ 7;
112
113 columns = ttywidth / width;
114 if (columns == 0)
115 columns = 1;
116 lines = (sl->sl_cur + columns - 1) / columns;
117 k = 0;
118 for (i = 0; i < lines; i++) {
119 for (j = 0; j < columns; j++) {
120 p = sl->sl_str[j * lines + i];
121 if (p)
122 (void)fputs(p, ttyout);
123 if (j * lines + i + lines >= sl->sl_cur) {
124 (void)putc('\n', ttyout);
125 break;
126 }
127 if (p) {
128 w = strlen(p);
129 while (w < width) {
130 w = (w + 8) &~ 7;
131 (void)putc('\t', ttyout);
132 }
133 }
134 }
135 if (ttyheight > 2 && ++k == ttyheight - 2) {
136 int ch;
137 k = 0;
138 (void)fputs("--more--", ttyout);
139 while ((ch = getchar()) != EOF && ch != ' ' && ch != 'q')
140 (void)putc(BELL, ttyout);
141 (void)fputs("\r \r", ttyout);
142 if (ch == 'q')
143 break;
144 }
145 }
146 }
147
148 /*
149 * Copy characters from src into dst, \ quoting characters that require it
150 */
151 static void
152 ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
153 {
154 int di, si;
155
156 for (di = si = 0;
157 src[si] != '\0' && di < dstlen && si < srclen;
158 di++, si++) {
159 switch (src[si]) {
160 case '\\':
161 case ' ':
162 case '\t':
163 case '\r':
164 case '\n':
165 case '"':
166 dst[di++] = '\\';
167 if (di >= dstlen)
168 break;
169 /* FALLTHROUGH */
170 default:
171 dst[di] = src[si];
172 }
173 }
174 dst[di] = '\0';
175 }
176
177 /*
178 * sl_init() with inbuilt error checking
179 */
180 static StringList *
181 mail_sl_init(void)
182 {
183 StringList *p;
184
185 p = sl_init();
186 if (p == NULL)
187 err(1, "Unable to allocate memory for stringlist");
188 return p;
189 }
190
191
192 /*
193 * sl_add() with inbuilt error checking
194 */
195 static void
196 mail_sl_add(StringList *sl, char *i)
197 {
198
199 if (sl_add(sl, i) == -1)
200 err(1, "Unable to add `%s' to stringlist", i);
201 }
202
203
204 /*
205 * Glob a local file name specification with the expectation of a single
206 * return value. Can't control multiple values being expanded from the
207 * expression, we return only the first.
208 * Returns NULL on error, or a pointer to a buffer containing the filename
209 * that's the caller's responsiblity to free(3) when finished with.
210 */
211 static char *
212 globulize(const char *pattern)
213 {
214 glob_t gl;
215 int flags;
216 char *p;
217
218 if (!doglob)
219 return estrdup(pattern);
220
221 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
222 (void)memset(&gl, 0, sizeof(gl));
223 if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) {
224 warnx("%s: not found", pattern);
225 globfree(&gl);
226 return NULL;
227 }
228 p = estrdup(gl.gl_pathv[0]);
229 globfree(&gl);
230 return p;
231 }
232
233 /* from src/usr.bin/ftp/utils.h (1.135) - end */
234 /************************************************************************/
235
236 static int
237 comparstr(const void *a, const void *b)
238 {
239 return strcmp(*(const char * const *)a, *(const char * const *)b);
240 }
241
242 /*
243 * Determine if complete is ambiguous. If unique, insert.
244 * If no choices, error. If unambiguous prefix, insert that.
245 * Otherwise, list choices. words is assumed to be filtered
246 * to only contain possible choices.
247 * Args:
248 * word word which started the match
249 * dolist list by default
250 * words stringlist containing possible matches
251 * Returns a result as per el_set(EL_ADDFN, ...)
252 */
253 static unsigned char
254 complete_ambiguous(EditLine *el, char *word, int dolist, StringList *words)
255 {
256 char insertstr[MAXPATHLEN];
257 char *lastmatch, *p;
258 int i, j;
259 size_t matchlen, wordlen;
260
261 wordlen = strlen(word);
262 if (words->sl_cur == 0)
263 return CC_ERROR; /* no choices available */
264
265 if (words->sl_cur == 1) { /* only once choice available */
266 p = words->sl_str[0] + wordlen;
267 if (*p == '\0') /* at end of word? */
268 return CC_REFRESH;
269 ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
270 if (el_insertstr(el, insertstr) == -1)
271 return CC_ERROR;
272 else
273 return CC_REFRESH;
274 }
275
276 if (!dolist) {
277 matchlen = 0;
278 lastmatch = words->sl_str[0];
279 matchlen = strlen(lastmatch);
280 for (i = 1; i < words->sl_cur; i++) {
281 for (j = wordlen; j < strlen(words->sl_str[i]); j++)
282 if (lastmatch[j] != words->sl_str[i][j])
283 break;
284 if (j < matchlen)
285 matchlen = j;
286 }
287 if (matchlen >= wordlen) {
288 ftpvis(insertstr, sizeof(insertstr),
289 lastmatch + wordlen, matchlen - wordlen);
290 if (el_insertstr(el, insertstr) == -1)
291 return CC_ERROR;
292 else
293 return CC_REFRESH_BEEP;
294 }
295 }
296
297 (void)putc('\n', ttyout);
298 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
299
300 list_vertical(words);
301 return CC_REDISPLAY;
302 }
303
304 /*
305 * Complete a mail command.
306 */
307 static unsigned char
308 complete_command(EditLine *el, char *word, int dolist)
309 {
310 const struct cmd *c;
311 StringList *words;
312 size_t wordlen;
313 unsigned char rv;
314
315 words = mail_sl_init();
316 wordlen = strlen(word);
317
318 for (c = cmdtab; c->c_name != NULL; c++) {
319 if (wordlen > strlen(c->c_name))
320 continue;
321 if (strncmp(word, c->c_name, wordlen) == 0)
322 mail_sl_add(words, __UNCONST(c->c_name));
323 }
324
325 rv = complete_ambiguous(el, word, dolist, words);
326 if (rv == CC_REFRESH) {
327 if (el_insertstr(el, " ") == -1)
328 rv = CC_ERROR;
329 }
330 sl_free(words, 0);
331 return rv;
332 }
333
334 /*
335 * Complete a local filename.
336 */
337 static unsigned char
338 complete_filename(EditLine *el, char *word, int dolist)
339 {
340 StringList *words;
341 char dir[MAXPATHLEN];
342 char *fname;
343 DIR *dd;
344 struct dirent *dp;
345 unsigned char rv;
346 size_t len;
347
348 if ((fname = strrchr(word, '/')) == NULL) {
349 dir[0] = '.';
350 dir[1] = '\0';
351 fname = word;
352 } else {
353 if (fname == word) {
354 dir[0] = '/';
355 dir[1] = '\0';
356 } else {
357 len = fname - word + 1;
358 (void)estrlcpy(dir, word, sizeof(dir));
359 dir[len] = '\0';
360 }
361 fname++;
362 }
363 if (dir[0] == '~') {
364 char *p;
365
366 if ((p = globulize(dir)) == NULL)
367 return CC_ERROR;
368 (void)estrlcpy(dir, p, sizeof(dir));
369 free(p);
370 }
371
372 if ((dd = opendir(dir)) == NULL)
373 return CC_ERROR;
374
375 words = mail_sl_init();
376 len = strlen(fname);
377
378 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
379 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
380 continue;
381
382 #if defined(DIRENT_MISSING_D_NAMLEN)
383 if (len > strlen(dp->d_name))
384 continue;
385 #else
386 if (len > dp->d_namlen)
387 continue;
388 #endif
389 if (strncmp(fname, dp->d_name, len) == 0) {
390 char *tcp;
391
392 tcp = estrdup(dp->d_name);
393 mail_sl_add(words, tcp);
394 }
395 }
396 (void)closedir(dd);
397
398 rv = complete_ambiguous(el, fname, dolist, words);
399 if (rv == CC_REFRESH) {
400 struct stat sb;
401 char path[MAXPATHLEN];
402
403 (void)estrlcpy(path, dir, sizeof(path));
404 (void)estrlcat(path, "/", sizeof(path));
405 (void)estrlcat(path, words->sl_str[0], sizeof(path));
406
407 if (stat(path, &sb) >= 0) {
408 char suffix[2] = " ";
409
410 if (S_ISDIR(sb.st_mode))
411 suffix[0] = '/';
412 if (el_insertstr(el, suffix) == -1)
413 rv = CC_ERROR;
414 }
415 }
416 sl_free(words, 1);
417 return rv;
418 }
419
420 static int
421 find_execs(char *word, char *path, StringList *list)
422 {
423 char *sep;
424 char *dir=path;
425 DIR *dd;
426 struct dirent *dp;
427 size_t len = strlen(word);
428 uid_t uid = getuid();
429 gid_t gid = getgid();
430
431 for (sep = dir; sep; dir = sep + 1) {
432 if ((sep=strchr(dir, ':')) != NULL) {
433 *sep=0;
434 }
435
436 if ((dd = opendir(dir)) == NULL) {
437 perror("dir");
438 return -1;
439 }
440
441 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
442
443 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
444 continue;
445
446 #if defined(DIRENT_MISSING_D_NAMLEN)
447 if (len > strlen(dp->d_name))
448 continue;
449 #else
450 if (len > dp->d_namlen)
451 continue;
452 #endif
453
454 if (strncmp(word, dp->d_name, len) == 0) {
455 struct stat sb;
456 char pathname[ MAXPATHLEN ];
457 unsigned mask;
458
459 (void)snprintf(pathname, sizeof(pathname),
460 "%s/%s", dir, dp->d_name);
461 if (stat(pathname, &sb) != 0) {
462 perror(pathname);
463 continue;
464 }
465
466 mask = 0001;
467 if (sb.st_uid == uid) mask |= 0100;
468 if (sb.st_gid == gid) mask |= 0010;
469
470 if ((sb.st_mode & mask) != 0) {
471 char *tcp;
472 tcp = estrdup(dp->d_name);
473 mail_sl_add(list, tcp);
474 }
475 }
476
477 }
478
479 (void)closedir(dd);
480 }
481
482 return 0;
483 }
484
485
486 /*
487 * Complete a local executable
488 */
489 static unsigned char
490 complete_executable(EditLine *el, char *word, int dolist)
491 {
492 StringList *words;
493 char dir[ MAXPATHLEN ];
494 char *fname;
495 unsigned char rv;
496 size_t len;
497 int error;
498
499 if ((fname = strrchr(word, '/')) == NULL) {
500 dir[0] = '\0'; /* walk the path */
501 fname = word;
502 } else {
503 if (fname == word) {
504 dir[0] = '/';
505 dir[1] = '\0';
506 } else {
507 len = fname - word;
508 (void)strncpy(dir, word, len);
509 dir[fname - word] = '\0';
510 }
511 fname++;
512 }
513
514 words = sl_init();
515
516 if (*dir == '\0') { /* walk path */
517 char *env;
518 char *path;
519 env = getenv("PATH");
520 len = strlen(env);
521 path = salloc(len + 1);
522 (void)strcpy(path, env);
523 error = find_execs(word, path, words);
524 }
525 else { /* check specified dir only */
526 error = find_execs(word, dir, words);
527 }
528 if (error != 0)
529 return CC_ERROR;
530
531 rv = complete_ambiguous(el, fname, dolist, words);
532 if (rv == CC_REFRESH) {
533 if (el_insertstr(el, " ") == -1)
534 rv = CC_ERROR;
535 }
536 sl_free(words, 1);
537
538 return rv;
539 }
540
541
542 static unsigned char
543 complete_set(EditLine *el, char *word, int dolist)
544 {
545 struct var *vp;
546 const char **ap;
547 const char **p;
548 int h;
549 int s;
550 size_t len = strlen(word);
551 StringList *words;
552 unsigned char rv;
553
554 words = sl_init();
555
556 /* allocate space for variables table */
557 s = 1;
558 for (h = 0; h < HSHSIZE; h++)
559 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
560 s++;
561 ap = salloc(s * sizeof *ap);
562
563 /* save the pointers */
564 for (h = 0, p = ap; h < HSHSIZE; h++)
565 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
566 *p++ = vp->v_name;
567 *p = NULL;
568 sort(ap);
569 for (p = ap; *p != NULL; p++)
570 if (len == 0 || strncmp(*p, word, len) == 0)
571 mail_sl_add(words, estrdup(*p));
572
573 rv = complete_ambiguous(el, word, dolist, words);
574
575 sl_free(words, 1);
576
577 return rv;
578 }
579
580
581 static unsigned char
582 complete_alias(EditLine *el, char *word, int dolist)
583 {
584 struct grouphead *gh;
585 const char **ap;
586 const char **p;
587 int h;
588 int s;
589 size_t len = strlen(word);
590 StringList *words;
591 unsigned char rv;
592
593 words = sl_init();
594
595 /* allocate space for alias table */
596 s = 1;
597 for (h = 0; h < HSHSIZE; h++)
598 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
599 s++;
600 ap = salloc(s * sizeof(*ap));
601
602 /* save pointers */
603 p = ap;
604 for (h = 0; h < HSHSIZE; h++)
605 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
606 *p++ = gh->g_name;
607
608 *p = NULL;
609
610 sort(ap);
611 for (p = ap; *p != NULL; p++)
612 if (len == 0 || strncmp(*p, word, len) == 0)
613 mail_sl_add(words, estrdup(*p));
614
615 rv = complete_ambiguous(el, word, dolist, words);
616 if (rv == CC_REFRESH) {
617 if (el_insertstr(el, " ") == -1)
618 rv = CC_ERROR;
619 }
620 sl_free(words, 1);
621 return rv;
622 }
623
624
625 static unsigned char
626 complete_smopts(EditLine *el, char *word, int dolist)
627 {
628 struct grouphead *gh;
629 struct smopts_s *sp;
630 const char **ap;
631 const char **p;
632 int h;
633 int s1;
634 int s2;
635 size_t len;
636 StringList *words;
637 unsigned char rv;
638
639 len = strlen(word);
640 words = sl_init();
641
642 /* count the entries in the smoptstbl and groups (alias) tables */
643 s1 = 1;
644 s2 = 1;
645 for (h = 0; h < HSHSIZE; h++) {
646 for (sp = smoptstbl[h]; sp != NULL; sp = sp->s_link)
647 s1++;
648 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
649 s2++;
650 }
651
652 /* allocate sufficient space for the pointers */
653 ap = salloc(MAX(s1, s2) * sizeof *ap);
654
655 /*
656 * First do the smoptstbl pointers. (case _insensitive_)
657 */
658 p = ap;
659 for (h = 0; h < HSHSIZE; h++)
660 for (sp = smoptstbl[h]; sp != NULL; sp = sp->s_link)
661 *p++ = sp->s_name;
662 *p = NULL;
663 sort(ap);
664 for (p = ap; *p != NULL; p++)
665 if (len == 0 || strncasecmp(*p, word, len) == 0)
666 mail_sl_add(words, estrdup(*p));
667
668 /*
669 * Now do the groups (alias) pointers. (case sensitive)
670 */
671 p = ap;
672 for (h = 0; h < HSHSIZE; h++)
673 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
674 *p++ = gh->g_name;
675 *p = NULL;
676 sort(ap);
677 for (p = ap; *p != NULL; p++)
678 if (len == 0 || strncmp(*p, word, len) == 0)
679 mail_sl_add(words, estrdup(*p));
680
681 rv = complete_ambiguous(el, word, dolist, words);
682
683 sl_free(words, 1);
684
685 return rv;
686 }
687
688
689 #ifdef THREAD_SUPPORT
690 static unsigned char
691 complete_thread_key(EditLine *el, char *word, int dolist)
692 {
693 const char **ap;
694 const char **p;
695 const char *name;
696 size_t len;
697 StringList *words;
698 unsigned char rv;
699 int cnt;
700 const void *cookie;
701
702 len = strlen(word);
703 words = sl_init();
704
705 /* count the entries in the table */
706 /* XXX - have a function return this rather than counting? */
707 cnt = 1; /* count the NULL terminator */
708 cookie = NULL;
709 while (thread_next_key_name(&cookie) != NULL)
710 cnt++;
711
712 /* allocate sufficient space for the pointers */
713 ap = salloc(cnt * sizeof *ap);
714
715 /* load the array */
716 p = ap;
717 cookie = NULL;
718 while ((name = thread_next_key_name(&cookie)) != NULL)
719 *p++ = name;
720 *p = NULL;
721 sort(ap);
722 for (p = ap; *p != NULL; p++)
723 if (len == 0 || strncmp(*p, word, len) == 0)
724 mail_sl_add(words, estrdup(*p));
725
726 rv = complete_ambiguous(el, word, dolist, words);
727
728 sl_free(words, 1);
729
730 return rv;
731 }
732 #endif /* THREAD_SUPPORT */
733
734 /* from /usr/src/usr.bin/ftp/main.c(1.101) - end */
735 /************************************************************************/
736
737 /* Some people like to bind file completion to CTRL-D. In emacs mode,
738 * CTRL-D is also used to delete the current character, we have to
739 * special case this situation.
740 */
741 #define EMACS_CTRL_D_BINDING_HACK
742
743 #ifdef EMACS_CTRL_D_BINDING_HACK
744 static int
745 is_emacs_mode(EditLine *el)
746 {
747 char *mode;
748 if (el_get(el, EL_EDITOR, &mode) == -1)
749 return 0;
750 return equal(mode, "emacs");
751 }
752
753 static int
754 emacs_ctrl_d(EditLine *el, const LineInfo *lf, int ch)
755 {
756 static char delunder[3] = { CTRL('f'), CTRL('h'), '\0' };
757 if (ch == CTRL('d') && is_emacs_mode(el)) { /* CTRL-D is special */
758 if (lf->buffer == lf->lastchar)
759 return CC_EOF;
760 if (lf->cursor != lf->lastchar) { /* delete without using ^D */
761 el_push(el, delunder); /* ^F^H */
762 return CC_NORM;
763 }
764 }
765 return -1;
766 }
767 #endif /* EMACS_CTRL_D_BINDING_HACK */
768
769 /*
770 * Check if this is the second request made for this line indicating
771 * the need to list all the completion possibilities.
772 */
773 static int
774 get_dolist(const LineInfo *lf)
775 {
776 static char last_line[LINESIZE];
777 static char *last_cursor_pos;
778 char *cursor_pos;
779 int dolist;
780 size_t len;
781
782 len = lf->lastchar - lf->buffer;
783 if (len >= sizeof(last_line) - 1)
784 return -1;
785
786 cursor_pos = last_line + (lf->cursor - lf->buffer);
787 dolist =
788 cursor_pos == last_cursor_pos &&
789 strncmp(last_line, lf->buffer, len) == 0;
790
791 (void)strlcpy(last_line, lf->buffer, len + 1);
792 last_cursor_pos = cursor_pos;
793
794 return dolist;
795 }
796
797 /*
798 * Take the full line (lf) including the command and split it into a
799 * sub-line (returned) and a completion context (cmplarray).
800 */
801 static LineInfo *
802 split_line(const char **cmplarray, const LineInfo *lf)
803 {
804 static LineInfo li;
805 const struct cmd *c;
806 char *cmdname;
807 char line[LINESIZE];
808 char *cp;
809 size_t len;
810
811 len = lf->cursor - lf->buffer;
812 if (len + 1 > sizeof(line))
813 return NULL;
814
815 (void)strlcpy(line, lf->buffer, len + 1);
816
817 li.cursor = line + len;
818 li.lastchar = line + len;
819
820 cp = skip_blank(line);
821 cmdname = get_cmdname(cp);
822 cp += strlen(cmdname);
823
824 if (cp == li.cursor) {
825 *cmplarray = "c";
826 li.buffer = cmdname;
827 return &li;
828 }
829
830 c = lex(cmdname);
831 if (c == NULL)
832 return NULL;
833
834 *cmplarray = c->c_complete;
835 if (c->c_pipe) {
836 char *cp2;
837 if ((cp2 = shellpr(cp)) != NULL) {
838 cp = cp2;
839 # define XX(a) a + (a[1] == '>' ? 2 : 1)
840 while ((cp2 = shellpr(XX(cp))) != NULL)
841 cp = cp2;
842
843 if (*cp == '|') {
844 *cmplarray = "xF";
845 cp = skip_blank(cp + 1);
846 }
847 else {
848 assert(*cp == '>');
849 cp = skip_blank(XX(cp));
850 *cmplarray = "f";
851 }
852 # undef XX
853 }
854 }
855 li.buffer = cp;
856 return &li;
857 }
858
859 /*
860 * Split a sub-line and a completion context into a word and a
861 * completion type. Use the editline tokenizer to handle the quoting
862 * and splitting.
863 */
864 static char *
865 split_word(int *cmpltype, const char *cmplarray, LineInfo *li)
866 {
867 static Tokenizer *t = NULL;
868 const char **argv;
869 char *word;
870 int argc;
871 int cursorc;
872 int cursoro;
873 int arraylen;
874
875 if (t != NULL)
876 tok_reset(t);
877 else {
878 if ((t = tok_init(NULL)) == NULL)
879 err(EXIT_FAILURE, "tok_init");
880 }
881 if (tok_line(t, li, &argc, &argv, &cursorc, &cursoro) == -1)
882 err(EXIT_FAILURE, "tok_line");
883
884 if (cursorc >= argc)
885 word = __UNCONST("");
886 else {
887 word = salloc((size_t)cursoro + 1);
888 (void)strlcpy(word, argv[cursorc], (size_t)cursoro + 1);
889 }
890
891 /* check for 'continuation' completes (which are uppercase) */
892 arraylen = strlen(cmplarray);
893 if (cursorc >= arraylen &&
894 arraylen > 0 &&
895 isupper((unsigned char)cmplarray[arraylen - 1]))
896 cursorc = arraylen - 1;
897
898 if (cursorc >= arraylen)
899 return NULL;
900
901 *cmpltype = cmplarray[cursorc];
902 return word;
903 }
904
905 /*
906 * A generic complete routine for the mail command line.
907 */
908 static unsigned char
909 mail_complete(EditLine *el, int ch)
910 {
911 LineInfo *li;
912 const LineInfo *lf;
913 const char *cmplarray;
914 int dolist;
915 int cmpltype;
916 char *word;
917
918 lf = el_line(el);
919
920 #ifdef EMACS_CTRL_D_BINDING_HACK
921 {
922 int cc_ret;
923 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
924 return cc_ret;
925 }
926 #endif /* EMACS_CTRL_D_BINDING_HACK */
927
928 if ((dolist = get_dolist(lf)) == -1)
929 return CC_ERROR;
930
931 if ((li = split_line(&cmplarray, lf)) == NULL)
932 return CC_ERROR;
933
934 if ((word = split_word(&cmpltype, cmplarray, li)) == NULL)
935 return CC_ERROR;
936
937 switch (cmpltype) {
938 case 'a': /* alias complete */
939 case 'A':
940 return complete_alias(el, word, dolist);
941
942 case 'c': /* command complete */
943 case 'C':
944 return complete_command(el, word, dolist);
945
946 case 'f': /* filename complete */
947 case 'F':
948 return complete_filename(el, word, dolist);
949
950 case 'm':
951 case 'M':
952 return complete_smopts(el, word, dolist);
953
954 case 'n': /* no complete */
955 case 'N': /* no complete */
956 return CC_ERROR;
957
958 case 's':
959 case 'S':
960 return complete_set(el, word, dolist);
961 #ifdef THREAD_SUPPORT
962 case 't':
963 case 'T':
964 return complete_thread_key(el, word, dolist);
965 #endif
966 case 'x': /* executable complete */
967 case 'X':
968 return complete_executable(el, word, dolist);
969
970 default:
971 warnx("unknown complete type `%c'", cmpltype);
972 #if 0
973 assert(/*CONSTCOND*/0);
974 #endif
975 return CC_ERROR;
976 }
977 /* NOTREACHED */
978 }
979
980
981 /*
982 * A generic file completion routine.
983 */
984 static unsigned char
985 file_complete(EditLine *el, int ch)
986 {
987 static char word[LINESIZE];
988 const LineInfo *lf;
989 size_t word_len;
990 int dolist;
991
992 lf = el_line(el);
993
994 #ifdef EMACS_CTRL_D_BINDING_HACK
995 {
996 int cc_ret;
997 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
998 return cc_ret;
999 }
1000 #endif /* EMACS_CTRL_D_BINDING_HACK */
1001
1002 word_len = lf->cursor - lf->buffer;
1003 if (word_len + 1 > sizeof(word))
1004 return CC_ERROR;
1005
1006 (void)strlcpy(word, lf->buffer, word_len + 1); /* do not use estrlcpy here! */
1007
1008 if ((dolist = get_dolist(lf)) == -1)
1009 return CC_ERROR;
1010
1011 return complete_filename(el, word, dolist);
1012 }
1013
1014
1015 #ifdef MIME_SUPPORT
1016 /*
1017 * Complete mime_transfer_encoding type.
1018 */
1019 static unsigned char
1020 mime_enc_complete(EditLine *el, int ch)
1021 {
1022 static char word[LINESIZE];
1023 StringList *words;
1024 unsigned char rv;
1025 const LineInfo *lf;
1026 size_t word_len;
1027 int dolist;
1028
1029 lf = el_line(el);
1030
1031 #ifdef EMACS_CTRL_D_BINDING_HACK
1032 {
1033 int cc_ret;
1034 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
1035 return cc_ret;
1036 }
1037 #endif /* EMACS_CTRL_D_BINDING_HACK */
1038
1039 word_len = lf->cursor - lf->buffer;
1040 if (word_len >= sizeof(word) - 1)
1041 return CC_ERROR;
1042
1043 words = mail_sl_init();
1044 {
1045 const char *ename;
1046 const void *cookie;
1047 cookie = NULL;
1048 for (ename = mime_next_encoding_name(&cookie);
1049 ename;
1050 ename = mime_next_encoding_name(&cookie))
1051 if (word_len == 0 ||
1052 strncmp(lf->buffer, ename, word_len) == 0) {
1053 char *cp;
1054 cp = estrdup(ename);
1055 mail_sl_add(words, cp);
1056 }
1057 }
1058 (void)strlcpy(word, lf->buffer, word_len + 1);
1059
1060 if ((dolist = get_dolist(lf)) == -1)
1061 return CC_ERROR;
1062
1063 rv = complete_ambiguous(el, word, dolist, words);
1064
1065 sl_free(words, 1);
1066 return rv;
1067 }
1068 #endif /* MIME_SUPPORT */
1069
1070
1071 /*************************************************************************
1072 * Our public interface to el_gets():
1073 *
1074 * init_editline()
1075 * Initializes of all editline and completion data strutures.
1076 *
1077 * my_gets()
1078 * Returns the next line of input as a NULL termnated string without
1079 * the trailing newline.
1080 *
1081 * my_getline()
1082 * Same as my_gets(), but strips leading and trailing whitespace
1083 * and returns an empty line if it gets a SIGINT.
1084 */
1085
1086 static const char *el_prompt;
1087
1088 /*ARGSUSED*/
1089 static const char *
1090 show_prompt(EditLine *e __unused)
1091 {
1092 return el_prompt;
1093 }
1094
1095 PUBLIC char *
1096 my_gets(el_mode_t *em, const char *prompt, char *string)
1097 {
1098 int cnt;
1099 size_t len;
1100 const char *buf;
1101 HistEvent ev;
1102 static char line[LINE_MAX];
1103
1104 el_prompt = prompt;
1105
1106 if (string)
1107 el_push(em->el, string);
1108
1109 buf = el_gets(em->el, &cnt);
1110
1111 if (buf == NULL || cnt <= 0) {
1112 if (cnt == 0)
1113 (void)putc('\n', stdout);
1114 return NULL;
1115 }
1116
1117 if (buf[cnt - 1] == '\n')
1118 cnt--; /* trash the trailing LF */
1119 len = MIN(sizeof(line) - 1, cnt);
1120 (void)memcpy(line, buf, len);
1121 line[cnt] = '\0';
1122
1123 /* enter non-empty lines into history */
1124 if (em->hist) {
1125 const char *p;
1126 p = skip_blank(line);
1127 if (*p && history(em->hist, &ev, H_ENTER, line) == 0)
1128 (void)printf("Failed history entry: %s", line);
1129 }
1130 return line;
1131 }
1132
1133 #ifdef MIME_SUPPORT
1134 /* XXX - do we really want this here? */
1135
1136 static jmp_buf intjmp;
1137 /*ARGSUSED*/
1138 static void
1139 sigint(int signum __unused)
1140 {
1141 siglongjmp(intjmp, 1);
1142 }
1143
1144 PUBLIC char *
1145 my_getline(el_mode_t *em, const char *prompt, const char *str)
1146 {
1147 sig_t saveint;
1148 char *cp;
1149 char *line;
1150
1151 saveint = signal(SIGINT, sigint);
1152 if (sigsetjmp(intjmp, 1)) {
1153 (void)signal(SIGINT, saveint);
1154 (void)putc('\n', stdout);
1155 return __UNCONST("");
1156 }
1157
1158 line = my_gets(em, prompt, __UNCONST(str));
1159 /* LINTED */
1160 line = line ? savestr(line) : __UNCONST("");
1161
1162 (void)signal(SIGINT, saveint);
1163
1164 /* strip trailing white space */
1165 for (cp = line + strlen(line) - 1;
1166 cp >= line && isblank((unsigned char)*cp); cp--)
1167 *cp = '\0';
1168
1169 /* skip leading white space */
1170 cp = skip_blank(line);
1171
1172 return cp;
1173 }
1174 #endif /* MIME_SUPPORT */
1175
1176 static el_mode_t
1177 init_el_mode(
1178 const char *el_editor,
1179 unsigned char (*completer)(EditLine *, int),
1180 struct name *keys,
1181 int history_size)
1182 {
1183 el_mode_t em;
1184 (void)memset(&em, 0, sizeof(em));
1185
1186 em.el = el_init(getprogname(), stdin, stdout, stderr);
1187
1188 (void)el_set(em.el, EL_PROMPT, show_prompt);
1189 (void)el_set(em.el, EL_SIGNAL, SIGHUP);
1190
1191 if (el_editor)
1192 (void)el_set(em.el, EL_EDITOR, el_editor);
1193
1194 if (completer) {
1195 struct name *np;
1196 (void)el_set(em.el, EL_ADDFN, "mail-complete",
1197 "Context sensitive argument completion", completer);
1198 for (np = keys; np; np = np->n_flink)
1199 (void)el_set(em.el, EL_BIND, np->n_name,
1200 "mail-complete", NULL);
1201 }
1202
1203 if (history_size) {
1204 HistEvent ev;
1205 em.hist = history_init();
1206 if (history(em.hist, &ev, H_SETSIZE, history_size))
1207 (void)printf("history: %s\n", ev.str);
1208 (void)el_set(em.el, EL_HIST, history, em.hist);
1209 }
1210
1211 (void)el_source(em.el, NULL); /* read ~/.editrc */
1212
1213 return em;
1214 }
1215
1216
1217 struct el_modes_s elm = {
1218 .command = { .el = NULL, .hist = NULL, },
1219 .string = { .el = NULL, .hist = NULL, },
1220 .filec = { .el = NULL, .hist = NULL, },
1221 #ifdef MIME_SUPPORT
1222 .mime_enc = { .el = NULL, .hist = NULL, },
1223 #endif
1224 };
1225
1226 PUBLIC void
1227 init_editline(void)
1228 {
1229 const char *mode;
1230 int hist_size;
1231 struct name *keys;
1232 char *cp;
1233
1234 mode = value(ENAME_EL_EDITOR);
1235
1236 cp = value(ENAME_EL_HISTORY_SIZE);
1237 hist_size = cp ? atoi(cp) : 0;
1238
1239 cp = value(ENAME_EL_COMPLETION_KEYS);
1240 keys = cp && *cp ? lexpand(cp, 0) : NULL;
1241
1242 elm.command = init_el_mode(mode, mail_complete, keys, hist_size);
1243 elm.filec = init_el_mode(mode, file_complete, keys, 0);
1244 elm.string = init_el_mode(mode, NULL, NULL, 0);
1245 #ifdef MIME_SUPPORT
1246 elm.mime_enc = init_el_mode(mode, mime_enc_complete, keys, 0);
1247 #endif
1248 return;
1249 }
1250
1251 #endif /* USE_EDITLINE */
1252