extension.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package editor
  2. import (
  3. "embed"
  4. "fmt"
  5. "moose/internal/editor/highlight"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. lua "github.com/yuin/gopher-lua"
  10. )
  11. type ExtensionManager struct {
  12. L *lua.LState
  13. M *Model
  14. LoadedFiles []string
  15. currentDiskDir string
  16. currentEmbedDir string
  17. }
  18. //go:embed lua
  19. var embeddedScripts embed.FS
  20. func NewExtensionManager(m *Model) *ExtensionManager {
  21. em := &ExtensionManager{
  22. L: lua.NewState(),
  23. M: m,
  24. }
  25. lua.OpenPackage(em.L)
  26. em.registerAPI()
  27. em.registerExtensionSearcher()
  28. if err := em.LoadEmbeddedFile("init.lua"); err != nil {
  29. m.Mode = ModeNormal
  30. m.BM.PaletteBuffer.Clear()
  31. m.BM.PaletteBuffer.Insert("moose.error:Lua error " + err.Error())
  32. }
  33. if err := em.LoadFile("/home/johron/.config/moose/moose.lua"); err != nil {
  34. m.Mode = ModeNormal
  35. m.BM.PaletteBuffer.Clear()
  36. m.BM.PaletteBuffer.Insert("moose.error:Lua error " + err.Error())
  37. }
  38. return em
  39. }
  40. func (em *ExtensionManager) registerExtensionSearcher() {
  41. if em.L == nil {
  42. return
  43. }
  44. pkg := em.L.GetGlobal("package")
  45. if pkg.Type() == lua.LTNil {
  46. return
  47. }
  48. loadersVal := em.L.GetField(pkg, "loaders")
  49. packageLoaders, ok := loadersVal.(*lua.LTable)
  50. if !ok {
  51. return
  52. }
  53. extensionSearcher := em.L.NewFunction(func(L *lua.LState) int {
  54. modName := L.CheckString(1)
  55. fileName := strings.ReplaceAll(modName, ".", "/") + ".lua"
  56. var errorsLogged []string
  57. if em.currentDiskDir != "" {
  58. targetDiskFile := filepath.Join(em.currentDiskDir, fileName)
  59. if fn, loadErr := L.LoadFile(targetDiskFile); loadErr == nil {
  60. return pushAndReturn(L, fn)
  61. } else {
  62. errorsLogged = append(errorsLogged, fmt.Sprintf("no relative file: %s", targetDiskFile))
  63. }
  64. }
  65. if em.currentEmbedDir != "" {
  66. targetEmbedFile := filepath.Join(em.currentEmbedDir, fileName)
  67. if bytes, err := embeddedScripts.ReadFile(targetEmbedFile); err == nil {
  68. if fn, err := L.LoadString(string(bytes)); err == nil {
  69. return pushAndReturn(L, fn)
  70. } else {
  71. L.RaiseError("failed to compile embedded module %s: %v", modName, err)
  72. return 0
  73. }
  74. } else {
  75. errorsLogged = append(errorsLogged, fmt.Sprintf("no relative embed file: %s", targetEmbedFile))
  76. }
  77. }
  78. L.Push(lua.LString("\n\t" + strings.Join(errorsLogged, "\n\t")))
  79. return 1
  80. })
  81. idx2 := packageLoaders.RawGetInt(2)
  82. idx3 := packageLoaders.RawGetInt(3)
  83. idx4 := packageLoaders.RawGetInt(4)
  84. packageLoaders.RawSetInt(2, extensionSearcher)
  85. packageLoaders.RawSetInt(3, idx2)
  86. packageLoaders.RawSetInt(4, idx3)
  87. if idx4 != lua.LNil {
  88. packageLoaders.RawSetInt(5, idx4)
  89. }
  90. }
  91. func pushAndReturn(L *lua.LState, fn *lua.LFunction) int {
  92. L.Push(fn)
  93. return 1
  94. }
  95. func (em *ExtensionManager) Close() {
  96. if em.L != nil {
  97. em.L.Close()
  98. em.L = nil
  99. }
  100. }
  101. func (em *ExtensionManager) registerAPI() {
  102. if em.L == nil {
  103. return
  104. }
  105. moose := em.L.NewTable()
  106. em.L.SetGlobal("ms", moose)
  107. moose.RawSetString("config", GetConfigTable(em))
  108. moose.RawSetString("syntax", GetSyntaxTable(em))
  109. }
  110. func (em *ExtensionManager) LoadFile(path string) error {
  111. oldDiskDir := em.currentDiskDir
  112. em.currentDiskDir = filepath.Dir(path)
  113. defer func() { em.currentDiskDir = oldDiskDir }()
  114. if err := em.L.DoFile(path); err != nil {
  115. return err
  116. }
  117. em.LoadedFiles = append(em.LoadedFiles, path)
  118. return nil
  119. }
  120. func (em *ExtensionManager) LoadEmbeddedFile(path string) error {
  121. if em.L == nil {
  122. return fmt.Errorf("lua state is closed")
  123. }
  124. embedPath := filepath.Join("lua", path)
  125. bytes, err := embeddedScripts.ReadFile(embedPath)
  126. if err != nil {
  127. return fmt.Errorf("failed to read embedded script %s: %w", path, err)
  128. }
  129. oldEmbedDir := em.currentEmbedDir
  130. em.currentEmbedDir = filepath.Dir(embedPath)
  131. defer func() { em.currentEmbedDir = oldEmbedDir }()
  132. if err := em.L.DoString(string(bytes)); err != nil {
  133. return err
  134. }
  135. em.LoadedFiles = append(em.LoadedFiles, path)
  136. return nil
  137. }
  138. func (em *ExtensionManager) LoadString(name string, src string) error {
  139. if em.L == nil {
  140. return fmt.Errorf("lua state is closed")
  141. }
  142. if err := em.L.DoString(src); err != nil {
  143. return err
  144. }
  145. em.LoadedFiles = append(em.LoadedFiles, name)
  146. return nil
  147. }
  148. func GetSyntaxTable(em *ExtensionManager) *lua.LTable {
  149. syntax := em.L.NewTable()
  150. em.L.SetFuncs(syntax, map[string]lua.LGFunction{
  151. "registerLang": luaRegisterLanguage,
  152. })
  153. return syntax
  154. }
  155. func luaRegisterLanguage(L *lua.LState) int {
  156. idx := 1
  157. if L.GetTop() >= 2 && L.Get(1).Type() == lua.LTTable {
  158. tb1 := L.ToTable(1)
  159. if tb1.RawGetString("name") == lua.LNil {
  160. idx = 2
  161. }
  162. }
  163. tb := L.CheckTable(idx)
  164. langName := tb.RawGetString("name").String()
  165. parserPath := tb.RawGetString("parser_path").String()
  166. queryScm := tb.RawGetString("query").String()
  167. if langName == "" || langName == "nil" || parserPath == "" || parserPath == "nil" {
  168. L.ArgError(idx, "expected table with 'name' and 'parser_path' fields")
  169. return 0
  170. }
  171. err := highlight.RegisterTreeSitterLang(langName, parserPath, queryScm)
  172. if err != nil {
  173. fmt.Fprintf(os.Stderr, "warning: failed to register language %s: %v\n", langName, err)
  174. return 0
  175. }
  176. return 0
  177. }