src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/ui/text_segment.go (about) 1 package ui 2 3 import ( 4 "fmt" 5 "math/big" 6 "strings" 7 8 "src.elv.sh/pkg/eval/vals" 9 "src.elv.sh/pkg/parse" 10 ) 11 12 // Segment is a string that has some style applied to it. 13 type Segment struct { 14 Style 15 Text string 16 } 17 18 // Kind returns "styled-segment". 19 func (*Segment) Kind() string { return "ui:text-segment" } 20 21 // Repr returns the representation of this Segment. The string can be used to 22 // construct an identical Segment. Unset or default attributes are skipped. If 23 // the Segment represents an unstyled string only this string is returned. 24 func (s *Segment) Repr(int) string { 25 var sb strings.Builder 26 addColor := func(key string, c Color) { 27 if c != nil { 28 fmt.Fprintf(&sb, " &%s=%s", key, c.String()) 29 } 30 } 31 addBool := func(key string, b bool) { 32 if b { 33 fmt.Fprintf(&sb, " &%s", key) 34 } 35 } 36 37 addColor("fg-color", s.Fg) 38 addColor("bg-color", s.Bg) 39 addBool("bold", s.Bold) 40 addBool("dim", s.Dim) 41 addBool("italic", s.Italic) 42 addBool("underlined", s.Underlined) 43 addBool("blink", s.Blink) 44 addBool("inverse", s.Inverse) 45 46 if sb.Len() == 0 { 47 return parse.Quote(s.Text) 48 } 49 50 return fmt.Sprintf("(styled-segment %s%s)", parse.Quote(s.Text), sb.String()) 51 } 52 53 // IterateKeys feeds the function with all valid attributes of styled-segment. 54 func (*Segment) IterateKeys(fn func(v any) bool) { 55 vals.Feed(fn, "text", "fg-color", "bg-color", "bold", "dim", "italic", "underlined", "blink", "inverse") 56 } 57 58 // Index provides access to the attributes of a styled-segment. 59 func (s *Segment) Index(k any) (v any, ok bool) { 60 switch k { 61 case "text": 62 v = s.Text 63 case "fg-color": 64 if s.Fg == nil { 65 return "default", true 66 } 67 return s.Fg.String(), true 68 case "bg-color": 69 if s.Bg == nil { 70 return "default", true 71 } 72 return s.Bg.String(), true 73 case "bold": 74 v = s.Bold 75 case "dim": 76 v = s.Dim 77 case "italic": 78 v = s.Italic 79 case "underlined": 80 v = s.Underlined 81 case "blink": 82 v = s.Blink 83 case "inverse": 84 v = s.Inverse 85 } 86 87 return v, v != nil 88 } 89 90 // Concat implements Segment+string, Segment+float64, Segment+Segment and 91 // Segment+Text. 92 func (s *Segment) Concat(v any) (any, error) { 93 switch rhs := v.(type) { 94 case string: 95 return Text{s, &Segment{Text: rhs}}, nil 96 case *Segment: 97 return Text{s, rhs}, nil 98 case Text: 99 return Text(append([]*Segment{s}, rhs...)), nil 100 case int, *big.Int, *big.Rat, float64: 101 return Text{s, &Segment{Text: vals.ToString(rhs)}}, nil 102 } 103 return nil, vals.ErrConcatNotImplemented 104 } 105 106 // RConcat implements string+Segment and float64+Segment. 107 func (s *Segment) RConcat(v any) (any, error) { 108 switch lhs := v.(type) { 109 case string: 110 return Text{&Segment{Text: lhs}, s}, nil 111 case int, *big.Int, *big.Rat, float64: 112 return Text{&Segment{Text: vals.ToString(lhs)}, s}, nil 113 } 114 return nil, vals.ErrConcatNotImplemented 115 } 116 117 // Clone returns a copy of the Segment. 118 func (s *Segment) Clone() *Segment { 119 value := *s 120 return &value 121 } 122 123 // CountRune counts the number of times a rune occurs in a Segment. 124 func (s *Segment) CountRune(r rune) int { 125 return strings.Count(s.Text, string(r)) 126 } 127 128 // SplitByRune splits a Segment by the given rune. 129 func (s *Segment) SplitByRune(r rune) []*Segment { 130 splitTexts := strings.Split(s.Text, string(r)) 131 splitSegs := make([]*Segment, len(splitTexts)) 132 for i, splitText := range splitTexts { 133 splitSegs[i] = &Segment{s.Style, splitText} 134 } 135 return splitSegs 136 } 137 138 // String returns a string representation of the styled segment. This now always 139 // assumes VT-style terminal output. 140 // TODO: Make string conversion sensible to environment, e.g. use HTML when 141 // output is web. 142 func (s *Segment) String() string { 143 return s.VTString() 144 } 145 146 // VTString renders the styled segment using VT-style escape sequences. Any 147 // existing SGR state will be cleared. 148 func (s *Segment) VTString() string { 149 sgr := s.SGR() 150 if sgr == "" { 151 return "\033[m" + s.Text 152 } 153 return fmt.Sprintf("\033[;%sm%s\033[m", sgr, s.Text) 154 }