Chapter 7: Scripting Leo with Python

This chapter tells how to create Python scripts to control Leo. This chapter assumes you are a proficient Python programmer.

Installing Python
Terminology
Type conventions
Leo's Python window
A basic script
Traversing outlines
Getting and changing body text
Creating objects & changing properties
Updating the screen
Functions
The Commands class
File menu commands
Edit menu commands
Outline menu commands
Window menu commands
Menu enablers
Commands utilities
The LeoPyWindow class
The Preferences class
The vnode class
Pure Python methods

Installing Python

To use Leo's Python window just install Python 1.5.2. Python's installer will place the Python DLL where Leo will find it. Leo's Python window has been tested only with Python 1.5.2. It's not clear whether later versions will work.

To open the Python window the following files should exist in the same directory as leo.exe. These files are part of both the leoNN.zip and leosrcNN.zip. Note: leosrc.leo is the primary source for these .py files.

 leo.py  Makes Leo's commands and data available to Python
 leoEval.py  Manages Leo's Python window
 wxdb.py  Runs the pdb debugger from Leo's Python window
 sitecustomize.py  Optional site-specific customization called by ..\Python\Lib\site.py

Terminology

A Commands object (also known as a commander) represents the operations that can be performed on a single window. Each open window in Leo has its own Commands object.

A vnode represents a headline and its associated body text. vnode methods get and set headline text, body text and properties. Some scripting methods return vnodes. These routines will return None if the corresponding Leo routine returns NULL. Similarly, scripting methods use Python strings and return Python strings if the corresponding C++ routine uses the C++ String data type.

Type conventions

In the following documentation, variable names will signify the types of function arguments and returns from function:
 b  Python bool.
 c  a commander (instance of Commands class)
 [c]  a list of commanders.
 [filenames]  a list of strings containing file names.
 n  a Python short int.
 s  a Python string.
 v  a vnode (instance of vnode class) or None.
 w  instance of LeoPyWindow class.

If no value is specified for the return value of method that method returns None. None is returned for vnodes if the corresponding C++ routine would return NULL. In general, scripts should test vnode values for None.

Leo's Python window

Executing the "Open Python Window" command from Leo's Window menu brings up a window with two panes. You enter Python source code in the top pane. Pressing return executes the code in the top pane. Results are sent to the bottom pane.

To run the standard pdb debugger inside Leo's Python window import wxdb and call the wxdb.run, wxdb.runeval and wxdb.runcall routines. Output goes to the output pane. Input comes from a modal dialog. The only way to close this dialog is by typing quit.

A basic script

The following script shows how to access the data of a Leo window:

	# import classes providing access to Leo's commands and data.
	# not needed, leo is preloaded.
	import leo
	# get the commander for the topmost outline window or the main window.
	c=leo.topCommand()  # c will never be None.
	v=c.currentVnode()  # get the current node (vnode) or None.
	if v != None:
		head=v.headString() # get v's headline string
		body=v.bodyString() # get v's body string
		print "The current headline is:  ", head
		print "The current body text is: ", body

Traversing outlines

The following shows two different ways of accessing all the nodes of an outline in order. As usual, c is the commander for the window being processed.

	# method 1: Less memory
	v=c.rootVnode()
	while v:
		# do something with v
		v=v.threadNext()
	
	#method 2:  More Python-like
	for v in c.flatVlist():
		# do something with v

Getting and changing body text

As of version 1.6 Leo ensures that the text in the body pane is always current. It is no longer necessary to call c.synchVnodes().

To set text in the body pane call v.setBodyStringOrPane(). This will work whether or not v is the current vnode. Similarly, call v.setHeadlineStringorHeadline() to set the headline for any vnode.

Creating objects & changing properties

Scripts never create or destroy commanders or vnodes directly. Creating and destroying commanders or vnodes can only be done using methods of the Commands class. Scripts have no direct access to constructors or destructors.

Some vnode methods take an optional tnode parameter, but this parameter must always be empty when called from a Python script; scripts have no direct access to tnodes.

Most properties of vnodes, such as whether a vnode is marked or dirty, are set as a consequence of commands, and may not be changed directly by scripts. For example, the marked property may be set or cleared only by using Commands methods.

The "visited" property is an exception; it may be changed directly by a script. Typically, this property is used to manage tree-traversal, though scripts may use this property for any purpose. See c.clearAllVisited(), v.isVisited(), v.clearVisited() and v.setVisited(). Warning: this property may be used by any Commands method, so it does not persist.

Updating the screen

c.BeginUpdate() suppresses all drawing until the matching c.EndUpdate() is seen. These methods can be nested and each c.BeginUpdate() must have a corresponding c.EndUpdate(). Wrapping code in calls to c.BeginUpdate() and c.EndUpdate() eliminates screen flicker. c.Repaint() forces an update of the entire screen. This should seldom be necessary since all commands update the screen properly. c.EndUpdate() calls c.Repaint(), so the typical way to update the screen is simply:

c.BeginUpdate()
# code that alters the screen.
c.EndUpdate()

Note: In the upcoming wxWindows version of Leo c.BeginUpdate() and c.EndUpdate() will do nothing because wxWindows handles all screen buffering automatically.

Functions

c=leo.topCommand() returns the Commands object of the top Leo window. For example:

	import leo
	c=leo.topCommand()
	v=c.rootVnode()

[c]=leo.getCommands() returns a list of the Commands objects for all open Leo windows. For example:

	import leo
	clist=leo.getCommands()
	for c in clist:
		root=c.rootVnode()
		name=c.fileName()
		print 'The root of ', name, ' is ', root.headString()

w=leo.getLeoPyWindow() returns the window (interface) class for Leo's Python window. User scripts should not use this class.

The Commands class

The commands class represents the commands for a particular open Leo window. The following description is organized by the menus in Leo's outline window. All commands return None; use Commands methods to determine whether an operation may take place before attempting the operation.

The leo.topCommand() function returns the commander for the topmost outline window. The leo.Commands() function returns a list of commanders for all open outline windows.

The Commands class provides almost all the operations for creating, deleting, moving, cloning vnodes. The vnode class provides getters for extracting information from vnodes and setters for changing some aspects of vnodes. When there is a choice, you should use commands in the Commands class; they are safer.

Scripts have no access to Leo's interactive Find/Change commands. It is easy to write scripts that mimic these commands. Indeed, these scripts will have the full power of Python's patter matching at their disposal.

File menu commands

c.new(windowName)
b=c.open(fileName)
	# returns TRUE if file could be opened or was already open. 
	# brings the window to the front if it has already been opened.
c.close()
b=c.save(fileName)
b=c.saveAs(fileName)
b=c.saveACopyAs(fileName)
c.revert(windowName)

c.tangle()
c.tangleAll()
c.tangleMarked()

c.untangle()
c.untangleAll()
c.untangleMarked()

c.flattenOutline( [fileNames] )
c.cwebToOutline( [fileNames] )
c.importFiles( [fileNames] )
c.importMoreText( [fileNames] )
c.exportMoreText()

Edit menu commands

c.cut
c.copy
c.paste
c.delete
c.selectAll

c.editCurrentHeadline()
c.extract()
c.extractSection()
c.extractSectionNames()
c.convertBlanks()

Outline menu commands

c.cutOutline()
c.copyOutline()
c.pasteOutline()
c.deleteHeadline()
c.insertHeadline()
c.clone()

c.contractSubheads()
c.contractAllSubheads()
c.contractAllHeadlines()

c.expandAllHeadlines()
c.expandAllSubheads()
c.expandSubheads()

c.expandLevel1()
c.expandLevel2()
c.expandLevel3()
c.expandLevel4()
c.expandLevel5()
c.expandLevel6()
c.expandLevel7()
c.expandLevel8()
c.expandLevel9()
c.expandNextLevel()

c.moveOutlineLeft()
c.moveOutlineRight()
c.moveOutlineUp()
c.moveOutlineDown()

c.promote()
c.demote()

c.selectThreadBack()
c.selectThreadNext()
c.selectVisBack()
c.selectVisNext()

c.markHeadline()
c.markSubheads()
c.markChangedHeadlines()
c.markChangedRoots()
c.unmarkAll()

c.goToNextDirtyHeadline()
c.goToNextMarkedHeadline()

Window menu commands

c.equalSizedPanes()

Menu enablers

The following routines all return true if the corresponding Menu command should be enabled. The actual menu commands themselves silently do nothing if they are called when the corresponding canXXX routines return false.

b=canContractAllHeadlines()
b=canContractAllSubheads()
b=canContractSubheads()
b=canCutOutline()
b=canDeleteHeadline()
b=canDemote()
b=canExpandAllHeadlines()
b=canExpandAllSubheads()
b=canExpandSubheads()
b=canExtractSection()
b=canExtractSectionNames()
b=canGoToNextDirtyHeadline ()
b=canGoToNextMarkedHeadline()
b=canMarkChangedHeadlines()
b=canMarkChangedRoots()
b=canMoveOutlineDown()
b=canMoveOutlineLeft()
b=canMoveOutlineRight()
b=canMoveOutlineUp()
b=canPasteOutline()
b=canPromote()
b=canRevert()
b=canSelectVisBack()
b=canSelectVisNext()
b=canSelectThreadBack()
b=canSelectThreadNext()
b=canSelectToEnd()
b=canSelectToStart()
b=canShiftBodyLeft()
b=canShiftBodyRight()
b=canUndo()
b=canUnmarkAll()

Commands Utilities

These utilities are important. There is no need to call them when using Leo's own commands, and they will come in handy when creating your own commands.

# Drawing utilities...
c.BeginUpdate() # suppress screen updates.
c.EndUpdate() # enable screen updates and redraw the screen.
c.Repaint() # force the screen to be redrawn.
c.BringToFront()

Enclose code in BeginUpdate/EndUpdate pairs to inhibit drawing while the screen is being changed. BeginUpdate/EndUpdate may be nested and each BeginUpdate must be matched with a corresponding EndUpdate call. EndUpdate calls Repaint automatically. You can call Repaint to force the screen to be updated immediately though all commands repaint the screen properly. c.bringToFront() brings the window corresponding to the command class to the front. A later leo.topCommand() will return c.

# Getters...
v=c.currentVnode() # The currently selected vnode or None.
v=c.rootVnode() # The first vnode of the outline or None.
b=c.isChanged() # True if the outline has been changed since it was saved.
s=c.fileName() # The file to which the Save command will write the window.
	#May be "Untitled"

# Setters...
c.bringToFront() # Bring the window to the front. see leo.topCommand().
c.clearAllVisited() # Clear all "visited" bits.
c.editVnode(v) # Enter editing mode for v's headline.
c.endEditing(v) # End editing mode for v's headline.
c.makeVisible(v) # Expand outline if necessary to make v visible.
c.selectVnode(v)  # Make v the presently selected vnode.
c.synchVnode()  # No longer used.  Retained for compatibility with old scripts.

The LeoPyWindow class

This class exists to handle the interaction between the Python interpreter and Leo's Python window. Your scripts should not use this class.

w=leo.getLeoPyWindow() # get the Python Window (interaction) class.
s=w.read() # Return what the user has already typed.
s=w.readline() # Put up a modal dialog to get input.
w.setStatus(n) # Controls Leo's Python window.
w.write(s) # Write s to the output pane of the Python window.

The Preferences class

This class provides access to the Preference settings and the contents of the log window.

# Getters...
p = leo.getPrefs() # returns instance of the Prefs class
b=p.defaultTangleDirectory()
b=p.doLeoDoneBat()
b=p.doLeoUnBat()
s=p.logWindowString() # returns contents of log window
n=p.pageWidth()
b=p.tangleOutputsHeaderLine()
b=p.tangleOutputsDocChunks()

# Setters...
p.setDefaultTangleDirectory(s)
p.setDoLeoDoneBat(b)
p.setDoLeoUnBat(b)
p.setPageWidth(n)
p.setTangleOutputsHeaderLine(b)
p.setTangleOutputsDocChunks(b)

The vnode class

The vnode class represents a node containing headline text, body text and various properties.

# Getters returning vnodes... May return None.
# It is more "Python-like" to use lists.
# See the "High-level" functions below.
v=v.next() # the previous sibling of v.
v=v.back() # the next sibling of v.
v=v.currentVnode() # the presently selected vnode.
v=v.findRoot() # the root vnode.
v=v.firstChild() # the first child of v.
v=v.lastChild() # the last child of v.
v=v.lastNode() # the last node of the subtree of which v is the root.
v=v.nodeAfterTree()  # v.lastNode.threadNext
v=v.nthChild(n)  # the n'th child of v, starting at index 0.
v=v.parent()  # the parent node of v.  Level 0 nodes have no parent.
v=v.threadBack()  # the node before v.
v=v.threadNext()  # the node after v.
v=v.visBack() # the visible node before v.
v=v.visNext() # the visible node after v.

# Getters returning Python strings...
s=v.bodyString()  # the body string of v.
	# Warning:  call c.synchVnode() before calling v.bodyString().
s=v.convertTreeToString()  # Converts v's subtree to an ascii string.
s=v.headString() # the headline string of v.
# These two are of limited use: they are called by c.convertTreeToString().
s=v.moreBody()  # the body text of v in MORE format.
s=v.moreHead(n)  # n = the first level of the converted string

# Getters returning Python (short) ints...
n=v.childIndex()
n=v.numberOfChildren()
n=v.level()

# Getters returning properties (bools)...
b=v.hasChildren()
b=v.isAncestorOf(v2) # true if v2 is a child, grandchild, etc. of v.
b=v.isCloned()
b=v.isDirty()
b=v.isExpanded()
b=v.isMarked()
b=v.isVisible()
b=v.isVisited()

# Setters altering headline or body text...
v.setBodyTextOrPane(s)
	# Sets the body text of v, whether or not v is the current node.
v.setHeadStringOrHeadline(s)
	# Replaces the headline text of v, whether or not v is the current node.
v.trimTrailingLines()  # Removes all blanks lines from v.
	# Warning: will not work if v is the current vnode.

# Setters 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

# Other setters...
v.setIcon() #Redraw v's icon.  Called automatically by commands.

# The "visited bit" may be used by commands or scripts for any purpose.
# This bit does not persist: commands use this bit for tree traversal.
# See also: c.clearAllVisited()
v.clearVisited()
v.setVisited()

Pure Python methods

The following methods are implemented solely in Python; there is no corresponding C code. You are free to change them as you please.

c.flatVlist() returns a flat list of all vnodes contained in c's window.

	def flatVlist(self):
		result = []
		v=self.rootVnode();
		while v:
			result.append(v)
			v=v.threadNext()
		return result

c.printFlatVlist(list) prints a list created by c.flatVlist().

	def printFlatVlist(self,list):
		printFlatVlist(list)

c.printVlist(list) prints a list created by c.vlist().

	def printVlist(self,list):
		self.printVlistInner(list)
		print "\n"

	def printVlistInner(self,list):
		if list == None or list == []:
			return
		elif type(list) == type([]):
			print "[",
			for i in range(len(list)):
				self.printVlistInner(list[i])
				if i + 1 < len(list):
					print ", ",
			print "]",
		else:
			print list.headString(),

c.vHeadList() contains a list similar to c.vlist() containing headline strings rather than vnode instances. In the example given for c.vlist() (see below) the resulting list would be: ['a' ['b' 'c'] 'd' 'e' ['f' ['g'] 'h']]

	def vHeadList(self):
		result = []
		v = self.rootVnode()
		while v != None:
			self.makeVHeadList(v, result)
			v = v.next()
		return result

	def makeVHeadList(self, obj, result):
		if obj == None or obj == []:
			return
		elif obj.hasChildren():
			innerResult = [] #children are represented as lists
			result.append(obj.headString())
			for child in obj.children():
				self.makeVHeadList(child,innerResult)
			result.append(innerResult)
		else:
			result.append(obj.headString())

c.vlist() creates a structured list of all vnodes in c's window. Each entry is an instance of the vnode class. Each node is followed by a list of its children; empty lists are not created. For example, if the window looks like:

	a
		b
		c
	d
	e
		f
			g
		h

the list returned by c.vlist() will be: [a [b c] d e [f [g] h]]

	def vList(self):
		result = []
		v = self.rootVnode()
		while v != None:
			self.makeVList(v, result)
			v = v.next()
		return result

	def makeVList(self, obj, result):
		if obj == None or obj == []:
			return
		elif obj.hasChildren():
			innerResult = [] #children are represented as lists
			result.append(obj)
			for child in obj.children():
				self.makeVList(child,innerResult)
			result.append(innerResult)
		else:
			result.append(obj)

v.children() returns a list of the children of v.

	def children(self):
		result = []
		child = self.firstChild()
		while child != None:
			result.append(child)
			child=child.next()
		return result