package tea import ( "io" "os" "os/exec" ) // execMsg is used internally to run an ExecCommand sent with Exec. type execMsg struct { cmd ExecCommand fn ExecCallback } // Exec is used to perform arbitrary I/O in a blocking fashion, effectively // pausing the Program while execution is running and resuming it when // execution has completed. // // Most of the time you'll want to use ExecProcess, which runs an exec.Cmd. // // For non-interactive i/o you should use a Cmd (that is, a tea.Cmd). func Exec(c ExecCommand, fn ExecCallback) Cmd { return func() Msg { return execMsg{cmd: c, fn: fn} } } // ExecProcess runs the given *exec.Cmd in a blocking fashion, effectively // pausing the Program while the command is running. After the *exec.Cmd exists // the Program resumes. It's useful for spawning other interactive applications // such as editors and shells from within a Program. // // To produce the command, pass an *exec.Cmd and a function which returns // a message containing the error which may have occurred when running the // ExecCommand. // // type VimFinishedMsg struct { err error } // // c := exec.Command("vim", "file.txt") // // cmd := ExecProcess(c, func(err error) Msg { // return VimFinishedMsg{err: err} // }) // // Or, if you don't care about errors, you could simply: // // cmd := ExecProcess(exec.Command("vim", "file.txt"), nil) // // For non-interactive i/o you should use a Cmd (that is, a tea.Cmd). func ExecProcess(c *exec.Cmd, fn ExecCallback) Cmd { return Exec(wrapExecCommand(c), fn) } // ExecCallback is used when executing an *exec.Command to return a message // with an error, which may or may not be nil. type ExecCallback func(error) Msg // ExecCommand can be implemented to execute things in a blocking fashion in // the current terminal. type ExecCommand interface { Run() error SetStdin(io.Reader) SetStdout(io.Writer) SetStderr(io.Writer) } // wrapExecCommand wraps an exec.Cmd so that it satisfies the ExecCommand // interface so it can be used with Exec. func wrapExecCommand(c *exec.Cmd) ExecCommand { return &osExecCommand{Cmd: c} } // osExecCommand is a layer over an exec.Cmd that satisfies the ExecCommand // interface. type osExecCommand struct{ *exec.Cmd } // SetStdin sets stdin on underlying exec.Cmd to the given io.Reader. func (c *osExecCommand) SetStdin(r io.Reader) { // If unset, have the command use the same input as the terminal. if c.Stdin == nil { c.Stdin = r } } // SetStdout sets stdout on underlying exec.Cmd to the given io.Writer. func (c *osExecCommand) SetStdout(w io.Writer) { // If unset, have the command use the same output as the terminal. if c.Stdout == nil { c.Stdout = w } } // SetStderr sets stderr on the underlying exec.Cmd to the given io.Writer. func (c *osExecCommand) SetStderr(w io.Writer) { // If unset, use stderr for the command's stderr if c.Stderr == nil { c.Stderr = w } } // exec runs an ExecCommand and delivers the results to the program as a Msg. func (p *Program) exec(c ExecCommand, fn ExecCallback) { if err := p.ReleaseTerminal(); err != nil { // If we can't release input, abort. if fn != nil { go p.Send(fn(err)) } return } c.SetStdin(p.input) c.SetStdout(p.output.TTY()) c.SetStderr(os.Stderr) // Execute system command. if err := c.Run(); err != nil { _ = p.RestoreTerminal() // also try to restore the terminal. if fn != nil { go p.Send(fn(err)) } return } // Have the program re-capture input. err := p.RestoreTerminal() if fn != nil { go p.Send(fn(err)) } }