diff --git a/signals_unix.go b/signals_unix.go index ef9ed9a3ad..826f58b98f 100644 --- a/signals_unix.go +++ b/signals_unix.go @@ -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() } } diff --git a/signals_windows.go b/signals_windows.go index c60ca57bcf..2fc6f8ae79 100644 --- a/signals_windows.go +++ b/signals_windows.go @@ -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) } diff --git a/tea.go b/tea.go index d6ba73c6c2..9e73589e60 100644 --- a/tea.go +++ b/tea.go @@ -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) } @@ -574,6 +563,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 } diff --git a/tty.go b/tty.go index 4f33d86602..3ab6639b75 100644 --- a/tty.go +++ b/tty.go @@ -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, + }) +}