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

     1  package ui
     2  
     3  import (
     4  	"strings"
     5  )
     6  
     7  // Styling specifies how to change a Style. It can also be applied to a Segment
     8  // or Text.
     9  type Styling interface{ transform(*Style) }
    10  
    11  // StyleText returns a new Text with the given Styling's applied. It does not
    12  // modify the given Text.
    13  func StyleText(t Text, ts ...Styling) Text {
    14  	newt := make(Text, len(t))
    15  	for i, seg := range t {
    16  		newt[i] = StyleSegment(seg, ts...)
    17  	}
    18  	return newt
    19  }
    20  
    21  // StyleSegment returns a new Segment with the given Styling's applied. It does
    22  // not modify the given Segment.
    23  func StyleSegment(seg *Segment, ts ...Styling) *Segment {
    24  	return &Segment{Text: seg.Text, Style: ApplyStyling(seg.Style, ts...)}
    25  }
    26  
    27  // ApplyStyling returns a new Style with the given Styling's applied.
    28  func ApplyStyling(s Style, ts ...Styling) Style {
    29  	for _, t := range ts {
    30  		if t != nil {
    31  			t.transform(&s)
    32  		}
    33  	}
    34  	return s
    35  }
    36  
    37  // Stylings joins several transformers into one.
    38  func Stylings(ts ...Styling) Styling { return jointStyling(ts) }
    39  
    40  // Common stylings.
    41  var (
    42  	Reset Styling = reset{}
    43  
    44  	FgDefault Styling = setForeground{nil}
    45  
    46  	FgBlack   Styling = setForeground{Black}
    47  	FgRed     Styling = setForeground{Red}
    48  	FgGreen   Styling = setForeground{Green}
    49  	FgYellow  Styling = setForeground{Yellow}
    50  	FgBlue    Styling = setForeground{Blue}
    51  	FgMagenta Styling = setForeground{Magenta}
    52  	FgCyan    Styling = setForeground{Cyan}
    53  	FgWhite   Styling = setForeground{White}
    54  
    55  	FgBrightBlack   Styling = setForeground{BrightBlack}
    56  	FgBrightRed     Styling = setForeground{BrightRed}
    57  	FgBrightGreen   Styling = setForeground{BrightGreen}
    58  	FgBrightYellow  Styling = setForeground{BrightYellow}
    59  	FgBrightBlue    Styling = setForeground{BrightBlue}
    60  	FgBrightMagenta Styling = setForeground{BrightMagenta}
    61  	FgBrightCyan    Styling = setForeground{BrightCyan}
    62  	FgBrightWhite   Styling = setForeground{BrightWhite}
    63  
    64  	BgDefault Styling = setBackground{nil}
    65  
    66  	BgBlack   Styling = setBackground{Black}
    67  	BgRed     Styling = setBackground{Red}
    68  	BgGreen   Styling = setBackground{Green}
    69  	BgYellow  Styling = setBackground{Yellow}
    70  	BgBlue    Styling = setBackground{Blue}
    71  	BgMagenta Styling = setBackground{Magenta}
    72  	BgCyan    Styling = setBackground{Cyan}
    73  	BgWhite   Styling = setBackground{White}
    74  
    75  	BgBrightBlack   Styling = setBackground{BrightBlack}
    76  	BgBrightRed     Styling = setBackground{BrightRed}
    77  	BgBrightGreen   Styling = setBackground{BrightGreen}
    78  	BgBrightYellow  Styling = setBackground{BrightYellow}
    79  	BgBrightBlue    Styling = setBackground{BrightBlue}
    80  	BgBrightMagenta Styling = setBackground{BrightMagenta}
    81  	BgBrightCyan    Styling = setBackground{BrightCyan}
    82  	BgBrightWhite   Styling = setBackground{BrightWhite}
    83  
    84  	Bold       Styling = boolOn{boldField{}}
    85  	Dim        Styling = boolOn{dimField{}}
    86  	Italic     Styling = boolOn{italicField{}}
    87  	Underlined Styling = boolOn{underlinedField{}}
    88  	Blink      Styling = boolOn{blinkField{}}
    89  	Inverse    Styling = boolOn{inverseField{}}
    90  
    91  	NoBold       Styling = boolOff{boldField{}}
    92  	NoDim        Styling = boolOff{dimField{}}
    93  	NoItalic     Styling = boolOff{italicField{}}
    94  	NoUnderlined Styling = boolOff{underlinedField{}}
    95  	NoBlink      Styling = boolOff{blinkField{}}
    96  	NoInverse    Styling = boolOff{inverseField{}}
    97  
    98  	ToggleBold       Styling = boolToggle{boldField{}}
    99  	ToggleDim        Styling = boolToggle{dimField{}}
   100  	ToggleItalic     Styling = boolToggle{italicField{}}
   101  	ToggleUnderlined Styling = boolToggle{underlinedField{}}
   102  	ToggleBlink      Styling = boolToggle{blinkField{}}
   103  	ToggleInverse    Styling = boolToggle{inverseField{}}
   104  )
   105  
   106  // Fg returns a Styling that sets the foreground color.
   107  func Fg(c Color) Styling { return setForeground{c} }
   108  
   109  // Bg returns a Styling that sets the background color.
   110  func Bg(c Color) Styling { return setBackground{c} }
   111  
   112  type reset struct{}
   113  type setForeground struct{ c Color }
   114  type setBackground struct{ c Color }
   115  type boolOn struct{ f boolField }
   116  type boolOff struct{ f boolField }
   117  type boolToggle struct{ f boolField }
   118  
   119  func (reset) transform(s *Style)           { *s = Style{} }
   120  func (t setForeground) transform(s *Style) { s.Fg = t.c }
   121  func (t setBackground) transform(s *Style) { s.Bg = t.c }
   122  func (t boolOn) transform(s *Style)        { *t.f.get(s) = true }
   123  func (t boolOff) transform(s *Style)       { *t.f.get(s) = false }
   124  func (t boolToggle) transform(s *Style)    { p := t.f.get(s); *p = !*p }
   125  
   126  type boolField interface{ get(*Style) *bool }
   127  
   128  type boldField struct{}
   129  type dimField struct{}
   130  type italicField struct{}
   131  type underlinedField struct{}
   132  type blinkField struct{}
   133  type inverseField struct{}
   134  
   135  func (boldField) get(s *Style) *bool       { return &s.Bold }
   136  func (dimField) get(s *Style) *bool        { return &s.Dim }
   137  func (italicField) get(s *Style) *bool     { return &s.Italic }
   138  func (underlinedField) get(s *Style) *bool { return &s.Underlined }
   139  func (blinkField) get(s *Style) *bool      { return &s.Blink }
   140  func (inverseField) get(s *Style) *bool    { return &s.Inverse }
   141  
   142  type jointStyling []Styling
   143  
   144  func (t jointStyling) transform(s *Style) {
   145  	for _, t := range t {
   146  		t.transform(s)
   147  	}
   148  }
   149  
   150  // ParseStyling parses a text representation of Styling, which are kebab
   151  // case counterparts to the names of the builtin Styling's. For example,
   152  // ToggleInverse is expressed as "toggle-inverse".
   153  //
   154  // Multiple stylings can be joined by spaces, which is equivalent to calling
   155  // Stylings.
   156  //
   157  // If the given string is invalid, ParseStyling returns nil.
   158  func ParseStyling(s string) Styling {
   159  	if !strings.ContainsRune(s, ' ') {
   160  		return parseOneStyling(s)
   161  	}
   162  	var joint jointStyling
   163  	for _, subs := range strings.Split(s, " ") {
   164  		parsed := parseOneStyling(subs)
   165  		if parsed == nil {
   166  			return nil
   167  		}
   168  		joint = append(joint, parseOneStyling(subs))
   169  	}
   170  	return joint
   171  }
   172  
   173  var boolFields = map[string]boolField{
   174  	"bold":       boldField{},
   175  	"dim":        dimField{},
   176  	"italic":     italicField{},
   177  	"underlined": underlinedField{},
   178  	"blink":      blinkField{},
   179  	"inverse":    inverseField{},
   180  }
   181  
   182  func parseOneStyling(name string) Styling {
   183  	switch {
   184  	case name == "default" || name == "fg-default":
   185  		return FgDefault
   186  	case strings.HasPrefix(name, "fg-"):
   187  		if color := parseColor(name[len("fg-"):]); color != nil {
   188  			return setForeground{color}
   189  		}
   190  	case name == "bg-default":
   191  		return BgDefault
   192  	case strings.HasPrefix(name, "bg-"):
   193  		if color := parseColor(name[len("bg-"):]); color != nil {
   194  			return setBackground{color}
   195  		}
   196  	case strings.HasPrefix(name, "no-"):
   197  		if f, ok := boolFields[name[len("no-"):]]; ok {
   198  			return boolOff{f}
   199  		}
   200  	case strings.HasPrefix(name, "toggle-"):
   201  		if f, ok := boolFields[name[len("toggle-"):]]; ok {
   202  			return boolToggle{f}
   203  		}
   204  	default:
   205  		if f, ok := boolFields[name]; ok {
   206  			return boolOn{f}
   207  		}
   208  		if color := parseColor(name); color != nil {
   209  			return setForeground{color}
   210  		}
   211  	}
   212  	return nil
   213  }