github.com/grahambrereton-form3/tilt@v0.10.18/internal/rty/text.go (about)

     1  package rty
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	"github.com/gdamore/tcell"
     8  	"github.com/pkg/errors"
     9  )
    10  
    11  type StringBuilder interface {
    12  	Text(string) StringBuilder
    13  	Textf(string, ...interface{}) StringBuilder
    14  	Fg(tcell.Color) StringBuilder
    15  	Bg(tcell.Color) StringBuilder
    16  	Build() Component
    17  }
    18  
    19  func NewStringBuilder() StringBuilder {
    20  	return &stringBuilder{}
    21  }
    22  
    23  type directive interface {
    24  	directive()
    25  }
    26  
    27  type textDirective string
    28  type fgDirective tcell.Color
    29  type bgDirective tcell.Color
    30  
    31  func (textDirective) directive() {}
    32  func (fgDirective) directive()   {}
    33  func (bgDirective) directive()   {}
    34  
    35  type stringBuilder struct {
    36  	directives []directive
    37  }
    38  
    39  func (b *stringBuilder) Text(t string) StringBuilder {
    40  	writer := ANSIWriter()
    41  	_, _ = writer.Write([]byte(t))
    42  	b.directives = append(b.directives, writer.directives...)
    43  	return b
    44  }
    45  
    46  func (b *stringBuilder) Textf(format string, a ...interface{}) StringBuilder {
    47  	b.Text(fmt.Sprintf(format, a...))
    48  	return b
    49  }
    50  
    51  func (b *stringBuilder) Fg(c tcell.Color) StringBuilder {
    52  	b.directives = append(b.directives, fgDirective(c))
    53  	return b
    54  }
    55  
    56  func (b *stringBuilder) Bg(c tcell.Color) StringBuilder {
    57  	b.directives = append(b.directives, bgDirective(c))
    58  	return b
    59  }
    60  
    61  func (b *stringBuilder) Build() Component {
    62  	return &StringLayout{directives: b.directives}
    63  }
    64  
    65  type StringLayout struct {
    66  	directives []directive
    67  }
    68  
    69  var _ Component = &StringLayout{}
    70  
    71  func TextString(s string) Component {
    72  	return NewStringBuilder().Text(s).Build()
    73  }
    74  
    75  func ColoredString(s string, fg tcell.Color) Component {
    76  	return NewStringBuilder().Fg(fg).Text(s).Build()
    77  }
    78  
    79  func BgColoredString(s string, fg tcell.Color, bg tcell.Color) Component {
    80  	return NewStringBuilder().Fg(fg).Bg(bg).Text(s).Build()
    81  }
    82  
    83  func (l *StringLayout) Size(availWidth int, availHeight int) (int, int, error) {
    84  	return l.render(nil, availWidth, availHeight)
    85  }
    86  
    87  func (l *StringLayout) Render(w Writer, width int, height int) error {
    88  	_, _, err := l.render(w, width, height)
    89  	return err
    90  }
    91  
    92  // returns width, height for laying out full string
    93  func (l *StringLayout) render(w Writer, width int, height int) (int, int, error) {
    94  	nextX, nextY := 0, 0
    95  	maxWidth := 0
    96  	for _, d := range l.directives {
    97  		var s string
    98  		switch d := d.(type) {
    99  		case textDirective:
   100  			s = string(d)
   101  		case fgDirective:
   102  			if w != nil {
   103  				w = w.Foreground(tcell.Color(d))
   104  			}
   105  			continue
   106  		case bgDirective:
   107  			if w != nil {
   108  				w = w.Background(tcell.Color(d))
   109  			}
   110  			continue
   111  		default:
   112  			return 0, 0, fmt.Errorf("StringLayout.Render: unexpected directive %T %+v", d, d)
   113  		}
   114  
   115  		// now we know it's a text directive
   116  		tokenizer := NewTokenizer(s)
   117  		for {
   118  			token, err := tokenizer.Next()
   119  			if err != nil {
   120  				if err == io.EOF {
   121  					break
   122  				}
   123  				return 0, 0, errors.Wrapf(err, "StringLayout.Tokenize")
   124  			}
   125  
   126  			if nextX != 0 && nextX+len(token)-1 >= width {
   127  				nextX, nextY = 0, nextY+1
   128  			}
   129  
   130  			for _, r := range token {
   131  				if nextX >= width {
   132  					nextX, nextY = 0, nextY+1
   133  				}
   134  				if nextX+1 > maxWidth {
   135  					maxWidth = nextX + 1
   136  				}
   137  				if nextY >= height {
   138  					return maxWidth, height, nil
   139  				}
   140  
   141  				if r == '\n' {
   142  					if nextX == 0 && w != nil {
   143  						// make sure we take up our space
   144  						w.SetContent(nextX, nextY, r, nil)
   145  					}
   146  					nextX, nextY = 0, nextY+1
   147  					continue
   148  				}
   149  
   150  				if w != nil {
   151  					w.SetContent(nextX, nextY, r, nil)
   152  				}
   153  				nextX = nextX + 1
   154  			}
   155  		}
   156  	}
   157  	return maxWidth, nextY + 1, nil
   158  }