As a diff is sometimes clearer than words, here's an additional one addressing some discussions above (it's on top of my previous patch, but I don't mean to suggest they should be applied as-is). It includes `_provided()` that can tell the caller whether it was the winner, `user_data` for the vfuncs, and a fairly thorough multi-extension support. <details><summary>Another not-actually-so-big diff</summary>
```diff diff --git a/src/document.c b/src/document.c index adf5b8cc6..9bcbc7996 100644 --- a/src/document.c +++ b/src/document.c @@ -2720,7 +2720,7 @@ void document_highlight_tags(GeanyDocument *doc) GString *keywords_str; gint keyword_idx;
- if (! plugin_extension_active(plugin_extension_geany)) + if (! plugin_extension_symbol_highlight_provided(doc, plugin_extension_geany)) return;
/* some filetypes support type keywords (such as struct names), but not diff --git a/src/editor.c b/src/editor.c index 7a1d2fdc1..0d0d5ff23 100644 --- a/src/editor.c +++ b/src/editor.c @@ -838,11 +838,8 @@ static void on_char_added(GeanyEditor *editor, SCNotification *nt) } }
- if (plugin_extension_calltips_provided(editor->document)) - plugin_extension_calltips_show(editor->document, FALSE); - - if (plugin_extension_autocomplete_provided(editor->document)) - plugin_extension_autocomplete_perform(editor->document, FALSE); + plugin_extension_calltips_show(editor->document, FALSE); + plugin_extension_autocomplete_perform(editor->document, FALSE);
check_line_breaking(editor, pos); } @@ -1137,7 +1134,7 @@ static gboolean on_editor_notify(G_GNUC_UNUSED GObject *object, GeanyEditor *edi /* now that autocomplete is finishing or was cancelled, reshow calltips * if they were showing */ autocomplete_scope_shown = FALSE; - if (plugin_extension_active(plugin_extension_geany)) + if (plugin_extension_calltips_provided(doc, plugin_extension_geany)) request_reshowing_calltip(nt); break; case SCN_NEEDSHOWN: @@ -1152,7 +1149,7 @@ static gboolean on_editor_notify(G_GNUC_UNUSED GObject *object, GeanyEditor *edi break;
case SCN_CALLTIPCLICK: - if (plugin_extension_active(plugin_extension_geany) && nt->position > 0) + if (plugin_extension_calltips_provided(doc, plugin_extension_geany) && nt->position > 0) { switch (nt->position) { @@ -5295,7 +5292,7 @@ void editor_indent(GeanyEditor *editor, gboolean increase) }
-void editor_extension_autocomplete_perform(GeanyDocument *doc, gboolean force) +void editor_extension_autocomplete_perform(GeanyDocument *doc, gboolean force, gpointer data G_GNUC_UNUSED) { gint pos = sci_get_current_position(doc->editor->sci);
@@ -5335,7 +5332,7 @@ void editor_extension_autocomplete_perform(GeanyDocument *doc, gboolean force) }
-void editor_extension_calltips_show(GeanyDocument *doc, gboolean force) +void editor_extension_calltips_show(GeanyDocument *doc, gboolean force, gpointer data G_GNUC_UNUSED) { gint pos = sci_get_current_position(doc->editor->sci);
diff --git a/src/editor.h b/src/editor.h index 95c36a3e3..cee71df5e 100644 --- a/src/editor.h +++ b/src/editor.h @@ -279,8 +279,8 @@ void editor_snippets_free(void);
const GeanyEditorPrefs *editor_get_prefs(GeanyEditor *editor);
-void editor_extension_autocomplete_perform(struct GeanyDocument *doc, gboolean force); -void editor_extension_calltips_show(struct GeanyDocument *doc, gboolean force); +void editor_extension_autocomplete_perform(struct GeanyDocument *doc, gboolean force, gpointer data); +void editor_extension_calltips_show(struct GeanyDocument *doc, gboolean force, gpointer data);
/* General editing functions */ diff --git a/src/keybindings.c b/src/keybindings.c index 9eba0ef30..b5e08172f 100644 --- a/src/keybindings.c +++ b/src/keybindings.c @@ -2152,12 +2152,10 @@ static gboolean cb_func_editor_action(guint key_id) sci_send_command(doc->editor->sci, SCI_LINETRANSPOSE); break; case GEANY_KEYS_EDITOR_AUTOCOMPLETE: - if (plugin_extension_autocomplete_provided(doc)) - plugin_extension_autocomplete_perform(doc, TRUE); + plugin_extension_autocomplete_perform(doc, TRUE); break; case GEANY_KEYS_EDITOR_CALLTIP: - if (plugin_extension_calltips_provided(doc)) - plugin_extension_calltips_show(doc, TRUE); + plugin_extension_calltips_show(doc, TRUE); break; case GEANY_KEYS_EDITOR_CONTEXTACTION: if (check_current_word(doc, FALSE)) diff --git a/src/libmain.c b/src/libmain.c index d849bfa45..a54831d5a 100644 --- a/src/libmain.c +++ b/src/libmain.c @@ -46,6 +46,7 @@ #include "navqueue.h" #include "notebook.h" #include "plugins.h" +#include "pluginextension.h" #include "projectprivate.h" #include "prefs.h" #include "printing.h" @@ -1033,6 +1034,8 @@ void main_init_headless(void) memset(&template_prefs, 0, sizeof(GeanyTemplatePrefs)); memset(&ui_prefs, 0, sizeof(UIPrefs)); memset(&ui_widgets, 0, sizeof(UIWidgets)); + + plugin_extension_register(plugin_extension_geany, 0, NULL); }
diff --git a/src/pluginextension.c b/src/pluginextension.c index 0460a7c5a..5d0c8d546 100644 --- a/src/pluginextension.c +++ b/src/pluginextension.c @@ -24,12 +24,12 @@ #include "symbols.h"
-static gboolean func_return_true(GeanyDocument *doc) +static gboolean func_return_true(GeanyDocument *doc, gpointer data) { return TRUE; }
-static GPtrArray *func_return_ptrarr(GeanyDocument *doc) +static GPtrArray *func_return_ptrarr(GeanyDocument *doc, gpointer data) { return NULL; } @@ -50,86 +50,161 @@ static PluginExtension plugin_extension_geany_intenral = { .symbol_highlight_provided = func_return_true };
-static PluginExtension *current_extension = &plugin_extension_geany_intenral; +typedef struct +{ + PluginExtension *extension; + gpointer data; + gint priority; +} PluginExtensionEntry; + +static GList *all_extensions = NULL; + PluginExtension *plugin_extension_geany = &plugin_extension_geany_intenral;
-GEANY_API_SYMBOL -void plugin_extension_register(PluginExtension *extension) +/* sort higher priorities first */ +static gint sort_extension_entries(gconstpointer a, gconstpointer b) { - /* possibly, in the future if there's a need for multiple extensions, - * have a list of extensions and add/remove to/from the list */ - current_extension = extension; + const PluginExtensionEntry *entry_a = a; + const PluginExtensionEntry *entry_b = b; + + return entry_b->priority - entry_a->priority; }
GEANY_API_SYMBOL -void plugin_extension_unregister(PluginExtension *extension) +void plugin_extension_register(PluginExtension *extension, gint priority, gpointer data) { - current_extension = &plugin_extension_geany_intenral; + PluginExtensionEntry *entry = g_malloc(sizeof *entry); + + entry->extension = extension; + entry->data = data; + entry->priority = priority; + + all_extensions = g_list_insert_sorted(all_extensions, entry, sort_extension_entries); }
GEANY_API_SYMBOL -gboolean plugin_extension_active(PluginExtension *extension) +void plugin_extension_unregister(PluginExtension *extension) { - return current_extension == extension; + for (GList *node = all_extensions; node; node = node->next) + { + PluginExtensionEntry *entry = node->data; + + if (entry->extension == extension) + { + g_free(entry); + all_extensions = g_list_delete_link(all_extensions, node); + break; + } + } }
-/* allow plugins not to implement all the functions and fall back to the dummy - * implementation */ -#define CALL_IF_EXISTS(f) (current_extension->f ? current_extension->f : plugin_extension_geany_intenral.f) +/* + * @brief Checks whether a feature is provided + * @param f The virtual function name + * @param doc The document to check the feature on + * @param ext A @c PluginExtension, or @c NULL + * @returns @c TRUE if the feature is provided, @c FALSE otherwise. If @p ext + * is @c NULL, it check whether any extension provides the feature; + * if it is an extension, it check whether it's this extension that + * provides the feature (taking into account possible overrides). + */ +#define CALL_PROVIDED(f, doc, ext) \ + G_STMT_START { \ + for (GList *node = all_extensions; node; node = node->next) \ + { \ + PluginExtensionEntry *entry = node->data; \ + \ + if (entry->extension->f && entry->extension->f(doc, entry->data)) \ + return (ext) ? entry->extension == (ext) : TRUE; \ + } \ + return FALSE; \ + } G_STMT_END + +/* + * @brief Calls the extension implementation for f_provided/f_perform + * @param f_provided The name of the virtual function checking if the feature is provided + * @param doc The document to check the feature on + * @param f_perform The name of the virtual function implementing the feature + * @param args Arguments for @p f_perform. This should include @c entry->data as the last argument + * @param defret Return value if the feature is not implemented + * @returns The return value of @p f_perform or @p defret + */ +#define CALL_PERFORM(f_provided, doc, f_perform, args, defret) \ + G_STMT_START { \ + for (GList *node = all_extensions; node; node = node->next) \ + { \ + PluginExtensionEntry *entry = node->data; \ + \ + if (entry->extension->f_provided && \ + entry->extension->f_provided(doc, entry->data)) \ + { \ + if (entry->extension->f_perform) \ + return entry->extension->f_perform args; \ + break; \ + } \ + } \ + return defret; \ + } G_STMT_END +
-gboolean plugin_extension_autocomplete_provided(GeanyDocument *doc) +GEANY_API_SYMBOL +gboolean plugin_extension_autocomplete_provided(GeanyDocument *doc, PluginExtension *ext) { - return CALL_IF_EXISTS(autocomplete_provided)(doc); + CALL_PROVIDED(autocomplete_provided, doc, ext); }
void plugin_extension_autocomplete_perform(GeanyDocument *doc, gboolean force) { - CALL_IF_EXISTS(autocomplete_perform)(doc, force); + CALL_PERFORM(autocomplete_provided, doc, autocomplete_perform, (doc, force, entry->data), /* void */); }
-gboolean plugin_extension_calltips_provided(GeanyDocument *doc) +GEANY_API_SYMBOL +gboolean plugin_extension_calltips_provided(GeanyDocument *doc, PluginExtension *ext) { - return CALL_IF_EXISTS(calltips_provided)(doc); + CALL_PROVIDED(calltips_provided, doc, ext); }
void plugin_extension_calltips_show(GeanyDocument *doc, gboolean force) { - CALL_IF_EXISTS(calltips_show)(doc, force); + CALL_PERFORM(calltips_provided, doc, calltips_show, (doc, force, entry->data), /* void */); }
-gboolean plugin_extension_goto_provided(GeanyDocument *doc) +GEANY_API_SYMBOL +gboolean plugin_extension_goto_provided(GeanyDocument *doc, PluginExtension *ext) { - return CALL_IF_EXISTS(goto_provided)(doc); + CALL_PROVIDED(goto_provided, doc, ext); }
void plugin_extension_goto_perform(GeanyDocument *doc, gint pos, gboolean definition) { - CALL_IF_EXISTS(goto_perform)(doc, pos, definition); + CALL_PERFORM(goto_provided, doc, goto_perform, (doc, pos, definition, entry->data), /* void */); }
-gboolean plugin_extension_doc_symbols_provided(GeanyDocument *doc) +GEANY_API_SYMBOL +gboolean plugin_extension_doc_symbols_provided(GeanyDocument *doc, PluginExtension *ext) { - return CALL_IF_EXISTS(doc_symbols_provided)(doc); + CALL_PROVIDED(doc_symbols_provided, doc, ext); }
GPtrArray *plugin_extension_doc_symbols_get(GeanyDocument *doc) { - return CALL_IF_EXISTS(doc_symbols_get)(doc); + CALL_PERFORM(doc_symbols_provided, doc, doc_symbols_get, (doc, entry->data), NULL); }
-gboolean plugin_extension_symbol_highlight_provided(GeanyDocument *doc) +GEANY_API_SYMBOL +gboolean plugin_extension_symbol_highlight_provided(GeanyDocument *doc, PluginExtension *ext) { - return CALL_IF_EXISTS(symbol_highlight_provided)(doc); + CALL_PROVIDED(symbol_highlight_provided, doc, ext); } diff --git a/src/pluginextension.h b/src/pluginextension.h index 48866a4a6..3f018efd8 100644 --- a/src/pluginextension.h +++ b/src/pluginextension.h @@ -31,47 +31,42 @@ G_BEGIN_DECLS
typedef struct { - gboolean (*autocomplete_provided)(GeanyDocument *doc); - void (*autocomplete_perform)(GeanyDocument *doc, gboolean force); + gboolean (*autocomplete_provided)(GeanyDocument *doc, gpointer data); + void (*autocomplete_perform)(GeanyDocument *doc, gboolean force, gpointer data);
- gboolean (*calltips_provided)(GeanyDocument *doc); - void (*calltips_show)(GeanyDocument *doc, gboolean force); + gboolean (*calltips_provided)(GeanyDocument *doc, gpointer data); + void (*calltips_show)(GeanyDocument *doc, gboolean force, gpointer data);
- gboolean (*goto_provided)(GeanyDocument *doc); - void (*goto_perform)(GeanyDocument *doc, gint pos, gboolean definition); + gboolean (*goto_provided)(GeanyDocument *doc, gpointer data); + void (*goto_perform)(GeanyDocument *doc, gint pos, gboolean definition, gpointer data);
- gboolean (*doc_symbols_provided)(GeanyDocument *doc); - GPtrArray *(*doc_symbols_get)(GeanyDocument *doc); + gboolean (*doc_symbols_provided)(GeanyDocument *doc, gpointer data); + GPtrArray *(*doc_symbols_get)(GeanyDocument *doc, gpointer data);
- gboolean (*symbol_highlight_provided)(GeanyDocument *doc); + gboolean (*symbol_highlight_provided)(GeanyDocument *doc, gpointer data);
gchar _dummy[1024]; } PluginExtension;
-void plugin_extension_register(PluginExtension *extension); +void plugin_extension_register(PluginExtension *extension, gint priority, gpointer data); void plugin_extension_unregister(PluginExtension *extension); -gboolean plugin_extension_active(PluginExtension *extension);
+gboolean plugin_extension_autocomplete_provided(GeanyDocument *doc, PluginExtension *ext); +gboolean plugin_extension_calltips_provided(GeanyDocument *doc, PluginExtension *ext); +gboolean plugin_extension_goto_provided(GeanyDocument *doc, PluginExtension *ext); +gboolean plugin_extension_doc_symbols_provided(GeanyDocument *doc, PluginExtension *ext); +gboolean plugin_extension_symbol_highlight_provided(GeanyDocument *doc, PluginExtension *ext);
#ifdef GEANY_PRIVATE
extern PluginExtension *plugin_extension_geany;
-gboolean plugin_extension_autocomplete_provided(GeanyDocument *doc); void plugin_extension_autocomplete_perform(GeanyDocument *doc, gboolean force); - -gboolean plugin_extension_calltips_provided(GeanyDocument *doc); void plugin_extension_calltips_show(GeanyDocument *doc, gboolean force); - -gboolean plugin_extension_goto_provided(GeanyDocument *doc); void plugin_extension_goto_perform(GeanyDocument *doc, gint pos, gboolean definition); - -gboolean plugin_extension_doc_symbols_provided(GeanyDocument *doc); GPtrArray *plugin_extension_doc_symbols_get(GeanyDocument *doc);
-gboolean plugin_extension_symbol_highlight_provided(GeanyDocument *doc); - #endif /* GEANY_PRIVATE */
G_END_DECLS diff --git a/src/symbols.c b/src/symbols.c index e1585ece1..366db783a 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -1708,7 +1708,7 @@ gboolean symbols_goto_tag(const gchar *name, gint pos, gboolean definition) { GeanyDocument *doc = document_get_current();
- if (plugin_extension_goto_provided(doc)) + if (plugin_extension_goto_provided(doc, NULL)) { /* FIXME: this should return TRUE on success so click handling in * editor.c can let the even pass through if there was nothing to do here @@ -1722,7 +1722,7 @@ gboolean symbols_goto_tag(const gchar *name, gint pos, gboolean definition) }
-void symbols_goto_perform(GeanyDocument *doc, gint pos, gboolean definition) +void symbols_goto_perform(GeanyDocument *doc, gint pos, gboolean definition, gpointer data G_GNUC_UNUSED) { const gchar *name;
diff --git a/src/symbols.h b/src/symbols.h index 00face7f8..070f4ed42 100644 --- a/src/symbols.h +++ b/src/symbols.h @@ -60,7 +60,7 @@ void symbols_show_load_tags_dialog(void);
gboolean symbols_goto_tag(const gchar *name, gint pos, gboolean definition);
-void symbols_goto_perform(GeanyDocument *doc, gint pos, gboolean definition); +void symbols_goto_perform(GeanyDocument *doc, gint pos, gboolean definition, gpointer data);
gint symbols_get_current_function(GeanyDocument *doc, const gchar **tagname);
``` </details>