Skip to content

Commit 1619466

Browse files
committed
chore: merge branch 'main' into vt-teatest
2 parents 2db7587 + 317c90d commit 1619466

File tree

22 files changed

+354
-112
lines changed

22 files changed

+354
-112
lines changed

cellbuf/buffer.go

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ func (b *Buffer) Cell(x, y int) (Cell, bool) {
3535
return b.cells[idx], true
3636
}
3737

38-
// SetCell sets the cell at the given x, y position.
39-
func (b *Buffer) SetCell(x, y int, c Cell) (v bool) {
38+
// Draw sets the cell at the given x, y position.
39+
func (b *Buffer) Draw(x, y int, c Cell) (v bool) {
4040
if b.width == 0 {
4141
return
4242
}
@@ -55,7 +55,7 @@ func (b *Buffer) SetCell(x, y int, c Cell) (v bool) {
5555
prev := b.cells[idx]
5656
if prev.Width > 1 {
5757
// Writing to the first wide cell
58-
for j := 0; j < prev.Width; j++ {
58+
for j := 0; j < prev.Width && idx+j < len(b.cells); j++ {
5959
newCell := prev
6060
newCell.Content = " "
6161
newCell.Width = 1
@@ -82,7 +82,7 @@ func (b *Buffer) SetCell(x, y int, c Cell) (v bool) {
8282
// Mark wide cells with emptyCell zero width
8383
// We set the wide cell down below
8484
if c.Width > 1 {
85-
for j := 1; j < c.Width; j++ {
85+
for j := 1; j < c.Width && idx+j < len(b.cells); j++ {
8686
b.cells[idx+j] = emptyCell
8787
}
8888
}
@@ -116,3 +116,39 @@ func (b *Buffer) Resize(width, height int) {
116116
b.cells = b.cells[:area]
117117
}
118118
}
119+
120+
// Bounds returns the bounds of the buffer.
121+
func (b *Buffer) Bounds() Rectangle {
122+
return Rect(0, 0, b.Width(), b.Height())
123+
}
124+
125+
// Fill fills the buffer with the given cell. If rect is not nil, it fills the
126+
// rectangle with the cell. Otherwise, it fills the whole buffer.
127+
func (b *Buffer) Fill(c Cell, rect *Rectangle) {
128+
Fill(b, c, rect)
129+
}
130+
131+
// Clear clears the buffer with space cells. If rect is not nil, it clears the
132+
// rectangle. Otherwise, it clears the whole buffer.
133+
func (b *Buffer) Clear(rect *Rectangle) {
134+
Clear(b, rect)
135+
}
136+
137+
// Paint writes the given data to the buffer. If rect is not nil, it writes the
138+
// data within the rectangle. Otherwise, it writes the data to the whole
139+
// buffer.
140+
func (b *Buffer) Paint(m Method, data string, rect *Rectangle) []int {
141+
return Paint(b, m, data, rect)
142+
}
143+
144+
// Render returns a string representation of the buffer with ANSI escape
145+
// sequences.
146+
func (b *Buffer) Render(opts ...RenderOption) string {
147+
return Render(b, opts...)
148+
}
149+
150+
// RenderLine returns a string representation of the yth line of the buffer along
151+
// with the width of the line.
152+
func (b *Buffer) RenderLine(n int, opts ...RenderOption) (w int, line string) {
153+
return RenderLine(b, n, opts...)
154+
}

cellbuf/geom.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package cellbuf
2+
3+
import (
4+
"fmt"
5+
"image"
6+
)
7+
8+
// Position represents an x, y position.
9+
type Position image.Point
10+
11+
// String returns a string representation of the position.
12+
func (p Position) String() string {
13+
return image.Point(p).String()
14+
}
15+
16+
// Pos is a shorthand for Position{X: x, Y: y}.
17+
func Pos(x, y int) Position {
18+
return Position{X: x, Y: y}
19+
}
20+
21+
// Rectange represents a rectangle.
22+
type Rectangle struct {
23+
X, Y, Width, Height int
24+
}
25+
26+
// String returns a string representation of the rectangle.
27+
func (r Rectangle) String() string {
28+
return fmt.Sprintf("(%d,%d)-(%d,%d)", r.X, r.Y, r.X+r.Width, r.Y+r.Height)
29+
}
30+
31+
// Bounds returns the rectangle as an image.Rectangle.
32+
func (r Rectangle) Bounds() image.Rectangle {
33+
return image.Rect(r.X, r.Y, r.X+r.Width, r.Y+r.Height)
34+
}
35+
36+
// Contains reports whether the rectangle contains the given point.
37+
func (r Rectangle) Contains(p Position) bool {
38+
return image.Point(p).In(r.Bounds())
39+
}
40+
41+
// Rect is a shorthand for Rectangle.
42+
func Rect(x, y, w, h int) Rectangle {
43+
return Rectangle{X: x, Y: y, Width: w, Height: h}
44+
}

cellbuf/go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require (
1313
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
1414
github.com/rivo/uniseg v0.4.7 // indirect
1515
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
16-
golang.org/x/sys v0.26.0 // indirect
17-
golang.org/x/text v0.19.0 // indirect
16+
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
17+
golang.org/x/sys v0.27.0 // indirect
18+
golang.org/x/text v0.20.0 // indirect
1819
)

cellbuf/go.sum

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
1212
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
1313
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
1414
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
15-
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
16-
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
17-
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
18-
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
19-
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
15+
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
16+
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
17+
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=

cellbuf/screen.go

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,57 @@ import (
1212
// attributes and hyperlink.
1313
type Segment = Cell
1414

15-
// Screen represents an interface for a grid of cells that can be written to
16-
// and read from.
15+
// Screen represents a screen grid of cells.
1716
type Screen interface {
1817
// Width returns the width of the grid.
1918
Width() int
2019

2120
// Height returns the height of the grid.
2221
Height() int
2322

24-
// SetCell writes a cell to the grid at the given position. It returns true
25-
// if the cell was written successfully.
26-
SetCell(x, y int, c Cell) bool
27-
2823
// Cell returns the cell at the given position.
2924
Cell(x, y int) (Cell, bool)
3025

31-
// Resize resizes the grid to the given width and height.
32-
Resize(width, height int)
26+
// Draw writes a cell to the grid at the given position. It returns true if
27+
// the cell was written successfully.
28+
Draw(x, y int, c Cell) bool
3329
}
3430

35-
// SetContent writes the given data to the grid starting from the first cell.
36-
func SetContent(d Screen, m Method, content string) []int {
37-
return setContent(d, content, m)
31+
// Paint writes the given data to the canvas. If rect is not nil, it only
32+
// writes to the rectangle. Otherwise, it writes to the whole canvas.
33+
func Paint(d Screen, m Method, content string, rect *Rectangle) []int {
34+
if rect == nil {
35+
rect = &Rectangle{0, 0, d.Width(), d.Height()}
36+
}
37+
return setContent(d, content, m, *rect)
3838
}
3939

40-
// Render returns a string representation of the grid with ANSI escape sequences.
41-
func Render(d Screen) string {
42-
return RenderWithProfile(d, colorprofile.TrueColor)
40+
// RenderOptions represents options for rendering a canvas.
41+
type RenderOptions struct {
42+
// Profile is the color profile to use when rendering the canvas.
43+
Profile colorprofile.Profile
44+
}
45+
46+
// RenderOption is a function that configures a RenderOptions.
47+
type RenderOption func(*RenderOptions)
48+
49+
// WithRenderProfile sets the color profile to use when rendering the canvas.
50+
func WithRenderProfile(p colorprofile.Profile) RenderOption {
51+
return func(o *RenderOptions) {
52+
o.Profile = p
53+
}
4354
}
4455

45-
// RenderWithProfile returns a string representation of the grid with ANSI escape
46-
// sequences converting styles and colors to the given color profile.
47-
func RenderWithProfile(d Screen, p colorprofile.Profile) string {
56+
// Render returns a string representation of the grid with ANSI escape sequences.
57+
func Render(d Screen, opts ...RenderOption) string {
58+
var opt RenderOptions
59+
for _, o := range opts {
60+
o(&opt)
61+
}
4862
var buf bytes.Buffer
4963
height := d.Height()
5064
for y := 0; y < height; y++ {
51-
_, line := RenderLineWithProfile(d, y, p)
65+
_, line := renderLine(d, y, opt)
5266
buf.WriteString(line)
5367
if y < height-1 {
5468
buf.WriteString("\r\n")
@@ -59,14 +73,15 @@ func RenderWithProfile(d Screen, p colorprofile.Profile) string {
5973

6074
// RenderLine returns a string representation of the yth line of the grid along
6175
// with the width of the line.
62-
func RenderLine(d Screen, n int) (w int, line string) {
63-
return RenderLineWithProfile(d, n, colorprofile.TrueColor)
76+
func RenderLine(d Screen, n int, opts ...RenderOption) (w int, line string) {
77+
var opt RenderOptions
78+
for _, o := range opts {
79+
o(&opt)
80+
}
81+
return renderLine(d, n, opt)
6482
}
6583

66-
// RenderLineWithProfile returns a string representation of the nth line of the
67-
// grid along with the width of the line converting styles and colors to the
68-
// given color profile.
69-
func RenderLineWithProfile(d Screen, n int, p colorprofile.Profile) (w int, line string) {
84+
func renderLine(d Screen, n int, opt RenderOptions) (w int, line string) {
7085
var pen Style
7186
var link Link
7287
var buf bytes.Buffer
@@ -87,8 +102,8 @@ func RenderLineWithProfile(d Screen, n int, p colorprofile.Profile) (w int, line
87102
for x := 0; x < d.Width(); x++ {
88103
if cell, ok := d.Cell(x, n); ok && cell.Width > 0 {
89104
// Convert the cell's style and link to the given color profile.
90-
cellStyle := cell.Style.Convert(p)
91-
cellLink := cell.Link.Convert(p)
105+
cellStyle := cell.Style.Convert(opt.Profile)
106+
cellLink := cell.Link.Convert(opt.Profile)
92107
if cellStyle.Empty() && !pen.Empty() {
93108
writePending()
94109
buf.WriteString(ansi.ResetStyle) //nolint:errcheck
@@ -134,15 +149,26 @@ func RenderLineWithProfile(d Screen, n int, p colorprofile.Profile) (w int, line
134149
return w, strings.TrimRight(buf.String(), " ") // Trim trailing spaces
135150
}
136151

137-
// Fill fills the grid with the given cell.
138-
func Fill(d Screen, c Cell) {
139-
for y := 0; y < d.Height(); y++ {
140-
for x := 0; x < d.Width(); x++ {
141-
d.SetCell(x, y, c) //nolint:errcheck
152+
// Fill fills the canvas with the given cell. If rect is not nil, it only fills
153+
// the rectangle. Otherwise, it fills the whole canvas.
154+
func Fill(d Screen, c Cell, rect *Rectangle) {
155+
if rect == nil {
156+
rect = &Rectangle{0, 0, d.Width(), d.Height()}
157+
}
158+
159+
for y := rect.Y; y < rect.Y+rect.Height; y++ {
160+
for x := rect.X; x < rect.X+rect.Width; x += c.Width {
161+
d.Draw(x, y, c) //nolint:errcheck
142162
}
143163
}
144164
}
145165

166+
// Clear clears the canvas with space cells. If rect is not nil, it only clears
167+
// the rectangle. Otherwise, it clears the whole canvas.
168+
func Clear(d Screen, rect *Rectangle) {
169+
Fill(d, spaceCell, rect)
170+
}
171+
146172
// Equal returns whether two grids are equal.
147173
func Equal(a, b Screen) bool {
148174
if a.Width() != b.Width() || a.Height() != b.Height() {

cellbuf/screen_write.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ import (
1212
// setContent writes the given data to the buffer starting from the first cell.
1313
// It accepts both string and []byte data types.
1414
func setContent(
15-
dis Screen,
15+
d Screen,
1616
data string,
1717
method Method,
18+
rect Rectangle,
1819
) []int {
1920
var cell Cell
2021
var pen Style
2122
var link Link
22-
var x, y int
23+
x, y := rect.X, rect.Y
2324

2425
p := ansi.GetParser()
2526
defer ansi.PutParser(p)
@@ -28,7 +29,7 @@ func setContent(
2829
// linew is a slice of line widths. We use this to keep track of the
2930
// written widths of each line. We use this information later to optimize
3031
// rendering of the buffer.
31-
linew := make([]int, dis.Height())
32+
linew := make([]int, rect.Height)
3233

3334
var pendingWidth int
3435

@@ -52,18 +53,22 @@ func setContent(
5253
}
5354
fallthrough
5455
case 1:
56+
if x >= rect.X+rect.Width || y >= rect.Y+rect.Height {
57+
break
58+
}
59+
5560
cell.Content = seq
5661
cell.Width = width
5762
cell.Style = pen
5863
cell.Link = link
5964

60-
dis.SetCell(x, y, cell) //nolint:errcheck
65+
d.Draw(x, y, cell) //nolint:errcheck
6166

6267
// Advance the cursor and line width
6368
x += cell.Width
6469
if cell.Equal(spaceCell) {
6570
pendingWidth += cell.Width
66-
} else if y < len(linew) {
71+
} else if y := y - rect.Y; y < len(linew) {
6772
linew[y] += cell.Width + pendingWidth
6873
pendingWidth = 0
6974
}
@@ -84,8 +89,8 @@ func setContent(
8489
}
8590
case ansi.Equal(seq, "\n"):
8691
// Reset the rest of the line
87-
for x < dis.Width() {
88-
dis.SetCell(x, y, spaceCell) //nolint:errcheck
92+
for x < rect.X+rect.Width {
93+
d.Draw(x, y, spaceCell) //nolint:errcheck
8994
x++
9095
}
9196

@@ -102,8 +107,8 @@ func setContent(
102107
data = data[n:]
103108
}
104109

105-
for x < dis.Width() {
106-
dis.SetCell(x, y, spaceCell) //nolint:errcheck
110+
for x < rect.X+rect.Width {
111+
d.Draw(x, y, spaceCell) //nolint:errcheck
107112
x++
108113
}
109114

colors/go.mod

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ require (
99
github.com/charmbracelet/x/ansi v0.4.2 // indirect
1010
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
1111
github.com/mattn/go-isatty v0.0.20 // indirect
12-
github.com/mattn/go-runewidth v0.0.16 // indirect
13-
github.com/muesli/termenv v0.15.2 // indirect
12+
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
1413
github.com/rivo/uniseg v0.4.7 // indirect
15-
golang.org/x/sys v0.26.0 // indirect
14+
golang.org/x/sys v0.27.0 // indirect
1615
)

colors/go.sum

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
88
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
99
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
1010
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
11-
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
12-
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
13-
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
14-
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
15-
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
11+
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
1612
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
1713
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
1814
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
19-
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
20-
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
15+
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=

0 commit comments

Comments
 (0)