fix: detect terminal size after exec

Based on @knz's work in #499, but slightly supersedes this change.

A little more coupling in the resize handling, but a lot less code
& logic repetition.

Co-authored-by: Raphael 'kena' Poss <knz@thaumogen.net>
This commit is contained in:
Christian Muehlhaeuser 2022-10-15 04:50:01 +02:00
parent 9bcfc026a2
commit 80f44c9384
4 changed files with 44 additions and 37 deletions

View File

@ -4,18 +4,15 @@
package tea
import (
"context"
"os"
"os/signal"
"syscall"
"golang.org/x/term"
)
// listenForResize sends messages (or errors) when the terminal resizes.
// Argument output should be the file descriptor for the terminal; usually
// os.Stdout.
func listenForResize(ctx context.Context, output *os.File, msgs chan Msg, errs chan error, done chan struct{}) {
func (p *Program) listenForResize(done chan struct{}) {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGWINCH)
@ -26,20 +23,11 @@ func listenForResize(ctx context.Context, output *os.File, msgs chan Msg, errs c
for {
select {
case <-ctx.Done():
case <-p.ctx.Done():
return
case <-sig:
}
w, h, err := term.GetSize(int(output.Fd()))
if err != nil {
errs <- err
}
select {
case <-ctx.Done():
return
case msgs <- WindowSizeMsg{w, h}:
}
p.checkResize()
}
}

View File

@ -3,15 +3,8 @@
package tea
import (
"context"
"os"
)
// listenForResize is not available on windows because windows does not
// implement syscall.SIGWINCH.
func listenForResize(ctx context.Context, output *os.File, msgs chan Msg,
errs chan error, done chan struct{},
) {
func (p *Program) listenForResize(done chan struct{}) {
close(done)
}

21
tea.go
View File

@ -24,7 +24,6 @@ import (
isatty "github.com/mattn/go-isatty"
"github.com/muesli/cancelreader"
"github.com/muesli/termenv"
"golang.org/x/term"
)
// ErrProgramKilled is returned by [Program.Run] when the program got killed.
@ -205,20 +204,10 @@ func (p *Program) handleResize() chan struct{} {
if f, ok := p.output.TTY().(*os.File); ok && isatty.IsTerminal(f.Fd()) {
// Get the initial terminal size and send it to the program.
go func() {
w, h, err := term.GetSize(int(f.Fd()))
if err != nil {
p.errs <- err
}
select {
case <-p.ctx.Done():
case p.msgs <- WindowSizeMsg{w, h}:
}
}()
go p.checkResize()
// Listen for window resizes.
go listenForResize(p.ctx, f, p.msgs, p.errs, ch)
go p.listenForResize(ch)
} else {
close(ch)
}
@ -577,6 +566,12 @@ func (p *Program) RestoreTerminal() error {
go p.Send(repaintMsg{})
}
// If the output is a terminal, it may have been resized while another
// process was at the foreground, in which case we may not have received
// SIGWINCH. Detect any size change now and propagate the new size as
// needed.
go p.checkResize()
return nil
}

33
tty.go
View File

@ -3,9 +3,12 @@ package tea
import (
"errors"
"io"
"os"
"time"
isatty "github.com/mattn/go-isatty"
"github.com/muesli/cancelreader"
"golang.org/x/term"
)
func (p *Program) initTerminal() error {
@ -76,7 +79,10 @@ func (p *Program) readLoop() {
msgs, err := readInputs(p.cancelReader)
if err != nil {
if !errors.Is(err, io.EOF) && !errors.Is(err, cancelreader.ErrCanceled) {
p.errs <- err
select {
case <-p.ctx.Done():
case p.errs <- err:
}
}
return
@ -98,3 +104,28 @@ func (p *Program) waitForReadLoop() {
// though it was not able to cancel the read.
}
}
// checkResize detects the current size of the output and informs the program
// via a WindowSizeMsg.
func (p *Program) checkResize() {
f, ok := p.output.TTY().(*os.File)
if !ok || !isatty.IsTerminal(f.Fd()) {
// can't query window size
return
}
w, h, err := term.GetSize(int(f.Fd()))
if err != nil {
select {
case <-p.ctx.Done():
case p.errs <- err:
}
return
}
p.Send(WindowSizeMsg{
Width: w,
Height: h,
})
}