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