1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
|
package xpty
import (
"context"
"errors"
"io"
"os"
"os/exec"
"runtime"
"github.com/charmbracelet/x/term"
"github.com/creack/pty"
)
// ErrUnsupported is returned when a feature is not supported.
var ErrUnsupported = pty.ErrUnsupported
// Pty represents a PTY (pseudo-terminal) interface.
type Pty interface {
term.File
io.ReadWriteCloser
// Resize resizes the PTY.
Resize(width, height int) error
// Size returns the size of the PTY.
Size() (width, height int, err error)
// Name returns the name of the PTY.
Name() string
// Start starts a command on the PTY.
// The command started will have its standard input, output, and error
// connected to the PTY.
// On Windows, calling Wait won't work since the Go runtime doesn't handle
// ConPTY processes correctly. See https://github.com/golang/go/pull/62710.
Start(cmd *exec.Cmd) error
}
// Options represents PTY options.
type Options struct {
Flags int
}
// PtyOption is a PTY option.
type PtyOption func(o Options)
// NewPty creates a new PTY.
//
// The returned PTY will be a Unix PTY on Unix systems and a ConPTY on Windows.
// The width and height parameters specify the initial size of the PTY.
// You can pass additional options to the PTY by passing PtyOptions.
//
// pty, err := xpty.NewPty(80, 24)
// if err != nil {
// // handle error
// }
//
// defer pty.Close() // Make sure to close the PTY when done.
// switch pty := pty.(type) {
// case xpty.UnixPty:
// // Unix PTY
// case xpty.ConPty:
// // ConPTY
// }
func NewPty(width, height int, opts ...PtyOption) (Pty, error) {
if runtime.GOOS == "windows" {
return NewConPty(width, height, opts...)
}
return NewUnixPty(width, height, opts...)
}
// WaitProcess waits for the process to exit.
// This exists because on Windows, cmd.Wait() doesn't work with ConPty.
func WaitProcess(ctx context.Context, c *exec.Cmd) (err error) {
if c.Process == nil {
return errors.New("process not started")
}
type result struct {
*os.ProcessState
error
}
donec := make(chan result, 1)
go func() {
state, err := c.Process.Wait()
donec <- result{state, err}
}()
select {
case <-ctx.Done():
err = c.Process.Kill()
case r := <-donec:
c.ProcessState = r.ProcessState
err = r.error
}
return
}
|