A brief history of plugin support in Gedit (and Gnome)
Gedit is one of the
oldest softwares of the Gnome fame,
and it got a major overhaul in time for Gnome 2.14. This was an eternity
ago: the new mdi
branch was merged in late 2005. This nearly full rewrite dropped many
then-obsolete dependencies (BonoboUI, popt) and introduced many
innovations over the previous versions, some of those spreading into
the whole Gnome project.
One of the best known innovations was a replacement for all those
annoying dialog boxes that popped for very good reasons at the worst
possible moment. Understanding that those messages were very contextual
in nature (they were always related to a particular document in some tab),
the dialog boxes were replaced by coloured message areas embedded directly
in the tab. This brand new idea got used by several other applications,
and eventually got into Gtk+ lately under the name “GtkInfoBar”.
Another of those innovations was the brand new plugin capabilities it
offered. At that time, it was possible to extend Gedit with C plugins for
a long time, but with Gedit 2.14 came a few new capabilities:
Object-Oriented Plugins. From then on, plugins were
objects that inherited from a base plugin class. Overriding virtual
methods became the standard way of defining the plugin behaviour. And this
was nice, as more and more people are familiar with object-oriented
programming these days.
Python Plugins.
PyGTK was in a very good state and
Epiphany, the web
browser everyone likes, was already using it to give access to its
internals to python developpers. Gedit borrowed it and extended it to
allow writing plugins as python objects as well.
This plugins engine has been very successful, and a lot of specialised
plugins appeared in a few years. It was also transplanted to a lot of
other Gnome applications (Eye of Gnome, Rhythmbox and Totem to cite the
most prominent ones) but we never made a library out of it: creating and
maintaining python bindings was time consuming and tedious, and it didn't
make sense to have a library if every users of it would have to duplicate
half of the code anyway.
But things have changed...
Thanks to the recent gobject-introspection project, “immediate”
bindings are flourishing. Out of a single easily generated introspection
dataset, it is now possible to benefit from full bindings for several
languages, including Python (through
PyGI) and the new kid in town,
Javascript (through Seed or
GJS). In total contrast with the
old “PyGTK way”, bindings are now generated in minutes and require close
to no maintainance and very few specific code. This fact alone creates the
ideal condition for a librarified version of the Gedit plugins engine.
Introducing libpeas
This is why I am very proud to announce the first release of
libpeas. libpeas is the next evolution of the Gedit
plugins engine, and is targetted at giving every application the chance to
assume its own extensibility. It also has a set of enhanced features with
regard to what Gedit used to provide, mirroring the desiderata of your
favourite text editor's developpers.
Multiple extension points.
One of the most frustrating limitations of the Gedit plugins engine was
that it only allows extending a single class, called
GeditPlugin. With libpeas, this limitation vanishes,
and the application writer is now able to provide a set of
GInterfaces the plugin writer will be able to implement as
his plugin requires.
On-demand programming language support.
libpeas comes with a set of supported languages (currently, C,
Python and Javascript). Those languages are supported through “loaders”
which are loaded on demand. What it means is that you only pay for what
you use: if you have no Python plugin, the Python interpreter won't be
loaded in memory. Of course, the same goes for the C and for the Seed/JS
loader.
Damn simple to use (or at least we try hard).
Adding support for libpeas-enabled plugins in your own
application is a matter of minutes. You only have to create an instance of
the plugins engine, and call methods on the implementations of the various
extension points. That's it, no clock harmed.
A shared library for everyone.
As I noted earlier, the latest improvements of our beloved development
platform made it possible to create bindings for apps very quickly. And
with the Gnome 3 announcement, this looked like the perfect timing to make
the plugins engine and its latest improvements available to everyone, with
a library. Also, hopefully it will reduce code duplication and allow bugs
to be fixed at the right place, once and for all, improving the quality of
our applications.
As a member of the Gnome community and as an offspring of gedit,
libpeas already shares most of the Gnome infrastructure and
philosophy:
- You can download the first release tarball on the Gnome FTP
server.
- You can browse the source and contribute using our
git repository.
- You can come and discuss with me and others on
#libpeas (GimpNet)
- And you can report bug or propose new features through the good old
Gnome
Bugzilla, against the libpeas module.
Many many thanks for their support to those who made this possible, and
especially to the current very active gedit team (Paolo Borelli, Jesse van
den Kieboom, Ignacio Casal Quinteiro and Garett Regier)
A few hints on using libpeas
As always for the new projects, it can take some time to grasp all the
subtleties at first.
Plugins versus Extensions.
Something that is going to puzzle most of the newcomers is the fact
that the libpeas API talks about both plugins and
extensions, two terms that are usually used interchangeably,
but who have very different meanings in libpeas.
Let's try and give a definition of both of these words in this
context:
Plugin.
In the context of libpeas, a plugin is a logical package. It's
what you will enable or disable from the UI, and at the end it is what
the user will see. An example of plugin for gedit would be the file
browser plugin.
Extension.
An extension is an object which implements an interface associated to
an extension point. There can be several extensions in a single
plugin. Examples of extensions provided by the file browser
plugin would be the configuration dialog, the left panel pane and
a completion provider for file names.
Building libpeas
Building libpeas is quite straightforward. But you need to
be careful to build pygobject with the
--enable-pygi option if you plan on using the Python
bindings capability, or you will experience weird bugs.
Sample code
The libpeas package contains a sample
application called peas-demo, and sample plugins written
in C, Python and Javascript.
I'm not going to cut and paste a lot of code here, but the global
idea is this one: you create a new PeasEngine instance
and give it the information needed for it to find your plugins. Then
you load some plugins (you can use the PeasUIPluginManager
for that purpose) and perform actions through some
PeasExtensions objects you can get from the engine.
PeasEngine.
The engine is the main object for the libpeas integration.
It will handle the loading and unloading of the various plugins, and
it will give you PeasExtensions.
PeasPluginInfo.
The plugin info object contains all the information about a plugin. It
is available even when the plugin is not loaded.
PeasExtension.
This is an extension, as seen from the application point of view. It
is actually a proxy to the real extension, and provides a single
peas_extension_call method that will allow you to call
a method of the extension by name. The reason why it is a proxy
instead of being the actual extension from the plugin is that some of
the bindings don't support GObject subclassing (and don't plan to).
Also, not using the actual object directly avoids quite a lot of
hard-to-debug reference issues. Thrust me on that, I tried!
PeasExtensionSet.
This is an automatically updated group of PeasExtensions.
At any moment of time, a PeasExtensionSet instance will
contain one PeasExtension for each plugin loaded that
provides the target extension.
PeasActivatable and PeasUIConfigurable.
Those are the two current built-in extension interfaces. The former
defines an object which will be activated on object creation and
deactivated on object destruction, while the latter is used by the
embedded plugin manager UI, to provide a configuration dialog. You
can of course provide your own extension interfaces!
What's next?
We are now planning on porting a few apps to libpeas in the
following weeks. Several contributors already marked their interest in
libpeas for the application they contribute to, and I would like
myself to see our mighty text editor ported as well. I guess I will be
posting more announcements for ported apps and niceties as time passes.
But libpeas is far from a finished project yet. Below are a
few tracks for future development of libpeas. Don't hesitate to
jump in!
Great documentation.
The current documentation for libpeas is, well, the bare minimum.
There is a quite complete reference documentation for the API, but there
is no howto, no implementation guide for the plugins in various languages,
etc. And a good documentation contributes a lot to making a good library.
Named extension points.
Extension points are currently defined by interface types. While this
works great for many use cases (Nautilus has done this for ages), it's
actually quite limitative sometimes. For instance, the
PeasActivatable interface, which defines an extension that
can be activated, and then deactivated on objects of a certain type,
could easily be re-used in different contexts: windows, tabs, documents.
So you can easily see that named extension points here would be a nice
feature.
Construction-time properties.
Currently, extensions are instanciated with no dynamic arguments. But in
some cases, it would be nice if we were able to give some arguments
at instanciation time, through GObject properties. For instance,
PeasActivatable extensions are usually related to a single
object, but currently this object is give as an argument for each of the
methods of the interface. Passing it to the extension at creation time
and removing that argument from the method prototypes would be a nice
touch.
More automatisms.
With the rise of GSettings, it would now be possible for
libpeas to manage itself the storage of the plugins state. It
would then make sense to deal ourselves with “invisible” and “autoloaded”
plugins, which are currently left at the application discretion.
Vala and C++ support.
Adding support for those should not be hard, and would turn out to be
very low cost as both are compiled languages and would make use of the
existing loader. Totem already has Vala support, too, and the C loader
has been thought with C++ support in mind, thanks to Debarshi Ray.
Guile support.
This might be the nasty secret desire of mine, but I've always found
Scheme support for plugins appealing. And this might well become true if
sbank succeeds, and with Andy Wingo's support... Andy?
So, here is the current status of libpeas. Now, download it,
try it, and come and join us on #libpeas to share your thoughts!