github.com/charmbracelet/glamour@v0.7.0/ansi/codeblock.go (about)

     1  package ansi
     2  
     3  import (
     4  	"io"
     5  	"sync"
     6  
     7  	"github.com/alecthomas/chroma/v2"
     8  	"github.com/alecthomas/chroma/v2/quick"
     9  	"github.com/alecthomas/chroma/v2/styles"
    10  	"github.com/muesli/reflow/indent"
    11  	"github.com/muesli/termenv"
    12  )
    13  
    14  const (
    15  	// The chroma style theme name used for rendering.
    16  	chromaStyleTheme = "charm"
    17  )
    18  
    19  // mutex for synchronizing access to the chroma style registry.
    20  // Related https://github.com/alecthomas/chroma/pull/650
    21  var mutex = sync.Mutex{}
    22  
    23  // A CodeBlockElement is used to render code blocks.
    24  type CodeBlockElement struct {
    25  	Code     string
    26  	Language string
    27  }
    28  
    29  func chromaStyle(style StylePrimitive) string {
    30  	var s string
    31  
    32  	if style.Color != nil {
    33  		s = *style.Color
    34  	}
    35  	if style.BackgroundColor != nil {
    36  		if s != "" {
    37  			s += " "
    38  		}
    39  		s += "bg:" + *style.BackgroundColor
    40  	}
    41  	if style.Italic != nil && *style.Italic {
    42  		if s != "" {
    43  			s += " "
    44  		}
    45  		s += "italic"
    46  	}
    47  	if style.Bold != nil && *style.Bold {
    48  		if s != "" {
    49  			s += " "
    50  		}
    51  		s += "bold"
    52  	}
    53  	if style.Underline != nil && *style.Underline {
    54  		if s != "" {
    55  			s += " "
    56  		}
    57  		s += "underline"
    58  	}
    59  
    60  	return s
    61  }
    62  
    63  func (e *CodeBlockElement) Render(w io.Writer, ctx RenderContext) error {
    64  	bs := ctx.blockStack
    65  
    66  	var indentation uint
    67  	var margin uint
    68  	rules := ctx.options.Styles.CodeBlock
    69  	if rules.Indent != nil {
    70  		indentation = *rules.Indent
    71  	}
    72  	if rules.Margin != nil {
    73  		margin = *rules.Margin
    74  	}
    75  	theme := rules.Theme
    76  
    77  	if rules.Chroma != nil && ctx.options.ColorProfile != termenv.Ascii {
    78  		theme = chromaStyleTheme
    79  		mutex.Lock()
    80  		// Don't register the style if it's already registered.
    81  		_, ok := styles.Registry[theme]
    82  		if !ok {
    83  			styles.Register(chroma.MustNewStyle(theme,
    84  				chroma.StyleEntries{
    85  					chroma.Text:                chromaStyle(rules.Chroma.Text),
    86  					chroma.Error:               chromaStyle(rules.Chroma.Error),
    87  					chroma.Comment:             chromaStyle(rules.Chroma.Comment),
    88  					chroma.CommentPreproc:      chromaStyle(rules.Chroma.CommentPreproc),
    89  					chroma.Keyword:             chromaStyle(rules.Chroma.Keyword),
    90  					chroma.KeywordReserved:     chromaStyle(rules.Chroma.KeywordReserved),
    91  					chroma.KeywordNamespace:    chromaStyle(rules.Chroma.KeywordNamespace),
    92  					chroma.KeywordType:         chromaStyle(rules.Chroma.KeywordType),
    93  					chroma.Operator:            chromaStyle(rules.Chroma.Operator),
    94  					chroma.Punctuation:         chromaStyle(rules.Chroma.Punctuation),
    95  					chroma.Name:                chromaStyle(rules.Chroma.Name),
    96  					chroma.NameBuiltin:         chromaStyle(rules.Chroma.NameBuiltin),
    97  					chroma.NameTag:             chromaStyle(rules.Chroma.NameTag),
    98  					chroma.NameAttribute:       chromaStyle(rules.Chroma.NameAttribute),
    99  					chroma.NameClass:           chromaStyle(rules.Chroma.NameClass),
   100  					chroma.NameConstant:        chromaStyle(rules.Chroma.NameConstant),
   101  					chroma.NameDecorator:       chromaStyle(rules.Chroma.NameDecorator),
   102  					chroma.NameException:       chromaStyle(rules.Chroma.NameException),
   103  					chroma.NameFunction:        chromaStyle(rules.Chroma.NameFunction),
   104  					chroma.NameOther:           chromaStyle(rules.Chroma.NameOther),
   105  					chroma.Literal:             chromaStyle(rules.Chroma.Literal),
   106  					chroma.LiteralNumber:       chromaStyle(rules.Chroma.LiteralNumber),
   107  					chroma.LiteralDate:         chromaStyle(rules.Chroma.LiteralDate),
   108  					chroma.LiteralString:       chromaStyle(rules.Chroma.LiteralString),
   109  					chroma.LiteralStringEscape: chromaStyle(rules.Chroma.LiteralStringEscape),
   110  					chroma.GenericDeleted:      chromaStyle(rules.Chroma.GenericDeleted),
   111  					chroma.GenericEmph:         chromaStyle(rules.Chroma.GenericEmph),
   112  					chroma.GenericInserted:     chromaStyle(rules.Chroma.GenericInserted),
   113  					chroma.GenericStrong:       chromaStyle(rules.Chroma.GenericStrong),
   114  					chroma.GenericSubheading:   chromaStyle(rules.Chroma.GenericSubheading),
   115  					chroma.Background:          chromaStyle(rules.Chroma.Background),
   116  				}))
   117  		}
   118  		mutex.Unlock()
   119  	}
   120  
   121  	iw := indent.NewWriterPipe(w, indentation+margin, func(wr io.Writer) {
   122  		renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, " ")
   123  	})
   124  
   125  	if len(theme) > 0 {
   126  		renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockPrefix)
   127  		err := quick.Highlight(iw, e.Code, e.Language, "terminal256", theme)
   128  		if err != nil {
   129  			return err
   130  		}
   131  		renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockSuffix)
   132  		return nil
   133  	}
   134  
   135  	// fallback rendering
   136  	el := &BaseElement{
   137  		Token: e.Code,
   138  		Style: rules.StylePrimitive,
   139  	}
   140  
   141  	return el.Render(iw, ctx)
   142  }