github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/ui/text_segment.go (about)

     1  package ui
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"src.elv.sh/pkg/eval/vals"
     9  )
    10  
    11  // Segment is a string that has some style applied to it.
    12  type Segment struct {
    13  	Style
    14  	Text string
    15  }
    16  
    17  // Kind returns "styled-segment".
    18  func (*Segment) Kind() string { return "ui:text-segment" }
    19  
    20  // Repr returns the representation of this Segment. The string can be used to
    21  // construct an identical Segment. Unset or default attributes are skipped. If
    22  // the Segment represents an unstyled string only this string is returned.
    23  func (s *Segment) Repr(int) string {
    24  	buf := new(bytes.Buffer)
    25  	addIfNotEqual := func(key string, val, cmp interface{}) {
    26  		if val != cmp {
    27  			var valString string
    28  			if c, ok := val.(Color); ok {
    29  				valString = c.String()
    30  			} else {
    31  				valString = vals.Repr(val, 0)
    32  			}
    33  			fmt.Fprintf(buf, "&%s=%s ", key, valString)
    34  		}
    35  	}
    36  
    37  	addIfNotEqual("fg-color", s.Foreground, nil)
    38  	addIfNotEqual("bg-color", s.Background, nil)
    39  	addIfNotEqual("bold", s.Bold, false)
    40  	addIfNotEqual("dim", s.Dim, false)
    41  	addIfNotEqual("italic", s.Italic, false)
    42  	addIfNotEqual("underlined", s.Underlined, false)
    43  	addIfNotEqual("blink", s.Blink, false)
    44  	addIfNotEqual("inverse", s.Inverse, false)
    45  
    46  	if buf.Len() == 0 {
    47  		return s.Text
    48  	}
    49  
    50  	return fmt.Sprintf("(ui:text-segment %s %s)", s.Text, strings.TrimSpace(buf.String()))
    51  }
    52  
    53  // IterateKeys feeds the function with all valid attributes of styled-segment.
    54  func (*Segment) IterateKeys(fn func(v interface{}) 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 interface{}) (v interface{}, ok bool) {
    60  	switch k {
    61  	case "text":
    62  		v = s.Text
    63  	case "fg-color":
    64  		if s.Foreground == nil {
    65  			return "default", true
    66  		}
    67  		return s.Foreground.String(), true
    68  	case "bg-color":
    69  		if s.Background == nil {
    70  			return "default", true
    71  		}
    72  		return s.Background.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 interface{}) (interface{}, error) {
    93  	switch rhs := v.(type) {
    94  	case string:
    95  		return Text{
    96  			s,
    97  			&Segment{Text: rhs},
    98  		}, nil
    99  	case float64:
   100  		return Text{
   101  			s,
   102  			&Segment{Text: vals.ToString(rhs)},
   103  		}, nil
   104  	case *Segment:
   105  		return Text{s, rhs}, nil
   106  	case Text:
   107  		return Text(append([]*Segment{s}, rhs...)), nil
   108  	}
   109  
   110  	return nil, vals.ErrConcatNotImplemented
   111  }
   112  
   113  // RConcat implements string+Segment and float64+Segment.
   114  func (s *Segment) RConcat(v interface{}) (interface{}, error) {
   115  	switch lhs := v.(type) {
   116  	case string:
   117  		return Text{
   118  			&Segment{Text: lhs},
   119  			s,
   120  		}, nil
   121  	case float64:
   122  		return Text{
   123  			&Segment{Text: vals.ToString(lhs)},
   124  			s,
   125  		}, nil
   126  	}
   127  	return nil, vals.ErrConcatNotImplemented
   128  }
   129  
   130  // Clone returns a copy of the Segment.
   131  func (s *Segment) Clone() *Segment {
   132  	value := *s
   133  	return &value
   134  }
   135  
   136  // CountRune counts the number of times a rune occurs in a Segment.
   137  func (s *Segment) CountRune(r rune) int {
   138  	return strings.Count(s.Text, string(r))
   139  }
   140  
   141  // SplitByRune splits a Segment by the given rune.
   142  func (s *Segment) SplitByRune(r rune) []*Segment {
   143  	splitTexts := strings.Split(s.Text, string(r))
   144  	splitSegs := make([]*Segment, len(splitTexts))
   145  	for i, splitText := range splitTexts {
   146  		splitSegs[i] = &Segment{s.Style, splitText}
   147  	}
   148  	return splitSegs
   149  }
   150  
   151  // String returns a string representation of the styled segment. This now always
   152  // assumes VT-style terminal output.
   153  // TODO: Make string conversion sensible to environment, e.g. use HTML when
   154  // output is web.
   155  func (s *Segment) String() string {
   156  	return s.VTString()
   157  }
   158  
   159  // VTString renders the styled segment using VT-style escape sequences.
   160  func (s *Segment) VTString() string {
   161  	sgr := s.SGR()
   162  	if sgr == "" {
   163  		return s.Text
   164  	}
   165  	return fmt.Sprintf("\033[%sm%s\033[m", sgr, s.Text)
   166  }