Labs/Bespin/DesignDocs/PluginAPI
We want to allow people to extend the Bespin project in as many ways as possible. Right now there are a few ways to do so:
This is fine for little things, but what about sharing functionality?
For this we want a plugin API. The goal is for this API to be as simple as possible.
As well as commands and the config work, we also have the low level construct of the publish/subscribe event system that you can tie into. This shows that there are actually a couple of pieces to a plugin API:
- First, we need an API that allows you to register and activate new plugin code
- Second, we need public APIs to let plugins easily get done what they need too, without having to go deep into the code, and accessing APIs that are a known public contract. This is vital to the goal of rapid innovation without constantly breaking plugins.
Use Cases
We want to drive the design of the plugin API from use cases. Can you think of any?
Commands
A set of commands, such as a new command store with subcommands (e.g. vcs)
Syntax Highlighter
Attach a new syntax highlighter
Code Processing
Such as:
- real-time syntax checking
- special processing for certain types of files
- spell checker type work on code
When you open up a settings file, limit what you can type so it is only "name value" and allow the user to rerun that file
Editor Tweaks
Add new features to the core editor itself. Mucking with the model.
Toolbar
Add new buttons and icons to the toolbar
Selection
When text is selected, hook in
Content Changed
As soon as the content in the edit buffer has changed, do something (meta with implementations below)
Settings
Create new settings
Other useful features
- Be able to use a Bespin project as a plugin (while working on plugins). Possibly even be able to use a directory within a Bespin project.
- Be able to reload plugins that you're working on
- Plugins should be loaded lazily to improve startup time. For example, a plugin that provides a syntax highlighter shouldn't be loaded until that highlighter is needed.
Core Plugin API
Here we will talk about the first goal, the core plugin API itself
A one-file plugin
Let's start with an example:
// Typically, we'll start with some metadata. This metadata must be valid JSON // (no functions in the metadata). exports.info = { // Plugins all have a name that represents the namespace of the plugin // This allows the plugin system to locate any functions that are // referred to by name name: "luaHighlighter", // up to the first "." would be used as the short description. Everything // else is viewed in a long description context. description: "Syntax highlighter for the Lua programming language.", // version numbers will be good for automatic updates. version: "1.0", // core parts of Bespin (and even plugins) can query for metadata // and request that a plugin is loaded. In this case, // Bespin's simple syntax highlighting engine will look for plugins // when it opens a file type it doesn't know how to handle. // If it's a lua file, it will see that this plugin can handle // lua. "bespin.syntax.simple": { extensions: ["lua"] } } // The activate function is called when this plugin is loaded. // It is responsible for setting up even listeners. exports.activate = function() { // note the use of "subscribe" instead of "bespin.subscribe". // in a plugin context, all subscriptions are handled for // you automatically. If the plugin needs to be reloaded, // all event handlers will automatically be unsubscribed. subscribe("bespin:syntax:simple:highlightBlock", function(block) { // do something }) }
Plugin modules are implemented not as Dojo modules, but rather as ServerJS Securable Modules. This would allow seamless interop for plugins that have both client and server side JS components.
Wordpress-like
(TODO: ideas in this section to be shuffled around in light of the new direction in the previous section.)
What if we have a new directory, BespinSettings/plugins which is where your plugins go.
They can either be one file (myplugin.js) or a directory (myplugin/plugin.js with other stuff too).
To activate a plugin, you add a line to your config file (loadplugin pluginname say). We don't automatically load all plugins JUST in case there is an issue, or you want to easily activate and deactivate plugins without having to delete files. In theory we could create a "loadplugin all" semantic for those that really want it.
To get a plugin into the system, we need to piggy back on the ability to import code into a project. Right now we have an explicit import command that ends up with a new project. We need to also allow you to import a piece into an existing project. In this case, into the "plugin" directory under the magic "BespinSettings" directory. This is a dependency. We also want to let you do this not just by grabbing a URL, but also via file upload of a .zip|tgz|...
The plugin itself should have a set of metadata associated with it. This can be placed into it via comments, a la Wordpress.
We will have new events for items such as:
- plugin:activate
- plugin:deactivate
- plugin:load:complete
and once loaded a plugin can listen for:
- cmdline:loaded
- settings:loaded
- toolbar:loaded
The plugin will listen in on these and will then hook itself up.
How does a plugin change the default behaviour? What if we change bespin.publish/subscribe to have an id, or a type associated with the event. All default behaviour will pass in a "default" or whatever makes sense.
Then, the plugin, can unsubscribe() on the type default when it adds itself to the subscription queue.
We also need to think about how data, or images, or JSON can be used from within a plugin (using its own resources). Furthermore, having a simple include() from within the config and plugin work would be nice. This could piggy back on dojo.require/provide.
Plugin Directory
Plugins will be placed in either BespinSettings/plugins/yourplugin.js or BespinSettings/plugins/yourplugin/*. You will use the directory version if your plugin packages up multiple files and resources such as CSS, images, etc.
Discovery
Another part of plugins, is having people find them! A natural fit would be to use the Mozilla Add-ons infrastructure (AMO) to host plugins.
Cross domain loading
With a cross domain Dojo build in place, we will be able to load plugins that live remotely.
In your config file you will be able to fire an event to load a plugin:
bespin.plugin.load("http://foo.com/bespinPlugin.zip");
Server and Client Plugins
One *future* item that we are excited to explore is the notion of a plugin that can not only contain client code to change the editor or dashboard, but also deploy server changes.
This would be pretty unique, and would enable feature rich changes across the stack.