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 }