diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index b2cbadb579..53437e690f 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -281,38 +281,92 @@ bool any_key_to_proceed(void) { return key != 'q'; } -int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) { - unsigned break_lines, break_modulo; - size_t n, per_column, i, j; +static size_t widest_list_element(char *const*l) { + size_t w = 0; + + /* Returns the largest console width of all elements in 'l' */ + + STRV_FOREACH(i, l) + w = MAX(w, utf8_console_width(*i)); + + return w; +} + +int show_menu(char **x, + size_t n_columns, + size_t column_width, + unsigned ellipsize_percentage, + const char *grey_prefix, + bool with_numbers) { assert(n_columns > 0); - n = strv_length(x); - per_column = DIV_ROUND_UP(n, n_columns); + if (n_columns == SIZE_MAX) + n_columns = 3; - break_lines = lines(); + if (column_width == SIZE_MAX) { + size_t widest = widest_list_element(x); + + /* If not specified, derive column width from screen width */ + size_t column_max = (columns()-1) / n_columns; + + /* Subtract room for numbers */ + if (with_numbers && column_max > 6) + column_max -= 6; + + /* If columns would get too tight let's make this a linear list instead. */ + if (column_max < 10 && widest > 10) { + n_columns = 1; + column_max = columns()-1; + + if (with_numbers && column_max > 6) + column_max -= 6; + } + + column_width = CLAMP(widest+1, 10U, column_max); + } + + size_t n = strv_length(x); + size_t per_column = DIV_ROUND_UP(n, n_columns); + + size_t break_lines = lines(); if (break_lines > 2) break_lines--; /* The first page gets two extra lines, since we want to show * a title */ - break_modulo = break_lines; + size_t break_modulo = break_lines; if (break_modulo > 3) break_modulo -= 3; - for (i = 0; i < per_column; i++) { + for (size_t i = 0; i < per_column; i++) { - for (j = 0; j < n_columns; j++) { + for (size_t j = 0; j < n_columns; j++) { _cleanup_free_ char *e = NULL; if (j * per_column + i >= n) break; - e = ellipsize(x[j * per_column + i], width, percentage); + e = ellipsize(x[j * per_column + i], column_width, ellipsize_percentage); if (!e) - return log_oom(); + return -ENOMEM; - printf("%4zu) %-*s", j * per_column + i + 1, (int) width, e); + if (with_numbers) + printf("%s%4zu)%s ", + ansi_grey(), + j * per_column + i + 1, + ansi_normal()); + + if (grey_prefix && startswith(e, grey_prefix)) { + size_t k = MIN(strlen(grey_prefix), column_width); + printf("%s%.*s%s", + ansi_grey(), + (int) k, e, + ansi_normal()); + printf("%-*s", + (int) (column_width - k), e+k); + } else + printf("%-*s", (int) column_width, e); } putchar('\n'); diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index d11daefb56..c4ee1b3243 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -84,7 +84,7 @@ 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 *text, ...) _printf_(3, 4); int ask_string(char **ret, const char *text, ...) _printf_(2, 3); bool any_key_to_proceed(void); -int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage); +int show_menu(char **x, size_t n_columns, size_t column_width, unsigned ellipsize_percentage, const char *grey_prefix, bool with_numbers); int vt_disallocate(const char *name); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index aaff8a8e88..2ee231e2d0 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -135,7 +135,13 @@ static void print_welcome(int rfd) { done = true; } -static int prompt_loop(int rfd, const char *text, char **l, unsigned percentage, bool (*is_valid)(int rfd, const char *name), char **ret) { +static int prompt_loop( + int rfd, + const char *text, + char **l, + unsigned ellipsize_percentage, + bool (*is_valid)(int rfd, const char *name), + char **ret) { int r; assert(text); @@ -144,7 +150,6 @@ static int prompt_loop(int rfd, const char *text, char **l, unsigned percentage, for (;;) { _cleanup_free_ char *p = NULL; - unsigned u; r = ask_string(&p, strv_isempty(l) ? "%s %s (empty to skip): " : "%s %s (empty to skip, \"list\" to list options): ", @@ -159,14 +164,20 @@ static int prompt_loop(int rfd, const char *text, char **l, unsigned percentage, if (!strv_isempty(l)) { if (streq(p, "list")) { - r = show_menu(l, 3, 20, percentage); + r = show_menu(l, + /* n_columns= */ 3, + /* column_width= */ 20, + ellipsize_percentage, + /* grey_prefix= */ NULL, + /* with_numbers= */ true); if (r < 0) - return r; + return log_error_errno(r, "Failed to show menu: %m"); putchar('\n'); continue; } + unsigned u; r = safe_atou(p, &u); if (r >= 0) { if (u <= 0 || u > strv_length(l)) { diff --git a/src/home/homectl.c b/src/home/homectl.c index 89e9c8b82b..e1611b7bfb 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2530,7 +2530,6 @@ static int create_interactively(void) { for (;;) { _cleanup_free_ char *s = NULL; - unsigned u; r = ask_string(&s, "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", @@ -2552,15 +2551,21 @@ static int create_interactively(void) { continue; } - r = show_menu(available, /*n_columns=*/ 3, /*width=*/ 20, /*percentage=*/ 60); + r = show_menu(available, + /* n_columns= */ 3, + /* column_width= */ 20, + /* ellipsize_percentage= */ 60, + /* grey_prefix= */ NULL, + /* with_numbers= */ true); if (r < 0) - return r; + return log_error_errno(r, "Failed to show menu: %m"); putchar('\n'); continue; }; if (!strv_isempty(available)) { + unsigned u; r = safe_atou(s, &u); if (r >= 0) { if (u <= 0 || u > strv_length(available)) {