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>

-- 
Reply to this email directly or view it on GitHub:
https://github.com/geany/geany/pull/3849#issuecomment-2150913604
You are receiving this because you are subscribed to this thread.

Message ID: <geany/geany/pull/3849/[email protected]>

Reply via email to