Index: bin/sh/expand.h =================================================================== --- bin/sh/expand.h +++ bin/sh/expand.h @@ -60,3 +60,4 @@ void expandarg(union node *, struct arglist *, int); void rmescapes(char *); int casematch(union node *, const char *); +const char *tilde_expand(const char *); Index: bin/sh/expand.c =================================================================== --- bin/sh/expand.c +++ bin/sh/expand.c @@ -356,6 +356,19 @@ } } +const char * +tilde_expand(const char *name) +{ + const char *next; + + STARTSTACKSTR(expdest); + next = exptilde(name, 0); + STPUTS(next, expdest); + STACKSTRNUL(expdest); + + return (stackblock()); +} + /* * Perform tilde expansion, placing the result in the stack string and * returning the next position in the input string to process. Index: bin/sh/histedit.c =================================================================== --- bin/sh/histedit.c +++ bin/sh/histedit.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -63,6 +64,7 @@ #include "myhistedit.h" #include "error.h" #include "eval.h" +#include "expand.h" #include "memalloc.h" #define MAXHISTLOOPS 4 /* max recursions through fc */ @@ -73,12 +75,14 @@ int displayhist; static int savehist; static FILE *el_in, *el_out; +static bool in_command_completion; static char *fc_replace(const char *, char *, char *); static int not_fcnumber(const char *); static int str_to_event(const char *, int); static int comparator(const void *, const void *, void *); static char **sh_matches(const char *, int, int); +static const char *append_char_function(const char *); static unsigned char sh_complete(EditLine *, int); static const char * @@ -603,8 +607,10 @@ size_t i = 0, size = 16, uniq; size_t curpos = end - start, lcstring = -1; + in_command_completion = false; if (start > 0 || memchr("/.~", text[0], 3) != NULL) return (NULL); + in_command_completion = true; if ((free_path = path = strdup(pathval())) == NULL) goto out; if ((matches = malloc(size * sizeof(matches[0]))) == NULL) @@ -697,6 +703,29 @@ return (matches); } +/* + * If we don't specify this function as app_func in the call to fn_complete2, + * libedit will use the default one, which adds a " " to plain files and + * a "/" to directories regardless of whether it's a command name or a plain + * path (relative or absolute). We never want to add "/" to commands. + * + * For example, after I did "mkdir rmdir", "rmdi" would be autocompleted to + * "rmdir/" instead of "rmdir ". + */ +static const char * +append_char_function(const char *name) +{ + struct stat stbuf; + + if (name[0] == '~') + name = tilde_expand(name); + + if (!in_command_completion && + stat(name, &stbuf) == 0 && S_ISDIR(stbuf.st_mode)) + return ("/"); + return (" "); +} + /* * This is passed to el_set(el, EL_ADDFN, ...) so that it's possible to * bind a key (tab by default) to execute the function. @@ -705,8 +734,8 @@ sh_complete(EditLine *sel, int ch __unused) { return (unsigned char)fn_complete2(sel, NULL, sh_matches, - L" \t\n\"\\'`@$><=;|&{(", NULL, NULL, (size_t)100, - NULL, &((int) {0}), NULL, NULL, FN_QUOTE_MATCH); + L" \t\n\"\\'`@$><=;|&{(", NULL, append_char_function, + (size_t)100, NULL, &((int) {0}), NULL, NULL, FN_QUOTE_MATCH); } #else