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 }