1package tea
2
3import (
4 "io"
5 "os"
6 "os/exec"
7)
8
9// execMsg is used internally to run an ExecCommand sent with Exec.
10type execMsg struct {
11 cmd ExecCommand
12 fn ExecCallback
13}
14
15// Exec is used to perform arbitrary I/O in a blocking fashion, effectively
16// pausing the Program while execution is running and resuming it when
17// execution has completed.
18//
19// Most of the time you'll want to use ExecProcess, which runs an exec.Cmd.
20//
21// For non-interactive i/o you should use a Cmd (that is, a tea.Cmd).
22func Exec(c ExecCommand, fn ExecCallback) Cmd {
23 return func() Msg {
24 return execMsg{cmd: c, fn: fn}
25 }
26}
27
28// ExecProcess runs the given *exec.Cmd in a blocking fashion, effectively
29// pausing the Program while the command is running. After the *exec.Cmd exists
30// the Program resumes. It's useful for spawning other interactive applications
31// such as editors and shells from within a Program.
32//
33// To produce the command, pass an *exec.Cmd and a function which returns
34// a message containing the error which may have occurred when running the
35// ExecCommand.
36//
37// type VimFinishedMsg struct { err error }
38//
39// c := exec.Command("vim", "file.txt")
40//
41// cmd := ExecProcess(c, func(err error) Msg {
42// return VimFinishedMsg{err: err}
43// })
44//
45// Or, if you don't care about errors, you could simply:
46//
47// cmd := ExecProcess(exec.Command("vim", "file.txt"), nil)
48//
49// For non-interactive i/o you should use a Cmd (that is, a tea.Cmd).
50func ExecProcess(c *exec.Cmd, fn ExecCallback) Cmd {
51 return Exec(wrapExecCommand(c), fn)
52}
53
54// ExecCallback is used when executing an *exec.Command to return a message
55// with an error, which may or may not be nil.
56type ExecCallback func(error) Msg
57
58// ExecCommand can be implemented to execute things in a blocking fashion in
59// the current terminal.
60type ExecCommand interface {
61 Run() error
62 SetStdin(io.Reader)
63 SetStdout(io.Writer)
64 SetStderr(io.Writer)
65}
66
67// wrapExecCommand wraps an exec.Cmd so that it satisfies the ExecCommand
68// interface so it can be used with Exec.
69func wrapExecCommand(c *exec.Cmd) ExecCommand {
70 return &osExecCommand{Cmd: c}
71}
72
73// osExecCommand is a layer over an exec.Cmd that satisfies the ExecCommand
74// interface.
75type osExecCommand struct{ *exec.Cmd }
76
77// SetStdin sets stdin on underlying exec.Cmd to the given io.Reader.
78func (c *osExecCommand) SetStdin(r io.Reader) {
79 // If unset, have the command use the same input as the terminal.
80 if c.Stdin == nil {
81 c.Stdin = r
82 }
83}
84
85// SetStdout sets stdout on underlying exec.Cmd to the given io.Writer.
86func (c *osExecCommand) SetStdout(w io.Writer) {
87 // If unset, have the command use the same output as the terminal.
88 if c.Stdout == nil {
89 c.Stdout = w
90 }
91}
92
93// SetStderr sets stderr on the underlying exec.Cmd to the given io.Writer.
94func (c *osExecCommand) SetStderr(w io.Writer) {
95 // If unset, use stderr for the command's stderr
96 if c.Stderr == nil {
97 c.Stderr = w
98 }
99}
100
101// exec runs an ExecCommand and delivers the results to the program as a Msg.
102func (p *Program) exec(c ExecCommand, fn ExecCallback) {
103 if err := p.releaseTerminal(false); err != nil {
104 // If we can't release input, abort.
105 if fn != nil {
106 go p.Send(fn(err))
107 }
108 return
109 }
110
111 c.SetStdin(p.input)
112 c.SetStdout(p.output)
113 c.SetStderr(os.Stderr)
114
115 // Execute system command.
116 if err := c.Run(); err != nil {
117 p.renderer.resetLinesRendered()
118 _ = p.RestoreTerminal() // also try to restore the terminal.
119 if fn != nil {
120 go p.Send(fn(err))
121 }
122 return
123 }
124
125 // Maintain the existing output from the command
126 p.renderer.resetLinesRendered()
127
128 // Have the program re-capture input.
129 err := p.RestoreTerminal()
130 if fn != nil {
131 go p.Send(fn(err))
132 }
133}