commit - 17219495f34a017ba2918517de54d8c7bf15a888
commit + 502e0d5bb61287accc4380aa23b380a8929faf1b
blob - dad863b30ac32f2fd1a3392e2953e7f5b9cf1aaa
blob + df3e4fec2c678e864b42e6ab4ee50929f564c3b0
--- internal/model/model.go
+++ internal/model/model.go
"charm.land/lipgloss/v2"
"github.com/charmbracelet/glamour"
+ "github.com/charmbracelet/x/ansi/kitty"
"github.com/maaslalani/slides/internal/bib"
"github.com/maaslalani/slides/internal/code"
"github.com/maaslalani/slides/internal/image"
}
}
+// centerMarker on its own line makes renderSlide horizontally center the
+// slide's text.
+const centerMarker = "<!-- center -->"
+
+// centerLines horizontally centers every non-blank text line of s within
+// width. Lines carrying a Kitty graphics placeholder are left untouched --
+// their own PadLeft already centers the image.
+func centerLines(s string, width int) string {
+ lines := strings.Split(s, "\n")
+ for i, line := range lines {
+ if strings.ContainsRune(line, kitty.Placeholder) {
+ continue
+ }
+ trimmed := strings.TrimSpace(line)
+ w := lipgloss.Width(trimmed)
+ if w == 0 || w >= width {
+ continue
+ }
+ lines[i] = strings.Repeat(" ", (width-w)/2) + trimmed
+ }
+ return strings.Join(lines, "\n")
+}
+
func fileWatchCmd() tea.Cmd {
return tea.Every(time.Second, func(t time.Time) tea.Msg {
return fileWatchMsg{}
r, _ := glamour.NewTermRenderer(m.Theme, glamour.WithWordWrap(contentWidth))
slide := code.HideComments(md)
+ // A slide carrying `<!-- center -->` on its own line has every text
+ // line horizontally centered after rendering (images keep their own
+ // centering). Strip the marker before glamour sees it.
+ centered := strings.Contains(slide, centerMarker)
+ if centered {
+ slide = strings.ReplaceAll(slide, centerMarker+"\n", "")
+ slide = strings.ReplaceAll(slide, centerMarker, "")
+ }
+
maxCols, maxRows := m.mediaSize()
spin := m.spinner.View()
if len(latexPlacements) > 0 {
slide = latex.Expand(slide, latexPlacements)
}
+ if centered {
+ slide = centerLines(slide, contentWidth)
+ }
if margin := m.leftMargin(); margin > 0 {
pad := strings.Repeat(" ", margin)
lines := strings.Split(slide, "\n")
blob - /dev/null
blob + 3f4d280872cee04739c4627f646e73179d20fb07 (mode 644)
--- /dev/null
+++ internal/model/center_test.go
+package model
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/charmbracelet/x/ansi/kitty"
+)
+
+func TestCenterLines(t *testing.T) {
+ const width = 20
+
+ got := centerLines("hello\n\n spaced ", width)
+ lines := strings.Split(got, "\n")
+ if lines[0] != strings.Repeat(" ", 7)+"hello" { // (20-5)/2 = 7
+ t.Errorf("line 0 not centered: %q", lines[0])
+ }
+ if lines[1] != "" {
+ t.Errorf("blank line changed: %q", lines[1])
+ }
+ if lines[2] != strings.Repeat(" ", 7)+"spaced" { // trimmed len 6, (20-6)/2 = 7
+ t.Errorf("line 2 not centered/trimmed: %q", lines[2])
+ }
+}
+
+func TestCenterLinesLeavesPlaceholdersAndAnsi(t *testing.T) {
+ ph := string(kitty.Placeholder) + "xxxx"
+ got := centerLines(ph, 40)
+ if got != ph {
+ t.Errorf("placeholder line was modified: %q", got)
+ }
+
+ ansi := "\x1b[1mbold\x1b[0m" // visible width 4
+ got = centerLines(ansi, 10)
+ if got != strings.Repeat(" ", 3)+ansi { // (10-4)/2 = 3
+ t.Errorf("ansi width miscounted: %q", got)
+ }
+
+ long := "this line is wider than the width"
+ if centerLines(long, 10) != long {
+ t.Errorf("over-wide line should be untouched")
+ }
+}