]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
shared/verbs: split verbs in two lines when the synopsis is > 25 characters
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 14 May 2026 09:23:40 +0000 (11:23 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 14 May 2026 10:59:20 +0000 (12:59 +0200)
The help tests would not pass because in cases where the verb synopsis
is very long, we'd format the table badly if the terminal is fairly
narrow. I experimented with a few solutions, but overall, it's hard to
achieve very good layout with the automatic formatting. I think the
approach in this commit works the best: we end up with an two- or
three-line verb synopis, which is similar to what we did manually
before.

$ COLUMNS=80 build/localectl -h
...
Commands:
  [status]                      Show current locale settings
  set-locale LOCALE...          Set system locale
  list-locales                  Show known locales
  set-keymap MAP [MAP]          Set console and X11 keyboard mappings
  list-keymaps                  Show known virtual console keyboard mappings
  set-x11-keymap LAYOUT [MODEL  Set X11 and console keyboard mappings
    [VARIANT [OPTIONS]]]
  list-x11-keymap-models        Show known X11 keyboard mapping models
  list-x11-keymap-layouts       Show known X11 keyboard mapping layouts
  list-x11-keymap-variants      Show known X11 keyboard mapping variants
    [LAYOUT]
  list-x11-keymap-options       Show known X11 keyboard mapping options

I think that almost nobody actually uses an 80 column terminal, and if
they do, they probably don't spend too much time looking at our --help
output there. So the goal here is to do something reasonable and robust
and get the tests to pass.

We can use strjoina here because the strings are fully under our
control.

src/shared/verbs.c

index e488af63fecd41a166da95867657fcf0ab6cde37..aab24c068199a58eb4961c4723ea199ff957e617 100644 (file)
@@ -7,6 +7,7 @@
 #include "log.h"
 #include "string-util.h"
 #include "strv.h"
+#include "terminal-util.h"
 #include "verbs.h"
 #include "virt.h"
 
@@ -160,6 +161,72 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
         return _dispatch_verb_with_args(strv_skip(argv, optind), verbs, verbs + n, userdata);
 }
 
+#define VERB_SYNOPSIS_WIDTH_SANE 25
+
+static const char* find_point_to_break(const char *s, size_t max_width) {
+        /* Locate the first space, preferably after max_width, or the last space otherwise.
+         * Return the part after the space. */
+
+        if (strlen(s) <= max_width)
+                return NULL;
+
+        const char *p = strchr(s + max_width, ' ') ?: strrchr(s, ' ');
+        return p ? p + 1 : NULL;
+}
+
+static int verb_add_help_one(Table *table, const Verb *verb) {
+        assert(table);
+        assert(verb);
+
+        bool is_default = FLAGS_SET(verb->flags, VERB_DEFAULT);
+        int r;
+
+        /* We indent the option string by two spaces. We could set the minimum cell width and
+         * right-align for a similar result, but that'd be more work. This is only used for
+         * display. */
+        _cleanup_free_ char *s = strjoin("  ",
+                                         is_default ? "[" : "",
+                                         verb->verb,
+                                         verb->argspec ? " " : "",
+                                         strempty(verb->argspec),
+                                         is_default ? "]" : "");
+        if (!s)
+                return log_oom();
+
+        const char *ss = NULL;
+        if (columns() < VERB_SYNOPSIS_WIDTH_SANE * 4) {
+                /* If the synopsis is very wide, try to split it up. But do this only if the terminal
+                 * is not very wide. If it _is_ wide, the broken up synopsis would look silly. */
+                const char *p = find_point_to_break(s, VERB_SYNOPSIS_WIDTH_SANE), *p2 = NULL;
+                if (p) {
+                        const char *s1 = strndupa_safe(s, p - s), *s2 = NULL;
+
+                        p2 = find_point_to_break(p, VERB_SYNOPSIS_WIDTH_SANE - 4); /* we indent by two spaces more */
+                        if (p2)
+                                s2 = strndupa_safe(p, p2 - p);
+
+                        if (s2)
+                                ss = strjoina(s1, "\n    ", s2, "\n    ", p2);
+                        else
+                                ss = strjoina(s1, "\n    ", p);
+                }
+        }
+
+        r = table_add_cell(table, NULL, TABLE_STRING, ss ?: s);
+        if (r < 0)
+                return table_log_add_error(r);
+
+        _cleanup_strv_free_ char **t = strv_split(verb->help, /* separators= */ NULL);
+        if (!t)
+                return log_oom();
+
+        r = table_add_many(table, TABLE_STRV_WRAPPED, t);
+        if (r < 0)
+                return table_log_add_error(r);
+
+        return 0;
+}
+
 int _verbs_get_help_table(
                 const Verb verbs[],
                 const Verb verbs_end[],
@@ -192,27 +259,9 @@ int _verbs_get_help_table(
                         /* No help string — we do not show the verb */
                         continue;
 
-                bool is_default = FLAGS_SET(verb->flags, VERB_DEFAULT);
-
-                /* We indent the option string by two spaces. We could set the minimum cell width and
-                 * right-align for a similar result, but that'd be more work. This is only used for
-                 * display. */
-                r = table_add_cell_stringf(table, NULL, "  %s%s%s%s%s",
-                                           is_default ? "[" : "",
-                                           verb->verb,
-                                           verb->argspec ? " " : "",
-                                           strempty(verb->argspec),
-                                           is_default ? "]" : "");
-                if (r < 0)
-                        return table_log_add_error(r);
-
-                _cleanup_strv_free_ char **s = strv_split(verb->help, /* separators= */ NULL);
-                if (!s)
-                        return log_oom();
-
-                r = table_add_many(table, TABLE_STRV_WRAPPED, s);
+                r = verb_add_help_one(table, verb);
                 if (r < 0)
-                        return table_log_add_error(r);
+                        return r;
         }
 
         table_set_header(table, false);