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