src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/builtin_fn_styled.go (about)

     1  package eval
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"src.elv.sh/pkg/eval/vals"
     8  	"src.elv.sh/pkg/parse"
     9  	"src.elv.sh/pkg/ui"
    10  	"src.elv.sh/pkg/ui/styledown"
    11  )
    12  
    13  var errStyledSegmentArgType = errors.New("argument to styled-segment must be a string or a styled segment")
    14  
    15  func init() {
    16  	addBuiltinFns(map[string]any{
    17  		"styled-segment":   styledSegment,
    18  		"styled":           styled,
    19  		"render-styledown": styledown.Render,
    20  	})
    21  }
    22  
    23  // Turns a string or ui.Segment into a new ui.Segment with the attributes
    24  // from the supplied options applied to it. If the input is already a Segment its
    25  // attributes are copied and modified.
    26  func styledSegment(options RawOptions, input any) (*ui.Segment, error) {
    27  	var text string
    28  	var style ui.Style
    29  
    30  	switch input := input.(type) {
    31  	case string:
    32  		text = input
    33  	case *ui.Segment:
    34  		text = input.Text
    35  		style = input.Style
    36  	default:
    37  		return nil, errStyledSegmentArgType
    38  	}
    39  
    40  	if err := style.MergeFromOptions(options); err != nil {
    41  		return nil, err
    42  	}
    43  
    44  	return &ui.Segment{
    45  		Text:  text,
    46  		Style: style,
    47  	}, nil
    48  }
    49  
    50  func styled(fm *Frame, input any, stylings ...any) (ui.Text, error) {
    51  	var text ui.Text
    52  
    53  	switch input := input.(type) {
    54  	case string:
    55  		text = ui.T(input)
    56  	case *ui.Segment:
    57  		text = ui.TextFromSegment(input)
    58  	case ui.Text:
    59  		text = input.Clone()
    60  	default:
    61  		return nil, fmt.Errorf("expected string, styled segment or styled text; got %s", vals.Kind(input))
    62  	}
    63  
    64  	for _, styling := range stylings {
    65  		switch styling := styling.(type) {
    66  		case string:
    67  			parsedStyling := ui.ParseStyling(styling)
    68  			if parsedStyling == nil {
    69  				return nil, fmt.Errorf("%s is not a valid style transformer", parse.Quote(styling))
    70  			}
    71  			text = ui.StyleText(text, parsedStyling)
    72  		case Callable:
    73  			for i, seg := range text {
    74  				vs, err := fm.CaptureOutput(func(fm *Frame) error {
    75  					return styling.Call(fm, []any{seg}, NoOpts)
    76  				})
    77  				if err != nil {
    78  					return nil, err
    79  				}
    80  
    81  				if n := len(vs); n != 1 {
    82  					return nil, fmt.Errorf("styling function must return a single segment; got %d values", n)
    83  				} else if styledSegment, ok := vs[0].(*ui.Segment); !ok {
    84  					return nil, fmt.Errorf("styling function must return a segment; got %s", vals.Kind(vs[0]))
    85  				} else {
    86  					text[i] = styledSegment
    87  				}
    88  			}
    89  
    90  		default:
    91  			return nil, fmt.Errorf("need string or callable; got %s", vals.Kind(styling))
    92  		}
    93  	}
    94  
    95  	return text, nil
    96  }