okami.mom/glamour
git clone https://git.nkp.pet/okami.mom/glamour
c293649
main
1package ansi 2 3import ( 4"bytes" 5"errors" 6"fmt" 7"io" 8"strings" 9"unicode/utf8" 10 11"charm.land/lipgloss/v2" 12"github.com/charmbracelet/x/ansi" 13) 14 15// MarginWriter is a Writer that applies indentation and padding around 16// whatever you write to it. 17type MarginWriter struct { 18w io.Writer 19iw * IndentWriter 20} 21 22// NewMarginWriter returns a new MarginWriter. 23func NewMarginWriter (ctx RenderContext ,w io.Writer ,rules StyleBlock )* MarginWriter { 24bs := ctx .blockStack 25 26var indentation uint 27var margin uint 28if rules .Indent != nil { 29indentation = * rules .Indent 30 } 31if rules .Margin != nil { 32margin = * rules .Margin 33 } 34 35pw := NewPaddingWriter (w ,int (bs .Width (ctx )),func (_ io.Writer ) { 36_ ,_ = renderText (w ,rules .StylePrimitive ," " ) 37 }) 38 39ic := " " 40if rules .IndentToken != nil { 41ic = * rules .IndentToken 42 } 43iw := NewIndentWriter (pw ,int (indentation + margin ),func (_ io.Writer ) { 44_ ,_ = renderText (w ,bs .Parent ().Style .StylePrimitive ,ic ) 45 }) 46 47return & MarginWriter { 48w :lipgloss .NewWrapWriter (w ), 49iw :iw , 50 } 51} 52 53// Write writes to the margin writer and implements [io.Writer]. 54func (w * MarginWriter )Write (b []byte ) (int ,error ) { 55n ,err := w .iw .Write (b ) 56if err != nil { 57return 0 ,fmt .Errorf ("glamour: error writing bytes: %w" ,err ) 58 } 59return n ,nil 60} 61 62// Close closes the [MarginWriter]. 63func (w * MarginWriter )Close ()error { 64var werr error 65if c ,ok := w .w .(io.WriteCloser );ok { 66werr = c .Close () 67 } 68 69return errors .Join (werr ,w .iw .Close ()) 70} 71 72// PaddingFunc is a function that applies padding around whatever you write to it. 73type PaddingFunc = func (w io.Writer ) 74 75// PaddingWriter is a writer that applies padding around whatever you write to 76// it. 77type PaddingWriter struct { 78Padding int 79PadFunc PaddingFunc 80w * lipgloss.WrapWriter 81cache bytes.Buffer 82} 83 84// NewPaddingWriter returns a new PaddingWriter. 85func NewPaddingWriter (w io.Writer ,padding int ,padFunc PaddingFunc )* PaddingWriter { 86return & PaddingWriter { 87Padding :padding , 88PadFunc :padFunc , 89w :lipgloss .NewWrapWriter (w ), 90 } 91} 92 93// Write writes to the padding writer. 94func (w * PaddingWriter )Write (p []byte ) (int ,error ) { 95// Use UTF-8 aware iteration to properly handle multi-byte characters (e.g., CJK) 96for i := 0 ;i < len (p ); { 97r ,size := utf8 .DecodeRune (p [i :]) 98if r == '\n' {//nolint:nestif 99line := w .cache .String () 100linew := ansi .StringWidth (line ) 101if w .Padding > 0 && linew < w .Padding { 102if w .PadFunc != nil { 103for n := 0 ;n < w .Padding - linew ;n ++ { 104w .PadFunc (w .w ) 105 } 106 }else { 107_ ,err := io .WriteString (w .w ,strings .Repeat (" " ,w .Padding - linew )) 108if err != nil { 109return 0 ,fmt .Errorf ("glamour: error writing padding: %w" ,err ) 110 } 111 } 112 } 113w .cache .Reset () 114 }else { 115// Write complete UTF-8 character bytes to cache 116w .cache .Write (p [i :i + size ]) 117 } 118 119// Write complete UTF-8 character bytes to output 120_ ,err := w .w .Write (p [i :i + size ]) 121if err != nil { 122return 0 ,fmt .Errorf ("glamour: error writing bytes: %w" ,err ) 123 } 124i += size 125 } 126 127return len (p ),nil 128} 129 130// Close closes the [PaddingWriter]. 131func (w * PaddingWriter )Close ()error { 132return w .w .Close ()//nolint:wrapcheck 133} 134 135// IndentFunc is a function that applies indentation around whatever you write to 136// it. 137type IndentFunc = func (w io.Writer ) 138 139// IndentWriter is a writer that applies indentation around whatever you write to 140// it. 141type IndentWriter struct { 142Indent int 143IndentFunc PaddingFunc 144w io.Writer 145pw * lipgloss.WrapWriter 146skipIndent bool 147} 148 149// NewIndentWriter returns a new IndentWriter. 150func NewIndentWriter (w io.Writer ,indent int ,indentFunc IndentFunc )* IndentWriter { 151return & IndentWriter { 152Indent :indent , 153IndentFunc :indentFunc , 154pw :lipgloss .NewWrapWriter (w ), 155w :w , 156 } 157} 158 159func (w * IndentWriter )resetPen () { 160style := w .pw .Style () 161link := w .pw .Link () 162if ! style .IsZero () { 163_ ,_ = io .WriteString (w .w ,ansi .ResetStyle ) 164 } 165if ! link .IsZero () { 166_ ,_ = io .WriteString (w .w ,ansi .ResetHyperlink ()) 167 } 168} 169 170func (w * IndentWriter )restorePen () { 171style := w .pw .Style () 172link := w .pw .Link () 173if ! style .IsZero () { 174_ ,_ = io .WriteString (w .w ,style .String ()) 175 } 176if ! link .IsZero () { 177_ ,_ = io .WriteString (w .w ,ansi .SetHyperlink (link .URL ,link .Params )) 178 } 179} 180 181// Write writes to the indentation writer. 182func (w * IndentWriter )Write (p []byte ) (int ,error ) { 183// Use UTF-8 aware iteration to properly handle multi-byte characters (e.g., CJK) 184for i := 0 ;i < len (p ); { 185r ,size := utf8 .DecodeRune (p [i :]) 186if ! w .skipIndent { 187w .resetPen () 188if w .IndentFunc != nil { 189for j := 0 ;j < w .Indent ;j ++ { 190w .IndentFunc (w .pw ) 191 } 192 }else { 193_ ,err := io .WriteString (w .pw ,strings .Repeat (" " ,w .Indent )) 194if err != nil { 195return 0 ,fmt .Errorf ("glamour: error writing indentation: %w" ,err ) 196 } 197 } 198 199w .skipIndent = true 200w .restorePen () 201 } 202 203if r == '\n' { 204w .skipIndent = false 205 } 206 207// Write complete UTF-8 character bytes to output 208_ ,err := w .pw .Write (p [i :i + size ]) 209if err != nil { 210return 0 ,fmt .Errorf ("glamour: error writing bytes: %w" ,err ) 211 } 212i += size 213 } 214 215return len (p ),nil 216} 217 218// Close closes the [IndentWriter]. 219func (w * IndentWriter )Close ()error { 220// Close the wrap writer (w.pw) before the downstream writer (w.w). w.pw 221// wraps w.w, so its Close flushes a trailing style/link reset back through 222// w.w. Closing w.w first would return its parser to the pool and nil it 223// out, turning that flush into a write on a closed writer. 224werr := w .pw .Close () 225 226if c ,ok := w .w .(io.WriteCloser );ok { 227werr = errors .Join (werr ,c .Close ()) 228 } 229 230return werr 231}