Python Plugin How To for gedit 3

This document describes how to create plugins for gedit 3 using Python, you can find the tutorial for the old Python plugins of gedit 2 here.

Below follows a list of the different topics you need to know about. Note that knowledge of GTK+ (and more importantly, PyGObject) is useful in creating an actual plugin. An extensive tutorial can be found here.

Files needed

Every Python plugin needs at least two files. One file (pluginname.plugin) is to tell gedit where the plugin can be found, what it's called, a short description, who is the author, etc. This file is in the .desktop format (see example). The second file is the actual Python code. Both of these files need to be placed in either the system-wide plugins directory /usr/lib/gedit/plugins/, or in the user plugins directory, which may need to be created, ~/.local/share/gedit/plugins/.

Gedit plugin infrastructure

This section gives some tips to try to understand the way gedit plugins work. It is important to read this section carefully. A Python plugin will be able to have one or more extensions. Each extension is derived from GObject.Object and must implement one of the interfaces that gedit provides for the extension points.

  • Gedit.AppActivatable: This extension provides two methods to be overriden: do_activate, do_deactivate

  • Gedit.WindowActivatable

  • Gedit.ViewActivatable: WindowActivatable and ViewActivatable provide: do_activate, do_deactivate and do_update_state

gedit is an application which usually only runs one instance, even if you launch it several times. For each instance one extension point of the Gedit.AppActivatable implemented by the plugins will be created. This is useful to implement in case you need to share some data across different windows. For each window one Gedit.WindowActivatable extension point from the plugins will be created, (for example, to add new menu items or share information between tabs and windows). Finally you can implement Gedit.ViewActivatable if you want to manage either the Gedit.View or the Gedit.Document. Implementing a Gedit.ViewActivatable does not mean that it will be only used by Gedit.Window objects. I.e the snippets plugin uses a Gedit.Document and the toplevel of this is a Gtk.Dialog and not a Gedit.Window.

When a plugin is activated, all "activate" methods from the extension points will be called, and when the plugin is deactivated the "deactivate" methods from all extension points will be called. Specifically:

  • Gedit.AppActivatable: do_activate will happen when a new instance is created (i.e launching gedit for the first time) or for each instance that it is already running and do_deactivate will be called when all windows of gedit are destroyed (so the main instance is destroyed) or when the plugin is disabled.

  • Gedit.WindowActivatable: do_activate will happen when a new window is created or for each window that it is already created in the instance, do_deactivate will happen when a window is destroyed or when the plugin is disabled, and do_update_state will be called when when it is requested to update the UI.

  • Gedit.ViewActivatable: do_activate will happen when a new view is created or for each view that it is already created in the window, do_deactivate will happen when a view is destroyed or when the plugin is disabled, and do_update_state will be called when it is requested to update the UI.

Writing the plugin

Now we are ready to actually write a plugin. As stated in Files needed we first need to create a .plugin file which will give gedit information about our new plugin.

examplepy.plugin

[Plugin]
Loader=python
Module=examplepy
IAge=3
Name=Example py
Description=A Python plugin example
Authors=Jesse van den Kieboom <jesse@icecrew.nl>
Copyright=Copyright © 2006 Jesse van den Kieboom <jesse@icecrew.nl>
Website=http://www.gedit.org

To explain the specified fields:

  • Loader: which loader gedit needs to use; for Python plugins this is python

  • Module: the file in which your plugin is located (leaving out the extension .py). You can also specify a directory here. As with normal Python applications you need to put a __init__.py in that directory

  • IAge: the plugin version, this is always 3

  • Name: the name of the plugin. This will show up in the plugins list in the preference dialog

  • Description: a short description of the plugin. This will show up in the plugins list in the preference dialog

  • Authors: a list of people who wrote the plugin. This will show up in the about dialog of the plugin (plugins list in the preference dialog)

  • Copyright: the plugins copyright. This will show up in the about dialog of the plugin (plugins list in the preference dialog)

  • Website: optionally, the plugins website

Now we need to create the plugin itself. The plugin is called examplepy, so we create examplepy.py. At the most minimal level, it just defines one class derived from Gedit.WindowActivatable defining do_activate, do_deactivate and do_update_state:

Minimal plugin

   1 from gi.repository import GObject, Gedit
   2 
   3 class ExamplePyPlugin(GObject.Object, Gedit.WindowActivatable):
   4     __gtype_name__ = "ExamplePyPlugin"
   5 
   6     window = GObject.property(type=Gedit.Window)
   7 
   8     def __init__(self):
   9         GObject.Object.__init__(self)
  10 
  11     def do_activate(self):
  12         pass
  13 
  14     def do_deactivate(self):
  15         pass
  16 
  17     def do_update_state(self):
  18         pass

Our first plugin! When we copy these two files to ~/.local/share/gedit/plugins and restart gedit the plugin will be detected. When you open the plugins list in the preferences dialog you should see something like this:

examplepy.png

Implementing an advanced plugin

Of course, this plugin does not do anything. We can activate it, but it's not of much use, yet. Before implementing useful stuff, let's first get the structure right. We're now going to implement one plugin with several extension points (ExamplePyWindowActivatable).

   1 from gi.repository import GObject, Gedit
   2 
   3 class ExamplePyWindowActivatable(GObject.Object, Gedit.WindowActivatable):
   4     __gtype_name__ = "ExamplePyWindowActivatable"
   5 
   6     window = GObject.property(type=Gedit.Window)
   7 
   8     def __init__(self):
   9         GObject.Object.__init__(self)
  10 
  11     def do_activate(self):
  12         print "Plugin created for", self.window
  13 
  14     def do_deactivate(self):
  15         print "Plugin stopped for", self.window
  16 
  17     def do_update_state(self):
  18         # Called whenever the window has been updated (active tab
  19         # changed, etc.)
  20         print "Plugin update for", self.window
  21 
  22 class ExamplePyViewActivatable(GObject.Object, Gedit.ViewActivatable):
  23     __gtype_name__ = "ExamplePyViewActivatable"
  24 
  25     view = GObject.property(type=Gedit.View)
  26 
  27     def __init__(self):
  28         GObject.Object.__init__(self)
  29 
  30     def do_activate(self):
  31         print "Plugin created for", self.view
  32 
  33     def do_deactivate(self):
  34         print "Plugin stopped for", self.view
  35 
  36     def do_update_state(self):
  37         # Called whenever the view has been updated
  38         print "Plugin update for", self.view

There are now two extensions implemented by one plugin. The ExamplePyWindowActivatable will be created as said before, for each window that is created or that exists before the plugin has been activated, and ExamplePyViewActivatable will be created for each view that is created. So at the end you can will see several messages printed in the terminal when each extension is activated, deactivated or the UI needs to be updated.

The plugin still does not do anything except printing some messages when it's created, stopped and updated. But we do have a good starting point now to really implement something.

Adding a menu item

So what we are going to do now is add a menu item in the menu bar. When the menu item is activated, the currently active buffer is cleared.

   1 from gettext import gettext as _
   2 from gi.repository import GObject, Gtk, Gedit
   3 
   4 # Menu item example, insert a new item in the Tools menu
   5 ui_str = """<ui>
   6   <menubar name="MenuBar">
   7     <menu name="ToolsMenu" action="Tools">
   8       <placeholder name="ToolsOps_2">
   9         <menuitem name="ExamplePy" action="ExamplePy"/>
  10       </placeholder>
  11     </menu>
  12   </menubar>
  13 </ui>
  14 """
  15 class ExamplePyWindowActivatable(GObject.Object, Gedit.WindowActivatable):
  16     __gtype_name__ = "ExamplePyWindowActivatable"
  17 
  18     window = GObject.property(type=Gedit.Window)
  19 
  20     def __init__(self):
  21         GObject.Object.__init__(self)
  22 
  23     def do_activate(self):
  24         # Insert menu items
  25         self._insert_menu()
  26 
  27     def do_deactivate(self):
  28         # Remove any installed menu items
  29         self._remove_menu()
  30 
  31         self._action_group = None
  32 
  33     def _insert_menu(self):
  34         # Get the Gtk.UIManager
  35         manager = self.window.get_ui_manager()
  36 
  37         # Create a new action group
  38         self._action_group = Gtk.ActionGroup("ExamplePyPluginActions")
  39         self._action_group.add_actions([("ExamplePy", None, _("Clear document"),
  40                                          None, _("Clear the document"),
  41                                          self.on_clear_document_activate)])
  42 
  43         # Insert the action group
  44         manager.insert_action_group(self._action_group, -1)
  45 
  46         # Merge the UI
  47         self._ui_id = manager.add_ui_from_string(ui_str)
  48 
  49     def _remove_menu(self):
  50         # Get the Gtk.UIManager
  51         manager = self.window.get_ui_manager()
  52 
  53         # Remove the ui
  54         manager.remove_ui(self._ui_id)
  55 
  56         # Remove the action group
  57         manager.remove_action_group(self._action_group)
  58 
  59         # Make sure the manager updates
  60         manager.ensure_update()
  61 
  62     def do_update_state(self):
  63         self._action_group.set_sensitive(self.window.get_active_document() != None)
  64 
  65     # Menu activate handlers
  66     def on_clear_document_activate(self, action):
  67         doc = self.window.get_active_document()
  68         if not doc:
  69             return
  70 
  71         doc.set_text('')

We left out the ExamplePyWindowActivatable because nothing has changed there. So what happens here. When the plugin instance is created it inserts a menu item. gedit uses Gtk.UIManager and defines placeholders for plugins to insert menu items in. This makes it easy for us to insert a menu item. We add a Clear document item to the tools menu and register the on_clear_document_activate callback to be called when the item is activated. In the callback we simply retrieve the currently active document and clear the text.

Additionally we change the sensitivity of the action group in do_update_state. The action group will be insensitive when there is no currently active document (because we can't clear a document that does not exist). That is everything there is to it. We now have our first functional Python plugin!

Making use of side/bottom panels

In the context of the above examples, the code

self.window.get_side_panel()

would return a Gedit.Panel object corresponding to the side panel, and

self.window.get_bottom_panel()

would return the bottom panel. Methods of the Gedit.Panel object can be found in the API section.

Events

This is a very, very incomplete description of signals. A proper discussion of signals can be found in the pyGTK tutorial. You will need to consult this.

Your plugin can react to certain events (such as the opening of a tab or the saving of a file). This is accomplished by listening to signals from various Gedit objects (Gedit.Window and so on). A full specification of the signals emitted by each object can be found in the C API.

To listen for a certain signal, use the connect() function of the object. The syntax is as follows:

object.connect("signal", handler, other_arg1, ...)

The signal that you are listening for is the first argument, in quotes. The second argument is the name of a function that will handle the signal event. The arguments to this function will vary by signal; consult the reference for your particular signal to find out all the information. connect() function returns an integer that server as an identified. An example in actual use would be:

l_id = doc.connect("loaded", self.add, view).

where doc is a Gedit.Document and the add function is defined in the plugin class. You need to keep the returned handler ID to be able to disconnect the handler later, and so you'll need a line that looks something like

doc.examplePyPluginHandlerId = l_id

where you can freely choose the name of the attribute (here examplePyPluginHandlerId).

When cleaning up, remember to kill all your signal handlers. Given the above example, cleanup code would look something like this:

l_id = doc.examplePyPluginHandlerId
doc.disconnect(l_id)
doc.examplePyPluginHandlerId = None

(FIXME)

Adding a configure dialog for your plugin

If you want to be able to display a configuration dialog for your plugin, your class must implement PeasGtk.Configurable.

   1 from gi.repository import PeasGtk
   2 
   3 class ExamplePyWindowActivatable(GObject.Object, Gedit.WindowActivatable, PeasGtk.Configurable):

Then your class can provide the do_create_configure_widget() method to provide a widget to display in the configure dialog.

   1     def do_create_configure_widget(self):
   2         widget = Gtk.Label("A configuration widget goes here.")
   3         return widget

gedit will handle packing the widget into a Gtk.Dialog and calling the run() method on the dialog for you.

Writing plugins without wrappers

If you want to write a plugin using Python, but the appropriate wrappers for a specific library are unavailable, you can use the dl module to access the library's functions. For example, if you want to access the Enchant spell checking library you could use the following code:

   1 import dl
   2 class Enchant(object):
   3     def __init__(self, lang='en_US'):
   4         try:
   5             self.library = dl.open('libenchant.so')
   6 
   7             broker = self.library.call('enchant_broker_init')
   8             self.dict = self.library.call(
   9                'enchant_broker_request_dict', broker, lang)
  10 
  11         except dl.error:
  12             self.incorrect_spelling = lambda x: False
  13 
  14     def incorrect_spelling(self, word):
  15         return bool(self.library.call('enchant_dict_check',
  16                     self.dict, word, len(word)))

Note: The dl module has been deprecated in Python 2.5, you should use the ctypes module instead.

Drag and Drop

The MDI document area is a drag and drop target. You can use the following data types from your source:

Data Type

Purpose

text/uri-list

Opens a tab for each filename passed.

This section is incomplete

Template generator

A Python plugin template can be easily generated using the little script attached to this page: gedit-py-plugin.py (mind that this is just a very simple little script, there is a much better one in the tools/ directory in gedit CVS).

Most important API

Below follows a list of the most important gedit specific API functions you need to know about. Note that this list is incomplete. A full documentation of the Gedit C API is contained in the docs/ directory of the latest gedit release, and can be found online here. This is useful because the C API and the Python API are very similar — generally, the C method foo_bar(foo_instance) is replaced by foo_instance.bar(). Wherever the API is incomplete, this can be used as a reference. See that you can also find the class diagram here.

Gedit

Gedit.App

The gedit application provides functions to get all the Gedit.Windows, create new windows, get all the documents, etc.

get_default()

staticmethod, gets the default Gedit.App instance

create_window(Gdk.Screen)

creates a new Gedit.Window on a Gdk.Screen. If Gdk.Screen is None then the default screen will be used

get_windows()

get a list of Gedit.Window

get_active_window()

get the currently active window

get_documents()

get all the Gedit.Document in all the windows

get_views()

get all the Gedit.View in all the windows


Gedit.Window

The gedit window based on Gtk.Window.

Methods

Method

Description

create_tab(jump_to)

create a new empty document. jump_to (boolean) specifies whether to select the new document when it's created

create_tab_from_location(location, encoding, line_pos, column_pos, create, jump_to)

open a file. location (Gio.File) specifies the file to open. encoding (Gedit.Encoding) specifies the encoding to use (None for default), line_pos (int) and column_pos (int) specifies to which position to jump when the file is opened. create (boolean) specifies whether to create a new file if it doesn't exist yet. jump_to (boolean) specifies whether to select the new document when it's created

close_tab(tab)

close a tab (Gedit.Tab)

close_all_tabs()

closes all the tabs in the window

get_active_tab()

gets the currently active tab (Gedit.Tab)

set_active_tab(tab)

sets the currently active tab (Gedit.Tab)

get_active_view()

gets the currently active view (Gedit.View)

get_active_document()

gets the currently active document (Gedit.Document)

get_documents()

gets a list of the documents (Gedit.Document) in the window

get_unsaved_documents()

gets a list of unsaved documents (Gedit.Document)

get_views()

get a list of the views (Gedit.View) in the window

get_group()

gets the Gtk.WindowGroup the window belongs to

get_side_panel()

gets the sidepanel (Gedit.Panel)

get_bottom_panel()

gets the bottompanel (Gedit.Panel)

get_statusbar()

gets the statusbar (Gtk.Statusbar)

get_ui_manager()

gets the ui manager (Gtk.UIManager)

get_state()

gets the state of the window (cumulative state of the different documents)

Signals

(FIXME)

Signal Name

Description

Callback

active-tab-changed

Active tab has been changed

def callback (tab, ...)

active-tab-state-changed

Active tab has changed state

def callback (...)

tab-added

Tab added to window

def callback (tab, ...)

tab-removed

Tab removed from window

def callback (tab, ...)

tabs-reordered

Tabs reordered

def callback (...)


Gedit.Panel

add_item(item, id, "display name", image)

Adds an item (a Gtk.Widget) to the panel. id (str) is an unique name for the new item. image is the icon (usually a Gtk.Image).

remove_item(item)

Removes an item from the panel. item is the Gtk.Widget that was specified in add_item.

activate_item(item)

Switches to an existing panel item. item is the Gtk.Widget that was specified in add_item.

set_property("visible", state)

Shows or hides the entire panel. state can be True to display the panel, or False to hide it.

Gedit.Tab

The tab is based on a Gtk.Box (vertical orientation) and is the controller of Gedit.View and Gedit.Document.

get_from_document(Gedit.Document)

staticmethod, gets the Gedit.Tab belonging to a Gedit.Document

get_view()

get the view (Gedit.View)

get_document()

get the document (Gedit.Document)

get_state()

get the state

get_auto_save_enabled()

gets whether autosave is enabled

set_auto_save_enabled(enabled)

sets whether autosave is enabled

get_auto_save_interval()

gets the interval on which to perform autosave

set_auto_save_interval(interval)

sets the interval on which to perform autosave

Gedit.View

The view is based on a GtkSource.View and is the view for Gedit.Document.

cut_clipboard()

cut selection to clipboard

copy_clipboard()

copy selection to clipboard

paste_clipboard()

paste clipboard at cursor position

delete_selection()

deletes the selected text

select_all()

select all text

scroll_to_cursor()

scroll to the cursor position

set_colors(def, background, text, selection, sel_text)

sets the different colors to use. def (boolean) specifies whether to use the default colors. background, text, selection and sel_text are gdk.Color objects which specify the corresponding colors

set_font(def, font_name)

sets the font to use. def (boolean) specifies whether to use the default font. font_name (string) specifies the font to use

Gedit.Document

The document is based on a GtkSource.Buffer and is the document for Gedit.View.

get_location()

gets the documents location (Gio.File or None if the document is unsaved)

get_uri_for_display()

gets the documents uri for display (or None if the document is unsaved)

get_short_name_for_display()

gets the documents short name for display

get_mime_type()

gets the documents mime type

get_readonly()

gets whether the document is read only

load(location, encoding, line_pos, column_pos, create)

loads a file. location (Gio.File) specifies the file. encoding (Gedit.Encoding) specifies the encoding (None for the default encoding). line_pos (int) and column_pos (int) specifies the position to scroll to after loading. create (boolean) specifies whether to create the file if it doesn't exist yet.

load_cancel()

cancels the loading of a file

is_untouched()

gets whether the document has been changed since the last time it was saved

is_untitled()

gets whether the document has a title

get_deleted()

gets whether the document has been deleted

goto_line(line)

scroll to a line number. line (int) specifies the line number to scroll to

set_language(lang)

sets the source language of the document to lang (GtkSource.Language)

get_language()

gets the source language of the document (GtkSource.Language)

get_encoding()

gets the encoding of the document (Gedit.Encoding)

Gedit.Encoding

copy()

free()

get_charset()

gets the actual character set like 'UTF-8'

get_name()

gets the name of the encoding like 'Unicode'

to_string()

gets its string representation like 'Unicode (UTF-8)'

Apps/Gedit/PythonPluginHowTo (last edited 2015-05-06 15:31:10 by DariusKellermann)