]> git.lizzy.rs Git - micro.git/commitdiff
Add support for plugin manager within micro
authorZachary Yedidia <zyedidia@gmail.com>
Sun, 2 Feb 2020 19:20:39 +0000 (14:20 -0500)
committerZachary Yedidia <zyedidia@gmail.com>
Sun, 2 Feb 2020 19:20:39 +0000 (14:20 -0500)
cmd/micro/micro.go
internal/action/command.go
internal/buffer/buffer.go
internal/buffer/eventhandler.go
internal/config/plugin.go
internal/config/plugin_installer.go

index 59cbb802fbc5b94afee02ac3f3e47f82d9008f55..4bcf99cbfe6ac6dd05b141bf5dbaff9a05c90009 100644 (file)
@@ -55,6 +55,12 @@ func InitFlags() {
                fmt.Println("    \tRemove plugin(s)")
                fmt.Println("-plugin update [PLUGIN]...")
                fmt.Println("    \tUpdate plugin(s) (if no argument is given, updates all plugins)")
+               fmt.Println("-plugin search [PLUGIN]...")
+               fmt.Println("    \tSearch for a plugin")
+               fmt.Println("-plugin list")
+               fmt.Println("    \tList installed plugins")
+               fmt.Println("-plugin available")
+               fmt.Println("    \tList available plugins")
 
                fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
                fmt.Println("-option value")
@@ -107,76 +113,8 @@ func DoPluginFlags() {
 
                args := flag.Args()
 
-               switch *flagPlugin {
-               case "install":
-                       installedVersions := config.GetInstalledVersions(false)
-                       for _, plugin := range args {
-                               pp := config.GetAllPluginPackages().Get(plugin)
-                               if pp == nil {
-                                       fmt.Println("Unknown plugin \"" + plugin + "\"")
-                               } else if err := pp.IsInstallable(); err != nil {
-                                       fmt.Println("Error installing ", plugin, ": ", err)
-                               } else {
-                                       for _, installed := range installedVersions {
-                                               if pp.Name == installed.Pack().Name {
-                                                       if pp.Versions[0].Version.Compare(installed.Version) == 1 {
-                                                               fmt.Println(pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
-                                                       } else {
-                                                               fmt.Println(pp.Name, " is already installed")
-                                                       }
-                                               }
-                                       }
-                                       pp.Install()
-                               }
-                       }
+               config.PluginCommand(os.Stdout, *flagPlugin, args)
 
-               case "remove":
-                       removed := ""
-                       for _, plugin := range args {
-                               // check if the plugin exists.
-                               for _, p := range config.Plugins {
-                                       if p.Name == plugin && p.Default {
-                                               fmt.Println("Default plugins cannot be removed, but can be disabled via settings.")
-                                               continue
-                                       }
-                                       if p.Name == plugin {
-                                               config.UninstallPlugin(plugin)
-                                               removed += plugin + " "
-                                               continue
-                                       }
-                               }
-                       }
-                       if removed != "" {
-                               fmt.Println("Removed ", removed)
-                       } else {
-                               fmt.Println("No plugins removed")
-                       }
-               case "update":
-                       config.UpdatePlugins(args)
-               case "list":
-                       plugins := config.GetInstalledVersions(false)
-                       fmt.Println("The following plugins are currently installed:")
-                       for _, p := range plugins {
-                               fmt.Printf("%s (%s)\n", p.Pack().Name, p.Version)
-                       }
-               case "search":
-                       plugins := config.SearchPlugin(args)
-                       fmt.Println(len(plugins), " plugins found")
-                       for _, p := range plugins {
-                               fmt.Println("----------------")
-                               fmt.Println(p.String())
-                       }
-                       fmt.Println("----------------")
-               case "available":
-                       packages := config.GetAllPluginPackages()
-                       fmt.Println("Available Plugins:")
-                       for _, pkg := range packages {
-                               fmt.Println(pkg.Name)
-                       }
-               default:
-                       fmt.Println("Invalid plugin command")
-                       os.Exit(1)
-               }
                os.Exit(0)
        }
 }
@@ -290,15 +228,15 @@ func main() {
                }
        }()
 
-       action.InitBindings()
-       action.InitCommands()
-
-       err = config.InitColorscheme()
+       err = config.LoadAllPlugins()
        if err != nil {
                screen.TermMessage(err)
        }
 
-       err = config.LoadAllPlugins()
+       action.InitBindings()
+       action.InitCommands()
+
+       err = config.InitColorscheme()
        if err != nil {
                screen.TermMessage(err)
        }
index bd303a186de7260693e55bb6ae3b578f8ce6d1d8..d6c7c3aec40f7fba2dab8270a434a2cd0b022616 100644 (file)
@@ -120,11 +120,20 @@ func CommandAction(cmd string) BufKeyAction {
        }
 }
 
-var PluginCmds = []string{"list", "info", "version"}
+var PluginCmds = []string{"install", "remove", "update", "available", "list", "search"}
 
 // PluginCmd installs, removes, updates, lists, or searches for given plugins
 func (h *BufPane) PluginCmd(args []string) {
-       InfoBar.Error("Plugin command disabled")
+       if len(args) < 1 {
+               InfoBar.Error("Not enough arguments")
+               return
+       }
+
+       if h.Buf.Type != buffer.BTLog {
+               OpenLogBuf(h)
+       }
+
+       config.PluginCommand(buffer.LogBuf, args[0], args[1:])
 }
 
 // RetabCmd changes all spaces to tabs or all tabs to spaces
index 01fe167f6522697eb9baa40b5ead49d8c6b863a4..48b30e04a55aa2fd1213c532517f8c83bb45f915 100644 (file)
@@ -76,16 +76,24 @@ type SharedBuffer struct {
        // Whether or not suggestions can be autocompleted must be shared because
        // it changes based on how the buffer has changed
        HasSuggestions bool
+
+       // Modifications is the list of modified regions for syntax highlighting
+       Modifications []Loc
 }
 
 func (b *SharedBuffer) insert(pos Loc, value []byte) {
        b.isModified = true
        b.HasSuggestions = false
        b.LineArray.insert(pos, value)
+
+       // b.Modifications is cleared every screen redraw so it's
+       // ok to append duplicates
+       b.Modifications = append(b.Modifications, Loc{pos.Y, pos.Y + bytes.Count(value, []byte{'\n'})})
 }
 func (b *SharedBuffer) remove(start, end Loc) []byte {
        b.isModified = true
        b.HasSuggestions = false
+       b.Modifications = append(b.Modifications, Loc{start.Y, start.Y})
        return b.LineArray.remove(start, end)
 }
 
@@ -115,9 +123,7 @@ type Buffer struct {
        // This stores the highlighting rules and filetype detection info
        SyntaxDef *highlight.Def
        // The Highlighter struct actually performs the highlighting
-       Highlighter *highlight.Highlighter
-       // Modifications is the list of modified regions for syntax highlighting
-       Modifications []Loc
+       Highlighter   *highlight.Highlighter
        HighlightLock sync.Mutex
 
        // Hash of the original buffer -- empty if fastdirty is on
@@ -333,10 +339,6 @@ func (b *Buffer) Insert(start Loc, text string) {
                b.EventHandler.active = b.curCursor
                b.EventHandler.Insert(start, text)
 
-               // b.Modifications is cleared every screen redraw so it's
-               // ok to append duplicates
-               b.Modifications = append(b.Modifications, Loc{start.Y, start.Y + strings.Count(text, "\n")})
-
                go b.Backup(true)
        }
 }
@@ -348,8 +350,6 @@ func (b *Buffer) Remove(start, end Loc) {
                b.EventHandler.active = b.curCursor
                b.EventHandler.Remove(start, end)
 
-               b.Modifications = append(b.Modifications, Loc{start.Y, start.Y})
-
                go b.Backup(true)
        }
 }
@@ -907,12 +907,12 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
        }
 
        startpos.Y, err = strconv.Atoi(cursorPositions[0])
-       startpos.Y -= 1
+       startpos.Y--
        if err == nil {
                if len(cursorPositions) > 1 {
                        startpos.X, err = strconv.Atoi(cursorPositions[1])
                        if startpos.X > 0 {
-                               startpos.X -= 1
+                               startpos.X--
                        }
                }
        }
@@ -925,6 +925,11 @@ func (b *Buffer) Line(i int) string {
        return string(b.LineBytes(i))
 }
 
+func (b *Buffer) Write(bytes []byte) (n int, err error) {
+       b.EventHandler.InsertBytes(b.End(), bytes)
+       return len(bytes), nil
+}
+
 // WriteLog writes a string to the log buffer
 func WriteLog(s string) {
        LogBuf.EventHandler.Insert(LogBuf.End(), s)
index aa14a5ba440eae32dbd6ca30ce09cbfc9b68d934..bd06f02ec8d9280a3d91c1c3d4f5fe3fefa9544e 100644 (file)
@@ -111,6 +111,11 @@ func (eh *EventHandler) ApplyDiff(new string) {
 // Insert creates an insert text event and executes it
 func (eh *EventHandler) Insert(start Loc, textStr string) {
        text := []byte(textStr)
+       eh.InsertBytes(start, text)
+}
+
+// InsertBytes creates an insert text event and executes it
+func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
        e := &TextEvent{
                C:         *eh.cursors[eh.active],
                EventType: TextEventInsert,
index e46e3c6e44b5140956fef58e0dcfc67ca25fb60d..1e1f96858d71bb4042bee1314a89e127763973dc 100644 (file)
@@ -8,6 +8,7 @@ import (
        ulua "github.com/zyedidia/micro/internal/lua"
 )
 
+// ErrNoSuchFunction is returned when Call is executed on a function that does not exist
 var ErrNoSuchFunction = errors.New("No such function exists")
 
 // LoadAllPlugins loads all detected plugins (in runtime/plugins and ConfigDir/plugins)
@@ -65,6 +66,7 @@ func RunPluginFnBool(fn string, args ...lua.LValue) (bool, error) {
        return retbool, reterr
 }
 
+// Plugin stores information about the source files/info for a plugin
 type Plugin struct {
        DirName string        // name of plugin folder
        Name    string        // name of plugin
@@ -74,6 +76,7 @@ type Plugin struct {
        Default bool // pre-installed plugin
 }
 
+// IsEnabled returns if a plugin is enabled
 func (p *Plugin) IsEnabled() bool {
        if v, ok := GlobalSettings[p.Name]; ok {
                return v.(bool) && p.Loaded
@@ -81,13 +84,15 @@ func (p *Plugin) IsEnabled() bool {
        return true
 }
 
+// Plugins is a list of all detected plugins (enabled or disabled)
 var Plugins []*Plugin
 
+// Load creates an option for the plugin and runs all source files
 func (p *Plugin) Load() error {
+       if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
+               return nil
+       }
        for _, f := range p.Srcs {
-               if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
-                       return nil
-               }
                dat, err := f.Data()
                if err != nil {
                        return err
@@ -96,12 +101,13 @@ func (p *Plugin) Load() error {
                if err != nil {
                        return err
                }
-               p.Loaded = true
-               RegisterGlobalOption(p.Name, true)
        }
+       p.Loaded = true
+       RegisterGlobalOption(p.Name, true)
        return nil
 }
 
+// Call calls a given function in this plugin
 func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
        plug := ulua.L.GetGlobal(p.Name)
        if plug == lua.LNil {
@@ -125,7 +131,23 @@ func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
        return ret, nil
 }
 
+// FindPlugin returns the plugin with the given name
 func FindPlugin(name string) *Plugin {
+       var pl *Plugin
+       for _, p := range Plugins {
+               if !p.IsEnabled() {
+                       continue
+               }
+               if p.Name == name {
+                       pl = p
+                       break
+               }
+       }
+       return pl
+}
+
+// FindAnyPlugin does not require the plugin to be enabled
+func FindAnyPlugin(name string) *Plugin {
        var pl *Plugin
        for _, p := range Plugins {
                if p.Name == name {
index 0ca38ddee45ea8a686ec41e30daa06ed639f340d..14622623f38d09fad786019e7d825b31943f2fe6 100644 (file)
@@ -118,17 +118,17 @@ func fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackag
 }
 
 // Fetch retrieves all available PluginPackages from the given channels
-func (pc PluginChannels) Fetch() PluginPackages {
+func (pc PluginChannels) Fetch(out io.Writer) PluginPackages {
        return fetchAllSources(len(pc), func(i int) PluginPackages {
-               return pc[i].Fetch()
+               return pc[i].Fetch(out)
        })
 }
 
 // Fetch retrieves all available PluginPackages from the given channel
-func (pc PluginChannel) Fetch() PluginPackages {
+func (pc PluginChannel) Fetch(out io.Writer) PluginPackages {
        resp, err := http.Get(string(pc))
        if err != nil {
-               fmt.Println("Failed to query plugin channel:\n", err)
+               fmt.Fprintln(out, "Failed to query plugin channel:\n", err)
                return PluginPackages{}
        }
        defer resp.Body.Close()
@@ -136,19 +136,19 @@ func (pc PluginChannel) Fetch() PluginPackages {
 
        var repositories []PluginRepository
        if err := decoder.Decode(&repositories); err != nil {
-               fmt.Println("Failed to decode channel data:\n", err)
+               fmt.Fprintln(out, "Failed to decode channel data:\n", err)
                return PluginPackages{}
        }
        return fetchAllSources(len(repositories), func(i int) PluginPackages {
-               return repositories[i].Fetch()
+               return repositories[i].Fetch(out)
        })
 }
 
 // Fetch retrieves all available PluginPackages from the given repository
-func (pr PluginRepository) Fetch() PluginPackages {
+func (pr PluginRepository) Fetch(out io.Writer) PluginPackages {
        resp, err := http.Get(string(pr))
        if err != nil {
-               fmt.Println("Failed to query plugin repository:\n", err)
+               fmt.Fprintln(out, "Failed to query plugin repository:\n", err)
                return PluginPackages{}
        }
        defer resp.Body.Close()
@@ -156,7 +156,7 @@ func (pr PluginRepository) Fetch() PluginPackages {
 
        var plugins PluginPackages
        if err := decoder.Decode(&plugins); err != nil {
-               fmt.Println("Failed to decode repository data:\n", err)
+               fmt.Fprintln(out, "Failed to decode repository data:\n", err)
                return PluginPackages{}
        }
        if len(plugins) > 0 {
@@ -218,7 +218,7 @@ func (pp *PluginPackage) UnmarshalJSON(data []byte) error {
 }
 
 // GetAllPluginPackages gets all PluginPackages which may be available.
-func GetAllPluginPackages() PluginPackages {
+func GetAllPluginPackages(out io.Writer) PluginPackages {
        if allPluginPackages == nil {
                getOption := func(name string) []string {
                        data := GetGlobalOption(name)
@@ -249,9 +249,9 @@ func GetAllPluginPackages() PluginPackages {
                }
                allPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages {
                        if i == 0 {
-                               return channels.Fetch()
+                               return channels.Fetch(out)
                        }
-                       return repos[i-1].Fetch()
+                       return repos[i-1].Fetch(out)
                })
        }
        return allPluginPackages
@@ -301,8 +301,8 @@ func (pp PluginPackage) Match(text string) bool {
 }
 
 // IsInstallable returns true if the package can be installed.
-func (pp PluginPackage) IsInstallable() error {
-       _, err := GetAllPluginPackages().Resolve(GetInstalledVersions(true), PluginDependencies{
+func (pp PluginPackage) IsInstallable(out io.Writer) error {
+       _, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
                &PluginDependency{
                        Name:  pp.Name,
                        Range: semver.Range(func(v semver.Version) bool { return true }),
@@ -312,18 +312,18 @@ func (pp PluginPackage) IsInstallable() error {
 
 // SearchPlugin retrieves a list of all PluginPackages which match the given search text and
 // could be or are already installed
-func SearchPlugin(texts []string) (plugins PluginPackages) {
+func SearchPlugin(out io.Writer, texts []string) (plugins PluginPackages) {
        plugins = make(PluginPackages, 0)
 
 pluginLoop:
-       for _, pp := range GetAllPluginPackages() {
+       for _, pp := range GetAllPluginPackages(out) {
                for _, text := range texts {
                        if !pp.Match(text) {
                                continue pluginLoop
                        }
                }
 
-               if err := pp.IsInstallable(); err == nil {
+               if err := pp.IsInstallable(out); err == nil {
                        plugins = append(plugins, pp)
                }
        }
@@ -363,6 +363,9 @@ func GetInstalledVersions(withCore bool) PluginVersions {
        }
 
        for _, p := range Plugins {
+               if !p.IsEnabled() {
+                       continue
+               }
                version := GetInstalledPluginVersion(p.Name)
                if pv := newStaticPluginVersion(p.Name, version); pv != nil {
                        result = append(result, pv)
@@ -386,8 +389,8 @@ func GetInstalledPluginVersion(name string) string {
 }
 
 // DownloadAndInstall downloads and installs the given plugin and version
-func (pv *PluginVersion) DownloadAndInstall() error {
-       fmt.Printf("Downloading %q (%s) from %q\n", pv.pack.Name, pv.Version, pv.Url)
+func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
+       fmt.Fprintf(out, "Downloading %q (%s) from %q\n", pv.pack.Name, pv.Version, pv.Url)
        resp, err := http.Get(pv.Url)
        if err != nil {
                return err
@@ -536,7 +539,7 @@ func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDe
        return selectedVersions, nil
 }
 
-func (pv PluginVersions) install() {
+func (pv PluginVersions) install(out io.Writer) {
        anyInstalled := false
        currentlyInstalled := GetInstalledVersions(true)
 
@@ -545,16 +548,16 @@ func (pv PluginVersions) install() {
                        shouldInstall := true
                        if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
                                if pv.Version.NE(sel.Version) {
-                                       fmt.Println("Uninstalling", sel.pack.Name)
-                                       UninstallPlugin(sel.pack.Name)
+                                       fmt.Fprintln(out, "Uninstalling", sel.pack.Name)
+                                       UninstallPlugin(out, sel.pack.Name)
                                } else {
                                        shouldInstall = false
                                }
                        }
 
                        if shouldInstall {
-                               if err := sel.DownloadAndInstall(); err != nil {
-                                       fmt.Println(err)
+                               if err := sel.DownloadAndInstall(out); err != nil {
+                                       fmt.Fprintln(out, err)
                                        return
                                }
                                anyInstalled = true
@@ -562,49 +565,56 @@ func (pv PluginVersions) install() {
                }
        }
        if anyInstalled {
-               fmt.Println("One or more plugins installed.")
+               fmt.Fprintln(out, "One or more plugins installed.")
        } else {
-               fmt.Println("Nothing to install / update")
+               fmt.Fprintln(out, "Nothing to install / update")
        }
 }
 
 // UninstallPlugin deletes the plugin folder of the given plugin
-func UninstallPlugin(name string) {
+func UninstallPlugin(out io.Writer, name string) {
        for _, p := range Plugins {
+               if !p.IsEnabled() {
+                       continue
+               }
                if p.Name == name {
                        p.Loaded = false
                        if err := os.RemoveAll(filepath.Join(ConfigDir, "plug", p.DirName)); err != nil {
-                               fmt.Println(err)
+                               fmt.Fprintln(out, err)
                                return
                        }
+                       break
                }
        }
 }
 
 // Install installs the plugin
-func (pl PluginPackage) Install() {
-       selected, err := GetAllPluginPackages().Resolve(GetInstalledVersions(true), PluginDependencies{
+func (pl PluginPackage) Install(out io.Writer) {
+       selected, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
                &PluginDependency{
                        Name:  pl.Name,
                        Range: semver.Range(func(v semver.Version) bool { return true }),
                }})
        if err != nil {
-               fmt.Println(err)
+               fmt.Fprintln(out, err)
                return
        }
-       selected.install()
+       selected.install(out)
 }
 
 // UpdatePlugins updates the given plugins
-func UpdatePlugins(plugins []string) {
+func UpdatePlugins(out io.Writer, plugins []string) {
        // if no plugins are specified, update all installed plugins.
        if len(plugins) == 0 {
                for _, p := range Plugins {
+                       if !p.IsEnabled() {
+                               continue
+                       }
                        plugins = append(plugins, p.Name)
                }
        }
 
-       fmt.Println("Checking for plugin updates")
+       fmt.Fprintln(out, "Checking for plugin updates")
        microVersion := PluginVersions{
                newStaticPluginVersion(CorePluginName, util.Version),
        }
@@ -621,10 +631,82 @@ func UpdatePlugins(plugins []string) {
                }
        }
 
-       selected, err := GetAllPluginPackages().Resolve(microVersion, updates)
+       selected, err := GetAllPluginPackages(out).Resolve(microVersion, updates)
        if err != nil {
-               fmt.Println(err)
+               fmt.Fprintln(out, err)
                return
        }
-       selected.install()
+       selected.install(out)
+}
+
+func PluginCommand(out io.Writer, cmd string, args []string) {
+       switch cmd {
+       case "install":
+               installedVersions := GetInstalledVersions(false)
+               for _, plugin := range args {
+                       pp := GetAllPluginPackages(out).Get(plugin)
+                       if pp == nil {
+                               fmt.Fprintln(out, "Unknown plugin \""+plugin+"\"")
+                       } else if err := pp.IsInstallable(out); err != nil {
+                               fmt.Fprintln(out, "Error installing ", plugin, ": ", err)
+                       } else {
+                               for _, installed := range installedVersions {
+                                       if pp.Name == installed.Pack().Name {
+                                               if pp.Versions[0].Version.Compare(installed.Version) == 1 {
+                                                       fmt.Fprintln(out, pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
+                                               } else {
+                                                       fmt.Fprintln(out, pp.Name, " is already installed")
+                                               }
+                                       }
+                               }
+                               pp.Install(out)
+                       }
+               }
+
+       case "remove":
+               removed := ""
+               for _, plugin := range args {
+                       // check if the plugin exists.
+                       for _, p := range Plugins {
+                               if p.Name == plugin && p.Default {
+                                       fmt.Fprintln(out, "Default plugins cannot be removed, but can be disabled via settings.")
+                                       continue
+                               }
+                               if p.Name == plugin {
+                                       UninstallPlugin(out, plugin)
+                                       removed += plugin + " "
+                                       continue
+                               }
+                       }
+               }
+               if removed != "" {
+                       fmt.Fprintln(out, "Removed ", removed)
+               } else {
+                       fmt.Fprintln(out, "No plugins removed")
+               }
+       case "update":
+               UpdatePlugins(out, args)
+       case "list":
+               plugins := GetInstalledVersions(false)
+               fmt.Fprintln(out, "The following plugins are currently installed:")
+               for _, p := range plugins {
+                       fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
+               }
+       case "search":
+               plugins := SearchPlugin(out, args)
+               fmt.Fprintln(out, len(plugins), " plugins found")
+               for _, p := range plugins {
+                       fmt.Fprintln(out, "----------------")
+                       fmt.Fprintln(out, p.String())
+               }
+               fmt.Fprintln(out, "----------------")
+       case "available":
+               packages := GetAllPluginPackages(out)
+               fmt.Fprintln(out, "Available Plugins:")
+               for _, pkg := range packages {
+                       fmt.Fprintln(out, pkg.Name)
+               }
+       default:
+               fmt.Fprintln(out, "Invalid plugin command")
+       }
 }