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