gem-graph-client/libide/plugins/ide-extension-set-adapter.c

815 lines
24 KiB
C

/* ide-extension-set-adapter.c
*
* Copyright 2015-2019 Christian Hergert <christian@hergert.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ide-extension-set-adapter"
#include "config.h"
#include <dazzle.h>
#include <glib/gi18n.h>
#include <stdlib.h>
#include "ide-extension-set-adapter.h"
#include "ide-extension-util-private.h"
struct _IdeExtensionSetAdapter
{
IdeObject parent_instance;
PeasEngine *engine;
gchar *key;
gchar *value;
GHashTable *extensions;
GPtrArray *settings;
GType interface_type;
guint reload_handler;
};
G_DEFINE_FINAL_TYPE (IdeExtensionSetAdapter, ide_extension_set_adapter, IDE_TYPE_OBJECT)
enum {
EXTENSIONS_LOADED,
EXTENSION_ADDED,
EXTENSION_REMOVED,
LAST_SIGNAL
};
enum {
PROP_0,
PROP_ENGINE,
PROP_INTERFACE_TYPE,
PROP_KEY,
PROP_VALUE,
LAST_PROP
};
static GParamSpec *properties [LAST_PROP];
static guint signals [LAST_SIGNAL];
static void ide_extension_set_adapter_queue_reload (IdeExtensionSetAdapter *);
static gchar *
ide_extension_set_adapter_repr (IdeObject *object)
{
IdeExtensionSetAdapter *self = (IdeExtensionSetAdapter *)object;
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
return g_strdup_printf ("%s interface=\"%s\" key=\"%s\" value=\"%s\"",
G_OBJECT_TYPE_NAME (self),
g_type_name (self->interface_type),
self->key ?: "",
self->value ?: "");
}
static void
add_extension (IdeExtensionSetAdapter *self,
PeasPluginInfo *plugin_info,
PeasExtension *exten)
{
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_assert (plugin_info != NULL);
g_assert (exten != NULL);
g_assert (g_type_is_a (G_OBJECT_TYPE (exten), self->interface_type));
g_hash_table_insert (self->extensions, plugin_info, exten);
/* Ensure that we take the reference in case it's a floating ref */
if (G_IS_INITIALLY_UNOWNED (exten) && g_object_is_floating (exten))
g_object_ref_sink (exten);
/*
* If the plugin object turned out to have IdeObject as a
* base, make it a child of ourselves, because we're an
* IdeObject too and that gives it access to the context.
*/
if (IDE_IS_OBJECT (exten))
ide_object_append (IDE_OBJECT (self), IDE_OBJECT (exten));
g_signal_emit (self, signals [EXTENSION_ADDED], 0, plugin_info, exten);
}
static void
remove_extension (IdeExtensionSetAdapter *self,
PeasPluginInfo *plugin_info,
PeasExtension *exten)
{
g_autoptr(GObject) hold = NULL;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_assert (plugin_info != NULL);
g_assert (exten != NULL);
g_assert (self->interface_type == G_TYPE_INVALID ||
g_type_is_a (G_OBJECT_TYPE (exten), self->interface_type));
hold = g_object_ref (exten);
g_hash_table_remove (self->extensions, plugin_info);
g_signal_emit (self, signals [EXTENSION_REMOVED], 0, plugin_info, hold);
if (IDE_IS_OBJECT (hold))
ide_object_destroy (IDE_OBJECT (hold));
}
static void
ide_extension_set_adapter_enabled_changed (IdeExtensionSetAdapter *self,
const gchar *key,
GSettings *settings)
{
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_assert (key != NULL);
g_assert (G_IS_SETTINGS (settings));
ide_extension_set_adapter_queue_reload (self);
}
static void
watch_extension (IdeExtensionSetAdapter *self,
PeasPluginInfo *plugin_info,
GType interface_type)
{
g_autoptr(GSettings) settings = NULL;
g_autofree char *path = NULL;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_assert (plugin_info != NULL);
g_assert (G_TYPE_IS_INTERFACE (interface_type) || G_TYPE_IS_OBJECT (interface_type));
path = g_strdup_printf ("/org/gnome/builder/extension-types/%s/%s/",
peas_plugin_info_get_module_name (plugin_info),
g_type_name (interface_type));
settings = g_settings_new_with_path ("org.gnome.builder.extension-type", path);
g_ptr_array_add (self->settings, g_object_ref (settings));
/* We have to fetch the key once to get changed events */
g_settings_get_boolean (settings, "enabled");
g_signal_connect_object (settings,
"changed::enabled",
G_CALLBACK (ide_extension_set_adapter_enabled_changed),
self,
G_CONNECT_SWAPPED);
}
static void
ide_extension_set_adapter_reload (IdeExtensionSetAdapter *self)
{
const GList *plugins;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_assert (self->interface_type != G_TYPE_INVALID);
while (self->settings->len > 0)
{
GSettings *settings;
settings = g_ptr_array_index (self->settings, self->settings->len - 1);
g_signal_handlers_disconnect_by_func (settings,
ide_extension_set_adapter_enabled_changed,
self);
g_ptr_array_remove_index (self->settings, self->settings->len - 1);
}
plugins = peas_engine_get_plugin_list (self->engine);
for (; plugins; plugins = plugins->next)
{
PeasPluginInfo *plugin_info = plugins->data;
gint priority;
if (!peas_plugin_info_is_loaded (plugin_info))
continue;
if (!peas_engine_provides_extension (self->engine, plugin_info, self->interface_type))
continue;
watch_extension (self, plugin_info, self->interface_type);
if (ide_extension_util_can_use_plugin (self->engine,
plugin_info,
self->interface_type,
self->key,
self->value,
&priority))
{
if (!g_hash_table_contains (self->extensions, plugin_info))
{
PeasExtension *exten;
exten = ide_extension_new (self->engine,
plugin_info,
self->interface_type,
NULL);
add_extension (self, plugin_info, exten);
}
}
else
{
PeasExtension *exten;
if ((exten = g_hash_table_lookup (self->extensions, plugin_info)))
remove_extension (self, plugin_info, exten);
}
}
g_signal_emit (self, signals [EXTENSIONS_LOADED], 0);
}
static gboolean
ide_extension_set_adapter_do_reload (gpointer data)
{
IdeExtensionSetAdapter *self = data;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
self->reload_handler = 0;
if (self->interface_type != G_TYPE_INVALID)
ide_extension_set_adapter_reload (self);
return G_SOURCE_REMOVE;
}
static void
ide_extension_set_adapter_queue_reload (IdeExtensionSetAdapter *self)
{
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_clear_handle_id (&self->reload_handler, g_source_remove);
self->reload_handler = g_idle_add_full (G_PRIORITY_HIGH,
ide_extension_set_adapter_do_reload,
self,
NULL);
}
static void
ide_extension_set_adapter_load_plugin (IdeExtensionSetAdapter *self,
PeasPluginInfo *plugin_info,
PeasEngine *engine)
{
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_assert (plugin_info != NULL);
g_assert (PEAS_IS_ENGINE (engine));
ide_extension_set_adapter_queue_reload (self);
}
static void
ide_extension_set_adapter_unload_plugin (IdeExtensionSetAdapter *self,
PeasPluginInfo *plugin_info,
PeasEngine *engine)
{
PeasExtension *exten;
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_assert (plugin_info != NULL);
g_assert (PEAS_IS_ENGINE (engine));
if ((exten = g_hash_table_lookup (self->extensions, plugin_info)))
{
remove_extension (self, plugin_info, exten);
g_hash_table_remove (self->extensions, plugin_info);
}
}
static void
ide_extension_set_adapter_set_engine (IdeExtensionSetAdapter *self,
PeasEngine *engine)
{
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_assert (!engine || PEAS_IS_ENGINE (engine));
if (engine == NULL)
engine = peas_engine_get_default ();
if (g_set_object (&self->engine, engine))
{
g_signal_connect_object (self->engine, "load-plugin",
G_CALLBACK (ide_extension_set_adapter_load_plugin),
self,
G_CONNECT_AFTER | G_CONNECT_SWAPPED);
g_signal_connect_object (self->engine, "unload-plugin",
G_CALLBACK (ide_extension_set_adapter_unload_plugin),
self,
G_CONNECT_SWAPPED);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ENGINE]);
ide_extension_set_adapter_queue_reload (self);
}
}
static void
ide_extension_set_adapter_set_interface_type (IdeExtensionSetAdapter *self,
GType interface_type)
{
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_assert (G_TYPE_IS_INTERFACE (interface_type) || G_TYPE_IS_OBJECT (interface_type));
if (interface_type != self->interface_type)
{
self->interface_type = interface_type;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_INTERFACE_TYPE]);
ide_extension_set_adapter_queue_reload (self);
}
}
static void
ide_extension_set_adapter_destroy (IdeObject *object)
{
IdeExtensionSetAdapter *self = (IdeExtensionSetAdapter *)object;
g_autoptr(GHashTable) extensions = NULL;
GHashTableIter iter;
gpointer key;
gpointer value;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
self->interface_type = G_TYPE_INVALID;
g_clear_handle_id (&self->reload_handler, g_source_remove);
/*
* Steal the extensions so we can be re-entrant safe and not break
* any assumptions about extensions being a real pointer.
*/
extensions = g_steal_pointer (&self->extensions);
self->extensions = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
g_hash_table_iter_init (&iter, extensions);
while (g_hash_table_iter_next (&iter, &key, &value))
{
PeasPluginInfo *plugin_info = key;
PeasExtension *exten = value;
remove_extension (self, plugin_info, exten);
g_hash_table_iter_remove (&iter);
}
IDE_OBJECT_CLASS (ide_extension_set_adapter_parent_class)->destroy (object);
}
static void
ide_extension_set_adapter_finalize (GObject *object)
{
IdeExtensionSetAdapter *self = (IdeExtensionSetAdapter *)object;
while (self->settings->len > 0)
{
guint i = self->settings->len - 1;
GSettings *settings = g_ptr_array_index (self->settings, i);
g_signal_handlers_disconnect_by_func (settings,
ide_extension_set_adapter_enabled_changed,
self);
g_ptr_array_remove_index (self->settings, i);
}
g_clear_object (&self->engine);
g_clear_pointer (&self->key, g_free);
g_clear_pointer (&self->value, g_free);
g_clear_pointer (&self->extensions, g_hash_table_unref);
g_clear_pointer (&self->settings, g_ptr_array_unref);
G_OBJECT_CLASS (ide_extension_set_adapter_parent_class)->finalize (object);
}
static void
ide_extension_set_adapter_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeExtensionSetAdapter *self = IDE_EXTENSION_SET_ADAPTER (object);
switch (prop_id)
{
case PROP_ENGINE:
g_value_set_object (value, ide_extension_set_adapter_get_engine (self));
break;
case PROP_INTERFACE_TYPE:
g_value_set_gtype (value, ide_extension_set_adapter_get_interface_type (self));
break;
case PROP_KEY:
g_value_set_string (value, ide_extension_set_adapter_get_key (self));
break;
case PROP_VALUE:
g_value_set_string (value, ide_extension_set_adapter_get_value (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_extension_set_adapter_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
IdeExtensionSetAdapter *self = IDE_EXTENSION_SET_ADAPTER (object);
switch (prop_id)
{
case PROP_ENGINE:
ide_extension_set_adapter_set_engine (self, g_value_get_object (value));
break;
case PROP_INTERFACE_TYPE:
ide_extension_set_adapter_set_interface_type (self, g_value_get_gtype (value));
break;
case PROP_KEY:
ide_extension_set_adapter_set_key (self, g_value_get_string (value));
break;
case PROP_VALUE:
ide_extension_set_adapter_set_value (self, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_extension_set_adapter_class_init (IdeExtensionSetAdapterClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
object_class->finalize = ide_extension_set_adapter_finalize;
object_class->get_property = ide_extension_set_adapter_get_property;
object_class->set_property = ide_extension_set_adapter_set_property;
i_object_class->destroy = ide_extension_set_adapter_destroy;
i_object_class->repr = ide_extension_set_adapter_repr;
properties [PROP_ENGINE] =
g_param_spec_object ("engine",
"Engine",
"Engine",
PEAS_TYPE_ENGINE,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
properties [PROP_INTERFACE_TYPE] =
g_param_spec_gtype ("interface-type",
"Interface Type",
"Interface Type",
G_TYPE_OBJECT,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
properties [PROP_KEY] =
g_param_spec_string ("key",
"Key",
"Key",
NULL,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_VALUE] =
g_param_spec_string ("value",
"Value",
"Value",
NULL,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, LAST_PROP, properties);
signals [EXTENSION_ADDED] =
g_signal_new ("extension-added",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE,
2,
PEAS_TYPE_PLUGIN_INFO,
PEAS_TYPE_EXTENSION);
signals [EXTENSION_REMOVED] =
g_signal_new ("extension-removed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE,
2,
PEAS_TYPE_PLUGIN_INFO,
PEAS_TYPE_EXTENSION);
signals [EXTENSIONS_LOADED] =
g_signal_new ("extensions-loaded",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static void
ide_extension_set_adapter_init (IdeExtensionSetAdapter *self)
{
self->settings = g_ptr_array_new_with_free_func (g_object_unref);
self->extensions = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
}
/**
* ide_extension_set_adapter_get_engine:
*
* Gets the #IdeExtensionSetAdapter:engine property.
*
* Returns: (transfer none): a #PeasEngine.
*
* Since: 3.32
*/
PeasEngine *
ide_extension_set_adapter_get_engine (IdeExtensionSetAdapter *self)
{
g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);
return self->engine;
}
GType
ide_extension_set_adapter_get_interface_type (IdeExtensionSetAdapter *self)
{
g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), G_TYPE_INVALID);
return self->interface_type;
}
const gchar *
ide_extension_set_adapter_get_key (IdeExtensionSetAdapter *self)
{
g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);
return self->key;
}
void
ide_extension_set_adapter_set_key (IdeExtensionSetAdapter *self,
const gchar *key)
{
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));
if (!ide_str_equal0 (self->key, key))
{
g_free (self->key);
self->key = g_strdup (key);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_KEY]);
ide_extension_set_adapter_queue_reload (self);
}
}
const gchar *
ide_extension_set_adapter_get_value (IdeExtensionSetAdapter *self)
{
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);
return self->value;
}
void
ide_extension_set_adapter_set_value (IdeExtensionSetAdapter *self,
const gchar *value)
{
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));
IDE_TRACE_MSG ("Setting extension adapter %s value to \"%s\"",
g_type_name (self->interface_type),
value ?: "");
if (!ide_str_equal0 (self->value, value))
{
g_free (self->value);
self->value = g_strdup (value);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VALUE]);
ide_extension_set_adapter_queue_reload (self);
}
}
/**
* ide_extension_set_adapter_foreach:
* @self: an #IdeExtensionSetAdapter
* @foreach_func: (scope call): A callback
* @user_data: user data for @foreach_func
*
* Calls @foreach_func for every extension loaded by the extension set.
*
* Since: 3.32
*/
void
ide_extension_set_adapter_foreach (IdeExtensionSetAdapter *self,
IdeExtensionSetAdapterForeachFunc foreach_func,
gpointer user_data)
{
const GList *list;
g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_return_if_fail (foreach_func != NULL);
/*
* Use the ordered list of plugins as it is sorted including any
* dependencies of plugins.
*/
list = peas_engine_get_plugin_list (self->engine);
for (const GList *iter = list; iter; iter = iter->next)
{
PeasPluginInfo *plugin_info = iter->data;
PeasExtension *exten = g_hash_table_lookup (self->extensions, plugin_info);
if (exten != NULL)
foreach_func (self, plugin_info, exten, user_data);
}
}
typedef struct
{
PeasPluginInfo *plugin_info;
PeasExtension *exten;
gint priority;
} SortedInfo;
static gint
sort_by_priority (gconstpointer a,
gconstpointer b)
{
const SortedInfo *sa = a;
const SortedInfo *sb = b;
/* Greater values are higher priority */
if (sa->priority < sb->priority)
return -1;
else if (sa->priority > sb->priority)
return 1;
else
return 0;
}
/**
* ide_extension_set_adapter_foreach_by_priority:
* @self: an #IdeExtensionSetAdapter
* @foreach_func: (scope call): A callback
* @user_data: user data for @foreach_func
*
* Calls @foreach_func for every extension loaded by the extension set.
*
* Since: 3.32
*/
void
ide_extension_set_adapter_foreach_by_priority (IdeExtensionSetAdapter *self,
IdeExtensionSetAdapterForeachFunc foreach_func,
gpointer user_data)
{
g_autoptr(GArray) sorted = NULL;
g_autofree gchar *prio_key = NULL;
GHashTableIter iter;
gpointer key;
gpointer value;
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));
g_return_if_fail (foreach_func != NULL);
if (self->key == NULL)
{
ide_extension_set_adapter_foreach (self, foreach_func, user_data);
return;
}
prio_key = g_strdup_printf ("%s-Priority", self->key);
sorted = g_array_new (FALSE, FALSE, sizeof (SortedInfo));
g_hash_table_iter_init (&iter, self->extensions);
while (g_hash_table_iter_next (&iter, &key, &value))
{
PeasPluginInfo *plugin_info = key;
PeasExtension *exten = value;
const gchar *priostr = peas_plugin_info_get_external_data (plugin_info, prio_key);
gint prio = priostr ? atoi (priostr) : 0;
SortedInfo info = { plugin_info, exten, prio };
g_array_append_val (sorted, info);
}
g_array_sort (sorted, sort_by_priority);
for (guint i = 0; i < sorted->len; i++)
{
const SortedInfo *info = &g_array_index (sorted, SortedInfo, i);
foreach_func (self, info->plugin_info, info->exten, user_data);
}
}
guint
ide_extension_set_adapter_get_n_extensions (IdeExtensionSetAdapter *self)
{
g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), 0);
if (self->extensions != NULL)
return g_hash_table_size (self->extensions);
return 0;
}
IdeExtensionSetAdapter *
ide_extension_set_adapter_new (IdeObject *parent,
PeasEngine *engine,
GType interface_type,
const gchar *key,
const gchar *value)
{
IdeExtensionSetAdapter *ret;
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
g_return_val_if_fail (!parent || IDE_IS_OBJECT (parent), NULL);
g_return_val_if_fail (!engine || PEAS_IS_ENGINE (engine), NULL);
g_return_val_if_fail (G_TYPE_IS_INTERFACE (interface_type) ||
G_TYPE_IS_OBJECT (interface_type), NULL);
ret = g_object_new (IDE_TYPE_EXTENSION_SET_ADAPTER,
"engine", engine,
"interface-type", interface_type,
"key", key,
"value", value,
NULL);
if (parent != NULL)
ide_object_append (parent, IDE_OBJECT (ret));
/* If we have a reload queued, just process it immediately so that
* there is some determinism in plugin loading.
*/
if (ret->reload_handler != 0)
{
g_clear_handle_id (&ret->reload_handler, g_source_remove);
ide_extension_set_adapter_do_reload (ret);
}
return ret;
}
/**
* ide_extension_set_adapter_get_extension:
* @self: a #IdeExtensionSetAdapter
* @plugin_info: a #PeasPluginInfo
*
* Locates the extension owned by @plugin_info if such extension exists.
*
* Returns: (transfer none) (nullable): a #PeasExtension or %NULL
*
* Since: 3.32
*/
PeasExtension *
ide_extension_set_adapter_get_extension (IdeExtensionSetAdapter *self,
PeasPluginInfo *plugin_info)
{
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);
g_return_val_if_fail (plugin_info != NULL, NULL);
return g_hash_table_lookup (self->extensions, plugin_info);
}