Skip to content

Commit

Permalink
Merge branch 'geany:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex313031 committed Aug 6, 2024
2 parents becd4f3 + 473c058 commit 41a9e7d
Show file tree
Hide file tree
Showing 16 changed files with 838 additions and 54 deletions.
143 changes: 143 additions & 0 deletions doc/plugins.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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@example.org>";

plugin->funcs->init = init_func;
plugin->funcs->cleanup = cleanup_func;
plugin->funcs->callbacks = plugin_callbacks;

GEANY_PLUGIN_REGISTER(plugin, 248);
}
@endcode

*/
3 changes: 3 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
5 changes: 5 additions & 0 deletions plugins/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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"\"
Expand All @@ -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)
Expand Down
130 changes: 130 additions & 0 deletions plugins/demopluginext.c
Original file line number Diff line number Diff line change
@@ -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@example.org>";

plugin->funcs->init = init_func;
plugin->funcs->cleanup = cleanup_func;
plugin->funcs->callbacks = plugin_callbacks;

GEANY_PLUGIN_REGISTER(plugin, 248);
}
1 change: 1 addition & 0 deletions plugins/geanyplugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions plugins/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ plugin_inc += iscintilla

plugins = [
'Demoplugin',
'Demopluginext',
'Demoproxy',
'Classbuilder',
'HTMLChars',
Expand Down
Loading

0 comments on commit 41a9e7d

Please sign in to comment.