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