github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/ui/text_segment.go (about) 1 package ui 2 3 import ( 4 "bytes" 5 "fmt" 6 "math/big" 7 "strings" 8 9 "github.com/markusbkk/elvish/pkg/eval/vals" 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 buf := new(bytes.Buffer) 26 addIfNotEqual := func(key string, val, cmp interface{}) { 27 if val != cmp { 28 var valString string 29 if c, ok := val.(Color); ok { 30 valString = c.String() 31 } else { 32 valString = vals.Repr(val, 0) 33 } 34 fmt.Fprintf(buf, "&%s=%s ", key, valString) 35 } 36 } 37 38 addIfNotEqual("fg-color", s.Foreground, nil) 39 addIfNotEqual("bg-color", s.Background, nil) 40 addIfNotEqual("bold", s.Bold, false) 41 addIfNotEqual("dim", s.Dim, false) 42 addIfNotEqual("italic", s.Italic, false) 43 addIfNotEqual("underlined", s.Underlined, false) 44 addIfNotEqual("blink", s.Blink, false) 45 addIfNotEqual("inverse", s.Inverse, false) 46 47 if buf.Len() == 0 { 48 return s.Text 49 } 50 51 return fmt.Sprintf("(ui:text-segment %s %s)", s.Text, strings.TrimSpace(buf.String())) 52 } 53 54 // IterateKeys feeds the function with all valid attributes of styled-segment. 55 func (*Segment) IterateKeys(fn func(v interface{}) bool) { 56 vals.Feed(fn, "text", "fg-color", "bg-color", "bold", "dim", "italic", "underlined", "blink", "inverse") 57 } 58 59 // Index provides access to the attributes of a styled-segment. 60 func (s *Segment) Index(k interface{}) (v interface{}, ok bool) { 61 switch k { 62 case "text": 63 v = s.Text 64 case "fg-color": 65 if s.Foreground == nil { 66 return "default", true 67 } 68 return s.Foreground.String(), true 69 case "bg-color": 70 if s.Background == nil { 71 return "default", true 72 } 73 return s.Background.String(), true 74 case "bold": 75 v = s.Bold 76 case "dim": 77 v = s.Dim 78 case "italic": 79 v = s.Italic 80 case "underlined": 81 v = s.Underlined 82 case "blink": 83 v = s.Blink 84 case "inverse": 85 v = s.Inverse 86 } 87 88 return v, v != nil 89 } 90 91 // Concat implements Segment+string, Segment+float64, Segment+Segment and 92 // Segment+Text. 93 func (s *Segment) Concat(v interface{}) (interface{}, error) { 94 switch rhs := v.(type) { 95 case string: 96 return Text{s, &Segment{Text: rhs}}, nil 97 case *Segment: 98 return Text{s, rhs}, nil 99 case Text: 100 return Text(append([]*Segment{s}, rhs...)), nil 101 case int, *big.Int, *big.Rat, float64: 102 return Text{s, &Segment{Text: vals.ToString(rhs)}}, nil 103 } 104 return nil, vals.ErrConcatNotImplemented 105 } 106 107 // RConcat implements string+Segment and float64+Segment. 108 func (s *Segment) RConcat(v interface{}) (interface{}, error) { 109 switch lhs := v.(type) { 110 case string: 111 return Text{&Segment{Text: lhs}, s}, nil 112 case int, *big.Int, *big.Rat, float64: 113 return Text{&Segment{Text: vals.ToString(lhs)}, s}, nil 114 } 115 return nil, vals.ErrConcatNotImplemented 116 } 117 118 // Clone returns a copy of the Segment. 119 func (s *Segment) Clone() *Segment { 120 value := *s 121 return &value 122 } 123 124 // CountRune counts the number of times a rune occurs in a Segment. 125 func (s *Segment) CountRune(r rune) int { 126 return strings.Count(s.Text, string(r)) 127 } 128 129 // SplitByRune splits a Segment by the given rune. 130 func (s *Segment) SplitByRune(r rune) []*Segment { 131 splitTexts := strings.Split(s.Text, string(r)) 132 splitSegs := make([]*Segment, len(splitTexts)) 133 for i, splitText := range splitTexts { 134 splitSegs[i] = &Segment{s.Style, splitText} 135 } 136 return splitSegs 137 } 138 139 // String returns a string representation of the styled segment. This now always 140 // assumes VT-style terminal output. 141 // TODO: Make string conversion sensible to environment, e.g. use HTML when 142 // output is web. 143 func (s *Segment) String() string { 144 return s.VTString() 145 } 146 147 // VTString renders the styled segment using VT-style escape sequences. 148 func (s *Segment) VTString() string { 149 sgr := s.SGR() 150 if sgr == "" { 151 return s.Text 152 } 153 return fmt.Sprintf("\033[%sm%s\033[m", sgr, s.Text) 154 }