Replace all casts of NULL to something with NULL.
[dragonfly.git] / lib / libedit / filecomplete.c
1 /*-
2  * Copyright (c) 1997 The NetBSD Foundation, Inc.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to The NetBSD Foundation
6  * by Jaromir Dolecek.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  *
29  * $NetBSD: filecomplete.c,v 1.11 2008/04/29 06:53:01 martin Exp $
30  * $DragonFly: src/lib/libedit/filecomplete.c,v 1.3 2008/05/17 22:48:04 pavalos Exp $
31  */
32
33 #include "config.h"
34
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <stdio.h>
38 #include <dirent.h>
39 #include <string.h>
40 #include <pwd.h>
41 #include <ctype.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 #include <limits.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #ifdef HAVE_VIS_H
48 #include <vis.h>
49 #else
50 #include "np/vis.h"
51 #endif
52 #ifdef HAVE_ALLOCA_H
53 #include <alloca.h>
54 #endif
55 #include "el.h"
56 #include "fcns.h"               /* for EL_NUM_FCNS */
57 #include "histedit.h"
58 #include "filecomplete.h"
59
60 static char break_chars[] = { ' ', '\t', '\n', '"', '\\', '\'', '`', '@', '$',
61     '>', '<', '=', ';', '|', '&', '{', '(', '\0' };
62
63
64 /********************************/
65 /* completion functions */
66
67 /*
68  * does tilde expansion of strings of type ``~user/foo''
69  * if ``user'' isn't valid user name or ``txt'' doesn't start
70  * w/ '~', returns pointer to strdup()ed copy of ``txt''
71  *
72  * it's callers's responsibility to free() returned string
73  */
74 char *
75 fn_tilde_expand(const char *txt)
76 {
77         /*
78                 XXX: replace the next line with this line when getpwuid_r or
79                 getpwnam_r become available:
80         struct passwd pwres, *pass;
81         */
82         struct passwd *pass;
83         char *temp;
84         size_t len = 0;
85         char pwbuf[1024];
86
87         if (txt[0] != '~')
88                 return (strdup(txt));
89
90         temp = strchr(txt + 1, '/');
91         if (temp == NULL) {
92                 temp = strdup(txt + 1);
93                 if (temp == NULL)
94                         return NULL;
95         } else {
96                 len = temp - txt + 1;   /* text until string after slash */
97                 temp = malloc(len);
98                 if (temp == NULL)
99                         return NULL;
100                 (void)strncpy(temp, txt + 1, len - 2);
101                 temp[len - 2] = '\0';
102         }
103         if (temp[0] == 0) {
104                 /*
105                         XXX: use the following instead of the next line when
106                         getpwuid_r is available:
107                 if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf), &pass) != 0)
108                         pass = NULL;
109                 */
110                 pass = getpwuid(getuid());
111         } else {
112                 
113                 /*
114                         XXX: use the following instead of the next line when
115                         getpwname_r is available:
116                 if (getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf), &pass) != 0)
117                         pass = NULL;
118                 */
119                 pass = getpwnam(temp);
120         }
121         free(temp);             /* value no more needed */
122         if (pass == NULL)
123                 return (strdup(txt));
124
125         /* update pointer txt to point at string immedially following */
126         /* first slash */
127         txt += len;
128
129         temp = malloc(strlen(pass->pw_dir) + 1 + strlen(txt) + 1);
130         if (temp == NULL)
131                 return NULL;
132         (void)sprintf(temp, "%s/%s", pass->pw_dir, txt);
133
134         return (temp);
135 }
136
137
138 /*
139  * return first found file name starting by the ``text'' or NULL if no
140  * such file can be found
141  * value of ``state'' is ignored
142  *
143  * it's caller's responsibility to free returned string
144  */
145 char *
146 fn_filename_completion_function(const char *text, int state)
147 {
148         static DIR *dir = NULL;
149         static char *filename = NULL, *dirname = NULL, *dirpath = NULL;
150         static size_t filename_len = 0;
151         struct dirent *entry;
152         char *temp;
153         size_t len;
154
155         if (state == 0 || dir == NULL) {
156                 temp = strrchr(text, '/');
157                 if (temp) {
158                         char *nptr;
159                         temp++;
160                         nptr = realloc(filename, strlen(temp) + 1);
161                         if (nptr == NULL) {
162                                 free(filename);
163                                 return NULL;
164                         }
165                         filename = nptr;
166                         (void)strcpy(filename, temp);
167                         len = temp - text;      /* including last slash */
168                         nptr = realloc(dirname, len + 1);
169                         if (nptr == NULL) {
170                                 free(filename);
171                                 return NULL;
172                         }
173                         dirname = nptr;
174                         (void)strncpy(dirname, text, len);
175                         dirname[len] = '\0';
176                 } else {
177                         if (*text == 0)
178                                 filename = NULL;
179                         else {
180                                 filename = strdup(text);
181                                 if (filename == NULL)
182                                         return NULL;
183                         }
184                         dirname = NULL;
185                 }
186
187                 if (dir != NULL) {
188                         (void)closedir(dir);
189                         dir = NULL;
190                 }
191
192                 /* support for ``~user'' syntax */
193                 free(dirpath);
194
195                 if (dirname == NULL && (dirname = strdup("./")) == NULL)
196                         return NULL;
197
198                 if (*dirname == '~')
199                         dirpath = fn_tilde_expand(dirname);
200                 else
201                         dirpath = strdup(dirname);
202
203                 if (dirpath == NULL)
204                         return NULL;
205
206                 dir = opendir(dirpath);
207                 if (!dir)
208                         return (NULL);  /* cannot open the directory */
209
210                 /* will be used in cycle */
211                 filename_len = filename ? strlen(filename) : 0;
212         }
213
214         /* find the match */
215         while ((entry = readdir(dir)) != NULL) {
216                 /* skip . and .. */
217                 if (entry->d_name[0] == '.' && (!entry->d_name[1]
218                     || (entry->d_name[1] == '.' && !entry->d_name[2])))
219                         continue;
220                 if (filename_len == 0)
221                         break;
222                 /* otherwise, get first entry where first */
223                 /* filename_len characters are equal      */
224                 if (entry->d_name[0] == filename[0]
225 #if defined(__SVR4) || defined(__linux__)
226                     && strlen(entry->d_name) >= filename_len
227 #else
228                     && entry->d_namlen >= filename_len
229 #endif
230                     && strncmp(entry->d_name, filename,
231                         filename_len) == 0)
232                         break;
233         }
234
235         if (entry) {            /* match found */
236
237 #if defined(__SVR4) || defined(__linux__)
238                 len = strlen(entry->d_name);
239 #else
240                 len = entry->d_namlen;
241 #endif
242
243                 temp = malloc(strlen(dirname) + len + 1);
244                 if (temp == NULL)
245                         return NULL;
246                 (void)sprintf(temp, "%s%s", dirname, entry->d_name);
247         } else {
248                 (void)closedir(dir);
249                 dir = NULL;
250                 temp = NULL;
251         }
252
253         return (temp);
254 }
255
256
257 static const char *
258 append_char_function(const char *name)
259 {
260         struct stat stbuf;
261         char *expname = *name == '~' ? fn_tilde_expand(name) : NULL;
262         const char *rs = "";
263
264         if (stat(expname ? expname : name, &stbuf) == -1)
265                 goto out;
266         if (S_ISDIR(stbuf.st_mode))
267                 rs = "/";
268 out:
269         if (expname)
270                 free(expname);
271         return rs;
272 }
273 /*
274  * returns list of completions for text given
275  * non-static for readline.
276  */
277 char ** completion_matches(const char *, char *(*)(const char *, int));
278 char **
279 completion_matches(const char *text, char *(*genfunc)(const char *, int))
280 {
281         char **match_list = NULL, *retstr, *prevstr;
282         size_t match_list_len, max_equal, which, i;
283         size_t matches;
284
285         matches = 0;
286         match_list_len = 1;
287         while ((retstr = (*genfunc) (text, (int)matches)) != NULL) {
288                 /* allow for list terminator here */
289                 if (matches + 3 >= match_list_len) {
290                         char **nmatch_list;
291                         while (matches + 3 >= match_list_len)
292                                 match_list_len <<= 1;
293                         nmatch_list = realloc(match_list,
294                             match_list_len * sizeof(char *));
295                         if (nmatch_list == NULL) {
296                                 free(match_list);
297                                 return NULL;
298                         }
299                         match_list = nmatch_list;
300
301                 }
302                 match_list[++matches] = retstr;
303         }
304
305         if (!match_list)
306                 return NULL;    /* nothing found */
307
308         /* find least denominator and insert it to match_list[0] */
309         which = 2;
310         prevstr = match_list[1];
311         max_equal = strlen(prevstr);
312         for (; which <= matches; which++) {
313                 for (i = 0; i < max_equal &&
314                     prevstr[i] == match_list[which][i]; i++)
315                         continue;
316                 max_equal = i;
317         }
318
319         retstr = malloc(max_equal + 1);
320         if (retstr == NULL) {
321                 free(match_list);
322                 return NULL;
323         }
324         (void)strncpy(retstr, match_list[1], max_equal);
325         retstr[max_equal] = '\0';
326         match_list[0] = retstr;
327
328         /* add NULL as last pointer to the array */
329         match_list[matches + 1] = NULL;
330
331         return (match_list);
332 }
333
334 /*
335  * Sort function for qsort(). Just wrapper around strcasecmp().
336  */
337 static int
338 _fn_qsort_string_compare(const void *i1, const void *i2)
339 {
340         const char *s1 = ((const char * const *)i1)[0];
341         const char *s2 = ((const char * const *)i2)[0];
342
343         return strcasecmp(s1, s2);
344 }
345
346 /*
347  * Display list of strings in columnar format on readline's output stream.
348  * 'matches' is list of strings, 'len' is number of strings in 'matches',
349  * 'max' is maximum length of string in 'matches'.
350  */
351 void
352 fn_display_match_list (EditLine *el, char **matches, int len, int max)
353 {
354         int i, idx, limit, count;
355         int screenwidth = el->el_term.t_size.h;
356
357         /*
358          * Find out how many entries can be put on one line, count
359          * with two spaces between strings.
360          */
361         limit = screenwidth / (max + 2);
362         if (limit == 0)
363                 limit = 1;
364
365         /* how many lines of output */
366         count = len / limit;
367         if (count * limit < len)
368                 count++;
369
370         /* Sort the items if they are not already sorted. */
371         qsort(&matches[1], (size_t)(len - 1), sizeof(char *),
372             _fn_qsort_string_compare);
373
374         idx = 1;
375         for(; count > 0; count--) {
376                 for(i = 0; i < limit && matches[idx]; i++, idx++)
377                         (void)fprintf(el->el_outfile, "%-*s  ", max,
378                             matches[idx]);
379                 (void)fprintf(el->el_outfile, "\n");
380         }
381 }
382
383 /*
384  * Complete the word at or before point,
385  * 'what_to_do' says what to do with the completion.
386  * \t   means do standard completion.
387  * `?' means list the possible completions.
388  * `*' means insert all of the possible completions.
389  * `!' means to do standard completion, and list all possible completions if
390  * there is more than one.
391  *
392  * Note: '*' support is not implemented
393  *       '!' could never be invoked
394  */
395 int
396 fn_complete(EditLine *el,
397         char *(*complet_func)(const char *, int),
398         char **(*attempted_completion_function)(const char *, int, int),
399         const char *word_break, const char *special_prefixes,
400         const char *(*app_func)(const char *), int query_items,
401         int *completion_type, int *over, int *point, int *end)
402 {
403         const LineInfo *li;
404         char *temp, **matches;
405         const char *ctemp;
406         size_t len;
407         int what_to_do = '\t';
408         int retval = CC_NORM;
409
410         if (el->el_state.lastcmd == el->el_state.thiscmd)
411                 what_to_do = '?';
412
413         /* readline's rl_complete() has to be told what we did... */
414         if (completion_type != NULL)
415                 *completion_type = what_to_do;
416
417         if (!complet_func)
418                 complet_func = fn_filename_completion_function;
419         if (!app_func)
420                 app_func = append_char_function;
421
422         /* We now look backwards for the start of a filename/variable word */
423         li = el_line(el);
424         ctemp = (const char *) li->cursor;
425         while (ctemp > li->buffer
426             && !strchr(word_break, ctemp[-1])
427             && (!special_prefixes || !strchr(special_prefixes, ctemp[-1]) ) )
428                 ctemp--;
429
430         len = li->cursor - ctemp;
431 #if defined(__SSP__) || defined(__SSP_ALL__)
432         temp = malloc(len + 1);
433 #else
434         temp = alloca(len + 1);
435 #endif
436         (void)strncpy(temp, ctemp, len);
437         temp[len] = '\0';
438
439         /* these can be used by function called in completion_matches() */
440         /* or (*attempted_completion_function)() */
441         if (point != 0)
442                 *point = li->cursor - li->buffer;
443         if (end != NULL)
444                 *end = li->lastchar - li->buffer;
445
446         if (attempted_completion_function) {
447                 int cur_off = li->cursor - li->buffer;
448                 matches = (*attempted_completion_function) (temp,
449                     (int)(cur_off - len), cur_off);
450         } else
451                 matches = 0;
452         if (!attempted_completion_function || 
453             (over != NULL && !*over && !matches))
454                 matches = completion_matches(temp, complet_func);
455
456         if (over != NULL)
457                 *over = 0;
458
459         if (matches) {
460                 int i;
461                 int matches_num, maxlen, match_len, match_display=1;
462
463                 retval = CC_REFRESH;
464                 /*
465                  * Only replace the completed string with common part of
466                  * possible matches if there is possible completion.
467                  */
468                 if (matches[0][0] != '\0') {
469                         el_deletestr(el, (int) len);
470                         el_insertstr(el, matches[0]);
471                 }
472
473                 if (what_to_do == '?')
474                         goto display_matches;
475
476                 if (matches[2] == NULL && strcmp(matches[0], matches[1]) == 0) {
477                         /*
478                          * We found exact match. Add a space after
479                          * it, unless we do filename completion and the
480                          * object is a directory.
481                          */
482                         el_insertstr(el, (*append_char_function)(matches[0])); 
483                 } else if (what_to_do == '!') {
484     display_matches:
485                         /*
486                          * More than one match and requested to list possible
487                          * matches.
488                          */
489
490                         for(i=1, maxlen=0; matches[i]; i++) {
491                                 match_len = strlen(matches[i]);
492                                 if (match_len > maxlen)
493                                         maxlen = match_len;
494                         }
495                         matches_num = i - 1;
496                                 
497                         /* newline to get on next line from command line */
498                         (void)fprintf(el->el_outfile, "\n");
499
500                         /*
501                          * If there are too many items, ask user for display
502                          * confirmation.
503                          */
504                         if (matches_num > query_items) {
505                                 (void)fprintf(el->el_outfile,
506                                     "Display all %d possibilities? (y or n) ",
507                                     matches_num);
508                                 (void)fflush(el->el_outfile);
509                                 if (getc(stdin) != 'y')
510                                         match_display = 0;
511                                 (void)fprintf(el->el_outfile, "\n");
512                         }
513
514                         if (match_display)
515                                 fn_display_match_list(el, matches, matches_num,
516                                         maxlen);
517                         retval = CC_REDISPLAY;
518                 } else if (matches[0][0]) {
519                         /*
520                          * There was some common match, but the name was
521                          * not complete enough. Next tab will print possible
522                          * completions.
523                          */
524                         el_beep(el);
525                 } else {
526                         /* lcd is not a valid object - further specification */
527                         /* is needed */
528                         el_beep(el);
529                         retval = CC_NORM;
530                 }
531
532                 /* free elements of array and the array itself */
533                 for (i = 0; matches[i]; i++)
534                         free(matches[i]);
535                 free(matches);
536                 matches = NULL;
537         }
538 #if defined(__SSP__) || defined(__SSP_ALL__)
539         free(temp);
540 #endif
541         return retval;
542 }
543
544 /*
545  * el-compatible wrapper around rl_complete; needed for key binding
546  */
547 /* ARGSUSED */
548 unsigned char
549 _el_fn_complete(EditLine *el, int ch __attribute__((__unused__)))
550 {
551         return (unsigned char)fn_complete(el, NULL, NULL,
552             break_chars, NULL, NULL, 100,
553             NULL, NULL, NULL, NULL);
554 }