mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-05-22 10:15:43 +02:00
677 lines
17 KiB
Go
677 lines
17 KiB
Go
// Copyright 2026 The TCell Authors
|
|
//
|
|
// 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 vt
|
|
|
|
import (
|
|
"slices"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gdamore/tcell/v3/color"
|
|
"github.com/gdamore/tcell/v3/tty"
|
|
)
|
|
|
|
// mockTerm implements MockTerm.
|
|
type mockTerm struct {
|
|
mb MockBackend
|
|
em Emulator
|
|
ks *KeyboardState
|
|
}
|
|
|
|
// Stop the terminal.
|
|
func (mt *mockTerm) Stop() error {
|
|
return mt.em.Stop()
|
|
}
|
|
|
|
// Start the terminal.
|
|
func (mt *mockTerm) Start() error {
|
|
return mt.em.Start()
|
|
}
|
|
|
|
// Drain all output from the terminal, ensuring
|
|
// any queued commands are processed.
|
|
func (mt *mockTerm) Drain() error {
|
|
return mt.em.Drain()
|
|
}
|
|
|
|
// Read data from the terminal. This is called by a terminal
|
|
// application (e.g. via tcell Tty.) Read data will include
|
|
// key strokes, mouse events, and responses to terminal queries.
|
|
func (mt *mockTerm) Read(data []byte) (int, error) {
|
|
return mt.em.Read(data)
|
|
}
|
|
|
|
// Write data to the terminal, typically either commands or data
|
|
// that should be displayed on the virtual screen.
|
|
func (mt *mockTerm) Write(b []byte) (n int, err error) {
|
|
return mt.em.Write(b)
|
|
}
|
|
|
|
// WindowSize obtains the dimensions of the window.
|
|
func (mt *mockTerm) WindowSize() (tty.WindowSize, error) {
|
|
sz := mt.mb.GetSize()
|
|
// No pixel sizes for now
|
|
return tty.WindowSize{Width: int(sz.X), Height: int(sz.Y)}, nil
|
|
}
|
|
|
|
// NotifyResize registers a channel to be signaled when a resize has occurred.
|
|
// In real terminal emulators this would be posted (non-blocking) by a signal handler.
|
|
func (mt *mockTerm) NotifyResize(resizeq chan<- bool) {
|
|
if rs, ok := mt.mb.(Resizer); ok {
|
|
rs.NotifyResize(resizeq)
|
|
}
|
|
}
|
|
|
|
// Close closes the terminal, after which it should no longer be used. Stop is implied.
|
|
func (mt *mockTerm) Close() error {
|
|
return mt.Stop()
|
|
}
|
|
|
|
// Pos returns the cursor position.
|
|
func (mt *mockTerm) Pos() Coord {
|
|
return mt.mb.GetPosition()
|
|
}
|
|
|
|
// GetCell returns the contents of the cell at the given coordinates, or a zero value
|
|
// if the coordinates are out of range.
|
|
func (mt *mockTerm) GetCell(pos Coord) Cell {
|
|
return mt.mb.GetCell(pos)
|
|
}
|
|
|
|
// Bells counts the number of times the bell has rung.
|
|
func (mt *mockTerm) Bells() int {
|
|
return mt.mb.Bells()
|
|
}
|
|
|
|
// KeyEvent is used to inject a key event. Call this to inject
|
|
// a synthetic, fully specified key event. Most uses should just use
|
|
// the KeyPress, KeyRelease, or even simpler KeyTap APIs.
|
|
func (mt *mockTerm) KeyEvent(ev KeyEvent) {
|
|
mt.em.KeyEvent(ev)
|
|
if ev.Key == KeyEsc {
|
|
// Inject a delay to simulate human typing.
|
|
// Necessary to disambiguate Escape from other sequences.
|
|
time.Sleep(time.Millisecond * 150)
|
|
}
|
|
}
|
|
|
|
// KeyPress implements MockTerm.KeyPress.
|
|
func (mt *mockTerm) KeyPress(k Key) {
|
|
if event := mt.ks.Pressed(k); event != nil {
|
|
mt.KeyEvent(*event)
|
|
}
|
|
}
|
|
|
|
// KeyRelease implements MockTerm.KeyRelease.
|
|
func (mt *mockTerm) KeyRelease(k Key) {
|
|
if event := mt.ks.Released(k); event != nil {
|
|
mt.KeyEvent(*event)
|
|
}
|
|
}
|
|
|
|
// KeyTap implements MockTerm.KeyTap.
|
|
func (mt *mockTerm) KeyTap(keys ...Key) {
|
|
for _, k := range keys {
|
|
mt.KeyPress(k)
|
|
}
|
|
for _, k := range slices.Backward(keys) {
|
|
mt.KeyRelease(k)
|
|
}
|
|
}
|
|
|
|
// SetRepeat sets the repeat interval for the keyboard.
|
|
// Set the interval to zero to disable repeat.
|
|
func (mt *mockTerm) SetRepeat(delay, interval time.Duration) {
|
|
mt.ks.SetRepeat(delay, interval)
|
|
}
|
|
|
|
// MouseEvent implements MockTerm.MouseEvent.
|
|
func (mt *mockTerm) MouseEvent(ev MouseEvent) {
|
|
mt.em.MouseEvent(ev)
|
|
}
|
|
|
|
// FocusEvent implements MockTerm.FocusEvent.
|
|
func (mt *mockTerm) FocusEvent(focused bool) {
|
|
mt.em.FocusEvent(focused)
|
|
}
|
|
|
|
// GetTitle returns the current window title.
|
|
func (mt *mockTerm) GetTitle() string {
|
|
return mt.mb.GetTitle()
|
|
}
|
|
|
|
// SetSize is used to change the terminal size.
|
|
func (mt *mockTerm) SetSize(size Coord) {
|
|
mt.mb.SetSize(size)
|
|
mt.em.ResizeEvent(size)
|
|
}
|
|
|
|
// Backend returns the backend for testing.
|
|
func (mt *mockTerm) Backend() MockBackend {
|
|
return mt.mb
|
|
}
|
|
|
|
// SendRaw is used to inject raw bytes to the read stream of the app.
|
|
// Use this for fuzz testing.
|
|
func (mt *mockTerm) SendRaw(data []byte) {
|
|
mt.em.SendRaw(data)
|
|
}
|
|
|
|
// SetLayout sets the keyboard layout.
|
|
func (mt *mockTerm) SetLayout(km *Layout) {
|
|
mt.ks.SetLayout(km)
|
|
}
|
|
|
|
// MockTerm is a mock terminal (emulator). It can be used to
|
|
// test the emulator itself, or to test applications (or tcell) that
|
|
// uses the terminal. It also implements the Tty interface used
|
|
// by tcell itself.
|
|
type MockTerm interface {
|
|
tty.Tty
|
|
|
|
// Pos reports the current cursor position.
|
|
Pos() Coord
|
|
|
|
// GetCell returns the cell at the given coordinates.
|
|
// The coordinates must be valid.
|
|
GetCell(Coord) Cell
|
|
|
|
// Bells returns the number of times the bell has been rung.
|
|
Bells() int
|
|
|
|
// Inject a keyboard event - this is a full event, and bypasses
|
|
// the layout and keyboard state processor.
|
|
KeyEvent(KeyEvent)
|
|
|
|
// Inject a key press.
|
|
KeyPress(Key)
|
|
|
|
// Inject a key release.
|
|
KeyRelease(Key)
|
|
|
|
// SetRepeat configures keyboard repeating. Repeat keystrokes
|
|
// will be assumed after the key has been held for at least delay,
|
|
// with new keys added each interval.
|
|
SetRepeat(delay, interval time.Duration)
|
|
|
|
// Inject one or more key press and releases.
|
|
// The keys are pressed in the order, and released in reverse order.
|
|
// Thus modifiers should be listed first. This should not be used
|
|
// to simulate typing a sequence (e.g. a word), but if you wanted to
|
|
// test say N-Key rollover you could do that here.
|
|
KeyTap(...Key)
|
|
|
|
// Inject a mouse event.
|
|
MouseEvent(MouseEvent)
|
|
|
|
// Inject a focus event.
|
|
FocusEvent(bool)
|
|
|
|
// GetTitle obtains the current window title.
|
|
GetTitle() string
|
|
|
|
// SetSize is used to resize the terminal.
|
|
SetSize(Coord)
|
|
|
|
// SendRaw is used to send raw data to the application.
|
|
// This is mostly intended to facilitate fuzz testing the application.
|
|
SendRaw([]byte)
|
|
|
|
// Backend returns the backend (used for testing).
|
|
Backend() MockBackend
|
|
|
|
// SetLayout sets the keyboard layout to use.
|
|
// If not specified, a US standard ANSI keyboard will be assumed.
|
|
SetLayout(*Layout)
|
|
}
|
|
|
|
type noMockBlit struct {
|
|
MockBackend
|
|
Blit struct{} // prevents use as Blitter
|
|
}
|
|
|
|
// NewMockTerm gives a mock terminal emulator.
|
|
func NewMockTerm(opts ...MockOpt) MockTerm {
|
|
mt := &mockTerm{}
|
|
mt.mb = NewMockBackend(opts...)
|
|
var be MockBackend = mt.mb
|
|
for _, o := range opts {
|
|
switch o.(type) {
|
|
case MockOptNoBlit:
|
|
be = &noMockBlit{be, struct{}{}}
|
|
}
|
|
}
|
|
mt.em = NewEmulator(be)
|
|
mt.em.SetId("TCellMock", "1.0")
|
|
mt.ks = &KeyboardState{}
|
|
return mt
|
|
}
|
|
|
|
// MockBackend provides additional mock-specific capabilities on top of Backend.
|
|
// This is meant to facilitate test cases
|
|
type MockBackend interface {
|
|
Backend
|
|
|
|
// GetCell returns the cell at the given position, or an empty cell if the
|
|
// position is out of the bounds of the window.
|
|
GetCell(Coord) Cell
|
|
|
|
// Bells counts the number of bells rung.
|
|
Bells() int
|
|
|
|
// GetTitle gets the current window title.
|
|
GetTitle() string
|
|
|
|
// SetSize is used to resize the window.
|
|
// Newly added cells are empty, and content in old cells that out of range is lost.
|
|
SetSize(Coord)
|
|
|
|
// GetCursor is used to obtain the current cursor style.
|
|
GetCursor() CursorStyle
|
|
|
|
// SetClipboard sets the clipboard contents (copy buffer).
|
|
SetClipboard([]byte)
|
|
|
|
// GetClipboard returns the clipboard (copy buffer).
|
|
GetClipboard() []byte
|
|
|
|
// IsAdvancedKeyboard returns true, as we always support the full keyboard protocol.
|
|
IsAdvancedKeyboard() bool
|
|
}
|
|
|
|
// mockBackend is a mock of a backend device for use with the emulator.
|
|
// It implements the following interfaces:
|
|
// vt.Backend, vt.Beeper, vt.Colorer, vt.Titler, vt.Resizer, vt.Blitter
|
|
type mockBackend struct {
|
|
cells []Cell // Content of cells
|
|
size Coord
|
|
pos Coord
|
|
colors int
|
|
style Style
|
|
defaultStyle Style
|
|
notifyQ chan<- bool
|
|
resized bool
|
|
newSize Coord
|
|
modes map[PrivateMode]ModeStatus
|
|
bells int
|
|
errs int
|
|
title string
|
|
clipboard []byte
|
|
cursor CursorStyle
|
|
lock sync.Mutex
|
|
}
|
|
|
|
func (mb *mockBackend) GetSize() Coord {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
mb.checkSize()
|
|
return mb.size
|
|
}
|
|
|
|
func (mb *mockBackend) Beep() {
|
|
mb.lock.Lock()
|
|
mb.bells++
|
|
mb.lock.Unlock()
|
|
}
|
|
|
|
func (mb *mockBackend) SetMouse(MouseReporting) {}
|
|
|
|
func (mb *mockBackend) GetPrivateMode(pm PrivateMode) ModeStatus {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
// note default (zero) value is ModeNA
|
|
return mb.modes[pm]
|
|
}
|
|
|
|
func (mb *mockBackend) SetPrivateMode(pm PrivateMode, status ModeStatus) error {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
if old := mb.modes[pm]; old == ModeOn || old == ModeOff {
|
|
if status == ModeOn || status == ModeOff {
|
|
mb.modes[pm] = status
|
|
} else {
|
|
mb.errs++
|
|
}
|
|
} else {
|
|
mb.errs++
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (mb *mockBackend) Put(pos Coord, cell Cell) { // grapheme string, width int, style Style) {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
mb.checkSize()
|
|
|
|
if index := mb.index(pos); index >= 0 {
|
|
mb.cells[index] = cell
|
|
|
|
// writing to a cell right after a wide
|
|
// character clears that wide character (but leaves style/attributes)
|
|
if cell.W > 0 && pos.X > 0 && mb.cells[index-1].W > 1 {
|
|
mb.cells[index-1].C = ""
|
|
mb.cells[index-1].W = 0
|
|
}
|
|
|
|
// wide characters delete the next cell
|
|
if cell.W == 2 && pos.X < mb.size.X-1 {
|
|
mb.cells[index+1].C = ""
|
|
mb.cells[index+1].W = 0
|
|
mb.cells[index+1].S = cell.S
|
|
}
|
|
} else {
|
|
mb.errs++
|
|
}
|
|
}
|
|
|
|
func (mb *mockBackend) isPositionValid(pos Coord) bool {
|
|
mb.checkSize()
|
|
|
|
return pos.X < mb.size.X && pos.Y < mb.size.Y && pos.X >= 0 && pos.Y >= 0
|
|
}
|
|
|
|
// index calculates the index in the cells array. If the coordinates are invalid,
|
|
// -1 will be returned.
|
|
func (mb *mockBackend) index(pos Coord) int {
|
|
mb.checkSize()
|
|
|
|
if !mb.isPositionValid(pos) {
|
|
return -1
|
|
}
|
|
return int(pos.X) + int(pos.Y)*int(mb.size.X)
|
|
}
|
|
|
|
func (mb *mockBackend) GetCell(pos Coord) Cell {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
|
|
if index := mb.index(pos); index >= 0 {
|
|
return mb.cells[index]
|
|
}
|
|
return Cell{S: BaseStyle}
|
|
}
|
|
|
|
func (mb *mockBackend) Bells() int {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
return mb.bells
|
|
}
|
|
|
|
func (mb *mockBackend) GetPosition() Coord {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
mb.checkSize()
|
|
return mb.pos
|
|
}
|
|
|
|
func (mb *mockBackend) SetPosition(pos Coord) {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
mb.checkSize()
|
|
pos.X = min(mb.size.X-1, max(0, pos.X))
|
|
pos.Y = min(mb.size.Y-1, max(0, pos.Y))
|
|
mb.pos = pos
|
|
}
|
|
|
|
func (mb *mockBackend) Colors() int {
|
|
return mb.colors
|
|
}
|
|
|
|
func (mb *mockBackend) SetStyle(style Style) {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
|
|
mb.style = style
|
|
}
|
|
|
|
// SetWindowTitle implements the Titler interface.
|
|
func (mb *mockBackend) SetWindowTitle(title string) {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
|
|
mb.title = title
|
|
}
|
|
|
|
// GetTitle allows test code to observe what was set with SetWindowTitle.
|
|
func (mb *mockBackend) GetTitle() string {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
|
|
return mb.title
|
|
}
|
|
|
|
// NotifyResize registers a channel to be written to (non-blocking) if the
|
|
// backend changes size.
|
|
func (mb *mockBackend) NotifyResize(rq chan<- bool) {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
|
|
mb.notifyQ = rq
|
|
}
|
|
|
|
// checkSize performs a possible terminal resize. Cells that are
|
|
// added are treated as empty, while cells that are removed are just lost.
|
|
// (Note that at least one other emulator erases content on a resize. There is no
|
|
// standard for what to do here.) This is done inline when calculating the index.
|
|
// The caller is expected to hold mb.lock.
|
|
func (mb *mockBackend) checkSize() {
|
|
if !mb.resized {
|
|
return
|
|
}
|
|
size := mb.newSize
|
|
old := mb.cells
|
|
ox := int(mb.size.X)
|
|
oy := int(mb.size.Y)
|
|
nx := int(size.X)
|
|
ny := int(size.Y)
|
|
cells := make([]Cell, int(size.Y)*int(size.X))
|
|
for i := range cells {
|
|
cells[i].S = BaseStyle
|
|
}
|
|
for y := range min(ny, oy) {
|
|
for x := range min(nx, ox) {
|
|
cells[y*nx+x] = old[y*ox+x]
|
|
}
|
|
}
|
|
mb.cells = cells
|
|
mb.size = size
|
|
mb.pos.X = min(mb.pos.X, size.X-1)
|
|
mb.pos.Y = min(mb.pos.Y, size.Y-1)
|
|
mb.resized = false
|
|
}
|
|
|
|
func (mb *mockBackend) RaiseResize() {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
|
|
if rq := mb.notifyQ; rq != nil {
|
|
select {
|
|
case rq <- true:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetSize is used to change the size of the virtual terminal.
|
|
func (mb *mockBackend) SetSize(size Coord) {
|
|
mb.lock.Lock()
|
|
mb.resized = true
|
|
mb.newSize = size
|
|
mb.lock.Unlock()
|
|
}
|
|
|
|
// Reset the terminal to startup defaults.
|
|
func (mb *mockBackend) Reset() {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
|
|
mb.style = mb.defaultStyle
|
|
|
|
mb.title = ""
|
|
mb.errs = 0
|
|
mb.bells = 0
|
|
mb.pos = Coord{X: 0, Y: 0}
|
|
mb.modes[PmShowCursor] = ModeOn
|
|
mb.modes[PmBlinkCursor] = ModeOn
|
|
mb.modes[PmGraphemeClusters] = ModeOff
|
|
}
|
|
|
|
func (mb *mockBackend) Blit(src, dst, dim Coord) {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
|
|
mb.checkSize()
|
|
|
|
// clip to visible source
|
|
if dim.X+src.X > mb.size.X {
|
|
dim.X = mb.size.X - src.X
|
|
}
|
|
if dim.Y+src.Y > mb.size.Y {
|
|
dim.Y = mb.size.Y - src.Y
|
|
}
|
|
// and clip to final destination
|
|
if dim.X+dst.X > mb.size.X {
|
|
dim.X = mb.size.X - dst.X
|
|
}
|
|
if dim.Y+dst.Y > mb.size.Y {
|
|
dim.Y = mb.size.Y - dst.Y
|
|
}
|
|
|
|
// gap represents decrement when shifting to the next row --
|
|
// skipping over the irrelevant cells. (The increment in the
|
|
// index when going from last cell of row to first cell of next row,
|
|
// or vice versa.)
|
|
gap := int(mb.size.X - dim.X)
|
|
|
|
// the following logic is carefully constructed to avoid expensive
|
|
// operations in the loops (only addition or subtraction)
|
|
if mb.index(src) > mb.index(dst) { // source appears later, so we can forward copy
|
|
si := mb.index(src)
|
|
di := mb.index(dst)
|
|
for range dim.Y {
|
|
for range dim.X {
|
|
mb.cells[di] = mb.cells[si]
|
|
di++
|
|
si++
|
|
}
|
|
// advance to next row
|
|
si += gap
|
|
di += gap
|
|
}
|
|
} else { // source appears earlier, so we have to reverse copy
|
|
src.Y += dim.Y - 1
|
|
dst.Y += dim.Y - 1
|
|
src.X += dim.X - 1
|
|
dst.X += dim.X - 1
|
|
si := mb.index(src)
|
|
di := mb.index(dst)
|
|
|
|
for range dim.Y {
|
|
for range dim.X {
|
|
mb.cells[di] = mb.cells[si]
|
|
si--
|
|
di--
|
|
}
|
|
si -= gap
|
|
di -= gap
|
|
}
|
|
}
|
|
}
|
|
|
|
// Buffering is not supported by the mockBackend, and there is little point in it.
|
|
func (mb *mockBackend) Buffering(bool) {}
|
|
|
|
// SetCursor is used to set how the cursor is displayed.
|
|
func (mb *mockBackend) SetCursor(cs CursorStyle) {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
mb.cursor = cs
|
|
}
|
|
|
|
// GetCursor returns the current cursor style.
|
|
func (mb *mockBackend) GetCursor() CursorStyle {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
return mb.cursor
|
|
}
|
|
|
|
// SetClipboard sets the current clipboard contents.
|
|
func (mb *mockBackend) SetClipboard(data []byte) {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
mb.clipboard = data
|
|
}
|
|
|
|
// GetClipboard gets the current clipboard contents.
|
|
func (mb *mockBackend) GetClipboard() []byte {
|
|
mb.lock.Lock()
|
|
defer mb.lock.Unlock()
|
|
return mb.clipboard
|
|
}
|
|
|
|
// IsAdvancedKeyboard returns true - we always implement
|
|
// the raw keyboard protocol.
|
|
func (mb *mockBackend) IsAdvancedKeyboard() bool { return true }
|
|
|
|
// MockOpt is an interface by which options can change the behavior of the mocked terminal.
|
|
// This is intended to permit easier testing.
|
|
type MockOpt interface{ SetMockOpt(mb *mockBackend) }
|
|
|
|
// MockOptSize changes the default terminal size, which is normally 80x24.
|
|
type MockOptSize Coord
|
|
|
|
func (o MockOptSize) SetMockOpt(mb *mockBackend) { mb.size = Coord(o) }
|
|
|
|
// MockOptColors changes the number of colors the terminal supports.
|
|
type MockOptColors int
|
|
|
|
func (o MockOptColors) SetMockOpt(mb *mockBackend) { mb.colors = int(o) }
|
|
|
|
// MockOptNoBlit suppresses the blitter interface.
|
|
type MockOptNoBlit struct{}
|
|
|
|
func (MockOptNoBlit) SetMockOpt(mb *mockBackend) {}
|
|
|
|
// NewMockBackend returns a MockBackend modified by the given options.
|
|
// The default is a fully featured 256-color backend with initial size 80x24.
|
|
func NewMockBackend(options ...MockOpt) MockBackend {
|
|
mb := &mockBackend{
|
|
size: Coord{X: 80, Y: 24},
|
|
colors: 256,
|
|
style: BaseStyle,
|
|
defaultStyle: BaseStyle.WithFg(color.Silver).WithBg(color.Black),
|
|
cursor: BlinkingBlock,
|
|
}
|
|
|
|
for _, opt := range options {
|
|
opt.SetMockOpt(mb)
|
|
}
|
|
|
|
if mb.colors > 0 {
|
|
mb.style = mb.defaultStyle
|
|
}
|
|
mb.cells = make([]Cell, int(mb.size.X)*int(mb.size.Y))
|
|
for i := range mb.cells {
|
|
mb.cells[i].S = BaseStyle
|
|
}
|
|
|
|
mb.modes = make(map[PrivateMode]ModeStatus)
|
|
mb.modes[PmShowCursor] = ModeOn
|
|
mb.modes[PmBlinkCursor] = ModeOn
|
|
mb.modes[PmGraphemeClusters] = ModeOff
|
|
mb.modes[PmSyncOutput] = ModeOff
|
|
return mb
|
|
}
|