]> git.lizzy.rs Git - micro.git/blob - internal/shell/job.go
Merge pull request #1386 from jncraton/docfix
[micro.git] / internal / shell / job.go
1 package shell
2
3 import (
4         "bytes"
5         "io"
6         "os/exec"
7         "strings"
8
9         luar "layeh.com/gopher-luar"
10
11         lua "github.com/yuin/gopher-lua"
12         "github.com/zyedidia/micro/internal/config"
13         ulua "github.com/zyedidia/micro/internal/lua"
14         "github.com/zyedidia/micro/internal/screen"
15 )
16
17 var Jobs chan JobFunction
18
19 func init() {
20         Jobs = make(chan JobFunction, 100)
21 }
22
23 // Jobs are the way plugins can run processes in the background
24 // A job is simply a process that gets executed asynchronously
25 // There are callbacks for when the job exits, when the job creates stdout
26 // and when the job creates stderr
27
28 // These jobs run in a separate goroutine but the lua callbacks need to be
29 // executed in the main thread (where the Lua VM is running) so they are
30 // put into the jobs channel which gets read by the main loop
31
32 // JobFunction is a representation of a job (this data structure is what is loaded
33 // into the jobs channel)
34 type JobFunction struct {
35         Function func(string, ...interface{})
36         Output   string
37         Args     []interface{}
38 }
39
40 // A CallbackFile is the data structure that makes it possible to catch stderr and stdout write events
41 type CallbackFile struct {
42         io.Writer
43
44         callback func(string, ...interface{})
45         args     []interface{}
46 }
47
48 func (f *CallbackFile) Write(data []byte) (int, error) {
49         // This is either stderr or stdout
50         // In either case we create a new job function callback and put it in the jobs channel
51         jobFunc := JobFunction{f.callback, string(data), f.args}
52         Jobs <- jobFunc
53         return f.Writer.Write(data)
54 }
55
56 // JobStart starts a shell command in the background with the given callbacks
57 // It returns an *exec.Cmd as the job id
58 func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
59         return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
60 }
61
62 // JobSpawn starts a process with args in the background with the given callbacks
63 // It returns an *exec.Cmd as the job id
64 func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
65         // Set up everything correctly if the functions have been provided
66         proc := exec.Command(cmdName, cmdArgs...)
67         var outbuf bytes.Buffer
68         if onStdout != "" {
69                 proc.Stdout = &CallbackFile{&outbuf, luaFunctionJob(onStdout), userargs}
70         } else {
71                 proc.Stdout = &outbuf
72         }
73         if onStderr != "" {
74                 proc.Stderr = &CallbackFile{&outbuf, luaFunctionJob(onStderr), userargs}
75         } else {
76                 proc.Stderr = &outbuf
77         }
78
79         go func() {
80                 // Run the process in the background and create the onExit callback
81                 proc.Run()
82                 jobFunc := JobFunction{luaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
83                 Jobs <- jobFunc
84         }()
85
86         return proc
87 }
88
89 // JobStop kills a job
90 func JobStop(cmd *exec.Cmd) {
91         cmd.Process.Kill()
92 }
93
94 // JobSend sends the given data into the job's stdin stream
95 func JobSend(cmd *exec.Cmd, data string) {
96         stdin, err := cmd.StdinPipe()
97         if err != nil {
98                 return
99         }
100
101         stdin.Write([]byte(data))
102 }
103
104 // luaFunctionJob returns a function that will call the given lua function
105 // structured as a job call i.e. the job output and arguments are provided
106 // to the lua function
107 func luaFunctionJob(fn string) func(string, ...interface{}) {
108         luaFn := strings.Split(fn, ".")
109         if len(luaFn) <= 1 {
110                 return nil
111         }
112         plName, plFn := luaFn[0], luaFn[1]
113         pl := config.FindPlugin(plName)
114         if pl == nil {
115                 return nil
116         }
117         return func(output string, args ...interface{}) {
118                 luaArgs := []lua.LValue{luar.New(ulua.L, output), luar.New(ulua.L, args)}
119                 _, err := pl.Call(plFn, luaArgs...)
120                 if err != nil && err != config.ErrNoSuchFunction {
121                         screen.TermMessage(err)
122                 }
123         }
124 }