diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 59cbb802..4bcf99cb 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -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) } diff --git a/internal/action/command.go b/internal/action/command.go index bd303a18..d6c7c3ae 100644 --- a/internal/action/command.go +++ b/internal/action/command.go @@ -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 diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index 01fe167f..48b30e04 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -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) diff --git a/internal/buffer/eventhandler.go b/internal/buffer/eventhandler.go index aa14a5ba..bd06f02e 100644 --- a/internal/buffer/eventhandler.go +++ b/internal/buffer/eventhandler.go @@ -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, diff --git a/internal/config/plugin.go b/internal/config/plugin.go index e46e3c6e..1e1f9685 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -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 { diff --git a/internal/config/plugin_installer.go b/internal/config/plugin_installer.go index 0ca38dde..14622623 100644 --- a/internal/config/plugin_installer.go +++ b/internal/config/plugin_installer.go @@ -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") + } }