exec.go 3.5 KB

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