The current/earlier system/plan for handling plugins has a number of problems:
PluginManageris kinda hairy. This can be fixed by #371
I propose the following design as a solution to all of these concerns, it works best after or together with #371.
Instead of buffers having a defined syntax, they have “tags” which are a
There should be an API for both the frontend and plugins to add, remove and perhaps atomically compare and replace tags. These might take the form of tag “transactions” with a set of inserted tags and removed tags, either of which may be empty, the transaction only runs if all the tags to be deleted are present. This will prevent asynchrony issues allowing for example, a file to end up with two syntax-related tags.
Plugin manifests may define syntaxes that they provide, these specify a human-readable
text.markdown), and a list of extension strings. When a file is loaded with one of the given extensions it will be tagged with the given scope plus the id of the plugin appended as an atom, for example
This allows disambiguation for example if
fast-rust both provide highlighting for Rust, it's possible to determine which one should highlight a buffer, and possibly provide a UI for choosing which does it, with the default determined by plugin priority/order (a concept we need for other things).
The use of scopes allows searching for buffers with varying specificities with selectors, for example "" matches all buffers, “source” matches all code files, “text” matches all markup/text files, “source.rust, text.html” matches all Rust and HTML files regardless of what is highlighting them, “text.html.ruby.syntect” applies only to Rails templates highlighted by syntect, “text.markdown plugin.simplenote.synced” matches markdown files that have also been specially marked for syncing by the
Plugin manifests can define a scope selector (which can use commas to match multiple things, or maybe this could be just a list of selector components) that they bind on. This is how lifetimes work, which will be described later. Plugins also automatically bind on the syntaxes they have defined (including the included plugin name), as well as
The core process has a set of currently running plugins. Every plugin (as specified by manifests), is either running or not running.
Whenever the tags of a buffer change, which includes when a buffer is created or deleted, all bindings are updated. Any plugins that did not previously bind on a buffer are sent a notification about the newly bound buffer. Any plugins that were binding on a buffer but no longer should (either a tag was removed or the buffer was closed.), are sent a message that a view was unbound.
If a plugin is bound on a view but isn‘t running, or a command defined in the plugin’s manifest is run, that plugin is started.
When a plugin's last bound view is unbound and all commands it is running are complete (TODO: we may need another mechanism for knowing this), stop the plugin. Note that buffers may be bound while a command is running, so a global command from the plugin can create a buffer that it binds on which keeps it alive.
All plugin notifications for a buffer are only sent to plugins bound to that buffer.
I think this design solves a lot of problems in one simple-ish mechanism:
There's a couple problems that make this not my primary suggestion: