terminal-util: when prompting for a choice from a list, preselect longest prefix

If all entries of a menu prompt start with the same prefix, let's
preselect the prefix to enhance user experience.

This is particularly relevant when prompting for a disk to install
things on, as typically they all start with the same prefix /dev/, and
if there's only a single target medium discoverable, then we can even
fill it out fully.
This commit is contained in:
Lennart Poettering
2026-05-05 17:47:20 +02:00
committed by Zbigniew Jędrzejewski-Szmek
parent c0aa351ba9
commit fc05165fce
4 changed files with 63 additions and 21 deletions
+21 -4
View File
@@ -293,17 +293,33 @@ int ask_string_full(
assert(ret);
assert(text);
_cleanup_free_ char *string = NULL;
size_t n = 0;
if (get_completions) {
/* Figure out what string to preselect the query with */
_cleanup_strv_free_ char **completions = NULL;
r = get_completions("", GET_COMPLETIONS_PRESELECT, &completions, userdata);
if (r < 0)
return r;
CompletionResult cr = pick_completion(string, completions, &string);
if (cr < 0)
return cr;
n = strlen_ptr(string);
}
/* Output the prompt */
fputs(ansi_highlight(), stdout);
va_start(ap, text);
vprintf(text, ap);
va_end(ap);
fputs(ansi_normal(), stdout);
if (string)
fputs(string, stdout);
fflush(stdout);
_cleanup_free_ char *string = NULL;
size_t n = 0;
/* Do interactive logic only if stdin + stdout are connected to the same place. And yes, we could use
* STDIN_FILENO and STDOUT_FILENO here, but let's be overly correct for once, after all libc allows
* swapping out stdin/stdout. */
@@ -344,7 +360,7 @@ int ask_string_full(
_cleanup_strv_free_ char **completions = NULL;
if (get_completions) {
r = get_completions(string, &completions, userdata);
r = get_completions(string, /* flags= */ 0, &completions, userdata);
if (r < 0)
return r;
}
@@ -450,6 +466,7 @@ int ask_string_full(
fallback:
/* A simple fallback without TTY magic */
string = mfree(string);
r = read_line(stdin, LONG_LINE_MAX, &string);
if (r < 0)
return r;
+7 -1
View File
@@ -88,7 +88,13 @@ int chvt(int vt);
int read_one_char(FILE *f, char *ret, usec_t timeout, bool echo, bool *need_nl);
int ask_char(char *ret, const char *replies, const char *fmt, ...) _printf_(3, 4);
typedef int (*GetCompletionsCallback)(const char *key, char ***ret_list, void *userdata);
typedef enum GetCompletionsFlags {
/* Only return the items subject to preselection: typically you want to suppress meta entries such as
* "list" or alias entries if this flag is set. */
GET_COMPLETIONS_PRESELECT = 1 << 0,
} GetCompletionsFlags;
typedef int (*GetCompletionsCallback)(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata);
int ask_string_full(char **ret, GetCompletionsCallback get_completions, void *userdata, const char *text, ...) _printf_(4, 5);
#define ask_string(ret, text, ...) ask_string_full(ret, NULL, NULL, text, ##__VA_ARGS__)