~ghost08/tcell-term

c0e1519a3492ff5395f2f382ce0993998ac4ec59 — Tim Culverhouse 1 year, 2 months ago e5a1898
terminal: BREAKING: use a redraw poll instead of channel

The processing of runes creates a very large amount of data sent to the
redrawRequest channel. Use a bool and periodic check for if a render is
required.

Previously, a debounce had been added. The debounce functioned similarly
to this implementation however there are fewer chances for error in this
implementation.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
2 files changed, 35 insertions(+), 42 deletions(-)

M terminal.go
M termutil/terminal.go
M terminal.go => terminal.go +19 -17
@@ 23,7 23,7 @@ type Terminal struct {
	screen tcell.Screen
	view   views.View

	redrawChan chan struct{}
	close bool

	views.WidgetWatchers
}


@@ 49,31 49,33 @@ func WithWindowManipulator(wm termutil.WindowManipulator) Option {

func (t *Terminal) Run(cmd *exec.Cmd) error {
	w, h := t.view.Size()
	t.redrawChan = make(chan struct{}, 10)
	tmr := time.NewTicker(16 * time.Millisecond)
	go func() {
		for {
			select {
			case <-t.redrawChan:
				t.screen.PostEvent(NewRedrawEvent())
				// term.Draw()
				// s.Show()
			case <-tmr.C:
				if t.close {
					return
				}
				if t.term.ShouldRedraw() {
					t.PostEventWidgetContent(t)
					t.term.SetRedraw(false)
				}
			}
		}
	}()
	return t.term.Run(cmd, t.redrawChan, uint16(h), uint16(w))
}

type RedrawEvent struct {
	when time.Time
}

func NewRedrawEvent() *RedrawEvent {
	return &RedrawEvent{
		when: time.Now(),
	err := t.term.Run(cmd, uint16(h), uint16(w))
	if err != nil {
		return err
	}
	t.Close()
	return nil
}

func (e RedrawEvent) When() time.Time { return e.when }
func (t *Terminal) Close() {
	t.close = true
	t.term.Pty().Close()
}

func (t *Terminal) SetView(view views.View) {
	t.view = view

M termutil/terminal.go => termutil/terminal.go +16 -25
@@ 23,22 23,19 @@ const (
type Terminal struct {
	windowManipulator WindowManipulator
	pty               *os.File
	updateChan        chan struct{}
	processChan       chan MeasuredRune
	closeChan         chan struct{}
	buffers           []*Buffer
	activeBuffer      *Buffer
	mouseMode         MouseMode
	mouseExtMode      MouseExtMode
	theme             *Theme
	renderDebounce    *time.Timer
	redraw            bool
}

// NewTerminal creates a new terminal instance
func New(options ...Option) *Terminal {
	term := &Terminal{
		processChan: make(chan MeasuredRune, 0xffff),
		closeChan:   make(chan struct{}),
		theme:       &Theme{},
	}
	for _, opt := range options {


@@ 119,8 116,7 @@ func (t *Terminal) SetSize(rows, cols uint16) error {
}

// Run starts the terminal/shell proxying process
func (t *Terminal) Run(c *exec.Cmd, updateChan chan struct{}, rows uint16, cols uint16) error {
	t.updateChan = updateChan
func (t *Terminal) Run(c *exec.Cmd, rows uint16, cols uint16) error {
	c.Env = append(os.Environ(), "TERM=xterm-256color")

	// Start the command with a pty.


@@ 148,34 144,29 @@ func (t *Terminal) Run(c *exec.Cmd, updateChan chan struct{}, rows uint16, cols 
	go t.process()

	_, _ = io.Copy(t, t.pty)
	close(t.closeChan)
	return nil
}

func (t *Terminal) requestRender() {
	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) ShouldRedraw() bool {
	return t.redraw
}

func (t *Terminal) SetRedraw(b bool) {
	t.redraw = b
}

func (t *Terminal) process() {
	for {
		select {
		case <-t.closeChan:
		mr, ok := <-t.processChan
		if !ok {
			return
		case mr := <-t.processChan:
			if mr.Rune == 0x1b { // ANSI escape char, which means this is a sequence
				if t.handleANSI(t.processChan) {
					t.requestRender()
				}
			} else if t.processRunes(mr) { // otherwise it's just an individual rune we need to process
				t.requestRender()
		}
		if mr.Rune == 0x1b { // ANSI escape char, which means this is a sequence
			if t.handleANSI(t.processChan) {
				t.SetRedraw(true)
			}
		} else if t.processRunes(mr) { // otherwise it's just an individual rune we need to process
			t.SetRedraw(true)
		}
	}
}