Something went wrong. Try again.
small gleam coding and persistent agent daemon with a detachable cli and a canvas ui forked from okami.mom/albedo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519package uv
import ( "image/color" "strings"
"github.com/charmbracelet/colorprofile" "github.com/charmbracelet/x/ansi")
// EmptyCell is a cell with a single space, width of 1, and no style or link.var EmptyCell = Cell{Content: " ", Width: 1}
// Cell represents a single cell in the terminal screen.//// A screen is a cell per column and row, so what a cell costs is what a screen// costs. Style and Link are therefore shared records rather than values: every// cell that has the same style points at the same one, and a nil pointer is// the zero style or link, which is what most cells have.type Cell struct { // Content is the [Cell]'s content, which consists of a single grapheme // cluster. Most of the time, this will be a single rune as well, but it // can also be a combination of runes that form a grapheme cluster. Content string
// The style of the cell, shared with every cell that has the same one. A // nil style means no style. Zero value prints a reset sequence. Style *Style
// Link is the hyperlink of the cell, shared like [Cell.Style]. Link *Link
// Width is the mono-spaced width of the grapheme cluster. Width int}
// StyleRef returns the style record for a cell: nil for the zero style, so a// cell with no style stays the zero [Cell], and a fresh record otherwise.// Cells that have the same style can share one record.func StyleRef(style Style) *Style { if style.IsZero() { return nil } return &style}
// LinkRef returns the link record for a cell, like [StyleRef].func LinkRef(link Link) *Link { if link.IsZero() { return nil } return &link}
// styleValue is the style a cell holds, with a nil style as the zero one.func styleValue(style *Style) Style { if style == nil { return Style{} } return *style}
// linkValue is the link a cell holds, with a nil link as the zero one.func linkValue(link *Link) Link { if link == nil { return Link{} } return *link}
// NewCell creates a new cell from the given string grapheme. It will only use// the first grapheme in the string and ignore the rest. The width of the cell// is determined using the given width method.func NewCell(method WidthMethod, gr string) *Cell { if len(gr) == 0 { return &Cell{} } if gr == " " { return EmptyCell.Clone() } return &Cell{ Content: gr, Width: method.StringWidth(gr), }}
// String returns the string content of the cell excluding any styles, links,// and escape sequences.func (c *Cell) String() string { return c.Content}
// Equal returns whether the cell is equal to the other cell.func (c *Cell) Equal(o *Cell) bool { return o != nil && c.Width == o.Width && c.Content == o.Content && c.Style.Equal(o.Style) && c.Link.Equal(o.Link)}
// IsZero returns whether the cell is an empty cell.func (c *Cell) IsZero() bool { return c.Width == 0 && c.Content == "" && c.Style.IsZero() && c.Link.IsZero()}
// isWidePlaceholder reports whether the cell is the continuation column of a// wide cell, marked with a zero display width.func (c *Cell) isWidePlaceholder() bool { return c != nil && c.Width == 0}
// Clone returns a copy of the cell.func (c *Cell) Clone() (n *Cell) { n = new(Cell) *n = *c return}
// Empty makes the cell an empty cell by setting its content to a single space// and width to 1.func (c *Cell) Empty() { c.Content = " " c.Width = 1}
// NewLink creates a new hyperlink with the given URL and parameters.func NewLink(url string, params ...string) Link { return Link{ URL: url, Params: strings.Join(params, ":"), }}
// Link represents a hyperlink in the terminal screen.type Link struct { URL string Params string}
// String returns a string representation of the hyperlink.func (h *Link) String() string { if h == nil { return "" } return h.URL}
// Equal returns whether the hyperlink is equal to the other hyperlink. A nil// link is the zero link.func (h *Link) Equal(o *Link) bool { if h == o { return true } if h == nil || o == nil { return h.IsZero() && o.IsZero() } return *h == *o}
// IsZero returns whether the hyperlink is empty. A nil link is empty.func (h *Link) IsZero() bool { return h == nil || *h == Link{}}
// These are the available text attributes that can be combined to create// different styles.const ( AttrBold = 1 << iota AttrFaint AttrItalic AttrBlink AttrRapidBlink // Not widely supported AttrReverse AttrConceal AttrStrikethrough
AttrReset = 0)
// AttrSlowBlink is an alias for AttrBlink.//// Deprecated: Use [AttrBlink] instead.const ( AttrSlowBlink = AttrBlink)
// Underline is the style of underline to use for text.type Underline = ansi.Underline
// UnderlineStyle is the style of underline to use for text.//// Deprecated: Use [Underline] instead.type UnderlineStyle = ansi.Underline
// These are the available underline styles.const ( UnderlineNone = ansi.UnderlineNone UnderlineSingle = ansi.UnderlineSingle UnderlineDouble = ansi.UnderlineDouble UnderlineCurly = ansi.UnderlineCurly UnderlineDotted = ansi.UnderlineDotted UnderlineDashed = ansi.UnderlineDashed)
// These are the available underline styles.//// Deprecated: Use the constants from [Underline] instead.const ( UnderlineStyleNone = ansi.UnderlineNone UnderlineStyleSingle = ansi.UnderlineSingle UnderlineStyleDouble = ansi.UnderlineDouble UnderlineStyleCurly = ansi.UnderlineCurly UnderlineStyleDotted = ansi.UnderlineDotted UnderlineStyleDashed = ansi.UnderlineDashed)
// Style represents the style of a cell.type Style struct { Fg color.Color Bg color.Color UnderlineColor color.Color Underline Underline Attrs uint8}
// Equal returns true if the style is equal to the other style. A nil style is// the zero style.func (s *Style) Equal(o *Style) bool { if s == nil || o == nil { return s.IsZero() && o.IsZero() } return s.Attrs == o.Attrs && s.Underline == o.Underline && colorEqual(s.Fg, o.Fg) && colorEqual(s.Bg, o.Bg) && colorEqual(s.UnderlineColor, o.UnderlineColor)}
// Styled wraps the given string with the style's ANSI sequences and resets.func (s *Style) Styled(str string) string { if s.IsZero() { return str } return s.String() + str + ansi.ResetStyle}
// String returns the ANSI SGR sequence for the style.func (s *Style) String() string { if s.IsZero() { return ansi.ResetStyle }
var b ansi.Style
if s.Attrs != 0 { //nolint:nestif if s.Attrs&AttrBold != 0 { b = b.Bold() } if s.Attrs&AttrFaint != 0 { b = b.Faint() } if s.Attrs&AttrItalic != 0 { b = b.Italic(true) } if s.Attrs&AttrBlink != 0 { b = b.Blink(true) } if s.Attrs&AttrRapidBlink != 0 { b = b.RapidBlink(true) } if s.Attrs&AttrReverse != 0 { b = b.Reverse(true) } if s.Attrs&AttrConceal != 0 { b = b.Conceal(true) } if s.Attrs&AttrStrikethrough != 0 { b = b.Strikethrough(true) } } if s.Underline != UnderlineStyleNone { switch s.Underline { case UnderlineStyleSingle: b = b.Underline(true) case UnderlineStyleDouble: b = b.UnderlineStyle(UnderlineStyleDouble) case UnderlineStyleCurly: b = b.UnderlineStyle(UnderlineStyleCurly) case UnderlineStyleDotted: b = b.UnderlineStyle(UnderlineStyleDotted) case UnderlineStyleDashed: b = b.UnderlineStyle(UnderlineStyleDashed) } } if s.Fg != nil { b = b.ForegroundColor(s.Fg) } if s.Bg != nil { b = b.BackgroundColor(s.Bg) } if s.UnderlineColor != nil { b = b.UnderlineColor(s.UnderlineColor) }
return b.String()}
// Diff returns the ANSI sequence that sets the style as a diff from// another style.func (s *Style) Diff(from *Style) string { return StyleDiff(from, s)}
// StyleDiff returns the SGR ANSI sequence necessary to transition from the// "from" style to the "to" style.func StyleDiff(from, to *Style) string { if from == nil && to == nil { return "" } if from != nil && to != nil && from.Equal(to) { return "" } if from == nil { return to.String() } if to == nil || to.IsZero() { // Resetting all styles is cheaper than calculating diffs. // "\x1b[m" is 3 bytes vs potentially much longer sequences. return ansi.ResetStyle }
// TODO: Optimize further by checking if a full reset is cheaper than // calculating diffs. Often, it might be cheaper to reset everything and // then set the desired styles rather than calculating diffs. Also more // compatible with terminals that have buggy SGR implementations.
var b ansi.Style
if !colorEqual(from.Fg, to.Fg) { b = b.ForegroundColor(to.Fg) }
if !colorEqual(from.Bg, to.Bg) { b = b.BackgroundColor(to.Bg) }
if !colorEqual(from.UnderlineColor, to.UnderlineColor) { // TODO: Investigate this. For backward compatibility, we might want to // set this at the end instead. Because on terminals that don't support // underline color, this might mess up some other attributes if set in // the middle. b = b.UnderlineColor(to.UnderlineColor) }
fromBold := from.Attrs&AttrBold != 0 fromFaint := from.Attrs&AttrFaint != 0 fromItalic := from.Attrs&AttrItalic != 0 fromUnderline := from.Underline != UnderlineStyleNone fromBlink := from.Attrs&AttrBlink != 0 fromRapidBlink := from.Attrs&AttrRapidBlink != 0 fromReverse := from.Attrs&AttrReverse != 0 fromConceal := from.Attrs&AttrConceal != 0 fromStrikethrough := from.Attrs&AttrStrikethrough != 0 toBold := to.Attrs&AttrBold != 0 toFaint := to.Attrs&AttrFaint != 0 toItalic := to.Attrs&AttrItalic != 0 toUnderline := to.Underline != UnderlineStyleNone toBlink := to.Attrs&AttrBlink != 0 toRapidBlink := to.Attrs&AttrRapidBlink != 0 toReverse := to.Attrs&AttrReverse != 0 toConceal := to.Attrs&AttrConceal != 0 toStrikethrough := to.Attrs&AttrStrikethrough != 0
// We perform the resets first since they are single attributes and // shouldn't interfere with others being set.
boldChanged := fromBold != toBold faintChanged := fromFaint != toFaint if boldChanged || faintChanged { if fromBold && !toBold || fromFaint && !toFaint { b = b.Normal() boldChanged = true faintChanged = true } }
italicChanged := fromItalic != toItalic if italicChanged && !toItalic { b = b.Italic(false) }
underlineChanged := fromUnderline != toUnderline || from.Underline != to.Underline if underlineChanged && !toUnderline { b = b.Underline(false) }
blinkChanged := fromBlink != toBlink rapidBlinkChanged := fromRapidBlink != toRapidBlink if blinkChanged || rapidBlinkChanged { if fromBlink && !toBlink || fromRapidBlink && !toRapidBlink { b = b.Blink(false) blinkChanged = true rapidBlinkChanged = true } }
reverseChanged := fromReverse != toReverse if reverseChanged && !toReverse { b = b.Reverse(false) }
concealChanged := fromConceal != toConceal if concealChanged && !toConceal { b = b.Conceal(false) }
strikethroughChanged := fromStrikethrough != toStrikethrough if strikethroughChanged && !toStrikethrough { b = b.Strikethrough(false) }
if boldChanged && toBold { b = b.Bold() }
if faintChanged && toFaint { b = b.Faint() }
if italicChanged && toItalic { b = b.Italic(true) }
if underlineChanged && toUnderline && to.Underline == UnderlineStyleSingle { // We only handle single underline here since others require more // specific handling at the end. b = b.Underline(true) }
if blinkChanged && toBlink { b = b.Blink(true) }
if rapidBlinkChanged && toRapidBlink { b = b.RapidBlink(true) }
if reverseChanged && toReverse { b = b.Reverse(true) }
if concealChanged && toConceal { b = b.Conceal(true) }
if strikethroughChanged && toStrikethrough { b = b.Strikethrough(true) }
// Handle special underline styles. if underlineChanged && toUnderline && to.Underline > UnderlineStyleSingle { b = b.UnderlineStyle(to.Underline) }
return b.String()}
func colorEqual(c, o color.Color) bool { if c == nil && o == nil { return true } if c == nil || o == nil { return false } cr, cg, cb, ca := c.RGBA() or, og, ob, oa := o.RGBA() return cr == or && cg == og && cb == ob && ca == oa}
// IsZero returns true if the style is empty. A nil style is empty.func (s *Style) IsZero() bool { return s == nil || *s == Style{}}
// ConvertStyle converts a style to respect the given color profile.func ConvertStyle(s Style, p colorprofile.Profile) Style { switch p { case colorprofile.TrueColor: return s case colorprofile.ANSI, colorprofile.ANSI256: case colorprofile.Ascii: s.Fg = nil s.Bg = nil s.UnderlineColor = nil case colorprofile.NoTTY: return Style{} }
if s.Fg != nil { s.Fg = p.Convert(s.Fg) } if s.Bg != nil { s.Bg = p.Convert(s.Bg) } if s.UnderlineColor != nil { s.UnderlineColor = p.Convert(s.UnderlineColor) } return s}
// ConvertLink converts a hyperlink to respect the given color profile.func ConvertLink(h Link, p colorprofile.Profile) Link { if p == colorprofile.NoTTY { return Link{} } return h}