Annotation of src/usr.bin/mail/complete.c, Revision 1.11.2.1
1.11.2.1! tron 1: /* $NetBSD$ */
1.1 christos 2:
3: /*-
1.9 christos 4: * Copyright (c) 1997-2000,2005,2006 The NetBSD Foundation, Inc.
1.1 christos 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:
1.11 christos 39: /*
40: * Most of this is derived or copied from src/usr.bin/ftp/complete.c (1.41).
41: */
1.1 christos 42:
1.9 christos 43: #ifdef USE_EDITLINE
44: #undef NO_EDITCOMPLETE
1.1 christos 45:
46: #include <sys/cdefs.h>
47: #ifndef lint
1.11.2.1! tron 48: __RCSID("$NetBSD$");
1.1 christos 49: #endif /* not lint */
50:
51: /*
52: * FTP user program - command and file completion routines
53: */
54:
1.11 christos 55: #include <assert.h>
1.1 christos 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>
1.9 christos 64: #include <util.h>
1.1 christos 65:
1.11 christos 66: #include <sys/param.h>
67: #include <sys/stat.h>
68:
1.1 christos 69: #include "rcv.h" /* includes "glob.h" */
70: #include "extern.h"
71: #include "complete.h"
1.9 christos 72: #ifdef MIME_SUPPORT
73: #include "mime.h"
74: #endif
1.11 christos 75: #ifdef THREAD_SUPPORT
76: #include "thread.h"
77: #endif
78:
79: #define BELL 0x7
1.1 christos 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: /************************************************************************/
1.9 christos 91: /* from src/usr.bin/ftp/utils.h (1.135) - begin */
1.1 christos 92:
93: /*
94: * List words in stringlist, vertically arranged
95: */
96: static void
97: list_vertical(StringList *sl)
98: {
1.11 christos 99: int i, j, k;
1.1 christos 100: int columns, lines;
101: char *p;
102: size_t w, width;
103:
104: width = 0;
105:
1.9 christos 106: for (i = 0; i < sl->sl_cur; i++) {
1.1 christos 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;
1.11 christos 117: k = 0;
1.1 christos 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)
1.9 christos 122: (void)fputs(p, ttyout);
1.1 christos 123: if (j * lines + i + lines >= sl->sl_cur) {
1.9 christos 124: (void)putc('\n', ttyout);
1.1 christos 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: }
1.11 christos 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: }
1.1 christos 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 *
1.9 christos 181: mail_sl_init(void)
1.1 christos 182: {
183: StringList *p;
184:
185: p = sl_init();
186: if (p == NULL)
187: err(1, "Unable to allocate memory for stringlist");
1.11 christos 188: return p;
1.1 christos 189: }
190:
191:
192: /*
193: * sl_add() with inbuilt error checking
194: */
195: static void
1.9 christos 196: mail_sl_add(StringList *sl, char *i)
1.1 christos 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)
1.9 christos 219: return estrdup(pattern);
1.1 christos 220:
221: flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
1.9 christos 222: (void)memset(&gl, 0, sizeof(gl));
1.1 christos 223: if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) {
224: warnx("%s: not found", pattern);
225: globfree(&gl);
1.11 christos 226: return NULL;
1.1 christos 227: }
1.9 christos 228: p = estrdup(gl.gl_pathv[0]);
1.1 christos 229: globfree(&gl);
1.11 christos 230: return p;
1.1 christos 231: }
232:
1.9 christos 233: /* from src/usr.bin/ftp/utils.h (1.135) - end */
1.1 christos 234: /************************************************************************/
235:
236: static int
237: comparstr(const void *a, const void *b)
238: {
1.11 christos 239: return strcmp(*(const char * const *)a, *(const char * const *)b);
1.1 christos 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
1.9 christos 249: * dolist list by default
1.1 christos 250: * words stringlist containing possible matches
251: * Returns a result as per el_set(EL_ADDFN, ...)
252: */
253: static unsigned char
1.9 christos 254: complete_ambiguous(EditLine *el, char *word, int dolist, StringList *words)
1.1 christos 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)
1.11 christos 263: return CC_ERROR; /* no choices available */
1.1 christos 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? */
1.11 christos 268: return CC_REFRESH;
1.1 christos 269: ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
270: if (el_insertstr(el, insertstr) == -1)
1.11 christos 271: return CC_ERROR;
1.1 christos 272: else
1.11 christos 273: return CC_REFRESH;
1.1 christos 274: }
275:
1.9 christos 276: if (!dolist) {
1.1 christos 277: matchlen = 0;
278: lastmatch = words->sl_str[0];
279: matchlen = strlen(lastmatch);
1.9 christos 280: for (i = 1; i < words->sl_cur; i++) {
281: for (j = wordlen; j < strlen(words->sl_str[i]); j++)
1.1 christos 282: if (lastmatch[j] != words->sl_str[i][j])
283: break;
284: if (j < matchlen)
285: matchlen = j;
286: }
1.11 christos 287: if (matchlen >= wordlen) {
1.1 christos 288: ftpvis(insertstr, sizeof(insertstr),
289: lastmatch + wordlen, matchlen - wordlen);
290: if (el_insertstr(el, insertstr) == -1)
1.11 christos 291: return CC_ERROR;
1.1 christos 292: else
1.11 christos 293: return CC_REFRESH_BEEP;
1.1 christos 294: }
295: }
296:
1.9 christos 297: (void)putc('\n', ttyout);
1.1 christos 298: qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
1.11 christos 299:
1.1 christos 300: list_vertical(words);
1.11 christos 301: return CC_REDISPLAY;
1.1 christos 302: }
303:
304: /*
305: * Complete a mail command.
306: */
307: static unsigned char
1.9 christos 308: complete_command(EditLine *el, char *word, int dolist)
1.1 christos 309: {
310: const struct cmd *c;
311: StringList *words;
312: size_t wordlen;
313: unsigned char rv;
314:
1.9 christos 315: words = mail_sl_init();
1.1 christos 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)
1.9 christos 322: mail_sl_add(words, __UNCONST(c->c_name));
1.1 christos 323: }
324:
1.9 christos 325: rv = complete_ambiguous(el, word, dolist, words);
1.1 christos 326: if (rv == CC_REFRESH) {
327: if (el_insertstr(el, " ") == -1)
328: rv = CC_ERROR;
329: }
330: sl_free(words, 0);
1.11 christos 331: return rv;
1.1 christos 332: }
333:
334: /*
335: * Complete a local filename.
336: */
337: static unsigned char
1.9 christos 338: complete_filename(EditLine *el, char *word, int dolist)
1.1 christos 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';
1.9 christos 356: } else {
357: len = fname - word + 1;
358: (void)estrlcpy(dir, word, sizeof(dir));
359: dir[len] = '\0';
360: }
1.1 christos 361: fname++;
362: }
363: if (dir[0] == '~') {
364: char *p;
365:
366: if ((p = globulize(dir)) == NULL)
1.11 christos 367: return CC_ERROR;
1.9 christos 368: (void)estrlcpy(dir, p, sizeof(dir));
1.1 christos 369: free(p);
370: }
371:
372: if ((dd = opendir(dir)) == NULL)
1.11 christos 373: return CC_ERROR;
1.1 christos 374:
1.9 christos 375: words = mail_sl_init();
1.1 christos 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:
1.9 christos 392: tcp = estrdup(dp->d_name);
393: mail_sl_add(words, tcp);
1.1 christos 394: }
395: }
1.9 christos 396: (void)closedir(dd);
1.1 christos 397:
1.9 christos 398: rv = complete_ambiguous(el, fname, dolist, words);
1.1 christos 399: if (rv == CC_REFRESH) {
400: struct stat sb;
401: char path[MAXPATHLEN];
402:
1.9 christos 403: (void)estrlcpy(path, dir, sizeof(path));
404: (void)estrlcat(path, "/", sizeof(path));
405: (void)estrlcat(path, words->sl_str[0], sizeof(path));
1.1 christos 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);
1.11 christos 417: return rv;
1.1 christos 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;
1.9 christos 427: size_t len = strlen(word);
1.1 christos 428: uid_t uid = getuid();
429: gid_t gid = getgid();
430:
1.9 christos 431: for (sep = dir; sep; dir = sep + 1) {
1.1 christos 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:
1.9 christos 459: (void)snprintf(pathname, sizeof(pathname),
460: "%s/%s", dir, dp->d_name);
1.1 christos 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;
1.9 christos 472: tcp = estrdup(dp->d_name);
473: mail_sl_add(list, tcp);
1.1 christos 474: }
475: }
476:
477: }
478:
1.9 christos 479: (void)closedir(dd);
1.1 christos 480: }
481:
482: return 0;
483: }
484:
485:
486: /*
487: * Complete a local executable
488: */
489: static unsigned char
1.9 christos 490: complete_executable(EditLine *el, char *word, int dolist)
1.1 christos 491: {
492: StringList *words;
493: char dir[ MAXPATHLEN ];
494: char *fname;
495: unsigned char rv;
1.9 christos 496: size_t len;
1.1 christos 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 {
1.9 christos 507: len = fname - word;
508: (void)strncpy(dir, word, len);
1.1 christos 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);
1.9 christos 522: (void)strcpy(path, env);
1.1 christos 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:
1.9 christos 531: rv = complete_ambiguous(el, fname, dolist, words);
1.11 christos 532: if (rv == CC_REFRESH) {
1.1 christos 533: if (el_insertstr(el, " ") == -1)
534: rv = CC_ERROR;
1.11 christos 535: }
1.1 christos 536: sl_free(words, 1);
537:
1.11 christos 538: return rv;
1.1 christos 539: }
540:
541:
1.11 christos 542: static unsigned char
543: complete_set(EditLine *el, char *word, int dolist)
1.1 christos 544: {
545: struct var *vp;
1.11 christos 546: const char **ap;
547: const char **p;
1.1 christos 548: int h;
549: int s;
1.9 christos 550: size_t len = strlen(word);
1.1 christos 551: StringList *words;
552: unsigned char rv;
553:
554: words = sl_init();
555:
556: /* allocate space for variables table */
1.10 christos 557: s = 1;
558: for (h = 0; h < HSHSIZE; h++)
1.1 christos 559: for (vp = variables[h]; vp != NULL; vp = vp->v_link)
560: s++;
1.11 christos 561: ap = salloc(s * sizeof *ap);
1.1 christos 562:
1.10 christos 563: /* save the pointers */
1.1 christos 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);
1.10 christos 569: for (p = ap; *p != NULL; p++)
570: if (len == 0 || strncmp(*p, word, len) == 0)
571: mail_sl_add(words, estrdup(*p));
1.1 christos 572:
1.9 christos 573: rv = complete_ambiguous(el, word, dolist, words);
1.1 christos 574:
575: sl_free(words, 1);
576:
1.11 christos 577: return rv;
1.1 christos 578: }
579:
580:
581: static unsigned char
1.9 christos 582: complete_alias(EditLine *el, char *word, int dolist)
1.1 christos 583: {
584: struct grouphead *gh;
1.11 christos 585: const char **ap;
586: const char **p;
1.1 christos 587: int h;
588: int s;
1.9 christos 589: size_t len = strlen(word);
1.1 christos 590: StringList *words;
591: unsigned char rv;
592:
593: words = sl_init();
594:
595: /* allocate space for alias table */
1.10 christos 596: s = 1;
597: for (h = 0; h < HSHSIZE; h++)
1.1 christos 598: for (gh = groups[h]; gh != NULL; gh = gh->g_link)
599: s++;
1.11 christos 600: ap = salloc(s * sizeof(*ap));
1.10 christos 601:
1.1 christos 602: /* save pointers */
1.10 christos 603: p = ap;
604: for (h = 0; h < HSHSIZE; h++)
1.1 christos 605: for (gh = groups[h]; gh != NULL; gh = gh->g_link)
606: *p++ = gh->g_name;
1.11 christos 607:
1.1 christos 608: *p = NULL;
1.11 christos 609:
1.10 christos 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);
1.11 christos 616: if (rv == CC_REFRESH) {
1.10 christos 617: if (el_insertstr(el, " ") == -1)
618: rv = CC_ERROR;
1.11 christos 619: }
1.10 christos 620: sl_free(words, 1);
1.11 christos 621: return rv;
1.10 christos 622: }
623:
624:
1.11 christos 625: static unsigned char
626: complete_smopts(EditLine *el, char *word, int dolist)
1.10 christos 627: {
628: struct grouphead *gh;
629: struct smopts_s *sp;
1.11 christos 630: const char **ap;
631: const char **p;
1.10 christos 632: int h;
633: int s1;
634: int s2;
635: size_t len;
636: StringList *words;
637: unsigned char rv;
1.1 christos 638:
1.10 christos 639: len = strlen(word);
640: words = sl_init();
1.1 christos 641:
1.10 christos 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++;
1.1 christos 650: }
651:
1.10 christos 652: /* allocate sufficient space for the pointers */
1.11 christos 653: ap = salloc(MAX(s1, s2) * sizeof *ap);
1.10 christos 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:
1.9 christos 681: rv = complete_ambiguous(el, word, dolist, words);
1.1 christos 682:
683: sl_free(words, 1);
684:
1.11 christos 685: return rv;
1.1 christos 686: }
687:
688:
1.11 christos 689: #ifdef THREAD_SUPPORT
690: static unsigned char
691: complete_thread_key(EditLine *el, char *word, int dolist)
1.1 christos 692: {
1.11 christos 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;
1.1 christos 701:
1.11 christos 702: len = strlen(word);
703: words = sl_init();
1.1 christos 704:
1.11 christos 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++;
1.1 christos 711:
1.11 christos 712: /* allocate sufficient space for the pointers */
713: ap = salloc(cnt * sizeof *ap);
1.1 christos 714:
1.11 christos 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));
1.1 christos 725:
1.11 christos 726: rv = complete_ambiguous(el, word, dolist, words);
1.1 christos 727:
1.11 christos 728: sl_free(words, 1);
1.1 christos 729:
1.11 christos 730: return rv;
1.1 christos 731: }
1.11 christos 732: #endif /* THREAD_SUPPORT */
1.1 christos 733:
1.9 christos 734: /* from /usr/src/usr.bin/ftp/main.c(1.101) - end */
1.1 christos 735: /************************************************************************/
736:
1.9 christos 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
1.11 christos 754: emacs_ctrl_d(EditLine *el, const LineInfo *lf, int ch)
1.9 christos 755: {
756: static char delunder[3] = { CTRL('f'), CTRL('h'), '\0' };
757: if (ch == CTRL('d') && is_emacs_mode(el)) { /* CTRL-D is special */
1.11 christos 758: if (lf->buffer == lf->lastchar)
759: return CC_EOF;
760: if (lf->cursor != lf->lastchar) { /* delete without using ^D */
1.9 christos 761: el_push(el, delunder); /* ^F^H */
1.11 christos 762: return CC_NORM;
1.9 christos 763: }
764: }
765: return -1;
766: }
767: #endif /* EMACS_CTRL_D_BINDING_HACK */
1.1 christos 768:
769: /*
1.11 christos 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.
1.1 christos 907: */
908: static unsigned char
1.9 christos 909: mail_complete(EditLine *el, int ch)
1.1 christos 910: {
1.11 christos 911: LineInfo *li;
1.1 christos 912: const LineInfo *lf;
1.11 christos 913: const char *cmplarray;
914: int dolist;
915: int cmpltype;
916: char *word;
1.1 christos 917:
918: lf = el_line(el);
1.9 christos 919:
920: #ifdef EMACS_CTRL_D_BINDING_HACK
921: {
922: int cc_ret;
1.11 christos 923: if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
1.9 christos 924: return cc_ret;
1.1 christos 925: }
1.9 christos 926: #endif /* EMACS_CTRL_D_BINDING_HACK */
927:
1.11 christos 928: if ((dolist = get_dolist(lf)) == -1)
929: return CC_ERROR;
1.9 christos 930:
1.11 christos 931: if ((li = split_line(&cmplarray, lf)) == NULL)
932: return CC_ERROR;
1.1 christos 933:
1.11 christos 934: if ((word = split_word(&cmpltype, cmplarray, li)) == NULL)
935: return CC_ERROR;
1.1 christos 936:
937: switch (cmpltype) {
1.11 christos 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);
1.10 christos 965: #endif
1.1 christos 966: case 'x': /* executable complete */
1.11 christos 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;
1.1 christos 976: }
977: /* NOTREACHED */
978: }
979:
980:
1.9 christos 981: /*
1.11 christos 982: * A generic file completion routine.
1.9 christos 983: */
984: static unsigned char
985: file_complete(EditLine *el, int ch)
986: {
987: static char word[LINESIZE];
988: const LineInfo *lf;
1.11 christos 989: size_t word_len;
990: int dolist;
1.9 christos 991:
992: lf = el_line(el);
993:
994: #ifdef EMACS_CTRL_D_BINDING_HACK
995: {
996: int cc_ret;
1.11 christos 997: if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
1.9 christos 998: return cc_ret;
999: }
1000: #endif /* EMACS_CTRL_D_BINDING_HACK */
1001:
1.11 christos 1002: word_len = lf->cursor - lf->buffer;
1003: if (word_len + 1 > sizeof(word))
1004: return CC_ERROR;
1.9 christos 1005:
1006: (void)strlcpy(word, lf->buffer, word_len + 1); /* do not use estrlcpy here! */
1.11 christos 1007:
1008: if ((dolist = get_dolist(lf)) == -1)
1009: return CC_ERROR;
1010:
1011: return complete_filename(el, word, dolist);
1.9 christos 1012: }
1.1 christos 1013:
1014:
1.9 christos 1015: #ifdef MIME_SUPPORT
1016: /*
1.11 christos 1017: * Complete mime_transfer_encoding type.
1.9 christos 1018: */
1019: static unsigned char
1020: mime_enc_complete(EditLine *el, int ch)
1.1 christos 1021: {
1.9 christos 1022: static char word[LINESIZE];
1023: StringList *words;
1024: unsigned char rv;
1025: const LineInfo *lf;
1.11 christos 1026: size_t word_len;
1027: int dolist;
1.9 christos 1028:
1029: lf = el_line(el);
1.1 christos 1030:
1.9 christos 1031: #ifdef EMACS_CTRL_D_BINDING_HACK
1032: {
1033: int cc_ret;
1.11 christos 1034: if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
1.9 christos 1035: return cc_ret;
1036: }
1037: #endif /* EMACS_CTRL_D_BINDING_HACK */
1.1 christos 1038:
1.11 christos 1039: word_len = lf->cursor - lf->buffer;
1040: if (word_len >= sizeof(word) - 1)
1041: return CC_ERROR;
1.1 christos 1042:
1.9 christos 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: }
1.11 christos 1058: (void)strlcpy(word, lf->buffer, word_len + 1);
1.1 christos 1059:
1.11 christos 1060: if ((dolist = get_dolist(lf)) == -1)
1061: return CC_ERROR;
1062:
1063: rv = complete_ambiguous(el, word, dolist, words);
1.1 christos 1064:
1.9 christos 1065: sl_free(words, 1);
1.11 christos 1066: return rv;
1.1 christos 1067: }
1.9 christos 1068: #endif /* MIME_SUPPORT */
1069:
1.11 christos 1070:
1.9 christos 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.
1.11 christos 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.
1.9 christos 1084: */
1.1 christos 1085:
1.9 christos 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: }
1.1 christos 1094:
1.11 christos 1095: PUBLIC char *
1.9 christos 1096: my_gets(el_mode_t *em, const char *prompt, char *string)
1.1 christos 1097: {
1.9 christos 1098: int cnt;
1099: size_t len;
1100: const char *buf;
1101: HistEvent ev;
1.3 christos 1102: static char line[LINE_MAX];
1.1 christos 1103:
1.9 christos 1104: el_prompt = prompt;
1.1 christos 1105:
1106: if (string)
1.9 christos 1107: el_push(em->el, string);
1108:
1109: buf = el_gets(em->el, &cnt);
1.1 christos 1110:
1.3 christos 1111: if (buf == NULL || cnt <= 0) {
1112: if (cnt == 0)
1.9 christos 1113: (void)putc('\n', stdout);
1114: return NULL;
1.3 christos 1115: }
1.1 christos 1116:
1.11 christos 1117: if (buf[cnt - 1] == '\n')
1118: cnt--; /* trash the trailing LF */
1.9 christos 1119: len = MIN(sizeof(line) - 1, cnt);
1120: (void)memcpy(line, buf, len);
1.1 christos 1121: line[cnt] = '\0';
1122:
1.9 christos 1123: /* enter non-empty lines into history */
1124: if (em->hist) {
1125: const char *p;
1.11 christos 1126: p = skip_blank(line);
1.9 christos 1127: if (*p && history(em->hist, &ev, H_ENTER, line) == 0)
1128: (void)printf("Failed history entry: %s", line);
1129: }
1.1 christos 1130: return line;
1131: }
1132:
1.11 christos 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:
1.9 christos 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:
1.11.2.1! tron 1186: if ((em.el = el_init(getprogname(), stdin, stdout, stderr)) == NULL) {
! 1187: warn("el_init");
! 1188: return em;
! 1189: }
1.1 christos 1190:
1.9 christos 1191: (void)el_set(em.el, EL_PROMPT, show_prompt);
1.11.2.1! tron 1192: (void)el_set(em.el, EL_SIGNAL, 1); /* editline handles the signals. */
1.9 christos 1193:
1194: if (el_editor)
1195: (void)el_set(em.el, EL_EDITOR, el_editor);
1196:
1197: if (completer) {
1198: struct name *np;
1199: (void)el_set(em.el, EL_ADDFN, "mail-complete",
1200: "Context sensitive argument completion", completer);
1201: for (np = keys; np; np = np->n_flink)
1202: (void)el_set(em.el, EL_BIND, np->n_name,
1203: "mail-complete", NULL);
1204: }
1205:
1206: if (history_size) {
1207: HistEvent ev;
1.11.2.1! tron 1208: if ((em.hist = history_init()) == NULL) {
! 1209: warn("history_init");
! 1210: return em;
! 1211: }
! 1212: if (history(em.hist, &ev, H_SETSIZE, history_size) == -1)
1.9 christos 1213: (void)printf("history: %s\n", ev.str);
1214: (void)el_set(em.el, EL_HIST, history, em.hist);
1215: }
1216:
1217: (void)el_source(em.el, NULL); /* read ~/.editrc */
1218:
1219: return em;
1.1 christos 1220: }
1221:
1.9 christos 1222:
1223: struct el_modes_s elm = {
1224: .command = { .el = NULL, .hist = NULL, },
1225: .string = { .el = NULL, .hist = NULL, },
1226: .filec = { .el = NULL, .hist = NULL, },
1227: #ifdef MIME_SUPPORT
1228: .mime_enc = { .el = NULL, .hist = NULL, },
1229: #endif
1230: };
1231:
1.11 christos 1232: PUBLIC void
1.9 christos 1233: init_editline(void)
1.1 christos 1234: {
1.9 christos 1235: const char *mode;
1236: int hist_size;
1237: struct name *keys;
1238: char *cp;
1239:
1240: mode = value(ENAME_EL_EDITOR);
1.3 christos 1241:
1.9 christos 1242: cp = value(ENAME_EL_HISTORY_SIZE);
1243: hist_size = cp ? atoi(cp) : 0;
1.1 christos 1244:
1.9 christos 1245: cp = value(ENAME_EL_COMPLETION_KEYS);
1246: keys = cp && *cp ? lexpand(cp, 0) : NULL;
1.1 christos 1247:
1.9 christos 1248: elm.command = init_el_mode(mode, mail_complete, keys, hist_size);
1249: elm.filec = init_el_mode(mode, file_complete, keys, 0);
1250: elm.string = init_el_mode(mode, NULL, NULL, 0);
1251: #ifdef MIME_SUPPORT
1252: elm.mime_enc = init_el_mode(mode, mime_enc_complete, keys, 0);
1253: #endif
1.1 christos 1254: return;
1255: }
1256:
1.9 christos 1257: #endif /* USE_EDITLINE */
CVSweb <webmaster@jp.NetBSD.org>