From 984f37245d11464c41feca666f2f3fb67db967a4 Mon Sep 17 00:00:00 2001 From: Jakub Sobon Date: Sun, 24 Feb 2019 16:29:44 -0500 Subject: [PATCH] Factoring private type buffer out of cell. --- cell/cell.go | 172 +------ cell/cell_test.go | 534 +-------------------- cell/color_test.go | 6 +- internal/canvas/buffer/buffer.go | 169 +++++++ internal/canvas/buffer/buffer_test.go | 580 +++++++++++++++++++++++ internal/canvas/canvas.go | 9 +- internal/canvas/canvas_test.go | 175 +++---- internal/canvas/testcanvas/testcanvas.go | 3 +- internal/faketerm/faketerm.go | 11 +- 9 files changed, 876 insertions(+), 783 deletions(-) create mode 100644 internal/canvas/buffer/buffer.go create mode 100644 internal/canvas/buffer/buffer_test.go diff --git a/cell/cell.go b/cell/cell.go index 103a60b..c3eb6df 100644 --- a/cell/cell.go +++ b/cell/cell.go @@ -12,25 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -/* -Package cell implements cell options and attributes. - -A cell is the smallest point on the terminal. -*/ +// Package cell implements cell options and attributes. package cell -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/internal/area" - "github.com/mum4k/termdash/internal/runewidth" -) - // Option is used to provide options for cells on a 2-D terminal. type Option interface { - // set sets the provided option. - set(*Options) + // Set sets the provided option. + Set(*Options) } // Options stores the provided options. @@ -39,8 +27,8 @@ type Options struct { BgColor Color } -// set allows existing options to be passed as an option. -func (o *Options) set(other *Options) { +// Set allows existing options to be passed as an option. +func (o *Options) Set(other *Options) { *other = *o } @@ -48,160 +36,16 @@ func (o *Options) set(other *Options) { func NewOptions(opts ...Option) *Options { o := &Options{} for _, opt := range opts { - opt.set(o) + opt.Set(o) } return o } -// 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 *Options -} - -// Copy returns a copy the cell. -func (c *Cell) Copy() *Cell { - return &Cell{ - Rune: c.Rune, - Opts: NewOptions(c.Opts), - } -} - -// New returns a new cell. -func New(r rune, opts ...Option) *Cell { - return &Cell{ - Rune: r, - Opts: NewOptions(opts...), - } -} - -// Apply applies the provided options to the cell. -func (c *Cell) Apply(opts ...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 NewBuffer 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 - -// 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 ...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 > 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) - } - - cell := b[p.X][p.Y] - cell.Rune = r - cell.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 -} - -// NewBuffer returns a new Buffer of the provided size. -func NewBuffer(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] = New(0) - } - } - return b, nil -} - -// Size returns the size of the buffer. -func (b Buffer) Size() image.Point { - return image.Point{ - len(b), - len(b[0]), - } -} - // option implements Option. type option func(*Options) -// set implements Option.set. -func (co option) set(opts *Options) { +// Set implements Option.set. +func (co option) Set(opts *Options) { co(opts) } diff --git a/cell/cell_test.go b/cell/cell_test.go index 087d1b2..153c5cc 100644 --- a/cell/cell_test.go +++ b/cell/cell_test.go @@ -15,7 +15,6 @@ package cell import ( - "image" "testing" "github.com/kylelemons/godebug/pretty" @@ -60,6 +59,19 @@ func TestNewOptions(t *testing.T) { BgColor: ColorMagenta, }, }, + { + desc: "setting options by passing the options struct", + opts: []Option{ + &Options{ + FgColor: ColorCyan, + BgColor: ColorMagenta, + }, + }, + want: &Options{ + FgColor: ColorCyan, + BgColor: ColorMagenta, + }, + }, } for _, tc := range tests { @@ -71,523 +83,3 @@ func TestNewOptions(t *testing.T) { }) } } - -func TestNew(t *testing.T) { - tests := []struct { - desc string - r rune - opts []Option - want Cell - }{ - { - desc: "creates empty cell with default options", - want: Cell{ - Opts: &Options{}, - }, - }, - { - desc: "cell with the specified rune", - r: 'X', - want: Cell{ - Rune: 'X', - Opts: &Options{}, - }, - }, - { - desc: "cell with options", - r: 'X', - opts: []Option{ - FgColor(ColorCyan), - BgColor(ColorMagenta), - }, - want: Cell{ - Rune: 'X', - Opts: &Options{ - FgColor: ColorCyan, - BgColor: ColorMagenta, - }, - }, - }, - { - desc: "passing full Options overwrites existing", - r: 'X', - opts: []Option{ - &Options{ - FgColor: ColorBlack, - BgColor: ColorBlue, - }, - }, - want: Cell{ - Rune: 'X', - Opts: &Options{ - FgColor: ColorBlack, - BgColor: ColorBlue, - }, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.desc, func(t *testing.T) { - got := New(tc.r, tc.opts...) - if diff := pretty.Compare(tc.want, got); diff != "" { - t.Errorf("New => unexpected diff (-want, +got):\n%s", diff) - } - }) - } -} - -func TestCellApply(t *testing.T) { - tests := []struct { - desc string - cell *Cell - opts []Option - want *Cell - }{ - { - desc: "no options provided", - cell: New(0), - want: New(0), - }, - { - desc: "no change in options", - cell: New(0, FgColor(ColorCyan)), - opts: []Option{ - FgColor(ColorCyan), - }, - want: New(0, FgColor(ColorCyan)), - }, - { - desc: "retains previous values", - cell: New(0, FgColor(ColorCyan)), - opts: []Option{ - BgColor(ColorBlack), - }, - want: New( - 0, - FgColor(ColorCyan), - BgColor(ColorBlack), - ), - }, - } - - for _, tc := range tests { - t.Run(tc.desc, func(t *testing.T) { - got := tc.cell - got.Apply(tc.opts...) - if diff := pretty.Compare(tc.want, got); diff != "" { - t.Errorf("Apply => unexpected diff (-want, +got):\n%s", diff) - } - }) - } -} - -func TestNewBuffer(t *testing.T) { - tests := []struct { - desc string - size image.Point - want Buffer - wantErr bool - }{ - { - desc: "zero buffer is invalid", - wantErr: true, - }, - { - desc: "width cannot be negative", - size: image.Point{-1, 1}, - wantErr: true, - }, - { - desc: "height cannot be negative", - size: image.Point{1, -1}, - wantErr: true, - }, - { - desc: "creates single cell buffer", - size: image.Point{1, 1}, - want: Buffer{ - { - New(0), - }, - }, - }, - { - desc: "creates the buffer", - size: image.Point{2, 3}, - want: Buffer{ - { - New(0), - New(0), - New(0), - }, - { - New(0), - New(0), - New(0), - }, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.desc, func(t *testing.T) { - got, err := NewBuffer(tc.size) - if (err != nil) != tc.wantErr { - t.Errorf("NewBuffer => unexpected error: %v, wantErr: %v", err, tc.wantErr) - } - if err != nil { - return - } - - if diff := pretty.Compare(tc.want, got); diff != "" { - t.Errorf("NewBuffer => unexpected diff (-want, +got):\n%s", diff) - } - }) - } -} - -func TestBufferSize(t *testing.T) { - sizes := []image.Point{ - {1, 1}, - {2, 3}, - } - - for _, size := range sizes { - t.Run("", func(t *testing.T) { - b, err := NewBuffer(size) - if err != nil { - t.Fatalf("NewBuffer => unexpected error: %v", err) - } - - got := b.Size() - if diff := pretty.Compare(size, got); diff != "" { - t.Errorf("Size => unexpected diff (-want, +got):\n%s", diff) - } - }) - } -} - -// mustNewBuffer returns a new Buffer or panics. -func mustNewBuffer(size image.Point) Buffer { - b, err := NewBuffer(size) - if err != nil { - panic(err) - } - return b -} - -func TestSetCell(t *testing.T) { - size := image.Point{3, 3} - tests := []struct { - desc string - buffer Buffer - point image.Point - r rune - opts []Option - wantCells int - want Buffer - wantErr bool - }{ - { - desc: "point falls before the buffer", - buffer: mustNewBuffer(size), - point: image.Point{-1, -1}, - r: 'A', - wantErr: true, - }, - { - desc: "point falls after the buffer", - buffer: mustNewBuffer(size), - point: image.Point{3, 3}, - r: 'A', - wantErr: true, - }, - { - desc: "point falls on cell with partial rune", - buffer: func() Buffer { - b := mustNewBuffer(size) - b[0][0].Rune = '世' - return b - }(), - point: image.Point{1, 0}, - r: 'A', - wantErr: true, - }, - { - desc: "point falls on cell with full-width rune and overwrites with half-width rune", - buffer: func() Buffer { - b := mustNewBuffer(size) - b[0][0].Rune = '世' - return b - }(), - point: image.Point{0, 0}, - r: 'A', - wantCells: 1, - want: func() Buffer { - b := mustNewBuffer(size) - b[0][0].Rune = 'A' - return b - }(), - }, - { - desc: "point falls on cell with full-width rune and overwrites with full-width rune", - buffer: func() Buffer { - b := mustNewBuffer(size) - b[0][0].Rune = '世' - return b - }(), - point: image.Point{0, 0}, - r: '界', - wantCells: 2, - want: func() Buffer { - b := mustNewBuffer(size) - b[0][0].Rune = '界' - return b - }(), - }, - { - desc: "not enough space for a wide rune on the line", - buffer: mustNewBuffer(image.Point{3, 3}), - point: image.Point{2, 0}, - r: '界', - wantErr: true, - }, - { - desc: "sets half-width rune in a cell", - buffer: mustNewBuffer(image.Point{3, 3}), - point: image.Point{1, 1}, - r: 'A', - wantCells: 1, - want: func() Buffer { - b := mustNewBuffer(size) - b[1][1].Rune = 'A' - return b - }(), - }, - { - desc: "sets full-width rune in a cell", - buffer: mustNewBuffer(image.Point{3, 3}), - point: image.Point{1, 2}, - r: '界', - wantCells: 2, - want: func() Buffer { - b := mustNewBuffer(size) - b[1][2].Rune = '界' - return b - }(), - }, - { - desc: "sets cell options", - buffer: mustNewBuffer(image.Point{3, 3}), - point: image.Point{1, 2}, - r: 'A', - opts: []Option{ - FgColor(ColorRed), - BgColor(ColorBlue), - }, - wantCells: 1, - want: func() Buffer { - b := mustNewBuffer(size) - cell := b[1][2] - cell.Rune = 'A' - cell.Opts = NewOptions(FgColor(ColorRed), BgColor(ColorBlue)) - return b - }(), - }, - { - desc: "overwrites only provided options", - buffer: func() Buffer { - b := mustNewBuffer(size) - cell := b[1][2] - cell.Opts = NewOptions(BgColor(ColorBlue)) - return b - }(), - point: image.Point{1, 2}, - r: 'A', - opts: []Option{ - FgColor(ColorRed), - }, - wantCells: 1, - want: func() Buffer { - b := mustNewBuffer(size) - cell := b[1][2] - cell.Rune = 'A' - cell.Opts = NewOptions(FgColor(ColorRed), BgColor(ColorBlue)) - return b - }(), - }, - } - - for _, tc := range tests { - t.Run(tc.desc, func(t *testing.T) { - gotCells, err := tc.buffer.SetCell(tc.point, tc.r, tc.opts...) - if (err != nil) != tc.wantErr { - t.Errorf("SetCell => unexpected error: %v, wantErr: %v", err, tc.wantErr) - } - if err != nil { - return - } - - if gotCells != tc.wantCells { - t.Errorf("SetCell => unexpected cell count, got %d, want %d", gotCells, tc.wantCells) - } - - got := tc.buffer - if diff := pretty.Compare(tc.want, got); diff != "" { - t.Errorf("SetCell=> unexpected buffer, diff (-want, +got):\n%s", diff) - } - }) - } -} - -func TestIsPartial(t *testing.T) { - tests := []struct { - desc string - buffer Buffer - point image.Point - want bool - wantErr bool - }{ - { - desc: "point falls before the buffer", - buffer: mustNewBuffer(image.Point{1, 1}), - point: image.Point{-1, -1}, - wantErr: true, - }, - { - desc: "point falls after the buffer", - buffer: mustNewBuffer(image.Point{1, 1}), - point: image.Point{1, 1}, - wantErr: true, - }, - { - desc: "the first cell cannot be partial", - buffer: mustNewBuffer(image.Point{1, 1}), - point: image.Point{0, 0}, - want: false, - }, - { - desc: "previous cell on the same line contains no rune", - buffer: mustNewBuffer(image.Point{3, 3}), - point: image.Point{1, 0}, - want: false, - }, - { - desc: "previous cell on the same line contains half-width rune", - buffer: func() Buffer { - b := mustNewBuffer(image.Point{3, 3}) - b[0][0].Rune = 'A' - return b - }(), - point: image.Point{1, 0}, - want: false, - }, - { - desc: "previous cell on the same line contains full-width rune", - buffer: func() Buffer { - b := mustNewBuffer(image.Point{3, 3}) - b[0][0].Rune = '世' - return b - }(), - point: image.Point{1, 0}, - want: true, - }, - { - desc: "previous cell on previous line contains no rune", - buffer: mustNewBuffer(image.Point{3, 3}), - point: image.Point{0, 1}, - want: false, - }, - { - desc: "previous cell on previous line contains half-width rune", - buffer: func() Buffer { - b := mustNewBuffer(image.Point{3, 3}) - b[2][0].Rune = 'A' - return b - }(), - point: image.Point{0, 1}, - want: false, - }, - { - desc: "previous cell on previous line contains full-width rune", - buffer: func() Buffer { - b := mustNewBuffer(image.Point{3, 3}) - b[2][0].Rune = '世' - return b - }(), - point: image.Point{0, 1}, - want: true, - }, - } - - for _, tc := range tests { - t.Run(tc.desc, func(t *testing.T) { - got, err := tc.buffer.IsPartial(tc.point) - if (err != nil) != tc.wantErr { - t.Errorf("IsPartial => unexpected error: %v, wantErr: %v", err, tc.wantErr) - } - if err != nil { - return - } - - if got != tc.want { - t.Errorf("IsPartial => got %v, want %v", got, tc.want) - } - }) - } -} - -func TestRemWidth(t *testing.T) { - tests := []struct { - desc string - size image.Point - point image.Point - want int - wantErr bool - }{ - { - desc: "point falls before the buffer", - size: image.Point{1, 1}, - point: image.Point{-1, -1}, - wantErr: true, - }, - { - desc: "point falls after the buffer", - size: image.Point{1, 1}, - point: image.Point{1, 1}, - wantErr: true, - }, - { - desc: "remaining width from the first cell on the line", - size: image.Point{3, 3}, - point: image.Point{0, 1}, - want: 3, - }, - { - desc: "remaining width from the last cell on the line", - size: image.Point{3, 3}, - point: image.Point{2, 2}, - want: 1, - }, - } - - for _, tc := range tests { - t.Run(tc.desc, func(t *testing.T) { - b, err := NewBuffer(tc.size) - if err != nil { - t.Fatalf("NewBuffer => unexpected error: %v", err) - } - got, err := b.RemWidth(tc.point) - if (err != nil) != tc.wantErr { - t.Errorf("RemWidth => unexpected error: %v, wantErr: %v", err, tc.wantErr) - } - if err != nil { - return - } - if got != tc.want { - t.Errorf("RemWidth => got %d, want %d", got, tc.want) - } - }) - } -} diff --git a/cell/color_test.go b/cell/color_test.go index edc68d1..720ea6b 100644 --- a/cell/color_test.go +++ b/cell/color_test.go @@ -14,7 +14,10 @@ package cell -import "testing" +import ( + "fmt" + "testing" +) func TestColorNumber(t *testing.T) { tests := []struct { @@ -46,6 +49,7 @@ func TestColorNumber(t *testing.T) { for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { + t.Logf(fmt.Sprintf("color: %v", tc.want)) got := ColorNumber(tc.number) if got != tc.want { t.Errorf("ColorNumber(%v) => %v, want %v", tc.number, got, tc.want) diff --git a/internal/canvas/buffer/buffer.go b/internal/canvas/buffer/buffer.go new file mode 100644 index 0000000..15d72a8 --- /dev/null +++ b/internal/canvas/buffer/buffer.go @@ -0,0 +1,169 @@ +// 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" +) + +// 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 +} + +// 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 > 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]), + } +} diff --git a/internal/canvas/buffer/buffer_test.go b/internal/canvas/buffer/buffer_test.go new file mode 100644 index 0000000..08956e8 --- /dev/null +++ b/internal/canvas/buffer/buffer_test.go @@ -0,0 +1,580 @@ +// 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 + +import ( + "image" + "testing" + + "github.com/kylelemons/godebug/pretty" + "github.com/mum4k/termdash/cell" +) + +func TestNewCell(t *testing.T) { + tests := []struct { + desc string + r rune + opts []cell.Option + want Cell + }{ + { + desc: "creates empty cell with default options", + want: Cell{ + Opts: &cell.Options{}, + }, + }, + { + desc: "cell with the specified rune", + r: 'X', + want: Cell{ + Rune: 'X', + Opts: &cell.Options{}, + }, + }, + { + desc: "cell with options", + r: 'X', + opts: []cell.Option{ + cell.FgColor(cell.ColorCyan), + cell.BgColor(cell.ColorMagenta), + }, + want: Cell{ + Rune: 'X', + Opts: &cell.Options{ + FgColor: cell.ColorCyan, + BgColor: cell.ColorMagenta, + }, + }, + }, + { + desc: "passing full cell.Options overwrites existing", + r: 'X', + opts: []cell.Option{ + &cell.Options{ + FgColor: cell.ColorBlack, + BgColor: cell.ColorBlue, + }, + }, + want: Cell{ + Rune: 'X', + Opts: &cell.Options{ + FgColor: cell.ColorBlack, + BgColor: cell.ColorBlue, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + got := NewCell(tc.r, tc.opts...) + if diff := pretty.Compare(tc.want, got); diff != "" { + t.Errorf("New => unexpected diff (-want, +got):\n%s", diff) + } + }) + } +} + +func TestCellCopy(t *testing.T) { + tests := []struct { + desc string + cell *Cell + want *Cell + }{ + { + desc: "copies empty cell", + cell: NewCell(0), + want: NewCell(0), + }, + { + desc: "copies cell with a rune", + cell: NewCell(33), + want: NewCell(33), + }, + { + desc: "copies cell with rune and options", + cell: NewCell(42, cell.FgColor(cell.ColorCyan), cell.BgColor(cell.ColorBlack)), + want: NewCell( + 42, + cell.FgColor(cell.ColorCyan), + cell.BgColor(cell.ColorBlack), + ), + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + got := tc.cell.Copy() + if diff := pretty.Compare(tc.want, got); diff != "" { + t.Errorf("Copy => unexpected diff (-want, +got):\n%s", diff) + } + }) + } +} + +func TestCellApply(t *testing.T) { + tests := []struct { + desc string + cell *Cell + opts []cell.Option + want *Cell + }{ + { + desc: "no options provided", + cell: NewCell(0), + want: NewCell(0), + }, + { + desc: "no change in options", + cell: NewCell(0, cell.FgColor(cell.ColorCyan)), + opts: []cell.Option{ + cell.FgColor(cell.ColorCyan), + }, + want: NewCell(0, cell.FgColor(cell.ColorCyan)), + }, + { + desc: "retains previous values", + cell: NewCell(0, cell.FgColor(cell.ColorCyan)), + opts: []cell.Option{ + cell.BgColor(cell.ColorBlack), + }, + want: NewCell( + 0, + cell.FgColor(cell.ColorCyan), + cell.BgColor(cell.ColorBlack), + ), + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + got := tc.cell + got.Apply(tc.opts...) + if diff := pretty.Compare(tc.want, got); diff != "" { + t.Errorf("Apply => unexpected diff (-want, +got):\n%s", diff) + } + }) + } +} + +func TestNew(t *testing.T) { + tests := []struct { + desc string + size image.Point + want Buffer + wantErr bool + }{ + { + desc: "zero buffer is invalid", + wantErr: true, + }, + { + desc: "width cannot be negative", + size: image.Point{-1, 1}, + wantErr: true, + }, + { + desc: "height cannot be negative", + size: image.Point{1, -1}, + wantErr: true, + }, + { + desc: "creates single cell buffer", + size: image.Point{1, 1}, + want: Buffer{ + { + NewCell(0), + }, + }, + }, + { + desc: "creates the buffer", + size: image.Point{2, 3}, + want: Buffer{ + { + NewCell(0), + NewCell(0), + NewCell(0), + }, + { + NewCell(0), + NewCell(0), + NewCell(0), + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + got, err := New(tc.size) + if (err != nil) != tc.wantErr { + t.Errorf("New => unexpected error: %v, wantErr: %v", err, tc.wantErr) + } + if err != nil { + return + } + + if diff := pretty.Compare(tc.want, got); diff != "" { + t.Errorf("New => unexpected diff (-want, +got):\n%s", diff) + } + }) + } +} + +func TestBufferSize(t *testing.T) { + sizes := []image.Point{ + {1, 1}, + {2, 3}, + } + + for _, size := range sizes { + t.Run("", func(t *testing.T) { + b, err := New(size) + if err != nil { + t.Fatalf("New => unexpected error: %v", err) + } + + got := b.Size() + if diff := pretty.Compare(size, got); diff != "" { + t.Errorf("Size => unexpected diff (-want, +got):\n%s", diff) + } + }) + } +} + +// mustNew returns a new Buffer or panics. +func mustNew(size image.Point) Buffer { + b, err := New(size) + if err != nil { + panic(err) + } + return b +} + +func TestSetCell(t *testing.T) { + size := image.Point{3, 3} + tests := []struct { + desc string + buffer Buffer + point image.Point + r rune + opts []cell.Option + wantCells int + want Buffer + wantErr bool + }{ + { + desc: "point falls before the buffer", + buffer: mustNew(size), + point: image.Point{-1, -1}, + r: 'A', + wantErr: true, + }, + { + desc: "point falls after the buffer", + buffer: mustNew(size), + point: image.Point{3, 3}, + r: 'A', + wantErr: true, + }, + { + desc: "point falls on cell with partial rune", + buffer: func() Buffer { + b := mustNew(size) + b[0][0].Rune = '世' + return b + }(), + point: image.Point{1, 0}, + r: 'A', + wantErr: true, + }, + { + desc: "point falls on cell with full-width rune and overwrites with half-width rune", + buffer: func() Buffer { + b := mustNew(size) + b[0][0].Rune = '世' + return b + }(), + point: image.Point{0, 0}, + r: 'A', + wantCells: 1, + want: func() Buffer { + b := mustNew(size) + b[0][0].Rune = 'A' + return b + }(), + }, + { + desc: "point falls on cell with full-width rune and overwrites with full-width rune", + buffer: func() Buffer { + b := mustNew(size) + b[0][0].Rune = '世' + return b + }(), + point: image.Point{0, 0}, + r: '界', + wantCells: 2, + want: func() Buffer { + b := mustNew(size) + b[0][0].Rune = '界' + return b + }(), + }, + { + desc: "not enough space for a wide rune on the line", + buffer: mustNew(image.Point{3, 3}), + point: image.Point{2, 0}, + r: '界', + wantErr: true, + }, + { + desc: "sets half-width rune in a cell", + buffer: mustNew(image.Point{3, 3}), + point: image.Point{1, 1}, + r: 'A', + wantCells: 1, + want: func() Buffer { + b := mustNew(size) + b[1][1].Rune = 'A' + return b + }(), + }, + { + desc: "sets full-width rune in a cell", + buffer: mustNew(image.Point{3, 3}), + point: image.Point{1, 2}, + r: '界', + wantCells: 2, + want: func() Buffer { + b := mustNew(size) + b[1][2].Rune = '界' + return b + }(), + }, + { + desc: "sets cell options", + buffer: mustNew(image.Point{3, 3}), + point: image.Point{1, 2}, + r: 'A', + opts: []cell.Option{ + cell.FgColor(cell.ColorRed), + cell.BgColor(cell.ColorBlue), + }, + wantCells: 1, + want: func() Buffer { + b := mustNew(size) + c := b[1][2] + c.Rune = 'A' + c.Opts = cell.NewOptions(cell.FgColor(cell.ColorRed), cell.BgColor(cell.ColorBlue)) + return b + }(), + }, + { + desc: "overwrites only provided options", + buffer: func() Buffer { + b := mustNew(size) + c := b[1][2] + c.Opts = cell.NewOptions(cell.BgColor(cell.ColorBlue)) + return b + }(), + point: image.Point{1, 2}, + r: 'A', + opts: []cell.Option{ + cell.FgColor(cell.ColorRed), + }, + wantCells: 1, + want: func() Buffer { + b := mustNew(size) + c := b[1][2] + c.Rune = 'A' + c.Opts = cell.NewOptions(cell.FgColor(cell.ColorRed), cell.BgColor(cell.ColorBlue)) + return b + }(), + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + gotCells, err := tc.buffer.SetCell(tc.point, tc.r, tc.opts...) + if (err != nil) != tc.wantErr { + t.Errorf("SetCell => unexpected error: %v, wantErr: %v", err, tc.wantErr) + } + if err != nil { + return + } + + if gotCells != tc.wantCells { + t.Errorf("SetCell => unexpected cell count, got %d, want %d", gotCells, tc.wantCells) + } + + got := tc.buffer + if diff := pretty.Compare(tc.want, got); diff != "" { + t.Errorf("SetCell=> unexpected buffer, diff (-want, +got):\n%s", diff) + } + }) + } +} + +func TestIsPartial(t *testing.T) { + tests := []struct { + desc string + buffer Buffer + point image.Point + want bool + wantErr bool + }{ + { + desc: "point falls before the buffer", + buffer: mustNew(image.Point{1, 1}), + point: image.Point{-1, -1}, + wantErr: true, + }, + { + desc: "point falls after the buffer", + buffer: mustNew(image.Point{1, 1}), + point: image.Point{1, 1}, + wantErr: true, + }, + { + desc: "the first cell cannot be partial", + buffer: mustNew(image.Point{1, 1}), + point: image.Point{0, 0}, + want: false, + }, + { + desc: "previous cell on the same line contains no rune", + buffer: mustNew(image.Point{3, 3}), + point: image.Point{1, 0}, + want: false, + }, + { + desc: "previous cell on the same line contains half-width rune", + buffer: func() Buffer { + b := mustNew(image.Point{3, 3}) + b[0][0].Rune = 'A' + return b + }(), + point: image.Point{1, 0}, + want: false, + }, + { + desc: "previous cell on the same line contains full-width rune", + buffer: func() Buffer { + b := mustNew(image.Point{3, 3}) + b[0][0].Rune = '世' + return b + }(), + point: image.Point{1, 0}, + want: true, + }, + { + desc: "previous cell on previous line contains no rune", + buffer: mustNew(image.Point{3, 3}), + point: image.Point{0, 1}, + want: false, + }, + { + desc: "previous cell on previous line contains half-width rune", + buffer: func() Buffer { + b := mustNew(image.Point{3, 3}) + b[2][0].Rune = 'A' + return b + }(), + point: image.Point{0, 1}, + want: false, + }, + { + desc: "previous cell on previous line contains full-width rune", + buffer: func() Buffer { + b := mustNew(image.Point{3, 3}) + b[2][0].Rune = '世' + return b + }(), + point: image.Point{0, 1}, + want: true, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + got, err := tc.buffer.IsPartial(tc.point) + if (err != nil) != tc.wantErr { + t.Errorf("IsPartial => unexpected error: %v, wantErr: %v", err, tc.wantErr) + } + if err != nil { + return + } + + if got != tc.want { + t.Errorf("IsPartial => got %v, want %v", got, tc.want) + } + }) + } +} + +func TestRemWidth(t *testing.T) { + tests := []struct { + desc string + size image.Point + point image.Point + want int + wantErr bool + }{ + { + desc: "point falls before the buffer", + size: image.Point{1, 1}, + point: image.Point{-1, -1}, + wantErr: true, + }, + { + desc: "point falls after the buffer", + size: image.Point{1, 1}, + point: image.Point{1, 1}, + wantErr: true, + }, + { + desc: "remaining width from the first cell on the line", + size: image.Point{3, 3}, + point: image.Point{0, 1}, + want: 3, + }, + { + desc: "remaining width from the last cell on the line", + size: image.Point{3, 3}, + point: image.Point{2, 2}, + want: 1, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + b, err := New(tc.size) + if err != nil { + t.Fatalf("New => unexpected error: %v", err) + } + got, err := b.RemWidth(tc.point) + if (err != nil) != tc.wantErr { + t.Errorf("RemWidth => unexpected error: %v, wantErr: %v", err, tc.wantErr) + } + if err != nil { + return + } + if got != tc.want { + t.Errorf("RemWidth => got %d, want %d", got, tc.want) + } + }) + } +} diff --git a/internal/canvas/canvas.go b/internal/canvas/canvas.go index f2f131c..cb5aa01 100644 --- a/internal/canvas/canvas.go +++ b/internal/canvas/canvas.go @@ -21,6 +21,7 @@ import ( "github.com/mum4k/termdash/cell" "github.com/mum4k/termdash/internal/area" + "github.com/mum4k/termdash/internal/canvas/buffer" "github.com/mum4k/termdash/internal/runewidth" "github.com/mum4k/termdash/terminal/terminalapi" ) @@ -33,7 +34,7 @@ type Canvas struct { area image.Rectangle // buffer is where the drawing happens. - buffer cell.Buffer + buffer buffer.Buffer } // New returns a new Canvas with a buffer for the provided area. @@ -42,7 +43,7 @@ func New(ar image.Rectangle) (*Canvas, error) { return nil, fmt.Errorf("area cannot start or end on the negative axis, got: %+v", ar) } - b, err := cell.NewBuffer(area.Size(ar)) + b, err := buffer.New(area.Size(ar)) if err != nil { return nil, err } @@ -65,7 +66,7 @@ func (c *Canvas) Area() image.Rectangle { // Clear clears all the content on the canvas. func (c *Canvas) Clear() error { - b, err := cell.NewBuffer(c.Size()) + b, err := buffer.New(c.Size()) if err != nil { return err } @@ -83,7 +84,7 @@ func (c *Canvas) SetCell(p image.Point, r rune, opts ...cell.Option) (int, error } // Cell returns a copy of the specified cell. -func (c *Canvas) Cell(p image.Point) (*cell.Cell, error) { +func (c *Canvas) Cell(p image.Point) (*buffer.Cell, error) { ar, err := area.FromSize(c.Size()) if err != nil { return nil, err diff --git a/internal/canvas/canvas_test.go b/internal/canvas/canvas_test.go index fe0328a..ea130bc 100644 --- a/internal/canvas/canvas_test.go +++ b/internal/canvas/canvas_test.go @@ -21,6 +21,7 @@ import ( "github.com/kylelemons/godebug/pretty" "github.com/mum4k/termdash/cell" "github.com/mum4k/termdash/internal/area" + "github.com/mum4k/termdash/internal/canvas/buffer" "github.com/mum4k/termdash/internal/faketerm" ) @@ -547,7 +548,7 @@ func TestSetCellAndApply(t *testing.T) { point image.Point r rune opts []cell.Option - want cell.Buffer // Expected back buffer in the fake terminal. + want buffer.Buffer // Expected back buffer in the fake terminal. wantCells int wantSetCellErr bool wantApplyErr bool @@ -566,21 +567,21 @@ func TestSetCellAndApply(t *testing.T) { point: image.Point{0, 0}, r: 'X', wantCells: 1, - want: cell.Buffer{ + want: buffer.Buffer{ { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New('X'), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell('X'), + buffer.NewCell(0), }, { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, }, }, @@ -591,21 +592,21 @@ func TestSetCellAndApply(t *testing.T) { point: image.Point{0, 0}, r: '界', wantCells: 2, - want: cell.Buffer{ + want: buffer.Buffer{ { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New('界'), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell('界'), + buffer.NewCell(0), }, { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, }, }, @@ -624,21 +625,21 @@ func TestSetCellAndApply(t *testing.T) { point: image.Point{1, 0}, r: 'X', wantCells: 1, - want: cell.Buffer{ + want: buffer.Buffer{ { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New('X'), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell('X'), + buffer.NewCell(0), }, }, }, @@ -649,21 +650,21 @@ func TestSetCellAndApply(t *testing.T) { point: image.Point{0, 1}, r: 'X', wantCells: 1, - want: cell.Buffer{ + want: buffer.Buffer{ { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New(0), - cell.New('X'), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell('X'), }, { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, }, }, @@ -674,21 +675,21 @@ func TestSetCellAndApply(t *testing.T) { point: image.Point{1, 1}, r: 'Z', wantCells: 1, - want: cell.Buffer{ + want: buffer.Buffer{ { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New(0), - cell.New('Z'), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell('Z'), }, }, }, @@ -702,21 +703,21 @@ func TestSetCellAndApply(t *testing.T) { cell.BgColor(cell.ColorRed), }, wantCells: 1, - want: cell.Buffer{ + want: buffer.Buffer{ { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New(0), - cell.New('A', cell.BgColor(cell.ColorRed)), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell('A', cell.BgColor(cell.ColorRed)), }, }, }, @@ -727,9 +728,9 @@ func TestSetCellAndApply(t *testing.T) { point: image.Point{0, 0}, r: 'A', wantCells: 1, - want: cell.Buffer{ + want: buffer.Buffer{ { - cell.New('A'), + buffer.NewCell('A'), }, }, }, @@ -806,21 +807,21 @@ func TestClear(t *testing.T) { t.Fatalf("Apply => unexpected error: %v", err) } - want := cell.Buffer{ + want := buffer.Buffer{ { - cell.New('A'), - cell.New(0), - cell.New(0), + buffer.NewCell('A'), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New('X'), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell('X'), + buffer.NewCell(0), }, { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, } got := ft.BackBuffer() @@ -837,21 +838,21 @@ func TestClear(t *testing.T) { t.Fatalf("Apply => unexpected error: %v", err) } - want = cell.Buffer{ + want = buffer.Buffer{ { - cell.New('A'), - cell.New(0), - cell.New(0), + buffer.NewCell('A'), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, { - cell.New(0), - cell.New(0), - cell.New(0), + buffer.NewCell(0), + buffer.NewCell(0), + buffer.NewCell(0), }, } @@ -889,9 +890,9 @@ func TestApplyFullWidthRunes(t *testing.T) { t.Fatalf("Apply => unexpected error: %v", err) } - want, err := cell.NewBuffer(area.Size(ar)) + want, err := buffer.New(area.Size(ar)) if err != nil { - t.Fatalf("NewBuffer => unexpected error: %v", err) + t.Fatalf("buffer.New => unexpected error: %v", err) } want[fullP.X][fullP.Y].Rune = '界' want[partP.X][partP.Y].Rune = 'A' @@ -907,7 +908,7 @@ func TestCell(t *testing.T) { desc string cvs func() (*Canvas, error) point image.Point - want *cell.Cell + want *buffer.Cell wantErr bool }{ { @@ -939,7 +940,7 @@ func TestCell(t *testing.T) { return cvs, nil }, point: image.Point{1, 1}, - want: &cell.Cell{ + want: &buffer.Cell{ Rune: 'A', Opts: cell.NewOptions( cell.FgColor(cell.ColorRed), diff --git a/internal/canvas/testcanvas/testcanvas.go b/internal/canvas/testcanvas/testcanvas.go index 6d10cda..b7aadf7 100644 --- a/internal/canvas/testcanvas/testcanvas.go +++ b/internal/canvas/testcanvas/testcanvas.go @@ -21,6 +21,7 @@ import ( "github.com/mum4k/termdash/cell" "github.com/mum4k/termdash/internal/canvas" + "github.com/mum4k/termdash/internal/canvas/buffer" "github.com/mum4k/termdash/internal/faketerm" ) @@ -59,7 +60,7 @@ func MustSetAreaCells(c *canvas.Canvas, cellArea image.Rectangle, r rune, opts . } // MustCell returns the cell or panics. -func MustCell(c *canvas.Canvas, p image.Point) *cell.Cell { +func MustCell(c *canvas.Canvas, p image.Point) *buffer.Cell { cell, err := c.Cell(p) if err != nil { panic(fmt.Sprintf("canvas.Cell => unexpected error: %v", err)) diff --git a/internal/faketerm/faketerm.go b/internal/faketerm/faketerm.go index f8d462d..8617a9a 100644 --- a/internal/faketerm/faketerm.go +++ b/internal/faketerm/faketerm.go @@ -24,6 +24,7 @@ import ( "sync" "github.com/mum4k/termdash/cell" + "github.com/mum4k/termdash/internal/canvas/buffer" "github.com/mum4k/termdash/internal/event/eventqueue" "github.com/mum4k/termdash/terminal/terminalapi" ) @@ -55,7 +56,7 @@ func WithEventQueue(eq *eventqueue.Unbound) Option { // This implementation is thread-safe. type Terminal struct { // buffer holds the terminal cells. - buffer cell.Buffer + buffer buffer.Buffer // events is a queue of input events. events *eventqueue.Unbound @@ -66,7 +67,7 @@ type Terminal struct { // New returns a new fake Terminal. func New(size image.Point, opts ...Option) (*Terminal, error) { - b, err := cell.NewBuffer(size) + b, err := buffer.New(size) if err != nil { return nil, err } @@ -95,7 +96,7 @@ func (t *Terminal) Resize(size image.Point) error { t.mu.Lock() defer t.mu.Unlock() - b, err := cell.NewBuffer(size) + b, err := buffer.New(size) if err != nil { return err } @@ -105,7 +106,7 @@ func (t *Terminal) Resize(size image.Point) error { } // BackBuffer returns the back buffer of the fake terminal. -func (t *Terminal) BackBuffer() cell.Buffer { +func (t *Terminal) BackBuffer() buffer.Buffer { t.mu.Lock() defer t.mu.Unlock() @@ -155,7 +156,7 @@ func (t *Terminal) Clear(opts ...cell.Option) error { t.mu.Lock() defer t.mu.Unlock() - b, err := cell.NewBuffer(t.buffer.Size()) + b, err := buffer.New(t.buffer.Size()) if err != nil { return err }