github.com/jmigpin/editor@v1.6.0/core/plugins.go (about) 1 package core 2 3 import ( 4 "context" 5 "fmt" 6 "plugin" 7 8 "github.com/jmigpin/editor/core/toolbarparser" 9 "github.com/jmigpin/editor/ui" 10 "github.com/jmigpin/editor/util/iout" 11 ) 12 13 type Plugins struct { 14 ed *Editor 15 plugs []*Plug 16 added map[string]bool 17 } 18 19 func NewPlugins(ed *Editor) *Plugins { 20 return &Plugins{ed: ed, added: map[string]bool{}} 21 } 22 23 func (p *Plugins) AddPath(path string) error { 24 if p.added[path] { 25 return nil 26 } 27 p.added[path] = true 28 29 oplugin, err := plugin.Open(path) 30 if err != nil { 31 return fmt.Errorf("plugin: %v: %w", path, err) 32 } 33 34 plug := &Plug{Plugin: oplugin, Path: path} 35 p.plugs = append(p.plugs, plug) 36 37 return p.runOnLoad(plug) 38 } 39 40 //---------- 41 42 func (p *Plugins) runOnLoad(plug *Plug) error { 43 // plugin should have this symbol 44 fname := "OnLoad" 45 f, err := plug.Plugin.Lookup(fname) 46 if err != nil { 47 return nil // ok if plugin doesn't implement this symbol 48 } 49 // the symbol must implement this signature 50 f2, ok := f.(func(*Editor)) 51 if !ok { 52 return p.badFuncSigErr(plug.Path, fname) 53 } 54 // run symbol 55 f2(p.ed) 56 return nil 57 } 58 59 //---------- 60 61 // Runs all plugins until it finds one that returns handled=true and has no errors. 62 func (p *Plugins) RunAutoComplete(ctx context.Context, cfb *ui.ContextFloatBox) (_ error, handled bool) { 63 me := iout.MultiError{} 64 for _, plug := range p.plugs { 65 err, handled := p.runAutoCompletePlug(ctx, plug, cfb) 66 if handled { 67 return err, true 68 } 69 me.Add(err) 70 } 71 return me.Result(), false 72 } 73 74 func (p *Plugins) runAutoCompletePlug(ctx context.Context, plug *Plug, cfb *ui.ContextFloatBox) (_ error, handled bool) { 75 // plugin should have this symbol 76 fname := "AutoComplete" 77 fn1, err := plug.Plugin.Lookup(fname) 78 if err != nil { 79 return nil, false // ok if plugin doesn't implement this symbol 80 } 81 // the symbol must implement this signature 82 fn2, ok := fn1.(func(context.Context, *Editor, *ui.ContextFloatBox) (_ error, handled bool)) 83 if !ok { 84 // report error 85 err := p.badFuncSigErr(plug.Path, fname) 86 p.ed.Error(err) 87 88 return nil, false // ok if plugin doesn't implement the sig 89 } 90 // run symbol 91 return fn2(ctx, p.ed, cfb) 92 } 93 94 //---------- 95 96 func (p *Plugins) RunToolbarCmd(erow *ERow, part *toolbarparser.Part) bool { 97 for _, plug := range p.plugs { 98 handled := p.runToolbarCmdPlug(plug, erow, part) 99 if handled { 100 return true 101 } 102 } 103 return false 104 } 105 106 func (p *Plugins) runToolbarCmdPlug(plug *Plug, erow *ERow, part *toolbarparser.Part) bool { 107 // plugin should have this symbol 108 fname := "ToolbarCmd" 109 f, err := plug.Plugin.Lookup(fname) 110 if err != nil { 111 // no error: ok if plugin doesn't implement this symbol 112 return false 113 } 114 // the symbol must implement this signature 115 f2, ok := f.(func(*Editor, *ERow, *toolbarparser.Part) bool) 116 if !ok { 117 // report error 118 err := p.badFuncSigErr(plug.Path, fname) 119 p.ed.Error(err) 120 121 return false // doesn't implement the required sig 122 } 123 // run symbol 124 return f2(p.ed, erow, part) 125 } 126 127 //---------- 128 129 func (p *Plugins) badFuncSigErr(path, name string) error { 130 return fmt.Errorf("plugins: bad func signature: %v, %v", path, name) 131 } 132 133 //---------- 134 135 type Plug struct { 136 Path string 137 Plugin *plugin.Plugin 138 }