mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-11 07:02:44 +09:00
Jobs and gutter messages for plugins
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"github.com/zyedidia/micro/internal/action"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
@@ -25,6 +26,8 @@ func LuaImport(pkg string) *lua.LTable {
|
||||
return luaImportMicro()
|
||||
case "micro/shell":
|
||||
return luaImportMicroShell()
|
||||
case "micro/buffer":
|
||||
return luaImportMicroBuffer()
|
||||
case "micro/util":
|
||||
return luaImportMicroUtil()
|
||||
default:
|
||||
@@ -52,6 +55,22 @@ func luaImportMicroShell() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "RunCommand", luar.New(ulua.L, shell.RunCommand))
|
||||
ulua.L.SetField(pkg, "RunBackgroundShell", luar.New(ulua.L, shell.RunBackgroundShell))
|
||||
ulua.L.SetField(pkg, "RunInteractiveShell", luar.New(ulua.L, shell.RunInteractiveShell))
|
||||
ulua.L.SetField(pkg, "JobStart", luar.New(ulua.L, shell.JobStart))
|
||||
ulua.L.SetField(pkg, "JobSpawn", luar.New(ulua.L, shell.JobSpawn))
|
||||
ulua.L.SetField(pkg, "JobStop", luar.New(ulua.L, shell.JobStop))
|
||||
ulua.L.SetField(pkg, "JobSend", luar.New(ulua.L, shell.JobSend))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func luaImportMicroBuffer() *lua.LTable {
|
||||
pkg := ulua.L.NewTable()
|
||||
|
||||
ulua.L.SetField(pkg, "NewMessage", luar.New(ulua.L, buffer.NewMessage))
|
||||
ulua.L.SetField(pkg, "NewMessageAtLine", luar.New(ulua.L, buffer.NewMessageAtLine))
|
||||
ulua.L.SetField(pkg, "MTInfo", luar.New(ulua.L, buffer.MTInfo))
|
||||
ulua.L.SetField(pkg, "MTWarning", luar.New(ulua.L, buffer.MTWarning))
|
||||
ulua.L.SetField(pkg, "MTError", luar.New(ulua.L, buffer.MTError))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -61,7 +80,7 @@ func luaImportMicroUtil() *lua.LTable {
|
||||
|
||||
ulua.L.SetField(pkg, "RuneAt", luar.New(ulua.L, util.LuaRuneAt))
|
||||
ulua.L.SetField(pkg, "GetLeadingWhitespace", luar.New(ulua.L, util.LuaGetLeadingWhitespace))
|
||||
ulua.L.SetField(pkg, "IsWordChar", luar.New(ulua.L, util.LuaIsWordChar))
|
||||
ulua.L.SetField(pkg, "", luar.New(ulua.L, util.LuaIsWordChar))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
@@ -231,6 +232,9 @@ func main() {
|
||||
|
||||
// Check for new events
|
||||
select {
|
||||
case f := <-shell.Jobs:
|
||||
// If a new job has finished while running in the background we should execute the callback
|
||||
f.Function(f.Output, f.Args...)
|
||||
case event = <-events:
|
||||
case <-screen.DrawChan:
|
||||
}
|
||||
|
||||
@@ -114,3 +114,14 @@ func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
|
||||
ulua.L.Pop(1)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func FindPlugin(name string) *Plugin {
|
||||
var pl *Plugin
|
||||
for _, p := range Plugins {
|
||||
if p.Name == name {
|
||||
pl = p
|
||||
break
|
||||
}
|
||||
}
|
||||
return pl
|
||||
}
|
||||
|
||||
@@ -54,13 +54,7 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
|
||||
func SetStatusInfoFnLua(s string, fn string) {
|
||||
luaFn := strings.Split(fn, ".")
|
||||
plName, plFn := luaFn[0], luaFn[1]
|
||||
var pl *config.Plugin
|
||||
for _, p := range config.Plugins {
|
||||
if p.Name == plName {
|
||||
pl = p
|
||||
break
|
||||
}
|
||||
}
|
||||
pl := config.FindPlugin(plName)
|
||||
statusInfo[s] = func(b *buffer.Buffer) string {
|
||||
if pl == nil || !pl.IsEnabled() {
|
||||
return ""
|
||||
|
||||
129
internal/shell/job.go
Normal file
129
internal/shell/job.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
)
|
||||
|
||||
var Jobs chan JobFunction
|
||||
|
||||
func init() {
|
||||
Jobs = make(chan JobFunction, 100)
|
||||
}
|
||||
|
||||
// Jobs are the way plugins can run processes in the background
|
||||
// A job is simply a process that gets executed asynchronously
|
||||
// There are callbacks for when the job exits, when the job creates stdout
|
||||
// and when the job creates stderr
|
||||
|
||||
// These jobs run in a separate goroutine but the lua callbacks need to be
|
||||
// executed in the main thread (where the Lua VM is running) so they are
|
||||
// put into the jobs channel which gets read by the main loop
|
||||
|
||||
// JobFunction is a representation of a job (this data structure is what is loaded
|
||||
// into the jobs channel)
|
||||
type JobFunction struct {
|
||||
Function func(string, ...string)
|
||||
Output string
|
||||
Args []string
|
||||
}
|
||||
|
||||
// A CallbackFile is the data structure that makes it possible to catch stderr and stdout write events
|
||||
type CallbackFile struct {
|
||||
io.Writer
|
||||
|
||||
callback func(string, ...string)
|
||||
args []string
|
||||
}
|
||||
|
||||
func (f *CallbackFile) Write(data []byte) (int, error) {
|
||||
// This is either stderr or stdout
|
||||
// In either case we create a new job function callback and put it in the jobs channel
|
||||
jobFunc := JobFunction{f.callback, string(data), f.args}
|
||||
Jobs <- jobFunc
|
||||
return f.Writer.Write(data)
|
||||
}
|
||||
|
||||
// JobStart starts a shell command in the background with the given callbacks
|
||||
// It returns an *exec.Cmd as the job id
|
||||
func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
|
||||
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
|
||||
}
|
||||
|
||||
// JobSpawn starts a process with args in the background with the given callbacks
|
||||
// It returns an *exec.Cmd as the job id
|
||||
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
|
||||
// Set up everything correctly if the functions have been provided
|
||||
proc := exec.Command(cmdName, cmdArgs...)
|
||||
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() {
|
||||
// Run the process in the background and create the onExit callback
|
||||
proc.Run()
|
||||
jobFunc := JobFunction{luaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
|
||||
Jobs <- jobFunc
|
||||
}()
|
||||
|
||||
return proc
|
||||
}
|
||||
|
||||
// JobStop kills a job
|
||||
func JobStop(cmd *exec.Cmd) {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
|
||||
// JobSend sends the given data into the job's stdin stream
|
||||
func JobSend(cmd *exec.Cmd, data string) {
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stdin.Write([]byte(data))
|
||||
}
|
||||
|
||||
// luaFunctionJob returns a function that will call the given lua function
|
||||
// structured as a job call i.e. the job output and arguments are provided
|
||||
// to the lua function
|
||||
func luaFunctionJob(fn string) func(string, ...string) {
|
||||
luaFn := strings.Split(fn, ".")
|
||||
plName, plFn := luaFn[0], luaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
return func(output string, args ...string) {
|
||||
var luaArgs []lua.LValue
|
||||
for _, v := range args {
|
||||
luaArgs = append(luaArgs, luar.New(ulua.L, v))
|
||||
}
|
||||
_, err := pl.Call(plFn, luaArgs...)
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func unpack(old []string) []interface{} {
|
||||
new := make([]interface{}, len(old))
|
||||
for i, v := range old {
|
||||
new[i] = v
|
||||
}
|
||||
return new
|
||||
}
|
||||
Reference in New Issue
Block a user