1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
package term
import (
"strings"
"src.elv.sh/pkg/ui"
"src.elv.sh/pkg/wcwidth"
)
// BufferBuilder supports building of Buffer.
type BufferBuilder struct {
Width, Col, Indent int
// EagerWrap controls whether to wrap line as soon as the cursor reaches the
// right edge of the terminal. This is not often desirable as it creates
// unneessary line breaks, but is is useful when echoing the user input.
// will otherwise
EagerWrap bool
// Lines the content of the buffer.
Lines Lines
// Dot is what the user perceives as the cursor.
Dot Pos
}
// NewBufferBuilder makes a new BufferBuilder, initially with one empty line.
func NewBufferBuilder(width int) *BufferBuilder {
return &BufferBuilder{Width: width, Lines: [][]Cell{make([]Cell, 0, width)}}
}
func (bb *BufferBuilder) Cursor() Pos {
return Pos{len(bb.Lines) - 1, bb.Col}
}
// Buffer returns a Buffer built by the BufferBuilder.
func (bb *BufferBuilder) Buffer() *Buffer {
return &Buffer{bb.Width, bb.Lines, bb.Dot}
}
func (bb *BufferBuilder) SetIndent(indent int) *BufferBuilder {
bb.Indent = indent
return bb
}
func (bb *BufferBuilder) SetEagerWrap(v bool) *BufferBuilder {
bb.EagerWrap = v
return bb
}
func (bb *BufferBuilder) setDot(dot Pos) *BufferBuilder {
bb.Dot = dot
return bb
}
func (bb *BufferBuilder) SetDotHere() *BufferBuilder {
return bb.setDot(bb.Cursor())
}
func (bb *BufferBuilder) appendLine() {
bb.Lines = append(bb.Lines, make([]Cell, 0, bb.Width))
bb.Col = 0
}
func (bb *BufferBuilder) appendCell(c Cell) {
n := len(bb.Lines)
bb.Lines[n-1] = append(bb.Lines[n-1], c)
bb.Col += wcwidth.Of(c.Text)
}
// Newline starts a newline.
func (bb *BufferBuilder) Newline() *BufferBuilder {
bb.appendLine()
if bb.Indent > 0 {
for i := 0; i < bb.Indent; i++ {
bb.appendCell(Cell{Text: " "})
}
}
return bb
}
// WriteRuneSGR writes a single rune to a buffer with an SGR style, wrapping the
// line when needed. If the rune is a control character, it will be written
// using the caret notation (like ^X) and gets the additional style of
// styleForControlChar.
func (bb *BufferBuilder) WriteRuneSGR(r rune, style string) *BufferBuilder {
if r == '\n' {
bb.Newline()
return bb
}
c := Cell{string(r), style}
if r < 0x20 || r == 0x7f {
// Always show control characters in reverse video.
if style != "" {
style = style + ";7"
} else {
style = "7"
}
c = Cell{"^" + string(r^0x40), style}
}
if bb.Col+wcwidth.Of(c.Text) > bb.Width {
bb.Newline()
bb.appendCell(c)
} else {
bb.appendCell(c)
if bb.Col == bb.Width && bb.EagerWrap {
bb.Newline()
}
}
return bb
}
// Write is equivalent to calling WriteStyled with ui.T(text, style...).
func (bb *BufferBuilder) Write(text string, ts ...ui.Styling) *BufferBuilder {
return bb.WriteStyled(ui.T(text, ts...))
}
// WriteSpaces writes w spaces with the given styles.
func (bb *BufferBuilder) WriteSpaces(w int, ts ...ui.Styling) *BufferBuilder {
return bb.Write(strings.Repeat(" ", w), ts...)
}
// DotHere is a special argument to MarkLines to mark the position of the dot.
var DotHere = struct{ x struct{} }{}
// MarkLines is like calling WriteStyled with ui.MarkLines(args...), but accepts
// an additional special parameter DotHere to mark the position of the dot.
func (bb *BufferBuilder) MarkLines(args ...any) *BufferBuilder {
for i, arg := range args {
if arg == DotHere {
return bb.WriteStyled(ui.MarkLines(args[:i]...)).
SetDotHere().WriteStyled(ui.MarkLines(args[i+1:]...))
}
}
return bb.WriteStyled(ui.MarkLines(args...))
}
// WriteStringSGR writes a string to a buffer with a SGR style.
func (bb *BufferBuilder) WriteStringSGR(text, style string) *BufferBuilder {
for _, r := range text {
bb.WriteRuneSGR(r, style)
}
return bb
}
// WriteStyled writes a styled text.
func (bb *BufferBuilder) WriteStyled(t ui.Text) *BufferBuilder {
for _, seg := range t {
bb.WriteStringSGR(seg.Text, seg.Style.SGR())
}
return bb
}
func makeSpacing(n int) []Cell {
s := make([]Cell, n)
for i := 0; i < n; i++ {
s[i].Text = " "
}
return s
}
|