Plugin metadata formats: what has been tried already, why did they fail, and can it succeed?

Introduction

A standard plugin metadata format could be very helpful in helping people sort, search, curate, and explore the Vim and Neovim plugin ecosystem in this time of amazing growth.

It’s interesting that, after many many years of Vim plugins, every attempt at creating a metadata and/or package format has more or less failed to catch on.

I am hoping to compile a list of past attempts at plugin metadata specifications, in order to learn from past ideas. I am also hoping to gather feedback on whether Vim/Neovim plugins should have a standard metadata specification, and, if so, where and how that specification should be maintained/enforced.

I am also interested in prior art from other ecosystems. What works well, and what doesn’t? It might be interesting to look at other IDEs and text editors with “plugin-oriented” designs (JetBrains, Sublime), the other “hackable” text editor Emacs, programming languages with package metadata systems (Python, NodeJS, etc), and even system-level package managers that have a high rate of community contribution (AUR, Brew).

There is also a question of interoperability with Vim, as we continue to diverge from Vim on things like remote plugins, Lua, etc. Should interoperability be a goal? What are the implications of deciding yes or no?

Or maybe we just don’t need any of this, and you can all ignore this post :laughing:


The situation

With the (relatively recent!) arrival of the built-in package system (:packadd and friends), we no longer need to worry about a stable file format for the most part. Great! Plugins have more or less standardized around the Vim runtimepath file layout, because they can be copied and pasted verbatim into pack/*/opt or pack/*/start. Some non-standard elements remain, for things like compiling artifacts required for remote plugins, but the existing ad-hoc solutions work well enough.

However, the community has not successfully developed a metadata format for packages. Moreover, the community has largely fixated itself on centrally-hosted Git repositories as the primary distribution channel, specifically Github repositories.

The benefit is that it is trivially easy for a hobbyist to distribute their plugin; all they need to do is publish the source code to a Git repository somewhere, most likely Github. This is a good outcome!

Github in particular has a lot of features that do the job of a package format and distribution system. It is a low-friction, high-convenience way to get software out into the world, and Vim plugin managers have embraced it as the default way to get software out into the world.

However, it’s fascinating to see a perfectly good distribution channel, Vim.org, almost entirely cast aside in favor of something as chaotic as “head-only”/“rolling” Git repos. Have we lost something in the process? I would argue that we have, particularly as the Neovim plugin ecosystem continues to expand very quickly, and a typical power user could have 50, 75, even 100+ plugins in their configuration.

Moreover, the current state of affairs makes it extremely difficult to do certain basic tasks and perform certain queries, such as:

  • Find plugins that have been updated in the last 6 months
  • Find plugins that can be used with both Vim and Neovim
  • Find remote plugins written in Rust
  • Find plugins that define colorschemes
  • Find plugins that define new filetypes

Lists like awesome-neovim and Neovimcraft are all the more impressive because of how scattered and disorganized the plugin ecosystem is. Building a curated list of plugins is challenging, and so is building a tidy user interface for browsing and interacting with them. Doing all of that without a metadata standard is an even bigger challenge, and it’s one that I think the community should be interested in mitigating.


Past attempts

Vim.org scripts

The Vim.org scripts collection is still alive and well. But I suspect that a significant fraction of its use comes through its Github mirror, and it’s certainly not where the cool kids go to upload Neovim plugins written in Lua and Rust.

I think that using opaque numerical “script IDs” instead of human-readable names/slugs was and remains its single biggest design flaw. It has pretty much everything one might want in a distribution system, but it’s impossible to tell what a plugin is, based on its URL.

There are/were other good reasons for people to switch to Github from Vim.org (e.g. nice Markdown rendering in readme files, Github stars, Git itself). But I genuinely think it might have maintained more its relevance if it didn’t have the opaque script IDs.

The craptacular Google custom search probably didn’t help. Those always sucked, even back when Google wasn’t considered evil.


What would a successful metadata standard look like?

I propose that any plugin metadata standard should have the following desirable attributes, in no particular order.

It needs a “killer app”

Show, don’t tell! People won’t start using a thing unless they see a benefit from using the thing. If a site like Neovimcraft adopted the specification and used it to improve the site, people would be eager to add it to their projects.

People aren’t lazy, but they need to be convinced.

Compatibility with existing standards

Vim-plug was not the first of its kind, but its tremendous popularity has helped solidify some standards around how people refer to and work with plugins.

For example, plugins are more or less universally identified with the author-name/plugin-name pattern. There’s no reason this shouldn’t be a universal identifier plugin across platforms. Name collisions would be very, very unlikely.

If you wanted to guard against name collisions, you could convert these to proper URIs by prepending schemes like github:/gh:, gitlab:/gl:, sourcehut:/srht:, et al. Omitting the scheme would of course imply github:. An easy upgrade path from informal current standards to something a little bit tidier with a little bit more longevity is a great feature, in my opinion.

(Note: These URI schemes seem like an easy win, and could be implemented in Your Favorite Plugin Manager right now, without regard to anything else I wrote in this post.)

Some kind of dependency tracking

Lack of sensible dependency tracking and management I think is a nontrivial impediment to building sophisticated and interesting software on top of Neovim. With an unambiguous naming specification, it should be pretty easy to list your dependencies, and it will be easy for plugin managers to resolve those dependencies.

Easy to get started

Metadata should be simple: easy to explain, easy to understand, easy to learn, easy to remember. It should be so easy to add metadata to your plugin that it should be embarrassing to skip it.

Low friction leads to moderate adoption, moderate adoption leads to social pressure on the holdouts. This is how network effects accumulate.

Smooth progression from “nothing” to “something”

The set of minimal required fields should be tiny. Maybe it should be nonexistent; clients could infer the metadata from a Git repository. Name is easy, author is easy on most (all?) major platforms, version and release dates are derived from tag and commit history, etc.

Wait, what?? Zero required fields? What’s the point of specifying anything at all? Because you can add metadata, eventually, whenever you get around to it, or when your plugin gets popular on Reddit and you want it to be taken more seriously, or when someone kindly does it for you in a PR.

As an additional benefit, having a specification means that people who build tooling (e.g. package managers like Vim-plug and Packer) have a set of common standard behavior to agree on.

Maybe the total set of fields would be as simple as:

  • A name; fall back to Git repo name
  • An author; fall back to Github author or equivalent if available
  • Where to get the code, if it isn’t already obvious
  • A version, if one can’t be inferred from a Git tag
  • A homepage, if it isn’t the same as “where to get the code” (most plugins don’t have separate homepages)
  • A summary description
  • Dependencies
  • Tags. I think people should tag their stuff. See below.

Metadata fields should not be tightly constrained

Don’t force people to think too hard. Don’t force people to make hard decisions. Naming things is hard. Don’t force people to do hard things. Simple should also be easy; we should work hard to make sure that writing your package metadata isn’t hard (!!).

Individual fields should not be tightly constrained. Version numbers just need to go up over time, they don’t need to follow Semver or Calver or Whateverver. Version numbering isn’t being used for high-precision reproducibility here. I just want to know if there’s been a stable release in the last 3 years.

The only exception is tags. People love to make a giant fucking mess out of tags, so don’t let them do it. Tags should be ASCII lowercase, /^[a-z0-9_-]+$/. Use a / to separate hierarchical levels. Examples of useful tags: colorscheme, implemented-in/rust, programming-language/lua, file-browser, frontend/gui, operators. That said, even with all these restrictions, tagging systems devolve into unusable chaos without strict central control (e.g. Stackoverflow). So maybe we just don’t have tags in the metadata; leave this up to the Neovimcrafts over the world?

Easy-to-use file format

The file format itself should easy to type without machine or IDE assistance, and it should also be easy to parse and work with programmatically.

TOML seems like a good candidate now that it has reached version 1.0, but there doesn’t appear to be a maintained TOML parser in Lua.

Maybe the configuration format could be (a subset of) Lua itself.

I’m not sure if the Neovim community is (or ever will be) ready for S-expressions. Although I’m sure the Fennel/Aniseed fans among us would be overjoyed to see it.


Notes

  1. I am not here to bemoan the lack of a traditional package distribution channel, the dependency on M$oft-owned Github, etc. Vim is not unique in its dependency on Git and specifically Github; very serious programming languages like Go and Crystal have adopted similar conventions.

  2. Another interesting consequence of the transition to Github is the growing epidemic of plugins that don’t use Vim help files for documentation. Perhaps we as a community should invest in better help file authoring tools, such as a Markdown-to-Vim-help converter.

5 Likes

thanks for this. I may answer more in details later but I’ve published a few days ago this My Hakyll Blog - Automatic install of neovim plugin dependencies to explain how to leverage luarocks to expose your plugin and its dependencies. The advantage is that the tool and lua ecosystem already exists. There are 5/6 plugins that can be leveraged like that right now. The difficulty is that packer doesn’t deal well with luarocks right now but were that fixed, I could see this (luarocks and a PLUGIN.rockspec file in each plugin repo) being one solution

2 Likes

That’s a really interesting idea. Happy to see other people are thinking about this!

Easing the burden of including “support” libraries like Plenary would be one of the big use cases for being able to describe metadata in such a way that dependencies are included. Arguably one could even start removing features from Neovim and re-distributing them as plugins, once a system is in place for this kind of thing.

Forgive my questions, I’m not a Lua user outside of Neovim and don’t know much of anything about the ecosystem:

  • Would the Luarocks technique only cover Lua-based plugins, or would you be able to distribute a pure-Vim plugin that way?
  • Would the Neovim community set up a standalone Luarocks index/repo/archive/whatever-it’s-called for Neovim plugins that aren’t of interest to non-Neovim Lua users?
  • What is the metadata story? How easy is it to search for packages? Can you tag your own packages?
  • How easy is it to publish a package for someone who isn’t a Lua dev outside of the Neovim world?

If you have any links to guides, tutorials, reference docs, etc. that would be helpful for understanding the above, I’d appreciate it.

I’ve spoken with the Luarocks maintainer and they are open to the idea of storing Vimscript plugins there. No major technical blockers, but might need a few small enhancements to Luarocks. First step is to try it.

  • Would the Neovim community set up a standalone Luarocks index/repo/archive/whatever-it’s-called for Neovim plugins that aren’t of interest to non-Neovim Lua users?

Maybe. Doesn’t need to be decided yet. In general I favor the most passive, zero-code approach before adding layers.

  • What is the metadata story? How easy is it to search for packages? Can you tag your own packages?

https://luarocks.org/ has a search and open source implementation. The bits are there and we can build on them. But zero-code is ideal, if possible.

  • How easy is it to publish a package for someone who isn’t a Lua dev outside of the Neovim world?

Publishing to luarocks isn’t too hard but requires an extra login. Perhaps we could figure out a way to add 3P auth (such as GitHub) to luarocks. That benefits everyone.

3 Likes

Great to hear that this is already in progress!

My big question is, how would this work with non-Lua files, like plugin/, ftplugin/, etc.?

I see some stuff on the Web about “data files” in Luarocks packages, but nothing conclusive:

Or is this area where the “few small enhancements” that might be needed?

Great idea. I’ve been asking myself that for a long time now, and I finally see someone bringing it up.

I’d definitely benefit from this, for I keep my plugins at gitlab instead of github. Not only that, I also use the branch called main as my default branch instead of master. For example, the main plugin have to be installed like the following:

Plug 'https://gitlab.com/nvpm/nvpm' , {'branch' : 'main'}

I know I should just follow standards, but - as you mentioned - the ones that are out there are not official, but merely agreed upon.

Although I couldn’t come up with a definite solution, I long to see this idea developed. I’m glad you started it.

1 Like

Good question, that’s actually my last blocking element for in my PoC. The copy_directories installs in a subfolder I can’t get rid of along with files that I think should not be installed. For pure lua plugins, I just manually move the “doc” folder after install.
I think it would work to tell luarocks to install the vim scripts as lua scripts, I dont think it checks the extension. Now if you set lua_modules_path = "." in your luarocks config and install with --tree plugin/folder you can reproduce the current plugin structure.

If the plugin is not going to be used as a dependency itself, the best is to keep its folder untouched and just install its dependencies

luarocks install --only-deps plugin.rockspec --tree $XDG_DATA_HOME/nvim/lua_deps

^ if plugin manager would run this for vim plugins with a *.rockspec file, that would solve 90% of the problem.

1 Like

Yes, please don’t forget VimL. My plugins are written in it.

2 Likes

Indeed, supporting VimL as well as Lua needs to be an explicit and first-class feature. Even if you work mostly in Lua, small amounts of Vimscript are useful as “glue” and as a “top layer” for your plugins. I still prefer writing a .vim file over Lua vim.cmd, for defining top-level commands and such.

1 Like

Here’s a funny idea: what if we as a community assembled a collection of scripts that made it easy to package Neovim plugins as actual packages for repos like AUR, Homebrew, Pkgsrc, or even Debian and Fedora?

It seems kind of wild, but in reality it solves the problem of not having a metadata format: just use your system’s existing package format. Plugin managers can continue support Git clone and other such functionality, but users who want to manage plugins in a more system-integrated fashion can also do so, and they can point their plugin managers to the appropriate locations on their systems.

This also helps deal with the situation where a plugin might require some external dependency (e.g. an executable or Luarocks package).

Thoughts? Are traditional package repos too restrictive/slow for our purposes? Is it too cumbersome for authors, even with some kind of CI script that automatically packaged the plugins?

I’ve written GitHub - teto/vim2rockspec: Generate a .rockspec for your plugin repository ! to help create a rockspec from a github repo.
I have automatic dependencies working in nixpkgs Software ninja - Automatic install of neovim plugin dependencies (2) so it is possible. Hopefully packer.nvim can implement it as well.

2 Likes

Just sharing one of my thoughts on this topic.

One of the thing I absolutely love is that the plugins are just git repos. If I want to update something or contribute to a plugin, I just go into the directory where they are installed, create a new branch and start experimenting, commit, push etc. If plugins start becoming traditional packages I’ll lose that workflow and I don’t really like that idea.

(I still use vim-plug and haven’t tried packer yet, so not sure if the behavior I described above also applies for packer.)

1 Like

One of the thing I absolutely love is that the plugins are just git repos. If I want to update something or contribute to a plugin, I just go into the directory where they are installed, create a new branch and start experimenting, commit, push etc.

One does not preclude the other, you can still reference the repo if that’s what you prefer.

I cringe when I see the install steps for a plugin that depends on others, it limits the ability to add new dependencies (since it breaks setups) and inherently the features of a plugin.
You can have a “central” package manager that eases plugin discoverability, you can refer to dependencies by a simple name without having to mention the whole url.

1 Like

@MunifTanjim as an example, the Python package manager (Pip) supports installing directly from a Git repo. But inside the repo, you still have a metadata file that specifies the package name, version, and dependencies. I believe the Go packaging system works similarly.

With a standard metadata format, you can choose to build out a centralized distribution channel (like the Vim.org “scripts” site), but you don’t need to use it.

Having a standard metadata format would also make it easier to write a script that does something like “turn any Vim plugin into an AUR package”.

I think my original post did a poor job of conveying the distinction between the metadata format itself and the various things you can do once you have that metadata format.

1 Like

My biggest concern right now is how to reconcile the need for some kind of standard dependency specification with the desire to not force plugin distribution to depend on any specific channel.

Let’s say you are writing a plugin that depends on another plugin. For example the your new plugin is called moondust/my-great-plugin, and it depends on wintershadows/cflow-vim:

return {
  name = 'moondust/my-great-plugin',
  requires = {
    'wintershadows/cflow-vim',
  }
}

We could say that the plugin has a namespaced name, wintershadows/cflow-vim, and that name could be independent of any distribution channel. But then the question arises of how an arbitrary plugin manager should go about fetching the contents of that plugin.

Currently the convention is to assume that the name of the plugin is a Github username and repo name, corresponding to the URL https://github.com/{username}/{reponame}.git.

But what if wintershadows/cflow-vim is not actually hosted on Github? For example, it might be hosted on Sourcehut, at https://git.sr.ht/~wintershadows/cflow-vim.

One solution is to require that plugins are encoded as a URI:

return {
  name = 'moondust/my-great-plugin',
  requires = {
    'srht:wintershadows/cflow-vim',
  }
}

Now the distribution channel is more or less attached to the name of the plugin. Maybe that’s not such a bad outcome! We already have the problem that, if the plugin author wants to switch to a different host, it creates a headache for all users. Requiring a URI in the standard package name simply means that this metadata standard is not attempting to solve that problem, and that’s probably okay.

The community would also have to agree on a common set of URI schemes, assuming Github when the URI scheme is omitted (which is what most plugin managers do today).

Things get a little tricker if you use an “opaque” URL that doesn’t correspond to a URI scheme like srht:, as with the Fugitive plugin:

return {
  name = 'moondust/my-great-plugin',
  requires = {
    -- Maybe we can auto-detect vcs='git' from the .git suffix?
    { uri = 'https://tpope.io/vim/fugitive.git', vcs = 'git' }
  }
}

What’s the name of this plugin? Now you can’t know the name until you actually go fetch the contents of the plugin (or at least its metadata file) from some remote system.

So maybe then you must specify a name in addition to a URI:

return {
  name = 'moondust/my-great-plugin',
  requires = {
    -- Inferring Git VCS from .git suffix
    { uri = 'https://tpope.io/vim/fugitive.git', name = 'tpope/fugitive' }
  }
}

But then you’re adding a lot of verbosity to the config! The fully-qualified “name” of this plugin is { uri = 'https://tpope.io/vim/fugitive.git', name = 'tpope/fugitive' }. Moreover, it raises some more questions. What happens if I make a mistake and write { uri = 'https://tpope.io/vim/fugitive.git', name = 'tpope/fugitivo' } instead? Should my plugin manager fail to run with an error saying that the name doesn’t match the name provided by the plugin itself?

Or maybe we are OK having to fetch the plugin name from the remote host, and we simply use the full URL in our dependency specification:

return {
  name = 'moondust/my-great-plugin',
  requires = {
    -- Inferring Git VCS from .git suffix
    'https://tpope.io/vim/fugitive.git'
  }
}

If you need to refer to the name of the plugin instead of its URL, you would have to go to the plugin repo itself and check its name = attribute. This seems like a reasonable compromise that covers current use cases, while it does not try to cover additional use cases.

Finally, there is always the possibility of establishing a central plugin registry that stores a mapping between tidy plugin URIs (maybe something like plugin:tpope/fugitive) and their upstream host locations (like https://tpope.io/vim/fugitive.git). This registry wouldn’t have to actually host the plugins, it would just provide redirection from some canonical URL to the place where the plugin is actually hosted. But then you have to worry about authorization and security, authenticating that only the plugin author is allowed to change the target URL. Someone would have to develop and maintain that central registry.

In the spirit of this discussion, here is a github action to autmatically push to luarocks https://www.reddit.com/r/neovim/comments/10z1y07/introducing_luarockstagrelease_publish_your/
My Hakyll Blog - Publish your Neovim plugins to LuaRocks
I am working with it to add it to sveral plugins and leverage the rockspecs in nixpkgs.

Some more work on using luarocks with Neovim:

  • I’ve written a blog post on how easy it is to use luarocks and busted to test Neovim plugins (with neovim 0.9+).
  • And here’s an over-engineered Nix derivation that luarocks-tag-release v5 now uses to run luarocks test if it detects a .busted file in the plugin directory.

The number of Neovim plugins on LuaRocks is growing…

2 Likes

Some more thoughts related to automatic dependency management: mrcjkb.dev - Rethinking the `setup` convention in Neovim. Is it time for a paradigm shift?

2 Likes

Thought it might be worth sharing rocks.nvim here :slight_smile:

It’s a plugin manager for Neovim that uses luarocks.
Still quite early in its development, but quite usable already.

3 Likes