Add support for plugin manager within micro

This commit is contained in:
Zachary Yedidia
2020-02-02 14:20:39 -05:00
parent 09ea82c97e
commit b0624cb66e
6 changed files with 190 additions and 129 deletions

View 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

View 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)

View 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,

View 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 {

View 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")
}
}