forked from mum4k/termdash
/
buffer.go
188 lines (164 loc) · 4.93 KB
/
buffer.go
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package buffer implements a 2-D buffer of cells.
package buffer
import (
"fmt"
"image"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/internal/area"
"github.com/mum4k/termdash/internal/runewidth"
)
// NewCells breaks the provided text into cells and applies the options.
func NewCells(text string, opts ...cell.Option) []*Cell {
var res []*Cell
for _, r := range text {
res = append(res, NewCell(r, opts...))
}
return res
}
// Cell represents a single cell on the terminal.
type Cell struct {
// Rune is the rune stored in the cell.
Rune rune
// Opts are the cell options.
Opts *cell.Options
}
// String implements fmt.Stringer.
func (c *Cell) String() string {
return fmt.Sprintf("{%q}", c.Rune)
}
// NewCell returns a new cell.
func NewCell(r rune, opts ...cell.Option) *Cell {
return &Cell{
Rune: r,
Opts: cell.NewOptions(opts...),
}
}
// Copy returns a copy the cell.
func (c *Cell) Copy() *Cell {
return &Cell{
Rune: c.Rune,
Opts: cell.NewOptions(c.Opts),
}
}
// Apply applies the provided options to the cell.
func (c *Cell) Apply(opts ...cell.Option) {
for _, opt := range opts {
opt.Set(c.Opts)
}
}
// Buffer is a 2-D buffer of cells.
// The axes increase right and down.
// Uninitialized buffer is invalid, use New to create an instance.
// Don't set cells directly, use the SetCell method instead which safely
// handles limits and wide unicode characters.
type Buffer [][]*Cell
// New returns a new Buffer of the provided size.
func New(size image.Point) (Buffer, error) {
if size.X <= 0 {
return nil, fmt.Errorf("invalid buffer width (size.X): %d, must be a positive number", size.X)
}
if size.Y <= 0 {
return nil, fmt.Errorf("invalid buffer height (size.Y): %d, must be a positive number", size.Y)
}
b := make([][]*Cell, size.X)
for col := range b {
b[col] = make([]*Cell, size.Y)
for row := range b[col] {
b[col][row] = NewCell(0)
}
}
return b, nil
}
// SetCell sets the rune of the specified cell in the buffer. Returns the
// number of cells the rune occupies, wide runes can occupy multiple cells when
// printed on the terminal. See http://www.unicode.org/reports/tr11/.
// Use the options to specify which attributes to modify, if an attribute
// option isn't specified, the attribute retains its previous value.
func (b Buffer) SetCell(p image.Point, r rune, opts ...cell.Option) (int, error) {
partial, err := b.IsPartial(p)
if err != nil {
return -1, err
}
if partial {
return -1, fmt.Errorf("cannot set rune %q at point %v, it is a partial cell occupied by a wide rune in the previous cell", r, p)
}
remW, err := b.RemWidth(p)
if err != nil {
return -1, err
}
rw := runewidth.RuneWidth(r)
if rw == 0 {
// Even if the rune is invisible, like the zero-value rune, it still
// occupies at least the target cell.
rw = 1
}
if rw > remW {
return -1, fmt.Errorf("cannot set rune %q of width %d at point %v, only have %d remaining cells at this line", r, rw, p, remW)
}
c := b[p.X][p.Y]
c.Rune = r
c.Apply(opts...)
return rw, nil
}
// IsPartial returns true if the cell at the specified point holds a part of a
// full width rune from a previous cell. See
// http://www.unicode.org/reports/tr11/.
func (b Buffer) IsPartial(p image.Point) (bool, error) {
size := b.Size()
ar, err := area.FromSize(size)
if err != nil {
return false, err
}
if !p.In(ar) {
return false, fmt.Errorf("point %v falls outside of the area %v occupied by the buffer", p, ar)
}
if p.X == 0 && p.Y == 0 {
return false, nil
}
prevP := image.Point{p.X - 1, p.Y}
if prevP.X < 0 {
prevP = image.Point{size.X - 1, p.Y - 1}
}
prevR := b[prevP.X][prevP.Y].Rune
switch rw := runewidth.RuneWidth(prevR); rw {
case 0, 1:
return false, nil
case 2:
return true, nil
default:
return false, fmt.Errorf("buffer cell %v contains rune %q which has an unsupported rune with %d", prevP, prevR, rw)
}
}
// RemWidth returns the remaining width (horizontal row of cells) available
// from and inclusive of the specified point.
func (b Buffer) RemWidth(p image.Point) (int, error) {
size := b.Size()
ar, err := area.FromSize(size)
if err != nil {
return -1, err
}
if !p.In(ar) {
return -1, fmt.Errorf("point %v falls outside of the area %v occupied by the buffer", p, ar)
}
return size.X - p.X, nil
}
// Size returns the size of the buffer.
func (b Buffer) Size() image.Point {
return image.Point{
len(b),
len(b[0]),
}
}