]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
terminal-util: beef up show_menu()
authorLennart Poettering <lennart@poettering.net>
Thu, 6 Feb 2025 11:02:24 +0000 (12:02 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 17 Feb 2025 14:21:13 +0000 (15:21 +0100)
This modernizes the function a bit, and adds some bits:

1. whether to show numbers before entries is now optional, and if they
   are shown they are displayed in grey.

2. a common prefix can now be grayed out (later useful for completion
   support)

3. some variables have been named to clarify their purpose

4. the table display dimensions can now be auto-sized (by specifying
   SIZE_MAX and number of columns and column width)

src/basic/terminal-util.c
src/basic/terminal-util.h
src/firstboot/firstboot.c
src/home/homectl.c

index b2cbadb579cb4f86d655e9b0de912635c52b107e..53437e690f184f2e2e5a89244238e24abe666ee8 100644 (file)
@@ -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;
+
+        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;
 
-        break_lines = lines();
+                /* 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();
-
-                        printf("%4zu) %-*s", j * per_column + i + 1, (int) width, e);
+                                return -ENOMEM;
+
+                        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');
index d11daefb561fde525ee8510e916cad541a2972af..c4ee1b32434905596c767cd57ab4fde250b061b7 100644 (file)
@@ -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);
 
index aaff8a8e8855f254e8ed2db69a13888b8ee20bb0..2ee231e2d00b4c4aaeae331e2f1f3c7b8857e8fa 100644 (file)
@@ -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)) {
index 89e9c8b82b117f05fdb3eff369bacff085329df9..e1611b7bfb4a1b1295fa9faa0cadb06a79eae71e 100644 (file)
@@ -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)) {