From: Kirill A. Korinsky Subject: bin/ksh: add bash-like expand-tilde option To: OpenBSD tech Date: Fri, 24 Apr 2026 21:08:03 +0200 tech@, default ksh completion already matches bash with expand-tilde enabled: typing ~/sr and pressing TAB expands and completes the word to /home/user/src. Here a new option expand-tilde which using the bash name, but keep current ksh behaviour by enabling it by default. Disabling the option preserves the original ~ prefix, so the same input completes to ~/src. Ok? Index: bin/ksh/edit.c =================================================================== RCS file: /home/cvs/src/bin/ksh/edit.c,v diff -u -p -r1.71 edit.c --- bin/ksh/edit.c 23 Apr 2024 13:34:50 -0000 1.71 +++ bin/ksh/edit.c 18 Apr 2026 22:50:42 -0000 @@ -30,6 +30,7 @@ static void check_sigwinch(void); static int x_file_glob(int, const char *, int, char ***); static int x_command_glob(int, const char *, int, char ***); static int x_locate_word(const char *, int, int, int *, int *); +static void x_preserve_tilde(char **, int, const char *, const char *); /* Called from main */ @@ -417,10 +418,24 @@ x_file_glob(int flags, const char *str, int nwords; XPtrV w; struct source *s, *sold; + char *tilde = NULL; + char *tilde_expanded = NULL; + int preserve_tilde = 0; + int tlen = 0; if (slen < 0) return 0; + if (!Flag(FEXPANDTILDE) && slen > 0 && str[0] == '~') { + for (tlen = 1; tlen < slen; tlen++) + if (str[tlen] == '/') + break; + tilde = str_nsave(str, tlen, ATEMP); + tilde_expanded = evalstr(tilde, DOTILDE); + if (tilde_expanded != null && tilde_expanded[0] != '~') + preserve_tilde = 1; + } + toglob = add_glob(str, slen); /* @@ -433,7 +448,8 @@ x_file_glob(int flags, const char *str, if (yylex(ONEWORD|UNESCAPE) != LWORD) { source = sold; internal_warningf("%s: substitute error", __func__); - return 0; + nwords = 0; + goto done; } source = sold; XPinit(w, 32); @@ -461,13 +477,52 @@ x_file_glob(int flags, const char *str, afree(toglob, ATEMP); if (nwords) { + if (preserve_tilde) + x_preserve_tilde(words, nwords, tilde, tilde_expanded); *wordsp = words; } else if (words) { x_free_words(nwords, words); *wordsp = NULL; } +done: + if (tilde) + afree(tilde, ATEMP); + if (tilde_expanded && tilde_expanded != null) + afree(tilde_expanded, ATEMP); return nwords; +} + +static void +x_preserve_tilde(char **words, int nwords, const char *tilde, + const char *tilde_expanded) +{ + size_t tilde_len, exp_len; + int i; + + tilde_len = strlen(tilde); + exp_len = strlen(tilde_expanded); + if (exp_len == 0) + return; + + for (i = 0; i < nwords; i++) { + char *w = words[i]; + size_t rest_len; + char *nw; + + if (strncmp(w, tilde_expanded, exp_len) != 0) + continue; + if (w[exp_len] != '\0' && w[exp_len] != '/') + continue; + + rest_len = strlen(w + exp_len); + nw = areallocarray(NULL, tilde_len + rest_len + 1, + sizeof(char), ATEMP); + memcpy(nw, tilde, tilde_len); + memcpy(nw + tilde_len, w + exp_len, rest_len + 1); + afree(w, ATEMP); + words[i] = nw; + } } /* Data structure used in x_command_glob() */ Index: bin/ksh/ksh.1 =================================================================== RCS file: /home/cvs/src/bin/ksh/ksh.1,v diff -u -p -r1.223 ksh.1 --- bin/ksh/ksh.1 31 Dec 2025 22:12:25 -0000 1.223 +++ bin/ksh/ksh.1 18 Apr 2026 22:50:42 -0000 @@ -3630,6 +3630,9 @@ is read 13 times in a row. The shell is an interactive shell. This option can only be used when the shell is invoked. See above for a description of what this means. +.It Ic expand-tilde +Expand leading tilde expressions in file name completion results. +Enabled by default. .It Ic login The shell is a login shell. This option can only be used when the shell is invoked. Index: bin/ksh/main.c =================================================================== RCS file: /home/cvs/src/bin/ksh/main.c,v diff -u -p -r1.100 main.c --- bin/ksh/main.c 23 Jul 2023 23:42:03 -0000 1.100 +++ bin/ksh/main.c 18 Apr 2026 22:50:42 -0000 @@ -261,11 +261,12 @@ main(int argc, char *argv[]) } /* Set edit mode to emacs by default, may be overridden - * by the environment or the user. Also, we want tab completion - * on in vi by default. */ + * by the environment or the user. Also, we want expand-tilde and + * tab completion on in vi by default. */ #if defined(EMACS) change_flag(FEMACS, OF_SPECIAL, 1); #endif /* EMACS */ + Flag(FEXPANDTILDE) = 1; #if defined(VI) Flag(FVITABCOMPLETE) = 1; #endif /* VI */ Index: bin/ksh/misc.c =================================================================== RCS file: /home/cvs/src/bin/ksh/misc.c,v diff -u -p -r1.78 misc.c --- bin/ksh/misc.c 24 Dec 2021 22:08:37 -0000 1.78 +++ bin/ksh/misc.c 18 Apr 2026 22:50:42 -0000 @@ -130,6 +130,7 @@ const struct option sh_options[] = { { "emacs", 0, OF_ANY }, #endif { "errexit", 'e', OF_ANY }, + { "expand-tilde", 0, OF_ANY }, /* non-standard */ #ifdef EMACS { "gmacs", 0, OF_ANY }, #endif Index: bin/ksh/sh.h =================================================================== RCS file: /home/cvs/src/bin/ksh/sh.h,v diff -u -p -r1.78 sh.h --- bin/ksh/sh.h 5 Mar 2026 05:38:58 -0000 1.78 +++ bin/ksh/sh.h 18 Apr 2026 22:50:42 -0000 @@ -141,6 +141,7 @@ enum sh_flag { FEMACS, /* emacs command editing */ #endif FERREXIT, /* -e: quit on error */ + FEXPANDTILDE, /* expand ~ in completion */ #ifdef EMACS FGMACS, /* gmacs command editing */ #endif -- wbr, Kirill