From 2f53eeb54b9019f7ae3673f502c70b9c0a032064 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 13 Jul 2020 11:39:04 -0400 Subject: [PATCH] Fix a bug in macOS terminal where stuff could get sucked into the scroll area --- renderer.go | 12 +++++++++++- tea.go | 38 ++++++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/renderer.go b/renderer.go index d747f4a..9143aac 100644 --- a/renderer.go +++ b/renderer.go @@ -29,6 +29,9 @@ type renderer struct { lastRender string linesRendered int + // essentially whether or not we're using the full size of the terminal + altScreenActive bool + // renderer dimensions; usually the size of the window width int height int @@ -156,7 +159,14 @@ func (r *renderer) flush() { // Make sure the cursor is at the start of the last line to keep rendering // behavior consistent. - cursorBack(out, r.width) + if r.altScreenActive { + // We need this case to fix a bug in macOS terminal. In other terminals + // the below case seems to do the job regardless of whether or not we're + // using the full terminal window. + moveCursor(out, r.linesRendered, 0) + } else { + cursorBack(out, r.width) + } _, _ = r.out.Write(out.Bytes()) r.lastRender = r.buf.String() diff --git a/tea.go b/tea.go index a48a8dc..6bb6318 100644 --- a/tea.go +++ b/tea.go @@ -53,8 +53,10 @@ type Program struct { update Update view View - mtx sync.Mutex - output *os.File // where to send output. this will usually be os.Stdout. + mtx sync.Mutex + output *os.File // where to send output. this will usually be os.Stdout. + renderer *renderer + altScreenActive bool } // Quit is a special command that tells the program to exit. @@ -91,13 +93,14 @@ func NewProgram(init Init, update Update, view View) *Program { // Start initializes the program. func (p *Program) Start() error { var ( - cmds = make(chan Cmd) - msgs = make(chan Msg) - errs = make(chan error) - done = make(chan struct{}) - mrRenderer = newRenderer(p.output, &p.mtx) + cmds = make(chan Cmd) + msgs = make(chan Msg) + errs = make(chan error) + done = make(chan struct{}) ) + p.renderer = newRenderer(p.output, &p.mtx) + err := initTerminal() if err != nil { return err @@ -113,10 +116,11 @@ func (p *Program) Start() error { } // Start renderer - mrRenderer.start() + p.renderer.start() + p.renderer.altScreenActive = p.altScreenActive // Render initial view - mrRenderer.write(p.view(model)) + p.renderer.write(p.view(model)) // Subscribe to user input go func() { @@ -167,7 +171,7 @@ func (p *Program) Start() error { // Handle quit message if _, ok := msg.(quitMsg); ok { - mrRenderer.stop() + p.renderer.stop() close(done) return nil } @@ -181,11 +185,11 @@ func (p *Program) Start() error { } // Process internal messages for the renderer - mrRenderer.handleMessages(msg) + p.renderer.handleMessages(msg) var cmd Cmd model, cmd = p.update(msg, model) // run update cmds <- cmd // process command (if any) - mrRenderer.write(p.view(model)) // send view to renderer + p.renderer.write(p.view(model)) // send view to renderer } } } @@ -196,6 +200,11 @@ func (p *Program) EnterAltScreen() { defer p.mtx.Unlock() fmt.Fprintf(p.output, te.CSI+te.AltScreenSeq) moveCursor(p.output, 0, 0) + + p.altScreenActive = true + if p.renderer != nil { + p.renderer.altScreenActive = p.altScreenActive + } } // ExitAltScreen exits the alternate screen buffer. @@ -203,6 +212,11 @@ func (p *Program) ExitAltScreen() { p.mtx.Lock() defer p.mtx.Unlock() fmt.Fprintf(p.output, te.CSI+te.ExitAltScreenSeq) + + p.altScreenActive = false + if p.renderer != nil { + p.renderer.altScreenActive = p.altScreenActive + } } // EnableMouseCellMotion enables mouse click, release, wheel and motion events if a