complete.c revision 1.6 1 /* $NetBSD: complete.c,v 1.6 2006/09/27 15:21:26 christos Exp $ */
2
3 /*-
4 * Copyright (c) 1997-2000,2005 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 /* Most of this is derived or copied from src/usr.bin/ftp/complete.c (1.41). */
40
41
42 #ifdef USE_READLINE
43
44 #include <sys/cdefs.h>
45 #ifndef lint
46 __RCSID("$NetBSD: complete.c,v 1.6 2006/09/27 15:21:26 christos Exp $");
47 #endif /* not lint */
48
49 /*
50 * FTP user program - command and file completion routines
51 */
52
53 #include <sys/stat.h>
54
55 #include <ctype.h>
56 #include <err.h>
57 #include <dirent.h>
58 #include <glob.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <histedit.h>
63 #include <sys/param.h>
64 #include <stringlist.h>
65
66 #include "rcv.h" /* includes "glob.h" */
67 #include "extern.h"
68 #include "complete.h"
69
70 /*
71 * Global variables
72 */
73 static int doglob = 1; /* glob local file names */
74
75 #define ttyout stdout
76 #define ttywidth screenwidth /* in "glob.h" */
77 #define ttyheight screenheight /* in "glob.h" */
78
79
80 /* This should find the first command that matches the name given or
81 * NULL if none. Use the routine from mail in lex.c.
82 */
83 #define getcmd(w) lex(w)
84
85
86 /************************************************************************/
87 /* from src/usr.bin/ftp/utils.h (1.135) */
88
89 /*
90 * List words in stringlist, vertically arranged
91 */
92 static void
93 list_vertical(StringList *sl)
94 {
95 int i, j;
96 int columns, lines;
97 char *p;
98 size_t w, width;
99
100 width = 0;
101
102 for (i = 0 ; i < sl->sl_cur ; i++) {
103 w = strlen(sl->sl_str[i]);
104 if (w > width)
105 width = w;
106 }
107 width = (width + 8) &~ 7;
108
109 columns = ttywidth / width;
110 if (columns == 0)
111 columns = 1;
112 lines = (sl->sl_cur + columns - 1) / columns;
113 for (i = 0; i < lines; i++) {
114 for (j = 0; j < columns; j++) {
115 p = sl->sl_str[j * lines + i];
116 if (p)
117 fputs(p, ttyout);
118 if (j * lines + i + lines >= sl->sl_cur) {
119 putc('\n', ttyout);
120 break;
121 }
122 if (p) {
123 w = strlen(p);
124 while (w < width) {
125 w = (w + 8) &~ 7;
126 (void)putc('\t', ttyout);
127 }
128 }
129 }
130 }
131 }
132
133 /*
134 * Copy characters from src into dst, \ quoting characters that require it
135 */
136 static void
137 ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
138 {
139 int di, si;
140
141 for (di = si = 0;
142 src[si] != '\0' && di < dstlen && si < srclen;
143 di++, si++) {
144 switch (src[si]) {
145 case '\\':
146 case ' ':
147 case '\t':
148 case '\r':
149 case '\n':
150 case '"':
151 dst[di++] = '\\';
152 if (di >= dstlen)
153 break;
154 /* FALLTHROUGH */
155 default:
156 dst[di] = src[si];
157 }
158 }
159 dst[di] = '\0';
160 }
161
162 /*
163 * sl_init() with inbuilt error checking
164 */
165 static StringList *
166 ftp_sl_init(void)
167 {
168 StringList *p;
169
170 p = sl_init();
171 if (p == NULL)
172 err(1, "Unable to allocate memory for stringlist");
173 return (p);
174 }
175
176
177 /*
178 * sl_add() with inbuilt error checking
179 */
180 static void
181 ftp_sl_add(StringList *sl, char *i)
182 {
183
184 if (sl_add(sl, i) == -1)
185 err(1, "Unable to add `%s' to stringlist", i);
186 }
187
188 /*
189 * strdup() with inbuilt error checking
190 */
191 static char *
192 ftp_strdup(const char *str)
193 {
194 char *s;
195
196 if (str == NULL)
197 errx(1, "ftp_strdup() called with NULL argument");
198 s = strdup(str);
199 if (s == NULL)
200 err(1, "Unable to allocate memory for string copy");
201 return (s);
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 (ftp_strdup(pattern));
220
221 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
222 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 = ftp_strdup(gl.gl_pathv[0]);
229 globfree(&gl);
230 return (p);
231 }
232
233 /* from src/usr.bin/ftp/utils.h (1.135) */
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 * list 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 list, 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 (!list) {
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 putc('\n', ttyout);
298 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
299 list_vertical(words);
300 return (CC_REDISPLAY);
301 }
302
303 /*
304 * Complete a mail command.
305 */
306 static unsigned char
307 complete_command(EditLine *el, char *word, int list)
308 {
309 const struct cmd *c;
310 StringList *words;
311 size_t wordlen;
312 unsigned char rv;
313
314 words = ftp_sl_init();
315 wordlen = strlen(word);
316
317 for (c = cmdtab; c->c_name != NULL; c++) {
318 if (wordlen > strlen(c->c_name))
319 continue;
320 if (strncmp(word, c->c_name, wordlen) == 0)
321 ftp_sl_add(words, __UNCONST(c->c_name));
322 }
323
324 rv = complete_ambiguous(el, word, list, words);
325 if (rv == CC_REFRESH) {
326 if (el_insertstr(el, " ") == -1)
327 rv = CC_ERROR;
328 }
329 sl_free(words, 0);
330 return (rv);
331 }
332
333 /*
334 * Complete a local filename.
335 */
336 static unsigned char
337 complete_filename(EditLine *el, char *word, int list)
338 {
339 StringList *words;
340 char dir[MAXPATHLEN];
341 char *fname;
342 DIR *dd;
343 struct dirent *dp;
344 unsigned char rv;
345 size_t len;
346
347 if ((fname = strrchr(word, '/')) == NULL) {
348 dir[0] = '.';
349 dir[1] = '\0';
350 fname = word;
351 } else {
352 if (fname == word) {
353 dir[0] = '/';
354 dir[1] = '\0';
355 } else
356 (void)strlcpy(dir, word, fname - word + 1);
357 fname++;
358 }
359 if (dir[0] == '~') {
360 char *p;
361
362 if ((p = globulize(dir)) == NULL)
363 return (CC_ERROR);
364 (void)strlcpy(dir, p, sizeof(dir));
365 free(p);
366 }
367
368 if ((dd = opendir(dir)) == NULL)
369 return (CC_ERROR);
370
371 words = ftp_sl_init();
372 len = strlen(fname);
373
374 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
375 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
376 continue;
377
378 #if defined(DIRENT_MISSING_D_NAMLEN)
379 if (len > strlen(dp->d_name))
380 continue;
381 #else
382 if (len > dp->d_namlen)
383 continue;
384 #endif
385 if (strncmp(fname, dp->d_name, len) == 0) {
386 char *tcp;
387
388 tcp = ftp_strdup(dp->d_name);
389 ftp_sl_add(words, tcp);
390 }
391 }
392 closedir(dd);
393
394 rv = complete_ambiguous(el, fname, list, words);
395 if (rv == CC_REFRESH) {
396 struct stat sb;
397 char path[MAXPATHLEN];
398
399 (void)strlcpy(path, dir, sizeof(path));
400 (void)strlcat(path, "/", sizeof(path));
401 (void)strlcat(path, words->sl_str[0], sizeof(path));
402
403 if (stat(path, &sb) >= 0) {
404 char suffix[2] = " ";
405
406 if (S_ISDIR(sb.st_mode))
407 suffix[0] = '/';
408 if (el_insertstr(el, suffix) == -1)
409 rv = CC_ERROR;
410 }
411 }
412 sl_free(words, 1);
413 return (rv);
414 }
415
416 static int
417 find_execs(char *word, char *path, StringList *list)
418 {
419 char *sep;
420 char *dir=path;
421 DIR *dd;
422 struct dirent *dp;
423 int len = strlen(word);
424 uid_t uid = getuid();
425 gid_t gid = getgid();
426
427 for (sep=dir ; sep ; dir=sep+1) {
428 if ((sep=strchr(dir, ':')) != NULL) {
429 *sep=0;
430 }
431
432 if ((dd = opendir(dir)) == NULL) {
433 perror("dir");
434 return -1;
435 }
436
437 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
438
439 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
440 continue;
441
442 #if defined(DIRENT_MISSING_D_NAMLEN)
443 if (len > strlen(dp->d_name))
444 continue;
445 #else
446 if (len > dp->d_namlen)
447 continue;
448 #endif
449
450 if (strncmp(word, dp->d_name, len) == 0) {
451 struct stat sb;
452 char pathname[ MAXPATHLEN ];
453 unsigned mask;
454
455 snprintf(pathname, sizeof(pathname), "%s/%s", dir, dp->d_name);
456 if (stat(pathname, &sb) != 0) {
457 perror(pathname);
458 continue;
459 }
460
461 mask = 0001;
462 if (sb.st_uid == uid) mask |= 0100;
463 if (sb.st_gid == gid) mask |= 0010;
464
465 if ((sb.st_mode & mask) != 0) {
466 char *tcp;
467 tcp = strdup(dp->d_name);
468 sl_add(list, tcp);
469 }
470 }
471
472 }
473
474 closedir(dd);
475 }
476
477 return 0;
478 }
479
480
481 /*
482 * Complete a local executable
483 */
484 static unsigned char
485 complete_executable(EditLine *el, char *word, int list)
486 {
487 StringList *words;
488 char dir[ MAXPATHLEN ];
489 char *fname;
490 unsigned char rv;
491 int error;
492
493 if ((fname = strrchr(word, '/')) == NULL) {
494 dir[0] = '\0'; /* walk the path */
495 fname = word;
496 } else {
497 if (fname == word) {
498 dir[0] = '/';
499 dir[1] = '\0';
500 } else {
501 (void)strncpy(dir, word, fname - word);
502 dir[fname - word] = '\0';
503 }
504 fname++;
505 }
506
507 words = sl_init();
508
509 if (*dir == '\0') { /* walk path */
510 char *env;
511 char *path;
512 int len;
513 env = getenv("PATH");
514 len = strlen(env);
515 path = salloc(len + 1);
516 strcpy(path, env);
517 error = find_execs(word, path, words);
518 }
519 else { /* check specified dir only */
520 error = find_execs(word, dir, words);
521 }
522 if (error != 0)
523 return CC_ERROR;
524
525 rv = complete_ambiguous(el, fname, list, words);
526
527 if (rv == CC_REFRESH)
528 if (el_insertstr(el, " ") == -1)
529 rv = CC_ERROR;
530
531 sl_free(words, 1);
532
533 return (rv);
534 }
535
536
537 static unsigned
538 char complete_set(EditLine *el, char *word, int list)
539 {
540 struct var *vp;
541 char **ap;
542 char **p;
543 int h;
544 int s;
545 int len = strlen(word);
546 StringList *words;
547 unsigned char rv;
548
549 words = sl_init();
550
551 /* allocate space for variables table */
552 for (h = 0, s = 1; h < HSHSIZE; h++)
553 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
554 s++;
555 ap = (char **) salloc(s * sizeof *ap);
556
557 /* save pointers */
558 for (h = 0, p = ap; h < HSHSIZE; h++)
559 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
560 *p++ = vp->v_name;
561 *p = NULL;
562
563 /* sort pointers */
564 sort(ap);
565
566 /* complete on list */
567 for (p = ap; *p != NULL; p++) {
568 if (len == 0 || strncmp(*p, word, len) == 0) {
569 char *tcp;
570 tcp = strdup(*p);
571 sl_add(words, tcp);
572 }
573 }
574
575 rv = complete_ambiguous(el, word, list, words);
576
577 sl_free(words, 1);
578
579 return(rv);
580 }
581
582
583
584
585
586 static unsigned char
587 complete_alias(EditLine *el, char *word, int list)
588 {
589 struct grouphead *gh;
590 char **ap;
591 char **p;
592 int h;
593 int s;
594 int len = strlen(word);
595 StringList *words;
596 unsigned char rv;
597
598 words = sl_init();
599
600 /* allocate space for alias table */
601 for (h = 0, s = 1; h < HSHSIZE; h++)
602 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
603 s++;
604 ap = (char **) salloc(s * sizeof *ap);
605
606 /* save pointers */
607 for (h = 0, p = ap; h < HSHSIZE; h++)
608 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
609 *p++ = gh->g_name;
610 *p = NULL;
611
612 /* sort pointers */
613 sort(ap);
614
615 /* complete on list */
616 for (p = ap; *p != NULL; p++) {
617 if (len == 0 || strncmp(*p, word, len) == 0) {
618 char *tcp;
619 tcp = strdup(*p);
620 sl_add(words, tcp);
621 }
622 }
623
624 rv = complete_ambiguous(el, word, list, words);
625 if (rv == CC_REFRESH)
626 if (el_insertstr(el, " ") == -1)
627 rv = CC_ERROR;
628
629 sl_free(words, 1);
630
631 return(rv);
632 }
633
634
635
636 static char *stringbase; /* current scan point in line buffer */
637 static char *argbase; /* current storage point in arg buffer */
638 static StringList *marg_sl; /* stringlist containing margv */
639 static int margc; /* count of arguments on input line */
640 #define margv (marg_sl->sl_str) /* args parsed from input line */
641
642 static char *cursor_pos;
643 static int cursor_argc;
644 static int cursor_argo;
645
646 static void
647 init_complete(void)
648 {
649 marg_sl = sl_init();
650 return;
651 }
652
653
654
655 /************************************************************************/
656 /* from /usr/src/usr.bin/ftp/main.c(1.101) */
657
658 static int slrflag;
659 static char *altarg; /* argv[1] with no shell-like preprocessing */
660
661 #ifdef NO_EDITCOMPLETE
662 #define INC_CHKCURSOR(x) (x)++
663 #else /* !NO_EDITCOMPLETE */
664 #define INC_CHKCURSOR(x) \
665 do { \
666 (x)++ ; \
667 if (x == cursor_pos) { \
668 cursor_argc = margc; \
669 cursor_argo = ap - argbase; \
670 cursor_pos = NULL; \
671 } \
672 } while(0)
673 #endif /* !NO_EDITCOMPLETE */
674
675 /*
676 * Parse string into argbuf;
677 * implemented with FSM to
678 * handle quoting and strings
679 */
680 static char *
681 slurpstring(void)
682 {
683 int got_one = 0;
684 char *sb = stringbase;
685 char *ap = argbase;
686 char *tmp = argbase; /* will return this if token found */
687
688 if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */
689 switch (slrflag) { /* and $ as token for macro invoke */
690 case 0:
691 slrflag++;
692 INC_CHKCURSOR(stringbase);
693 return __UNCONST((*sb == '!') ? "!" : "$");
694 /* NOTREACHED */
695 case 1:
696 slrflag++;
697 altarg = stringbase;
698 break;
699 default:
700 break;
701 }
702 }
703
704 S0:
705 switch (*sb) {
706
707 case '\0':
708 goto OUT;
709
710 case ' ':
711 case '\t':
712 INC_CHKCURSOR(sb);
713 goto S0;
714
715 default:
716 switch (slrflag) {
717 case 0:
718 slrflag++;
719 break;
720 case 1:
721 slrflag++;
722 altarg = sb;
723 break;
724 default:
725 break;
726 }
727 goto S1;
728 }
729
730 S1:
731 switch (*sb) {
732
733 case ' ':
734 case '\t':
735 case '\0':
736 goto OUT; /* end of token */
737
738 case '\\':
739 INC_CHKCURSOR(sb);
740 goto S2; /* slurp next character */
741
742 case '"':
743 INC_CHKCURSOR(sb);
744 goto S3; /* slurp quoted string */
745
746 default:
747 /* the first arg (command) is special - see execute() in lex.c */
748 if (margc == 0 && index(" \t0123456789$^.:/-+*'\"", *sb))
749 goto OUT;
750
751 *ap = *sb; /* add character to token */
752 ap++;
753 INC_CHKCURSOR(sb);
754 got_one = 1;
755 goto S1;
756 }
757
758 S2:
759 switch (*sb) {
760
761 case '\0':
762 goto OUT;
763
764 default:
765 *ap = *sb;
766 ap++;
767 INC_CHKCURSOR(sb);
768 got_one = 1;
769 goto S1;
770 }
771
772 S3:
773 switch (*sb) {
774
775 case '\0':
776 goto OUT;
777
778 case '"':
779 INC_CHKCURSOR(sb);
780 goto S1;
781
782 default:
783 *ap = *sb;
784 ap++;
785 INC_CHKCURSOR(sb);
786 got_one = 1;
787 goto S3;
788 }
789
790 OUT:
791 if (got_one)
792 *ap++ = '\0';
793 argbase = ap; /* update storage pointer */
794 stringbase = sb; /* update scan pointer */
795 if (got_one) {
796 return (tmp);
797 }
798 switch (slrflag) {
799 case 0:
800 slrflag++;
801 break;
802 case 1:
803 slrflag++;
804 altarg = NULL;
805 break;
806 default:
807 break;
808 }
809 return (NULL);
810 }
811
812
813 /*
814 * Slice a string up into argc/argv.
815 */
816 static void
817 makeargv(char *line)
818 {
819 static char argbuf[ LINESIZE ]; /* argument storage buffer */
820 char *argp;
821
822 stringbase = line; /* scan from first of buffer */
823 argbase = argbuf; /* store from first of buffer */
824 slrflag = 0;
825 marg_sl->sl_cur = 0; /* reset to start of marg_sl */
826 for (margc = 0; ; margc++) {
827 argp = slurpstring();
828 ftp_sl_add(marg_sl, argp);
829 if (argp == NULL)
830 break;
831 }
832 #ifndef NO_EDITCOMPLETE
833 if (cursor_pos == line) {
834 cursor_argc = 0;
835 cursor_argo = 0;
836 } else if (cursor_pos != NULL) {
837 cursor_argc = margc;
838 cursor_argo = strlen(margv[margc-1]);
839 }
840 #endif /* !NO_EDITCOMPLETE */
841 }
842
843 /* from /usr/src/usr.bin/ftp/main.c(1.101) */
844 /************************************************************************/
845
846
847 /*
848 * Generic complete routine
849 */
850 static unsigned char
851 complete(EditLine *el, int ch)
852 {
853 static char line[LINESIZE]; /* input line buffer */
854 static char word[LINESIZE];
855 static int lastc_argc, lastc_argo;
856
857 const struct cmd *c;
858 const LineInfo *lf;
859 int celems, dolist, cmpltype;
860 size_t len;
861
862 lf = el_line(el);
863 len = lf->lastchar - lf->buffer;
864 #if 1
865 if (ch == 04) { /* CTRL-D is special */
866 if (len == 0)
867 return (CC_EOF);
868 if (lf->lastchar != lf->cursor) {
869 el_push(el, __UNCONST("")); /* delete current char without using ^D */
870 return (CC_NORM);
871 }
872 }
873 #endif
874 if (len >= sizeof(line))
875 return (CC_ERROR);
876 (void)strlcpy(line, lf->buffer, len + 1);
877 cursor_pos = line + (lf->cursor - lf->buffer);
878 lastc_argc = cursor_argc; /* remember last cursor pos */
879 lastc_argo = cursor_argo;
880 makeargv(line); /* build argc/argv of current line */
881
882 if (cursor_argo >= sizeof(word))
883 return (CC_ERROR);
884
885 dolist = 0;
886 /* if cursor and word are the same, list alternatives */
887 if (lastc_argc == cursor_argc && lastc_argo == cursor_argo
888 && strncmp(word, margv[cursor_argc] ? margv[cursor_argc] : "",
889 cursor_argo) == 0)
890 dolist = 1;
891 else if (cursor_argc < margc)
892 (void)strlcpy(word, margv[cursor_argc], cursor_argo + 1);
893 word[cursor_argo] = '\0';
894
895 if (cursor_argc == 0)
896 return (complete_command(el, word, dolist));
897
898 c = getcmd(margv[0]);
899 if (c == NULL)
900 return (CC_ERROR);
901 celems = strlen(c->c_complete);
902
903 /* check for 'continuation' completes (which are uppercase) */
904 if ((cursor_argc > celems) && (celems > 0)
905 && isupper((unsigned char) c->c_complete[celems-1]))
906 cursor_argc = celems;
907
908 if (cursor_argc > celems)
909 return (CC_ERROR);
910
911 cmpltype = c->c_complete[cursor_argc - 1];
912 switch (cmpltype) {
913 case 'a': /* alias complete */
914 case 'A':
915 return (complete_alias(el, word, dolist));
916
917 case 'c': /* command complete */
918 case 'C':
919 return (complete_command(el, word, dolist));
920
921 case 'f': /* filename complete */
922 case 'F':
923 return (complete_filename(el, word, dolist));
924
925 case 'n': /* no complete */
926 case 'N': /* no complete */
927 return (CC_ERROR);
928
929 case 's':
930 case 'S':
931 return (complete_set(el, word, dolist));
932
933 case 'x': /* executable complete */
934 case 'X':
935 return (complete_executable(el, word, dolist));
936
937 default:
938 errx(1, "unknown complete type `%c'", cmpltype);
939 return (CC_ERROR);
940 }
941 /* NOTREACHED */
942 }
943
944
945 /*************************************************************************/
946 /* Most of this was originally taken directly from the readline manpage. */
947
948 static struct {
949 EditLine *el; /* editline(3) with completion and history */
950 EditLine *elo; /* editline(3) editline only, no completion */
951 History *hist; /* editline(3) history structure */
952 const char *prompt; /* prompt */
953 } rl_global = {
954 .el = NULL,
955 .hist = NULL,
956 .prompt = NULL
957 };
958
959 char *
960 rl_gets(const char *prompt)
961 {
962 int cnt;
963 const char *buf;
964 HistEvent ev;
965 static char line[LINE_MAX];
966
967 rl_global.prompt = prompt;
968 buf = el_gets(rl_global.el, &cnt);
969
970 if (buf == NULL || cnt <= 0)
971 return NULL;
972
973 /* enter the line into history */
974 if (history(rl_global.hist, &ev, H_ENTER, buf) == 0)
975 printf("Failed history entry: %s", buf);
976 #ifdef DEBUG
977 else
978 printf("history entry: %s\n", ev.str);
979 #endif
980
981 cnt--; /* trash the trailing LF */
982 cnt = MIN(sizeof(line) - 1, cnt);
983 (void)memcpy(line, buf, cnt);
984 line[cnt] = '\0';
985
986 return line;
987 }
988
989
990 /*
991 * Edit a line containing string, with no history or completion.
992 */
993 char *
994 rl_getline(const char *prompt, char *string)
995 {
996 static char line[LINE_MAX];
997 const char *buf;
998 int cnt;
999
1000 rl_global.prompt = prompt;
1001
1002 if (string)
1003 el_push(rl_global.elo, string);
1004
1005 buf = el_gets(rl_global.elo, &cnt);
1006 if (buf == NULL || cnt <= 0) {
1007 if (cnt == 0)
1008 fputc('\n', stdout);
1009 line[0] = '\0';
1010 return line;
1011 }
1012
1013 cnt--;
1014 cnt = MIN(sizeof(line) - 1, cnt);
1015 (void)memcpy(line, buf, cnt);
1016 line[cnt] = '\0';
1017
1018 return line;
1019 }
1020
1021
1022 static const char *
1023 show_prompt(EditLine *e __attribute__((unused)))
1024 {
1025 return rl_global.prompt;
1026 }
1027
1028 void
1029 init_readline(void)
1030 {
1031 HistEvent ev;
1032 const char *el_editor;
1033 const char *el_history_size;
1034 char *el_completion_keys;
1035
1036 rl_global.hist = history_init(); /* init the builtin history */
1037 el_history_size = value("el_history_size") ? : "0";
1038 if (history(rl_global.hist, &ev, H_SETSIZE, atoi(el_history_size)))
1039 printf("history: %s\n", ev.str);
1040
1041 rl_global.el = el_init(getprogname(), stdin, stdout, stderr);
1042 rl_global.elo = el_init(getprogname(), stdin, stdout, stderr);
1043
1044 el_editor = value("el_editor");
1045 if (el_editor) {
1046 el_set(rl_global.el, EL_EDITOR, el_editor);
1047 el_set(rl_global.elo, EL_EDITOR, el_editor);
1048 }
1049
1050 el_set(rl_global.el, EL_PROMPT, show_prompt);
1051 el_set(rl_global.elo, EL_PROMPT, show_prompt);
1052 el_set(rl_global.el, EL_HIST, history, rl_global.hist); /* use history */
1053 el_source(rl_global.el, NULL); /* read ~/.editrc */
1054
1055 /* add local file completion, bind to TAB */
1056 el_set(rl_global.el, EL_ADDFN, "mail-complete",
1057 "Context sensitive argument completion",
1058 complete);
1059
1060 el_completion_keys = value("el_completion_keys");
1061 if (el_completion_keys && *el_completion_keys) {
1062 struct name *np, *nq;
1063 np = lexpand(el_completion_keys, 0);
1064 for (nq = np ; nq ; nq = nq->n_flink)
1065 el_set(rl_global.el, EL_BIND, nq->n_name, "mail-complete", NULL);
1066 }
1067
1068 init_complete();
1069
1070 el_set(rl_global.el, EL_SIGNAL, 1);
1071 el_set(rl_global.elo, EL_SIGNAL, 1);
1072
1073 return;
1074 }
1075
1076 /************************************************************************/
1077 #endif /* USE_READLINE */
1078