github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/ui/text_segment.go (about)

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