fix: use same chroma formatter as diffview for markdown (#2656)

Andrey Nering created

Change summary

internal/ui/common/markdown.go   | 15 ++++++++
internal/ui/diffview/chroma.go   | 57 ----------------------------------
internal/ui/diffview/diffview.go | 12 +++++-
internal/ui/xchroma/chroma.go    | 52 +++++++++++++++++++++++++++++++
4 files changed, 76 insertions(+), 60 deletions(-)

Detailed changes

internal/ui/common/markdown.go 🔗

@@ -1,16 +1,30 @@
 package common
 
 import (
+	"image/color"
+
 	"charm.land/glamour/v2"
+	"github.com/alecthomas/chroma/v2/formatters"
 	"github.com/charmbracelet/crush/internal/ui/styles"
+	"github.com/charmbracelet/crush/internal/ui/xchroma"
 )
 
+const formatterName = "crush"
+
+func init() {
+	// NOTE: Glamour does not offer us an option to pass the formatter
+	// implementation directly. We need to register and use by name.
+	var zero color.Color
+	formatters.Register(formatterName, xchroma.Formatter(zero, nil))
+}
+
 // MarkdownRenderer returns a glamour [glamour.TermRenderer] configured with
 // the given styles and width.
 func MarkdownRenderer(sty *styles.Styles, width int) *glamour.TermRenderer {
 	r, _ := glamour.NewTermRenderer(
 		glamour.WithStyles(sty.Markdown),
 		glamour.WithWordWrap(width),
+		glamour.WithChromaFormatter(formatterName),
 	)
 	return r
 }
@@ -21,6 +35,7 @@ func PlainMarkdownRenderer(sty *styles.Styles, width int) *glamour.TermRenderer
 	r, _ := glamour.NewTermRenderer(
 		glamour.WithStyles(sty.PlainMarkdown),
 		glamour.WithWordWrap(width),
+		glamour.WithChromaFormatter(formatterName),
 	)
 	return r
 }

internal/ui/diffview/chroma.go 🔗

@@ -1,57 +0,0 @@
-package diffview
-
-import (
-	"fmt"
-	"image/color"
-	"io"
-	"strings"
-
-	"charm.land/lipgloss/v2"
-	"github.com/alecthomas/chroma/v2"
-	"github.com/charmbracelet/crush/internal/ansiext"
-)
-
-var _ chroma.Formatter = chromaFormatter{}
-
-// chromaFormatter is a custom formatter for Chroma that uses Lip Gloss for
-// foreground styling, while keeping a forced background color.
-type chromaFormatter struct {
-	bgColor color.Color
-}
-
-// Format implements the chroma.Formatter interface.
-func (c chromaFormatter) Format(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
-	for token := it(); token != chroma.EOF; token = it() {
-		value := strings.TrimRight(token.Value, "\n")
-		value = ansiext.Escape(value)
-
-		entry := style.Get(token.Type)
-		if entry.IsZero() {
-			if _, err := fmt.Fprint(w, value); err != nil {
-				return err
-			}
-			continue
-		}
-
-		s := lipgloss.NewStyle().
-			Background(c.bgColor)
-
-		if entry.Bold == chroma.Yes {
-			s = s.Bold(true)
-		}
-		if entry.Underline == chroma.Yes {
-			s = s.Underline(true)
-		}
-		if entry.Italic == chroma.Yes {
-			s = s.Italic(true)
-		}
-		if entry.Colour.IsSet() {
-			s = s.Foreground(lipgloss.Color(entry.Colour.String()))
-		}
-
-		if _, err := fmt.Fprint(w, s.Render(value)); err != nil {
-			return err
-		}
-	}
-	return nil
-}

internal/ui/diffview/diffview.go 🔗

@@ -10,6 +10,8 @@ import (
 	"github.com/alecthomas/chroma/v2"
 	"github.com/alecthomas/chroma/v2/lexers"
 	"github.com/aymanbagabas/go-udiff"
+	"github.com/charmbracelet/crush/internal/ansiext"
+	"github.com/charmbracelet/crush/internal/ui/xchroma"
 	"github.com/charmbracelet/x/ansi"
 	"github.com/zeebo/xxh3"
 )
@@ -768,7 +770,11 @@ func (dv *DiffView) getChromaLexer() chroma.Lexer {
 }
 
 func (dv *DiffView) getChromaFormatter(bgColor color.Color) chroma.Formatter {
-	return chromaFormatter{
-		bgColor: bgColor,
-	}
+	return xchroma.Formatter(bgColor, processChromaValue)
+}
+
+func processChromaValue(value string) string {
+	value = strings.TrimRight(value, "\n")
+	value = ansiext.Escape(value)
+	return value
 }

internal/ui/xchroma/chroma.go 🔗

@@ -0,0 +1,52 @@
+package xchroma
+
+import (
+	"fmt"
+	"image/color"
+	"io"
+
+	"charm.land/lipgloss/v2"
+	"github.com/alecthomas/chroma/v2"
+)
+
+// Formatter is func that returns a custom formatter for Chroma that uses
+// Lip Gloss for foreground styling, while keeping a forced background color.
+func Formatter(bgColor color.Color, processValue func(string) string) chroma.Formatter {
+	return chroma.FormatterFunc(func(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
+		for token := it(); token != chroma.EOF; token = it() {
+			value := token.Value
+			if processValue != nil {
+				value = processValue(value)
+			}
+
+			entry := style.Get(token.Type)
+			if entry.IsZero() {
+				if _, err := fmt.Fprint(w, value); err != nil {
+					return err
+				}
+				continue
+			}
+
+			s := lipgloss.NewStyle().
+				Background(bgColor)
+
+			if entry.Bold == chroma.Yes {
+				s = s.Bold(true)
+			}
+			if entry.Underline == chroma.Yes {
+				s = s.Underline(true)
+			}
+			if entry.Italic == chroma.Yes {
+				s = s.Italic(true)
+			}
+			if entry.Colour.IsSet() {
+				s = s.Foreground(lipgloss.Color(entry.Colour.String()))
+			}
+
+			if _, err := fmt.Fprint(w, s.Render(value)); err != nil {
+				return err
+			}
+		}
+		return nil
+	})
+}