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

     1  package ui
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"strings"
     7  
     8  	"src.elv.sh/pkg/eval/vals"
     9  	"src.elv.sh/pkg/parse"
    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  	var sb strings.Builder
    26  	addColor := func(key string, c Color) {
    27  		if c != nil {
    28  			fmt.Fprintf(&sb, " &%s=%s", key, c.String())
    29  		}
    30  	}
    31  	addBool := func(key string, b bool) {
    32  		if b {
    33  			fmt.Fprintf(&sb, " &%s", key)
    34  		}
    35  	}
    36  
    37  	addColor("fg-color", s.Fg)
    38  	addColor("bg-color", s.Bg)
    39  	addBool("bold", s.Bold)
    40  	addBool("dim", s.Dim)
    41  	addBool("italic", s.Italic)
    42  	addBool("underlined", s.Underlined)
    43  	addBool("blink", s.Blink)
    44  	addBool("inverse", s.Inverse)
    45  
    46  	if sb.Len() == 0 {
    47  		return parse.Quote(s.Text)
    48  	}
    49  
    50  	return fmt.Sprintf("(styled-segment %s%s)", parse.Quote(s.Text), sb.String())
    51  }
    52  
    53  // IterateKeys feeds the function with all valid attributes of styled-segment.
    54  func (*Segment) IterateKeys(fn func(v any) 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 any) (v any, ok bool) {
    60  	switch k {
    61  	case "text":
    62  		v = s.Text
    63  	case "fg-color":
    64  		if s.Fg == nil {
    65  			return "default", true
    66  		}
    67  		return s.Fg.String(), true
    68  	case "bg-color":
    69  		if s.Bg == nil {
    70  			return "default", true
    71  		}
    72  		return s.Bg.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 any) (any, error) {
    93  	switch rhs := v.(type) {
    94  	case string:
    95  		return Text{s, &Segment{Text: rhs}}, nil
    96  	case *Segment:
    97  		return Text{s, rhs}, nil
    98  	case Text:
    99  		return Text(append([]*Segment{s}, rhs...)), nil
   100  	case int, *big.Int, *big.Rat, float64:
   101  		return Text{s, &Segment{Text: vals.ToString(rhs)}}, nil
   102  	}
   103  	return nil, vals.ErrConcatNotImplemented
   104  }
   105  
   106  // RConcat implements string+Segment and float64+Segment.
   107  func (s *Segment) RConcat(v any) (any, error) {
   108  	switch lhs := v.(type) {
   109  	case string:
   110  		return Text{&Segment{Text: lhs}, s}, nil
   111  	case int, *big.Int, *big.Rat, float64:
   112  		return Text{&Segment{Text: vals.ToString(lhs)}, s}, nil
   113  	}
   114  	return nil, vals.ErrConcatNotImplemented
   115  }
   116  
   117  // Clone returns a copy of the Segment.
   118  func (s *Segment) Clone() *Segment {
   119  	value := *s
   120  	return &value
   121  }
   122  
   123  // CountRune counts the number of times a rune occurs in a Segment.
   124  func (s *Segment) CountRune(r rune) int {
   125  	return strings.Count(s.Text, string(r))
   126  }
   127  
   128  // SplitByRune splits a Segment by the given rune.
   129  func (s *Segment) SplitByRune(r rune) []*Segment {
   130  	splitTexts := strings.Split(s.Text, string(r))
   131  	splitSegs := make([]*Segment, len(splitTexts))
   132  	for i, splitText := range splitTexts {
   133  		splitSegs[i] = &Segment{s.Style, splitText}
   134  	}
   135  	return splitSegs
   136  }
   137  
   138  // String returns a string representation of the styled segment. This now always
   139  // assumes VT-style terminal output.
   140  // TODO: Make string conversion sensible to environment, e.g. use HTML when
   141  // output is web.
   142  func (s *Segment) String() string {
   143  	return s.VTString()
   144  }
   145  
   146  // VTString renders the styled segment using VT-style escape sequences. Any
   147  // existing SGR state will be cleared.
   148  func (s *Segment) VTString() string {
   149  	sgr := s.SGR()
   150  	if sgr == "" {
   151  		return "\033[m" + s.Text
   152  	}
   153  	return fmt.Sprintf("\033[;%sm%s\033[m", sgr, s.Text)
   154  }