]> git.lizzy.rs Git - micro.git/blob - cmd/micro/plugin.go
Give Lua access to most of the Go stdlib
[micro.git] / cmd / micro / plugin.go
1 package main
2
3 import (
4         "errors"
5         "io/ioutil"
6         "os"
7         "strings"
8
9         "github.com/yuin/gopher-lua"
10         "github.com/zyedidia/tcell"
11         "layeh.com/gopher-luar"
12 )
13
14 var loadedPlugins map[string]string
15
16 // Call calls the lua function 'function'
17 // If it does not exist nothing happens, if there is an error,
18 // the error is returned
19 func Call(function string, args ...interface{}) (lua.LValue, error) {
20         var luaFunc lua.LValue
21         if strings.Contains(function, ".") {
22                 plugin := L.GetGlobal(strings.Split(function, ".")[0])
23                 if plugin.String() == "nil" {
24                         return nil, errors.New("function does not exist: " + function)
25                 }
26                 luaFunc = L.GetField(plugin, strings.Split(function, ".")[1])
27         } else {
28                 luaFunc = L.GetGlobal(function)
29         }
30
31         if luaFunc.String() == "nil" {
32                 return nil, errors.New("function does not exist: " + function)
33         }
34         var luaArgs []lua.LValue
35         for _, v := range args {
36                 luaArgs = append(luaArgs, luar.New(L, v))
37         }
38         err := L.CallByParam(lua.P{
39                 Fn:      luaFunc,
40                 NRet:    1,
41                 Protect: true,
42         }, luaArgs...)
43         ret := L.Get(-1) // returned value
44         if ret.String() != "nil" {
45                 L.Pop(1) // remove received value
46         }
47         return ret, err
48 }
49
50 // LuaFunctionBinding is a function generator which takes the name of a lua function
51 // and creates a function that will call that lua function
52 // Specifically it creates a function that can be called as a binding because this is used
53 // to bind keys to lua functions
54 func LuaFunctionBinding(function string) func(*View, bool) bool {
55         return func(v *View, _ bool) bool {
56                 _, err := Call(function, nil)
57                 if err != nil {
58                         TermMessage(err)
59                 }
60                 return false
61         }
62 }
63
64 func LuaFunctionMouseBinding(function string) func(*View, bool, *tcell.EventMouse) bool {
65         return func(v *View, _ bool, e *tcell.EventMouse) bool {
66                 _, err := Call(function, e)
67                 if err != nil {
68                         TermMessage(err)
69                 }
70                 return false
71         }
72 }
73
74 func unpack(old []string) []interface{} {
75         new := make([]interface{}, len(old))
76         for i, v := range old {
77                 new[i] = v
78         }
79         return new
80 }
81
82 // LuaFunctionCommand is the same as LuaFunctionBinding except it returns a normal function
83 // so that a command can be bound to a lua function
84 func LuaFunctionCommand(function string) func([]string) {
85         return func(args []string) {
86                 _, err := Call(function, unpack(args)...)
87                 if err != nil {
88                         TermMessage(err)
89                 }
90         }
91 }
92
93 // LuaFunctionComplete returns a function which can be used for autocomplete in plugins
94 func LuaFunctionComplete(function string) func(string) []string {
95         return func(input string) (result []string) {
96
97                 res, err := Call(function, input)
98                 if err != nil {
99                         TermMessage(err)
100                 }
101                 if tbl, ok := res.(*lua.LTable); !ok {
102                         TermMessage(function, "should return a table of strings")
103                 } else {
104                         for i := 1; i <= tbl.Len(); i++ {
105                                 val := tbl.RawGetInt(i)
106                                 if v, ok := val.(lua.LString); !ok {
107                                         TermMessage(function, "should return a table of strings")
108                                 } else {
109                                         result = append(result, string(v))
110                                 }
111                         }
112                 }
113                 return result
114         }
115 }
116
117 func LuaFunctionJob(function string) func(string, ...string) {
118         return func(output string, args ...string) {
119                 _, err := Call(function, unpack(append([]string{output}, args...))...)
120                 if err != nil {
121                         TermMessage(err)
122                 }
123         }
124 }
125
126 // luaPluginName convert a human-friendly plugin name into a valid lua variable name.
127 func luaPluginName(name string) string {
128         return strings.Replace(name, "-", "_", -1)
129 }
130
131 // LoadPlugins loads the pre-installed plugins and the plugins located in ~/.config/micro/plugins
132 func LoadPlugins() {
133         loadedPlugins = make(map[string]string)
134
135         for _, plugin := range ListRuntimeFiles(RTPlugin) {
136                 pluginName := plugin.Name()
137                 if _, ok := loadedPlugins[pluginName]; ok {
138                         continue
139                 }
140
141                 data, err := plugin.Data()
142                 if err != nil {
143                         TermMessage("Error loading plugin: " + pluginName)
144                         continue
145                 }
146
147                 pluginLuaName := luaPluginName(pluginName)
148
149                 if err := LoadFile(pluginName, pluginName, string(data)); err != nil {
150                         TermMessage(err)
151                         continue
152                 }
153
154                 loadedPlugins[pluginName] = pluginLuaName
155
156         }
157
158         if _, err := os.Stat(configDir + "/init.lua"); err == nil {
159                 data, _ := ioutil.ReadFile(configDir + "/init.lua")
160                 if err := LoadFile("init", configDir+"init.lua", string(data)); err != nil {
161                         TermMessage(err)
162                 }
163                 loadedPlugins["init"] = "init"
164         }
165 }