Contents
A plugin is a Python file that appears in Leo's plugin directory. Plugins modify how Leo works. With plugins you can give Leo new commands, modify how existing commands work, or change any other aspect of Leo's look and feel. leoPlugins.leo contains all of Leo's official plugins. Studying this file is a good way to learn how to write plugins.
You enable plugins using @enabled-plugins nodes in leoSettings.leo or myLeoSettings.leo. For more details, see the @enabled-plugins node in leoSettings.leo. Leo imports all enabled plugins at startup time. Plugins become active if importing the plugin was successful.
Writing plugins is quite similar to writing any other Leo script. See Chapter 7: Scripting Leo with Python. In particular:
The rest of this chapters discusses topics related specifically to plugins.
The plugins test suite creates a new convention: if a plugin has a function at the outer (module) level called unitTest, Leo will call that function when doing unit testing for plugins. So it would be good if writers of plugins would create such a unitTest function. To indicate a failure the unitTest just throws an exception. Leo's plugins test suite takes care of the rest.
This section provides step-by-step instructions for turning a script button into a plugin. The plugin will define a minibuffer command that does the same thing as pressing the button.
We shall start with a script button whose script is:
g.es_print('c: %s' % (c.fileName()),color='red') g.es_print('p: %s' % (p.headString()),color='red')
Not very exciting, but it uses the predefined c and p constants. Our plugin will create a minibuffer command called print-cp.
Here are the step-by-step instructions:
Plugins--> Templates: these show recommended ways of defining plugins.-->Template for Tk plugin with per-commander controller class
Paste the tree somewhere else and rename it to @thin print_cp.py. I copied the tree to:
Plugins-->Example code-->@thin print_cp.py
Update the docstring, the __version__ constant and the << imports >> section. Note that unlike when using script buttons, you must have the following imports:
import leoGlobals as g import leoPlugins
Because this plugin doesn't require any gui interface, we simplify the init function:
def init (): leoPlugins.registerHandler('after-create-leo-frame',onCreate) return True
The init function registers the onCreate hook and returns True to indicate that it loaded properly.
Leave the onCreate function unchanged. It creates a per-commander instance of the pluginController class. This class exists mainly to bind self.c properly to a commander.
Change the constructor (__init__ method) of the pluginController class to this:
def __init__ (self,c): self.c = c c.k.registerCommand('print-cp',shortcut=None,func=self.print_cp) script = "c.k.simulateCommand('print-cp')" g.makeScriptButton(c,script=script,buttonText='Print c & p',bg='red')
This registers the print_cp method of the print_cp class as the print-cp minibuffer command, and creates a script button with the following script:
c.k.simulateCommand('print-cp')
Define the print_cp method as follows:
def print_cp (self,event=None): c = self.c ; p = c.currentPosition() g.es_print('c: %s' % (c.fileName()),color='red') g.es_print('p: %s' % (p.headString()),color='red')
The print_cp method must have the event argument as shown because it implements a minibuffer command. The print_cp method gets the proper commander from the c ivar (instance variable) and computes the current position p as shown.
Enable the print_cp plugin using the plugins manager plugin, or just add the following line somewhere in pluginsManager.txt:
print_cp.py
Test the plugin by restarting Leo (I just start test.leo). You can test the plugin by pressing the 'Print c&p' button or by typing <Alt-x> print-cp <Return>.
That's all. You can find the completed version of the print_cp plugin in leoPlugins.leo, or leoPluginsRef.leo if you are using cvs.
Naively using plugins can expose you and your .leo files to malicious attacks. The fundamental principles are:
Scripts and plugins must never blindly execute code from untrusted sources.
and:
.leo files obtained from other people may potentially contain hostile code.
Stephen Schaefer summarizes the danger this way:
I foresee a future in which the majority of leo projects come from marginally trusted sources...a world of leo documents sent hither and yon - resumes, project proposals, textbooks, magazines, contracts - and as a race of Pandora's, we cannot resist wanting to see "What's in the box?" And are we going to fire up a text editor to make a detailed examination of the ASCII XML? Never! We're going to double click on the cute leo file icon, and leo will fire up in all its raging glory. Just like Word (and its macros) or Excel (and its macros).
In other words:
When we share "our" .leo files we can NOT assume that we know what is in our "own" documents!
Not all environments are untrustworthy. Code in a commercial cvs repository is probably trustworthy: employees might be terminated for posting malicious code. Still, the potential for abuse exists anywhere.
In Python it is very easy to write a script that will blindly execute other scripts:
# Warning: extremely dangerous code # Execute the body text of all nodes that start with `@script`. def onLoadFile(): for p in c.allNodes_iter(): h = p.headString().lower() if g.match_word(h,0,"@script"): s = p.bodyString() if s and len(s) > 0: try: # SECURITY BREACH: s may be malicious! exec(s + '\n') except: es_exception()
Executing this kind of code is typically an intolerable security risk. Important: rexec provides no protection whatever. Leo is a repository of source code, so any text operation is potentially malicious. For example, consider the following script, which is valid in rexec mode:
badNode = c.currentPosition() for p in c.allNodes_iter(): << change `rexec` to `exec` in p's body >> << delete badNode >> << clear the undo stack >>
This script will introduce a security hole the .leo file without doing anything prohibited by rexec, and without leaving any traces of the perpetrating script behind. The damage will become permanent outside this script when the user saves the .leo file.