From 7fe79a13e51c084eaf39f3ee934449937a63ec7a Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Thu, 28 May 2026 10:32:06 +0200 Subject: [PATCH 1/2] cmdio: add Bold and Dim color helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two helpers to the existing Red/Green/etc. set: - Bold renders text in bold (\x1b[1m). The ansiBold constant already existed in color.go but wasn't exposed as a helper. - Dim renders text in dim/faint intensity (\x1b[2m). The ansiDim constant is new. Both gate on the same SupportsStdoutColor capability check as the existing color helpers, so they degrade to plain text in the same conditions. These are useful for command output where colored emphasis would be distracting but visual hierarchy still helps — e.g. bold IDs and command names, dim secondary metadata. Co-authored-by: Isaac --- libs/cmdio/color.go | 7 +++++++ libs/cmdio/color_test.go | 2 ++ 2 files changed, 9 insertions(+) diff --git a/libs/cmdio/color.go b/libs/cmdio/color.go index ffece82e47e..246147add36 100644 --- a/libs/cmdio/color.go +++ b/libs/cmdio/color.go @@ -11,6 +11,7 @@ import ( const ( ansiReset = "\x1b[0m" ansiBold = "\x1b[1m" + ansiDim = "\x1b[2m" ansiFaint = "\x1b[2m" ansiItalic = "\x1b[3m" ansiUnderline = "\x1b[4m" @@ -44,6 +45,12 @@ func render(ctx context.Context, code, msg string) string { return code + msg + ansiReset } +// Bold renders msg in bold. +func Bold(ctx context.Context, msg string) string { return render(ctx, ansiBold, msg) } + +// Dim renders msg in dim (faint) intensity. +func Dim(ctx context.Context, msg string) string { return render(ctx, ansiDim, msg) } + // Red renders msg in red. func Red(ctx context.Context, msg string) string { return render(ctx, ansiRed, msg) } diff --git a/libs/cmdio/color_test.go b/libs/cmdio/color_test.go index 54df1859827..dcc45f9c943 100644 --- a/libs/cmdio/color_test.go +++ b/libs/cmdio/color_test.go @@ -27,6 +27,8 @@ func TestColorHelpersEmitSGRWhenEnabled(t *testing.T) { got string want string }{ + {"Bold", cmdio.Bold(ctx, "id"), "\x1b[1mid\x1b[0m"}, + {"Dim", cmdio.Dim(ctx, "hint"), "\x1b[2mhint\x1b[0m"}, {"Red", cmdio.Red(ctx, "hello"), "\x1b[31mhello\x1b[0m"}, {"Green", cmdio.Green(ctx, "ok"), "\x1b[32mok\x1b[0m"}, {"Yellow", cmdio.Yellow(ctx, "warn"), "\x1b[33mwarn\x1b[0m"}, From 9177849fce34dedf52f1cd11346f7c904746199a Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Thu, 28 May 2026 11:46:44 +0200 Subject: [PATCH 2/2] cmdio: rename Dim to Faint, add Italic/Underline/Magenta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align helpers with the constants and SGR nomenclature they reference: - Rename the previously-added Dim helper to Faint. The underlying constant is ansiFaint (SGR 2, "Faint, decreased intensity, or dim"). Helpers in this file are named after the SGR descriptor of their constant (Bold/Red/Green/…), so Faint matches that pattern. The earlier addition introduced ansiDim as a second constant for the same byte sequence; drop that and route Faint through ansiFaint, removing the duplicate. - Add Italic, Underline, and Magenta helpers. Constants for these already lived in color.go (and Magenta even had a template binding via RenderFuncMap), but no Go-callable wrapper. They round out the basic-16 styles likely to be useful for CLI emphasis without going to the full fatih/color kitchen sink. - Add faint and underline bindings to RenderFuncMap so templates can reach them on the same footing as bold and italic. - Align the Bold and Faint doc comments with the SGR table phrasing ("increased intensity" / "decreased intensity") so the two read as paired intensities, matching the spec. The Hi* set is intentionally left as-is (only HiBlack and HiBlue) — those have real callers in libs/apps/logstream and the warehouse picker; the other six bright variants have none. Co-authored-by: Isaac --- libs/cmdio/color.go | 34 ++++++++++++++++++++++------------ libs/cmdio/color_test.go | 7 +++++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/libs/cmdio/color.go b/libs/cmdio/color.go index 246147add36..6ad00834a4a 100644 --- a/libs/cmdio/color.go +++ b/libs/cmdio/color.go @@ -11,7 +11,6 @@ import ( const ( ansiReset = "\x1b[0m" ansiBold = "\x1b[1m" - ansiDim = "\x1b[2m" ansiFaint = "\x1b[2m" ansiItalic = "\x1b[3m" ansiUnderline = "\x1b[4m" @@ -45,11 +44,17 @@ func render(ctx context.Context, code, msg string) string { return code + msg + ansiReset } -// Bold renders msg in bold. +// Bold renders msg with increased intensity (bold). func Bold(ctx context.Context, msg string) string { return render(ctx, ansiBold, msg) } -// Dim renders msg in dim (faint) intensity. -func Dim(ctx context.Context, msg string) string { return render(ctx, ansiDim, msg) } +// Faint renders msg with decreased intensity (faint, dim). +func Faint(ctx context.Context, msg string) string { return render(ctx, ansiFaint, msg) } + +// Italic renders msg in italic. +func Italic(ctx context.Context, msg string) string { return render(ctx, ansiItalic, msg) } + +// Underline renders msg underlined. +func Underline(ctx context.Context, msg string) string { return render(ctx, ansiUnderline, msg) } // Red renders msg in red. func Red(ctx context.Context, msg string) string { return render(ctx, ansiRed, msg) } @@ -63,6 +68,9 @@ func Yellow(ctx context.Context, msg string) string { return render(ctx, ansiYel // Blue renders msg in blue. func Blue(ctx context.Context, msg string) string { return render(ctx, ansiBlue, msg) } +// Magenta renders msg in magenta. +func Magenta(ctx context.Context, msg string) string { return render(ctx, ansiMagenta, msg) } + // Cyan renders msg in cyan. func Cyan(ctx context.Context, msg string) string { return render(ctx, ansiCyan, msg) } @@ -77,14 +85,16 @@ func HiBlue(ctx context.Context, msg string) string { return render(ctx, ansiHiB // helpers accept a format string + args. func RenderFuncMap(ctx context.Context) template.FuncMap { return template.FuncMap{ - "red": templateColor(ctx, ansiRed), - "green": templateColor(ctx, ansiGreen), - "blue": templateColor(ctx, ansiBlue), - "yellow": templateColor(ctx, ansiYellow), - "magenta": templateColor(ctx, ansiMagenta), - "cyan": templateColor(ctx, ansiCyan), - "bold": templateColor(ctx, ansiBold), - "italic": templateColor(ctx, ansiItalic), + "red": templateColor(ctx, ansiRed), + "green": templateColor(ctx, ansiGreen), + "blue": templateColor(ctx, ansiBlue), + "yellow": templateColor(ctx, ansiYellow), + "magenta": templateColor(ctx, ansiMagenta), + "cyan": templateColor(ctx, ansiCyan), + "bold": templateColor(ctx, ansiBold), + "faint": templateColor(ctx, ansiFaint), + "italic": templateColor(ctx, ansiItalic), + "underline": templateColor(ctx, ansiUnderline), } } diff --git a/libs/cmdio/color_test.go b/libs/cmdio/color_test.go index dcc45f9c943..fbbd4cf700e 100644 --- a/libs/cmdio/color_test.go +++ b/libs/cmdio/color_test.go @@ -28,11 +28,14 @@ func TestColorHelpersEmitSGRWhenEnabled(t *testing.T) { want string }{ {"Bold", cmdio.Bold(ctx, "id"), "\x1b[1mid\x1b[0m"}, - {"Dim", cmdio.Dim(ctx, "hint"), "\x1b[2mhint\x1b[0m"}, + {"Faint", cmdio.Faint(ctx, "hint"), "\x1b[2mhint\x1b[0m"}, + {"Italic", cmdio.Italic(ctx, "em"), "\x1b[3mem\x1b[0m"}, + {"Underline", cmdio.Underline(ctx, "link"), "\x1b[4mlink\x1b[0m"}, {"Red", cmdio.Red(ctx, "hello"), "\x1b[31mhello\x1b[0m"}, {"Green", cmdio.Green(ctx, "ok"), "\x1b[32mok\x1b[0m"}, {"Yellow", cmdio.Yellow(ctx, "warn"), "\x1b[33mwarn\x1b[0m"}, {"Blue", cmdio.Blue(ctx, "info"), "\x1b[34minfo\x1b[0m"}, + {"Magenta", cmdio.Magenta(ctx, "trace"), "\x1b[35mtrace\x1b[0m"}, {"Cyan", cmdio.Cyan(ctx, "debug"), "\x1b[36mdebug\x1b[0m"}, {"HiBlack", cmdio.HiBlack(ctx, "dim"), "\x1b[90mdim\x1b[0m"}, {"HiBlue", cmdio.HiBlue(ctx, "APP"), "\x1b[94mAPP\x1b[0m"}, @@ -66,7 +69,7 @@ func TestRenderFuncMap(t *testing.T) { ctx := ttyContext(t) fm := cmdio.RenderFuncMap(ctx) - for _, name := range []string{"red", "green", "blue", "yellow", "magenta", "cyan", "bold", "italic"} { + for _, name := range []string{"red", "green", "blue", "yellow", "magenta", "cyan", "bold", "faint", "italic", "underline"} { _, ok := fm[name].(func(string, ...any) string) assert.True(t, ok, "FuncMap missing %q or wrong signature", name) }