~ghost08/tcell-term

tcell-term: buffer: use tcell.StyleDefault as default cursorAttr v1 PROPOSED

Tim Culverhouse: 9
 buffer: use tcell.StyleDefault as default cursorAttr
 csi: try to split params by colon
 theme: use tcell.StyleDefault for Default fg|bg
 chore: gofumpt
 csi: use default fg|bg on error for code 38|48
 draw: clear terminal on draw
 render: debounce render requests to 4ms
 theme: return palette color instead of named color
 example: add simple terminal example

 13 files changed, 180 insertions(+), 113 deletions(-)
Great work, thanks. I hope that it will then be integrated to aerc :)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~ghost08/tcell-term/patches/35070/mbox | git am -3
Learn more about email & git

[PATCH tcell-term 1/9] buffer: use tcell.StyleDefault as default cursorAttr Export this patch

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 termutil/buffer.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/termutil/buffer.go b/termutil/buffer.go
index 015b33becbce..c2595b5b0fd6 100644
--- a/termutil/buffer.go
+++ b/termutil/buffer.go
@@ -75,7 +75,7 @@ func NewBuffer(width, height uint16, maxLines uint64, fg tcell.Color, bg tcell.C
		maxLines:     maxLines,
		topMargin:    0,
		bottomMargin: uint(height - 1),
		cursorAttr:   tcell.StyleDefault.Foreground(fg).Background(bg),
		cursorAttr:   tcell.StyleDefault,
		charsets:     []*map[rune]rune{nil, nil},
		modes: Modes{
			LineFeedMode: true,
-- 
2.37.3

[PATCH tcell-term 2/9] csi: try to split params by colon Export this patch

"The ITU's T.416 Information technology - Open Document Architecture
(ODA) and interchange format: Character content architectures[47] uses
':' as separator characters instead"

Reference: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 termutil/csi.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/termutil/csi.go b/termutil/csi.go
index fb700c9895e3..4cccdc8e4208 100644
--- a/termutil/csi.go
+++ b/termutil/csi.go
@@ -29,7 +29,12 @@ CSI:
		}
	}

	unprocessed := strings.Split(param, ";")
	unprocessed := []string{}
	if strings.Contains(param, ":") {
		unprocessed = strings.Split(param, ":")
	} else {
		unprocessed = strings.Split(param, ";")
	}
	for _, par := range unprocessed {
		if par != "" {
			par = strings.TrimLeft(par, "0")
-- 
2.37.3

[PATCH tcell-term 3/9] theme: use tcell.StyleDefault for Default fg|bg Export this patch

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 termutil/theme.go | 14 ++++----------
 1 file changed, 4 insertions(+), 10 deletions(-)

diff --git a/termutil/theme.go b/termutil/theme.go
index ca2083315735..ad426827c942 100644
--- a/termutil/theme.go
+++ b/termutil/theme.go
@@ -85,19 +85,13 @@ func (t *Theme) ColourFrom4Bit(code uint8) tcell.Color {
}

func (t *Theme) DefaultBackground() tcell.Color {
	c, ok := t.colourMap[ColourBackground]
	if !ok {
		return tcell.ColorBlack
	}
	return c
	_, bg, _ := tcell.StyleDefault.Decompose()
	return bg
}

func (t *Theme) DefaultForeground() tcell.Color {
	c, ok := t.colourMap[ColourForeground]
	if !ok {
		return tcell.ColorWhite
	}
	return c
	fg, _, _ := tcell.StyleDefault.Decompose()
	return fg
}

func (t *Theme) CursorBackground() tcell.Color {
-- 
2.37.3

[PATCH tcell-term 4/9] chore: gofumpt Export this patch

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 terminal.go          |  5 +++-
 termutil/buffer.go   | 22 --------------
 termutil/csi.go      | 23 ++++----------
 termutil/terminal.go | 22 +++++++-------
 termutil/theme.go    | 71 +++++++++++++++++++++-----------------------
 5 files changed, 54 insertions(+), 89 deletions(-)

diff --git a/terminal.go b/terminal.go
index da5f32ee8fc9..2d1d9edb240a 100644
--- a/terminal.go
+++ b/terminal.go
@@ -59,7 +59,7 @@ func (t *Terminal) Draw(s tcell.Screen, X, Y uint16) {
		for viewX := uint16(0); viewX < buf.ViewWidth(); viewX++ {
			cell := buf.GetCell(viewX, uint16(viewY))
			if cell == nil {
				//s.SetContent(int(viewX+X), viewY+int(Y), ' ', nil, tcell.StyleDefault.Background(tcell.ColorBlack))
				// s.SetContent(int(viewX+X), viewY+int(Y), ' ', nil, tcell.StyleDefault.Background(tcell.ColorBlack))
				continue
			}
			s.SetContent(int(viewX+X), viewY+int(Y), cell.Rune().Rune, nil, cell.Style())
@@ -107,10 +107,12 @@ func (w *windowManipulator) SizeInPixels() (int, int) {
	sz, _ := GetWinSize()
	return int(sz.XPixel), int(sz.YPixel)
}

func (w *windowManipulator) CellSizeInPixels() (int, int) {
	sz, _ := GetWinSize()
	return int(sz.Cols / sz.XPixel), int(sz.Rows / sz.YPixel)
}

func (w *windowManipulator) SizeInChars() (int, int) {
	sz, _ := GetWinSize()
	return int(sz.Cols), int(sz.Rows)
@@ -120,6 +122,7 @@ func (w *windowManipulator) ResizeInChars(int, int)  {}
func (w *windowManipulator) ScreenSizeInPixels() (int, int) {
	return w.SizeInPixels()
}

func (w *windowManipulator) ScreenSizeInChars() (int, int) {
	return w.SizeInChars()
}
diff --git a/termutil/buffer.go b/termutil/buffer.go
index c2595b5b0fd6..0d24044b837c 100644
--- a/termutil/buffer.go
+++ b/termutil/buffer.go
@@ -123,7 +123,6 @@ func (buffer *Buffer) getAreaScrollRange() (top uint64, bottom uint64) {
}

func (buffer *Buffer) areaScrollDown(lines uint16) {

	// NOTE: bottom is exclusive
	top, bottom := buffer.getAreaScrollRange()

@@ -138,7 +137,6 @@ func (buffer *Buffer) areaScrollDown(lines uint16) {
}

func (buffer *Buffer) areaScrollUp(lines uint16) {

	// NOTE: bottom is exclusive
	top, bottom := buffer.getAreaScrollRange()

@@ -278,7 +276,6 @@ func (buffer *Buffer) deleteLine() {
}

func (buffer *Buffer) insertLine() {

	if !buffer.InScrollableRegion() {
		pos := buffer.RawLine()
		maxLines := buffer.GetMaxLines()
@@ -319,7 +316,6 @@ func (buffer *Buffer) insertLine() {
}

func (buffer *Buffer) insertBlankCharacters(count int) {

	index := int(buffer.RawLine())
	for i := 0; i < count; i++ {
		cells := buffer.lines[index].cells
@@ -328,7 +324,6 @@ func (buffer *Buffer) insertBlankCharacters(count int) {
}

func (buffer *Buffer) insertLines(count int) {

	if buffer.HasScrollableRegion() && !buffer.InScrollableRegion() {
		// should have no effect outside of scrollable region
		return
@@ -339,11 +334,9 @@ func (buffer *Buffer) insertLines(count int) {
	for i := 0; i < count; i++ {
		buffer.insertLine()
	}

}

func (buffer *Buffer) deleteLines(count int) {

	if buffer.HasScrollableRegion() && !buffer.InScrollableRegion() {
		// should have no effect outside of scrollable region
		return
@@ -354,11 +347,9 @@ func (buffer *Buffer) deleteLines(count int) {
	for i := 0; i < count; i++ {
		buffer.deleteLine()
	}

}

func (buffer *Buffer) index() {

	// This sequence causes the active position to move downward one line without changing the column position.
	// If the active position is at the bottom margin, a scroll up is performed."

@@ -387,7 +378,6 @@ func (buffer *Buffer) index() {
}

func (buffer *Buffer) reverseIndex() {

	cursorVY := buffer.convertRawLineToViewLine(buffer.cursorPosition.Line)

	if uint(cursorVY) == buffer.topMargin {
@@ -399,7 +389,6 @@ func (buffer *Buffer) reverseIndex() {

// write will write a rune to the terminal at the position of the cursor, and increment the cursor position
func (buffer *Buffer) write(runes ...MeasuredRune) {

	// scroll to bottom on input
	buffer.scrollLinesFromBottom = 0

@@ -448,7 +437,6 @@ func (buffer *Buffer) write(runes ...MeasuredRune) {
				// no more room on line and wrapping is disabled
				return
			}

		} else {

			for int(buffer.CursorColumn()) >= len(line.cells) {
@@ -480,7 +468,6 @@ func (buffer *Buffer) inDoWrap() bool {
}

func (buffer *Buffer) backspace() {

	if buffer.cursorPosition.Col == 0 {
		line := buffer.getCurrentLine()
		if line.wrapped {
@@ -495,7 +482,6 @@ func (buffer *Buffer) backspace() {
}

func (buffer *Buffer) carriageReturn() {

	cursorVY := buffer.convertRawLineToViewLine(buffer.cursorPosition.Line)

	for {
@@ -514,7 +500,6 @@ func (buffer *Buffer) carriageReturn() {
}

func (buffer *Buffer) tab() {

	tabStop := buffer.getNextTabStopAfter(buffer.cursorPosition.Col)
	for buffer.cursorPosition.Col < tabStop && buffer.cursorPosition.Col < buffer.viewWidth-1 { // @todo rightMargin
		buffer.write(MeasuredRune{Rune: ' ', Width: 1})
@@ -523,7 +508,6 @@ func (buffer *Buffer) tab() {

// return next tab stop x pos
func (buffer *Buffer) getNextTabStopAfter(col uint16) uint16 {

	defaultStop := col + (TabSize - (col % TabSize))
	if defaultStop == col {
		defaultStop += TabSize
@@ -562,7 +546,6 @@ func (buffer *Buffer) verticalTab() {
}

func (buffer *Buffer) newLineEx(forceCursorToMargin bool) {

	if buffer.IsNewLineMode() || forceCursorToMargin {
		buffer.cursorPosition.Col = 0
	}
@@ -578,7 +561,6 @@ func (buffer *Buffer) newLineEx(forceCursorToMargin bool) {
}

func (buffer *Buffer) movePosition(x int16, y int16) {

	var toX uint16
	var toY uint16

@@ -599,7 +581,6 @@ func (buffer *Buffer) movePosition(x int16, y int16) {
}

func (buffer *Buffer) setPosition(col uint16, line uint16) {

	useCol := col
	useLine := line
	maxLine := buffer.ViewHeight() - 1
@@ -649,7 +630,6 @@ func (buffer *Buffer) getCurrentLine() *Line {
}

func (buffer *Buffer) getViewLine(index uint16) *Line {

	if index >= buffer.ViewHeight() {
		return &buffer.lines[len(buffer.lines)-1]
	}
@@ -712,7 +692,6 @@ func (buffer *Buffer) eraseDisplay() {
}

func (buffer *Buffer) deleteChars(n int) {

	line := buffer.getCurrentLine()
	if int(buffer.cursorPosition.Col) >= len(line.cells) {
		return
@@ -726,7 +705,6 @@ func (buffer *Buffer) deleteChars(n int) {
}

func (buffer *Buffer) eraseCharacters(n int) {

	line := buffer.getCurrentLine()

	max := int(buffer.cursorPosition.Col) + n
diff --git a/termutil/csi.go b/termutil/csi.go
index 4cccdc8e4208..53d135528ce7 100644
--- a/termutil/csi.go
+++ b/termutil/csi.go
@@ -130,7 +130,6 @@ func (t *Terminal) handleCSI(readChan chan MeasuredRune) (renderRequired bool) {
	_ = raw
	log.Printf("UNKNOWN CSI P(%s) I(%s) %c", strings.Join(params, ";"), string(intermediate), final)
	return false

}

type WindowState uint8
@@ -166,7 +165,6 @@ type WindowManipulator interface {
}

func (t *Terminal) csiWindowManipulation(params []string) (renderRequired bool) {

	if t.windowManipulator == nil {
		return false
	}
@@ -177,7 +175,7 @@ func (t *Terminal) csiWindowManipulation(params []string) (renderRequired bool)
			t.windowManipulator.Restore()
		case "2":
			t.windowManipulator.Minimise()
		case "3": //move window
		case "3": // move window
			if i+2 >= len(params) {
				return false
			}
@@ -185,7 +183,7 @@ func (t *Terminal) csiWindowManipulation(params []string) (renderRequired bool)
			y, _ := strconv.Atoi(params[i+2])
			i += 2
			t.windowManipulator.Move(x, y)
		case "4": //resize h,w
		case "4": // resize h,w
			w, h := t.windowManipulator.SizeInPixels()
			if i+1 < len(params) {
				h, _ = strconv.Atoi(params[i+1])
@@ -309,7 +307,6 @@ func (t *Terminal) csiWindowManipulation(params []string) (renderRequired bool)
// CSI c
// Send Device Attributes (Primary/Secondary/Tertiary DA)
func (t *Terminal) csiSendDeviceAttributesHandler(params []string) (renderRequired bool) {

	// we are VT100
	// for DA1 we'll respond ?1;2
	// for DA2 we'll respond >0;0;0
@@ -326,7 +323,6 @@ func (t *Terminal) csiSendDeviceAttributesHandler(params []string) (renderRequir
// CSI n
// Device Status Report (DSR)
func (t *Terminal) csiDeviceStatusReportHandler(params []string) (renderRequired bool) {

	if len(params) == 0 {
		return false
	}
@@ -411,7 +407,6 @@ func (t *Terminal) csiCursorBackwardHandler(params []string) (renderRequired boo
// CSI E
// Cursor Next Line Ps Times (default = 1) (CNL)
func (t *Terminal) csiCursorNextLineHandler(params []string) (renderRequired bool) {

	distance := 1
	if len(params) > 0 {
		var err error
@@ -429,7 +424,6 @@ func (t *Terminal) csiCursorNextLineHandler(params []string) (renderRequired boo
// CSI F
// Cursor Preceding Line Ps Times (default = 1) (CPL)
func (t *Terminal) csiCursorPrecedingLineHandler(params []string) (renderRequired bool) {

	distance := 1
	if len(params) > 0 {
		var err error
@@ -673,7 +667,6 @@ func (t *Terminal) csiSetModes(modes []string, enabled bool) bool {
}

func parseModes(mode string) []string {

	var output []string

	if mode == "" {
@@ -695,9 +688,7 @@ func parseModes(mode string) []string {
}

func (t *Terminal) csiSetMode(modes string, enabled bool) bool {

	for _, modeStr := range parseModes(modes) {

		switch modeStr {
		case "4":
			t.activeBuffer.modes.ReplaceMode = !enabled
@@ -723,7 +714,7 @@ func (t *Terminal) csiSetMode(modes string, enabled bool) bool {
			t.activeBuffer.modes.OriginMode = enabled
		case "?7":
			// auto-wrap mode
			//DECAWM
			// DECAWM
			t.activeBuffer.modes.AutoWrap = enabled
		case "?9":
			if enabled {
@@ -795,7 +786,7 @@ func (t *Terminal) csiSetMode(modes string, enabled bool) bool {
		case "?2004":
			t.activeBuffer.modes.BracketedPasteMode = enabled
		case "?80":
			//t.activeBuffer.modes.SixelScrolling = enabled
			// t.activeBuffer.modes.SixelScrolling = enabled
		default:
			log.Printf("Unsupported CSI mode %s = %t", modeStr, enabled)
		}
@@ -880,14 +871,13 @@ func (t *Terminal) csiEraseInDisplayHandler(params []string) (renderRequired boo
// CSI K
// Erase in Line (EL), VT100
func (t *Terminal) csiEraseInLineHandler(params []string) (renderRequired bool) {

	n := "0"
	if len(params) > 0 {
		n = params[0]
	}

	switch n {
	case "0", "": //erase adter cursor
	case "0", "": // erase adter cursor
		t.GetActiveBuffer().eraseLineFromCursor()
	case "1": // erase to cursor inclusive
		t.GetActiveBuffer().eraseLineToCursor()
@@ -902,7 +892,6 @@ func (t *Terminal) csiEraseInLineHandler(params []string) (renderRequired bool)
// CSI m
// Character Attributes (SGR)
func (t *Terminal) sgrSequenceHandler(params []string) bool {

	if len(params) == 0 {
		params = []string{"0"}
	}
@@ -928,7 +917,7 @@ func (t *Terminal) sgrSequenceHandler(params []string) bool {
		case "7", "07":
			*attr = attr.Reverse(true)
		case "8", "08":
			//*attr = attr.Hidden( true)
			// *attr = attr.Hidden(true)
		case "9", "09":
			*attr = attr.StrikeThrough(true)
		case "21":
diff --git a/termutil/terminal.go b/termutil/terminal.go
index c96ae6f1cede..edebf9683566 100644
--- a/termutil/terminal.go
+++ b/termutil/terminal.go
@@ -175,33 +175,31 @@ func (t *Terminal) process() {
}

func (t *Terminal) processRunes(runes ...MeasuredRune) (renderRequired bool) {

	for _, r := range runes {

		switch r.Rune {
		case 0x05: //enq
		case 0x05: // enq
			continue
		case 0x07: //bell
			//DING DING DING
		case 0x07: // bell
			// DING DING DING
			continue
		case 0x8: //backspace
		case 0x8: // backspace
			t.GetActiveBuffer().backspace()
			renderRequired = true
		case 0x9: //tab
		case 0x9: // tab
			t.GetActiveBuffer().tab()
			renderRequired = true
		case 0xa, 0xc: //newLine/form feed
		case 0xa, 0xc: // newLine/form feed
			t.GetActiveBuffer().newLine()
			renderRequired = true
		case 0xb: //vertical tab
		case 0xb: // vertical tab
			t.GetActiveBuffer().verticalTab()
			renderRequired = true
		case 0xd: //carriageReturn
		case 0xd: // carriageReturn
			t.GetActiveBuffer().carriageReturn()
			renderRequired = true
		case 0xe: //shiftOut
		case 0xe: // shiftOut
			t.GetActiveBuffer().currentCharset = 1
		case 0xf: //shiftIn
		case 0xf: // shiftIn
			t.GetActiveBuffer().currentCharset = 0
		default:
			if r.Rune < 0x20 {
diff --git a/termutil/theme.go b/termutil/theme.go
index ad426827c942..57025a67b293 100644
--- a/termutil/theme.go
+++ b/termutil/theme.go
@@ -39,42 +39,40 @@ type Theme struct {
	colourMap map[Colour]tcell.Color
}

var (
	map4Bit = map[uint8]Colour{
		30:  ColourBlack,
		31:  ColourRed,
		32:  ColourGreen,
		33:  ColourYellow,
		34:  ColourBlue,
		35:  ColourMagenta,
		36:  ColourCyan,
		37:  ColourWhite,
		90:  ColourBrightBlack,
		91:  ColourBrightRed,
		92:  ColourBrightGreen,
		93:  ColourBrightYellow,
		94:  ColourBrightBlue,
		95:  ColourBrightMagenta,
		96:  ColourBrightCyan,
		97:  ColourBrightWhite,
		40:  ColourBlack,
		41:  ColourRed,
		42:  ColourGreen,
		43:  ColourYellow,
		44:  ColourBlue,
		45:  ColourMagenta,
		46:  ColourCyan,
		47:  ColourWhite,
		100: ColourBrightBlack,
		101: ColourBrightRed,
		102: ColourBrightGreen,
		103: ColourBrightYellow,
		104: ColourBrightBlue,
		105: ColourBrightMagenta,
		106: ColourBrightCyan,
		107: ColourBrightWhite,
	}
)
var map4Bit = map[uint8]Colour{
	30:  ColourBlack,
	31:  ColourRed,
	32:  ColourGreen,
	33:  ColourYellow,
	34:  ColourBlue,
	35:  ColourMagenta,
	36:  ColourCyan,
	37:  ColourWhite,
	90:  ColourBrightBlack,
	91:  ColourBrightRed,
	92:  ColourBrightGreen,
	93:  ColourBrightYellow,
	94:  ColourBrightBlue,
	95:  ColourBrightMagenta,
	96:  ColourBrightCyan,
	97:  ColourBrightWhite,
	40:  ColourBlack,
	41:  ColourRed,
	42:  ColourGreen,
	43:  ColourYellow,
	44:  ColourBlue,
	45:  ColourMagenta,
	46:  ColourCyan,
	47:  ColourWhite,
	100: ColourBrightBlack,
	101: ColourBrightRed,
	102: ColourBrightGreen,
	103: ColourBrightYellow,
	104: ColourBrightBlue,
	105: ColourBrightMagenta,
	106: ColourBrightCyan,
	107: ColourBrightWhite,
}

func (t *Theme) ColourFrom4Bit(code uint8) tcell.Color {
	colour, ok := map4Bit[code]
@@ -135,7 +133,6 @@ func (t *Theme) ColourFrom24Bit(r, g, b string) (tcell.Color, error) {
}

func (t *Theme) ColourFromAnsi(ansi []string) (tcell.Color, error) {

	if len(ansi) == 0 {
		return tcell.ColorDefault, fmt.Errorf("invalid ansi colour code")
	}
-- 
2.37.3

[PATCH tcell-term 5/9] csi: use default fg|bg on error for code 38|48 Export this patch

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 termutil/csi.go | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

diff --git a/termutil/csi.go b/termutil/csi.go
index 53d135528ce7..377146dc3d6a 100644
--- a/termutil/csi.go
+++ b/termutil/csi.go
@@ -896,10 +896,7 @@ func (t *Terminal) sgrSequenceHandler(params []string) bool {
		params = []string{"0"}
	}

	for i := range params {

		p := strings.Replace(strings.Replace(params[i], "[", "", -1), "]", "", -1)

	for i, p := range params {
		attr := t.GetActiveBuffer().getCursorAttr()
		switch p {
		case "00", "0", "":
@@ -937,12 +934,20 @@ func (t *Terminal) sgrSequenceHandler(params []string) bool {
		case "29":
			*attr = attr.StrikeThrough(false)
		case "38": // set foreground
			fg, _ := t.theme.ColourFromAnsi(params[i+1:])
			*attr = attr.Foreground(fg)
			fg, err := t.theme.ColourFromAnsi(params[i+1:])
			if err != nil {
				*attr = attr.Foreground(t.theme.DefaultForeground())
			} else {
				*attr = attr.Foreground(fg)
			}
			return false
		case "48": // set background
			bg, _ := t.theme.ColourFromAnsi(params[i+1:])
			*attr = attr.Background(bg)
			bg, err := t.theme.ColourFromAnsi(params[i+1:])
			if err != nil {
				*attr = attr.Background(t.theme.DefaultBackground())
			} else {
				*attr = attr.Background(bg)
			}
			return false
		case "39":
			*attr = attr.Foreground(t.theme.DefaultForeground())
-- 
2.37.3

[PATCH tcell-term 6/9] draw: clear terminal on draw Export this patch

Calls to draw redraw the entire terminal. Clear it first.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 terminal.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/terminal.go b/terminal.go
index 2d1d9edb240a..11cd9c8fe746 100644
--- a/terminal.go
+++ b/terminal.go
@@ -54,6 +54,7 @@ func (t *Terminal) Event(e tcell.Event) {
}

func (t *Terminal) Draw(s tcell.Screen, X, Y uint16) {
	s.Clear()
	buf := t.term.GetActiveBuffer()
	for viewY := int(buf.ViewHeight()) - 1; viewY >= 0; viewY-- {
		for viewX := uint16(0); viewX < buf.ViewWidth(); viewX++ {
-- 
2.37.3

[PATCH tcell-term 7/9] render: debounce render requests to 4ms Export this patch

The requestRender method sends to a channel and requests the host app to
redraw the terminal and call screen.Show. The requests are very
frequent, and are easily dropped if not using a large buffered channel.
Minimal testing showed that reaching 1000 in-queue requests was not
uncommon.

Debounce the requests to a reasonable refresh rate. 4 milliseconds is
250hz which should be plenty for TUIs.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 termutil/terminal.go | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/termutil/terminal.go b/termutil/terminal.go
index edebf9683566..3e7dc7493d4c 100644
--- a/termutil/terminal.go
+++ b/termutil/terminal.go
@@ -7,6 +7,7 @@ import (
	"io"
	"os"
	"os/exec"
	"time"

	"github.com/creack/pty"
	"golang.org/x/term"
@@ -30,6 +31,7 @@ type Terminal struct {
	mouseMode         MouseMode
	mouseExtMode      MouseExtMode
	theme             *Theme
	renderDebounce    *time.Timer
}

// NewTerminal creates a new terminal instance
@@ -151,10 +153,14 @@ func (t *Terminal) Run(c *exec.Cmd, updateChan chan struct{}, rows uint16, cols
}

func (t *Terminal) requestRender() {
	select {
	case t.updateChan <- struct{}{}:
	default:
	if t.renderDebounce != nil {
		t.renderDebounce.Stop()
	}
	// 4 milliseconds = 250Hz. Probably don't need to render faster than
	// that
	t.renderDebounce = time.AfterFunc(4*time.Millisecond, func() {
		t.updateChan <- struct{}{}
	})
}

func (t *Terminal) process() {
-- 
2.37.3

[PATCH tcell-term 8/9] theme: return palette color instead of named color Export this patch

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 termutil/theme.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/termutil/theme.go b/termutil/theme.go
index 57025a67b293..48b1086dd74f 100644
--- a/termutil/theme.go
+++ b/termutil/theme.go
@@ -79,7 +79,7 @@ func (t *Theme) ColourFrom4Bit(code uint8) tcell.Color {
	if !ok {
		return tcell.ColorBlack
	}
	return t.colourMap[colour]
	return tcell.PaletteColor(int(colour))
}

func (t *Theme) DefaultBackground() tcell.Color {
-- 
2.37.3

[PATCH tcell-term 9/9] example: add simple terminal example Export this patch

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 example_simple/simple.go | 91 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 91 insertions(+)
 create mode 100644 example_simple/simple.go

diff --git a/example_simple/simple.go b/example_simple/simple.go
new file mode 100644
index 000000000000..4f91ae226879
--- /dev/null
+++ b/example_simple/simple.go
@@ -0,0 +1,91 @@
package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"

	tcellterm "git.sr.ht/~ghost08/tcell-term"
	"github.com/gdamore/tcell/v2"
)

func main() {
	f, _ := os.Create("meh.log")
	defer f.Close()
	logbuf := bytes.NewBuffer(nil)
	log.SetOutput(io.MultiWriter(f, logbuf))
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	s, err := tcell.NewScreen()
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
	if err = s.Init(); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}

	s.Clear()

	quit := make(chan struct{})
	redraw := make(chan struct{}, 10)
	var term *tcellterm.Terminal
	if term == nil {
		term = tcellterm.New()

		cmd := exec.Command("zsh")
		go func() {
			w, h := s.Size()
			lh := h
			lw := w
			if err := term.Run(cmd, redraw, uint16(lw), uint16(lh)); err != nil {
				log.Println(err)
			}
			s.HideCursor()
			term = nil
			close(quit)
		}()
	}
	go func() {
		for {
			ev := s.PollEvent()
			switch ev := ev.(type) {
			case *tcell.EventKey:
				switch ev.Key() {
				case tcell.KeyCtrlC:
					close(quit)
					return
				}
				if term != nil {
					term.Event(ev)
				}
			case *tcell.EventResize:
				if term != nil {
					w, h := s.Size()
					lh := h
					lw := w
					term.Resize(lw, lh)
				}
				s.Sync()
			}
		}
	}()

loop:
	for {
		select {
		case <-quit:
			break loop
		case <-redraw:
			term.Draw(s, 0, 0)
			s.Show()
		}
	}

	s.Fini()
	os.Stdout.Write(logbuf.Bytes())
}
-- 
2.37.3
Great work, thanks. I hope that it will then be integrated to aerc :)