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