Add support for job control

This commit adds support for job control (running processes
asynchronously from plugins) with the JobStart, JobSend, and JobStop
functions (copying neovim's job control).

This commit also makes the linter plugin work asynchronously, so the
editor won't be frozen while the linter checks your code for errors.
This commit is contained in:
Zachary Yedidia
2016-06-15 11:19:00 -04:00
parent ceb5760d6d
commit d2277a376a
6 changed files with 118 additions and 15 deletions

67
cmd/micro/job.go Normal file
View File

@@ -0,0 +1,67 @@
package main
import (
"bytes"
"io"
"os/exec"
"strings"
)
type JobFunction struct {
function func(string, ...string)
output string
args []string
}
type CallbackFile struct {
io.Writer
callback func(string, ...string)
args []string
}
func (f *CallbackFile) Write(data []byte) (int, error) {
jobFunc := JobFunction{f.callback, string(data), f.args}
jobs <- jobFunc
return f.Writer.Write(data)
}
func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
split := strings.Split(cmd, " ")
args := split[1:]
cmdName := split[0]
proc := exec.Command(cmdName, args...)
var outbuf bytes.Buffer
if onStdout != "" {
proc.Stdout = &CallbackFile{&outbuf, LuaFunctionJob(onStdout), userargs}
} else {
proc.Stdout = &outbuf
}
if onStderr != "" {
proc.Stderr = &CallbackFile{&outbuf, LuaFunctionJob(onStderr), userargs}
} else {
proc.Stderr = &outbuf
}
go func() {
proc.Run()
jobFunc := JobFunction{LuaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
jobs <- jobFunc
}()
return proc
}
func JobStop(cmd *exec.Cmd) {
cmd.Process.Kill()
}
func JobSend(cmd *exec.Cmd, data string) {
stdin, err := cmd.StdinPipe()
if err != nil {
return
}
stdin.Write([]byte(data))
}

View File

@@ -106,7 +106,7 @@ func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
m.Display()
screen.ShowCursor(Count(m.message), h-1)
screen.Show()
event := screen.PollEvent()
event := <-events
switch e := event.(type) {
case *tcell.EventKey:
@@ -149,7 +149,7 @@ func (m *Messenger) Prompt(prompt, historyType string, completionType Completion
m.Clear()
m.Display()
event := screen.PollEvent()
event := <-events
switch e := event.(type) {
case *tcell.EventKey:

View File

@@ -52,6 +52,9 @@ var (
// This is the currently open tab
// It's just an index to the tab in the tabs array
curTab int
jobs chan JobFunction
events chan tcell.Event
)
// LoadInput loads the file input for the editor
@@ -245,14 +248,33 @@ func main() {
L.SetGlobal("MakeCommand", luar.New(L, MakeCommand))
L.SetGlobal("CurView", luar.New(L, CurView))
L.SetGlobal("JobStart", luar.New(L, JobStart))
L.SetGlobal("JobSend", luar.New(L, JobSend))
L.SetGlobal("JobStop", luar.New(L, JobStop))
LoadPlugins()
jobs = make(chan JobFunction, 100)
events = make(chan tcell.Event)
go func() {
for {
events <- screen.PollEvent()
}
}()
for {
// Display everything
RedrawAll()
// Wait for the user's action
event := screen.PollEvent()
var event tcell.Event
select {
case f := <-jobs:
f.function(f.output, f.args...)
continue
case event = <-events:
}
if TabbarHandleMouseEvent(event) {
continue
}

View File

@@ -23,12 +23,15 @@ func Call(function string, args []string) error {
if luaFunc.String() == "nil" {
return errors.New("function does not exist: " + function)
}
luaArgs := luar.New(L, args)
var luaArgs []lua.LValue
for _, v := range args {
luaArgs = append(luaArgs, luar.New(L, v))
}
err := L.CallByParam(lua.P{
Fn: luaFunc,
NRet: 0,
Protect: true,
}, luaArgs)
}, luaArgs...)
return err
}
@@ -57,6 +60,15 @@ func LuaFunctionCommand(function string) func([]string) {
}
}
func LuaFunctionJob(function string) func(string, ...string) {
return func(output string, args ...string) {
err := Call(function, append([]string{output}, args...))
if err != nil {
TermMessage(err)
}
}
}
// LoadPlugins loads the pre-installed plugins and the plugins located in ~/.config/micro/plugins
func LoadPlugins() {
files, _ := ioutil.ReadDir(configDir + "/plugins")

File diff suppressed because one or more lines are too long

View File

@@ -34,9 +34,11 @@ end
function linter_lint(linter, cmd, errorformat)
CurView():ClearGutterMessages(linter)
local handle = io.popen("(" .. cmd .. ")" .. " 2>&1")
local lines = linter_split(handle:read("*a"), "\n")
handle:close()
JobStart(cmd, "", "", "linter_onExit", linter, errorformat)
end
function linter_onExit(output, linter, errorformat)
local lines = linter_split(output, "\n")
local regex = errorformat:gsub("%%f", "(.+)"):gsub("%%l", "(%d+)"):gsub("%%m", "(.+)")
for _,line in ipairs(lines) do