1package ansi
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7
8 "github.com/charmbracelet/lipgloss/v2"
9 "github.com/charmbracelet/lipgloss/v2/table"
10 astext "github.com/yuin/goldmark/extension/ast"
11)
12
13// A TableElement is used to render tables.
14type TableElement struct {
15 lipgloss *table.Table
16 table *astext.Table
17 header []string
18 row []string
19 source []byte
20
21 tableImages []tableLink
22 tableLinks []tableLink
23}
24
25// A TableRowElement is used to render a single row in a table.
26type TableRowElement struct{}
27
28// A TableHeadElement is used to render a table's head element.
29type TableHeadElement struct{}
30
31// A TableCellElement is used to render a single cell in a row.
32type TableCellElement struct {
33 Children []ElementRenderer
34 Head bool
35}
36
37// Render renders a TableElement.
38func (e *TableElement) Render(w io.Writer, ctx RenderContext) error {
39 bs := ctx.blockStack
40
41 var indentation uint
42 var margin uint
43 rules := ctx.options.Styles.Table
44 if rules.Indent != nil {
45 indentation = *rules.Indent
46 }
47 if rules.Margin != nil {
48 margin = *rules.Margin
49 }
50
51 iw := NewIndentWriter(w, int(indentation+margin), func(_ io.Writer) { //nolint:gosec
52 _, _ = renderText(w, bs.Current().Style.StylePrimitive, " ")
53 })
54
55 style := bs.With(rules.StylePrimitive)
56
57 _, _ = renderText(iw, bs.Current().Style.StylePrimitive, rules.BlockPrefix)
58 _, _ = renderText(iw, style, rules.Prefix)
59 width := int(ctx.blockStack.Width(ctx)) //nolint: gosec
60
61 wrap := true
62 if ctx.options.TableWrap != nil {
63 wrap = *ctx.options.TableWrap
64 }
65 ctx.table.lipgloss = table.New().Width(width).Wrap(wrap)
66
67 if err := e.collectLinksAndImages(ctx); err != nil {
68 return err
69 }
70
71 return nil
72}
73
74func (e *TableElement) setStyles(ctx RenderContext) {
75 docRules := ctx.options.Styles.Document
76 if docRules.BackgroundColor != nil {
77 baseStyle := lipgloss.NewStyle().Background(lipgloss.Color(*docRules.BackgroundColor))
78 ctx.table.lipgloss.BaseStyle(baseStyle)
79 }
80
81 ctx.table.lipgloss = ctx.table.lipgloss.StyleFunc(func(_, col int) lipgloss.Style {
82 st := lipgloss.NewStyle().Inline(false)
83 // Default Styles
84 st = st.Margin(0, 1)
85
86 // Override with custom styles
87 if m := ctx.options.Styles.Table.Margin; m != nil {
88 st = st.Padding(0, int(*m)) //nolint: gosec
89 }
90 switch e.table.Alignments[col] {
91 case astext.AlignLeft:
92 st = st.Align(lipgloss.Left).PaddingRight(0)
93 case astext.AlignCenter:
94 st = st.Align(lipgloss.Center)
95 case astext.AlignRight:
96 st = st.Align(lipgloss.Right).PaddingLeft(0)
97 case astext.AlignNone:
98 // do nothing
99 }
100
101 return st
102 })
103}
104
105func (e *TableElement) setBorders(ctx RenderContext) {
106 rules := ctx.options.Styles.Table
107 border := lipgloss.NormalBorder()
108
109 if rules.RowSeparator != nil && rules.ColumnSeparator != nil {
110 border = lipgloss.Border{
111 Top: *rules.RowSeparator,
112 Bottom: *rules.RowSeparator,
113 Left: *rules.ColumnSeparator,
114 Right: *rules.ColumnSeparator,
115 Middle: *rules.CenterSeparator,
116 }
117 }
118 ctx.table.lipgloss.Border(border)
119 ctx.table.lipgloss.BorderTop(false)
120 ctx.table.lipgloss.BorderLeft(false)
121 ctx.table.lipgloss.BorderRight(false)
122 ctx.table.lipgloss.BorderBottom(false)
123}
124
125// Finish finishes rendering a TableElement.
126func (e *TableElement) Finish(_ io.Writer, ctx RenderContext) error {
127 defer func() {
128 ctx.table.lipgloss = nil
129 ctx.table.tableImages = nil
130 ctx.table.tableLinks = nil
131 }()
132
133 rules := ctx.options.Styles.Table
134
135 e.setStyles(ctx)
136 e.setBorders(ctx)
137
138 ow := ctx.blockStack.Current().Block
139 if _, err := ow.WriteString(ctx.table.lipgloss.String()); err != nil {
140 return fmt.Errorf("glamour: error writing to buffer: %w", err)
141 }
142
143 _, _ = renderText(ow, ctx.blockStack.With(rules.StylePrimitive), rules.Suffix)
144 _, _ = renderText(ow, ctx.blockStack.Current().Style.StylePrimitive, rules.BlockSuffix)
145
146 e.printTableLinks(ctx)
147
148 ctx.table.lipgloss = nil
149 return nil
150}
151
152// Finish finishes rendering a TableRowElement.
153func (e *TableRowElement) Finish(_ io.Writer, ctx RenderContext) error {
154 if ctx.table.lipgloss == nil {
155 return nil
156 }
157
158 ctx.table.lipgloss.Row(ctx.table.row...)
159 ctx.table.row = []string{}
160 return nil
161}
162
163// Finish finishes rendering a TableHeadElement.
164func (e *TableHeadElement) Finish(_ io.Writer, ctx RenderContext) error {
165 if ctx.table.lipgloss == nil {
166 return nil
167 }
168
169 ctx.table.lipgloss.Headers(ctx.table.header...)
170 ctx.table.header = []string{}
171 return nil
172}
173
174// Render renders a TableCellElement.
175func (e *TableCellElement) Render(_ io.Writer, ctx RenderContext) error {
176 var b bytes.Buffer
177 style := ctx.options.Styles.Table.StylePrimitive
178 for _, child := range e.Children {
179 if r, ok := child.(StyleOverriderElementRenderer); ok {
180 if err := r.StyleOverrideRender(&b, ctx, style); err != nil {
181 return fmt.Errorf("glamour: error rendering with style: %w", err)
182 }
183 } else {
184 var bb bytes.Buffer
185 if err := child.Render(&bb, ctx); err != nil {
186 return fmt.Errorf("glamour: error rendering: %w", err)
187 }
188 el := &BaseElement{
189 Token: bb.String(),
190 Style: style,
191 }
192 if err := el.Render(&b, ctx); err != nil {
193 return err
194 }
195 }
196 }
197
198 if e.Head {
199 ctx.table.header = append(ctx.table.header, b.String())
200 } else {
201 ctx.table.row = append(ctx.table.row, b.String())
202 }
203
204 return nil
205}