Branch: refs/heads/master
Author: Jiří Techet <techet(a)gmail.com>
Committer: Jiří Techet <techet(a)gmail.com>
Date: Sun, 30 Jun 2024 09:52:40 UTC
Commit: ef70f3b7d12207a19e4b2ae904028bf911c5fc1c
https://github.com/geany/geany/commit/ef70f3b7d12207a19e4b2ae904028bf911c5f…
Log Message:
-----------
Add Geany code delegating keyword highlighting to plugins
Modified Paths:
--------------
src/document.c
Modified: src/document.c
4 lines changed, 4 insertions(+), 0 deletions(-)
===================================================================
@@ -44,6 +44,7 @@
#include "msgwindow.h"
#include "navqueue.h"
#include "notebook.h"
+#include "pluginextension.h"
#include "project.h"
#include "sciwrappers.h"
#include "sidebar.h"
@@ -2719,6 +2720,9 @@ void document_highlight_tags(GeanyDocument *doc)
GString *keywords_str;
gint keyword_idx;
+ if (plugin_extension_symbol_highlight_provided(doc, NULL))
+ return;
+
/* some filetypes support type keywords (such as struct names), but not
* necessarily all filetypes for a particular scintilla lexer. this
* tells us whether the filetype supports keywords, and if so
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
Branch: refs/heads/master
Author: Jiří Techet <techet(a)gmail.com>
Committer: GitHub <noreply(a)github.com>
Date: Sat, 03 Aug 2024 14:41:50 UTC
Commit: 6444a182e205ee2598be78cee02fdeb10379e2ff
https://github.com/geany/geany/commit/6444a182e205ee2598be78cee02fdeb10379e…
Log Message:
-----------
Merge pull request #3849 from techee/lsp2
Plugin extensions (aka LSP API)
Modified Paths:
--------------
doc/plugins.dox
meson.build
plugins/Makefile.am
plugins/demopluginext.c
plugins/geanyplugin.h
plugins/meson.build
src/Makefile.am
src/document.c
src/editor.c
src/keybindings.c
src/plugindata.h
src/pluginextension.c
src/pluginextension.h
src/symbols.c
src/symbols.h
Modified: doc/plugins.dox
143 lines changed, 143 insertions(+), 0 deletions(-)
===================================================================
@@ -40,6 +40,7 @@ GeanyFuncs::cleanup functions).
@section pluginsupport Plugin Support
- @link howto Plugin HowTo @endlink - get started
- @ref proxy
+- @ref plugin_extension
- @ref legacy
- @link plugindata.h Plugin Datatypes and Macros @endlink
- @link pluginsignals.c Plugin Signals @endlink
@@ -54,6 +55,7 @@ GeanyFuncs::cleanup functions).
- @link filetypes.h @endlink
- @link keybindings.h @endlink
- @link msgwindow.h @endlink
+- @link pluginextension.h @endlink
- @link project.h @endlink
- @link sciwrappers.h Scintilla Wrapper Functions @endlink
- @link spawn.h Spawning programs @endlink
@@ -1116,4 +1118,145 @@ static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata)
@endcode
+@page plugin_extension Plugin Extension HowTo
+
+@section plugin_extension_intro Introduction
+
+Originally the Geany plugin API only allowed plugins to add to Geany
+functionality, plugins could not modify Geany built-in functionality, but since
+Geany 2.1 the PluginExtension API allows plugins to take over some of
+the core Geany functionality: autocopletion, calltip display, symbol goto, and
+typename highlighting inside document.
+
+@section plugin_extension_init Initialization and cleanup
+
+Plugins using the @c PluginExtension API are just normal plugins and behave as
+described in @link howto Plugin HowTo@endlink.
+
+First, any plugin interested in using this interface has to register its
+@c PluginExtension structure pointer using @c plugin_extension_register(). This
+typically happens in the @c init() function of the plugin. Registered
+@c PluginExtension pointers have to be unregistered before the plugin is
+unloaded using @c plugin_extension_unregister(), typically inside the
+@c cleanup() function of the plugin.
+
+@section plugin_extension_impl Implementing extensions
+
+Inside the @c PluginExtension struct, the plugin fills-in the pointers of the
+functions it wishes to implement. Typically, these functions
+come in pairs:
+ - functions assigned to members ending with @c _provided are used by Geany to
+ query the plugin whether it implements the particular feature for the passed
+ document
+ - functions assigned to members ending with @c _perform are used by Geany to
+ pass control to the plugin to perform the feature instead of performing the
+ Geany built-in functionality.
+
+When the plugin returns @c TRUE from the function assigned to the @c _provided
+member of @c PluginExtension, it indicates
+it wants to take control of the particular feature and disable Geany's
+implementation. However, returning @c TRUE does not automatically guarantee that
+the plugin's implementation is executed - if there are multiple plugins competing
+for implementing a feature, the extension with the highest priority
+passed into the @c plugin_extension_register() function gets executed.
+
+A plugin can perform a check if it gets executed for the
+particular feature; e.g. for autocompletion the plugin can use
+@c plugin_extension_autocomplete_provided() which returns @c TRUE if the
+passed extension is executed, taking into account all registered extension
+priorities and the return values of all functions assigned to
+@c autocomplete_provided members of the registered extensions.
+This can be used if the plugin needs to perform auxiliary actions outside the
+function assigned to @c autocomplete_perform to verify it is actually active
+for this feature.
+
+@section plugin_extension_ex Example
+
+Below you will find an example of a plugin implementing autocompletion for
+Python. The full version of this code can be found under
+plugins/demopluginext.c inside the Geany repository.
+
+
+@code
+/* License blob */
+
+#include <geanyplugin.h>
+
+static gboolean autocomplete_provided(GeanyDocument *doc, gpointer data)
+{
+ /* Check whether the plugin provides the feature for the passed document */
+ return doc->file_type->id == GEANY_FILETYPES_PYTHON;
+}
+
+
+static void autocomplete_perform(GeanyDocument *doc, gboolean force, gpointer data)
+{
+ /* The autocompletion logic comes here, including the autocompletion UI
+ * display (either using some custom widget or using Scintilla's
+ * SCI_AUTOCSHOW) */
+}
+
+
+/* The PluginExtension struct - we only implement autocompletion here. */
+static PluginExtension extension = {
+ .autocomplete_provided = autocomplete_provided,
+ .autocomplete_perform = autocomplete_perform
+};
+
+
+static gboolean on_editor_notify(G_GNUC_UNUSED GObject *obj, GeanyEditor *editor, SCNotification *nt,
+ G_GNUC_UNUSED gpointer user_data)
+{
+ if (nt->nmhdr.code == SCN_AUTOCSELECTION)
+ {
+ if (plugin_extension_autocomplete_provided(editor->document, &extension))
+ {
+ /* This is an example of using plugin_extension_autocomplete_provided()
+ * to detect whether this plugin extension was used to perform
+ * autocompletion. */
+ msgwin_status_add("PluginExtensionDemo autocompleted '%s'", nt->text);
+ }
+ }
+
+ return FALSE;
+}
+
+
+static PluginCallback plugin_callbacks[] = {
+ {"editor-notify", (GCallback) &on_editor_notify, FALSE, NULL},
+ {NULL, NULL, FALSE, NULL}
+};
+
+
+static gboolean init_func(GeanyPlugin *plugin, gpointer pdata)
+{
+ /* Extension registration */
+ plugin_extension_register(&extension, "Python keyword autocompletion", 450, NULL);
+ return TRUE;
+}
+
+
+static void cleanup_func(GeanyPlugin *plugin, gpointer pdata)
+{
+ /* Extension unregistration */
+ plugin_extension_unregister(&extension);
+}
+
+
+G_MODULE_EXPORT
+void geany_load_module(GeanyPlugin *plugin)
+{
+ plugin->info->name = "PluginExtensionDemo";
+ plugin->info->description = "Demo performing simple Python keyword autocompletion";
+ plugin->info->version = "1.0";
+ plugin->info->author = "John Doe <john.doe(a)example.org>";
+
+ plugin->funcs->init = init_func;
+ plugin->funcs->cleanup = cleanup_func;
+ plugin->funcs->callbacks = plugin_callbacks;
+
+ GEANY_PLUGIN_REGISTER(plugin, 248);
+}
+@endcode
+
*/
Modified: meson.build
3 lines changed, 3 insertions(+), 0 deletions(-)
===================================================================
@@ -785,6 +785,7 @@ install_headers(
'src/msgwindow.h',
'src/navqueue.h',
'src/plugindata.h',
+ 'src/pluginextension.h',
'src/pluginutils.h',
'src/prefs.h',
'src/project.h',
@@ -846,6 +847,8 @@ libgeany = shared_library('geany',
'src/navqueue.h',
'src/notebook.c',
'src/notebook.h',
+ 'src/pluginextension.c',
+ 'src/pluginextension.h',
'src/plugins.c',
'src/plugins.h',
'src/pluginutils.c',
Modified: plugins/Makefile.am
5 lines changed, 5 insertions(+), 0 deletions(-)
===================================================================
@@ -11,6 +11,7 @@ plugins_include_HEADERS = \
geanyplugin.h
demoplugin_la_LDFLAGS = -module -avoid-version -no-undefined
+demopluginext_la_LDFLAGS = -module -avoid-version -no-undefined
demoproxy_la_LDFLAGS = -module -avoid-version -no-undefined
classbuilder_la_LDFLAGS = -module -avoid-version -no-undefined
htmlchars_la_LDFLAGS = -module -avoid-version -no-undefined
@@ -32,9 +33,11 @@ plugin_LTLIBRARIES = \
# Plugins not to be installed
noinst_LTLIBRARIES = \
demoplugin.la \
+ demopluginext.la \
demoproxy.la
demoplugin_la_SOURCES = demoplugin.c
+demopluginext_la_SOURCES = demopluginext.c
demoproxy_la_SOURCES = demoproxy.c
classbuilder_la_SOURCES = classbuilder.c
htmlchars_la_SOURCES = htmlchars.c
@@ -44,6 +47,7 @@ filebrowser_la_SOURCES = filebrowser.c
splitwindow_la_SOURCES = splitwindow.c
demoplugin_la_CFLAGS = -DG_LOG_DOMAIN=\""Demoplugin"\"
+demopluginext_la_CFLAGS = -DG_LOG_DOMAIN=\""Demopluginext"\"
demoproxy_la_CFLAGS = -DG_LOG_DOMAIN=\""Demoproxy"\"
classbuilder_la_CFLAGS = -DG_LOG_DOMAIN=\""Classbuilder"\"
htmlchars_la_CFLAGS = -DG_LOG_DOMAIN=\""HTMLChars"\"
@@ -53,6 +57,7 @@ filebrowser_la_CFLAGS = -DG_LOG_DOMAIN=\""FileBrowser"\"
splitwindow_la_CFLAGS = -DG_LOG_DOMAIN=\""SplitWindow"\"
demoplugin_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS)
+demopluginext_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS)
demoproxy_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS)
classbuilder_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS)
htmlchars_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS)
Modified: plugins/demopluginext.c
130 lines changed, 130 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,130 @@
+/*
+ * demopluginext.c - this file is part of Geany, a fast and lightweight IDE
+ *
+ * Copyright 2024 The Geany contributors
+ *
+ * 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 2 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * Demo plugin extension - example of a plugin using the PluginExtension API
+ * to provide simple autocompletion of Python keywords. This is the plugin
+ * example used in the documentation with full implementation of
+ * @c autocomplete_perform().
+ *
+ * Note: This is not installed by default, but (on *nix) you can build it as follows:
+ * cd plugins
+ * make demopluginext.so
+ *
+ * Then copy or symlink the plugins/demopluginext.so file to ~/.config/geany/plugins
+ * - it will be loaded at next startup.
+ */
+
+#include <geanyplugin.h>
+
+static gboolean autocomplete_provided(GeanyDocument *doc, gpointer data)
+{
+ return doc->file_type->id == GEANY_FILETYPES_PYTHON;
+}
+
+
+static void autocomplete_perform(GeanyDocument *doc, gboolean force, gpointer data)
+{
+ const gchar *kwd_str = "False None True and as assert async await break case class continue def del elif else except finally for from global if import in is lambda match nonlocal not or pass raise return try while with yield";
+ gint pos = sci_get_current_position(doc->editor->sci);
+ gchar *word = editor_get_word_at_pos(doc->editor, pos, NULL);
+ gchar **kwd;
+
+ if (word && *word)
+ {
+ GString *words = g_string_sized_new(100);
+ gchar **kwds = g_strsplit(kwd_str, " ", -1);
+
+ foreach_strv(kwd, kwds)
+ {
+ if (g_str_has_prefix(*kwd, word))
+ {
+ if (words->len > 0)
+ g_string_append(words, "\n");
+
+ g_string_append(words, *kwd);
+ }
+ }
+
+ scintilla_send_message(doc->editor->sci, SCI_AUTOCSHOW, strlen(word), (sptr_t) words->str);
+ g_string_free(words, TRUE);
+ g_strfreev(kwds);
+ }
+
+ g_free(word);
+}
+
+
+static PluginExtension extension = {
+ .autocomplete_provided = autocomplete_provided,
+ .autocomplete_perform = autocomplete_perform
+};
+
+
+static gboolean on_editor_notify(G_GNUC_UNUSED GObject *obj, GeanyEditor *editor, SCNotification *nt,
+ G_GNUC_UNUSED gpointer user_data)
+{
+ if (nt->nmhdr.code == SCN_AUTOCSELECTION)
+ {
+ if (plugin_extension_autocomplete_provided(editor->document, &extension))
+ {
+ /* we can be sure it was us who performed the autocompletion and
+ * not Geany or some other plugin extension */
+ msgwin_status_add("PluginExtensionDemo autocompleted '%s'", nt->text);
+ }
+ }
+
+ return FALSE;
+}
+
+
+static PluginCallback plugin_callbacks[] = {
+ {"editor-notify", (GCallback) &on_editor_notify, FALSE, NULL},
+ {NULL, NULL, FALSE, NULL}
+};
+
+
+static gboolean init_func(GeanyPlugin *plugin, gpointer pdata)
+{
+ plugin_extension_register(&extension, "Python keyword autocompletion", 450, NULL);
+ return TRUE;
+}
+
+
+static void cleanup_func(GeanyPlugin *plugin, gpointer pdata)
+{
+ plugin_extension_unregister(&extension);
+}
+
+
+G_MODULE_EXPORT
+void geany_load_module(GeanyPlugin *plugin)
+{
+ plugin->info->name = "PluginExtensionDemo";
+ plugin->info->description = "Demo performing simple Python keyword autocompletion";
+ plugin->info->version = "1.0";
+ plugin->info->author = "John Doe <john.doe(a)example.org>";
+
+ plugin->funcs->init = init_func;
+ plugin->funcs->cleanup = cleanup_func;
+ plugin->funcs->callbacks = plugin_callbacks;
+
+ GEANY_PLUGIN_REGISTER(plugin, 248);
+}
Modified: plugins/geanyplugin.h
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -46,6 +46,7 @@
#include "msgwindow.h"
#include "navqueue.h"
#include "plugindata.h"
+#include "pluginextension.h"
#include "pluginutils.h"
#include "prefs.h"
#include "project.h"
Modified: plugins/meson.build
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -3,6 +3,7 @@ plugin_inc += iscintilla
plugins = [
'Demoplugin',
+ 'Demopluginext',
'Demoproxy',
'Classbuilder',
'HTMLChars',
Modified: src/Makefile.am
2 lines changed, 2 insertions(+), 0 deletions(-)
===================================================================
@@ -51,6 +51,7 @@ geany_include_HEADERS = \
msgwindow.h \
navqueue.h \
plugindata.h \
+ pluginextension.h \
pluginutils.h \
prefs.h \
project.h \
@@ -89,6 +90,7 @@ libgeany_la_SOURCES = \
msgwindow.c msgwindow.h \
navqueue.c navqueue.h \
notebook.c notebook.h \
+ pluginextension.c pluginextension.h \
plugins.c plugins.h \
pluginutils.c pluginutils.h \
prefs.c prefs.h \
Modified: src/document.c
4 lines changed, 4 insertions(+), 0 deletions(-)
===================================================================
@@ -44,6 +44,7 @@
#include "msgwindow.h"
#include "navqueue.h"
#include "notebook.h"
+#include "pluginextension.h"
#include "project.h"
#include "sciwrappers.h"
#include "sidebar.h"
@@ -2703,6 +2704,9 @@ void document_highlight_tags(GeanyDocument *doc)
GString *keywords_str;
gint keyword_idx;
+ if (plugin_extension_symbol_highlight_provided(doc, NULL))
+ return;
+
/* some filetypes support type keywords (such as struct names), but not
* necessarily all filetypes for a particular scintilla lexer. this
* tells us whether the filetype supports keywords, and if so
Modified: src/editor.c
32 lines changed, 17 insertions(+), 15 deletions(-)
===================================================================
@@ -46,6 +46,7 @@
#include "highlighting.h"
#include "keybindings.h"
#include "main.h"
+#include "pluginextension.h"
#include "prefs.h"
#include "projectprivate.h"
#include "sciwrappers.h"
@@ -316,11 +317,7 @@ static gboolean on_editor_button_press_event(GtkWidget *widget, GdkEventButton *
{
sci_set_current_position(editor->sci, editor_info.click_pos, FALSE);
- editor_find_current_word(editor, editor_info.click_pos,
- current_word, sizeof current_word, NULL);
- if (*current_word)
- return symbols_goto_tag(current_word, TRUE);
- else
+ if (!symbols_goto_tag(doc, editor_info.click_pos, TRUE))
keybindings_send_command(GEANY_KEY_GROUP_GOTO, GEANY_KEYS_GOTO_MATCHINGBRACE);
return TRUE;
}
@@ -821,13 +818,15 @@ static void on_char_added(GeanyEditor *editor, SCNotification *nt)
case '(':
{
auto_close_chars(sci, pos, nt->ch);
- /* show calltips */
- editor_show_calltip(editor, --pos);
+ if (!plugin_extension_calltips_provided(editor->document, NULL))
+ /* show calltips */
+ editor_show_calltip(editor, --pos);
break;
}
case ')':
{ /* hide calltips */
- if (SSM(sci, SCI_CALLTIPACTIVE, 0, 0))
+ if (SSM(sci, SCI_CALLTIPACTIVE, 0, 0) &&
+ !plugin_extension_calltips_provided(editor->document, NULL))
{
SSM(sci, SCI_CALLTIPCANCEL, 0, 0);
}
@@ -857,13 +856,12 @@ static void on_char_added(GeanyEditor *editor, SCNotification *nt)
case ':': /* C/C++ class:: syntax */
/* tag autocompletion */
default:
-#if 0
- if (! editor_start_auto_complete(editor, pos, FALSE))
- request_reshowing_calltip(nt);
-#else
editor_start_auto_complete(editor, pos, FALSE);
-#endif
}
+
+ plugin_extension_autocomplete_perform(editor->document, FALSE);
+ plugin_extension_calltips_show(editor->document, FALSE);
+
check_line_breaking(editor, pos);
}
@@ -1157,7 +1155,8 @@ 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;
- request_reshowing_calltip(nt);
+ if (!plugin_extension_calltips_provided(doc, NULL))
+ request_reshowing_calltip(nt);
break;
case SCN_NEEDSHOWN:
ensure_range_visible(sci, nt->position, nt->position + nt->length, FALSE);
@@ -1171,7 +1170,7 @@ static gboolean on_editor_notify(G_GNUC_UNUSED GObject *object, GeanyEditor *edi
break;
case SCN_CALLTIPCLICK:
- if (nt->position > 0)
+ if (!plugin_extension_calltips_provided(doc, NULL) && nt->position > 0)
{
switch (nt->position)
{
@@ -2223,6 +2222,9 @@ gboolean editor_start_auto_complete(GeanyEditor *editor, gint pos, gboolean forc
g_return_val_if_fail(editor != NULL, FALSE);
+ if (plugin_extension_autocomplete_provided(editor->document, NULL))
+ return FALSE;
+
if (! editor_prefs.auto_complete_symbols && ! force)
return FALSE;
Modified: src/keybindings.c
19 lines changed, 10 insertions(+), 9 deletions(-)
===================================================================
@@ -43,6 +43,7 @@
#include "msgwindow.h"
#include "navqueue.h"
#include "notebook.h"
+#include "pluginextension.h"
#include "prefs.h"
#include "sciwrappers.h"
#include "sidebar.h"
@@ -1971,14 +1972,8 @@ static gboolean cb_func_clipboard_action(guint key_id)
static void goto_tag(GeanyDocument *doc, gboolean definition)
{
- gchar *text = get_current_word_or_sel(doc, FALSE);
-
- if (text)
- symbols_goto_tag(text, definition);
- else
+ if (!symbols_goto_tag(doc, sci_get_current_position(doc->editor->sci), definition))
utils_beep();
-
- g_free(text);
}
@@ -2151,10 +2146,16 @@ static gboolean cb_func_editor_action(guint key_id)
sci_send_command(doc->editor->sci, SCI_LINETRANSPOSE);
break;
case GEANY_KEYS_EDITOR_AUTOCOMPLETE:
- editor_start_auto_complete(doc->editor, sci_get_current_position(doc->editor->sci), TRUE);
+ if (plugin_extension_autocomplete_provided(doc, NULL))
+ plugin_extension_autocomplete_perform(doc, TRUE);
+ else
+ editor_start_auto_complete(doc->editor, sci_get_current_position(doc->editor->sci), TRUE);
break;
case GEANY_KEYS_EDITOR_CALLTIP:
- editor_show_calltip(doc->editor, -1);
+ if (plugin_extension_calltips_provided(doc, NULL))
+ plugin_extension_calltips_show(doc, TRUE);
+ else
+ editor_show_calltip(doc->editor, -1);
break;
case GEANY_KEYS_EDITOR_CONTEXTACTION:
if (check_current_word(doc, FALSE))
Modified: src/plugindata.h
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -57,7 +57,7 @@ G_BEGIN_DECLS
* @warning You should not test for values below 200 as previously
* @c GEANY_API_VERSION was defined as an enum value, not a macro.
*/
-#define GEANY_API_VERSION 247
+#define GEANY_API_VERSION 248
/* hack to have a different ABI when built with different GTK major versions
* because loading plugins linked to a different one leads to crashes.
Modified: src/pluginextension.c
277 lines changed, 277 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,277 @@
+/*
+ * pluginextension.c - this file is part of Geany, a fast and lightweight IDE
+ *
+ * Copyright 2023 The Geany contributors
+ *
+ * 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 2 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "pluginextension.h"
+
+#include "editor.h"
+
+
+typedef struct
+{
+ PluginExtension *extension;
+ gpointer data;
+ gint priority;
+} PluginExtensionEntry;
+
+static GList *all_extensions = NULL;
+
+
+/* sort higher priorities first */
+static gint sort_extension_entries(gconstpointer a, gconstpointer b)
+{
+ const PluginExtensionEntry *entry_a = a;
+ const PluginExtensionEntry *entry_b = b;
+
+ return entry_b->priority - entry_a->priority;
+}
+
+
+/**
+ * Registers the provided extension in Geany. There can be multiple extensions
+ * registered in Geany - these are sorted by the priority
+ * parameter. When executing functions assigned to the @c PluginExtension
+ * members ending with @c _perform, Geany goes
+ * through the registered extensions and executes the @c _perform() function of
+ * the first extension for which the function assigned to the corresponding
+ * @c _provided member returns @c TRUE.
+ *
+ * This function is typically called in the plugin's @c init() function.
+ *
+ * Plugins wishing to re-register themselves, e.g. with a different priority,
+ * should first unregister themselves using @c plugin_extension_unregister()
+ * and call @c plugin_extension_register() afterwards.
+ *
+ * @param extension A pointer to the @c PluginExtension structure to register.
+ * This pointer and the @c PluginExtension it points to have to remain valid
+ * until the extension is unregistered. All fields of the @c PluginExtension
+ * structure have to be fully initialized either to the @c NULL pointer or to a
+ * proper implementation. Usually, this structure is statically allocated which
+ * automatically zero-initializes uninitialized members as appropriate.
+ * @param ext_name Human-readable name of the extension that can appear in
+ * the user interface. The string should be reasonably unique so extensions can
+ * be distinguished from each other.
+ * @param priority Extension priority. The recommended values are:
+ * - if the extension is the only thing the plugin provides, and if it targets
+ * a single filetype, use 400 <= priority < 500.
+ * - if the extension is the only thing the plugin provides, but it targets
+ * several filetypes, use 300 <= priority < 400.
+ * - if the plugin provides other features than the extension, but it only
+ * targets a single filetype, use 200 <= priority < 300.
+ * - if the plugin provides other features than the extension, and targets
+ * several filetypes, use 100 <= priority < 200.
+ * - a priority of 0 or less is reserved, and using it has unspecified behavior.
+ * @param data User data passed to the functions from the @c PluginExtension
+ * struct.
+ * @warning Plugins are responsible for calling @c plugin_extension_unregister()
+ * when they no longer provide the extension and when the plugin is unloaded.
+ *
+ * @see @c plugin_extension_unregister().
+ *
+ * @since 2.1
+ **/
+GEANY_API_SYMBOL
+void plugin_extension_register(PluginExtension *extension, const gchar *ext_name,
+ gint priority, gpointer data)
+{
+ PluginExtensionEntry *entry;
+
+ g_return_if_fail(ext_name != NULL);
+
+ 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);
+}
+
+
+/**
+ * Plugins are responsible for calling this function when they no longer
+ * provide the extension, at the latest in the plugin's @c cleanup() function.
+ *
+ * @param extension The @c PluginExtension structure pointer to unregister, as
+ * previously registered with @c plugin_extension_register().
+ *
+ * @see @c plugin_extension_register().
+ *
+ * @since 2.1
+ **/
+GEANY_API_SYMBOL
+void plugin_extension_unregister(PluginExtension *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;
+ }
+ }
+}
+
+
+/*
+ * @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; \
+ \
+ if ((ext) && entry->extension == (ext)) \
+ return FALSE; \
+ } \
+ 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
+
+
+/*
+ * Geany itself can also pass NULL as the extension parameter to determine
+ * whether there is any extension implementing autocompletion. Plugins should
+ * not use this and rely on Geany's extension list and the priorities so
+ * this feature is not documented in the plugin API.
+ */
+/**
+ * Plugins can call this function to check whether, based on the extensions
+ * registered and the provided extension priorities, the extension passed in
+ * the @c ext parameter is used for autocompletion.
+ *
+ * Plugins will typically call this function with their own @c PluginExtension
+ * to check, if they get (or got) executed for autocompletion of the
+ * provided document. This is useful for various auxiliary functions such as
+ * cleanups after the function assigned to @c autocomplete_perform is completed
+ * so plugins know they executed this function and do not have to store this
+ * information by some other means.
+ *
+ * @param doc Document for which the check is performed.
+ * @param ext The extension for which the check is performed.
+ * @return Returns @c TRUE if autocompletion provided by the passed extension
+ * is used, @c FALSE otherwise.
+ *
+ * @since 2.1
+ **/
+GEANY_API_SYMBOL
+gboolean plugin_extension_autocomplete_provided(GeanyDocument *doc, PluginExtension *ext)
+{
+ CALL_PROVIDED(autocomplete_provided, doc, ext);
+}
+
+
+void plugin_extension_autocomplete_perform(GeanyDocument *doc, gboolean force)
+{
+ CALL_PERFORM(autocomplete_provided, doc, autocomplete_perform, (doc, force, entry->data), /* void */);
+}
+
+
+/**
+ * Checks whether the provided extension is used for showing calltips.
+ *
+ * @see @c plugin_extension_autocomplete_provided()
+ *
+ * @since 2.1
+ **/
+GEANY_API_SYMBOL
+gboolean plugin_extension_calltips_provided(GeanyDocument *doc, PluginExtension *ext)
+{
+ CALL_PROVIDED(calltips_provided, doc, ext);
+}
+
+
+void plugin_extension_calltips_show(GeanyDocument *doc, gboolean force)
+{
+ CALL_PERFORM(calltips_provided, doc, calltips_show, (doc, force, entry->data), /* void */);
+}
+
+
+/**
+ * Checks whether the provided extension is used for going to symbol
+ * definition/declaration.
+ *
+ * @see @c plugin_extension_autocomplete_provided()
+ *
+ * @since 2.1
+ **/
+GEANY_API_SYMBOL
+gboolean plugin_extension_goto_provided(GeanyDocument *doc, PluginExtension *ext)
+{
+ CALL_PROVIDED(goto_provided, doc, ext);
+}
+
+
+gboolean plugin_extension_goto_perform(GeanyDocument *doc, gint pos, gboolean definition)
+{
+ CALL_PERFORM(goto_provided, doc, goto_perform, (doc, pos, definition, entry->data), FALSE);
+}
+
+
+/**
+ * Checks whether the provided extension is used for highlighting symbols in
+ * the document.
+ *
+ * @see @c plugin_extension_autocomplete_provided()
+ *
+ * @since 2.1
+ **/
+GEANY_API_SYMBOL
+gboolean plugin_extension_symbol_highlight_provided(GeanyDocument *doc, PluginExtension *ext)
+{
+ CALL_PROVIDED(symbol_highlight_provided, doc, ext);
+}
Modified: src/pluginextension.h
191 lines changed, 191 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,191 @@
+/*
+ * pluginextension.h - this file is part of Geany, a fast and lightweight IDE
+ *
+ * Copyright 2023 The Geany contributors
+ *
+ * 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 2 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * @file pluginextension.h
+ * This file defines an interface allowing plugins to take over some of the
+ * core functionality provided by Geany: autocompletion, calltips,
+ * symbol goto, and types highlighting inside document.
+ **/
+
+#ifndef GEANY_PLUGIN_EXTENSION_H
+#define GEANY_PLUGIN_EXTENSION_H 1
+
+#include "document.h"
+
+
+G_BEGIN_DECLS
+
+/**
+ * Structure serving as an interface between plugins and Geany allowing
+ * plugins to inform Geany about what features they provide and allowing
+ * Geany to delegate its functionality to the plugins.
+ *
+ * Depending on the functionality they provide, plugins should assign pointers
+ * to the functions implementing this interface to the appropriate members of
+ * the structure. Not all of the functions have to be implemented by the
+ * plugin - member pointers of unimplemented functions can be left to contain
+ * the @c NULL pointer.
+ *
+ * Typically, functions from this interface come in pairs. Functions assigned to
+ * the members ending with @c _provided inform Geany whether the plugin
+ * implements the given feature for the passed document. Functions assigned
+ * to the members ending with @c _perform are called by Geany at appropriate
+ * moments to inform the plugin when to perform the given feature.
+ *
+ * The extension is defined by the pointers in the PluginExtension structure and
+ * is registered in Geany using the @c plugin_extension_register() function.
+ *
+ * @warning The API provided by this file is subject to change and should not be
+ * considered stable at this point. That said, it is highly probable that if
+ * a change of this API is required in the future, it will not be of a major
+ * nature and should not require major modifications of the affected plugins
+ * (e.g. added/modified parameter of a function and similar changes).
+ **/
+typedef struct
+{
+ /**
+ * Pointer to function called by Geany to check whether the plugin
+ * implements autocompletion for the provided document.
+ *
+ * @param doc The document for which Geany is querying whether the plugin
+ * provides autocompletion. This allows plugins to restrict their
+ * autocompletion implementation to documents of specific filetypes or other
+ * characteristics.
+ * @param data User data passed during the @c plugin_extension_register()
+ * call.
+ *
+ * @return Plugins should return @c TRUE if they impelment autocompletion
+ * for the provided document, @c FALSE otherwise.
+ *
+ * @since 2.1
+ **/
+ gboolean (*autocomplete_provided)(GeanyDocument *doc, gpointer data);
+ /**
+ * Pointer to function called by Geany to inform the plugin to perform
+ * autocompletion, e.g by showing an autocompletion popup window with
+ * suggestions.
+ *
+ * @param doc The document for which autocompletion is being performed.
+ * @param force @c TRUE when autocompletion was requested explicitly by the
+ * user, e.g. by pressing a keybinding requesting the autocompletion popup
+ * to appear. @c FALSE means autocompletion is being auto-performed as the
+ * user types, possibly allowing the plugin not to do anything if there are
+ * no meaningful suggestions.
+ * @param data User data passed during the @c plugin_extension_register()
+ * call.
+ *
+ * @since 2.1
+ **/
+ void (*autocomplete_perform)(GeanyDocument *doc, gboolean force, gpointer data);
+
+ /**
+ * Pointer to function called by Geany to check whether the plugin
+ * implements calltips containing function signatures for the provided
+ * document.
+ *
+ * @see @c autocomplete_provided() for more details.
+ *
+ * @since 2.1
+ **/
+ gboolean (*calltips_provided)(GeanyDocument *doc, gpointer data);
+ /**
+ * Pointer to function called by Geany to inform the plugin to show calltips.
+ *
+ * @see @c autocomplete_perform() for more details.
+ *
+ * @since 2.1
+ **/
+ void (*calltips_show)(GeanyDocument *doc, gboolean force, gpointer data);
+
+ /**
+ * Pointer to function called by Geany to check whether the plugin implements
+ * symbol definition/declaration goto functionality.
+ *
+ * @see @c autocomplete_provided() for more details.
+ *
+ * @since 2.1
+ **/
+ gboolean (*goto_provided)(GeanyDocument *doc, gpointer data);
+ /**
+ * Pointer to function called by Geany to inform the plugin to perform
+ * symbol goto.
+ *
+ * @param doc The document for which symbol goto is being performed.
+ * @param pos Scintilla position in the document at which the goto request
+ * was invoked (typically corresponds to the caret position or the position
+ * where the corresponding mouse event happened).
+ * @param definition If @c TRUE, this is the go to definition request, if
+ * @c FALSE, this is the goto declaration request.
+ * @param data User data passed during the @c plugin_extension_register()
+ * call.
+ *
+ * @return Plugins should return @c TRUE if the goto was performed, @c FALSE
+ * otherwise. Plugins that work asynchronously and do not know the result
+ * at the moment this function is called should return @c TRUE. However, such
+ * plugins can perform some synchronous pre-check such as whether there is
+ * an identifier at the caret position and if the necessary conditions are
+ * not satisfied and the plugins know that goto cannot be performed, they
+ * should return @c FALSE.
+ *
+ * @since 2.1
+ **/
+ gboolean (*goto_perform)(GeanyDocument *doc, gint pos, gboolean definition, gpointer data);
+
+ /**
+ * Pointer to function called by Geany to check whether the plugin implements
+ * additional symbol (e.g. type) highlighting in Scintilla.
+ *
+ * @see @c autocomplete_provided() for more details.
+ * @note There is no function in the @c PluginExtension structure informing
+ * plugins to perform symbol highlighting. Plugins
+ * implementing symbol highlighting should perform it at the appropriate
+ * moments based on Scintilla and Geany events such as when the document
+ * becomes visible or when the document is modified.
+ *
+ * @since 2.1
+ **/
+ gboolean (*symbol_highlight_provided)(GeanyDocument *doc, gpointer data);
+
+ /* Padding serving for possible future extensions of this API. */
+ void (*_dummy[100])(void);
+} PluginExtension;
+
+
+void plugin_extension_register(PluginExtension *extension, const gchar *ext_name,
+ gint priority, gpointer data);
+void plugin_extension_unregister(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_symbol_highlight_provided(GeanyDocument *doc, PluginExtension *ext);
+
+#ifdef GEANY_PRIVATE
+
+void plugin_extension_autocomplete_perform(GeanyDocument *doc, gboolean force);
+void plugin_extension_calltips_show(GeanyDocument *doc, gboolean force);
+gboolean plugin_extension_goto_perform(GeanyDocument *doc, gint pos, gboolean definition);
+
+#endif /* GEANY_PRIVATE */
+
+G_END_DECLS
+
+#endif /* GEANY_PLUGIN_EXTENSION_H */
Modified: src/symbols.c
41 lines changed, 32 insertions(+), 9 deletions(-)
===================================================================
@@ -47,6 +47,7 @@
#include "highlighting.h"
#include "main.h"
#include "navqueue.h"
+#include "pluginextension.h"
#include "sciwrappers.h"
#include "sidebar.h"
#include "support.h"
@@ -1710,19 +1711,41 @@ static gboolean goto_tag(const gchar *name, gboolean definition)
}
-gboolean symbols_goto_tag(const gchar *name, gboolean definition)
+gboolean symbols_goto_tag(GeanyDocument *doc, gint pos, gboolean definition)
{
- if (goto_tag(name, definition))
- return TRUE;
+ gchar *name;
+ gboolean success;
- /* if we are here, there was no match and we are beeping ;-) */
- utils_beep();
+ if (plugin_extension_goto_provided(doc, NULL))
+ return plugin_extension_goto_perform(doc, pos, definition);
- if (!definition)
- ui_set_statusbar(FALSE, _("Forward declaration \"%s\" not found."), name);
+ /* get the current selection if any, or current word */
+ if (sci_has_selection(doc->editor->sci))
+ name = sci_get_selection_contents(doc->editor->sci);
else
- ui_set_statusbar(FALSE, _("Definition of \"%s\" not found."), name);
- return FALSE;
+ {
+ editor_find_current_word(doc->editor, pos,
+ editor_info.current_word, GEANY_MAX_WORD_LENGTH, NULL);
+ name = *editor_info.current_word ? g_strdup(editor_info.current_word) : NULL;
+ }
+
+ if (!name)
+ return FALSE;
+
+ if (! (success = goto_tag(name, definition)))
+ {
+ /* if we are here, there was no match and we are beeping ;-) */
+ utils_beep();
+
+ if (!definition)
+ ui_set_statusbar(FALSE, _("Forward declaration \"%s\" not found."), name);
+ else
+ ui_set_statusbar(FALSE, _("Definition of \"%s\" not found."), name);
+ }
+
+ g_free(name);
+
+ return success;
}
Modified: src/symbols.h
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -58,7 +58,7 @@ gint symbols_generate_global_tags(gint argc, gchar **argv, gboolean want_preproc
void symbols_show_load_tags_dialog(void);
-gboolean symbols_goto_tag(const gchar *name, gboolean definition);
+gboolean symbols_goto_tag(GeanyDocument *doc, gint pos, gboolean definition);
gint symbols_get_current_function(GeanyDocument *doc, const gchar **tagname);
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
Branch: refs/heads/master
Author: Jiří Techet <techet(a)gmail.com>
Committer: Jiří Techet <techet(a)gmail.com>
Date: Tue, 02 Jul 2024 09:08:05 UTC
Commit: e5ee2227807c50604176b73119f9d8c90c6b0e02
https://github.com/geany/geany/commit/e5ee2227807c50604176b73119f9d8c90c6b0…
Log Message:
-----------
Fix runtime warning when opening Geany without open files
When Geany is opened with no open document, the following warning
appears:
```
(geany:1735619): Geany-CRITICAL **: 10:49:58.729: document_get_notebook_child: assertion 'doc != NULL' failed
```
The corresponding stack trace when using `--g-fatal-warnings` is:
```
#5 0x0000fffff7c232c8 in document_get_notebook_child (doc=<optimized out>)
at document.c:269
#6 0x0000fffff7c24140 in document_get_notebook_page (doc=<optimized out>)
at document.c:291
#7 0x0000fffff7c25198 in document_show_tab (doc=0x0) at document.c:1269
#8 0x0000fffff7c4b24c in configuration_open_files
(session_files=<optimized out>) at keyfile.c:1390
#9 0x0000fffff7c4b354 in configuration_open_default_session ()
at keyfile.c:1404
#10 0x0000fffff7c4dfb0 in load_startup_files (argv=0xffffffffeef8, argc=1)
at libmain.c:987
```
This patch fixes that.
Modified Paths:
--------------
src/document.c
Modified: src/document.c
3 lines changed, 3 insertions(+), 0 deletions(-)
===================================================================
@@ -1266,6 +1266,9 @@ void document_apply_indent_settings(GeanyDocument *doc)
void document_show_tab(GeanyDocument *doc)
{
+ if (!doc)
+ return;
+
gtk_notebook_set_current_page(GTK_NOTEBOOK(main_widgets.notebook),
document_get_notebook_page(doc));
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
Branch: refs/heads/master
Author: Jiří Techet <techet(a)gmail.com>
Committer: Jiří Techet <techet(a)gmail.com>
Date: Sun, 07 Jul 2024 08:31:22 UTC
Commit: 8056d620bc067d153197ae5b4b5fae00163c711a
https://github.com/geany/geany/commit/8056d620bc067d153197ae5b4b5fae00163c7…
Log Message:
-----------
Fix open document check in document_get_from_page()
The check `page_num >= documents_array->len` isn't correct as
`documents_array->len` isn't the number of open documents. This array
never shrinks and closed documents are only marked as invalid.
Without this patch, when closing a Geany project and the default session
doesn't contain any document, the following warning appears:
```
(geany:1740553): Geany-CRITICAL **: 11:02:39.105: document_get_from_notebook_child: assertion 'GTK_IS_BOX(page)' failed
```
The corresponding stack trace when using `--g-fatal-warnings` is:
```
#5 0x0000fffff7c241d0 in document_get_from_notebook_child
(page=<optimized out>) at document.c:335
#6 0x0000fffff7c242a4 in document_get_from_page (page_num=<optimized out>)
at document.c:361
#7 0x0000fffff7c4b248 in configuration_open_files
(session_files=<optimized out>) at keyfile.c:1390
#8 0x0000fffff7c4b354 in configuration_open_default_session ()
at keyfile.c:1404
#9 0x0000fffff7c5e430 in destroy_project (open_default=1) at project.c:480
#10 0x0000fffff7c5e4d8 in project_close (open_default=1) at project.c:435
```
This patch fixes that.
Modified Paths:
--------------
src/document.c
Modified: src/document.c
3 lines changed, 2 insertions(+), 1 deletions(-)
===================================================================
@@ -351,9 +351,10 @@ GeanyDocument *document_get_from_notebook_child(GtkWidget *page)
GEANY_API_SYMBOL
GeanyDocument *document_get_from_page(guint page_num)
{
+ gint pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
GtkWidget *parent;
- if (page_num >= documents_array->len)
+ if (page_num >= pages)
return NULL;
parent = gtk_notebook_get_nth_page(GTK_NOTEBOOK(main_widgets.notebook), page_num);
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).