baseelement.go

  1package ansi
  2
  3import (
  4	"bytes"
  5	"fmt"
  6	"io"
  7	"strings"
  8	"text/template"
  9
 10	"github.com/charmbracelet/lipgloss/v2"
 11	"github.com/charmbracelet/x/ansi"
 12	"golang.org/x/text/cases"
 13	"golang.org/x/text/language"
 14)
 15
 16// BaseElement renders a styled primitive element.
 17type BaseElement struct {
 18	Token  string
 19	Prefix string
 20	Suffix string
 21	Style  StylePrimitive
 22}
 23
 24func formatToken(format string, token string) (string, error) {
 25	var b bytes.Buffer
 26
 27	v := make(map[string]interface{})
 28	v["text"] = token
 29
 30	tmpl, err := template.New(format).Funcs(TemplateFuncMap).Parse(format)
 31	if err != nil {
 32		return "", fmt.Errorf("glamour: error parsing template: %w", err)
 33	}
 34
 35	err = tmpl.Execute(&b, v)
 36	return b.String(), err
 37}
 38
 39func renderText(w io.Writer, rules StylePrimitive, s string) (int, error) { //nolint:unparam
 40	if len(s) == 0 {
 41		return 0, nil
 42	}
 43
 44	// XXX: We're using [ansi.Style] instead of [lipgloss.Style] because
 45	// Lip Gloss has a weird bug where it adds spaces when rendering joined
 46	// strings. Needs further investigation.
 47	style := ansi.Style{}
 48	if rules.Upper != nil && *rules.Upper {
 49		s = cases.Upper(language.English).String(s)
 50	}
 51	if rules.Lower != nil && *rules.Lower {
 52		s = cases.Lower(language.English).String(s)
 53	}
 54	if rules.Title != nil && *rules.Title {
 55		s = cases.Title(language.English).String(s)
 56	}
 57	if rules.Color != nil {
 58		style = style.ForegroundColor(lipgloss.Color(*rules.Color))
 59	}
 60	if rules.BackgroundColor != nil {
 61		style = style.BackgroundColor(lipgloss.Color(*rules.BackgroundColor))
 62	}
 63	if rules.Underline != nil && *rules.Underline {
 64		style = style.Underline()
 65	}
 66	if rules.Bold != nil && *rules.Bold {
 67		style = style.Bold()
 68	}
 69	if rules.Italic != nil && *rules.Italic {
 70		style = style.Italic()
 71	}
 72	if rules.CrossedOut != nil && *rules.CrossedOut {
 73		style = style.Strikethrough()
 74	}
 75	if rules.Inverse != nil && *rules.Inverse {
 76		style = style.Reverse()
 77	}
 78	if rules.Blink != nil && *rules.Blink {
 79		style = style.SlowBlink()
 80	}
 81
 82	n, err := io.WriteString(w, style.Styled(s))
 83	if err != nil {
 84		return n, fmt.Errorf("glamour: error writing to writer: %w", err)
 85	}
 86
 87	return n, nil
 88}
 89
 90// StyleOverrideRender renders a BaseElement with an overridden style.
 91func (e *BaseElement) StyleOverrideRender(w io.Writer, ctx RenderContext, style StylePrimitive) error {
 92	bs := ctx.blockStack
 93	st1 := cascadeStylePrimitives(bs.Current().Style.StylePrimitive, style)
 94	st2 := cascadeStylePrimitives(bs.With(e.Style), style)
 95
 96	return e.doRender(w, st1, st2)
 97}
 98
 99// Render renders a BaseElement.
100func (e *BaseElement) Render(w io.Writer, ctx RenderContext) error {
101	bs := ctx.blockStack
102	st1 := bs.Current().Style.StylePrimitive
103	st2 := bs.With(e.Style)
104	return e.doRender(w, st1, st2)
105}
106
107func (e *BaseElement) doRender(w io.Writer, st1, st2 StylePrimitive) error {
108	_, _ = renderText(w, st1, e.Prefix)
109	defer func() {
110		_, _ = renderText(w, st1, e.Suffix)
111	}()
112
113	// render unstyled prefix/suffix
114	_, _ = renderText(w, st1, st2.BlockPrefix)
115	defer func() {
116		_, _ = renderText(w, st1, st2.BlockSuffix)
117	}()
118
119	// render styled prefix/suffix
120	_, _ = renderText(w, st2, st2.Prefix)
121	defer func() {
122		_, _ = renderText(w, st2, st2.Suffix)
123	}()
124
125	s := e.Token
126	if len(st2.Format) > 0 {
127		var err error
128		s, err = formatToken(st2.Format, s)
129		if err != nil {
130			return err
131		}
132	}
133	_, _ = renderText(w, st2, escapeReplacer.Replace(s))
134	return nil
135}
136
137// https://www.markdownguide.org/basic-syntax/#characters-you-can-escape
138var escapeReplacer = strings.NewReplacer(
139	"\\\\", "\\",
140	"\\`", "`",
141	"\\*", "*",
142	"\\_", "_",
143	"\\{", "{",
144	"\\}", "}",
145	"\\[", "[",
146	"\\]", "]",
147	"\\<", "<",
148	"\\>", ">",
149	"\\(", "(",
150	"\\)", ")",
151	"\\#", "#",
152	"\\+", "+",
153	"\\-", "-",
154	"\\.", ".",
155	"\\!", "!",
156	"\\|", "|",
157)