This chapter tells how to create Python scripts to control the Python version of Leo, leo.py. The appendix to this Chapter discusses how to create Python scripts for the Borland version of Leo.
This chapter assumes you are a proficient Python programmer.
Leo's source code is a collection classes, along with utility functions in leoGlobals.py. The application class represents the entire Leo application. The ivars (instance variables) of this class represent Leo's global variables. A commander (an instance of the Commands class) represents the operations that can be performed on a particular window. That is, each open Leo window has its own commander. A frame (an instance of the LeoFrame class) contains all the internal data needed to manage a Leo window. A vnode represents a headline and its associated body text. There are vnode methods to get and set headline text, body text and properties.
These classes have ivars that point to other classes. For example, if f is a frame and c is a commander, f.commands is the commander for f, and c.frame is the frame associated with c.
The app() function returns the singleton instance of the application class. The app().windowList ivar is the list of all open frames. The top() function returns the commander of the topmost window, i.e., the commander of the presently active window. For example:
from leoGlobals import app, top a=app() # The singleton instance. windows = a.windowList # The list of all open frames for w in windows: print w print w.commands print w.commands.rootVnode() print top()
The following script shows how to access the data in vnodes:
from leoGlobals import top c=top() v=c.currentVnode() # get the current vnode. head = v.headString() # get the headline text of v. body v.bodyString() # get the body text of v. v.setBodyStringOrPane(body) # set body text of v to s. v.setHeadStringOrHeadline(head) # set headline text of v to s.
The following shows how to access all the vnodes in outline order.
from leoGlobals import top v=top().rootVnode() while v: print v # do something with v... v=v.threadNext() # set v to the next vnode in outline order.
All of Leo's commands update the screen so as to eliminate flicker. That is, commands cause the outline pane to be redrawn only once. This is typically done using c.beginUpdate() and c.endUpdate().
c.beginUpdate() suppresses all drawing in the outline pane until the matching c.endUpdate() is seen. These methods can be nested; each c.beginUpdate() must have a corresponding c.endUpdate(). c.endUpdate takes an optional parameter. If this parameter is false, no redrawing is done. This can sometimes be useful; inner code can tell other code whether redrawing is actually required. c.redraw() forces an update of the entire screen. c.endUpdate() calls c.redraw() as needed.
Leo dispatches commands using the frame.doCommand and "Menu Command Handlers" defined in the node by the same name in @node leoFrame.py in LeoPy.leo. This node contains the menu handlers for all of Leo's menu commands, organized by menu and submenu. I recommend that your scripts use frame.doCommand for the following reasons:
For example, here is how you can execute the Mark command using frame.doCommand:
from leoGlobals import top c = top() ; f = c.frame f.doCommand(f.OnMark,label="markheadline") # Executes command and calls hooks.
Your scripts may also call command methods directly, like this:
from leoGlobals import top top().markHeadline() # Executes the command without calling hooks.
If you want to invoke commands without calling frame.doCommand, you should study the corresponding menu command handler to see what may be required. Among the details hidden by the command handlers is this: commanders create the following subcommanders to handle complex commands:
Your scripts may also get one of the following prefs ivars in a commander.
c.tangle_batch_flag c.untangle_batch_flag c.use_header_flag c.output_doc_flag c.page_width c.tab_width c.tangle_directory c.target_language
LeoPrefs.ivars is a list of the names of these ivars. Each commander maintains its own preferences. The prefs panel updates its commander's ivars as needed. For example:
from leoGlobals import top
from leoPrefs import ivars
c = top()
print "\n\nPrefs ivars...\n"
for ivar in ivars:
exec("val=c."+ivar) # do val = c.ivar
print "c.%-20s = %s" % (ivar,`val`)
Your scripts may also set preferences by setting one of the following commands ivars: c.tangle_batch_flag, c.untangle_batch_flag, c.use_header_flag, c.output_doc_flag, c.page_width, c.tab_width, c.tangle_directory, c.target_language.
If the prefs panel is open, your script may call f.prefsPanel.init(c) to update the corresponding prefs panel ivars. For example:
from leoGlobals import top c = top() ; f = c.frame c.tab_width = 4 # Change this and see what happens. # If the prefs panel is open, your script may call # f.prefsPanel.init(c) to update it. if f.prefsPanel: f.prefsPanel.init(c) # If your script sets c.tab_width your script may call # f.setTabWidth to redraw the screen. f.setTabWidth(c.tab_width)
The file leoFindScript.py contains functions for finding and changing text from within scripts. See the @file leoFindScript.py tree in LeoPy.leo for full details. Here we shall provide only two examples:
Example 1: The findall function returns a list of tuples (v,pos) describing matches in c's entire tree.
from leoGlobals import top, get_line_after
from leoFindScript import findAll
pattern = "from leoGlobals import"
result = findAll(top(),pattern,bodyFlag=1)
print "\n\n%-3d instances of: '%s'...\n" % (len(result),pattern)
for v,pos in result:
body = v.bodyString()
print "\n%-4d %s" % (pos,v.headString())
print get_line_after(body,pos)
Example 2: The reFindall function returns a list of tuples (v,mo,pos), where mo is a "Match Object". The reFlags argument are flags to re.search():
from leoGlobals import top,get_line_after from leoFindScript import reFindAll pattern = "from .* import" result = reFindAll(top(),pattern,bodyFlag=1,reFlags=None) print "\n\n%-3d instances of: '%s'...\n" % (len(result),pattern) for v,mo,pos in result: body = v.bodyString() print "\n%-4d %s" % (pos,v.headString()) print get_line_after(body,pos)
Most scripts will use the vnode class methods. For a complete list, see the @file leoNodes.py tree of LeoPy.leo.
Getters: Here are the most useful vnode methods for getting information from vnodes.
# Returning commanders... v.commands() # The commander for v. # Returning vnodes...These may return None. v.next() # the previous sibling of v. v.back() # the next sibling of v. v.firstChild() # the first child of v. v.lastChild() # the last child of v. v.lastNode() # the last node of the subtree of which v is the root. v.nodeAfterTree() # v.lastNode.threadNext v.nthChild(n) # the n'th child of v, starting at index 0. v.parent() # the parent node of v. Level 0 nodes have no parent. v.threadBack() # the node before v. v.threadNext() # the node after v. v.visBack() # the visible node before v. v.visNext() # the visible node after v. # Returning strings... v.bodyString() # the body string of v. v.headString() # the headline string of v. # Returning ints... v.childIndex() # v's child index. The first child has index 0. v.numberOfChildren() # the number of children v has. v.level() # the number of ancestors v has. # Returning bools (property bits)... v.hasChildren() v.isAncestorOf(v2) # true if v2 is a child, grandchild, etc. of v. v.isCloned() v.isDirty() v.isExpanded() v.isMarked() v.isVisible() v.isVisited()
Setters: Here are the most useful vnode methods that set vnode data or properties.
# Convenience methods for altering headline or body text... # These work whether or not v is the presently selected node. v.setBodyTextOrPane(s) # Sets the body text of v. v.setHeadStringOrHeadline(s) # Sets the headline text of v. # Moving nodes... v.moveAfter(v2) # move v after v2. v.moveToNthChildOf(v2,n) # move v to the n'th child of v2. v.moveToRoot() # make v the root vnode. # The "visited bit" may be used by commands or scripts for any purpose. # Many commands use this bits for tree traversal, so these bits do not persist. c.clearAllVisited() # Clears all visited bits in c's tree. v.clearVisited() v.setVisited()
Example: Here is an example of code that uses vnode methods:
# Return a list of v's children. def children (v): result = [] child = v.firstChild() while child: result.append(child) child=child.next() return result
The file leoGlobals.py contains a large number of utility functions. We have already discussed the app() and top() functions. true and false are synonyms for True and False (1 and 0 before Python 2.2). The es(s) function puts s+'\n' to the log pane. es(s,newline=false) puts s to the log pane. For example, here are two different ways of writing "Hello World" to the log pane:
from leoGlobals import es,false
es("Hello World")
es("Hello",newline=false) ; es(" World")
For a list of all these functions defined in leoGlobals.py, execute the following script:
# Print all the symbols defined in leoGlobals.
import leoGlobals
print "\n\nNames defined in leoGlobals.py...\n"
names = leoGlobals.__dict__.keys()
names.sort()
for name in names:
print name
Unless you are familiar with the names of all these functions it may be more comfortable to import just the names you want, like this:
from leoGlobals import app, es, top # And whatever else you want to use...
For complete details of all routines defined in leoGlobals.py, see LeoPy.leo.
leoGlobals.py defines 6 convenience methods for redirecting stdout and stderr:
redirectStderr() # Redirect stderr to the log pane. redirectStdout() # Redirect stdout to the log pane. restoreStderr() # Restore stderr to the console window. restoreStdout() # Restore stdout to the console window. stdErrIsRedirected() # Return true if the stderr stream goes to the log pane. stdOutIsRedirected() # Return true if the stdout stream goes to the log pane.
For example:
from leoGlobals import redirectStderr, redirectStdout redirectStderr() # Redirect stderr to the current log pane. redirectStdout() # Redirect stdout to the current log pane.
Calls need not be paired. Redundant calls are ignored and the last call made controls where output for each stream goes. It may be convenient to redirect output in customizeLeo.py. This can be done as follows:
if tag == "start1": from leoGlobals import redirectStdout, redirectStderr redirectStdout() # Redirect stdout redirectStderr() # Redirect stderr