123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- package ui
- import (
- "bytes"
- "fmt"
- "math/big"
- "strings"
- "src.elv.sh/pkg/eval/vals"
- )
- // Segment is a string that has some style applied to it.
- type Segment struct {
- Style
- Text string
- }
- // Kind returns "styled-segment".
- func (*Segment) Kind() string { return "ui:text-segment" }
- // Repr returns the representation of this Segment. The string can be used to
- // construct an identical Segment. Unset or default attributes are skipped. If
- // the Segment represents an unstyled string only this string is returned.
- func (s *Segment) Repr(int) string {
- buf := new(bytes.Buffer)
- addIfNotEqual := func(key string, val, cmp any) {
- if val != cmp {
- var valString string
- if c, ok := val.(Color); ok {
- valString = c.String()
- } else {
- valString = vals.Repr(val, 0)
- }
- fmt.Fprintf(buf, "&%s=%s ", key, valString)
- }
- }
- addIfNotEqual("fg-color", s.Foreground, nil)
- addIfNotEqual("bg-color", s.Background, nil)
- addIfNotEqual("bold", s.Bold, false)
- addIfNotEqual("dim", s.Dim, false)
- addIfNotEqual("italic", s.Italic, false)
- addIfNotEqual("underlined", s.Underlined, false)
- addIfNotEqual("blink", s.Blink, false)
- addIfNotEqual("inverse", s.Inverse, false)
- if buf.Len() == 0 {
- return s.Text
- }
- return fmt.Sprintf("(ui:text-segment %s %s)", s.Text, strings.TrimSpace(buf.String()))
- }
- // IterateKeys feeds the function with all valid attributes of styled-segment.
- func (*Segment) IterateKeys(fn func(v any) bool) {
- vals.Feed(fn, "text", "fg-color", "bg-color", "bold", "dim", "italic", "underlined", "blink", "inverse")
- }
- // Index provides access to the attributes of a styled-segment.
- func (s *Segment) Index(k any) (v any, ok bool) {
- switch k {
- case "text":
- v = s.Text
- case "fg-color":
- if s.Foreground == nil {
- return "default", true
- }
- return s.Foreground.String(), true
- case "bg-color":
- if s.Background == nil {
- return "default", true
- }
- return s.Background.String(), true
- case "bold":
- v = s.Bold
- case "dim":
- v = s.Dim
- case "italic":
- v = s.Italic
- case "underlined":
- v = s.Underlined
- case "blink":
- v = s.Blink
- case "inverse":
- v = s.Inverse
- }
- return v, v != nil
- }
- // Concat implements Segment+string, Segment+float64, Segment+Segment and
- // Segment+Text.
- func (s *Segment) Concat(v any) (any, error) {
- switch rhs := v.(type) {
- case string:
- return Text{s, &Segment{Text: rhs}}, nil
- case *Segment:
- return Text{s, rhs}, nil
- case Text:
- return Text(append([]*Segment{s}, rhs...)), nil
- case int, *big.Int, *big.Rat, float64:
- return Text{s, &Segment{Text: vals.ToString(rhs)}}, nil
- }
- return nil, vals.ErrConcatNotImplemented
- }
- // RConcat implements string+Segment and float64+Segment.
- func (s *Segment) RConcat(v any) (any, error) {
- switch lhs := v.(type) {
- case string:
- return Text{&Segment{Text: lhs}, s}, nil
- case int, *big.Int, *big.Rat, float64:
- return Text{&Segment{Text: vals.ToString(lhs)}, s}, nil
- }
- return nil, vals.ErrConcatNotImplemented
- }
- // Clone returns a copy of the Segment.
- func (s *Segment) Clone() *Segment {
- value := *s
- return &value
- }
- // CountRune counts the number of times a rune occurs in a Segment.
- func (s *Segment) CountRune(r rune) int {
- return strings.Count(s.Text, string(r))
- }
- // SplitByRune splits a Segment by the given rune.
- func (s *Segment) SplitByRune(r rune) []*Segment {
- splitTexts := strings.Split(s.Text, string(r))
- splitSegs := make([]*Segment, len(splitTexts))
- for i, splitText := range splitTexts {
- splitSegs[i] = &Segment{s.Style, splitText}
- }
- return splitSegs
- }
- // String returns a string representation of the styled segment. This now always
- // assumes VT-style terminal output.
- // TODO: Make string conversion sensible to environment, e.g. use HTML when
- // output is web.
- func (s *Segment) String() string {
- return s.VTString()
- }
- // VTString renders the styled segment using VT-style escape sequences. Any
- // existing SGR state will be cleared.
- func (s *Segment) VTString() string {
- sgr := s.SGR()
- if sgr == "" {
- return "\033[m" + s.Text
- }
- return fmt.Sprintf("\033[;%sm%s\033[m", sgr, s.Text)
- }
|