Skip to content

Commit

Permalink
chore: merge branch 'main' into vt-teatest
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed Nov 9, 2024
2 parents 2db7587 + 317c90d commit 1619466
Show file tree
Hide file tree
Showing 22 changed files with 354 additions and 112 deletions.
44 changes: 40 additions & 4 deletions cellbuf/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ func (b *Buffer) Cell(x, y int) (Cell, bool) {
return b.cells[idx], true
}

// SetCell sets the cell at the given x, y position.
func (b *Buffer) SetCell(x, y int, c Cell) (v bool) {
// Draw sets the cell at the given x, y position.
func (b *Buffer) Draw(x, y int, c Cell) (v bool) {
if b.width == 0 {
return
}
Expand All @@ -55,7 +55,7 @@ func (b *Buffer) SetCell(x, y int, c Cell) (v bool) {
prev := b.cells[idx]
if prev.Width > 1 {
// Writing to the first wide cell
for j := 0; j < prev.Width; j++ {
for j := 0; j < prev.Width && idx+j < len(b.cells); j++ {
newCell := prev
newCell.Content = " "
newCell.Width = 1
Expand All @@ -82,7 +82,7 @@ func (b *Buffer) SetCell(x, y int, c Cell) (v bool) {
// Mark wide cells with emptyCell zero width
// We set the wide cell down below
if c.Width > 1 {
for j := 1; j < c.Width; j++ {
for j := 1; j < c.Width && idx+j < len(b.cells); j++ {
b.cells[idx+j] = emptyCell
}
}
Expand Down Expand Up @@ -116,3 +116,39 @@ func (b *Buffer) Resize(width, height int) {
b.cells = b.cells[:area]
}
}

// Bounds returns the bounds of the buffer.
func (b *Buffer) Bounds() Rectangle {
return Rect(0, 0, b.Width(), b.Height())
}

// Fill fills the buffer with the given cell. If rect is not nil, it fills the
// rectangle with the cell. Otherwise, it fills the whole buffer.
func (b *Buffer) Fill(c Cell, rect *Rectangle) {
Fill(b, c, rect)
}

// Clear clears the buffer with space cells. If rect is not nil, it clears the
// rectangle. Otherwise, it clears the whole buffer.
func (b *Buffer) Clear(rect *Rectangle) {
Clear(b, rect)
}

// Paint writes the given data to the buffer. If rect is not nil, it writes the
// data within the rectangle. Otherwise, it writes the data to the whole
// buffer.
func (b *Buffer) Paint(m Method, data string, rect *Rectangle) []int {
return Paint(b, m, data, rect)
}

// Render returns a string representation of the buffer with ANSI escape
// sequences.
func (b *Buffer) Render(opts ...RenderOption) string {
return Render(b, opts...)
}

// RenderLine returns a string representation of the yth line of the buffer along
// with the width of the line.
func (b *Buffer) RenderLine(n int, opts ...RenderOption) (w int, line string) {
return RenderLine(b, n, opts...)
}
44 changes: 44 additions & 0 deletions cellbuf/geom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cellbuf

import (
"fmt"
"image"
)

// Position represents an x, y position.
type Position image.Point

// String returns a string representation of the position.
func (p Position) String() string {
return image.Point(p).String()
}

// Pos is a shorthand for Position{X: x, Y: y}.
func Pos(x, y int) Position {
return Position{X: x, Y: y}
}

// Rectange represents a rectangle.
type Rectangle struct {
X, Y, Width, Height int
}

// String returns a string representation of the rectangle.
func (r Rectangle) String() string {
return fmt.Sprintf("(%d,%d)-(%d,%d)", r.X, r.Y, r.X+r.Width, r.Y+r.Height)
}

// Bounds returns the rectangle as an image.Rectangle.
func (r Rectangle) Bounds() image.Rectangle {
return image.Rect(r.X, r.Y, r.X+r.Width, r.Y+r.Height)
}

// Contains reports whether the rectangle contains the given point.
func (r Rectangle) Contains(p Position) bool {
return image.Point(p).In(r.Bounds())
}

// Rect is a shorthand for Rectangle.
func Rect(x, y, w, h int) Rectangle {
return Rectangle{X: x, Y: y, Width: w, Height: h}
}
5 changes: 3 additions & 2 deletions cellbuf/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
)
8 changes: 3 additions & 5 deletions cellbuf/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
88 changes: 57 additions & 31 deletions cellbuf/screen.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,57 @@ import (
// attributes and hyperlink.
type Segment = Cell

// Screen represents an interface for a grid of cells that can be written to
// and read from.
// Screen represents a screen grid of cells.
type Screen interface {
// Width returns the width of the grid.
Width() int

// Height returns the height of the grid.
Height() int

// SetCell writes a cell to the grid at the given position. It returns true
// if the cell was written successfully.
SetCell(x, y int, c Cell) bool

// Cell returns the cell at the given position.
Cell(x, y int) (Cell, bool)

// Resize resizes the grid to the given width and height.
Resize(width, height int)
// Draw writes a cell to the grid at the given position. It returns true if
// the cell was written successfully.
Draw(x, y int, c Cell) bool
}

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

// Render returns a string representation of the grid with ANSI escape sequences.
func Render(d Screen) string {
return RenderWithProfile(d, colorprofile.TrueColor)
// RenderOptions represents options for rendering a canvas.
type RenderOptions struct {
// Profile is the color profile to use when rendering the canvas.
Profile colorprofile.Profile
}

// RenderOption is a function that configures a RenderOptions.
type RenderOption func(*RenderOptions)

// WithRenderProfile sets the color profile to use when rendering the canvas.
func WithRenderProfile(p colorprofile.Profile) RenderOption {
return func(o *RenderOptions) {
o.Profile = p
}
}

// RenderWithProfile returns a string representation of the grid with ANSI escape
// sequences converting styles and colors to the given color profile.
func RenderWithProfile(d Screen, p colorprofile.Profile) string {
// Render returns a string representation of the grid with ANSI escape sequences.
func Render(d Screen, opts ...RenderOption) string {
var opt RenderOptions
for _, o := range opts {
o(&opt)
}
var buf bytes.Buffer
height := d.Height()
for y := 0; y < height; y++ {
_, line := RenderLineWithProfile(d, y, p)
_, line := renderLine(d, y, opt)
buf.WriteString(line)
if y < height-1 {
buf.WriteString("\r\n")
Expand All @@ -59,14 +73,15 @@ func RenderWithProfile(d Screen, p colorprofile.Profile) string {

// RenderLine returns a string representation of the yth line of the grid along
// with the width of the line.
func RenderLine(d Screen, n int) (w int, line string) {
return RenderLineWithProfile(d, n, colorprofile.TrueColor)
func RenderLine(d Screen, n int, opts ...RenderOption) (w int, line string) {
var opt RenderOptions
for _, o := range opts {
o(&opt)
}
return renderLine(d, n, opt)
}

// RenderLineWithProfile returns a string representation of the nth line of the
// grid along with the width of the line converting styles and colors to the
// given color profile.
func RenderLineWithProfile(d Screen, n int, p colorprofile.Profile) (w int, line string) {
func renderLine(d Screen, n int, opt RenderOptions) (w int, line string) {
var pen Style
var link Link
var buf bytes.Buffer
Expand All @@ -87,8 +102,8 @@ func RenderLineWithProfile(d Screen, n int, p colorprofile.Profile) (w int, line
for x := 0; x < d.Width(); x++ {
if cell, ok := d.Cell(x, n); ok && cell.Width > 0 {
// Convert the cell's style and link to the given color profile.
cellStyle := cell.Style.Convert(p)
cellLink := cell.Link.Convert(p)
cellStyle := cell.Style.Convert(opt.Profile)
cellLink := cell.Link.Convert(opt.Profile)
if cellStyle.Empty() && !pen.Empty() {
writePending()
buf.WriteString(ansi.ResetStyle) //nolint:errcheck
Expand Down Expand Up @@ -134,15 +149,26 @@ func RenderLineWithProfile(d Screen, n int, p colorprofile.Profile) (w int, line
return w, strings.TrimRight(buf.String(), " ") // Trim trailing spaces
}

// Fill fills the grid with the given cell.
func Fill(d Screen, c Cell) {
for y := 0; y < d.Height(); y++ {
for x := 0; x < d.Width(); x++ {
d.SetCell(x, y, c) //nolint:errcheck
// Fill fills the canvas with the given cell. If rect is not nil, it only fills
// the rectangle. Otherwise, it fills the whole canvas.
func Fill(d Screen, c Cell, rect *Rectangle) {
if rect == nil {
rect = &Rectangle{0, 0, d.Width(), d.Height()}
}

for y := rect.Y; y < rect.Y+rect.Height; y++ {
for x := rect.X; x < rect.X+rect.Width; x += c.Width {
d.Draw(x, y, c) //nolint:errcheck
}
}
}

// Clear clears the canvas with space cells. If rect is not nil, it only clears
// the rectangle. Otherwise, it clears the whole canvas.
func Clear(d Screen, rect *Rectangle) {
Fill(d, spaceCell, rect)
}

// Equal returns whether two grids are equal.
func Equal(a, b Screen) bool {
if a.Width() != b.Width() || a.Height() != b.Height() {
Expand Down
23 changes: 14 additions & 9 deletions cellbuf/screen_write.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import (
// setContent writes the given data to the buffer starting from the first cell.
// It accepts both string and []byte data types.
func setContent(
dis Screen,
d Screen,
data string,
method Method,
rect Rectangle,
) []int {
var cell Cell
var pen Style
var link Link
var x, y int
x, y := rect.X, rect.Y

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

var pendingWidth int

Expand All @@ -52,18 +53,22 @@ func setContent(
}
fallthrough
case 1:
if x >= rect.X+rect.Width || y >= rect.Y+rect.Height {
break
}

cell.Content = seq
cell.Width = width
cell.Style = pen
cell.Link = link

dis.SetCell(x, y, cell) //nolint:errcheck
d.Draw(x, y, cell) //nolint:errcheck

// Advance the cursor and line width
x += cell.Width
if cell.Equal(spaceCell) {
pendingWidth += cell.Width
} else if y < len(linew) {
} else if y := y - rect.Y; y < len(linew) {
linew[y] += cell.Width + pendingWidth
pendingWidth = 0
}
Expand All @@ -84,8 +89,8 @@ func setContent(
}
case ansi.Equal(seq, "\n"):
// Reset the rest of the line
for x < dis.Width() {
dis.SetCell(x, y, spaceCell) //nolint:errcheck
for x < rect.X+rect.Width {
d.Draw(x, y, spaceCell) //nolint:errcheck
x++
}

Expand All @@ -102,8 +107,8 @@ func setContent(
data = data[n:]
}

for x < dis.Width() {
dis.SetCell(x, y, spaceCell) //nolint:errcheck
for x < rect.X+rect.Width {
d.Draw(x, y, spaceCell) //nolint:errcheck
x++
}

Expand Down
5 changes: 2 additions & 3 deletions colors/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ require (
github.com/charmbracelet/x/ansi v0.4.2 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/sys v0.27.0 // indirect
)
9 changes: 2 additions & 7 deletions colors/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
Loading

0 comments on commit 1619466

Please sign in to comment.