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.
Contents
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:
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.
Then your class can provide the do_create_configure_widget() method to provide a widget to display in the configure dialog.
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)' |