okami.mom/glamour
git clone https://git.nkp.pet/okami.mom/glamour
c293649
main
1package ansi 2 3import ( 4"bytes" 5"fmt" 6"io" 7"net/url" 8"slices" 9"strconv" 10"strings" 11 12"charm.land/glamour/v2/internal/autolink" 13 xansi"github.com/charmbracelet/x/ansi" 14"github.com/charmbracelet/x/exp/slice" 15"github.com/yuin/goldmark/ast" 16 astext"github.com/yuin/goldmark/extension/ast" 17) 18 19type tableLink struct { 20href string 21title string 22content string 23linkType linkType 24} 25 26type linkType int 27 28const ( 29_ linkType = iota 30linkTypeAuto 31linkTypeImage 32linkTypeRegular 33) 34 35func (e * TableElement )printTableLinks (ctx RenderContext ) { 36if ! e .shouldPrintTableLinks (ctx ) { 37return 38 } 39 40w := ctx .blockStack .Current ().Block 41termWidth := int (ctx .blockStack .Width (ctx )) 42 43renderLinkText := func (link tableLink ,position ,padding int )string { 44token := strings .Repeat (" " ,padding ) 45style := ctx .options .Styles .LinkText 46 47switch link .linkType { 48case linkTypeAuto ,linkTypeRegular : 49token += fmt .Sprintf ("[%d]: %s" ,position ,link .content ) 50case linkTypeImage : 51token += link .content 52style = ctx .options .Styles .ImageText 53style .Prefix = fmt .Sprintf ("[%d]: %s" ,position ,style .Prefix ) 54 } 55 56var b bytes.Buffer 57el := & BaseElement {Token :token ,Style :style } 58_ = el .Render (io .MultiWriter (w ,& b ),ctx ) 59return b .String () 60 } 61 62renderLinkHref := func (link tableLink ,linkText string ) { 63hyperlink ,resetHyperlink ,_ := makeHyperlink (link .href ) 64 65style := ctx .options .Styles .Link 66if link .linkType == linkTypeImage { 67style = ctx .options .Styles .Image 68 } 69 70linkMaxWidth := max (termWidth - xansi .StringWidth (linkText )- 1 ,0 ) 71token := hyperlink + xansi .Truncate (link .href ,linkMaxWidth ,"…" )+ resetHyperlink 72 73el := & BaseElement {Token :token ,Style :style } 74_ = el .Render (w ,ctx ) 75 } 76 77renderString := func (str string ) { 78_ ,_ = renderText (w ,ctx .blockStack .Current ().Style .StylePrimitive ,str ) 79 } 80 81paddingFor := func (total ,position int )int { 82totalSize := len (strconv .Itoa (total )) 83positionSize := len (strconv .Itoa (position )) 84 85return max (totalSize - positionSize ,0 ) 86 } 87 88renderList := func (list []tableLink ) { 89for i ,item := range list { 90position := i + 1 91padding := paddingFor (len (list ),position ) 92 93renderString ("\n" ) 94linkText := renderLinkText (item ,position ,padding ) 95renderString (" " ) 96renderLinkHref (item ,linkText ) 97 } 98 } 99 100if len (ctx .table .tableLinks )> 0 { 101renderString ("\n" ) 102 } 103renderList (ctx .table .tableLinks ) 104 105if len (ctx .table .tableImages )> 0 { 106renderString ("\n" ) 107 } 108renderList (ctx .table .tableImages ) 109} 110 111func (e * TableElement )shouldPrintTableLinks (ctx RenderContext )bool { 112if ctx .options .InlineTableLinks { 113return false 114 } 115if len (ctx .table .tableLinks )== 0 && len (ctx .table .tableImages )== 0 { 116return false 117 } 118return true 119} 120 121func (e * TableElement )collectLinksAndImages (ctx RenderContext )error { 122images := make ([]tableLink ,0 ) 123links := make ([]tableLink ,0 ) 124 125err := ast .Walk (e .table ,func (node ast.Node ,entering bool ) (ast.WalkStatus ,error ) { 126if ! entering { 127return ast .WalkContinue ,nil 128 } 129 130switch n := node .(type ) { 131case * ast.AutoLink : 132uri := string (n .URL (e .source )) 133autoLink := tableLink { 134href :uri , 135content :linkDomain (uri ), 136linkType :linkTypeAuto , 137 } 138if shortned ,ok := autolink .Detect (uri );ok { 139autoLink .content = shortned 140 } 141links = append (links ,autoLink ) 142case * ast.Image : 143content ,err := nodeContent (node ,e .source ) 144if err != nil { 145return ast .WalkStop ,err 146 } 147image := tableLink { 148href :string (n .Destination ), 149title :string (n .Title ), 150content :string (content ), 151linkType :linkTypeImage , 152 } 153if image .content == "" { 154image .content = linkDomain (image .href ) 155 } 156images = append (images ,image ) 157case * ast.Link : 158content ,err := nodeContent (node ,e .source ) 159if err != nil { 160return ast .WalkStop ,err 161 } 162link := tableLink { 163href :string (n .Destination ), 164title :string (n .Title ), 165content :string (content ), 166linkType :linkTypeRegular , 167 } 168links = append (links ,link ) 169 } 170 171return ast .WalkContinue ,nil 172 }) 173if err != nil { 174return fmt .Errorf ("glamour: error collecting links: %w" ,err ) 175 } 176 177ctx .table .tableImages = slice .Uniq (images ) 178ctx .table .tableLinks = slice .Uniq (links ) 179return nil 180} 181 182func isInsideTable (node ast.Node )bool { 183parent := node .Parent () 184for parent != nil { 185switch parent .Kind () { 186case astext .KindTable ,astext .KindTableHeader ,astext .KindTableRow ,astext .KindTableCell : 187return true 188default : 189parent = parent .Parent () 190 } 191 } 192return false 193} 194 195func nodeContent (node ast.Node ,source []byte ) ([]byte ,error ) { 196var builder bytes.Buffer 197 198var traverse func (node ast.Node )error 199traverse = func (node ast.Node )error { 200for n := node .FirstChild ();n != nil ;n = n .NextSibling () { 201switch nn := n .(type ) { 202case * ast.Text : 203if _ ,err := builder .Write (nn .Segment .Value (source ));err != nil { 204return fmt .Errorf ("glamour: error writing text node: %w" ,err ) 205 } 206default : 207if err := traverse (nn );err != nil { 208return err 209 } 210 } 211 } 212return nil 213 } 214if err := traverse (node );err != nil { 215return nil ,err 216 } 217 218return builder .Bytes (),nil 219} 220 221func linkDomain (href string )string { 222if uri ,err := url .Parse (href );err == nil { 223return uri .Hostname () 224 } 225return "link" 226} 227 228func linkWithSuffix (tl tableLink ,list []tableLink )string { 229index := slices .Index (list ,tl ) 230if index == - 1 { 231return tl .content 232 } 233return fmt .Sprintf ("%s[%d]" ,tl .content ,index + 1 ) 234}