diff --git a/helper/wrappedreadline/wrappedreadline.go b/helper/wrappedreadline/wrappedreadline.go new file mode 100644 index 0000000000..08314a1f3f --- /dev/null +++ b/helper/wrappedreadline/wrappedreadline.go @@ -0,0 +1,84 @@ +// wrappedreadline is a package that has helpers for interacting with +// readline from a panicwrap executable. +// +// panicwrap overrides the standard file descriptors so that the child process +// no longer looks like a TTY. The helpers here access the extra file descriptors +// passed by panicwrap to fix that. +package wrappedreadline + +import ( + "os" + "runtime" + + "github.com/chzyer/readline" +) + +// These are the file descriptor numbers for the original stdin, stdout, stderr +// streams from the parent process. +const ( + StdinFd = 3 + StdoutFd = 4 + StderrFd = 5 +) + +// These are the *os.File values for the standard streams. +var ( + Stdin = os.NewFile(uintptr(StdinFd), "stdin") + Stdout = os.NewFile(uintptr(StdoutFd), "stdout") + Stderr = os.NewFile(uintptr(StderrFd), "stderr") +) + +// Override overrides the values in readline.Config that need to be +// set with wrapped values. +func Override(cfg *readline.Config) *readline.Config { + cfg.Stdin = Stdin + cfg.Stdout = Stdout + cfg.Stderr = Stderr + + cfg.FuncGetWidth = TerminalWidth + cfg.FuncIsTerminal = IsTerminal + + var rm RawMode + cfg.FuncMakeRaw = rm.Enter + cfg.FuncExitRaw = rm.Exit + + return cfg +} + +// IsTerminal determines if this process is attached to a TTY. +func IsTerminal() bool { + // Windows is always a terminal + if runtime.GOOS == "windows" { + return true + } + + // Same implementation as readline but with our custom fds + return readline.IsTerminal(StdinFd) && (readline.IsTerminal(StdoutFd) || readline.IsTerminal(StderrFd)) +} + +// TerminalWidth gets the terminal width in characters. +func TerminalWidth() int { + if runtime.GOOS == "windows" { + return readline.GetScreenWidth() + } + + return getWidth() +} + +// RawMode is a helper for entering and exiting raw mode. +type RawMode struct { + state *readline.State +} + +func (r *RawMode) Enter() (err error) { + r.state, err = readline.MakeRaw(StdinFd) + return err +} + +func (r *RawMode) Exit() error { + if r.state == nil { + return nil + } + + return readline.Restore(StdinFd, r.state) +} diff --git a/helper/wrappedreadline/wrappedreadline_unix.go b/helper/wrappedreadline/wrappedreadline_unix.go new file mode 100644 index 0000000000..ba7246265d --- /dev/null +++ b/helper/wrappedreadline/wrappedreadline_unix.go @@ -0,0 +1,41 @@ +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd + +package wrappedreadline + +import ( + "syscall" + "unsafe" +) + +// getWidth impl for Unix +func getWidth() int { + w := getWidthFd(StdoutFd) + if w < 0 { + w = getWidthFd(StderrFd) + } + + return w +} + +type winsize struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 +} + +// get width of the terminal +func getWidthFd(stdoutFd int) int { + ws := &winsize{} + retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL, + uintptr(stdoutFd), + uintptr(syscall.TIOCGWINSZ), + uintptr(unsafe.Pointer(ws))) + + if int(retCode) == -1 { + _ = errno + return -1 + } + + return int(ws.Col) +} diff --git a/helper/wrappedreadline/wrappedreadline_windows.go b/helper/wrappedreadline/wrappedreadline_windows.go new file mode 100644 index 0000000000..110ee5197e --- /dev/null +++ b/helper/wrappedreadline/wrappedreadline_windows.go @@ -0,0 +1,8 @@ +// +build windows + +package wrappedreadline + +// getWidth impl for other +func getWidth() int { + return 0 +}