builtin_fn_styled.go 2.3 KB

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