forked from Mirrors/bubbletea
chore(exec): small API edits (#323)
* feat: add OSExec helper function for running exec.Cmds * chore: for now, un-expose WrapExecCommand * chore: move exec struff into its own file * chore(exec): better name for Exec func that wraps exec.Cmd (thanks, @toby)
This commit is contained in:
parent
97050569c9
commit
775dbfbeff
|
@ -12,7 +12,7 @@ type editorFinishedMsg struct{ err error }
|
||||||
|
|
||||||
func openEditor() tea.Cmd {
|
func openEditor() tea.Cmd {
|
||||||
c := exec.Command(os.Getenv("EDITOR")) //nolint:gosec
|
c := exec.Command(os.Getenv("EDITOR")) //nolint:gosec
|
||||||
return tea.Exec(tea.WrapExecCommand(c), func(err error) tea.Msg {
|
return tea.ExecProcess(c, func(err error) tea.Msg {
|
||||||
return editorFinishedMsg{err}
|
return editorFinishedMsg{err}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
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 runnning 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: error}
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// 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)
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
114
tea.go
114
tea.go
|
@ -14,7 +14,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -246,44 +245,6 @@ func HideCursor() Msg {
|
||||||
return hideCursorMsg{}
|
return hideCursorMsg{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec runs the given ExecCommand 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 ExecCommand 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 := Exec(WrapExecCommand(c), func(err error) Msg {
|
|
||||||
// return VimFinishedMsg{err: error}
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// Or, if you don't care about errors you could simply:
|
|
||||||
//
|
|
||||||
// cmd := Exec(WrapExecCommand(exec.Command("vim", "file.txt")), nil)
|
|
||||||
//
|
|
||||||
// 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}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// execMsg is used internally to run an ExecCommand sent with Exec.
|
|
||||||
type execMsg struct {
|
|
||||||
cmd ExecCommand
|
|
||||||
fn ExecCallback
|
|
||||||
}
|
|
||||||
|
|
||||||
// hideCursorMsg is an internal command used to hide the cursor. You can send
|
// hideCursorMsg is an internal command used to hide the cursor. You can send
|
||||||
// this message with HideCursor.
|
// this message with HideCursor.
|
||||||
type hideCursorMsg struct{}
|
type hideCursorMsg struct{}
|
||||||
|
@ -564,7 +525,7 @@ func (p *Program) StartReturningModel() (Model, error) {
|
||||||
hideCursor(p.output)
|
hideCursor(p.output)
|
||||||
|
|
||||||
case execMsg:
|
case execMsg:
|
||||||
// Note: this blocks.
|
// NB: this blocks.
|
||||||
p.exec(msg.cmd, msg.fn)
|
p.exec(msg.cmd, msg.fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -746,76 +707,3 @@ func (p *Program) RestoreTerminal() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecCommand can be implemented to execute things in the current
|
|
||||||
// terminal using the Exec Cmd.
|
|
||||||
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.
|
|
||||||
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)
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue