diff --git a/cancelreader.go b/cancelreader.go deleted file mode 100644 index 90b22f0e60..0000000000 --- a/cancelreader.go +++ /dev/null @@ -1,72 +0,0 @@ -package tea - -import ( - "fmt" - "io" - "sync" -) - -var errCanceled = fmt.Errorf("read cancelled") - -// cancelReader is a io.Reader whose Read() calls can be cancelled without data -// being consumed. The cancelReader has to be closed. -type cancelReader interface { - io.ReadCloser - - // Cancel cancels ongoing and future reads an returns true if it succeeded. - Cancel() bool -} - -// fallbackCancelReader implements cancelReader but does not actually support -// cancelation during an ongoing Read() call. Thus, Cancel() always returns -// false. However, after calling Cancel(), new Read() calls immediately return -// errCanceled and don't consume any data anymore. -type fallbackCancelReader struct { - r io.Reader - cancelled bool -} - -// newFallbackCancelReader is a fallback for newCancelReader that cannot -// actually cancel an ongoing read but will immediately return on future reads -// if it has been cancelled. -func newFallbackCancelReader(reader io.Reader) (cancelReader, error) { - return &fallbackCancelReader{r: reader}, nil -} - -func (r *fallbackCancelReader) Read(data []byte) (int, error) { - if r.cancelled { - return 0, errCanceled - } - - return r.r.Read(data) -} - -func (r *fallbackCancelReader) Cancel() bool { - r.cancelled = true - - return false -} - -func (r *fallbackCancelReader) Close() error { - return nil -} - -// cancelMixin represents a goroutine-safe cancelation status. -type cancelMixin struct { - unsafeCancelled bool - lock sync.Mutex -} - -func (c *cancelMixin) isCancelled() bool { - c.lock.Lock() - defer c.lock.Unlock() - - return c.unsafeCancelled -} - -func (c *cancelMixin) setCancelled() { - c.lock.Lock() - defer c.lock.Unlock() - - c.unsafeCancelled = true -} diff --git a/cancelreader_bsd.go b/cancelreader_bsd.go deleted file mode 100644 index e122fa2aa9..0000000000 --- a/cancelreader_bsd.go +++ /dev/null @@ -1,146 +0,0 @@ -//go:build darwin || freebsd || netbsd || openbsd || dragonfly -// +build darwin freebsd netbsd openbsd dragonfly - -// nolint:revive -package tea - -import ( - "errors" - "fmt" - "io" - "os" - "strings" - - "golang.org/x/sys/unix" -) - -// newkqueueCancelReader returns a reader and a cancel function. If the input reader -// is an *os.File, the cancel function can be used to interrupt a blocking call -// read call. In this case, the cancel function returns true if the call was -// cancelled successfully. If the input reader is not a *os.File, the cancel -// function does nothing and always returns false. The BSD and macOS -// implementation is based on the kqueue mechanism. -func newCancelReader(reader io.Reader) (cancelReader, error) { - file, ok := reader.(*os.File) - if !ok { - return newFallbackCancelReader(reader) - } - - // kqueue returns instantly when polling /dev/tty so fallback to select - if file.Name() == "/dev/tty" { - return newSelectCancelReader(reader) - } - - kQueue, err := unix.Kqueue() - if err != nil { - return nil, fmt.Errorf("create kqueue: %w", err) - } - - r := &kqueueCancelReader{ - file: file, - kQueue: kQueue, - } - - r.cancelSignalReader, r.cancelSignalWriter, err = os.Pipe() - if err != nil { - return nil, err - } - - unix.SetKevent(&r.kQueueEvents[0], int(file.Fd()), unix.EVFILT_READ, unix.EV_ADD) - unix.SetKevent(&r.kQueueEvents[1], int(r.cancelSignalReader.Fd()), unix.EVFILT_READ, unix.EV_ADD) - - return r, nil -} - -type kqueueCancelReader struct { - file *os.File - cancelSignalReader *os.File - cancelSignalWriter *os.File - cancelMixin - kQueue int - kQueueEvents [2]unix.Kevent_t -} - -func (r *kqueueCancelReader) Read(data []byte) (int, error) { - if r.isCancelled() { - return 0, errCanceled - } - - err := r.wait() - if err != nil { - if errors.Is(err, errCanceled) { - // remove signal from pipe - var b [1]byte - _, errRead := r.cancelSignalReader.Read(b[:]) - if errRead != nil { - return 0, fmt.Errorf("reading cancel signal: %w", errRead) - } - } - - return 0, err - } - - return r.file.Read(data) -} - -func (r *kqueueCancelReader) Cancel() bool { - r.setCancelled() - - // send cancel signal - _, err := r.cancelSignalWriter.Write([]byte{'c'}) - return err == nil -} - -func (r *kqueueCancelReader) Close() error { - var errMsgs []string - - // close kqueue - err := unix.Close(r.kQueue) - if err != nil { - errMsgs = append(errMsgs, fmt.Sprintf("closing kqueue: %v", err)) - } - - // close pipe - err = r.cancelSignalWriter.Close() - if err != nil { - errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal writer: %v", err)) - } - - err = r.cancelSignalReader.Close() - if err != nil { - errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal reader: %v", err)) - } - - if len(errMsgs) > 0 { - return fmt.Errorf(strings.Join(errMsgs, ", ")) - } - - return nil -} - -func (r *kqueueCancelReader) wait() error { - events := make([]unix.Kevent_t, 1) - - for { - _, err := unix.Kevent(r.kQueue, r.kQueueEvents[:], events, nil) - if errors.Is(err, unix.EINTR) { - continue // try again if the syscall was interrupted - } - - if err != nil { - return fmt.Errorf("kevent: %w", err) - } - - break - } - - ident := uint64(events[0].Ident) - switch ident { - case uint64(r.file.Fd()): - return nil - case uint64(r.cancelSignalReader.Fd()): - return errCanceled - } - - return fmt.Errorf("unknown error") -} diff --git a/cancelreader_default.go b/cancelreader_default.go deleted file mode 100644 index eba557e547..0000000000 --- a/cancelreader_default.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build !darwin && !windows && !linux && !solaris && !freebsd && !netbsd && !openbsd && !dragonfly -// +build !darwin,!windows,!linux,!solaris,!freebsd,!netbsd,!openbsd,!dragonfly - -package tea - -import ( - "io" -) - -// newCancelReader returns a fallbackCancelReader that satisfies the -// cancelReader but does not actually support cancelation. -func newCancelReader(reader io.Reader) (cancelReader, error) { - return newFallbackCancelReader(reader) -} diff --git a/cancelreader_linux.go b/cancelreader_linux.go deleted file mode 100644 index 343f7d1c98..0000000000 --- a/cancelreader_linux.go +++ /dev/null @@ -1,152 +0,0 @@ -//go:build linux -// +build linux - -// nolint:revive -package tea - -import ( - "errors" - "fmt" - "io" - "os" - "strings" - - "golang.org/x/sys/unix" -) - -// newCancelReader returns a reader and a cancel function. If the input reader -// is an *os.File, the cancel function can be used to interrupt a blocking call -// read call. In this case, the cancel function returns true if the call was -// cancelled successfully. If the input reader is not a *os.File, the cancel -// function does nothing and always returns false. The linux implementation is -// based on the epoll mechanism. -func newCancelReader(reader io.Reader) (cancelReader, error) { - file, ok := reader.(*os.File) - if !ok { - return newFallbackCancelReader(reader) - } - - epoll, err := unix.EpollCreate1(0) - if err != nil { - return nil, fmt.Errorf("create epoll: %w", err) - } - - r := &epollCancelReader{ - file: file, - epoll: epoll, - } - - r.cancelSignalReader, r.cancelSignalWriter, err = os.Pipe() - if err != nil { - return nil, err - } - - err = unix.EpollCtl(epoll, unix.EPOLL_CTL_ADD, int(file.Fd()), &unix.EpollEvent{ - Events: unix.EPOLLIN, - Fd: int32(file.Fd()), - }) - if err != nil { - return nil, fmt.Errorf("add reader to epoll interrest list") - } - - err = unix.EpollCtl(epoll, unix.EPOLL_CTL_ADD, int(r.cancelSignalReader.Fd()), &unix.EpollEvent{ - Events: unix.EPOLLIN, - Fd: int32(r.cancelSignalReader.Fd()), - }) - if err != nil { - return nil, fmt.Errorf("add reader to epoll interrest list") - } - - return r, nil -} - -type epollCancelReader struct { - file *os.File - cancelSignalReader *os.File - cancelSignalWriter *os.File - cancelMixin - epoll int -} - -func (r *epollCancelReader) Read(data []byte) (int, error) { - if r.isCancelled() { - return 0, errCanceled - } - - err := r.wait() - if err != nil { - if errors.Is(err, errCanceled) { - // remove signal from pipe - var b [1]byte - _, readErr := r.cancelSignalReader.Read(b[:]) - if readErr != nil { - return 0, fmt.Errorf("reading cancel signal: %w", readErr) - } - } - - return 0, err - } - - return r.file.Read(data) -} - -func (r *epollCancelReader) Cancel() bool { - r.setCancelled() - - // send cancel signal - _, err := r.cancelSignalWriter.Write([]byte{'c'}) - return err == nil -} - -func (r *epollCancelReader) Close() error { - var errMsgs []string - - // close kqueue - err := unix.Close(r.epoll) - if err != nil { - errMsgs = append(errMsgs, fmt.Sprintf("closing epoll: %v", err)) - } - - // close pipe - err = r.cancelSignalWriter.Close() - if err != nil { - errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal writer: %v", err)) - } - - err = r.cancelSignalReader.Close() - if err != nil { - errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal reader: %v", err)) - } - - if len(errMsgs) > 0 { - return fmt.Errorf(strings.Join(errMsgs, ", ")) - } - - return nil -} - -func (r *epollCancelReader) wait() error { - events := make([]unix.EpollEvent, 1) - - for { - _, err := unix.EpollWait(r.epoll, events, -1) - if errors.Is(err, unix.EINTR) { - continue // try again if the syscall was interrupted - } - - if err != nil { - return fmt.Errorf("kevent: %w", err) - } - - break - } - - switch events[0].Fd { - case int32(r.file.Fd()): - return nil - case int32(r.cancelSignalReader.Fd()): - return errCanceled - } - - return fmt.Errorf("unknown error") -} diff --git a/cancelreader_select.go b/cancelreader_select.go deleted file mode 100644 index 1749945c96..0000000000 --- a/cancelreader_select.go +++ /dev/null @@ -1,137 +0,0 @@ -//go:build solaris || darwin || freebsd || netbsd || openbsd || dragonfly -// +build solaris darwin freebsd netbsd openbsd dragonfly - -// nolint:revive -package tea - -import ( - "errors" - "fmt" - "io" - "os" - "strings" - - "golang.org/x/sys/unix" -) - -// newSelectCancelReader returns a reader and a cancel function. If the input -// reader is an *os.File, the cancel function can be used to interrupt a -// blocking call read call. In this case, the cancel function returns true if -// the call was cancelled successfully. If the input reader is not a *os.File or -// the file descriptor is 1024 or larger, the cancel function does nothing and -// always returns false. The generic unix implementation is based on the posix -// select syscall. -func newSelectCancelReader(reader io.Reader) (cancelReader, error) { - file, ok := reader.(*os.File) - if !ok || file.Fd() >= unix.FD_SETSIZE { - return newFallbackCancelReader(reader) - } - r := &selectCancelReader{file: file} - - var err error - - r.cancelSignalReader, r.cancelSignalWriter, err = os.Pipe() - if err != nil { - return nil, err - } - - return r, nil -} - -type selectCancelReader struct { - file *os.File - cancelSignalReader *os.File - cancelSignalWriter *os.File - cancelMixin -} - -func (r *selectCancelReader) Read(data []byte) (int, error) { - if r.isCancelled() { - return 0, errCanceled - } - - for { - err := waitForRead(r.file, r.cancelSignalReader) - if err != nil { - if errors.Is(err, unix.EINTR) { - continue // try again if the syscall was interrupted - } - - if errors.Is(err, errCanceled) { - // remove signal from pipe - var b [1]byte - _, readErr := r.cancelSignalReader.Read(b[:]) - if readErr != nil { - return 0, fmt.Errorf("reading cancel signal: %w", readErr) - } - } - - return 0, err - } - - return r.file.Read(data) - } -} - -func (r *selectCancelReader) Cancel() bool { - r.setCancelled() - - // send cancel signal - _, err := r.cancelSignalWriter.Write([]byte{'c'}) - return err == nil -} - -func (r *selectCancelReader) Close() error { - var errMsgs []string - - // close pipe - err := r.cancelSignalWriter.Close() - if err != nil { - errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal writer: %v", err)) - } - - err = r.cancelSignalReader.Close() - if err != nil { - errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal reader: %v", err)) - } - - if len(errMsgs) > 0 { - return fmt.Errorf(strings.Join(errMsgs, ", ")) - } - - return nil -} - -func waitForRead(reader *os.File, abort *os.File) error { - readerFd := int(reader.Fd()) - abortFd := int(abort.Fd()) - - maxFd := readerFd - if abortFd > maxFd { - maxFd = abortFd - } - - // this is a limitation of the select syscall - if maxFd >= unix.FD_SETSIZE { - return fmt.Errorf("cannot select on file descriptor %d which is larger than 1024", maxFd) - } - - fdSet := &unix.FdSet{} - fdSet.Set(int(reader.Fd())) - fdSet.Set(int(abort.Fd())) - - _, err := unix.Select(maxFd+1, fdSet, nil, nil, nil) - if err != nil { - return fmt.Errorf("select: %w", err) - } - - if fdSet.IsSet(abortFd) { - return errCanceled - } - - if fdSet.IsSet(readerFd) { - return nil - } - - return fmt.Errorf("select returned without setting a file descriptor") -} diff --git a/cancelreader_unix.go b/cancelreader_unix.go deleted file mode 100644 index c69db614c2..0000000000 --- a/cancelreader_unix.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build solaris -// +build solaris - -// nolint:revive -package tea - -import ( - "io" -) - -// newCancelReader returns a reader and a cancel function. If the input reader -// is an *os.File, the cancel function can be used to interrupt a blocking call -// read call. In this case, the cancel function returns true if the call was -// cancelled successfully. If the input reader is not a *os.File or the file -// descriptor is 1024 or larger, the cancel function does nothing and always -// returns false. The generic unix implementation is based on the posix select -// syscall. -func newCancelReader(reader io.Reader) (cancelReader, error) { - return newSelectCancelReader(reader) -} diff --git a/cancelreader_windows.go b/cancelreader_windows.go deleted file mode 100644 index 0bd75d69b7..0000000000 --- a/cancelreader_windows.go +++ /dev/null @@ -1,245 +0,0 @@ -//go:build windows -// +build windows - -package tea - -import ( - "fmt" - "io" - "os" - "syscall" - "time" - "unicode/utf16" - - "golang.org/x/sys/windows" -) - -var fileShareValidFlags uint32 = 0x00000007 - -// newCancelReader returns a reader and a cancel function. If the input reader -// is an *os.File with the same file descriptor as os.Stdin, the cancel function -// can be used to interrupt a blocking call read call. In this case, the cancel -// function returns true if the call was cancelled successfully. If the input -// reader is not a *os.File with the same file descriptor as os.Stdin, the -// cancel function does nothing and always returns false. The Windows -// implementation is based on WaitForMultipleObject with overlapping reads from -// CONIN$. -func newCancelReader(reader io.Reader) (cancelReader, error) { - if f, ok := reader.(*os.File); !ok || f.Fd() != os.Stdin.Fd() { - return newFallbackCancelReader(reader) - } - - // it is neccessary to open CONIN$ (NOT windows.STD_INPUT_HANDLE) in - // overlapped mode to be able to use it with WaitForMultipleObjects. - conin, err := windows.CreateFile( - &(utf16.Encode([]rune("CONIN$\x00"))[0]), windows.GENERIC_READ|windows.GENERIC_WRITE, - fileShareValidFlags, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_OVERLAPPED, 0) - if err != nil { - return nil, fmt.Errorf("open CONIN$ in overlapping mode: %w", err) - } - - resetConsole, err := prepareConsole(conin) - if err != nil { - return nil, fmt.Errorf("prepare console: %w", err) - } - - // flush input, otherwise it can contain events which trigger - // WaitForMultipleObjects but which ReadFile cannot read, resulting in an - // un-cancelable read - err = flushConsoleInputBuffer(conin) - if err != nil { - return nil, fmt.Errorf("flush console input buffer: %w", err) - } - - cancelEvent, err := windows.CreateEvent(nil, 0, 0, nil) - if err != nil { - return nil, fmt.Errorf("create stop event: %w", err) - } - - return &winCancelReader{ - conin: conin, - cancelEvent: cancelEvent, - resetConsole: resetConsole, - blockingReadSignal: make(chan struct{}, 1), - }, nil -} - -type winCancelReader struct { - conin windows.Handle - cancelEvent windows.Handle - cancelMixin - - resetConsole func() error - blockingReadSignal chan struct{} -} - -func (r *winCancelReader) Read(data []byte) (int, error) { - if r.isCancelled() { - return 0, errCanceled - } - - err := r.wait() - if err != nil { - return 0, err - } - - if r.isCancelled() { - return 0, errCanceled - } - - // windows.Read does not work on overlapping windows.Handles - return r.readAsync(data) -} - -// Cancel cancels ongoing and future Read() calls and returns true if the -// cancelation of the ongoing Read() was successful. On Windows Terminal, -// WaitForMultipleObjects sometimes immediately returns without input being -// available. In this case, graceful cancelation is not possible and Cancel() -// returns false. -func (r *winCancelReader) Cancel() bool { - r.setCancelled() - - select { - case r.blockingReadSignal <- struct{}{}: - err := windows.SetEvent(r.cancelEvent) - if err != nil { - return false - } - <-r.blockingReadSignal - case <-time.After(100 * time.Millisecond): - // Read() hangs in a GetOverlappedResult which is likely due to - // WaitForMultipleObjects returning without input being available - // so we cannot cancel this ongoing read. - return false - } - - return true -} - -func (r *winCancelReader) Close() error { - err := windows.CloseHandle(r.cancelEvent) - if err != nil { - return fmt.Errorf("closing cancel event handle: %w", err) - } - - err = r.resetConsole() - if err != nil { - return err - } - - err = windows.Close(r.conin) - if err != nil { - return fmt.Errorf("closing CONIN$") - } - - return nil -} - -func (r *winCancelReader) wait() error { - event, err := windows.WaitForMultipleObjects([]windows.Handle{r.conin, r.cancelEvent}, false, windows.INFINITE) - switch { - case windows.WAIT_OBJECT_0 <= event && event < windows.WAIT_OBJECT_0+2: - if event == windows.WAIT_OBJECT_0+1 { - return errCanceled - } - - if event == windows.WAIT_OBJECT_0 { - return nil - } - - return fmt.Errorf("unexpected wait object is ready: %d", event-windows.WAIT_OBJECT_0) - case windows.WAIT_ABANDONED <= event && event < windows.WAIT_ABANDONED+2: - return fmt.Errorf("abandoned") - case event == uint32(windows.WAIT_TIMEOUT): - return fmt.Errorf("timeout") - case event == windows.WAIT_FAILED: - return fmt.Errorf("failed") - default: - return fmt.Errorf("unexpected error: %w", error(err)) - } -} - -// readAsync is neccessary to read from a windows.Handle in overlapping mode. -func (r *winCancelReader) readAsync(data []byte) (int, error) { - hevent, err := windows.CreateEvent(nil, 0, 0, nil) - if err != nil { - return 0, fmt.Errorf("create event: %w", err) - } - - overlapped := windows.Overlapped{ - HEvent: hevent, - } - - var n uint32 - - err = windows.ReadFile(r.conin, data, &n, &overlapped) - if err != nil && err != windows.ERROR_IO_PENDING { - return int(n), err - } - - r.blockingReadSignal <- struct{}{} - err = windows.GetOverlappedResult(r.conin, &overlapped, &n, true) - if err != nil { - return int(n), nil - } - <-r.blockingReadSignal - - return int(n), nil -} - -func prepareConsole(input windows.Handle) (reset func() error, err error) { - var originalMode uint32 - - err = windows.GetConsoleMode(input, &originalMode) - if err != nil { - return nil, fmt.Errorf("get console mode: %w", err) - } - - var newMode uint32 - newMode &^= windows.ENABLE_ECHO_INPUT - newMode &^= windows.ENABLE_LINE_INPUT - newMode &^= windows.ENABLE_MOUSE_INPUT - newMode &^= windows.ENABLE_WINDOW_INPUT - newMode &^= windows.ENABLE_PROCESSED_INPUT - - newMode |= windows.ENABLE_EXTENDED_FLAGS - newMode |= windows.ENABLE_INSERT_MODE - newMode |= windows.ENABLE_QUICK_EDIT_MODE - - // Enabling virutal terminal input is necessary for processing certain - // types of input like X10 mouse events and arrows keys with the current - // bytes-based input reader. It does, however, prevent cancelReader from - // being able to cancel input. The planned solution for this is to read - // Windows events in a more native fashion, rather than the current simple - // bytes-based input reader which works well on unix systems. - newMode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT - - err = windows.SetConsoleMode(input, newMode) - if err != nil { - return nil, fmt.Errorf("set console mode: %w", err) - } - - return func() error { - err := windows.SetConsoleMode(input, originalMode) - if err != nil { - return fmt.Errorf("reset console mode: %w", err) - } - - return nil - }, nil -} - -var ( - modkernel32 = windows.NewLazySystemDLL("kernel32.dll") - procFlushConsoleInputBuffer = modkernel32.NewProc("FlushConsoleInputBuffer") -) - -func flushConsoleInputBuffer(consoleInput windows.Handle) error { - r, _, e := syscall.Syscall(procFlushConsoleInputBuffer.Addr(), 1, - uintptr(consoleInput), 0, 0) - if r == 0 { - return error(e) - } - - return nil -} diff --git a/examples/go.sum b/examples/go.sum index 80b316bfb3..e9df944259 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -40,6 +40,8 @@ github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY6 github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.0 h1:SOpr+CfyVNce341kKqvbhhzQhBPyJRXQaCtn03Pae1Q= +github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= @@ -73,6 +75,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/go.mod b/go.mod index 878c5104a1..805e0769ab 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/containerd/console v1.0.3 github.com/mattn/go-isatty v0.0.14 github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b + github.com/muesli/cancelreader v0.2.0 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 golang.org/x/sys v0.0.0-20220209214540-3681064d5158 diff --git a/go.sum b/go.sum index 80de0607ee..fadb408309 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.0 h1:SOpr+CfyVNce341kKqvbhhzQhBPyJRXQaCtn03Pae1Q= +github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI= @@ -19,6 +21,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= diff --git a/tea.go b/tea.go index 83e96a18d8..fab77d8bf0 100644 --- a/tea.go +++ b/tea.go @@ -23,6 +23,7 @@ import ( "github.com/containerd/console" isatty "github.com/mattn/go-isatty" + "github.com/muesli/cancelreader" te "github.com/muesli/termenv" "golang.org/x/term" ) @@ -404,7 +405,7 @@ func (p *Program) StartReturningModel() (Model, error) { // Render the initial view. p.renderer.write(model.View()) - cancelReader, err := newCancelReader(p.input) + cancelReader, err := cancelreader.NewReader(p.input) if err != nil { return model, err } @@ -423,7 +424,7 @@ func (p *Program) StartReturningModel() (Model, error) { msgs, err := readInputs(cancelReader) if err != nil { - if !errors.Is(err, io.EOF) && !errors.Is(err, errCanceled) { + if !errors.Is(err, io.EOF) && !errors.Is(err, cancelreader.ErrCanceled) { errs <- err } diff --git a/tutorials/go.sum b/tutorials/go.sum index 80de0607ee..fadb408309 100644 --- a/tutorials/go.sum +++ b/tutorials/go.sum @@ -9,6 +9,8 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.0 h1:SOpr+CfyVNce341kKqvbhhzQhBPyJRXQaCtn03Pae1Q= +github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI= @@ -19,6 +21,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=