Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
Subject:
bin/ksh: add keep-tilde completion option
To:
OpenBSD tech <tech@openbsd.org>
Date:
Mon, 26 Jan 2026 18:54:46 +0100

Download raw body.

Thread
tech@,

this is a new optional feature for ksh: keep ~ as unexpanded for completion.

Not sure does I only one who prefer to keep short path with ~ isntead expand
to /home/user/

Feedbacks? Ok?

diff --git bin/ksh/edit.c bin/ksh/edit.c
index 83bab8ffbad..ad74110c7a9 100644
--- bin/ksh/edit.c
+++ bin/ksh/edit.c
@@ -31,6 +31,7 @@ void (*x_resize_cb)(void);	/* callback for resize during edit */
 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_keep_tilde(char **, int, const char *, const char *);
 
 
 /* Called from main */
@@ -425,10 +426,24 @@ x_file_glob(int flags, const char *str, int slen, char ***wordsp)
 	int nwords;
 	XPtrV w;
 	struct source *s, *sold;
+	char *tilde = NULL;
+	char *tilde_expanded = NULL;
+	int keep_tilde = 0;
+	int tlen = 0;
 
 	if (slen < 0)
 		return 0;
 
+	if (Flag(FKEEPTILDE) && 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] != '~')
+			keep_tilde = 1;
+	}
+
 	toglob = add_glob(str, slen);
 
 	/*
@@ -441,7 +456,8 @@ x_file_glob(int flags, const char *str, int slen, char ***wordsp)
 	if (yylex(ONEWORD|UNESCAPE) != LWORD) {
 		source = sold;
 		internal_warningf("%s: substitute error", __func__);
-		return 0;
+		nwords = 0;
+		goto done;
 	}
 	source = sold;
 	XPinit(w, 32);
@@ -469,15 +485,54 @@ x_file_glob(int flags, const char *str, int slen, char ***wordsp)
 	afree(toglob, ATEMP);
 
 	if (nwords) {
+		if (keep_tilde)
+			x_keep_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_keep_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() */
 struct path_order_info {
 	char *word;
diff --git bin/ksh/ksh.1 bin/ksh/ksh.1
index 4b5176c6598..f7286b10e4f 100644
--- bin/ksh/ksh.1
+++ bin/ksh/ksh.1
@@ -3630,6 +3630,8 @@ 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 keep-tilde
+Keep leading tilde expressions in file name completion results.
 .It Ic login
 The shell is a login shell.
 This option can only be used when the shell is invoked.
diff --git bin/ksh/misc.c bin/ksh/misc.c
index 809fae38d94..30cf21e9ba2 100644
--- bin/ksh/misc.c
+++ bin/ksh/misc.c
@@ -135,6 +135,7 @@ const struct option sh_options[] = {
 #endif
 	{ "ignoreeof",	  0,		OF_ANY },
 	{ "interactive",'i',	    OF_CMDLINE },
+	{ "keep-tilde",   0,		OF_ANY }, /* non-standard */
 	{ "keyword",	'k',		OF_ANY },
 	{ "login",	'l',	    OF_CMDLINE },
 	{ "markdirs",	'X',		OF_ANY },
diff --git bin/ksh/sh.h bin/ksh/sh.h
index bffb374e0d1..85c18567eda 100644
--- bin/ksh/sh.h
+++ bin/ksh/sh.h
@@ -146,6 +146,7 @@ enum sh_flag {
 #endif
 	FIGNOREEOF,	/* eof does not exit */
 	FTALKING,	/* -i: interactive */
+	FKEEPTILDE,	/* keep ~ in completion */
 	FKEYWORD,	/* -k: name=value anywhere */
 	FLOGIN,		/* -l: a login shell */
 	FMARKDIRS,	/* mark dirs with / in file name completion */