No empty .Rs/.Re
[netbsd-mini2440.git] / usr.bin / mail / complete.c
blob6ee3ebdfc96ce1eba6eb3934ef20868a92c7f6e1
1 /* $NetBSD: complete.c,v 1.18 2009/02/11 19:22:22 christos Exp $ */
3 /*-
4 * Copyright (c) 1997-2000,2005,2006 The NetBSD Foundation, Inc.
5 * All rights reserved.
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Luke Mewburn.
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.
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
33 * Most of this is derived or copied from src/usr.bin/ftp/complete.c (1.41).
36 #ifdef USE_EDITLINE
38 #include <sys/cdefs.h>
39 #ifndef lint
40 __RCSID("$NetBSD: complete.c,v 1.18 2009/02/11 19:22:22 christos Exp $");
41 #endif /* not lint */
44 * FTP user program - command and file completion routines
47 #include <assert.h>
48 #include <ctype.h>
49 #include <err.h>
50 #include <dirent.h>
51 #include <glob.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <stringlist.h>
56 #include <termcap.h>
57 #include <util.h>
59 #include <sys/param.h>
60 #include <sys/stat.h>
62 #include "rcv.h" /* includes "glob.h" */
63 #include "extern.h"
64 #include "complete.h"
65 #ifdef MIME_SUPPORT
66 #include "mime.h"
67 #endif
68 #include "sig.h"
69 #ifdef THREAD_SUPPORT
70 #include "thread.h"
71 #endif
73 #define BELL 0x7
76 * Global variables
78 static int doglob = 1; /* glob local file names */
80 #define ttyout stdout
81 #define ttywidth screenwidth /* in "glob.h" */
82 #define ttyheight screenheight /* in "glob.h" */
84 /************************************************************************/
85 /* from src/usr.bin/ftp/utils.h (1.135) - begin */
88 * List words in stringlist, vertically arranged
90 static void
91 list_vertical(StringList *sl)
93 int k;
94 size_t i, j, columns, lines;
95 char *p;
96 size_t w, width;
98 width = 0;
100 for (i = 0; i < sl->sl_cur; i++) {
101 w = strlen(sl->sl_str[i]);
102 if (w > width)
103 width = w;
105 width = (width + 8) &~ 7;
107 columns = ttywidth / width;
108 if (columns == 0)
109 columns = 1;
110 lines = (sl->sl_cur + columns - 1) / columns;
111 k = 0;
112 for (i = 0; i < lines; i++) {
113 for (j = 0; j < columns; j++) {
114 p = sl->sl_str[j * lines + i];
115 if (p)
116 (void)fputs(p, ttyout);
117 if (j * lines + i + lines >= sl->sl_cur) {
118 (void)putc('\n', ttyout);
119 break;
121 if (p) {
122 w = strlen(p);
123 while (w < width) {
124 w = (w + 8) &~ 7;
125 (void)putc('\t', ttyout);
129 if (ttyheight > 2 && ++k == ttyheight - 2) {
130 int ch;
131 k = 0;
132 (void)fputs("--more--", ttyout);
133 while ((ch = getchar()) != EOF && ch != ' ' && ch != 'q')
134 (void)putc(BELL, ttyout);
135 (void)fputs("\r \r", ttyout);
136 if (ch == 'q')
137 break;
143 * Copy characters from src into dst, \ quoting characters that require it
145 static void
146 ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
148 size_t di, si;
150 for (di = si = 0;
151 src[si] != '\0' && di < dstlen && si < srclen;
152 di++, si++) {
153 switch (src[si]) {
154 case '\\':
155 case ' ':
156 case '\t':
157 case '\r':
158 case '\n':
159 case '"':
160 dst[di++] = '\\';
161 if (di >= dstlen)
162 break;
163 /* FALLTHROUGH */
164 default:
165 dst[di] = src[si];
168 dst[di] = '\0';
172 * sl_init() with inbuilt error checking
174 static StringList *
175 mail_sl_init(void)
177 StringList *p;
179 p = sl_init();
180 if (p == NULL)
181 err(1, "Unable to allocate memory for stringlist");
182 return p;
187 * sl_add() with inbuilt error checking
189 static void
190 mail_sl_add(StringList *sl, char *i)
193 if (sl_add(sl, i) == -1)
194 err(1, "Unable to add `%s' to stringlist", i);
199 * Glob a local file name specification with the expectation of a single
200 * return value. Can't control multiple values being expanded from the
201 * expression, we return only the first.
202 * Returns NULL on error, or a pointer to a buffer containing the filename
203 * that's the caller's responsiblity to free(3) when finished with.
205 static char *
206 globulize(const char *pattern)
208 glob_t gl;
209 int flags;
210 char *p;
212 if (!doglob)
213 return estrdup(pattern);
215 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
216 (void)memset(&gl, 0, sizeof(gl));
217 if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) {
218 warnx("%s: not found", pattern);
219 globfree(&gl);
220 return NULL;
222 p = estrdup(gl.gl_pathv[0]);
223 globfree(&gl);
224 return p;
227 /* from src/usr.bin/ftp/utils.h (1.135) - end */
228 /************************************************************************/
230 static int
231 comparstr(const void *a, const void *b)
233 return strcmp(*(const char * const *)a, *(const char * const *)b);
237 * Determine if complete is ambiguous. If unique, insert.
238 * If no choices, error. If unambiguous prefix, insert that.
239 * Otherwise, list choices. words is assumed to be filtered
240 * to only contain possible choices.
241 * Args:
242 * word word which started the match
243 * dolist list by default
244 * words stringlist containing possible matches
245 * Returns a result as per el_set(EL_ADDFN, ...)
247 static unsigned char
248 complete_ambiguous(EditLine *el, char *word, int dolist, StringList *words)
250 char insertstr[MAXPATHLEN];
251 char *lastmatch, *p;
252 size_t i, j, matchlen, wordlen;
254 wordlen = strlen(word);
255 if (words->sl_cur == 0)
256 return CC_ERROR; /* no choices available */
258 if (words->sl_cur == 1) { /* only once choice available */
259 p = words->sl_str[0] + wordlen;
260 if (*p == '\0') /* at end of word? */
261 return CC_REFRESH;
262 ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
263 if (el_insertstr(el, insertstr) == -1)
264 return CC_ERROR;
265 else
266 return CC_REFRESH;
269 if (!dolist) {
270 matchlen = 0;
271 lastmatch = words->sl_str[0];
272 matchlen = strlen(lastmatch);
273 for (i = 1; i < words->sl_cur; i++) {
274 for (j = wordlen; j < strlen(words->sl_str[i]); j++)
275 if (lastmatch[j] != words->sl_str[i][j])
276 break;
277 if (j < matchlen)
278 matchlen = j;
280 if (matchlen >= wordlen) {
281 ftpvis(insertstr, sizeof(insertstr),
282 lastmatch + wordlen, matchlen - wordlen);
283 if (el_insertstr(el, insertstr) == -1)
284 return CC_ERROR;
285 else
286 return CC_REFRESH_BEEP;
290 (void)putc('\n', ttyout);
291 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
293 list_vertical(words);
294 return CC_REDISPLAY;
298 * Complete a mail command.
300 static unsigned char
301 complete_command(EditLine *el, char *word, int dolist)
303 const struct cmd *c;
304 StringList *words;
305 size_t wordlen;
306 unsigned char rv;
308 words = mail_sl_init();
309 wordlen = strlen(word);
311 for (c = cmdtab; c->c_name != NULL; c++) {
312 if (wordlen > strlen(c->c_name))
313 continue;
314 if (strncmp(word, c->c_name, wordlen) == 0)
315 mail_sl_add(words, __UNCONST(c->c_name));
318 rv = complete_ambiguous(el, word, dolist, words);
319 if (rv == CC_REFRESH) {
320 if (el_insertstr(el, " ") == -1)
321 rv = CC_ERROR;
323 sl_free(words, 0);
324 return rv;
328 * Complete a local filename.
330 static unsigned char
331 complete_filename(EditLine *el, char *word, int dolist)
333 StringList *words;
334 char dir[MAXPATHLEN];
335 char *fname;
336 DIR *dd;
337 struct dirent *dp;
338 unsigned char rv;
339 size_t len;
341 if ((fname = strrchr(word, '/')) == NULL) {
342 dir[0] = '.';
343 dir[1] = '\0';
344 fname = word;
345 } else {
346 if (fname == word) {
347 dir[0] = '/';
348 dir[1] = '\0';
349 } else {
350 len = fname - word + 1;
351 (void)estrlcpy(dir, word, sizeof(dir));
352 dir[len] = '\0';
354 fname++;
356 if (dir[0] == '~') {
357 char *p;
359 if ((p = globulize(dir)) == NULL)
360 return CC_ERROR;
361 (void)estrlcpy(dir, p, sizeof(dir));
362 free(p);
365 if ((dd = opendir(dir)) == NULL)
366 return CC_ERROR;
368 words = mail_sl_init();
369 len = strlen(fname);
371 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
372 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
373 continue;
375 #if defined(DIRENT_MISSING_D_NAMLEN)
376 if (len > strlen(dp->d_name))
377 continue;
378 #else
379 if (len > dp->d_namlen)
380 continue;
381 #endif
382 if (strncmp(fname, dp->d_name, len) == 0) {
383 char *tcp;
385 tcp = estrdup(dp->d_name);
386 mail_sl_add(words, tcp);
389 (void)closedir(dd);
391 rv = complete_ambiguous(el, fname, dolist, words);
392 if (rv == CC_REFRESH) {
393 struct stat sb;
394 char path[MAXPATHLEN];
396 (void)estrlcpy(path, dir, sizeof(path));
397 (void)estrlcat(path, "/", sizeof(path));
398 (void)estrlcat(path, words->sl_str[0], sizeof(path));
400 if (stat(path, &sb) >= 0) {
401 char suffix[2] = " ";
403 if (S_ISDIR(sb.st_mode))
404 suffix[0] = '/';
405 if (el_insertstr(el, suffix) == -1)
406 rv = CC_ERROR;
409 sl_free(words, 1);
410 return rv;
413 static int
414 find_execs(char *word, char *path, StringList *list)
416 char *sep;
417 char *dir=path;
418 DIR *dd;
419 struct dirent *dp;
420 size_t len = strlen(word);
421 uid_t uid = getuid();
422 gid_t gid = getgid();
424 for (sep = dir; sep; dir = sep + 1) {
425 if ((sep=strchr(dir, ':')) != NULL) {
426 *sep=0;
429 if ((dd = opendir(dir)) == NULL) {
430 perror("dir");
431 return -1;
434 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
436 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
437 continue;
439 #if defined(DIRENT_MISSING_D_NAMLEN)
440 if (len > strlen(dp->d_name))
441 continue;
442 #else
443 if (len > dp->d_namlen)
444 continue;
445 #endif
447 if (strncmp(word, dp->d_name, len) == 0) {
448 struct stat sb;
449 char pathname[ MAXPATHLEN ];
450 unsigned mask;
452 (void)snprintf(pathname, sizeof(pathname),
453 "%s/%s", dir, dp->d_name);
454 if (stat(pathname, &sb) != 0) {
455 perror(pathname);
456 continue;
459 mask = 0001;
460 if (sb.st_uid == uid) mask |= 0100;
461 if (sb.st_gid == gid) mask |= 0010;
463 if ((sb.st_mode & mask) != 0) {
464 char *tcp;
465 tcp = estrdup(dp->d_name);
466 mail_sl_add(list, tcp);
472 (void)closedir(dd);
475 return 0;
480 * Complete a local executable
482 static unsigned char
483 complete_executable(EditLine *el, char *word, int dolist)
485 StringList *words;
486 char dir[ MAXPATHLEN ];
487 char *fname;
488 unsigned char rv;
489 size_t len;
490 int error;
492 if ((fname = strrchr(word, '/')) == NULL) {
493 dir[0] = '\0'; /* walk the path */
494 fname = word;
495 } else {
496 if (fname == word) {
497 dir[0] = '/';
498 dir[1] = '\0';
499 } else {
500 len = fname - word;
501 (void)strncpy(dir, word, len);
502 dir[fname - word] = '\0';
504 fname++;
507 words = sl_init();
509 if (*dir == '\0') { /* walk path */
510 char *env;
511 char *path;
512 env = getenv("PATH");
513 len = strlen(env);
514 path = salloc(len + 1);
515 (void)strcpy(path, env);
516 error = find_execs(word, path, words);
518 else { /* check specified dir only */
519 error = find_execs(word, dir, words);
521 if (error != 0)
522 return CC_ERROR;
524 rv = complete_ambiguous(el, fname, dolist, words);
525 if (rv == CC_REFRESH) {
526 if (el_insertstr(el, " ") == -1)
527 rv = CC_ERROR;
529 sl_free(words, 1);
531 return rv;
535 static unsigned char
536 complete_set(EditLine *el, char *word, int dolist)
538 struct var *vp;
539 const char **ap;
540 const char **p;
541 int h;
542 int s;
543 size_t len = strlen(word);
544 StringList *words;
545 unsigned char rv;
547 words = sl_init();
549 /* allocate space for variables table */
550 s = 1;
551 for (h = 0; h < HSHSIZE; h++)
552 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
553 s++;
554 ap = salloc(s * sizeof(*ap));
556 /* save the pointers */
557 for (h = 0, p = ap; h < HSHSIZE; h++)
558 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
559 *p++ = vp->v_name;
560 *p = NULL;
561 sort(ap);
562 for (p = ap; *p != NULL; p++)
563 if (len == 0 || strncmp(*p, word, len) == 0)
564 mail_sl_add(words, estrdup(*p));
566 rv = complete_ambiguous(el, word, dolist, words);
568 sl_free(words, 1);
570 return rv;
574 static unsigned char
575 complete_alias(EditLine *el, char *word, int dolist)
577 struct grouphead *gh;
578 const char **ap;
579 const char **p;
580 int h;
581 int s;
582 size_t len = strlen(word);
583 StringList *words;
584 unsigned char rv;
586 words = sl_init();
588 /* allocate space for alias table */
589 s = 1;
590 for (h = 0; h < HSHSIZE; h++)
591 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
592 s++;
593 ap = salloc(s * sizeof(*ap));
595 /* save pointers */
596 p = ap;
597 for (h = 0; h < HSHSIZE; h++)
598 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
599 *p++ = gh->g_name;
601 *p = NULL;
603 sort(ap);
604 for (p = ap; *p != NULL; p++)
605 if (len == 0 || strncmp(*p, word, len) == 0)
606 mail_sl_add(words, estrdup(*p));
608 rv = complete_ambiguous(el, word, dolist, words);
609 if (rv == CC_REFRESH) {
610 if (el_insertstr(el, " ") == -1)
611 rv = CC_ERROR;
613 sl_free(words, 1);
614 return rv;
618 static unsigned char
619 complete_smopts(EditLine *el, char *word, int dolist)
621 struct grouphead *gh;
622 struct smopts_s *sp;
623 const char **ap;
624 const char **p;
625 int h;
626 int s1;
627 int s2;
628 size_t len;
629 StringList *words;
630 unsigned char rv;
632 len = strlen(word);
633 words = sl_init();
635 /* count the entries in the smoptstbl and groups (alias) tables */
636 s1 = 1;
637 s2 = 1;
638 for (h = 0; h < HSHSIZE; h++) {
639 for (sp = smoptstbl[h]; sp != NULL; sp = sp->s_link)
640 s1++;
641 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
642 s2++;
645 /* allocate sufficient space for the pointers */
646 ap = salloc(MAX(s1, s2) * sizeof(*ap));
649 * First do the smoptstbl pointers. (case _insensitive_)
651 p = ap;
652 for (h = 0; h < HSHSIZE; h++)
653 for (sp = smoptstbl[h]; sp != NULL; sp = sp->s_link)
654 *p++ = sp->s_name;
655 *p = NULL;
656 sort(ap);
657 for (p = ap; *p != NULL; p++)
658 if (len == 0 || strncasecmp(*p, word, len) == 0)
659 mail_sl_add(words, estrdup(*p));
662 * Now do the groups (alias) pointers. (case sensitive)
664 p = ap;
665 for (h = 0; h < HSHSIZE; h++)
666 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
667 *p++ = gh->g_name;
668 *p = NULL;
669 sort(ap);
670 for (p = ap; *p != NULL; p++)
671 if (len == 0 || strncmp(*p, word, len) == 0)
672 mail_sl_add(words, estrdup(*p));
674 rv = complete_ambiguous(el, word, dolist, words);
676 sl_free(words, 1);
678 return rv;
682 #ifdef THREAD_SUPPORT
683 static unsigned char
684 complete_thread_key(EditLine *el, char *word, int dolist)
686 const char **ap;
687 const char **p;
688 const char *name;
689 size_t len;
690 StringList *words;
691 unsigned char rv;
692 int cnt;
693 const void *cookie;
695 len = strlen(word);
696 words = sl_init();
698 /* count the entries in the table */
699 /* XXX - have a function return this rather than counting? */
700 cnt = 1; /* count the NULL terminator */
701 cookie = NULL;
702 while (thread_next_key_name(&cookie) != NULL)
703 cnt++;
705 /* allocate sufficient space for the pointers */
706 ap = salloc(cnt * sizeof(*ap));
708 /* load the array */
709 p = ap;
710 cookie = NULL;
711 while ((name = thread_next_key_name(&cookie)) != NULL)
712 *p++ = name;
713 *p = NULL;
714 sort(ap);
715 for (p = ap; *p != NULL; p++)
716 if (len == 0 || strncmp(*p, word, len) == 0)
717 mail_sl_add(words, estrdup(*p));
719 rv = complete_ambiguous(el, word, dolist, words);
721 sl_free(words, 1);
723 return rv;
725 #endif /* THREAD_SUPPORT */
727 /* from /usr/src/usr.bin/ftp/main.c(1.101) - end */
728 /************************************************************************/
730 /* Some people like to bind file completion to CTRL-D. In emacs mode,
731 * CTRL-D is also used to delete the current character, we have to
732 * special case this situation.
734 #define EMACS_CTRL_D_BINDING_HACK
736 #ifdef EMACS_CTRL_D_BINDING_HACK
737 static int
738 is_emacs_mode(EditLine *el)
740 char *mode;
742 if (el_get(el, EL_EDITOR, &mode) == -1)
743 return 0;
744 return equal(mode, "emacs");
747 static int
748 emacs_ctrl_d(EditLine *el, const LineInfo *lf, int ch)
750 static char delunder[3] = { CTRL('f'), CTRL('h'), '\0' };
752 if (ch == CTRL('d') && is_emacs_mode(el)) { /* CTRL-D is special */
753 if (lf->buffer == lf->lastchar)
754 return CC_EOF;
755 if (lf->cursor != lf->lastchar) { /* delete without using ^D */
756 el_push(el, delunder); /* ^F^H */
757 return CC_NORM;
760 return -1;
762 #endif /* EMACS_CTRL_D_BINDING_HACK */
765 * Check if this is the second request made for this line indicating
766 * the need to list all the completion possibilities.
768 static int
769 get_dolist(const LineInfo *lf)
771 static char last_line[LINESIZE];
772 static char *last_cursor_pos;
773 char *cursor_pos;
774 int dolist;
775 size_t len;
777 len = lf->lastchar - lf->buffer;
778 if (len >= sizeof(last_line) - 1)
779 return -1;
781 cursor_pos = last_line + (lf->cursor - lf->buffer);
782 dolist =
783 cursor_pos == last_cursor_pos &&
784 strncmp(last_line, lf->buffer, len) == 0;
786 (void)strlcpy(last_line, lf->buffer, len + 1);
787 last_cursor_pos = cursor_pos;
789 return dolist;
793 * Take the full line (lf) including the command and split it into a
794 * sub-line (returned) and a completion context (cmplarray).
796 static LineInfo *
797 split_line(const char **cmplarray, const LineInfo *lf)
799 static LineInfo li;
800 const struct cmd *c;
801 char *cmdname;
802 char line[LINESIZE];
803 char *cp;
804 size_t len;
806 len = lf->cursor - lf->buffer;
807 if (len + 1 > sizeof(line))
808 return NULL;
810 (void)strlcpy(line, lf->buffer, len + 1);
812 li.cursor = line + len;
813 li.lastchar = line + len;
815 cp = skip_WSP(line);
816 cmdname = get_cmdname(cp);
817 cp += strlen(cmdname);
819 if (cp == li.cursor) {
820 *cmplarray = "c";
821 li.buffer = cmdname;
822 return &li;
825 c = lex(cmdname);
826 if (c == NULL)
827 return NULL;
829 *cmplarray = c->c_complete;
830 if (c->c_pipe) {
831 char *cp2;
832 if ((cp2 = shellpr(cp)) != NULL) {
833 cp = cp2;
834 # define XX(a) ((a) + ((a)[1] == '>' ? 2 : 1))
835 while ((cp2 = shellpr(XX(cp))) != NULL)
836 cp = cp2;
838 if (*cp == '|') {
839 *cmplarray = "xF";
840 cp = skip_WSP(cp + 1);
842 else {
843 assert(*cp == '>');
844 cp = skip_WSP(XX(cp));
845 *cmplarray = "f";
847 # undef XX
850 li.buffer = cp;
851 return &li;
855 * Split a sub-line and a completion context into a word and a
856 * completion type. Use the editline tokenizer to handle the quoting
857 * and splitting.
859 static char *
860 split_word(int *cmpltype, const char *cmplarray, LineInfo *li)
862 static Tokenizer *t = NULL;
863 const char **argv;
864 char *word;
865 int argc;
866 int cursorc;
867 int cursoro;
868 int arraylen;
870 if (t != NULL)
871 tok_reset(t);
872 else {
873 if ((t = tok_init(NULL)) == NULL)
874 err(EXIT_FAILURE, "tok_init");
876 if (tok_line(t, li, &argc, &argv, &cursorc, &cursoro) == -1)
877 err(EXIT_FAILURE, "tok_line");
879 if (cursorc >= argc)
880 word = __UNCONST("");
881 else {
882 word = salloc((size_t)cursoro + 1);
883 (void)strlcpy(word, argv[cursorc], (size_t)cursoro + 1);
886 /* check for 'continuation' completes (which are uppercase) */
887 arraylen = (int)strlen(cmplarray);
888 if (cursorc >= arraylen &&
889 arraylen > 0 &&
890 isupper((unsigned char)cmplarray[arraylen - 1]))
891 cursorc = arraylen - 1;
893 if (cursorc >= arraylen)
894 return NULL;
896 *cmpltype = cmplarray[cursorc];
897 return word;
901 * A generic complete routine for the mail command line.
903 static unsigned char
904 mail_complete(EditLine *el, int ch)
906 LineInfo *li;
907 const LineInfo *lf;
908 const char *cmplarray;
909 int dolist;
910 int cmpltype;
911 char *word;
913 lf = el_line(el);
915 #ifdef EMACS_CTRL_D_BINDING_HACK
917 int cc_ret;
918 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
919 return cc_ret;
921 #endif /* EMACS_CTRL_D_BINDING_HACK */
923 if ((dolist = get_dolist(lf)) == -1)
924 return CC_ERROR;
926 if ((li = split_line(&cmplarray, lf)) == NULL)
927 return CC_ERROR;
929 if ((word = split_word(&cmpltype, cmplarray, li)) == NULL)
930 return CC_ERROR;
932 switch (cmpltype) {
933 case 'a': /* alias complete */
934 case 'A':
935 return complete_alias(el, word, dolist);
937 case 'c': /* command complete */
938 case 'C':
939 return complete_command(el, word, dolist);
941 case 'f': /* filename complete */
942 case 'F':
943 return complete_filename(el, word, dolist);
945 case 'm':
946 case 'M':
947 return complete_smopts(el, word, dolist);
949 case 'n': /* no complete */
950 case 'N': /* no complete */
951 return CC_ERROR;
953 case 's':
954 case 'S':
955 return complete_set(el, word, dolist);
956 #ifdef THREAD_SUPPORT
957 case 't':
958 case 'T':
959 return complete_thread_key(el, word, dolist);
960 #endif
961 case 'x': /* executable complete */
962 case 'X':
963 return complete_executable(el, word, dolist);
965 default:
966 warnx("unknown complete type `%c'", cmpltype);
967 #if 0
968 assert(/*CONSTCOND*/0);
969 #endif
970 return CC_ERROR;
972 /* NOTREACHED */
977 * A generic file completion routine.
979 static unsigned char
980 file_complete(EditLine *el, int ch)
982 static char word[LINESIZE];
983 const LineInfo *lf;
984 size_t word_len;
985 int dolist;
987 lf = el_line(el);
989 #ifdef EMACS_CTRL_D_BINDING_HACK
991 int cc_ret;
992 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
993 return cc_ret;
995 #endif /* EMACS_CTRL_D_BINDING_HACK */
997 word_len = lf->cursor - lf->buffer;
998 if (word_len + 1 > sizeof(word))
999 return CC_ERROR;
1001 (void)strlcpy(word, lf->buffer, word_len + 1); /* do not use estrlcpy here! */
1003 if ((dolist = get_dolist(lf)) == -1)
1004 return CC_ERROR;
1006 return complete_filename(el, word, dolist);
1010 #ifdef MIME_SUPPORT
1012 * Complete mime_transfer_encoding type.
1014 static unsigned char
1015 mime_enc_complete(EditLine *el, int ch)
1017 static char word[LINESIZE];
1018 StringList *words;
1019 unsigned char rv;
1020 const LineInfo *lf;
1021 size_t word_len;
1022 int dolist;
1024 lf = el_line(el);
1026 #ifdef EMACS_CTRL_D_BINDING_HACK
1028 int cc_ret;
1029 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
1030 return cc_ret;
1032 #endif /* EMACS_CTRL_D_BINDING_HACK */
1034 word_len = lf->cursor - lf->buffer;
1035 if (word_len >= sizeof(word) - 1)
1036 return CC_ERROR;
1038 words = mail_sl_init();
1040 const char *ename;
1041 const void *cookie;
1042 cookie = NULL;
1043 for (ename = mime_next_encoding_name(&cookie);
1044 ename;
1045 ename = mime_next_encoding_name(&cookie))
1046 if (word_len == 0 ||
1047 strncmp(lf->buffer, ename, word_len) == 0) {
1048 char *cp;
1049 cp = estrdup(ename);
1050 mail_sl_add(words, cp);
1053 (void)strlcpy(word, lf->buffer, word_len + 1);
1055 if ((dolist = get_dolist(lf)) == -1)
1056 return CC_ERROR;
1058 rv = complete_ambiguous(el, word, dolist, words);
1060 sl_free(words, 1);
1061 return rv;
1063 #endif /* MIME_SUPPORT */
1066 /*************************************************************************
1067 * Our public interface to el_gets():
1069 * init_editline()
1070 * Initializes of all editline and completion data strutures.
1072 * my_gets()
1073 * Displays prompt, calls el_gets() and deals with history.
1074 * Returns the next line of input as a NULL termnated string
1075 * without the trailing newline, or NULL if el_gets() sees is an
1076 * error or signal.
1079 static const char *el_prompt;
1081 /*ARGSUSED*/
1082 static const char *
1083 show_prompt(EditLine *e __unused)
1085 return el_prompt;
1089 * Write the current INTR character to fp in a friendly form.
1091 static void
1092 echo_INTR(void *p)
1094 struct termios ttybuf;
1095 char buf[5];
1096 FILE *fp;
1098 fp = p;
1099 if (tcgetattr(fileno(stdin), &ttybuf) == -1)
1100 warn("tcgetattr");
1101 else {
1102 (void)vis(buf, ttybuf.c_cc[VINTR], VIS_SAFE | VIS_NOSLASH, 0);
1103 (void)fprintf(fp, "%s", buf);
1104 (void)fflush(fp);
1108 static sig_t old_sigint;
1109 static void
1110 comp_intr(int signo)
1113 echo_INTR(stdout);
1114 old_sigint(signo);
1117 PUBLIC char *
1118 my_gets(el_mode_t *em, const char *prompt, char *string)
1120 static char line[LINE_MAX];
1121 size_t len;
1122 int cnt;
1123 const char *buf;
1124 HistEvent ev;
1126 sig_check();
1128 el_prompt = prompt;
1129 if (string)
1130 el_push(em->el, string);
1133 * Let el_gets() deal with flow control. Also, make sure we
1134 * output a ^C when we get a SIGINT as el_gets() doesn't echo
1135 * one.
1137 old_sigint = sig_signal(SIGINT, comp_intr);
1138 buf = el_gets(em->el, &cnt);
1139 (void)sig_signal(SIGINT, old_sigint);
1141 if (buf == NULL) {
1142 sig_check();
1143 return NULL;
1146 assert(cnt > 0);
1147 if (buf[cnt - 1] == '\n')
1148 cnt--; /* trash the trailing LF */
1150 len = MIN(sizeof(line) - 1, (size_t)cnt);
1151 (void)memcpy(line, buf, len);
1152 line[cnt] = '\0';
1154 /* enter non-empty lines into history */
1155 if (em->hist) {
1156 const char *p;
1158 p = skip_WSP(line);
1159 if (*p && history(em->hist, &ev, H_ENTER, line) == 0)
1160 (void)printf("Failed history entry: %s", line);
1162 sig_check();
1163 return line;
1166 static el_mode_t
1167 init_el_mode(
1168 const char *el_editor,
1169 unsigned char (*completer)(EditLine *, int),
1170 struct name *keys,
1171 int history_size)
1173 FILE *nullfp;
1174 el_mode_t em;
1176 (void)memset(&em, 0, sizeof(em));
1178 if ((nullfp = fopen(_PATH_DEVNULL, "w")) == NULL)
1179 err(EXIT_FAILURE, "Cannot open `%s'", _PATH_DEVNULL);
1181 if ((em.el = el_init(getprogname(), stdin, stdout, nullfp)) == NULL) {
1182 warn("el_init");
1183 return em;
1185 (void)fflush(nullfp);
1186 (void)dup2(STDERR_FILENO, fileno(nullfp));
1188 (void)el_set(em.el, EL_PROMPT, show_prompt);
1189 (void)el_set(em.el, EL_SIGNAL, 1); /* editline handles the signals. */
1191 if (el_editor)
1192 (void)el_set(em.el, EL_EDITOR, el_editor);
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);
1203 if (history_size) {
1204 HistEvent ev;
1205 if ((em.hist = history_init()) == NULL) {
1206 warn("history_init");
1207 return em;
1209 if (history(em.hist, &ev, H_SETSIZE, history_size) == -1)
1210 (void)printf("history: %s\n", ev.str);
1211 (void)el_set(em.el, EL_HIST, history, em.hist);
1214 (void)el_source(em.el, NULL); /* read ~/.editrc */
1216 return em;
1220 struct el_modes_s elm = {
1221 .command = { .el = NULL, .hist = NULL, },
1222 .string = { .el = NULL, .hist = NULL, },
1223 .filec = { .el = NULL, .hist = NULL, },
1224 #ifdef MIME_SUPPORT
1225 .mime_enc = { .el = NULL, .hist = NULL, },
1226 #endif
1229 PUBLIC void
1230 init_editline(void)
1232 const char *mode;
1233 int hist_size;
1234 struct name *keys;
1235 char *cp;
1237 mode = value(ENAME_EL_EDITOR);
1239 cp = value(ENAME_EL_HISTORY_SIZE);
1240 hist_size = cp ? atoi(cp) : 0;
1242 cp = value(ENAME_EL_COMPLETION_KEYS);
1243 keys = cp && *cp ? lexpand(cp, 0) : NULL;
1245 elm.command = init_el_mode(mode, mail_complete, keys, hist_size);
1246 elm.filec = init_el_mode(mode, file_complete, keys, 0);
1247 elm.string = init_el_mode(mode, NULL, NULL, 0);
1248 #ifdef MIME_SUPPORT
1249 elm.mime_enc = init_el_mode(mode, mime_enc_complete, keys, 0);
1250 #endif
1251 return;
1254 #endif /* USE_EDITLINE */