github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/builtin_fn_styled.go (about) 1 package eval 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/markusbkk/elvish/pkg/eval/vals" 8 "github.com/markusbkk/elvish/pkg/parse" 9 "github.com/markusbkk/elvish/pkg/ui" 10 ) 11 12 var errStyledSegmentArgType = errors.New("argument to styled-segment must be a string or a styled segment") 13 14 func init() { 15 addBuiltinFns(map[string]interface{}{ 16 "styled-segment": styledSegment, 17 "styled": styled, 18 }) 19 } 20 21 //elvdoc:fn styled-segment 22 // 23 // ```elvish 24 // styled-segment $object &fg-color=default &bg-color=default &bold=$false &dim=$false &italic=$false &underlined=$false &blink=$false &inverse=$false 25 // ``` 26 // 27 // Constructs a styled segment and is a helper function for styled transformers. 28 // `$object` can be a plain string, a styled segment or a concatenation thereof. 29 // Probably the only reason to use it is to build custom style transformers: 30 // 31 // ```elvish 32 // fn my-awesome-style-transformer {|seg| styled-segment $seg &bold=(not $seg[dim]) &dim=(not $seg[italic]) &italic=$seg[bold] } 33 // styled abc $my-awesome-style-transformer~ 34 // ``` 35 // 36 // As just seen the properties of styled segments can be inspected by indexing into 37 // it. Valid indices are the same as the options to `styled-segment` plus `text`. 38 // 39 // ```elvish 40 // var s = (styled-segment abc &bold) 41 // put $s[text] 42 // put $s[fg-color] 43 // put $s[bold] 44 // ``` 45 46 // Turns a string or ui.Segment into a new ui.Segment with the attributes 47 // from the supplied options applied to it. If the input is already a Segment its 48 // attributes are copied and modified. 49 func styledSegment(options RawOptions, input interface{}) (*ui.Segment, error) { 50 var text string 51 var style ui.Style 52 53 switch input := input.(type) { 54 case string: 55 text = input 56 case *ui.Segment: 57 text = input.Text 58 style = input.Style 59 default: 60 return nil, errStyledSegmentArgType 61 } 62 63 if err := style.MergeFromOptions(options); err != nil { 64 return nil, err 65 } 66 67 return &ui.Segment{ 68 Text: text, 69 Style: style, 70 }, nil 71 } 72 73 //elvdoc:fn styled 74 // 75 // ```elvish 76 // styled $object $style-transformer... 77 // ``` 78 // 79 // Construct a styled text by applying the supplied transformers to the supplied 80 // object. `$object` can be either a string, a styled segment (see below), a styled 81 // text or an arbitrary concatenation of them. A `$style-transformer` is either: 82 // 83 // - The name of a builtin style transformer, which may be one of the following: 84 // 85 // - One of the attribute names `bold`, `dim`, `italic`, `underlined`, 86 // `blink` or `inverse` for setting the corresponding attribute. 87 // 88 // - An attribute name prefixed by `no-` for unsetting the attribute. 89 // 90 // - An attribute name prefixed by `toggle-` for toggling the attribute 91 // between set and unset. 92 // 93 // - A color name for setting the text color, which may be one of the 94 // following: 95 // 96 // - One of the 8 basic ANSI colors: `black`, `red`, `green`, `yellow`, 97 // `blue`, `magenta`, `cyan` and `white`. 98 // 99 // - The bright variant of the 8 basic ANSI colors, with a `bright-` 100 // prefix. 101 // 102 // - Any color from the xterm 256-color palette, as `colorX` (such as 103 // `color12`). 104 // 105 // - A 24-bit RGB color written as `#RRGGBB` such as `'#778899'`. 106 // 107 // **Note**: You need to quote such values since an unquoted `#` char 108 // introduces a comment. So use `'bg-#778899'` not `bg-#778899`. If 109 // you omit the quotes the text after the `#` char is ignored which 110 // will result in an error or unexpected behavior. 111 // 112 // - A color name prefixed by `bg-` to set the background color. 113 // 114 // - A color name prefixed by `fg-` to set the foreground color. This has 115 // the same effect as specifying the color name without the `fg-` prefix. 116 // 117 // - A lambda that receives a styled segment as the only argument and returns a 118 // single styled segment. 119 // 120 // - A function with the same properties as the lambda (provided via the 121 // `$transformer~` syntax). 122 // 123 // When a styled text is converted to a string the corresponding 124 // [ANSI SGR code](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_.28Select_Graphic_Rendition.29_parameters) 125 // is built to render the style. 126 // 127 // A styled text is nothing more than a wrapper around a list of styled segments. 128 // They can be accessed by indexing into it. 129 // 130 // ```elvish 131 // var s = (styled abc red)(styled def green) 132 // put $s[0] $s[1] 133 // ``` 134 135 func styled(fm *Frame, input interface{}, stylings ...interface{}) (ui.Text, error) { 136 var text ui.Text 137 138 switch input := input.(type) { 139 case string: 140 text = ui.Text{&ui.Segment{ 141 Text: input, 142 Style: ui.Style{}, 143 }} 144 case *ui.Segment: 145 text = ui.Text{input.Clone()} 146 case ui.Text: 147 text = input.Clone() 148 default: 149 return nil, fmt.Errorf("expected string, styled segment or styled text; got %s", vals.Kind(input)) 150 } 151 152 for _, styling := range stylings { 153 switch styling := styling.(type) { 154 case string: 155 parsedStyling := ui.ParseStyling(styling) 156 if parsedStyling == nil { 157 return nil, fmt.Errorf("%s is not a valid style transformer", parse.Quote(styling)) 158 } 159 text = ui.StyleText(text, parsedStyling) 160 case Callable: 161 for i, seg := range text { 162 vs, err := fm.CaptureOutput(func(fm *Frame) error { 163 return styling.Call(fm, []interface{}{seg}, NoOpts) 164 }) 165 if err != nil { 166 return nil, err 167 } 168 169 if n := len(vs); n != 1 { 170 return nil, fmt.Errorf("styling function must return a single segment; got %d values", n) 171 } else if styledSegment, ok := vs[0].(*ui.Segment); !ok { 172 return nil, fmt.Errorf("styling function must return a segment; got %s", vals.Kind(vs[0])) 173 } else { 174 text[i] = styledSegment 175 } 176 } 177 178 default: 179 return nil, fmt.Errorf("need string or callable; got %s", vals.Kind(styling)) 180 } 181 } 182 183 return text, nil 184 }