package ansi import ( "bytes" "errors" "io" "strconv" "strings" "charm.land/lipgloss/v2" "github.com/charmbracelet/x/ansi" ) // An ItemElement is used to render items inside a list. type ItemElement struct { IsOrdered bool Enumeration uint } // Render renders an ItemElement. func (e *ItemElement) Render(w io.Writer, ctx RenderContext) error { var el *BaseElement if e.IsOrdered { el = &BaseElement{ Style: ctx.options.Styles.Enumeration, Prefix: strconv.FormatInt(int64(e.Enumeration), 10), //nolint:gosec } } else { el = &BaseElement{ Style: ctx.options.Styles.Item, } } return el.Render(w, ctx) } // A ListItemElement renders a list item as a block: its marker leads the // first row and every later row hangs under the text after it, so wrapped // lines, paragraphs, code and nested lists stay inside the item. type ListItemElement struct { Marker ElementRenderer } func (e *ListItemElement) marker(ctx RenderContext) string { var marker bytes.Buffer _ = e.Marker.Render(&marker, ctx) return marker.String() } // Render opens the item's block, indented by the marker's width. func (e *ListItemElement) Render(_ io.Writer, ctx RenderContext) error { width := uint(ansi.StringWidth(e.marker(ctx))) //nolint:gosec bs := ctx.blockStack bs.Push(BlockElement{ Block: &bytes.Buffer{}, Style: cascadeStyle(bs.Current().Style, StyleBlock{Indent: &width}, false), }) return nil } // Finish writes the item: the marker leads its first row and pads the rest. func (e *ListItemElement) Finish(w io.Writer, ctx RenderContext) error { bs := ctx.blockStack marker := e.marker(ctx) lead, pad := marker, strings.Repeat(" ", ansi.StringWidth(marker)) iw := NewIndentWriter(w, 1, func(w io.Writer) { _, _ = io.WriteString(w, lead) lead = pad }) body := lipgloss.Wrap(bs.Current().Block.String(), int(bs.Width(ctx)), " ,.;-+|") _, err := io.WriteString(iw, strings.TrimRight(body, "\n")) bs.Current().Block.Reset() bs.Pop() return errors.Join(err, iw.Close()) }