From 9ec2c438a39542ec485e221d485c43b04d692aca Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 23 Jul 2026 23:28:15 +0200 Subject: tui: fix width-wrap garbling + straight-aligned verse column (export render.Wrap, hanging indent); strip niedziela summary subtitle (tui/cli/web); web verse grid alignment; refrain-italic pl-only --- internal/tui/tui.go | 57 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 11 deletions(-) (limited to 'internal/tui') diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 06ad19f..a4c14b9 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -306,16 +306,16 @@ func (m Model) bodyLines(w int) []string { } heading := render.LocalizeHeading(sec.Heading, sec.PartID, m.cfg.UILanguage) lines = append(lines, headingStyle.Render(heading)) - if sec.Subtitle != "" { - lines = append(lines, citationStyle.Render(sec.Subtitle)) - } lines = append(lines, "") _, blocks := render.GatherVersion(ver, sec, m.cfg.Lectionary, m.cfg.UILanguage) - isPsalm := sec.PartID == "psalm" + numW := maxNumWidth(blocks) + // The refrain-italic only applies to the pl responsorial-psalm block + // (its first, deduped paragraph); bible versions have no refrain block. + isPsalm := sec.PartID == "psalm" && ver == "pl" for bi, b := range blocks { refrain := isPsalm && bi == 0 - lines = append(lines, styleBlock(b, refrain, w)...) + lines = append(lines, styleBlock(b, refrain, w, numW)...) lines = append(lines, "") } } @@ -330,18 +330,53 @@ var verseNumRe = regexp.MustCompile(`^(\d+:\d+) (.*)$`) // block gets its "chapter:verse" prefix in the muted verse-number style and // its text in the default verse style; a psalm's first (refrain) block // renders italic; everything else renders in the default verse style. -func styleBlock(b string, refrain bool, w int) []string { +func styleBlock(b string, refrain bool, w, numW int) []string { + if w < 1 { + w = 1 + } if g := verseNumRe.FindStringSubmatch(b); g != nil { num, text := g[1], g[2] - wrapped := verseTextStyle.Width(w).Render(text) - wlines := strings.Split(wrapped, "\n") - wlines[0] = verseNumStyle.Render(num+" ") + wlines[0] - return wlines + col := numW + 2 // verse-number column: widest "chapter:verse" + 2 spaces + indent := strings.Repeat(" ", col) + tw := w - col + if tw < 1 { + tw = 1 + } + lines := strings.Split(render.Wrap(text, tw), "\n") + out := make([]string, 0, len(lines)) + for i, ln := range lines { + if i == 0 { + pad := strings.Repeat(" ", col-len([]rune(num))) + out = append(out, verseNumStyle.Render(num)+pad+verseTextStyle.Render(ln)) + } else { + out = append(out, indent+verseTextStyle.Render(ln)) + } + } + return out } style := verseTextStyle if refrain { style = refrainStyle } - return strings.Split(style.Width(w).Render(b), "\n") + lines := strings.Split(render.Wrap(b, w), "\n") + out := make([]string, 0, len(lines)) + for _, ln := range lines { + out = append(out, style.Render(ln)) + } + return out +} + +// maxNumWidth returns the widest "chapter:verse" prefix rune-width among the +// verse blocks, so styleBlock can align every verse's text to one column. +func maxNumWidth(blocks []string) int { + m := 0 + for _, b := range blocks { + if g := verseNumRe.FindStringSubmatch(b); g != nil { + if n := len([]rune(g[1])); n > m { + m = n + } + } + } + return m } -- cgit v1.3