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. */
_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;
}
fallback:
/* A simple fallback without TTY magic */
+ string = mfree(string);
r = read_line(stdin, LONG_LINE_MAX, &string);
if (r < 0)
return r;
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__)
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;
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;
}
_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");
#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;
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,
r = ask_string_full(
&p,
get_completions,
- accepted ?: menu,
+ &(CompletionData) { menu, accepted },
"%s%s%s%s: ",
emoji >= 0 ? glyph(emoji) : "",
emoji >= 0 ? " " : "",