on main help sign in
fix: use same chroma formatter as diffview for markdown (#2656)
commit3138c8bc656075148b05e893ed2b9498d4cbef39 authorAndrey Nering <andreynering@users.noreply.github.com> dateMon 20 Apr 2026, 17:06 (6 months ago) parent0e9e0a6
4 files changed+76−60
internal/ui/common/markdown.go+15 internal/ui/diffview/chroma.go−57 internal/ui/diffview/diffview.go+9−3 internal/ui/xchroma/chroma.go+52
internal/ui/common/markdown.go+15
@@ -1,16 +1,30 @@
11package common
22
33import (
4+ "image/color"
5+
46 "charm.land/glamour/v2"
7+ "github.com/alecthomas/chroma/v2/formatters"
58 "github.com/charmbracelet/crush/internal/ui/styles"
9+ "github.com/charmbracelet/crush/internal/ui/xchroma"
610)
711
12+const formatterName = "crush"
13+
14+func init() {
15+ // NOTE: Glamour does not offer us an option to pass the formatter
16+ // implementation directly. We need to register and use by name.
17+ var zero color.Color
18+ formatters.Register(formatterName, xchroma.Formatter(zero, nil))
19+}
20+
821// MarkdownRenderer returns a glamour [glamour.TermRenderer] configured with
922// the given styles and width.
1023func MarkdownRenderer(sty *styles.Styles, width int) *glamour.TermRenderer {
1124 r, _ := glamour.NewTermRenderer(
1225 glamour.WithStyles(sty.Markdown),
1326 glamour.WithWordWrap(width),
27+ glamour.WithChromaFormatter(formatterName),
1428 )
1529 return r
1630}
@@ -21,6 +35,7 @@ func PlainMarkdownRenderer(sty *styles.Styles, width int) *glamour.TermRenderer
2135 r, _ := glamour.NewTermRenderer(
2236 glamour.WithStyles(sty.PlainMarkdown),
2337 glamour.WithWordWrap(width),
38+ glamour.WithChromaFormatter(formatterName),
2439 )
2540 return r
2641}
internal/ui/diffview/chroma.godeleted−57
@@ -1,57 +0,0 @@
1−package diffview
2−
3−import (
4− "fmt"
5− "image/color"
6− "io"
7− "strings"
8−
9− "charm.land/lipgloss/v2"
10− "github.com/alecthomas/chroma/v2"
11− "github.com/charmbracelet/crush/internal/ansiext"
12−)
13−
14−var _ chroma.Formatter = chromaFormatter{}
15−
16−// chromaFormatter is a custom formatter for Chroma that uses Lip Gloss for
17−// foreground styling, while keeping a forced background color.
18−type chromaFormatter struct {
19− bgColor color.Color
20−}
21−
22−// Format implements the chroma.Formatter interface.
23−func (c chromaFormatter) Format(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
24− for token := it(); token != chroma.EOF; token = it() {
25− value := strings.TrimRight(token.Value, "\n")
26− value = ansiext.Escape(value)
27−
28− entry := style.Get(token.Type)
29− if entry.IsZero() {
30− if _, err := fmt.Fprint(w, value); err != nil {
31− return err
32− }
33− continue
34− }
35−
36− s := lipgloss.NewStyle().
37− Background(c.bgColor)
38−
39− if entry.Bold == chroma.Yes {
40− s = s.Bold(true)
41− }
42− if entry.Underline == chroma.Yes {
43− s = s.Underline(true)
44− }
45− if entry.Italic == chroma.Yes {
46− s = s.Italic(true)
47− }
48− if entry.Colour.IsSet() {
49− s = s.Foreground(lipgloss.Color(entry.Colour.String()))
50− }
51−
52− if _, err := fmt.Fprint(w, s.Render(value)); err != nil {
53− return err
54− }
55− }
56− return nil
57−}
internal/ui/diffview/diffview.go+9−3
@@ -10,6 +10,8 @@ import (
1010 "github.com/alecthomas/chroma/v2"
1111 "github.com/alecthomas/chroma/v2/lexers"
1212 "github.com/aymanbagabas/go-udiff"
13+ "github.com/charmbracelet/crush/internal/ansiext"
14+ "github.com/charmbracelet/crush/internal/ui/xchroma"
1315 "github.com/charmbracelet/x/ansi"
1416 "github.com/zeebo/xxh3"
1517)
@@ -768,7 +770,11 @@ func (dv *DiffView) getChromaLexer() chroma.Lexer {
768770}
769771
770772func (dv *DiffView) getChromaFormatter(bgColor color.Color) chroma.Formatter {
771− return chromaFormatter{
772− bgColor: bgColor,
773− }
773+ return xchroma.Formatter(bgColor, processChromaValue)
774+}
775+
776+func processChromaValue(value string) string {
777+ value = strings.TrimRight(value, "\n")
778+ value = ansiext.Escape(value)
779+ return value
774780}
internal/ui/xchroma/chroma.gonew file+52
@@ -0,0 +1,52 @@
1+package xchroma
2+
3+import (
4+ "fmt"
5+ "image/color"
6+ "io"
7+
8+ "charm.land/lipgloss/v2"
9+ "github.com/alecthomas/chroma/v2"
10+)
11+
12+// Formatter is func that returns a custom formatter for Chroma that uses
13+// Lip Gloss for foreground styling, while keeping a forced background color.
14+func Formatter(bgColor color.Color, processValue func(string) string) chroma.Formatter {
15+ return chroma.FormatterFunc(func(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
16+ for token := it(); token != chroma.EOF; token = it() {
17+ value := token.Value
18+ if processValue != nil {
19+ value = processValue(value)
20+ }
21+
22+ entry := style.Get(token.Type)
23+ if entry.IsZero() {
24+ if _, err := fmt.Fprint(w, value); err != nil {
25+ return err
26+ }
27+ continue
28+ }
29+
30+ s := lipgloss.NewStyle().
31+ Background(bgColor)
32+
33+ if entry.Bold == chroma.Yes {
34+ s = s.Bold(true)
35+ }
36+ if entry.Underline == chroma.Yes {
37+ s = s.Underline(true)
38+ }
39+ if entry.Italic == chroma.Yes {
40+ s = s.Italic(true)
41+ }
42+ if entry.Colour.IsSet() {
43+ s = s.Foreground(lipgloss.Color(entry.Colour.String()))
44+ }
45+
46+ if _, err := fmt.Fprint(w, s.Render(value)); err != nil {
47+ return err
48+ }
49+ }
50+ return nil
51+ })
52+}