Hello,
I'm (as of now) motivated to implement proxy plugins (to my amusement, until splitwindow2 gets finally reviewed...). Therefore I would like to receive comments on my proposed concept and APIs (further below).
I would also love to discuss about better terms for proxy and "the proxied plugins". I think some adorable internal code/nick names could help us to not get confused in this business.
Note that I have implemented very little yet, so there might be unknown roadblocks and things can easily change. Please also note that I intend to achieve the goals without breaking plugin API/ABI.
The concept:
The basic idea is that proxy plugins initially call a Geany API to register themselves as proxies, providing criterias to select potential plugins (for now, this is only a list of file extensions) and a few hooks to call into the plugin when Geany needs it.
Geany will match files against these criterias and, on match, call into the plugin for the first time. This call should probe whether or not the plugin can actually handle the plugin. I think this is needed because a) criterias/file extensions might be ambiguous and b) the plugin might be written against an incompatible version of the proxy (think python 2 vs 3).
Then when Geany generates the plugin list (i.e. when the user opens plugin manager (PM) dialog) it'll call a second time into the proxy. This time the purpose is to load the actual plugin, and install plugin-specific init(), configure(), help() and cleanup() hooks. Unlike in classic plugins these hooks must call into proxy, where they have to be dispatched to call the actual plugin code (this is why we call them proxy right?).
Once loaded, Geany generally doesn't know/care about the difference between classic plugins and proxied plugins. This achieves the ultimate goal: That proxied plugins transparently integrate into the PM dialog and become first-class citizens.
Part of the proposed concept is to add a "fake proxy" for the builtin .so-file support. This enables to use the same code for all plugins (proxied or not). This should greatly reduce the maintenance effort. The fake proxy naturally must be compiled in but otherwise exposes the same APIs as actual proxies.
The APIs:
This propsal adds 3 major functions and alters the 4 existing plugin hooks.
plugin_register_proxy(GeanyPlugin *proxy_plugin, ProxyPluginInfo *info, gpointer user_data);
This is initially called by the proxy and lets Geany know about it. ProxyPluginInfo contains the criterias and pointers to the probe() and load() functions. user_data is proxy-specific and passed to probe().
gboolean (*probe)(const gchar *file, gpointer user_data, gpointer *plugin_data);
This is implemented in the proxy and should return true when it can handle the plugin, it basically replaces the existing utils_str_casecmp(G_MODULE_SUFFIX, ...) call. There is a catch though, while probe() should be as fast as possible, I expect that some proxies still need to load the file half-way or even in full to make that decision. In order to maintain that state, it can set the plugin_data pointer. This will be passed to the load function. This way loading the plugin again can be avoided.
gboolean (*load)(const gchar *file, gpointer user_data, gpointer *plugin_data);
The same function signature, but a different purpose. The proxy should now fully load the plugin specified with file such that is ready to run (it has the same signature in case it needs to load in probe() already, then this function can be the same as probe()).
As for the existing hooks, these need gain a parameter (gpointer plugin_data). Because the hooks can be indirect now, the proxy needs plugin_data to be able to properly dispatch the hooks on a per-plugin basis. I.e. the hooks become:
void (*init) (GeanyData *data, gpointer plugin_data); GtkWidget* (*configure) (GtkDialog *dialog, gpointer plugin_data); void (*configure_single) (GtkWidget *parent, gpointer plugin_data); void (*help) (gpointer plugin_data); void (*cleanup) (gpointer plugin_data);
Because the plugin_data parameter is exclusively for the proxy, the actual plugins do not receive it. They still implement the non-plugin_data variant. The parameter is added to the end so that these can still point to the module symbols for .so file plugins.
That's it so far. I think this proposal should allow to reach our goal (first class plugin scripts) while avoiding to break plugins ABI/API.
Best regards.