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:
committed by
Zbigniew Jędrzejewski-Szmek
parent
c0aa351ba9
commit
fc05165fce
@@ -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;
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
+13
-8
@@ -2697,7 +2697,7 @@ static int acquire_group_list(char ***ret) {
|
||||
return !!*ret;
|
||||
}
|
||||
|
||||
static int group_completion_callback(const char *key, char ***ret_list, void *userdata) {
|
||||
static int group_completion_callback(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata) {
|
||||
char ***available = userdata;
|
||||
int r;
|
||||
|
||||
@@ -2711,9 +2711,11 @@ static int group_completion_callback(const char *key, char ***ret_list, void *us
|
||||
if (!l)
|
||||
return -ENOMEM;
|
||||
|
||||
r = strv_extend(&l, "list");
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT)) {
|
||||
r = strv_extend(&l, "list");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
*ret_list = TAKE_PTR(l);
|
||||
return 0;
|
||||
@@ -2745,10 +2747,13 @@ static int prompt_groups(const char *username, char ***ret_groups) {
|
||||
}
|
||||
|
||||
_cleanup_free_ char *s = NULL;
|
||||
r = ask_string_full(&s,
|
||||
group_completion_callback, &available,
|
||||
"%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ",
|
||||
glyph(GLYPH_LABEL), username);
|
||||
r = ask_string_full(
|
||||
&s,
|
||||
group_completion_callback,
|
||||
&available,
|
||||
"%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ",
|
||||
glyph(GLYPH_LABEL),
|
||||
username);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to query user for auxiliary group: %m");
|
||||
|
||||
|
||||
@@ -16,27 +16,41 @@
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
|
||||
typedef struct CompletionData {
|
||||
char **menu; /* What to show in menu */
|
||||
char **accepted; /* What to accept (usually larger than the menu, but may be NULL if same) */
|
||||
} CompletionData;
|
||||
|
||||
static int get_completions(
|
||||
const char *key,
|
||||
GetCompletionsFlags flags,
|
||||
char ***ret_list,
|
||||
void *userdata) {
|
||||
|
||||
CompletionData *data = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
assert(ret_list);
|
||||
|
||||
if (!userdata) {
|
||||
/* Figure out the list to operate on. We'll generally work based on the "accepted" list, if it is
|
||||
* set. If not we'll operate with the full menu. When doing pre-selection we'll also pick the menu */
|
||||
char **l = data->accepted && !FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT) ? data->accepted : data->menu;
|
||||
|
||||
if (strv_isempty(l)) {
|
||||
*ret_list = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_strv_free_ char **copy = strv_copy(userdata);
|
||||
_cleanup_strv_free_ char **copy = strv_copy(l);
|
||||
if (!copy)
|
||||
return -ENOMEM;
|
||||
|
||||
r = strv_extend(©, "list");
|
||||
if (r < 0)
|
||||
return r;
|
||||
/* Never consider "list" for preselecting an item, but do consider it when doing a regular completion */
|
||||
if (!FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT)) {
|
||||
r = strv_extend(©, "list");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
*ret_list = TAKE_PTR(copy);
|
||||
return 0;
|
||||
@@ -45,8 +59,8 @@ static int get_completions(
|
||||
int prompt_loop(
|
||||
const char *text,
|
||||
Glyph emoji,
|
||||
char **menu, /* if non-NULL: choices to suggest */
|
||||
char **accepted, /* if non-NULL: choices to accept (should be a superset of 'menu') */
|
||||
char **menu, /* if non-NULL: choices to suggest */
|
||||
char **accepted, /* if non-NULL: choices to accept (should be a superset of 'menu') */
|
||||
unsigned ellipsize_percentage,
|
||||
size_t n_columns,
|
||||
size_t column_width,
|
||||
@@ -102,7 +116,7 @@ int prompt_loop(
|
||||
r = ask_string_full(
|
||||
&p,
|
||||
get_completions,
|
||||
accepted ?: menu,
|
||||
&(CompletionData) { menu, accepted },
|
||||
"%s%s%s%s: ",
|
||||
emoji >= 0 ? glyph(emoji) : "",
|
||||
emoji >= 0 ? " " : "",
|
||||
|
||||
Reference in New Issue
Block a user