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

View File

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

21
tea.go
View File

@@ -24,7 +24,6 @@ import (
isatty "github.com/mattn/go-isatty" isatty "github.com/mattn/go-isatty"
"github.com/muesli/cancelreader" "github.com/muesli/cancelreader"
"github.com/muesli/termenv" "github.com/muesli/termenv"
"golang.org/x/term"
) )
// ErrProgramKilled is returned by [Program.Run] when the program got killed. // 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()) { if f, ok := p.output.TTY().(*os.File); ok && isatty.IsTerminal(f.Fd()) {
// Get the initial terminal size and send it to the program. // Get the initial terminal size and send it to the program.
go func() { go p.checkResize()
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}:
}
}()
// Listen for window resizes. // Listen for window resizes.
go listenForResize(p.ctx, f, p.msgs, p.errs, ch) go p.listenForResize(ch)
} else { } else {
close(ch) close(ch)
} }
@@ -577,6 +566,12 @@ func (p *Program) RestoreTerminal() error {
go p.Send(repaintMsg{}) 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 return nil
} }

33
tty.go
View File

@@ -3,9 +3,12 @@ package tea
import ( import (
"errors" "errors"
"io" "io"
"os"
"time" "time"
isatty "github.com/mattn/go-isatty"
"github.com/muesli/cancelreader" "github.com/muesli/cancelreader"
"golang.org/x/term"
) )
func (p *Program) initTerminal() error { func (p *Program) initTerminal() error {
@@ -76,7 +79,10 @@ func (p *Program) readLoop() {
msgs, err := readInputs(p.cancelReader) msgs, err := readInputs(p.cancelReader)
if err != nil { if err != nil {
if !errors.Is(err, io.EOF) && !errors.Is(err, cancelreader.ErrCanceled) { if !errors.Is(err, io.EOF) && !errors.Is(err, cancelreader.ErrCanceled) {
p.errs <- err select {
case <-p.ctx.Done():
case p.errs <- err:
}
} }
return return
@@ -98,3 +104,28 @@ func (p *Program) waitForReadLoop() {
// though it was not able to cancel the read. // 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,
})
}