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