From d69f94b4c6a6ed03c9a69fb3d0778cfb54430024 Mon Sep 17 00:00:00 2001 From: TastyHeadphones <76083688+TastyHeadphones@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:21:54 +0900 Subject: [PATCH] fix: style multiline messages line-by-line lipgloss pads every line to the widest line when Render gets a string with newlines, so styled multiline messages picked up huge trailing spaces. Render each line on its own instead. Fixes #187 --- text.go | 23 ++++++++++++++++++++++- text_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/text.go b/text.go index 9187c4c..b93aafe 100644 --- a/text.go +++ b/text.go @@ -8,6 +8,8 @@ import ( "time" "unicode" "unicode/utf8" + + "charm.land/lipgloss/v2" ) const ( @@ -164,6 +166,25 @@ func writeSpace(w io.Writer, first bool) { } } +// renderStyledMultiline applies style per line. lipgloss pads every line to the +// width of the widest line when Render is called on a string containing newlines, +// which produces trailing whitespace on shorter and empty lines (#187). +func renderStyledMultiline(style lipgloss.Style, s string) string { + if !strings.Contains(s, "\n") { + return style.Render(s) + } + + lines := strings.Split(s, "\n") + for i, line := range lines { + if line == "" { + continue + } + lines[i] = style.Render(line) + } + + return strings.Join(lines, "\n") +} + func (l *Logger) textFormatter(keyvals ...any) { st := l.styles lenKeyvals := len(keyvals) @@ -210,7 +231,7 @@ func (l *Logger) textFormatter(keyvals ...any) { case MessageKey: if msg := keyvals[i+1]; msg != nil { m := fmt.Sprint(msg) - m = st.Message.Render(m) + m = renderStyledMultiline(st.Message, m) writeSpace(&l.b, firstKey) l.b.WriteString(m) } diff --git a/text_test.go b/text_test.go index b194ee1..72b6f1c 100644 --- a/text_test.go +++ b/text_test.go @@ -9,6 +9,7 @@ import ( "os/exec" "path/filepath" "runtime" + "strings" "testing" "time" @@ -422,3 +423,26 @@ func TestCustomLevelStyle(t *testing.T) { l.Log(lvl, "foobar") assert.Equal(t, "\x1b[1mFUNKY\x1b[m foobar\n", buf.String()) } + +func TestMultilineMessageStyleNoTrailingPadding(t *testing.T) { + var buf bytes.Buffer + l := New(&buf) + l.SetColorProfile(colorprofile.TrueColor) + l.SetReportTimestamp(false) + + st := DefaultStyles() + st.Message = lipgloss.NewStyle().Foreground(lipgloss.Color("12")) + st.Levels[InfoLevel] = lipgloss.NewStyle(). + SetString("INFO"). + Padding(0, 1, 0, 1). + Background(lipgloss.Color("2")). + Foreground(lipgloss.Color("0")) + l.SetStyles(st) + + msg := "head\n\t* detail\n" + l.Infof("Error: '%s", msg) + + for _, line := range strings.Split(strings.TrimSuffix(buf.String(), "\n"), "\n") { + assert.Equal(t, line, strings.TrimRight(line, " "), "line %q has trailing whitespace padding", line) + } +}