Annotation of src/lib/libedit/filecomplete.c, Revision 1.28
1.28 ! christos 1: /* $NetBSD: filecomplete.c,v 1.27 2011/07/29 15:16:33 christos Exp $ */
1.1 dsl 2:
3: /*-
4: * Copyright (c) 1997 The NetBSD Foundation, Inc.
5: * All rights reserved.
6: *
7: * This code is derived from software contributed to The NetBSD Foundation
8: * by Jaromir Dolecek.
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: *
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.
30: */
31:
32: #include "config.h"
33: #if !defined(lint) && !defined(SCCSID)
1.28 ! christos 34: __RCSID("$NetBSD: filecomplete.c,v 1.27 2011/07/29 15:16:33 christos Exp $");
1.1 dsl 35: #endif /* not lint && not SCCSID */
36:
37: #include <sys/types.h>
38: #include <sys/stat.h>
39: #include <stdio.h>
40: #include <dirent.h>
41: #include <string.h>
42: #include <pwd.h>
43: #include <ctype.h>
44: #include <stdlib.h>
45: #include <unistd.h>
46: #include <limits.h>
47: #include <errno.h>
48: #include <fcntl.h>
1.28 ! christos 49:
1.1 dsl 50: #include "el.h"
51: #include "fcns.h" /* for EL_NUM_FCNS */
52: #include "histedit.h"
53: #include "filecomplete.h"
54:
1.22 dholland 55: static const Char break_chars[] = { ' ', '\t', '\n', '"', '\\', '\'', '`', '@',
1.17 christos 56: '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0' };
1.1 dsl 57:
58:
59: /********************************/
60: /* completion functions */
61:
62: /*
63: * does tilde expansion of strings of type ``~user/foo''
64: * if ``user'' isn't valid user name or ``txt'' doesn't start
65: * w/ '~', returns pointer to strdup()ed copy of ``txt''
66: *
67: * it's callers's responsibility to free() returned string
68: */
69: char *
1.7 christos 70: fn_tilde_expand(const char *txt)
1.1 dsl 71: {
1.25 christos 72: #if defined(HAVE_GETPW_R_POSIX) || defined(HAVE_GETPW_R_DRAFT)
73: struct passwd pwres;
74: char pwbuf[1024];
75: #endif
76: struct passwd *pass;
1.1 dsl 77: char *temp;
78: size_t len = 0;
79:
80: if (txt[0] != '~')
1.27 christos 81: return strdup(txt);
1.1 dsl 82:
83: temp = strchr(txt + 1, '/');
84: if (temp == NULL) {
85: temp = strdup(txt + 1);
86: if (temp == NULL)
87: return NULL;
88: } else {
89: len = temp - txt + 1; /* text until string after slash */
1.26 christos 90: temp = el_malloc(len * sizeof(*temp));
1.1 dsl 91: if (temp == NULL)
92: return NULL;
93: (void)strncpy(temp, txt + 1, len - 2);
94: temp[len - 2] = '\0';
95: }
1.3 dsl 96: if (temp[0] == 0) {
1.24 christos 97: #ifdef HAVE_GETPW_R_POSIX
98: if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf),
99: &pass) != 0)
100: pass = NULL;
101: #elif HAVE_GETPW_R_DRAFT
102: pass = getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf));
103: #else
104: pass = getpwuid(getuid());
105: #endif
1.3 dsl 106: } else {
1.24 christos 107: #ifdef HAVE_GETPW_R_POSIX
1.3 dsl 108: if (getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf), &pass) != 0)
109: pass = NULL;
1.24 christos 110: #elif HAVE_GETPW_R_DRAFT
111: pass = getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf));
112: #else
113: pass = getpwnam(temp);
114: #endif
1.3 dsl 115: }
1.26 christos 116: el_free(temp); /* value no more needed */
1.1 dsl 117: if (pass == NULL)
1.27 christos 118: return strdup(txt);
1.1 dsl 119:
120: /* update pointer txt to point at string immedially following */
121: /* first slash */
122: txt += len;
123:
1.26 christos 124: len = strlen(pass->pw_dir) + 1 + strlen(txt) + 1;
125: temp = el_malloc(len * sizeof(*temp));
1.1 dsl 126: if (temp == NULL)
127: return NULL;
1.26 christos 128: (void)snprintf(temp, len, "%s/%s", pass->pw_dir, txt);
1.1 dsl 129:
1.27 christos 130: return temp;
1.1 dsl 131: }
132:
133:
134: /*
135: * return first found file name starting by the ``text'' or NULL if no
136: * such file can be found
137: * value of ``state'' is ignored
138: *
139: * it's caller's responsibility to free returned string
140: */
1.2 dsl 141: char *
1.7 christos 142: fn_filename_completion_function(const char *text, int state)
1.1 dsl 143: {
144: static DIR *dir = NULL;
1.3 dsl 145: static char *filename = NULL, *dirname = NULL, *dirpath = NULL;
1.1 dsl 146: static size_t filename_len = 0;
147: struct dirent *entry;
148: char *temp;
149: size_t len;
150:
151: if (state == 0 || dir == NULL) {
152: temp = strrchr(text, '/');
153: if (temp) {
154: char *nptr;
155: temp++;
1.26 christos 156: nptr = el_realloc(filename, (strlen(temp) + 1) *
157: sizeof(*nptr));
1.1 dsl 158: if (nptr == NULL) {
1.26 christos 159: el_free(filename);
1.19 christos 160: filename = NULL;
1.1 dsl 161: return NULL;
162: }
163: filename = nptr;
164: (void)strcpy(filename, temp);
165: len = temp - text; /* including last slash */
1.19 christos 166:
1.26 christos 167: nptr = el_realloc(dirname, (len + 1) *
168: sizeof(*nptr));
1.1 dsl 169: if (nptr == NULL) {
1.26 christos 170: el_free(dirname);
1.19 christos 171: dirname = NULL;
1.1 dsl 172: return NULL;
173: }
174: dirname = nptr;
175: (void)strncpy(dirname, text, len);
176: dirname[len] = '\0';
177: } else {
1.26 christos 178: el_free(filename);
1.1 dsl 179: if (*text == 0)
180: filename = NULL;
181: else {
182: filename = strdup(text);
183: if (filename == NULL)
184: return NULL;
185: }
1.26 christos 186: el_free(dirname);
1.1 dsl 187: dirname = NULL;
188: }
189:
190: if (dir != NULL) {
191: (void)closedir(dir);
192: dir = NULL;
193: }
1.3 dsl 194:
195: /* support for ``~user'' syntax */
1.19 christos 196:
1.26 christos 197: el_free(dirpath);
1.19 christos 198: dirpath = NULL;
199: if (dirname == NULL) {
200: if ((dirname = strdup("")) == NULL)
201: return NULL;
202: dirpath = strdup("./");
203: } else if (*dirname == '~')
1.7 christos 204: dirpath = fn_tilde_expand(dirname);
1.4 christos 205: else
206: dirpath = strdup(dirname);
207:
208: if (dirpath == NULL)
209: return NULL;
210:
1.3 dsl 211: dir = opendir(dirpath);
1.1 dsl 212: if (!dir)
1.27 christos 213: return NULL; /* cannot open the directory */
1.3 dsl 214:
215: /* will be used in cycle */
216: filename_len = filename ? strlen(filename) : 0;
1.1 dsl 217: }
218:
219: /* find the match */
220: while ((entry = readdir(dir)) != NULL) {
221: /* skip . and .. */
222: if (entry->d_name[0] == '.' && (!entry->d_name[1]
223: || (entry->d_name[1] == '.' && !entry->d_name[2])))
224: continue;
225: if (filename_len == 0)
226: break;
227: /* otherwise, get first entry where first */
228: /* filename_len characters are equal */
229: if (entry->d_name[0] == filename[0]
1.13 apb 230: #if HAVE_STRUCT_DIRENT_D_NAMLEN
231: && entry->d_namlen >= filename_len
232: #else
1.1 dsl 233: && strlen(entry->d_name) >= filename_len
234: #endif
235: && strncmp(entry->d_name, filename,
236: filename_len) == 0)
237: break;
238: }
239:
240: if (entry) { /* match found */
241:
1.13 apb 242: #if HAVE_STRUCT_DIRENT_D_NAMLEN
243: len = entry->d_namlen;
244: #else
1.3 dsl 245: len = strlen(entry->d_name);
1.1 dsl 246: #endif
247:
1.26 christos 248: len = strlen(dirname) + len + 1;
249: temp = el_malloc(len * sizeof(*temp));
1.3 dsl 250: if (temp == NULL)
251: return NULL;
1.26 christos 252: (void)snprintf(temp, len, "%s%s", dirname, entry->d_name);
1.1 dsl 253: } else {
254: (void)closedir(dir);
255: dir = NULL;
256: temp = NULL;
257: }
258:
1.27 christos 259: return temp;
1.1 dsl 260: }
261:
262:
1.6 christos 263: static const char *
264: append_char_function(const char *name)
265: {
266: struct stat stbuf;
1.7 christos 267: char *expname = *name == '~' ? fn_tilde_expand(name) : NULL;
1.12 christos 268: const char *rs = " ";
1.6 christos 269:
270: if (stat(expname ? expname : name, &stbuf) == -1)
271: goto out;
272: if (S_ISDIR(stbuf.st_mode))
273: rs = "/";
274: out:
275: if (expname)
1.26 christos 276: el_free(expname);
1.6 christos 277: return rs;
278: }
1.1 dsl 279: /*
280: * returns list of completions for text given
1.5 christos 281: * non-static for readline.
1.1 dsl 282: */
1.5 christos 283: char ** completion_matches(const char *, char *(*)(const char *, int));
284: char **
1.1 dsl 285: completion_matches(const char *text, char *(*genfunc)(const char *, int))
286: {
287: char **match_list = NULL, *retstr, *prevstr;
288: size_t match_list_len, max_equal, which, i;
289: size_t matches;
290:
291: matches = 0;
292: match_list_len = 1;
293: while ((retstr = (*genfunc) (text, (int)matches)) != NULL) {
294: /* allow for list terminator here */
295: if (matches + 3 >= match_list_len) {
296: char **nmatch_list;
297: while (matches + 3 >= match_list_len)
298: match_list_len <<= 1;
1.26 christos 299: nmatch_list = el_realloc(match_list,
300: match_list_len * sizeof(*nmatch_list));
1.1 dsl 301: if (nmatch_list == NULL) {
1.26 christos 302: el_free(match_list);
1.1 dsl 303: return NULL;
304: }
305: match_list = nmatch_list;
306:
307: }
308: match_list[++matches] = retstr;
309: }
310:
311: if (!match_list)
312: return NULL; /* nothing found */
313:
314: /* find least denominator and insert it to match_list[0] */
315: which = 2;
316: prevstr = match_list[1];
317: max_equal = strlen(prevstr);
318: for (; which <= matches; which++) {
319: for (i = 0; i < max_equal &&
320: prevstr[i] == match_list[which][i]; i++)
321: continue;
322: max_equal = i;
323: }
324:
1.26 christos 325: retstr = el_malloc((max_equal + 1) * sizeof(*retstr));
1.1 dsl 326: if (retstr == NULL) {
1.26 christos 327: el_free(match_list);
1.1 dsl 328: return NULL;
329: }
330: (void)strncpy(retstr, match_list[1], max_equal);
331: retstr[max_equal] = '\0';
332: match_list[0] = retstr;
333:
334: /* add NULL as last pointer to the array */
335: match_list[matches + 1] = (char *) NULL;
336:
1.27 christos 337: return match_list;
1.1 dsl 338: }
339:
340: /*
341: * Sort function for qsort(). Just wrapper around strcasecmp().
342: */
343: static int
344: _fn_qsort_string_compare(const void *i1, const void *i2)
345: {
346: const char *s1 = ((const char * const *)i1)[0];
347: const char *s2 = ((const char * const *)i2)[0];
348:
349: return strcasecmp(s1, s2);
350: }
351:
352: /*
353: * Display list of strings in columnar format on readline's output stream.
1.21 dholland 354: * 'matches' is list of strings, 'num' is number of strings in 'matches',
355: * 'width' is maximum length of string in 'matches'.
356: *
1.23 dholland 357: * matches[0] is not one of the match strings, but it is counted in
358: * num, so the strings are matches[1] *through* matches[num-1].
1.1 dsl 359: */
360: void
1.21 dholland 361: fn_display_match_list (EditLine *el, char **matches, size_t num, size_t width)
1.1 dsl 362: {
1.21 dholland 363: size_t line, lines, col, cols, thisguy;
1.24 christos 364: int screenwidth = el->el_terminal.t_size.h;
1.1 dsl 365:
1.21 dholland 366: /* Ignore matches[0]. Avoid 1-based array logic below. */
367: matches++;
1.23 dholland 368: num--;
1.21 dholland 369:
370: /*
371: * Find out how many entries can be put on one line; count
372: * with one space between strings the same way it's printed.
373: */
374: cols = screenwidth / (width + 1);
375: if (cols == 0)
376: cols = 1;
377:
378: /* how many lines of output, rounded up */
379: lines = (num + cols - 1) / cols;
380:
381: /* Sort the items. */
382: qsort(matches, num, sizeof(char *), _fn_qsort_string_compare);
383:
1.1 dsl 384: /*
1.21 dholland 385: * On the ith line print elements i, i+lines, i+lines*2, etc.
1.1 dsl 386: */
1.21 dholland 387: for (line = 0; line < lines; line++) {
388: for (col = 0; col < cols; col++) {
389: thisguy = line + col * lines;
390: if (thisguy >= num)
391: break;
392: (void)fprintf(el->el_outfile, "%s%-*s",
393: col == 0 ? "" : " ", (int)width, matches[thisguy]);
1.16 christos 394: }
1.1 dsl 395: (void)fprintf(el->el_outfile, "\n");
396: }
397: }
398:
399: /*
400: * Complete the word at or before point,
401: * 'what_to_do' says what to do with the completion.
402: * \t means do standard completion.
403: * `?' means list the possible completions.
404: * `*' means insert all of the possible completions.
405: * `!' means to do standard completion, and list all possible completions if
406: * there is more than one.
407: *
408: * Note: '*' support is not implemented
409: * '!' could never be invoked
410: */
411: int
412: fn_complete(EditLine *el,
413: char *(*complet_func)(const char *, int),
414: char **(*attempted_completion_function)(const char *, int, int),
1.17 christos 415: const Char *word_break, const Char *special_prefixes,
1.15 christos 416: const char *(*app_func)(const char *), size_t query_items,
1.1 dsl 417: int *completion_type, int *over, int *point, int *end)
418: {
1.17 christos 419: const TYPE(LineInfo) *li;
420: Char *temp;
421: char **matches;
422: const Char *ctemp;
1.1 dsl 423: size_t len;
424: int what_to_do = '\t';
1.10 christos 425: int retval = CC_NORM;
1.1 dsl 426:
427: if (el->el_state.lastcmd == el->el_state.thiscmd)
428: what_to_do = '?';
429:
430: /* readline's rl_complete() has to be told what we did... */
431: if (completion_type != NULL)
432: *completion_type = what_to_do;
433:
434: if (!complet_func)
1.7 christos 435: complet_func = fn_filename_completion_function;
1.6 christos 436: if (!app_func)
437: app_func = append_char_function;
1.1 dsl 438:
439: /* We now look backwards for the start of a filename/variable word */
1.17 christos 440: li = FUN(el,line)(el);
441: ctemp = li->cursor;
1.1 dsl 442: while (ctemp > li->buffer
1.17 christos 443: && !Strchr(word_break, ctemp[-1])
444: && (!special_prefixes || !Strchr(special_prefixes, ctemp[-1]) ) )
1.1 dsl 445: ctemp--;
446:
447: len = li->cursor - ctemp;
1.26 christos 448: temp = el_malloc((len + 1) * sizeof(*temp));
1.17 christos 449: (void)Strncpy(temp, ctemp, len);
1.1 dsl 450: temp[len] = '\0';
451:
452: /* these can be used by function called in completion_matches() */
453: /* or (*attempted_completion_function)() */
454: if (point != 0)
1.14 christos 455: *point = (int)(li->cursor - li->buffer);
1.1 dsl 456: if (end != NULL)
1.14 christos 457: *end = (int)(li->lastchar - li->buffer);
1.1 dsl 458:
459: if (attempted_completion_function) {
1.14 christos 460: int cur_off = (int)(li->cursor - li->buffer);
1.17 christos 461: matches = (*attempted_completion_function) (ct_encode_string(temp, &el->el_scratch),
1.1 dsl 462: (int)(cur_off - len), cur_off);
463: } else
464: matches = 0;
465: if (!attempted_completion_function ||
1.8 christos 466: (over != NULL && !*over && !matches))
1.17 christos 467: matches = completion_matches(ct_encode_string(temp, &el->el_scratch), complet_func);
1.1 dsl 468:
469: if (over != NULL)
470: *over = 0;
471:
472: if (matches) {
1.10 christos 473: int i;
1.14 christos 474: size_t matches_num, maxlen, match_len, match_display=1;
1.1 dsl 475:
1.10 christos 476: retval = CC_REFRESH;
1.1 dsl 477: /*
478: * Only replace the completed string with common part of
479: * possible matches if there is possible completion.
480: */
481: if (matches[0][0] != '\0') {
482: el_deletestr(el, (int) len);
1.17 christos 483: FUN(el,insertstr)(el,
484: ct_decode_string(matches[0], &el->el_scratch));
1.1 dsl 485: }
486:
487: if (what_to_do == '?')
488: goto display_matches;
489:
490: if (matches[2] == NULL && strcmp(matches[0], matches[1]) == 0) {
491: /*
492: * We found exact match. Add a space after
493: * it, unless we do filename completion and the
494: * object is a directory.
495: */
1.17 christos 496: FUN(el,insertstr)(el,
497: ct_decode_string((*app_func)(matches[0]),
498: &el->el_scratch));
1.1 dsl 499: } else if (what_to_do == '!') {
500: display_matches:
501: /*
502: * More than one match and requested to list possible
503: * matches.
504: */
505:
1.17 christos 506: for(i = 1, maxlen = 0; matches[i]; i++) {
1.1 dsl 507: match_len = strlen(matches[i]);
508: if (match_len > maxlen)
509: maxlen = match_len;
510: }
1.21 dholland 511: /* matches[1] through matches[i-1] are available */
1.1 dsl 512: matches_num = i - 1;
513:
514: /* newline to get on next line from command line */
515: (void)fprintf(el->el_outfile, "\n");
516:
517: /*
518: * If there are too many items, ask user for display
519: * confirmation.
520: */
521: if (matches_num > query_items) {
522: (void)fprintf(el->el_outfile,
1.14 christos 523: "Display all %zu possibilities? (y or n) ",
1.1 dsl 524: matches_num);
525: (void)fflush(el->el_outfile);
526: if (getc(stdin) != 'y')
527: match_display = 0;
528: (void)fprintf(el->el_outfile, "\n");
529: }
530:
1.23 dholland 531: if (match_display) {
532: /*
533: * Interface of this function requires the
534: * strings be matches[1..num-1] for compat.
535: * We have matches_num strings not counting
536: * the prefix in matches[0], so we need to
537: * add 1 to matches_num for the call.
538: */
539: fn_display_match_list(el, matches,
540: matches_num+1, maxlen);
541: }
1.1 dsl 542: retval = CC_REDISPLAY;
543: } else if (matches[0][0]) {
544: /*
545: * There was some common match, but the name was
546: * not complete enough. Next tab will print possible
547: * completions.
548: */
549: el_beep(el);
550: } else {
551: /* lcd is not a valid object - further specification */
552: /* is needed */
553: el_beep(el);
554: retval = CC_NORM;
555: }
556:
557: /* free elements of array and the array itself */
558: for (i = 0; matches[i]; i++)
1.26 christos 559: el_free(matches[i]);
560: el_free(matches);
1.10 christos 561: matches = NULL;
1.1 dsl 562: }
1.26 christos 563: el_free(temp);
1.10 christos 564: return retval;
1.1 dsl 565: }
566:
567: /*
568: * el-compatible wrapper around rl_complete; needed for key binding
569: */
570: /* ARGSUSED */
571: unsigned char
572: _el_fn_complete(EditLine *el, int ch __attribute__((__unused__)))
573: {
574: return (unsigned char)fn_complete(el, NULL, NULL,
1.6 christos 575: break_chars, NULL, NULL, 100,
1.1 dsl 576: NULL, NULL, NULL, NULL);
577: }
CVSweb <webmaster@jp.NetBSD.org>