[geany/geany] 6e5ca6: plugins: add geany_plugin_register_proxy() to the plugin API

Thomas Martitz git-noreply at xxxxx
Mon Oct 5 20:11:12 UTC 2015


Branch:      refs/heads/master
Author:      Thomas Martitz <kugel at rockbox.org>
Committer:   Thomas Martitz <kugel at rockbox.org>
Date:        Mon, 05 Oct 2015 20:11:12 UTC
Commit:      6e5ca69e2e9ddbfc2cc44527a1aa5fca8db09167
             https://github.com/geany/geany/commit/6e5ca69e2e9ddbfc2cc44527a1aa5fca8db09167

Log Message:
-----------
plugins: add geany_plugin_register_proxy() to the plugin API

This function finally allows plugins to register themselves as a proxy
for one or more file extensions.

Lots of documentation is added to doc/plugins.dox, please refer to that for more
details.


Modified Paths:
--------------
    doc/plugins.dox
    src/plugindata.h
    src/plugins.c

Modified: doc/plugins.dox
397 lines changed, 397 insertions(+), 0 deletions(-)
===================================================================
@@ -43,6 +43,7 @@ GeanyFuncs::cleanup functions).
 
 @section pluginsupport Plugin Support
 - @link howto Plugin HowTo @endlink - get started
+- @ref proxy
 - @ref legacy
 - @link plugindata.h Plugin Datatypes and Macros @endlink
 - @link pluginsignals.c Plugin Signals @endlink
@@ -725,4 +726,400 @@ void geany_load_module(GeanyPlugin *plugin)
 @endcode
 
 
+ at page proxy Proxy Plugin HowTo
+
+ at section proxy_intro Introduction
+
+Geany has built-in support for plugins. These plugins can alter the way Geany operates in many
+imaginable ways which leaves little to be desired.
+
+However, there is one significant short-coming. Due to the infrastructure, Geany's built-in support
+only covers plugins written in C, perhaps C++ and Vala. Basically all languages which can be
+compiled into native shared libraries and can link GTK libraries. This excludes dynamic languages
+such as Python.
+
+Geany provides a mechanism to enable support for those languages. Native plugins can register as
+proxy plugins by being a normal plugin to the Geany-side and by providing a bridge to write plugins
+in another language on the other side.
+
+These plugins are also called sub-plugins. This refers to the relation to their proxy.
+To Geany they are first-class citizens.
+
+ at section proxy_protocol Writing a Proxy Plugin
+
+The basic idea is that a proxy plugin provides methods to match, load and unload one or more
+sub-plugin plugins in an abstract manner:
+
+ - Matching consists of providing a list of supported file extensions for the sub-plugins and
+   a mechanism to resolve file extension uncertainty or ambiguity. The matching makes the plugin
+   visible to the user within the Plugin Manager.
+ - Loading consists of loading the sub-plugin's file, passing the file to some form of interpreter
+   and calling GEANY_PLUGIN_REGISTER() or GEANY_PLUGIN_REGISTER_FULL() on behalf of the sub-plugin
+   at some point.
+ - Unloading simply reverses the effect of loading.
+
+For providing these methods, GeanyPlugin has a field GeanyProxyFuncs which contains three function
+pointers which must be initialized proir to calling geany_plugin_register_proxy(). This should be
+done in the GeanyPluginFuncs::init function of the proxy plugin.
+
+ - In the call to geany_plugin_register_proxy() the proxy plugin passes a list of file extensions.
+   When Geany scans through its plugin directories as usual it will also look for files with
+   that extensions and consider found files as plugin candidate.
+ - GeanyProxyFuncs::probe may be implemented to probe if a plugin candidate (that has one of the
+   provided file extensions) is actually a plugin. This may depend on the plugin file itself in
+   case of ambiguity or availability of runtime dependencies or even configuration.
+   @ref PROXY_IGNORED or @ref PROXY_MATCHED should be returned, possibly in combination
+   with the @ref PROXY_NOLOAD flag. Not implementing GeanyProxyFuncs::probe at all is eqivalent to
+   always returning @ref PROXY_MATCHED.
+ - GeanyProxyFuncs::load must be implemented to actually load the plugin. It is called by Geany
+   when the user enables the sub-plugin. What "loading" means is entirely up to the proxy plugin and
+   probably depends on the interpreter of the dynamic language that shall be supported. After
+   setting everything up as necessary GEANY_PLUGIN_REGISTER() or GEANY_PLUGIN_REGISTER_FULL() must
+   be called to register the sub-plugin.
+ - GeanyProxyFuncs::unload must be implemented and is called when the user unchecks the sub-plugin
+   or when Geany exits. Here, the proxy should release any references or memory associated to the
+   sub-plugin. Note that if GeanyProxyFuncs::load didn't succeed, i.e. didn't successfully register
+   the sub-plugin, then this function won't be called.
+
+GeanyProxyFuncs::load and GeanyProxyFuncs::unload receive two GeanyPlugin pointers: One that
+corresponds to the proxy itself and another that corresponds to the sub-plugin. The sub-plugin's
+one may be used to call various API functions on behalf of the sub-plugin, including
+GEANY_PLUGIN_REGISTER() and GEANY_PLUGIN_REGISTER_FULL().
+
+GeanyProxyFuncs::load may return a pointer that is passed back to GeanyProxyFuncs::unload. This can
+be used to store proxy-defined but sub-plugin-specific data required for unloading. However, this
+pointer is not passed to the sub-plugin's GeanyPluginFuncs. To arrange for that, you want to call
+GEANY_PLUGIN_REGISTER_FULL(). This method is the key to enable proxy plugins to wrap the
+GeanyPluginFuncs of all sub-plugins and yet multiplex between multiple sub-plugin, for example by
+storing a per-sub-plugin interpreter context.
+
+ at note If the pointer returned from GeanyProxyFuncs::load is the same that is passed to
+GEANY_PLUGIN_REGISTER_FULL() then you must pass NULL as free_func, because that would be invoked
+prior to unloading. Insert the corresponding code into GeanyProxyFuncs::unload.
+
+ at section proxy_compat_guideline Guideline for Checking Compatiblity
+
+Determining if a plugin candidate is compatible is not a single test. There are multiple levels and
+each should be handled differently in order to give the user a consistent feedback.
+
+Consider the 5 basic cases:
+
+1) A candidate comes with a suitable file extension but is not a workable plugin file at all. For
+example, your proxy supports plugins written in a shell script (.sh) but the shebang of that script
+points to an incompatible shell (or even lacks a shebang). You should check for this in
+GeanyProxyFuncs::probe() and return @ref PROXY_IGNORED which hides that script from the Plugin
+Manager and allows other enabled proxy plugins to pick it up. GeanyProxyFuncs::probe() returning
+ at ref PROXY_IGNORED is an indication that the candidate is meant for another proxy, or the user
+placed the file by accident in one of Geany's plugin directories. In other words the candidate
+simply doesn't correspond to your proxy. Thus any noise by debug messages for this case is
+undesirable.
+
+2) A proxy plugin provides its own, versioned API to sub-plugin. The API version of the sub-plugin
+is not compatible with the API exposed by the proxy. GeanyProxyFuncs::probe() should never perform
+a version check because its sole purpose is to indicate a proxy's correspondence to a given
+candidate. It should return @ref PROXY_MATCHED instead. Later, Geany will invoke the
+GeanyProxyFuncs::load(), and this function is the right place for a version check. If it fails then
+you simply do not call GEANY_PLUGIN_REGISTER(), but rather print a debug message. The result is
+that the sub-plugin is not shown in the Plugin Manager at all. This is consistent with the
+treatment of native plugins by Geany.
+
+3) The sub-plugin is also depending on Geany's API version (whether it is or not depends on the
+design of the proxy). In this case do not do anything special but forward the API version the
+sub-plugin is written/compiled against to GEANY_PLUGIN_REGISTER(). Here, Geany will perform its own
+compatiblity check, allowing for a consistent user feedback. The result is again that the
+sub-plugin is hidden from the Plugin Manager, like in case 2. But Geany will print a debug message
+so you can skip that.
+
+
+If you have even more cases try to fit it into case 1 or 2, depending on whether other proxy
+plugins should get a chance to load the candidate or not.
+
+ at section proxy_dep_guideline Guideline for Runtime Errors
+
+A sub-plugin might not be able to run even if it's perfectly compatible with its proxy. This
+includes the case when it lacks certain runtime dependencies such as programs or modules but also
+syntactic problems or other errors.
+
+There are two basic classes:
+
+1) Runtime errors that can be determined at load time. For example, the shebang of a script
+indicates a specific interpeter version but that version is not installed on the system. Your proxy
+should respond the same way as for version-incompatible plugins: don't register the plugin at
+all, but leave a message the user suggesting what has to be installed in order to work. Handle
+syntax errors in the scripts of sub-plugins the same way if possible.
+
+2) Runtime errors that cannot be determined without actually running the plugin. An example would
+be missing modules in Python scripts. If your proxy has no way of foreseeing the problem the plugin
+will be registered normally. However, you can catch runtime errors by implementing
+GeanyPluginFuncs::init() on the plugin's behalf. This is called after user activation and allows to
+indicate errors by returning @c FALSE. However, allowing the user to enable a plugin and then
+disabling anyway is a poor user experience.
+
+Therefore, if possible, try to fail fast and disallow registration.
+
+ at section Proxy Plugin Example
+
+In this section a dumb example proxy plugin is shown in order to give a practical starting point.
+The sub-plugin are not actually code but rather a ini-style description of one or more menu items
+that are added to Geany's tools menu and a help dialog. Real world sub-plugins would contain actual
+code, usually written in a scripting language.
+
+A sub-plugin file looks like this:
+
+ at code{.ini}
+#!!PROXY_MAGIC!!
+
+[Init]
+item0 = Bam
+item1 = Foo
+item2 = Bar
+
+[Help]
+text = I'm a simple test. Nothing to see!
+
+[Info]
+name = Demo Proxy Tester
+description = I'm a simple test. Nothing to see!
+version = 0.1
+author = The Geany developer team
+ at endcode
+
+The first line acts as a verification that this file is truly a sub-plugin. Within the [Init] section
+there is the menu items for Geany's tools menu. The [Help] section declares the sub-plugins help
+text which is shown in its help dialog (via GeanyPluginFuncs::help). The [Info] section is
+used as-is for filling the sub-plugins PluginInfo fields.
+
+That's it, this dumb format is purely declarative and contains no logic. Yet we will create plugins
+from it.
+
+We start by registering the proxy plugin to Geany. There is nothing special to it compared to
+normal plugins. A proxy plugin must also fill its own @ref PluginInfo and @ref GeanyPluginFuncs,
+followed by registering through GEANY_PLUGIN_REGISTER().
+
+
+ at code{.c}
+
+/* Called by Geany to initialize the plugin. */
+static gboolean demoproxy_init(GeanyPlugin *plugin, gpointer pdata)
+{
+	// ...
+}
+
+
+/* Called by Geany before unloading the plugin. */
+static void demoproxy_cleanup(GeanyPlugin *plugin, gpointer data)
+{
+	// ...
+}
+
+
+G_MODULE_EXPORT
+void geany_load_module(GeanyPlugin *plugin)
+{
+	plugin->info->name = _("Demo Proxy");
+	plugin->info->description = _("Example Proxy.");
+	plugin->info->version = "0.1";
+	plugin->info->author = _("The Geany developer team");
+
+	plugin->funcs->init = demoproxy_init;
+	plugin->funcs->cleanup = demoproxy_cleanup;
+
+	GEANY_PLUGIN_REGISTER(plugin, 225);
+}
+
+ at endcode
+
+The next step is to actually register as a proxy plugin. This is done in demoproxy_init().
+As previously mentioned, it needs a list of accepted file extensions and a set of callback
+functions.
+
+ at code{.c}
+static gboolean demoproxy_init(GeanyPlugin *plugin, gpointer pdata)
+{
+	const gchar *extensions[] = { "ini", "px", NULL };
+
+	plugin->proxy_funcs->probe  = demoproxy_probe;
+	plugin->proxy_funcs->load   = demoproxy_load;
+	plugin->proxy_funcs->unload = demoproxy_unload;
+
+	return geany_plugin_register_proxy(plugin, extensions);
+}
+
+ at endcode
+
+The callback functions deserve a closer look.
+
+As already mentioned the file format includes a magic first line which must be present.
+GeanyProxyFuncs::probe() verifies that it's present and avoids showing the sub-plugin in the
+Plugin Manager if not.
+
+ at code{.c}
+static gint demoproxy_probe(GeanyPlugin *proxy, const gchar *filename, gpointer pdata)
+{
+	/* We know the extension is right (Geany checks that). For demo purposes we perform an
+	 * additional check. This is not necessary when the extension is unique enough. */
+	gboolean match = FALSE;
+	gchar linebuf[128];
+	FILE *f = fopen(filename, "r");
+	if (f != NULL)
+	{
+		if (fgets(linebuf, sizeof(linebuf), f) != NULL)
+			match = utils_str_equal(linebuf, "#!!PROXY_MAGIC!!\n");
+		fclose(f);
+	}
+	return match ? PROXY_MATCHED : PROXY_IGNORED;
+}
+ at endcode
+
+GeanyProxyFuncs::load is a bit more complex. It reads the file, fills the sub-plugin's PluginInfo
+fields and calls GEANY_PLUGIN_REGISTER_FULL(). Additionally, it creates a per-plugin context that
+holds GKeyFile instance (a poor man's interpeter context). You can also see that it does not call
+GEANY_PLUGIN_REGISTER_FULL() if g_key_file_load_from_file() found an error (probably a syntax
+problem) which means the sub-plugin cannot be enabled.
+
+It also installs wrapper functions for the sub-plugin's GeanyPluginFuncs as ini files aren't code.
+It's very likely that your proxy needs something similar because you can only install function
+pointers to native code.
+
+ at code{.c}
+typedef struct {
+	GKeyFile       *file;
+	gchar          *help_text;
+	GSList         *menu_items;
+}
+PluginContext;
+
+
+static gboolean proxy_init(GeanyPlugin *plugin, gpointer pdata);
+static void proxy_help(GeanyPlugin *plugin, gpointer pdata);
+static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata);
+
+
+static gpointer demoproxy_load(GeanyPlugin *proxy, GeanyPlugin *plugin,
+                               const gchar *filename, gpointer pdata)
+{
+	GKeyFile *file;
+	gboolean result;
+
+	file = g_key_file_new();
+	result = g_key_file_load_from_file(file, filename, 0, NULL);
+
+	if (result)
+	{
+		PluginContext *data = g_new0(PluginContext, 1);
+		data->file = file;
+
+		plugin->info->name = g_key_file_get_locale_string(data->file, "Info", "name", NULL, NULL);
+		plugin->info->description = g_key_file_get_locale_string(data->file, "Info", "description", NULL, NULL);
+		plugin->info->version = g_key_file_get_locale_string(data->file, "Info", "version", NULL, NULL);
+		plugin->info->author = g_key_file_get_locale_string(data->file, "Info", "author", NULL, NULL);
+
+		plugin->funcs->init = proxy_init;
+		plugin->funcs->help = proxy_help;
+		plugin->funcs->cleanup = proxy_cleanup;
+
+		/* Cannot pass g_free as free_func be Geany calls it before unloading, and since 
+		 * demoproxy_unload() accesses the data this would be catastrophic */
+		GEANY_PLUGIN_REGISTER_FULL(plugin, 225, data, NULL);
+		return data;
+	}
+
+	g_key_file_free(file);
+	return NULL;
+}
+ at endcode
+
+demoproxy_unload() simply releases all resources aquired in demoproxy_load(). It does not have to
+do anything else in for unloading.
+
+ at code{.c}
+static void demoproxy_unload(GeanyPlugin *proxy, GeanyPlugin *plugin, gpointer load_data, gpointer pdata)
+{
+	PluginContext *data = load_data;
+
+	g_free((gchar *)plugin->info->name);
+	g_free((gchar *)plugin->info->description);
+	g_free((gchar *)plugin->info->version);
+	g_free((gchar *)plugin->info->author);
+
+	g_key_file_free(data->file);
+	g_free(data);
+}
+ at endcode
+
+Finally the demo_proxy's wrapper GeanyPluginFuncs. They are called for each possible sub-plugin and
+therefore have to multiplex between each using the plugin-defined data pointer. Each is called by
+Geany as if it were an ordinary, native plugin.
+
+proxy_init() actually reads the sub-plugin's file using GKeyFile APIs. It prepares for the help
+dialog and installs the menu items. proxy_help() is called when the user clicks the help button in
+the Plugin Manager. Consequently, this fires up a suitable dialog, although with a dummy message.
+proxy_cleanup() frees all memory allocated in proxy_init().
+
+ at code{.c}
+static gboolean proxy_init(GeanyPlugin *plugin, gpointer pdata)
+{
+	PluginContext *data;
+	gint i = 0;
+	gchar *text;
+
+	data = (PluginContext *) pdata;
+
+	/* Normally, you would instruct the VM/interpreter to call into the actual plugin. The
+	 * plugin would be identified by pdata. Because there is no interpreter for
+	 * .ini files we do it inline, as this is just a demo */
+	data->help_text = g_key_file_get_locale_string(data->file, "Help", "text", NULL, NULL);
+	while (TRUE)
+	{
+		GtkWidget *item;
+		gchar *key = g_strdup_printf("item%d", i++);
+		text = g_key_file_get_locale_string(data->file, "Init", key, NULL, NULL);
+		g_free(key);
+
+		if (!text)
+			break;
+
+		item = gtk_menu_item_new_with_label(text);
+		gtk_widget_show(item);
+		gtk_container_add(GTK_CONTAINER(plugin->geany_data->main_widgets->tools_menu), item);
+		gtk_widget_set_sensitive(item, FALSE);
+		data->menu_items = g_slist_prepend(data->menu_items, (gpointer) item);
+		g_free(text);
+	}
+
+	return TRUE;
+}
+
+
+static void proxy_help(GeanyPlugin *plugin, gpointer pdata)
+{
+	PluginContext *data;
+	GtkWidget *dialog;
+
+	data = (PluginContext *) pdata;
+
+	dialog = gtk_message_dialog_new(
+		GTK_WINDOW(plugin->geany_data->main_widgets->window),
+		GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_MESSAGE_INFO,
+		GTK_BUTTONS_OK,
+		"%s", data->help_text);
+	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+		_("(From the %s plugin)"), plugin->info->name);
+
+	gtk_dialog_run(GTK_DIALOG(dialog));
+	gtk_widget_destroy(dialog);
+}
+
+
+static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata)
+{
+	PluginContext *data = (PluginContext *) pdata;
+
+	g_slist_free_full(data->menu_items, (GDestroyNotify) gtk_widget_destroy);
+	g_free(data->help_text);
+}
+ at endcode
+
+
 */


Modified: src/plugindata.h
43 lines changed, 38 insertions(+), 5 deletions(-)
===================================================================
@@ -240,6 +240,7 @@ GeanyData;
 #define geany			geany_data	/**< Simple macro for @c geany_data that reduces typing. */
 
 typedef struct GeanyPluginFuncs GeanyPluginFuncs;
+typedef struct GeanyProxyFuncs GeanyProxyFuncs;
 
 /** Basic information for the plugin and identification.
  * @see geany_plugin. */
@@ -248,7 +249,8 @@ typedef struct GeanyPlugin
 	PluginInfo	*info;	/**< Fields set in plugin_set_info(). */
 	GeanyData	*geany_data;	/**< Pointer to global GeanyData intance */
 	GeanyPluginFuncs *funcs;	/**< Functions implemented by the plugin, set in geany_load_module() */
-
+	GeanyProxyFuncs	*proxy_funcs; /**< Hooks implemented by the plugin if it wants to act as a proxy
+									   Must be set prior to calling geany_plugin_register_proxy() */
 	struct GeanyPluginPrivate *priv;	/* private */
 }
 GeanyPlugin;
@@ -347,24 +349,55 @@ void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify fr
 	geany_plugin_register_full((plugin), GEANY_API_VERSION, \
 	                           (min_api_version), GEANY_ABI_VERSION, (pdata), (free_func))
 
+/** Return values for GeanyProxyHooks::probe()
+ *
+ * Only @c PROXY_IGNORED, @c PROXY_MATCHED or @c PROXY_MATCHED|PROXY_NOLOAD
+ * are valid return values.
+ *
+ * @see geany_plugin_register_proxy() for a full description of the proxy plugin mechanisms.
+ *
+ * @since 1.26 (API 226)
+ */
 typedef enum
 {
+	/** The proxy is not responsible at all, and Geany or other plugins are free
+	 * to probe it.
+	 **/
 	PROXY_IGNORED,
+	/** The proxy is responsible for this file, and creates a plugin for it */
 	PROXY_MATCHED,
 
+	/** The proxy is does not directly load it, but it's still tied to the proxy
+	 *
+	 * This is for plugins that come in multiple files where only one of these
+	 * files is relevant for the plugin creation (for the PM dialog). The other
+	 * files should be ignored by Geany and other proxies. Example: libpeas has
+	 * a .plugin and a .so per plugin. Geany should not process the .so file
+	 * if there is a corresponding .plugin.
+	 */
 	PROXY_NOLOAD = 0x100,
 }
 GeanyProxyProbeResults;
 
-/* Hooks that need to be implemented for every proxy */
-typedef struct _GeanyProxyFuncs
+
+/** Hooks that need to be implemented by every proxy
+ *
+ * @see geany_plugin_register_proxy() for a full description of the proxy mechanism.
+ *
+ * @since 1.26 (API 226)
+ **/
+struct GeanyProxyFuncs
 {
+	/** Called to determine whether the proxy is truly responsible for the requested plugin.
+	 * A NULL pointer assumes the probe() function would always return @ref PROXY_MATCHED */
 	gint		(*probe)     (GeanyPlugin *proxy, const gchar *filename, gpointer pdata);
+	/** Called after probe(), to perform the actual job of loading the plugin */
 	gpointer	(*load)      (GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, gpointer pdata);
+	/** Called when the user initiates unloading of a plugin, e.g. on Geany exit */
 	void		(*unload)    (GeanyPlugin *proxy, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata);
-}
-GeanyProxyFuncs;
+};
 
+gint geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensions);
 
 /* Deprecated aliases */
 #ifndef GEANY_DISABLE_DEPRECATED


Modified: src/plugins.c
52 lines changed, 52 insertions(+), 0 deletions(-)
===================================================================
@@ -653,6 +653,7 @@ plugin_new(Plugin *proxy, const gchar *fname, gboolean load_plugin, gboolean add
 	/* Fields of plugin->info/funcs must to be initialized by the plugin */
 	plugin->public.info = &plugin->info;
 	plugin->public.funcs = &plugin->cbs;
+	plugin->public.proxy_funcs = &plugin->proxy_cbs;
 
 	if (plugin_loaded(plugin))
 	{
@@ -1758,4 +1759,55 @@ static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data)
 }
 
 
+/** Register the plugin as a proxy for other plugins
+ *
+ * Proxy plugins register a list of file extensions and a set of callbacks that are called
+ * appropriately. A plugin can be a proxy for multiple types of sub-plugins by handling
+ * separate file extensions, however they must share the same set of hooks, because this
+ * function can only be called at most once per plugin.
+ *
+ * Each callback receives the plugin-defined data as parameter (see geany_plugin_register()). The
+ * callbacks must be set prior to calling this, by assigning to @a plugin->proxy_funcs.
+ * GeanyProxyFuncs::load and GeanyProxyFuncs::unload must be implemented.
+ *
+ * Nested proxies are unsupported at this point (TODO).
+ *
+ * @note It is entirely up to the proxy to provide access to Geany's plugin API. Native code
+ * can naturally call Geany's API directly, for interpreted languages the proxy has to
+ * implement some kind of bindings that the plugin can use.
+ *
+ * @see proxy for detailed documentation and an example.
+ *
+ * @param plugin The pointer to the plugin's GeanyPlugin instance
+ * @param extensions A @c NULL-terminated string array of file extensions, excluding the dot.
+ * @return @c TRUE if the proxy was successfully registered, otherwise @c FALSE
+ *
+ * @since 1.26 (API 226)
+ */
+GEANY_API_SYMBOL
+gboolean geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensions)
+{
+	Plugin *p;
+	const gchar **ext;
+
+	g_return_val_if_fail(plugin != NULL, FALSE);
+	g_return_val_if_fail(extensions != NULL, FALSE);
+	g_return_val_if_fail(*extensions != NULL, FALSE);
+	g_return_val_if_fail(plugin->proxy_funcs->load != NULL, FALSE);
+	g_return_val_if_fail(plugin->proxy_funcs->unload != NULL, FALSE);
+
+	p = plugin->priv;
+
+	foreach_strv(ext, extensions)
+	{
+		PluginProxy *proxy = g_new(PluginProxy, 1);
+		g_strlcpy(proxy->extension, *ext, sizeof(proxy->extension));
+		proxy->plugin = p;
+		/* prepend, so that plugins automatically override core providers for a given extension */
+		g_ptr_array_insert(active_proxies, 0, proxy);
+	}
+
+	return TRUE;
+}
+
 #endif



--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).


More information about the Commits mailing list