diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 2a7d48b95da..cae42887c44 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -59,6 +59,8 @@ /* Other ANSI codes */ #define ANSI_UNDERLINE "\x1B[0;4m" +#define ANSI_ADD_UNDERLINE "\x1B[4m" +#define ANSI_ADD_UNDERLINE_GREY ANSI_ADD_UNDERLINE "\x1B[58;5;245m" #define ANSI_HIGHLIGHT "\x1B[0;1;39m" #define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" @@ -189,6 +191,15 @@ static inline const char *ansi_underline(void) { return underline_enabled() ? ANSI_UNDERLINE : ANSI_NORMAL; } +static inline const char *ansi_add_underline(void) { + return underline_enabled() ? ANSI_ADD_UNDERLINE : ""; +} + +static inline const char *ansi_add_underline_grey(void) { + return underline_enabled() ? + (colors_enabled() ? ANSI_ADD_UNDERLINE_GREY : ANSI_ADD_UNDERLINE) : ""; +} + #define DEFINE_ANSI_FUNC_UNDERLINE(name, NAME) \ static inline const char *ansi_##name(void) { \ return underline_enabled() ? ANSI_##NAME##_UNDERLINE : \ diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 9a19177fde3..41471dade03 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -77,7 +77,9 @@ typedef struct TableData { unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */ unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */ - bool uppercase; /* Uppercase string on display */ + bool uppercase:1; /* Uppercase string on display */ + bool underline:1; + bool rgap_underline:1; const char *color; /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */ const char *rgap_color; /* The ANSI color to use for the gap right of this cell. Usually used to underline entire rows in a gapless fashion */ @@ -401,7 +403,7 @@ static bool table_data_matches( return false; /* If a color/url is set, refuse to merge */ - if (d->color || d->rgap_color) + if (d->color || d->rgap_color || d->underline || d->rgap_underline) return false; if (d->url) return false; @@ -617,6 +619,8 @@ static int table_dedup_cell(Table *t, TableCell *cell) { nd->color = od->color; nd->rgap_color = od->rgap_color; + nd->underline = od->underline; + nd->rgap_underline = od->rgap_underline; nd->url = TAKE_PTR(curl); table_data_unref(od); @@ -759,6 +763,46 @@ int table_set_rgap_color(Table *t, TableCell *cell, const char *color) { return 0; } +int table_set_underline(Table *t, TableCell *cell, bool b) { + TableData *d; + int r; + + assert(t); + assert(cell); + + r = table_dedup_cell(t, cell); + if (r < 0) + return r; + + assert_se(d = table_get_data(t, cell)); + + if (d->underline == b) + return 0; + + d->underline = b; + return 1; +} + +int table_set_rgap_underline(Table *t, TableCell *cell, bool b) { + TableData *d; + int r; + + assert(t); + assert(cell); + + r = table_dedup_cell(t, cell); + if (r < 0) + return r; + + assert_se(d = table_get_data(t, cell)); + + if (d->rgap_underline == b) + return 0; + + d->rgap_underline = b; + return 1; +} + int table_set_url(Table *t, TableCell *cell, const char *url) { _cleanup_free_ char *copy = NULL; int r; @@ -834,6 +878,8 @@ int table_update(Table *t, TableCell *cell, TableDataType type, const void *data nd->color = od->color; nd->rgap_color = od->rgap_color; + nd->underline = od->underline; + nd->rgap_underline = od->rgap_underline; nd->url = TAKE_PTR(curl); table_data_unref(od); @@ -1101,6 +1147,31 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { goto check; } + case TABLE_SET_UNDERLINE: { + int u = va_arg(ap, int); + r = table_set_underline(t, last_cell, u); + goto check; + } + + case TABLE_SET_RGAP_UNDERLINE: { + int u = va_arg(ap, int); + r = table_set_rgap_underline(t, last_cell, u); + goto check; + } + + case TABLE_SET_BOTH_UNDERLINES: { + int u = va_arg(ap, int); + + r = table_set_underline(t, last_cell, u); + if (r < 0) { + va_end(ap); + return r; + } + + r = table_set_rgap_underline(t, last_cell, u); + goto check; + } + case TABLE_SET_URL: { const char *u = va_arg(ap, const char*); r = table_set_url(t, last_cell, u); @@ -2130,8 +2201,6 @@ static const char* table_data_color(TableData *d) { if (d->type == TABLE_FIELD) return ansi_bright_blue(); - if (d->type == TABLE_HEADER) - return ansi_underline(); return NULL; } @@ -2139,11 +2208,29 @@ static const char* table_data_color(TableData *d) { static const char* table_data_rgap_color(TableData *d) { assert(d); - if (d->rgap_color) - return d->rgap_color; + return d->rgap_color ?: d->rgap_color; +} + +static const char* table_data_underline(TableData *d) { + assert(d); + + if (d->underline) + return /* cescape( */ansi_add_underline_grey()/* ) */; if (d->type == TABLE_HEADER) - return ansi_underline(); + return ansi_add_underline(); + + return NULL; +} + +static const char* table_data_rgap_underline(TableData *d) { + assert(d); + + if (d->rgap_underline) + return ansi_add_underline_grey(); + + if (d->type == TABLE_HEADER) + return ansi_add_underline(); return NULL; } @@ -2418,13 +2505,13 @@ int table_print(Table *t, FILE *f) { row = t->data + i * t->n_columns; do { - const char *gap_color = NULL; + const char *gap_color = NULL, *gap_underline = NULL; more_sublines = false; for (size_t j = 0; j < display_columns; j++) { _cleanup_free_ char *buffer = NULL, *extracted = NULL; bool lines_truncated = false; - const char *field, *color = NULL; + const char *field, *color = NULL, *underline = NULL; TableData *d; size_t l; @@ -2490,6 +2577,7 @@ int table_print(Table *t, FILE *f) { /* Drop trailing white spaces of last column when no cosmetics is set. */ if (j == display_columns - 1 && (!colors_enabled() || !table_data_color(d)) && + (!underline_enabled() || !table_data_underline(d)) && (!urlify_enabled() || !d->url)) delete_trailing_chars(aligned, NULL); @@ -2511,27 +2599,36 @@ int table_print(Table *t, FILE *f) { if (colors_enabled() && gap_color) fputs(gap_color, f); + if (underline_enabled() && gap_underline) + fputs(gap_underline, f); if (j > 0) fputc(' ', f); /* column separator left of cell */ + /* Undo gap color/underline */ + if ((colors_enabled() && gap_color) || + (underline_enabled() && gap_underline)) + fputs(ANSI_NORMAL, f); + if (colors_enabled()) { color = table_data_color(d); - - /* Undo gap color */ - if (gap_color) - fputs(ANSI_NORMAL, f); - if (color) fputs(color, f); } + if (underline_enabled()) { + underline = table_data_underline(d); + if (underline) + fputs(underline, f); + } + fputs(field, f); - if (colors_enabled() && color) + if (color || underline) fputs(ANSI_NORMAL, f); gap_color = table_data_rgap_color(d); + gap_underline = table_data_rgap_underline(d); } fputc('\n', f); diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 37bfbca693b..0368447638c 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -68,6 +68,9 @@ typedef enum TableDataType { TABLE_SET_COLOR, TABLE_SET_RGAP_COLOR, TABLE_SET_BOTH_COLORS, + TABLE_SET_UNDERLINE, + TABLE_SET_RGAP_UNDERLINE, + TABLE_SET_BOTH_UNDERLINES, TABLE_SET_URL, TABLE_SET_UPPERCASE, @@ -111,6 +114,8 @@ int table_set_align_percent(Table *t, TableCell *cell, unsigned percent); int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent); int table_set_color(Table *t, TableCell *cell, const char *color); int table_set_rgap_color(Table *t, TableCell *cell, const char *color); +int table_set_underline(Table *t, TableCell *cell, bool b); +int table_set_rgap_underline(Table *t, TableCell *cell, bool b); int table_set_url(Table *t, TableCell *cell, const char *url); int table_set_uppercase(Table *t, TableCell *cell, bool b); diff --git a/src/systemctl/systemctl-list-units.c b/src/systemctl/systemctl-list-units.c index e48cf45333b..a1482d40c22 100644 --- a/src/systemctl/systemctl-list-units.c +++ b/src/systemctl/systemctl-list-units.c @@ -127,29 +127,45 @@ static int output_units_list(const UnitInfo *unit_infos, size_t c) { table_set_ersatz_string(table, TABLE_ERSATZ_DASH); FOREACH_ARRAY(u, unit_infos, c) { + const char *on_loaded = NULL, *on_active = NULL, *on_sub = NULL, *on_circle = NULL; _cleanup_free_ char *id = NULL; - const char *on_underline = "", *on_loaded = "", *on_active = "", *on_circle = ""; - bool circle = false, underline = false; + bool circle = false, underline; - if (u + 1 < unit_infos + c && - !streq(unit_type_suffix(u->id), unit_type_suffix((u + 1)->id))) { - on_underline = ansi_underline(); - underline = true; + underline = u + 1 < unit_infos + c && !streq(unit_type_suffix(u->id), unit_type_suffix((u + 1)->id)); + + if (streq(u->load_state, "not-found")) { + on_circle = on_loaded = ansi_highlight_yellow(); + on_circle = ansi_highlight_yellow(); + circle = true; + } else if (STR_IN_SET(u->load_state, "bad-setting", "error", "masked")) { + on_loaded = ansi_highlight_red(); + on_circle = ansi_highlight_yellow(); + circle = true; } - if (STR_IN_SET(u->load_state, "error", "not-found", "bad-setting", "masked") && !arg_plain) { - on_circle = underline ? ansi_highlight_yellow_underline() : ansi_highlight_yellow(); + if (streq(u->active_state, "failed")) { + on_sub = on_active = ansi_highlight_red(); + + /* Here override any load_state highlighting */ + on_circle = ansi_highlight_red(); circle = true; - on_loaded = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); - } else if (streq(u->active_state, "failed") && !arg_plain) { - on_circle = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); - circle = true; - on_active = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); - } else { - on_circle = on_underline; - on_active = on_underline; - on_loaded = on_underline; - } + } else if (STR_IN_SET(u->active_state, "reloading", "activating", "maintenance", "deactivating")) { + on_sub = on_active = ansi_highlight(); + + if (!circle) { /* Here we let load_state highlighting win */ + on_circle = ansi_highlight(); + circle = true; + } + } else if (streq(u->active_state, "inactive")) + on_sub = on_active = ansi_grey(); + + /* As a special case, when this is a service which has not process running, let's grey out + * its state, to highlight that a bit */ + if (!on_sub && endswith(u->id, ".service") && streq(u->sub_state, "exited")) + on_sub = ansi_grey(); + + if (arg_plain) + circle = false; id = format_unit_id(u->id, u->machine); if (!id) @@ -157,19 +173,24 @@ static int output_units_list(const UnitInfo *unit_infos, size_t c) { r = table_add_many(table, TABLE_STRING, circle ? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE) : " ", - TABLE_SET_BOTH_COLORS, on_circle, + TABLE_SET_COLOR, on_circle, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, id, - TABLE_SET_BOTH_COLORS, on_active, + TABLE_SET_COLOR, on_active, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, u->load_state, - TABLE_SET_BOTH_COLORS, on_loaded, + TABLE_SET_COLOR, on_loaded, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, u->active_state, - TABLE_SET_BOTH_COLORS, on_active, + TABLE_SET_COLOR, on_active, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, u->sub_state, - TABLE_SET_BOTH_COLORS, on_active, + TABLE_SET_COLOR, on_sub, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, u->job_id ? u->job_type: "", - TABLE_SET_BOTH_COLORS, on_underline, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, u->description, - TABLE_SET_BOTH_COLORS, on_underline); + TABLE_SET_BOTH_UNDERLINES, underline); if (r < 0) return table_log_add_error(r); @@ -214,12 +235,14 @@ static int output_units_list(const UnitInfo *unit_infos, size_t c) { if (arg_all || strv_contains(arg_states, "inactive")) printf("%s%zu loaded units listed.%s\n" - "To show all installed unit files use 'systemctl list-unit-files'.\n", - on, records, off); + "%sTo show all installed unit files use 'systemctl list-unit-files'.%s\n", + on, records, off, + ansi_grey(), ansi_normal()); else if (!arg_states) - printf("%s%zu loaded units listed.%s Pass --all to see loaded but inactive units, too.\n" - "To show all installed unit files use 'systemctl list-unit-files'.\n", - on, records, off); + printf("%s%zu loaded units listed.%s %sPass --all to see loaded but inactive units, too.%s\n" + "%sTo show all installed unit files use 'systemctl list-unit-files'.%s\n", + on, records, off, + ansi_grey(), ansi_normal(), ansi_grey(), ansi_normal()); else printf("%zu loaded units listed.\n", records); }