From bc9f2db62842bd7a8d9920da34ac1e5593f49e0e Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Tue, 13 Oct 2015 23:25:33 +0200 Subject: [PATCH 1/5] Convert to a proxy plugin This allows to drop the custom loader and plugin manager and instead make use of Geany's new proxy plugin feature, where python plugins are embedded into the standard plugin manager as first class citizen. Existing plugins continue to run with one exception: The help and configure methods have been renamed, and in case of configure the samantics have changed accordingly to Geany's unified configure dialog for all plugins. So existing scripts that used either show_help() or show_configure() need to to the following: 1) rename show_help() to help() 2a) rename show_configure to configure() 2b) change configure to just return the content widget and remove the creation of a custom dialog The plugin script dir is now $geanylibdir/geany, the same as for native plugns. Geany loooks only there and doesn't make a difference between native and sub-plugins. --- geanypy/geany/Makefile.am | 2 - geanypy/geany/__init__.py | 2 - geanypy/geany/loader.py | 172 -------------- geanypy/geany/manager.py | 179 --------------- geanypy/geany/plugin.py | 1 - geanypy/plugins/Makefile.am | 2 +- geanypy/plugins/console.py | 16 +- geanypy/src/Makefile.am | 2 +- geanypy/src/geanypy-plugin.c | 337 +++++++++++++++++----------- geanypy/src/geanypy-plugin.h | 5 +- geanypy/src/geanypy-signalmanager.c | 1 + 11 files changed, 218 insertions(+), 501 deletions(-) delete mode 100644 geanypy/geany/loader.py delete mode 100644 geanypy/geany/manager.py diff --git a/geanypy/geany/Makefile.am b/geanypy/geany/Makefile.am index 880c5f662..62bac20df 100644 --- a/geanypy/geany/Makefile.am +++ b/geanypy/geany/Makefile.am @@ -1,7 +1,5 @@ geanypy_sources = __init__.py \ console.py \ - manager.py \ - loader.py \ plugin.py \ signalmanager.py geanypy_objects = $(geanypy_sources:.py=.pyc) diff --git a/geanypy/geany/__init__.py b/geanypy/geany/__init__.py index 493dfa4d8..264d6eee1 100644 --- a/geanypy/geany/__init__.py +++ b/geanypy/geany/__init__.py @@ -15,9 +15,7 @@ import encoding import filetypes import highlighting -import loader import main -import manager import msgwindow import navqueue import prefs diff --git a/geanypy/geany/loader.py b/geanypy/geany/loader.py deleted file mode 100644 index 14b9b73d6..000000000 --- a/geanypy/geany/loader.py +++ /dev/null @@ -1,172 +0,0 @@ -import os -import imp -from collections import namedtuple -import geany - -PluginInfo = namedtuple('PluginInfo', 'filename, name, version, description, author, cls') - - -class PluginLoader(object): - - plugins = {} - - def __init__(self, plugin_dirs): - - self.plugin_dirs = plugin_dirs - - self.available_plugins = [] - for plugin in self.iter_plugin_info(): - self.available_plugins.append(plugin) - - self.restore_loaded_plugins() - - - def update_loaded_plugins_file(self): - for path in self.plugin_dirs: - if os.path.isdir(path): - try: - state_file = os.path.join(path, '.loaded_plugins') - with open(state_file, 'w') as f: - for plugfn in self.plugins: - f.write("%s\n" % plugfn) - except IOError as err: - if err.errno == 13: #perms - pass - else: - raise - - - def restore_loaded_plugins(self): - loaded_plugins = [] - for path in reversed(self.plugin_dirs): - state_file = os.path.join(path, ".loaded_plugins") - if os.path.exists(state_file): - for line in open(state_file): - line = line.strip() - if line not in loaded_plugins: - loaded_plugins.append(line) - for filename in loaded_plugins: - self.load_plugin(filename) - - - def load_all_plugins(self): - - for plugin_info in self.iter_plugin_info(): - if plugin_filename.endswith('test.py'): # hack for testing - continue - plug = self.load_plugin(plugin_info.filename) - if plug: - print("Loaded plugin: %s" % plugin_info.filename) - print(" Name: %s v%s" % (plug.name, plug.version)) - print(" Desc: %s" % plug.description) - print(" Author: %s" % plug.author) - - - def unload_all_plugins(self): - - for plugin in self.plugins: - self.unload_plugin(plugin) - - - def reload_all_plugins(self): - - self.unload_all_plugins() - self.load_all_plugins() - - - def iter_plugin_info(self): - - for d in self.plugin_dirs: - if os.path.isdir(d): - for current_file in os.listdir(d): - #check inside folders inside the plugins dir so we can load .py files here as plugins - current_path=os.path.abspath(os.path.join(d, current_file)) - if os.path.isdir(current_path): - for plugin_folder_file in os.listdir(current_path): - if plugin_folder_file.endswith('.py'): - #loop around results if its fails to load will never reach yield - for p in self.load_plugin_info(current_path,plugin_folder_file): - yield p - - #not a sub directory so if it ends with .py lets just attempt to load it as a plugin - if current_file.endswith('.py'): - #loop around results if its fails to load will never reach yield - for p in self.load_plugin_info(d,current_file): - yield p - - def load_plugin_info(self,d,f): - filename = os.path.abspath(os.path.join(d, f)) - if filename.endswith("test.py"): - pass - text = open(filename).read() - module_name = os.path.basename(filename)[:-3] - try: - module = imp.load_source(module_name, filename) - except ImportError as exc: - print "Error: failed to import settings module ({})".format(exc) - module=None - if module: - for k, v in module.__dict__.iteritems(): - if k == geany.Plugin.__name__: - continue - try: - if issubclass(v, geany.Plugin): - inf = PluginInfo( - filename, - getattr(v, '__plugin_name__'), - getattr(v, '__plugin_version__', ''), - getattr(v, '__plugin_description__', ''), - getattr(v, '__plugin_author__', ''), - v) - yield inf - - except TypeError: - continue - - - def load_plugin(self, filename): - - for avail in self.available_plugins: - if avail.filename == filename: - inst = avail.cls() - self.plugins[filename] = inst - self.update_loaded_plugins_file() - geany.ui_utils.set_statusbar('GeanyPy: plugin activated: %s' % - inst.name, True) - return inst - - - def unload_plugin(self, filename): - - try: - plugin = self.plugins[filename] - name = plugin.name - plugin.cleanup() - del self.plugins[filename] - self.update_loaded_plugins_file() - geany.ui_utils.set_statusbar('GeanyPy: plugin deactivated: %s' % - name, True) - except KeyError: - print("Unable to unload plugin '%s': it's not loaded" % filename) - - - def reload_plugin(self, filename): - - if filename in self.plugins: - self.unload_plugin(filename) - self.load_plugin(filename) - - - def plugin_has_help(self, filename): - - for plugin_info in self.iter_plugin_info(): - if plugin_info.filename == filename: - return hasattr(plugin_info.cls, 'show_help') - - - def plugin_has_configure(self, filename): - - try: - return hasattr(self.plugins[filename], 'show_configure') - except KeyError: - return None diff --git a/geanypy/geany/manager.py b/geanypy/geany/manager.py deleted file mode 100644 index 9c7e9a478..000000000 --- a/geanypy/geany/manager.py +++ /dev/null @@ -1,179 +0,0 @@ -import gtk -import gobject -import glib -from htmlentitydefs import name2codepoint -from loader import PluginLoader - - -class PluginManager(gtk.Dialog): - - def __init__(self, plugin_dirs=[]): - gtk.Dialog.__init__(self, title="Plugin Manager") - self.loader = PluginLoader(plugin_dirs) - - self.set_default_size(400, 450) - self.set_has_separator(True) - icon = self.render_icon(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) - self.set_icon(icon) - - self.connect("response", lambda w,d: self.hide()) - - vbox = gtk.VBox(False, 12) - vbox.set_border_width(12) - - lbl = gtk.Label("Choose plugins to load or unload:") - lbl.set_alignment(0.0, 0.5) - vbox.pack_start(lbl, False, False, 0) - - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - vbox.pack_start(sw, True, True, 0) - - self.treeview = gtk.TreeView() - sw.add(self.treeview) - - vbox.show_all() - - self.get_content_area().add(vbox) - - action_area = self.get_action_area() - action_area.set_spacing(0) - action_area.set_homogeneous(False) - - btn = gtk.Button(stock=gtk.STOCK_CLOSE) - btn.set_border_width(6) - btn.connect("clicked", lambda x: self.response(gtk.RESPONSE_CLOSE)) - action_area.pack_start(btn, False, True, 0) - btn.show() - - self.btn_help = gtk.Button(stock=gtk.STOCK_HELP) - self.btn_help.set_border_width(6) - self.btn_help.set_no_show_all(True) - action_area.pack_start(self.btn_help, False, True, 0) - action_area.set_child_secondary(self.btn_help, True) - - self.btn_prefs = gtk.Button(stock=gtk.STOCK_PREFERENCES) - self.btn_prefs.set_border_width(6) - self.btn_prefs.set_no_show_all(True) - action_area.pack_start(self.btn_prefs, False, True, 0) - action_area.set_child_secondary(self.btn_prefs, True) - - action_area.show() - - self.load_plugins_list() - - - def on_help_button_clicked(self, button, treeview, model): - path = treeview.get_cursor()[0] - iter = model.get_iter(path) - filename = model.get_value(iter, 2) - for plugin in self.loader.available_plugins: - if plugin.filename == filename: - plugin.cls.show_help() - break - else: - print("Plugin does not support help function") - - - def on_preferences_button_clicked(self, button, treeview, model): - path = treeview.get_cursor()[0] - iter = model.get_iter(path) - filename = model.get_value(iter, 2) - try: - self.loader.plugins[filename].show_configure() - except KeyError: - print("Plugin is not loaded, can't run configure function") - - - def activate_plugin(self, filename): - self.loader.load_plugin(filename) - - - def deactivate_plugin(self, filename): - self.loader.unload_plugin(filename) - - - def load_plugins_list(self): - liststore = gtk.ListStore(gobject.TYPE_BOOLEAN, str, str) - - self.btn_help.connect("clicked", - self.on_help_button_clicked, self.treeview, liststore) - - self.btn_prefs.connect("clicked", - self.on_preferences_button_clicked, self.treeview, liststore) - - self.treeview.set_model(liststore) - self.treeview.set_headers_visible(False) - self.treeview.set_grid_lines(True) - - check_renderer = gtk.CellRendererToggle() - check_renderer.set_radio(False) - check_renderer.connect('toggled', self.on_plugin_load_toggled, liststore) - text_renderer = gtk.CellRendererText() - - check_column = gtk.TreeViewColumn(None, check_renderer, active=0) - text_column = gtk.TreeViewColumn(None, text_renderer, markup=1) - - self.treeview.append_column(check_column) - self.treeview.append_column(text_column) - - self.treeview.connect('row-activated', - self.on_row_activated, check_renderer, liststore) - self.treeview.connect('cursor-changed', - self.on_selected_plugin_changed, liststore) - - self.load_sorted_plugins_info(liststore) - - - def load_sorted_plugins_info(self, list_store): - - plugin_info_list = list(self.loader.iter_plugin_info()) - #plugin_info_list.sort(key=lambda pi: pi[1]) - - for plugin_info in plugin_info_list: - - lbl = str('%s %s\n%s\n' + - 'Author: %s\n' + - 'Filename: %s') % ( - glib.markup_escape_text(plugin_info.name), - glib.markup_escape_text(plugin_info.version), - glib.markup_escape_text(plugin_info.description), - glib.markup_escape_text(plugin_info.author), - glib.markup_escape_text(plugin_info.filename)) - - loaded = plugin_info.filename in self.loader.plugins - - list_store.append([loaded, lbl, plugin_info.filename]) - - - def on_selected_plugin_changed(self, treeview, model): - - path = treeview.get_cursor()[0] - iter = model.get_iter(path) - filename = model.get_value(iter, 2) - active = model.get_value(iter, 0) - - if self.loader.plugin_has_configure(filename): - self.btn_prefs.set_visible(True) - else: - self.btn_prefs.set_visible(False) - - if self.loader.plugin_has_help(filename): - self.btn_help.set_visible(True) - else: - self.btn_help.set_visible(False) - - - def on_plugin_load_toggled(self, cell, path, model): - active = not cell.get_active() - iter = model.get_iter(path) - model.set_value(iter, 0, active) - if active: - self.activate_plugin(model.get_value(iter, 2)) - else: - self.deactivate_plugin(model.get_value(iter, 2)) - - - def on_row_activated(self, tvw, path, view_col, cell, model): - self.on_plugin_load_toggled(cell, path, model) diff --git a/geanypy/geany/plugin.py b/geanypy/geany/plugin.py index f8adef6be..8a9a2afce 100644 --- a/geanypy/geany/plugin.py +++ b/geanypy/geany/plugin.py @@ -49,7 +49,6 @@ class Plugin(object): #__plugin_version__ = None #__plugin_author__ = None - _events = { "document-open": [], # TODO: add more events here diff --git a/geanypy/plugins/Makefile.am b/geanypy/plugins/Makefile.am index 6aa07ff22..3cb662de8 100644 --- a/geanypy/plugins/Makefile.am +++ b/geanypy/plugins/Makefile.am @@ -1,4 +1,4 @@ geanypy_plugins = demo.py hello.py console.py -geanypydir = $(datadir)/geany/geanypy/plugins +geanypydir = $(libdir)/geany geanypy_DATA = $(geanypy_plugins) EXTRA_DIST = $(geanypy_plugins) diff --git a/geanypy/plugins/console.py b/geanypy/plugins/console.py index ee26ae35b..df5180e7e 100644 --- a/geanypy/plugins/console.py +++ b/geanypy/plugins/console.py @@ -191,16 +191,7 @@ def on_bg_color_changed(self, clr_btn, data=None): self.bg = clr_btn.get_color().to_string() - def show_configure(self): - dialog = gtk.Dialog("Configure Python Console", - geany.main_widgets.window, - gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT)) - - dialog.set_has_separator(True) - - content_area = dialog.get_content_area() - content_area.set_border_width(6) + def configure(self, dialog): vbox = gtk.VBox(spacing=6) vbox.set_border_width(6) @@ -306,8 +297,5 @@ def show_configure(self): vbox.pack_start(fra_general, True, True, 0) vbox.pack_start(fra_appearances, False, True, 0) - content_area.pack_start(vbox, True, True, 0) - content_area.show_all() - dialog.run() - dialog.destroy() + return vbox diff --git a/geanypy/src/Makefile.am b/geanypy/src/Makefile.am index e7135f2f0..a3fc96184 100644 --- a/geanypy/src/Makefile.am +++ b/geanypy/src/Makefile.am @@ -6,7 +6,7 @@ geanyplugindir = $(libdir)/geany geanypy_la_LDFLAGS = -module -avoid-version -Wl,--export-dynamic geanypy_la_CPPFLAGS = @GEANY_CFLAGS@ @PYGTK_CFLAGS@ @PYTHON_CPPFLAGS@ \ -DGEANYPY_PYTHON_DIR="\"$(libdir)/geany/geanypy\"" \ - -DGEANYPY_PLUGIN_DIR="\"$(datadir)/geany/geanypy/plugins\"" \ + -DGEANYPY_PLUGIN_DIR="\"$(libdir)/geany\"" \ -DG_LOG_DOMAIN=\"GeanyPy\" geanypy_la_CFLAGS = @GEANYPY_CFLAGS@ @GMODULE_CFLAGS@ geanypy_la_LIBADD = @GEANY_LIBS@ @PYGTK_LIBS@ \ diff --git a/geanypy/src/geanypy-plugin.c b/geanypy/src/geanypy-plugin.c index 338067c6a..3a0629403 100644 --- a/geanypy/src/geanypy-plugin.c +++ b/geanypy/src/geanypy-plugin.c @@ -27,25 +27,10 @@ #include "geanypy.h" -G_MODULE_EXPORT GeanyPlugin *geany_plugin; -G_MODULE_EXPORT GeanyData *geany_data; -G_MODULE_EXPORT GeanyFunctions *geany_functions; - - -G_MODULE_EXPORT PLUGIN_VERSION_CHECK(211) - -G_MODULE_EXPORT PLUGIN_SET_INFO( - _("GeanyPy"), - _("Python plugins support"), - "1.0", - "Matthew Brush ") - - -static GtkWidget *loader_item = NULL; -static PyObject *manager = NULL; -static gchar *plugin_dir = NULL; -static SignalManager *signal_manager = NULL; +#include +#include +GeanyData *geany_data; /* Forward declarations to prevent compiler warnings. */ PyMODINIT_FUNC initapp(void); @@ -131,7 +116,9 @@ GeanyPy_start_interpreter(void) "import os, sys\n" "path = '%s'.replace('~', os.path.expanduser('~'))\n" "sys.path.append(path)\n" - "import geany\n", py_dir); + "path = '%s'.replace('~', os.path.expanduser('~'))\n" + "sys.path.append(path)\n" + "import geany\n", py_dir, GEANYPY_PLUGIN_DIR); g_free(py_dir); PyRun_SimpleString(init_code); @@ -146,141 +133,241 @@ GeanyPy_stop_interpreter(void) Py_Finalize(); } +typedef struct +{ + PyObject *base; + SignalManager *signal_manager; +} +GeanyPyData; -static void -GeanyPy_init_manager(const gchar *dir) +typedef struct { - PyObject *module, *man, *args; - gchar *sys_plugin_dir = NULL; + PyObject *class; + PyObject *module; + PyObject *instance; +} +GeanyPyPluginData; - g_return_if_fail(dir != NULL); +static gboolean has_error(void) +{ + if (PyErr_Occurred()) + { + PyErr_Print(); + return TRUE; + } + return FALSE; +} - module = PyImport_ImportModule("geany.manager"); - if (module == NULL) - { - g_warning(_("Failed to import manager module")); - return; - } +static gboolean geanypy_proxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + GeanyPyPluginData *data = (GeanyPyPluginData *) pdata; - man = PyObject_GetAttrString(module, "PluginManager"); - Py_DECREF(module); + data->instance = PyObject_CallObject(data->class, NULL); + if (has_error()) + return FALSE; - if (man == NULL) - { - g_warning(_("Failed to retrieve PluginManager from manager module")); - return; - } + return TRUE; +} -#ifdef GEANYPY_WINDOWS - { /* Detect the system plugin's dir at runtime on Windows since we - * don't really know where Geany is installed. */ - gchar *geany_base_dir; - geany_base_dir = g_win32_get_package_installation_directory_of_module(NULL); - if (geany_base_dir) - { - sys_plugin_dir = g_build_filename(geany_base_dir, "lib", "geanypy", "plugins", NULL); - g_free(geany_base_dir); - } - if (!g_file_test(sys_plugin_dir, G_FILE_TEST_EXISTS)) - { - g_warning(_("System plugin directory not found.")); - g_free(sys_plugin_dir); - sys_plugin_dir = NULL; - } - } -#else - sys_plugin_dir = g_strdup(GEANYPY_PLUGIN_DIR); -#endif +static void geanypy_proxy_cleanup(GeanyPlugin *plugin, gpointer pdata) +{ + GeanyPyPluginData *data = (GeanyPyPluginData *) pdata; - g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "User plugins: %s", dir); + PyObject_CallMethod(data->instance, "cleanup", NULL); + if (has_error()) + return; +} - if (sys_plugin_dir) + +static GtkWidget *geanypy_proxy_configure(GeanyPlugin *plugin, GtkDialog *parent, gpointer pdata) +{ + GeanyPyPluginData *data = (GeanyPyPluginData *) pdata; + PyObject *o, *oparent; + GObject *widget; + + oparent = pygobject_new(G_OBJECT(parent)); + o = PyObject_CallMethod(data->instance, "configure", "O", oparent, NULL); + Py_DECREF(oparent); + + if (!has_error() && o != Py_None) { - g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "System plugins: %s", sys_plugin_dir); - args = Py_BuildValue("([s, s])", sys_plugin_dir, dir); - g_free(sys_plugin_dir); + /* Geany wants only the underlying GtkWidget, we must only ref that + * and free the pygobject wrapper */ + widget = g_object_ref(pygobject_get(o)); + Py_DECREF(o); + return GTK_WIDGET(widget); } - else - args = Py_BuildValue("([s])", dir); - manager = PyObject_CallObject(man, args); - if (PyErr_Occurred()) - PyErr_Print(); - Py_DECREF(man); - Py_DECREF(args); - - if (manager == NULL) - { - g_warning(_("Unable to instantiate new PluginManager")); - return; - } + Py_DECREF(o); /* Must unref even if it's Py_None */ + return NULL; +} + +static void geanypy_proxy_help(GeanyPlugin *plugin, gpointer pdata) +{ + GeanyPyPluginData *data = (GeanyPyPluginData *) pdata; + + PyObject_CallMethod(data->instance, "help", NULL); + if (has_error()) + return; } +static gint +geanypy_probe(GeanyPlugin *proxy, const gchar *filename, gpointer pdata) +{ + gchar *file_plugin = g_strdup_printf("%.*s.plugin", + (int)(strrchr(filename, '.') - filename), filename); + gint ret = PROXY_IGNORED; -static void -GeanyPy_show_manager(void) + /* avoid clash with libpeas py plugins, those come with a corresponding .plugin file */ + if (!g_file_test(file_plugin, G_FILE_TEST_EXISTS)) + ret = PROXY_MATCHED; + + g_free(file_plugin); + return ret; +} + + +static const gchar *string_from_attr(PyObject *o, const gchar *attr) { - PyObject *show_method; - - g_return_if_fail(manager != NULL); - - show_method = PyObject_GetAttrString(manager, "show_all"); - if (show_method == NULL) - { - g_warning(_("Unable to get show_all() method on plugin manager")); - return; - } - PyObject_CallObject(show_method, NULL); - Py_DECREF(show_method); + PyObject *string = PyObject_GetAttrString(o, attr); + const gchar *ret = PyString_AsString(string); + Py_DECREF(string); + + return ret; +} + + +static gpointer +geanypy_load(GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, gpointer pdata) +{ + GeanyPyData *data = pdata; + PyObject *fromlist, *module, *dict, *key, *val, *found = NULL; + Py_ssize_t pos = 0; + gchar *modulename, *dot; + gpointer ret = NULL; + + modulename = g_path_get_basename(filename); + /* We are guaranteed that filename has a .py extension + * because we did geany_plugin_register_proxy() for it */ + dot = strrchr(modulename, '.'); + *dot = '\0'; + /* we need a fromlist to be able to import modules with a '.' in the + * name. -- libpeas */ + fromlist = PyTuple_New (0); + + module = PyImport_ImportModuleEx(modulename, NULL, NULL, fromlist); + if (has_error() || !module) + goto err; + + dict = PyModule_GetDict(module); + + while (PyDict_Next (dict, &pos, &key, &val) && found == NULL) + { + if (PyType_Check(val) && PyObject_IsSubclass(val, data->base)) + found = val; + } + + if (found) + { + GeanyPyPluginData *pdata = g_slice_new(GeanyPyPluginData); + PluginInfo *info = subplugin->info; + GeanyPluginFuncs *funcs = subplugin->funcs; + Py_INCREF(found); + pdata->module = module; + pdata->class = found; + pdata->instance = NULL; + info->name = string_from_attr(pdata->class, "__plugin_name__"); + info->description = string_from_attr(pdata->class, "__plugin_description__"); + info->version = string_from_attr(pdata->class, "__plugin_version__"); + info->author = string_from_attr(pdata->class, "__plugin_author__"); + funcs->init = geanypy_proxy_init; + funcs->cleanup = geanypy_proxy_cleanup; + if (PyObject_HasAttrString(found, "configure")) + funcs->configure = geanypy_proxy_configure; + if (PyObject_HasAttrString(found, "help")) + funcs->help = geanypy_proxy_help; + if (GEANY_PLUGIN_REGISTER_FULL(subplugin, 224, pdata, NULL)) + ret = pdata; + } + +err: + g_free(modulename); + Py_DECREF(fromlist); + return ret; } static void -on_python_plugin_loader_activate(GtkMenuItem *item, gpointer user_data) +geanypy_unload(GeanyPlugin *plugin, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata_) { - GeanyPy_show_manager(); + GeanyPyPluginData *pdata = load_data; + + Py_XDECREF(pdata->instance); + Py_DECREF(pdata->class); + Py_DECREF(pdata->module); + while (PyGC_Collect()); + g_slice_free(GeanyPyPluginData, pdata); } -G_MODULE_EXPORT void -plugin_init(GeanyData *data) +static gboolean geanypy_init(GeanyPlugin *plugin_, gpointer pdata) { - GeanyPy_start_interpreter(); - signal_manager = signal_manager_new(geany_plugin); - - plugin_dir = g_build_filename(geany->app->configdir, - "plugins", "geanypy", "plugins", NULL); - - if (!g_file_test(plugin_dir, G_FILE_TEST_IS_DIR)) - { - if (g_mkdir_with_parents(plugin_dir, 0755) == -1) - { - g_warning(_("Unable to create Python plugins directory: %s: %s"), - plugin_dir, - strerror(errno)); - g_free(plugin_dir); - plugin_dir = NULL; - } - } - - if (plugin_dir != NULL) - GeanyPy_init_manager(plugin_dir); - - loader_item = gtk_menu_item_new_with_label(_("Python Plugin Manager")); - gtk_widget_set_sensitive(loader_item, plugin_dir != NULL); - gtk_menu_append(GTK_MENU(geany->main_widgets->tools_menu), loader_item); - gtk_widget_show(loader_item); - g_signal_connect(loader_item, "activate", - G_CALLBACK(on_python_plugin_loader_activate), NULL); + const gchar *exts[] = { "py", NULL }; + GeanyPyData *state = pdata; + PyObject *module; + + plugin_->proxy_funcs->probe = geanypy_probe; + plugin_->proxy_funcs->load = geanypy_load; + plugin_->proxy_funcs->unload = geanypy_unload; + + geany_data = plugin_->geany_data; + + GeanyPy_start_interpreter(); + state->signal_manager = signal_manager_new(plugin_); + + module = PyImport_ImportModule("geany.plugin"); + if (has_error() || !module) + goto err; + + state->base = PyObject_GetAttrString(module, "Plugin"); + Py_DECREF(module); + if (has_error() || !state->base) + goto err; + + if (!geany_plugin_register_proxy(plugin_, exts)) { + Py_DECREF(state->base); + goto err; + } + + return TRUE; + +err: + signal_manager_free(state->signal_manager); + GeanyPy_stop_interpreter(); + return FALSE; } -G_MODULE_EXPORT void plugin_cleanup(void) +static void geanypy_cleanup(GeanyPlugin *plugin, gpointer pdata) { - signal_manager_free(signal_manager); - Py_XDECREF(manager); + GeanyPyData *state = pdata; + signal_manager_free(state->signal_manager); + Py_DECREF(state->base); GeanyPy_stop_interpreter(); - gtk_widget_destroy(loader_item); - g_free(plugin_dir); +} + +G_MODULE_EXPORT void +geany_load_module(GeanyPlugin *plugin) +{ + GeanyPyData *state = g_new0(GeanyPyData, 1); + + plugin->info->name = _("GeanyPy"); + plugin->info->description = _("Python plugins support"); + plugin->info->version = "1.0"; + plugin->info->author = "Matthew Brush "; + plugin->funcs->init = geanypy_init; + plugin->funcs->cleanup = geanypy_cleanup; + + GEANY_PLUGIN_REGISTER_FULL(plugin, 226, state, g_free); } diff --git a/geanypy/src/geanypy-plugin.h b/geanypy/src/geanypy-plugin.h index 5457404d8..c97bd9606 100644 --- a/geanypy/src/geanypy-plugin.h +++ b/geanypy/src/geanypy-plugin.h @@ -26,10 +26,7 @@ extern "C" { #endif - -extern GeanyPlugin *geany_plugin; -extern GeanyData *geany_data; -extern GeanyFunctions *geany_functions; +extern GeanyData *geany_data; #ifndef PyMODINIT_FUNC diff --git a/geanypy/src/geanypy-signalmanager.c b/geanypy/src/geanypy-signalmanager.c index 0f7fd5ee7..8682da816 100644 --- a/geanypy/src/geanypy-signalmanager.c +++ b/geanypy/src/geanypy-signalmanager.c @@ -88,6 +88,7 @@ GObject *signal_manager_get_gobject(SignalManager *signal_manager) static void signal_manager_connect_signals(SignalManager *man) { + GeanyPlugin *geany_plugin = man->geany_plugin; plugin_signal_connect(geany_plugin, NULL, "build-start", TRUE, G_CALLBACK(on_build_start), man); plugin_signal_connect(geany_plugin, NULL, "document-activate", TRUE, G_CALLBACK(on_document_activate), man); plugin_signal_connect(geany_plugin, NULL, "document-before-save", TRUE, G_CALLBACK(on_document_before_save), man); From 1b0b25fc5b20bb0e8eaf27e5448c235373f5d36b Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Mon, 16 Nov 2015 10:08:37 +0100 Subject: [PATCH 2/5] Store per-plugin GeanyPlugin pointer This is required for some APIs, e.g. keybindings, and is made possible trough the proxy plugin conversion, since now Geany actually creates GeanyPlugin instances for Geanypy plugins. --- geanypy/src/geanypy-plugin.c | 2 ++ geanypy/src/geanypy-plugin.h | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/geanypy/src/geanypy-plugin.c b/geanypy/src/geanypy-plugin.c index 3a0629403..e5c9c0786 100644 --- a/geanypy/src/geanypy-plugin.c +++ b/geanypy/src/geanypy-plugin.c @@ -273,9 +273,11 @@ geanypy_load(GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, GeanyPyPluginData *pdata = g_slice_new(GeanyPyPluginData); PluginInfo *info = subplugin->info; GeanyPluginFuncs *funcs = subplugin->funcs; + PyObject *caps = PyCapsule_New(subplugin, "GeanyPlugin", NULL); Py_INCREF(found); pdata->module = module; pdata->class = found; + PyObject_SetAttrString(pdata->class, "__geany_plugin__", caps); pdata->instance = NULL; info->name = string_from_attr(pdata->class, "__plugin_name__"); info->description = string_from_attr(pdata->class, "__plugin_description__"); diff --git a/geanypy/src/geanypy-plugin.h b/geanypy/src/geanypy-plugin.h index c97bd9606..007309149 100644 --- a/geanypy/src/geanypy-plugin.h +++ b/geanypy/src/geanypy-plugin.h @@ -33,6 +33,13 @@ extern GeanyData *geany_data; #define PyMODINIT_FUNC void #endif +static inline GeanyPlugin *plugin_get(PyObject *self) +{ + PyObject *caps = PyObject_GetAttrString(self, "__geany_plugin__"); + return PyCapsule_GetPointer(caps, "GeanyPlugin"); +} + + #ifdef __cplusplus } /* extern "C" */ From 2aec034029f9a84ab0777fa00b54560c8954e7e2 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Fri, 6 Nov 2015 15:33:36 +0100 Subject: [PATCH 3/5] Add support for keybindings geany.Plugin gains a method to create keybinding groups, which in turn has a method to add key items (losely matching Geany's original API, but heavily simplified). --- geanypy/geany/__init__.py | 2 + geanypy/geany/plugin.py | 8 ++ geanypy/src/Makefile.am | 1 + geanypy/src/geanypy-keybindings.c | 213 ++++++++++++++++++++++++++++++ geanypy/src/geanypy-keybindings.h | 39 ++++++ geanypy/src/geanypy-plugin.c | 3 + 6 files changed, 266 insertions(+) create mode 100644 geanypy/src/geanypy-keybindings.c create mode 100644 geanypy/src/geanypy-keybindings.h diff --git a/geanypy/geany/__init__.py b/geanypy/geany/__init__.py index 264d6eee1..ccada0cd4 100644 --- a/geanypy/geany/__init__.py +++ b/geanypy/geany/__init__.py @@ -24,6 +24,7 @@ import search import templates import ui_utils +import keybindings from app import App from prefs import Prefs, ToolPrefs @@ -41,6 +42,7 @@ "main_widgets", "interface_prefs", "app", + "keybindings", "general_prefs", "search_prefs", "template_prefs", diff --git a/geanypy/geany/plugin.py b/geanypy/geany/plugin.py index 8a9a2afce..4cf05158e 100644 --- a/geanypy/geany/plugin.py +++ b/geanypy/geany/plugin.py @@ -36,6 +36,7 @@ def cleanup(self): files with a `.py` extension will be loaded. """ +import keybindings class Plugin(object): """ @@ -120,3 +121,10 @@ def author(self): return self.__plugin_author__ else: return "" + + def set_key_group(self, section_name, count, callback = None): + """ + Sets up a GeanyKeyGroup for this plugin. You can use that group to add keybindings + with group.add_key_item(). + """ + return keybindings.set_key_group(self, section_name, count, callback) diff --git a/geanypy/src/Makefile.am b/geanypy/src/Makefile.am index a3fc96184..1600b59b9 100644 --- a/geanypy/src/Makefile.am +++ b/geanypy/src/Makefile.am @@ -23,6 +23,7 @@ geanypy_la_SOURCES = geanypy-app.c \ geanypy-highlighting.c \ geanypy-indentprefs.c \ geanypy-interfaceprefs.c \ + geanypy-keybindings.c \ geanypy-main.c \ geanypy-mainwidgets.c \ geanypy-msgwindow.c \ diff --git a/geanypy/src/geanypy-keybindings.c b/geanypy/src/geanypy-keybindings.c new file mode 100644 index 000000000..1c6ea269f --- /dev/null +++ b/geanypy/src/geanypy-keybindings.c @@ -0,0 +1,213 @@ +/* + * plugin.c + * + * Copyright 2015 Thomas Martitz + * + * 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 "geanypy.h" +#include "geanypy-keybindings.h" + +#include + +static gboolean call_key(gpointer *unused, guint key_id, gpointer data) +{ + PyObject *callback = data; + PyObject *args; + + args = Py_BuildValue("(i)", key_id); + PyObject_CallObject(callback, args); + Py_DECREF(args); +} + + +/* plugin.py provides an OOP-style wrapper around this so call it like: + * class Foo(geany.Plugin): + * def __init__(self): + * self.set_key_group(...) + */ +static PyObject * +Keybindings_set_key_group(PyObject *self, PyObject *args, PyObject *kwargs) +{ + static gchar *kwlist[] = { "plugin", "section_name", "count", "callback", NULL }; + int count = 0; + const gchar *section_name = NULL; + GeanyKeyGroup *group = NULL; + PyObject *py_callback = NULL; + PyObject *py_ret = Py_None; + PyObject *py_plugin; + gboolean has_cb = FALSE; + + Py_INCREF(Py_None); + + if (PyArg_ParseTupleAndKeywords(args, kwargs, "Osi|O", kwlist, + &py_plugin, §ion_name, &count, &py_callback)) + { + GeanyPlugin *plugin = plugin_get(py_plugin); + g_return_val_if_fail(plugin != NULL, Py_None); + + has_cb = PyCallable_Check(py_callback); + if (has_cb) + { + Py_INCREF(py_callback); + group = plugin_set_key_group_full(plugin, section_name, count, + (GeanyKeyGroupFunc) call_key, py_callback, + (GDestroyNotify) Py_DecRef); + } + else + group = plugin_set_key_group(plugin, section_name, count, NULL); + } + + if (group) + { + Py_DECREF(py_ret); + py_ret = KeyGroup_new_with_geany_key_group(group, has_cb); + } + + return py_ret; +} + + +static PyObject * +KeyGroup_add_key_item(KeyGroup *self, PyObject *args, PyObject *kwargs) +{ + static gchar *kwlist[] = { "name", "label", "callback", "key_id", "key", "mod" , "menu_item", NULL }; + int id = -1; + int key = 0, mod = 0; + const gchar *name = NULL, *label = NULL; + PyObject *py_menu_item = NULL; + PyObject *py_callback = NULL; + GeanyKeyBinding *item = NULL; + + if (PyArg_ParseTupleAndKeywords(args, kwargs, "ss|OiiiO", kwlist, + &name, &label, &py_callback, &id, &key, &mod, &py_menu_item)) + { + if (id == -1) + id = self->item_index; + + GtkWidget *menu_item = (py_menu_item == NULL || py_menu_item == Py_None) + ? NULL : GTK_WIDGET(pygobject_get(py_menu_item)); + if (PyCallable_Check(py_callback)) + { + Py_INCREF(py_callback); + item = keybindings_set_item_full(self->kb_group, id, (guint) key, + (GdkModifierType) mod, name, label, menu_item, + (GeanyKeyBindingFunc) call_key, py_callback, + (GDestroyNotify) Py_DecRef); + } + else + { + if (!self->has_cb) + g_warning("Either KeyGroup or the Keybinding must have a callback\n"); + else + item = keybindings_set_item(self->kb_group, id, NULL, (guint) key, + (GdkModifierType) mod, name, label, menu_item); + } + Py_XDECREF(py_menu_item); + + self->item_index = id + 1; + } + + if (item) + { + /* Return a tuple containing the key group and the opaque GeanyKeyBinding pointer. + * This is in preparation of allowing chained calls like + * set_kb_group(X, 3).add_key_item().add_key_item().add_key_item() + * without losing access to the keybinding pointer (might become necessary for newer + * Geany APIs). + * Note that the plain tuple doesn't support the above yet, we've got to subclass it, + * but we are prepared without breaking sub-plugins */ + PyObject *ret = PyTuple_Pack(2, self, PyCapsule_New(item, "GeanyKeyBinding", NULL)); + return ret; + } + Py_RETURN_NONE; +} + + +static PyMethodDef +KeyGroup_methods[] = { + { "add_key_item", (PyCFunction)KeyGroup_add_key_item, METH_KEYWORDS, + "Adds an action to the plugin's key group" }, + { NULL } +}; + +static PyMethodDef +Keybindings_methods[] = { + { "set_key_group", (PyCFunction)Keybindings_set_key_group, METH_KEYWORDS, + "Sets up a GeanyKeybindingGroup for this plugin." }, + { NULL } +}; + + +static PyGetSetDef +KeyGroup_getseters[] = { + { NULL }, +}; + + +static void +KeyGroup_dealloc(KeyGroup *self) +{ + g_return_if_fail(self != NULL); + self->ob_type->tp_free((PyObject *) self); +} + + +static PyTypeObject KeyGroupType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "geany.keybindings.KeyGroup", /* tp_name */ + sizeof(KeyGroup), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor) KeyGroup_dealloc, /* tp_dealloc */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* tp_print - tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + "Wrapper around a GeanyKeyGroup structure." ,/* tp_doc */ + 0, 0, 0, 0, 0, 0, /* tp_traverse - tp_iternext */ + KeyGroup_methods, /* tp_methods */ + 0, /* tp_members */ + KeyGroup_getseters, /* tp_getset */ + 0, 0, 0, 0, 0, /* tp_base - tp_dictoffset */ + 0, 0, (newfunc) PyType_GenericNew, /* tp_init - tp_alloc, tp_new */ +}; + + +PyMODINIT_FUNC initkeybindings(void) +{ + PyObject *m; + + if (PyType_Ready(&KeyGroupType) < 0) + return; + + m = Py_InitModule3("keybindings", Keybindings_methods, "Keybindings support."); + + Py_INCREF(&KeyGroupType); + PyModule_AddObject(m, "KeyGroup", (PyObject *)&KeyGroupType); +} + +PyObject *KeyGroup_new_with_geany_key_group(GeanyKeyGroup *group, gboolean has_cb) +{ + KeyGroup *ret = PyObject_New(KeyGroup, &KeyGroupType); + + ret->kb_group = group; + ret->has_cb = has_cb; + ret->item_index = 0; + + return (PyObject *) ret; +} + diff --git a/geanypy/src/geanypy-keybindings.h b/geanypy/src/geanypy-keybindings.h new file mode 100644 index 000000000..27ecf651a --- /dev/null +++ b/geanypy/src/geanypy-keybindings.h @@ -0,0 +1,39 @@ +/* + * geanypy-keybindings.h + * + * Copyright 2015 Thomas Martitz + * + * 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. + */ + + +#ifndef GEANYPY_KEYBINDINGS_H +#define GEANYPY_KEYBINDINGS_H + +#include + +typedef struct +{ + PyObject_HEAD + GeanyKeyGroup *kb_group; + gboolean has_cb; + gint item_index; +} KeyGroup; + +extern PyObject * +KeyGroup_new_with_geany_key_group(GeanyKeyGroup *group, gboolean has_cb); + +#endif /* GEANYPY_KEYBINDINGS_H */ diff --git a/geanypy/src/geanypy-plugin.c b/geanypy/src/geanypy-plugin.c index e5c9c0786..268f7378e 100644 --- a/geanypy/src/geanypy-plugin.c +++ b/geanypy/src/geanypy-plugin.c @@ -26,6 +26,7 @@ #define INCLUDE_PYGOBJECT_ONCE_FULL #include "geanypy.h" +#include "geanypy-keybindings.h" #include #include @@ -49,6 +50,7 @@ PyMODINIT_FUNC initscintilla(void); PyMODINIT_FUNC initsearch(void); PyMODINIT_FUNC inittemplates(void); PyMODINIT_FUNC initui_utils(void); +PyMODINIT_FUNC initkeybindings(void); static void @@ -89,6 +91,7 @@ GeanyPy_start_interpreter(void) initsearch(); inittemplates(); initui_utils(); + initkeybindings(); #ifdef GEANYPY_WINDOWS { /* On windows, get path at runtime since we don't really know where From 87bc37865632005ffeb581038576fedf0c2bb558 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Mon, 4 Jan 2016 17:44:27 +0100 Subject: [PATCH 4/5] proxy: backward compatibility for legaxy show_configure() Older plugins implement just show_configure() that spawns a dialog itself. geanypy now adds a generic per plugin to the unified plugin preferences dialog, to open that legacy dialog. --- geanypy/src/geanypy-plugin.c | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/geanypy/src/geanypy-plugin.c b/geanypy/src/geanypy-plugin.c index 268f7378e..134808af9 100644 --- a/geanypy/src/geanypy-plugin.c +++ b/geanypy/src/geanypy-plugin.c @@ -206,6 +206,45 @@ static GtkWidget *geanypy_proxy_configure(GeanyPlugin *plugin, GtkDialog *parent return NULL; } + +static void do_show_configure(GtkWidget *button, gpointer pdata) +{ + GeanyPyPluginData *data = (GeanyPyPluginData *) pdata; + PyObject_CallMethod(data->instance, "show_configure", NULL); +} + + +static GtkWidget *geanypy_proxy_configure_legacy(GeanyPlugin *plugin, GtkDialog *parent, gpointer pdata) +{ + GeanyPyPluginData *data = (GeanyPyPluginData *) pdata; + PyObject *o, *oparent; + GtkWidget *box, *label, *button, *align; + gchar *text; + + /* This creates a simple page that has only one button to show the plugin's legacy configure + * dialog. It is for older plugins that implement show_configure(). It's not pretty but + * it provides basic backwards compatibility. */ + box = gtk_vbox_new(FALSE, 2); + + text = g_strdup_printf("The plugin \"%s\" is older and hasn't been updated\nto provide a configuration UI. However, it provides a dialog to\nallow you to change the plugin's preferences.", plugin->info->name); + label = gtk_label_new(text); + + align = gtk_alignment_new(0, 0, 1, 1); + gtk_container_add(GTK_CONTAINER(align), label); + gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 6, 2, 2); + gtk_box_pack_start(GTK_BOX(box), align, FALSE, FALSE, 0); + + button = gtk_button_new_with_label("Open dialog"); + align = gtk_alignment_new(0.5, 0, 0.3f, 1); + gtk_container_add(GTK_CONTAINER(align), button); + g_signal_connect(button, "clicked", (GCallback) do_show_configure, pdata); + gtk_box_pack_start(GTK_BOX(box), align, FALSE, TRUE, 0); + + gtk_widget_show_all(box); + g_free(text); + return box; +} + static void geanypy_proxy_help(GeanyPlugin *plugin, gpointer pdata) { GeanyPyPluginData *data = (GeanyPyPluginData *) pdata; @@ -290,6 +329,8 @@ geanypy_load(GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, funcs->cleanup = geanypy_proxy_cleanup; if (PyObject_HasAttrString(found, "configure")) funcs->configure = geanypy_proxy_configure; + else if (PyObject_HasAttrString(found, "show_configure")) + funcs->configure = geanypy_proxy_configure_legacy; if (PyObject_HasAttrString(found, "help")) funcs->help = geanypy_proxy_help; if (GEANY_PLUGIN_REGISTER_FULL(subplugin, 224, pdata, NULL)) From 8786fee0797b1e1d3864a460bf4067f2e2a1aea0 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Wed, 24 Feb 2016 09:05:49 +0100 Subject: [PATCH 5/5] docs: update docs for the proxy plugin changes Most importantly is the changed plugin directory. --- geanypy/README | 11 +++++---- geanypy/doc/source/starting.rst | 40 +++++++++++++++------------------ geanypy/geany/plugin.py | 7 +++--- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/geanypy/README b/geanypy/README index 7a65ac619..e9ae6247b 100644 --- a/geanypy/README +++ b/geanypy/README @@ -33,14 +33,13 @@ module more "Pythonic". To write a plugin, inherit from the ``geany.Plugin`` class and implmenent the required members (see ``geany/plugin.py`` documentation comments). Then put the plugin in a searched plugin path. Currently two locations are search for -plugins. The first is ``PREFIX/share/geany/geanypy/plugins`` and the recommended +plugins. The first is ``PREFIX/lib/geany`` and the recommended location is under your personal Geany directory (usually -``~/.config/geany/plugins/geanypy/plugins``). To load or unload plugins, click -the Python Plugin Manager item under the Tools menu which will appear when you -activate GeanyPy through Geany's regular plugin manager. +``~/.config/geany/plugins``). To load or unload plugins, use Geany's regular Plugin ++Manager. Python plugins appear there once GeanyPy is activated. -When the GeanyPy plugin is loaded, it has an option to add a new tab to -the notebook in the message window area that contains an interactive +When the Python Console plugin is enabled, it will add a new tab to the notebook in +the message window area that contains an interactive Python shell with the `geany` Python shell with the ``geany`` module pre-imported. You can tinker around with API with this console, for example:: diff --git a/geanypy/doc/source/starting.rst b/geanypy/doc/source/starting.rst index 917dae262..238913e29 100644 --- a/geanypy/doc/source/starting.rst +++ b/geanypy/doc/source/starting.rst @@ -7,34 +7,30 @@ GeanyPy, it's important to note how it works and some features it provides. What the heck is GeanyPy, really? ================================= -GeanyPy is "just another Geany plugin", really. Geany sees GeanyPy as any -other `plugin `_, so -to activate GeanyPy, use Geany's +GeanyPy is a proxy plugin. Geany initially sees GeanyPy as any other +`plugin `_, but +GeanyPy registers some additional stuff that enables Geany to load python plugins +through GeanyPy. So to activate, use Geany's `Plugin Manager `_ under the Tools menu as you would for any other plugin. -Once the GeanyPy plugin has been activated, a few elements are added to Geany's -user interface as described below. +Once the GeanyPy plugin has been activated, Geany should rescan the plugin +directories and pick those up that are supported through GeanyPy. It'll integrate +the python plugins into the Plugin Manager in an additional hierarchy level below +GeanyPy. -Python Plugin Manager -===================== +* [ ] Geany plugin 1 +* [x] GeanyPy + * [ ] Python plugin 1 + * [x] Python plugin 2 + * [ ] Python plugin 3 +* [ ] Geany plugin 3 -Under the Tools menu, you will find the Python Plugin Manager, which is meant -to be similar to Geany's own Plugin Manager. This is where you will activate -any plugins written in Python. +Remember that Geany looks in three places for plugins: -The Python Plugin Manager looks in exactly two places for plugins: - -1. For system-wide plugins, it will search in PREFIX/share/geany/geanypy/plugins. -2. In Geany's config directory under your home directory, typically ~/.config/geany/plugins/geanypy/plugins. - -Where `PREFIX` is the prefix used at configure time with Geany/GeanyPy (see -the previous section, Installation). Both of these paths may vary depending on -your platform, but for most \*nix systems, the above paths should hold true. - -Any plugins which follow the proper interface found in either of those two -directories will be listed in the Python Plugin Manager and you will be able -to activate and deactivate them there. +1. For system-wide plugins, it will search in (usually) /usr/share/geany or /usr/local/share/geany. +2. In Geany's config directory under your home directory, typically ~/.config/geany/plugins. +3. A user-configurable plugin directory (useful during plugin development). Python Console ============== diff --git a/geanypy/geany/plugin.py b/geanypy/geany/plugin.py index 4cf05158e..c93e2c885 100644 --- a/geanypy/geany/plugin.py +++ b/geanypy/geany/plugin.py @@ -30,10 +30,9 @@ def cleanup(self): The guts of the API are exposed to plugins through the `geany` package and its modules. -Plugins should be placed in either the system plugin directory (something -like /usr/local/share/geany/geanypy/plugins) or in their personal plugin -directory (something like ~/.config/geany/plugins/geanypy/plugins). Only -files with a `.py` extension will be loaded. +Plugins should be placed in either the system plugin directory (something like +/usr/local/lib/geany) or in the user plugin directory (something like +~/.config/geany/plugins). Only files with a `.py` extension will be loaded. """ import keybindings