SIGN IN SIGN UP
charmbracelet / lipgloss UNCLAIMED

Style definitions for nice terminal layouts 👄

package lipgloss
import (
"bytes"
"io"
uv "github.com/charmbracelet/ultraviolet"
"github.com/charmbracelet/x/ansi"
)
// Wrap wraps the given string to the given width, preserving ANSI styles and links.
func Wrap(s string, width int, breakpoints string) string {
var buf bytes.Buffer
s = ansi.Wrap(s, width, breakpoints)
w := NewWrapWriter(&buf)
defer w.Close() //nolint:errcheck
_, _ = io.WriteString(w, s)
return buf.String()
}
// WrapWriter is a writer that writes to a buffer and keeps track of the
// current pen style and link state for the purpose of wrapping with newlines.
//
// When it encounters a newline, it resets the style and link, writes the
// newline, and then reapplies the style and link to the next line.
type WrapWriter struct {
w io.Writer
p *ansi.Parser
style uv.Style
link uv.Link
}
// NewWrapWriter returns a new [WrapWriter].
func NewWrapWriter(w io.Writer) *WrapWriter {
pw := &WrapWriter{w: w}
pw.p = ansi.GetParser()
handleCsi := func(cmd ansi.Cmd, params ansi.Params) {
if cmd == 'm' {
uv.ReadStyle(params, &pw.style)
}
}
handleOsc := func(cmd int, data []byte) {
if cmd == 8 {
uv.ReadLink(data, &pw.link)
}
}
pw.p.SetHandler(ansi.Handler{
HandleCsi: handleCsi,
HandleOsc: handleOsc,
})
return pw
}
// Style returns the current pen style.
func (w *WrapWriter) Style() uv.Style {
return w.style
}
// Link returns the current pen link.
func (w *WrapWriter) Link() uv.Link {
return w.link
}
// Write writes to the buffer.
func (w *WrapWriter) Write(p []byte) (int, error) {
for i := range p {
b := p[i]
w.p.Advance(b)
if b == '\n' {
if !w.style.IsZero() {
_, _ = w.w.Write([]byte(ansi.ResetStyle))
}
if !w.link.IsZero() {
_, _ = w.w.Write([]byte(ansi.ResetHyperlink()))
}
}
_, _ = w.w.Write([]byte{b})
if b == '\n' {
if !w.link.IsZero() {
_, _ = w.w.Write([]byte(ansi.SetHyperlink(w.link.URL, w.link.Params)))
}
if !w.style.IsZero() {
_, _ = w.w.Write([]byte(w.style.String()))
}
}
}
return len(p), nil
}
// Close closes the writer, resets the style and link if necessary, and releases
// its parser. Calling it is performance critical, but forgetting it does not
// cause safety issues or leaks.
func (w *WrapWriter) Close() error {
if !w.style.IsZero() {
_, _ = w.w.Write([]byte(ansi.ResetStyle))
}
if !w.link.IsZero() {
_, _ = w.w.Write([]byte(ansi.ResetHyperlink()))
}
if w.p != nil {
ansi.PutParser(w.p)
w.p = nil
}
return nil
}