github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/ui/text_segment.go (about) 1 package ui 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 8 "src.elv.sh/pkg/eval/vals" 9 ) 10 11 // Segment is a string that has some style applied to it. 12 type Segment struct { 13 Style 14 Text string 15 } 16 17 // Kind returns "styled-segment". 18 func (*Segment) Kind() string { return "ui:text-segment" } 19 20 // Repr returns the representation of this Segment. The string can be used to 21 // construct an identical Segment. Unset or default attributes are skipped. If 22 // the Segment represents an unstyled string only this string is returned. 23 func (s *Segment) Repr(int) string { 24 buf := new(bytes.Buffer) 25 addIfNotEqual := func(key string, val, cmp interface{}) { 26 if val != cmp { 27 var valString string 28 if c, ok := val.(Color); ok { 29 valString = c.String() 30 } else { 31 valString = vals.Repr(val, 0) 32 } 33 fmt.Fprintf(buf, "&%s=%s ", key, valString) 34 } 35 } 36 37 addIfNotEqual("fg-color", s.Foreground, nil) 38 addIfNotEqual("bg-color", s.Background, nil) 39 addIfNotEqual("bold", s.Bold, false) 40 addIfNotEqual("dim", s.Dim, false) 41 addIfNotEqual("italic", s.Italic, false) 42 addIfNotEqual("underlined", s.Underlined, false) 43 addIfNotEqual("blink", s.Blink, false) 44 addIfNotEqual("inverse", s.Inverse, false) 45 46 if buf.Len() == 0 { 47 return s.Text 48 } 49 50 return fmt.Sprintf("(ui:text-segment %s %s)", s.Text, strings.TrimSpace(buf.String())) 51 } 52 53 // IterateKeys feeds the function with all valid attributes of styled-segment. 54 func (*Segment) IterateKeys(fn func(v interface{}) 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 interface{}) (v interface{}, ok bool) { 60 switch k { 61 case "text": 62 v = s.Text 63 case "fg-color": 64 if s.Foreground == nil { 65 return "default", true 66 } 67 return s.Foreground.String(), true 68 case "bg-color": 69 if s.Background == nil { 70 return "default", true 71 } 72 return s.Background.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 interface{}) (interface{}, error) { 93 switch rhs := v.(type) { 94 case string: 95 return Text{ 96 s, 97 &Segment{Text: rhs}, 98 }, nil 99 case float64: 100 return Text{ 101 s, 102 &Segment{Text: vals.ToString(rhs)}, 103 }, nil 104 case *Segment: 105 return Text{s, rhs}, nil 106 case Text: 107 return Text(append([]*Segment{s}, rhs...)), nil 108 } 109 110 return nil, vals.ErrConcatNotImplemented 111 } 112 113 // RConcat implements string+Segment and float64+Segment. 114 func (s *Segment) RConcat(v interface{}) (interface{}, error) { 115 switch lhs := v.(type) { 116 case string: 117 return Text{ 118 &Segment{Text: lhs}, 119 s, 120 }, nil 121 case float64: 122 return Text{ 123 &Segment{Text: vals.ToString(lhs)}, 124 s, 125 }, nil 126 } 127 return nil, vals.ErrConcatNotImplemented 128 } 129 130 // Clone returns a copy of the Segment. 131 func (s *Segment) Clone() *Segment { 132 value := *s 133 return &value 134 } 135 136 // CountRune counts the number of times a rune occurs in a Segment. 137 func (s *Segment) CountRune(r rune) int { 138 return strings.Count(s.Text, string(r)) 139 } 140 141 // SplitByRune splits a Segment by the given rune. 142 func (s *Segment) SplitByRune(r rune) []*Segment { 143 splitTexts := strings.Split(s.Text, string(r)) 144 splitSegs := make([]*Segment, len(splitTexts)) 145 for i, splitText := range splitTexts { 146 splitSegs[i] = &Segment{s.Style, splitText} 147 } 148 return splitSegs 149 } 150 151 // String returns a string representation of the styled segment. This now always 152 // assumes VT-style terminal output. 153 // TODO: Make string conversion sensible to environment, e.g. use HTML when 154 // output is web. 155 func (s *Segment) String() string { 156 return s.VTString() 157 } 158 159 // VTString renders the styled segment using VT-style escape sequences. 160 func (s *Segment) VTString() string { 161 sgr := s.SGR() 162 if sgr == "" { 163 return s.Text 164 } 165 return fmt.Sprintf("\033[%sm%s\033[m", sgr, s.Text) 166 }