source: heimdal/trunk/lib/libedit/src/filecomplete.c

Last change on this file was 1, checked in by Paul Smedley, 10 years ago

Initial commit of Heimdal 1.5.3

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