github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/cmds/core/elvish/edit/ui/buffer.go (about) 1 package ui 2 3 import ( 4 "strings" 5 6 "github.com/u-root/u-root/cmds/core/elvish/util" 7 ) 8 9 // Cell is an indivisible unit on the screen. It is not necessarily 1 column 10 // wide. 11 type Cell struct { 12 Text string 13 Width byte 14 Style string 15 } 16 17 // Pos is the position within a buffer. 18 type Pos struct { 19 Line, Col int 20 } 21 22 // CellsWidth returns the total width of a Cell slice. 23 func CellsWidth(cs []Cell) int { 24 w := 0 25 for _, c := range cs { 26 w += int(c.Width) 27 } 28 return w 29 } 30 31 // CompareCells returns whether two Cell slices are equal, and when they are 32 // not, the first index at which they differ. 33 func CompareCells(r1, r2 []Cell) (bool, int) { 34 for i, c := range r1 { 35 if i >= len(r2) || c != r2[i] { 36 return false, i 37 } 38 } 39 if len(r1) < len(r2) { 40 return false, len(r1) 41 } 42 return true, 0 43 } 44 45 // Buffer reflects a continuous range of lines on the terminal. 46 // 47 // The Unix terminal API provides only awkward ways of querying the terminal 48 // Buffer, so we keep an internal reflection and do one-way synchronizations 49 // (Buffer -> terminal, and not the other way around). This requires us to 50 // exactly match the terminal's idea of the width of characters (wcwidth) and 51 // where to insert soft carriage returns, so there could be bugs. 52 type Buffer struct { 53 Width, Col, Indent int 54 // EagerWrap controls whether to wrap line as soon as the cursor reaches the 55 // right edge of the terminal. This is not often desirable as it creates 56 // unneessary line breaks, but is is useful when echoing the user input. 57 // will otherwise 58 EagerWrap bool 59 // Lines the content of the buffer. 60 Lines [][]Cell 61 // Dot is what the user perceives as the cursor. 62 Dot Pos 63 } 64 65 // NewBuffer builds a new buffer, with one empty line. 66 func NewBuffer(width int) *Buffer { 67 return &Buffer{Width: width, Lines: [][]Cell{make([]Cell, 0, width)}} 68 } 69 70 func (b *Buffer) SetIndent(indent int) *Buffer { 71 b.Indent = indent 72 return b 73 } 74 75 func (b *Buffer) SetEagerWrap(v bool) *Buffer { 76 b.EagerWrap = v 77 return b 78 } 79 80 func (b *Buffer) SetLines(lines ...[]Cell) *Buffer { 81 b.Lines = lines 82 b.Col = CellsWidth(lines[len(lines)-1]) 83 return b 84 } 85 86 func (b *Buffer) SetDot(dot Pos) *Buffer { 87 b.Dot = dot 88 return b 89 } 90 91 // Cursor returns the current position of the cursor. 92 func (b *Buffer) Cursor() Pos { 93 return Pos{len(b.Lines) - 1, b.Col} 94 } 95 96 // BuffersHeight computes the combined height of a number of buffers. 97 func BuffersHeight(bufs ...*Buffer) (l int) { 98 for _, buf := range bufs { 99 if buf != nil { 100 l += len(buf.Lines) 101 } 102 } 103 return 104 } 105 106 // Low level buffer mutations. 107 108 func (b *Buffer) appendLine() { 109 b.Lines = append(b.Lines, make([]Cell, 0, b.Width)) 110 b.Col = 0 111 } 112 113 func (b *Buffer) appendCell(c Cell) { 114 n := len(b.Lines) 115 b.Lines[n-1] = append(b.Lines[n-1], c) 116 b.Col += int(c.Width) 117 } 118 119 // High-level buffer mutations. 120 121 // Newline starts a newline. 122 func (b *Buffer) Newline() { 123 b.appendLine() 124 125 if b.Indent > 0 { 126 for i := 0; i < b.Indent; i++ { 127 b.appendCell(Cell{Text: " ", Width: 1}) 128 } 129 } 130 } 131 132 var styleForControlChar = Styles{"inverse"} 133 134 // Write writes a single rune to a buffer, wrapping the line when needed. If the 135 // rune is a control character, it will be written using the caret notation 136 // (like ^X) and gets the additional style of styleForControlChar. 137 func (b *Buffer) Write(r rune, style string) { 138 if r == '\n' { 139 b.Newline() 140 return 141 } 142 wd := util.Wcwidth(r) 143 c := Cell{string(r), byte(wd), style} 144 if r < 0x20 || r == 0x7f { 145 wd = 2 146 if style != "" { 147 style = style + ";" + styleForControlChar.String() 148 } else { 149 style = styleForControlChar.String() 150 } 151 c = Cell{"^" + string(r^0x40), 2, style} 152 } 153 154 if b.Col+wd > b.Width { 155 b.Newline() 156 b.appendCell(c) 157 } else { 158 b.appendCell(c) 159 if b.Col == b.Width && b.EagerWrap { 160 b.Newline() 161 } 162 } 163 } 164 165 // WriteString writes a string to a buffer, with one style. 166 func (b *Buffer) WriteString(text, style string) { 167 for _, r := range text { 168 b.Write(r, style) 169 } 170 } 171 172 // WriteSpaces writes w spaces. 173 func (b *Buffer) WriteSpaces(w int, style string) { 174 b.WriteString(strings.Repeat(" ", w), style) 175 } 176 177 // WriteStyleds writes a slice of styled structs. 178 func (b *Buffer) WriteStyleds(ss []*Styled) { 179 for _, s := range ss { 180 b.WriteString(s.Text, s.Styles.String()) 181 } 182 } 183 184 // TrimToLines trims a buffer to the lines [low, high). 185 func (b *Buffer) TrimToLines(low, high int) { 186 for i := 0; i < low; i++ { 187 b.Lines[i] = nil 188 } 189 for i := high; i < len(b.Lines); i++ { 190 b.Lines[i] = nil 191 } 192 b.Lines = b.Lines[low:high] 193 b.Dot.Line -= low 194 if b.Dot.Line < 0 { 195 b.Dot.Line = 0 196 } 197 } 198 199 // Extend adds all lines from b2 to the bottom of this buffer. If moveDot is 200 // true, the dot is updated to match the dot of b2. 201 func (b *Buffer) Extend(b2 *Buffer, moveDot bool) { 202 if b2 != nil && b2.Lines != nil { 203 if moveDot { 204 b.Dot.Line = b2.Dot.Line + len(b.Lines) 205 b.Dot.Col = b2.Dot.Col 206 } 207 b.Lines = append(b.Lines, b2.Lines...) 208 b.Col = b2.Col 209 } 210 } 211 212 // ExtendRight extends b to the right. It pads each line in b to be at least of 213 // width w and appends the corresponding line in b2 to it, making new lines in b 214 // when b2 has more lines than b. 215 // BUG(xiaq): after calling ExtendRight, the widths of some lines can exceed 216 // b.width. 217 func (b *Buffer) ExtendRight(b2 *Buffer, w int) { 218 i := 0 219 for ; i < len(b.Lines) && i < len(b2.Lines); i++ { 220 if w0 := CellsWidth(b.Lines[i]); w0 < w { 221 b.Lines[i] = append(b.Lines[i], makeSpacing(w-w0)...) 222 } 223 b.Lines[i] = append(b.Lines[i], b2.Lines[i]...) 224 } 225 for ; i < len(b2.Lines); i++ { 226 row := append(makeSpacing(w), b2.Lines[i]...) 227 b.Lines = append(b.Lines, row) 228 } 229 b.Col = CellsWidth(b.Lines[len(b.Lines)-1]) 230 } 231 232 func makeSpacing(n int) []Cell { 233 s := make([]Cell, n) 234 for i := 0; i < n; i++ { 235 s[i].Text = " " 236 s[i].Width = 1 237 } 238 return s 239 }