Startup
@chapters
@settings
@enabled-plugins
@bool warn_about_redefined_shortcuts = False
vim plugin
@string vim_cmd = c:\vim\vim63\gvim --servername LEO
@string vim_exe = c:\vim\vim63\gvim
buttons
@button Remove newlines
@@button prev-show @key=Alt-4
@@button next-show @key=Alt-5
@@button prev-slide @key=Alt-6
@@button next-slide @key=Alt-7
@@button make-slide @key=Alt-0
Dummy clones for slide show: do not delete!
treeSelectHelper
contractNode
contractNodeOrGoToParent
expandNode
expandNodeAnd/OrGoToFirstChild
goToParent
p.moveToVisBack
checkVisBackLimit
p.moveToVisNext
checkVisNextLimit
selectVisBack
selectVisNext
Other
Sounds
@url http://mail.python.org/pipermail/python-list/2004-October/248738.html
@url c:\prog\Sounds\s1.wav
@url c:\prog\Sounds\s2.wav
Scripting with Leo (do not delete)
Leo's classes and objects
c, g and p
g.app
commanders: c
frames: c.frame, c.frame.body, etc.
positions and iterators
vnodes
Useful methods
g.es writes to the log tab
g.es can write to other tabs
app.windowList: the list of all open frames
Getting and setting headline and body text
Copying positions
Traversing outlines with generators.
c.all_positions
p.children_iter
p.parents_iter & p.self_and_parents_iter
p.siblings_iter & p.following_siblings_iter
p.subtree_iter & p.self_and_subtree_iter
Testing whether a position is valid
Updating the screen
Invoking commands from scripts
Getting settings from @settings trees
Getting and setting preferences
Functions for finding and changing text from scripts
Running Leo in batch mode
Functions defined in leoGlobals.py
Redirecting output from scripts
Adding new Tabs to the log pane
Summary of the vnode and position classes
Iterators
Getters
Setters
Creating script buttons
\@test and @suite nodes
\@test nodes
\@suite nodes
conclusion
Introducing Leo (old)
Leo looks like an ordinary outliner
But no other program can do what Leo does
Leo outlines shows you your data *your* way
@url file:c:\leo.repo\trunk\leo\core\leoPy.leo --> Projects
Leo scripts create new ways to handle data
Any node can contain a Python script
Leo scripts have full access to outline data
Some example scripts
A first script
It is easy to access nodes
It is easy to traverse Leo outines
The execute-script command *composes* scripts from outlines
This script is formed from a tree
class myClass
printHi
Leo can compose external files from outlines
@thin ..\test\slide-show-file.py
Execute slide-show-file.py
You can apply scripts to data
Nodes can be typed
@html Leo's home page
Leo changes how I work
Some tasks disappear
More interesting tasks arise
Leo plays well with others
Conclusions
What people do with Leo
Understand complex programs more easily
Organize scripts
Manage web sites
@url http://webpages.charter.net/edreamleo/leo_toc.html
Store scientific data
Views *are* mathematical relations
@image Lion image
@image C:\leo.repo\trunk\leo\Icons\Lioness.jpg
@html Lion image
15-20 minutes: Introducing Leo (OLD)
What people are saying about Leo
Leo is revolutionary
Leo is fun, even addicting
Leo is a great outliner
Leo is a great programming tool
Leo is a great PIM
Leo is a great documentation tool
Leo clarifies complex systems
Leo's screen
What makes Leo special?
1. Leo outlines
Clones make Leo outlines special
Clones are nodes appearing in several places in the outline
A
B
C
D
Clones create multiple views of data
Here is an example of a view
Fixed contractOrGoRight
The bug
What I did
There is no such thing as *the* right view of data
Outlines can represent external files
@@thin leoNodes.py
<< imports >>
class vnode
<< vnode constants >>
v.Birth & death
v.__init
v.__repr__ & v.__str__
v.dump
v.__hash__ (only for zodb)
v.Comparisons
v.findAtFileName
anyAtFileNodeName
at...FileNodeName & tests
@test v.atAutoNodeName & v.atAutoRstNodeName
isAtAllNode
isAnyAtFileNode
isAt...FileNode (vnode)
isAtIgnoreNode
isAtOthersNode
matchHeadline
v.Getters
v.bodyString
v.Children
v.firstChild
v.hasChildren & hasFirstChild
v.lastChild
v.nthChild
v.numberOfChildren
v.directParents
v.hasBody
v.headString & v.cleanHeadString
v.Status Bits
v.isCloned
v.isDirty
v.isExpanded
v.isMarked
v.isOrphan
v.isSelected
v.isTopBitSet
v.isVisited
v.isWriteBit
v.status
v.Setters
v.Dirty bits
v.clearDirty
v.findAllPotentiallyDirtyNodes
v.setAllAncestorAtFileNodesDirty
v.setDirty
v.Status bits
v.clearClonedBit
v.clearMarked
v.clearWriteBit
v.clearOrphan
v.clearVisited
v.contract & expand & initExpandedBit
v.initStatus
v.setClonedBit & initClonedBit
v.setMarked & initMarkedBit
v.setOrphan
v.setSelected
v.setVisited
v.setWriteBit
v .setBodyString & v.setHeadString
v.setFileIndex
v.computeIcon & setIcon
v.setSelection
v.Low level methods
v._addLink & helper
v._addParentLinks
v._cutLink
v._cutParentLinks
v._linkAsNthChild (used by 4.x read logic)
v.createOutlineFromCacheList & helpers
v.fastAddLastChild
v.Properties
v.b Property
v.h property
v.u Property
v.gnx Property
class nodeIndices
nodeIndices.__init__
areEqual (no longer used)
get/setDefaultId
getNewIndex
isGnx (not used)
scanGnx
setTimeStamp
toString
class position
<< about the position class >>
p.ctor & other special methods...
p.__init__
p.__eq__ & __ne__
p.__getattr__ (no longer used)
p.__nonzero__ & __bool__
p.__str__ and p.__repr__
p.archivedPosition
p.copy
p.dump
p.key
p.Properties
p.b property
p.h property
p.gnx property
p.Getters
p.vnode proxies
p.Comparisons
p.Headline & body strings
p.Status bits
p.children & parents
p.childIndex
p.directParents
p.hasChildren & p.numberOfChildren
p.getX & vnode compatibility traversal routines
p.hasX
hasThreadNext (the only complex hasX method)
p.findRootPosition
p.isAncestorOf
p.isCloned
p.isRoot
p.isVisible
p.level & simpleLevel
p.textOffset
p.Setters
p.Vnode proxies
p.Status bits
p.computeIcon & p.setIcon
p.setSelection
p.setBodyString & setHeadString
p.Visited bits
p.clearVisitedInTree
p.clearAllVisitedInTree
p.Dirty bits
p.clearDirty
p.findAllPotentiallyDirtyNodes
p.inAtIgnoreRange
p.setAllAncestorAtFileNodesDirty
p.setDirty
p.File Conversion
p.convertTreeToString
p.moreHead
p.moreBody
p.generators
p.children
p.following_siblings
p.nodes
p.parents
p.self_and_parents
p.self_and_siblings
p.self_and_subtree
p.subtree
p.unique_nodes
p.unique_subtree
p.Moving, Inserting, Deleting, Cloning, Sorting
p.clone
p.copyTreeAfter, copyTreeTo
p.doDelete
p.insertAfter
p.insertAsLastChild
p.insertAsNthChild
p.invalidOutline
p.moveAfter
p.moveToFirst/LastChildOf
p.moveToNthChildOf
p.moveToRoot
p.validateOutlineWithParent
<< validate parent ivar >>
<< validate childIndex ivar >>
<< validate x ivar >>
p.moveToX
p.moveToBack
p.moveToFirstChild
p.moveToLastChild
p.moveToLastNode
p.moveToNext
p.moveToNodeAfterTree
p.moveToNthChild
p.moveToParent
p.moveToThreadBack
p.moveToThreadNext
p.moveToVisBack
checkVisBackLimit
p.moveToVisNext
checkVisNextLimit
p.Low level methods
p._adjustPositionBeforeUnlink
@test p.adjustPositionBeforeUnlink
node 1
node 1-1
node 1-1-1
node 1-1-2
node 1-2
node 1-2-1
node 1-2-2
node 2
node 2-1
node 2-1-1
node 2-1-2
node 2-2
node 2-2-1
node 2-2-2
node 3
node 3-1
node 3-1-1
node 3-1-2
node 3-2
node 3-2-1
node 3-2-2
p._linkAfter
p._linkAsNthChild
p._parentVnode
p._linkAsRoot
p._unlink
p.badUnlink
p.makeCacheList
class poslist
select_h
select_b
2. Leo scripts
Scripts have full access to outlines
An example script
The @x convention: headlines are metadata
Examples
@html Leo's home page
@url http://webpages.charter.net/edreamleo/front.html
@test add-tab-to-lines
work
before sel=2.0,5.6
after sel=2.0,5.10
@button count-headline
Conclusions
@url http://webpages.charter.net/edreamleo/front.html
Description
Leo is really simple
The elevator talk
Leo's creation talk
The essence of Leo existed before Leo did
Example markup
@@file mClass.py
<< A longish docstring >>
<< imports >>
ctor
doSomething
Example script
Scripts have full access to outlines
2-5 Minutes: Communication
Speakers Notes
Who am I?
Who are you?
Intension: to give you the Leo Aha
What people are saying about Leo
The elements
Leo is a new playground, and a new set of tools
Surprising results
Contact me
References
To do
Bug double-click-icon-box is not selecting the proper node.
Bug contracting a node should redraw the screen
newHeadline
Slide shows
5-10 minutes: Managing an Open Source Project
Put your project first
Encourage others
Focus on simplicity
Avoid politics
Spend 1/2 of your time communicating about your project
@url http://www.cs.virginia.edu/~robins/YouAndYourResearch.html
@image 15-20 minutes: The Leo Aha
Leo's users say...
revolutionary
fun, even addicting
a great outliner
a great editor and IDE
a great PIM
a great scripting tool
a great documentation tool
a great way to understand complex systems
Leo's screen
Elements
Outlines
Clones: The same node can appear in many places in an outline
A
B
C
D
A little bit of markup
@@thin leoNodes.py
<< imports >>
class vnode
<< vnode constants >>
v.Birth & death
v.__init
v.__repr__ & v.__str__
v.dump
v.__hash__ (only for zodb)
v.Comparisons
v.findAtFileName
anyAtFileNodeName
at...FileNodeName & tests
@test v.atAutoNodeName & v.atAutoRstNodeName
isAtAllNode
isAnyAtFileNode
isAt...FileNode (vnode)
isAtIgnoreNode
isAtOthersNode
matchHeadline
v.Getters
v.bodyString
v.Children
v.firstChild
v.hasChildren & hasFirstChild
v.lastChild
v.nthChild
v.numberOfChildren
v.directParents
v.hasBody
v.headString & v.cleanHeadString
v.Status Bits
v.isCloned
v.isDirty
v.isExpanded
v.isMarked
v.isOrphan
v.isSelected
v.isTopBitSet
v.isVisited
v.isWriteBit
v.status
v.Setters
v.Dirty bits
v.clearDirty
v.findAllPotentiallyDirtyNodes
v.setAllAncestorAtFileNodesDirty
v.setDirty
v.Status bits
v.clearClonedBit
v.clearMarked
v.clearWriteBit
v.clearOrphan
v.clearVisited
v.contract & expand & initExpandedBit
v.initStatus
v.setClonedBit & initClonedBit
v.setMarked & initMarkedBit
v.setOrphan
v.setSelected
v.setVisited
v.setWriteBit
v .setBodyString & v.setHeadString
v.setFileIndex
v.computeIcon & setIcon
v.setSelection
v.Low level methods
v._addLink & helper
v._addParentLinks
v._cutLink
v._cutParentLinks
v._linkAsNthChild (used by 4.x read logic)
v.createOutlineFromCacheList & helpers
v.fastAddLastChild
v.Properties
v.b Property
v.h property
v.u Property
v.gnx Property
class nodeIndices
nodeIndices.__init__
areEqual (no longer used)
get/setDefaultId
getNewIndex
isGnx (not used)
scanGnx
setTimeStamp
toString
class position
<< about the position class >>
p.ctor & other special methods...
p.__init__
p.__eq__ & __ne__
p.__getattr__ (no longer used)
p.__nonzero__ & __bool__
p.__str__ and p.__repr__
p.archivedPosition
p.copy
p.dump
p.key
p.Properties
p.b property
p.h property
p.gnx property
p.Getters
p.vnode proxies
p.Comparisons
p.Headline & body strings
p.Status bits
p.children & parents
p.childIndex
p.directParents
p.hasChildren & p.numberOfChildren
p.getX & vnode compatibility traversal routines
p.hasX
hasThreadNext (the only complex hasX method)
p.findRootPosition
p.isAncestorOf
p.isCloned
p.isRoot
p.isVisible
p.level & simpleLevel
p.textOffset
p.Setters
p.Vnode proxies
p.Status bits
p.computeIcon & p.setIcon
p.setSelection
p.setBodyString & setHeadString
p.Visited bits
p.clearVisitedInTree
p.clearAllVisitedInTree
p.Dirty bits
p.clearDirty
p.findAllPotentiallyDirtyNodes
p.inAtIgnoreRange
p.setAllAncestorAtFileNodesDirty
p.setDirty
p.File Conversion
p.convertTreeToString
p.moreHead
p.moreBody
p.generators
p.children
p.following_siblings
p.nodes
p.parents
p.self_and_parents
p.self_and_siblings
p.self_and_subtree
p.subtree
p.unique_nodes
p.unique_subtree
p.Moving, Inserting, Deleting, Cloning, Sorting
p.clone
p.copyTreeAfter, copyTreeTo
p.doDelete
p.insertAfter
p.insertAsLastChild
p.insertAsNthChild
p.invalidOutline
p.moveAfter
p.moveToFirst/LastChildOf
p.moveToNthChildOf
p.moveToRoot
p.validateOutlineWithParent
<< validate parent ivar >>
<< validate childIndex ivar >>
<< validate x ivar >>
p.moveToX
p.moveToBack
p.moveToFirstChild
p.moveToLastChild
p.moveToLastNode
p.moveToNext
p.moveToNodeAfterTree
p.moveToNthChild
p.moveToParent
p.moveToThreadBack
p.moveToThreadNext
p.moveToVisBack
checkVisBackLimit
p.moveToVisNext
checkVisNextLimit
p.Low level methods
p._adjustPositionBeforeUnlink
@test p.adjustPositionBeforeUnlink
node 1
node 1-1
node 1-1-1
node 1-1-2
node 1-2
node 1-2-1
node 1-2-2
node 2
node 2-1
node 2-1-1
node 2-1-2
node 2-2
node 2-2-1
node 2-2-2
node 3
node 3-1
node 3-1-1
node 3-1-2
node 3-2
node 3-2-1
node 3-2-2
p._linkAfter
p._linkAsNthChild
p._parentVnode
p._linkAsRoot
p._unlink
p.badUnlink
p.makeCacheList
class poslist
select_h
select_b
Scripts
Python
A real DOM
A brag
Consequences
There is no such thing as *the* right view of data
Fixed contractOrGoRight
The bug
What I did
selectVisNext
selectVisBack
goToParent
contractNodeOrGoToParent
expandNodeAnd/OrGoToFirstChild
contractNode
expandNode
treeSelectHelper
p.moveToVisBack
checkVisBackLimit
p.moveToVisNext
checkVisNextLimit
Outlines create external files
The @x convention: headlines are metadata
Examples
@html Leo's home page
@url http://webpages.charter.net/edreamleo/front.html
@image Lion image
@test fail
@test add-tab-to-lines
work
before sel=2.0,5.6
after sel=2.0,5.10
@button count-headline
Conclusions
Contact
c.k.simulateCommand('prev-slide-command')
c.k.simulateCommand('next-slide-show-command')
from PMIDI import * from time import sleep
@color

@ A point that can cause confusion:

The slideshow.py plugin defines four commands whose names end in -command. It would be possible to bind shortcuts for *these* commands in leoSettings.leo or myLeoSettings.leo

The buttons below define commands whose names do *not* end in -command. These button define shortcuts for these buttons using the @key syntax.
c.k.simulateCommand('prev-slide-show-command')
c.k.simulateCommand('next-slide-command')
@language rest @ The @language **directive** shown above means that this slide show is syntax-colored as reStructured text, a popular markup language. @c

This is the first slide of a slideshow introducing Leo.

@ I'm not going to read everything on every slide. So pay attention :-)

c.beginUpdate()

try: p1 = p.copy() undoData = c.undoer.beforeChangeTree(p1) for p in c.currentPosition().self_and_subtree_iter(): h = p.headString().strip() if not h.startswith('@slide'): p.setHeadString('@slide ' + h) c.undoer.afterChangeTree(p1,'make-slide',undoData) finally: c.endUpdate()
@language python

@nocolor-node

This slideshow describes the most useful functions, classes and methods in leo.py. Keep in mind that *your scripts have complete access to all of Leo's source code*, that is, all the code in LeoPy.leo. Your scripts can use Leo's code in whatever ways are convenient. You are *not* limited by what you see in this chapter.

**Important:** You can execute all the code in this slideshow. Just select the code you want to execute and run the Execute Script command.

**Important**: Python is finicky about indentation, so you must be careful to select the leading whitespace of the first line.
# Note: Everything between '@' and '@c' is a comment.

@ Leo's source code is a collection of classes, and utility functions. @c
# The g.es method prints its arguments to the Log tab of the log pane: c.frame.log.clearTab('Log') g.es("Hello world")

# g.es converts non-string arguments using repr: g.es(c)

# g.es prints multiple arguments separated by commas: g.es("Hello","world")
# g.app.windowlist is the list of the frames of all open windows.

aList = g.app.windowList # get the list of all open frames. g.es("windows...") for f in aList: print(f) print(f.c) print(f.shortFileName())
# For any position p, p.h is the headline and p.b is the body text. print("head:",p.h) print("body:",p.b)

p.setBodyString(p.b) # set p's body text. p.setHeadString(p.h) # set p's headline text.
@ The proper way to traverse an outline is with one of Leo's generators.
# Scripts and plugins should use p.copy() to 'capture' the value of a position:

p2 = p.copy() # Correct: p2 will not change when p changes later. p2 = p # Wrong. p2 will change after this assignment.

# For example, this creates a dictionary of positions, indexed by vnode.

d = {} for p in c.all_positions(): d[p.v] = p.copy()

print(d.get(c.rootPosition().v))

# **Warning** This dictionary will become invalid when the outline changes!
@nocolor The c.allNodes_iter iterator returns a list of all positions in the outline. This script makes a list of all the nodes in an outline: @color

nodes = [p for p in c.allNodes_iter()] g.es("This outline contains %d nodes" % len(nodes))

@nocolor Here is one way to count the nodes of an outline: @color

count = 0 for p in c.allNodes_iter(): count += 1 g.es("This outline contains %d nodes" % count)

@nocolor Here is a way to count the *distinct* vnodes of an outline: @color

positions = 0 ; tnodes = {} for p in c.allNodes_iter(): positions += 1 if not tnodes.get(p.v.t): tnodes[p.v.t] = p.v.t g.es("%8s positions" % positions) g.es("%8s vnodes" % len(tnodes.keys()))
@nocolor The p.children_iter iterator returns a list of all children of position p: @color

parent = p.parent() g.es("children of %s" % parent.headString(),color="purple") for p in parent.children_iter(): g.es(p.headString())
@nocolor The p.parents_iter iterator returns a list of all parents of position p, excluding p: @color

current = p.copy() g.es("inclusive parents of %s" % current.headString(),color="purple") for p in current.self_and_parents_iter(): g.es(p.headString())

@nocolor The p.self_and_parents_iter iterator returns a list of all parents of position p, including p: @color

current = p.copy() g.es("exclusive of %s" % current.headString(),color="purple") for p in current.parents_iter(): g.es(p.headString())
@nocolor The p.siblings_iter iterator returns a list of all siblings of position p: @color

current = c.currentPosition() g.es("all siblings of %s" % current.headString(),color="purple") for p in current.self_and_siblings_iter(): g.es(p.headString())

@nocolor The p.following_siblings_iter iterator returns a list of all siblings that follow position p: @color

current = c.currentPosition() g.es("following siblings of %s" % current.headString(),color="purple") for p in current.following_siblings_iter(): g.es(p.headString())
@nocolor The p.subtree_iter iterator returns a list of all positions in p's subtree, excluding p: @color

parent = p.parent() g.es("inclusive subtree of %s" % parent.headString(),color="purple") for p in parent.self_and_subtree_iter(): g.es(p.headString())

@nocolor The p.self_and_subtree_iter iterator returns a list of all positions in p's subtree, including p: @color

parent = p.parent() g.es("exclusive subtree of %s" % parent.headString(),color="purple") for p in parent.subtree_iter(): g.es(p.headString())
@nocolor The tests: @color

if p: # Right if not p: # Right

@nocolor are the **only** correct ways to test whether a position p is valid. In particular, the following **will not work**: @color

if p is None: # Wrong if p is not None: # Wrong
@nocolor You can use c.redraw_now to redraw the entire screen immediately: @color

c.redraw_now()

@nocolor However, Leo's code redraws the screen using the following pattern: @color

c.beginUpdate() try: << whatever >> finally: c.endUpdate()

@nocolor This suppresses redraws inside <<whatever>> that would otherwise be caused by c.endUpdate. c.endUpdate takes an optional argument: @color

c.endUpdate(flag)

@nocolor redraws the screen only if flag is True. This is an important pattern. Leo uses c.beginUpdate and c.endUpdate almost everywhere to redraw the screen, so provided that << whatever >> contains no calls to c.redraw_now this pattern insures that at most one redraw occurs.
@nocolor Leo dispatches commands using c.doCommand, which calls the "command1" and "command2" hook routines for the given label. c.doCommand catches all exceptions thrown by the command: @color

c.doCommand(c.markHeadline,label="markheadline")

@nocolor You can also call command handlers directly so that hooks will not be called: @color

c.markHeadline()

@nocolor You can invoke minibuffer commands by name. For example: @color

c.executeMinibufferCommand('open-outline')

@nocolor c.keyHandler.funcReturn contains the value returned from the command. In many cases, as above, this value is simply 'break'.
@nocolor Any .leo file may contain an @settings tree, so settings may be different for each commander. Plugins and other scripts can get the value of settings as follows: @color

format_headlines = c.config.getBool('rst3_format_headlines') g.es('format_headlines',format_headlines)

@nocolor The c.config class has the following getters. See the configSettings in leoCommands.py for details:

getBool getColor getDirectory getFloat getInt getLanguage getRatio getShortcut getString

You can set any *existing* item in an @settings tree with c.config.set(p,setting,val). For example: @color

for val in (False,True): c.config.set(p,'rst3_format_headlines',val) format_headlines = c.config.getBool('rst3_format_headlines') g.es('format_headlines',format_headlines)

@nocolor c.config.set does not change the @settings tree; it simply changes the values returned by the getters.
@nocolor Each commander maintains its own preferences. Your scripts can get the following ivars: @color

ivars = ( 'output_doc_flag', 'page_width', 'page_width', 'tab_width', 'tangle_batch_flag', 'tangle_directory', 'target_language', 'untangle_batch_flag', 'use_header_flag', )

g.es("Prefs ivars...\n",color="purple") for ivar in ivars: g.es(getattr(c,ivar))

@nocolor If your script sets c.tab_width your script may call f.setTabWidth to redraw the screen: @color

c.tab_width = -4 # Change this and see what happens. c.frame.setTabWidth(c.tab_width)
@nocolor The file leoFindScript.py contains functions for finding and changing text from within scripts. See leoFindScript.py in LeoPy.leo for full details.

The findall function returns a list of tuples (v,pos) describing matches in c's entire tree: @color

import leoFindScript

pattern="import leoGlobals as g" result = leoFindScript.findAll(c,pattern,bodyFlag=1)

g.es("%-3d instances of: '%s'...\n" % (len(result),pattern),color="purple")

for v,pos in result: body = v.bodyString() g.es('\n%-4d %s' % (pos,v.headString())) g.es(g.get_line_after(body,pos))

@nocolor The reFindall function returns a list of tuples (v,mo,pos), where mo is a MatchObject. The reFlags argument are flags to re.search: @color

import leoFindScript

pattern="from .* import" result = leoFindScript.reFindAll(c,pattern,bodyFlag=1,reFlags=None)

g.es("%-3d instances of: '%s'...\n" % (len(result),pattern),color="purple") for v,mo,pos in result: body = v.bodyString() g.es('\n%-4d %s' % (pos,v.headString())) g.es(g.get_line_after(body,pos))
@nocolor On startup, Leo looks for two arguments of the form:

--script scriptFile

If found, Leo enters batch mode. In batch mode Leo does not show any windows. Leo assumes the scriptFile contains a Python script and executes the contents of that file using Leo's Execute Script command. By default, Leo sends all output to the console window. Scripts in the scriptFile may disable or enable this output by calling app.log.disable or app.log.enable

Scripts in the scriptFile may execute any of Leo's commands except the Edit Body and Edit Headline commands. Those commands require interaction with the user. For example, the following batch script reads a Leo file and prints all the headlines in that file: @color

path = r"c:\prog\leoCVS\leo\test\test.leo"

g.app.log.disable() # disable reading messages while opening the file flag,newFrame = g.openWithFileName(path,None) g.app.log.enable() # re-enable the log.

for p in newFrame.c.allNodes_iter(): g.es(g.toEncodedString(p.headString(),"utf-8"))
@nocolor leoGlobals.py contains many utility functions and constants. The following script prints all the names defined in leoGlobals.py: @color

g.es("Names defined in leoGlobals.py",color="purple") names = g.__dict__.keys() names.sort() for name in names: g.es(name)
@nocolor leoGlobals.py defines 6 convenience methods for redirecting stdout and stderr: @color

g.redirectStderr() # Redirect stderr to the current log pane. g.redirectStdout() # Redirect stdout to the current log pane. g.restoreStderr() # Restores stderr so it prints to the console window. g.restoreStdout() # Restores stdout so it prints to the console window. g.stdErrIsRedirected() # Returns True if the stderr stream is redirected to the log pane. g.stdOutIsRedirected() # Returns True if the stdout stream is redirected to the log pane.

@nocolor Calls need *not* be paired. Redundant calls are ignored and the last call made controls where output for each stream goes. **Note**: you must execute Leo in a console window to see non-redirected output from the print statement: @color

print "stdout isRedirected:", g.stdOutIsRedirected() print "stderr isRedirected:", g.stdErrIsRedirected()

g.redirectStderr() print "stdout isRedirected:", g.stdOutIsRedirected() print "stderr isRedirected:", g.stdErrIsRedirected()

g.redirectStdout() print "stdout isRedirected:", g.stdOutIsRedirected() print "stderr isRedirected:", g.stdErrIsRedirected()

g.restoreStderr() print "stdout isRedirected:", g.stdOutIsRedirected() print "stderr isRedirected:", g.stdErrIsRedirected()

g.restoreStdout() print "stdout isRedirected:", g.stdOutIsRedirected() print "stderr isRedirected:", g.stdErrIsRedirected()
@nocolor **New in Leo 4.4a2**: Plugins and scripts can create new tabs in the log panel. The following creates a tab named test or make it visible if it already exists: @color

c.frame.log.selectTab('Test')

@nocolor When first created, a tab contains a text widget. The c.frame.log class contains the following 'official' ivars:

- c.frame.log.tabName is the name of the active tab. - c.frame.log.tabFrame is the Tk.Frame containing all the other widgets of the tab. - c.frame.log.logCtrl is the initial Tk.Text widget.

The following line removes the initial text widget: @color

c.frame.log.logCtrl.pack_forget()

@nocolor To make the text widget visible again: @color

c.frame.log.logCtrl.pack(side='top',expand=1,fill='both')

@nocolor Plugins and scripts can pack any other widgets into c.frame.log.tabFrame. For example, the following replaces the default text widget with a red box: @color

import Tkinter as Tk ; log = c.frame.log log.selectTab('Test') log.logCtrl.pack_forget() f = Tk.Frame(log.tabFrame,bg='red',height='1inch',width='2inch') f.pack(side='left',expand=0,fill='none')

@nocolor Notes:

- g.es, g.enl, g.ecnl, g.ecnls write to the log tab specified by the optional tabName argument. The default for tabName is 'Log', so existing code works as before.

- The put and putnl methods of the tkinterLog class also take an optional tabName argument which defaults to 'Log'.
Most scripts will use methods of the position class to access information in an outline. The following sections summarizes the most useful methods that your scripts can use. For a complete list, see the leoNodes.py in of LeoPy.leo.
@nocolor Iterators exist only in the position class: @color

c.allNodes_iter # returns all positions in c's outline. p.children_iter # returns all children of p. p.parents_iter # returns all parents of p. p.self_and_parents_iter # returns p and all parents of p. p.siblings_iter # returns all siblings of p, including p. p.following_siblings_iter # returns all siblings following p. p.subtree_iter # returns all positions in p's subtree, excluding p. p.self_and_subtree_iter # returns all positions in p's subtree, including p.
@nocolor p.c is an 'official' ivar of the position class. p.c is p's commander.

Here are the most useful getters of the vnode and position classes.

Returning strings: @color

p.bodyString() # the body string of p. p.headString() # the headline string of p.

@nocolor Returning ints: @color

p.childIndex() p.numberOfChildren() p.level()

@nocolor Returning bools representing property bits: @color

p.hasChildren() p.isAncestorOf(v2) # True if v2 is a child, grandchild, etc. of p. p.isCloned() p.isDirty() p.isExpanded() p.isMarked() p.isVisible() p.isVisited()
@nocolor Here are the most useful setters of the Commands and position classes. The following setters of the position class regardless of whether p is the presently selected position: @color

p.setBodyString(s) # Sets the body text of p. p.setHeadStringOrHeadline(s) # Sets the headline text of p.

@nocolor Moving nodes: @color

p.moveAfter(v2) # move p after v2 p.moveToNthChildOf(v2,n) # move p to the n'th child of v2 p.moveToRoot(oldRoot) # make p the root position. # oldRoot must be the old root position if it exists.

@nocolor 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: @color

c.clearAllVisited() # Clears all visited bits in c's tree. p.clearVisited() p.setVisited()
@nocolor Creating a script button should be your first thought whenever you want to automate any task. The scripting plugin, mod_scripting.py, puts two buttons in the icon menu, a pink Run Script button and a yellow Script Button button. The Run Script button does the same thing as the Execute Script command. The Script Button button is the interesting one. It creates a button, confusingly called a **script button** in the icon area. A script button executes a script when you push it.

Suppose node N is selected. When you press the Script Button button a new (pink) script button is created. The name of the new button is N's headline text. The script associated with the new button is N's body text. Now whenever you press the new button, N's script is executed on the **presently** selected node. Script buttons are extraordinarily useful. In effect, each script button defines an instant command! For example, sometimes my fingers get tired of saving a file. I simply put Save in a node's headline and c.save() in the body text. I hit the Script Button button and I get a new button called Save that will save the outline when I press it.

Here's a more interesting example. The following script searches the present node and its ancestors looking for an @rst node. When such a node is found the script calls the `rst3 plugin`_ to format it. I don't have to select the actual @rst node; I can select any of its children: @color

import leoPlugins rst3 = leoPlugins.getPluginModule('rst3') if rst3: # already loaded. controller = rst3.controllers.get(c) if controller: for p in p.self_and_parents_iter(): if p.headString().startswith('@rst '): controller.processTree(p) break else: # Just load the plugin. rst3 = leoPlugins.loadOnePlugin('rst3',verbose=True) if rst3: g.es('rst3 loaded') rst3.onCreate('tag',{'c':c}) else: # Ask to be removed. g.app.scriptDict['removeMe'] = True

@nocolor **Notes**:

- The scripting plugin pre-defines the c, g and p symbols just as the Execute Script command does.

- By default a script button executes the **present** body text of the node that original created the script button. This is very handy: you can modify a script button's script at any time without having to recreate the script button.

- You can delete any script button by right-clicking on it.

- On startup, the scripting plugin scans the entire .leo file and creates a script button for every node whose headline starts with @button scriptName. **Warning**: this is indeed a security risk of the kind discussed later. This feature can be disabled by setting atButtonNodes = True at the start of mod_scripting.py.
@test and @suite nodes are a major advance in unit testing. Such nodes have headlines that start with @test or @suite. The file test.leo contains many examples of @test and @suite nodes. @test and @suite nodes are a direct outgrowth of script buttons. The unit test script button in test.leo scans the selected outline, creating one unit test for each test node and zero or more unit tests for each @suite node. The unit test script then runs all the selected unit tests.
@nocolor @test nodes create a single unit test from the script in its body **without having to create a subclass of unittest.TestCase**. For example, here is a complete unit test. The headline is: @color

@test p.t == p.v.t

@nocolor The body text is: @color

# NOTE: p.__getattr__ must be enabled for this test to work. root = c.rootPosition() for p in root.allNodes_iter(): assert(p.t == p.v.t)

@nocolor **That's all!** The headline marks the node as containing a unit test. The body text contains the meat of the test. No cruft at all!

**Notes**: The unit tests created from @test nodes define setUp and tearDown methods. These methods ensure that the script being executed is the presently selected node. The scripts in @test nodes can not override the setUp and tearDown methods. In practice, this is not a problem because the scripts in @test nodes are typically very simple. Such scripts can use try/finally if they need to clean up after themselves. Scripts in @test nodes often assume that descendant nodes hold test data. Scripts in @test nodes can generally define and use sections, but scripts may not use the @others directive if descendant nodes hold data.
@nocolor @suite are an easy way to run legacy unit tests. The Unit Test button assumes that the body text of an @suite node is a script that creates a test suite and leaves the result in g.app.scriptDict['suite']. test.leo several examples of @suite nodes. Here is an @suite node that executes all doctests in Leo's sources. The headline is: @color

@suite run all doctests

The body text is: @color

import unittest import leoTest import sys

# DocTestSuite exists in Python 2.3 and above. if g.CheckVersion(sys.version, "2.3"): path = g.os_path_join(g.app.loadDir,"..","src") modules = leoTest.importAllModulesInPath(path) suite = leoTest.createUnitTestsFromDoctests(modules) if suite: g.app.scriptDict['suite'] = suite else: # Create an empty suite to suppress a warning. g.app.scriptDict['suite'] = unittest.TestSuite() print "Can't create unit tests from doctests" print "doctest.DocTestSuite requires Python 2.3 or above."

@nocolor The real work of creating the test suites is done by leoTest.createUnitTestsFromDoctests.

**Important**: if the suite was created successfully, the script must pass the suite to script in the unit test script button by as follows: @color

g.app.scriptDict['suite'] = suite

@nocolor @button unit test The script in the Unit Test script button in test.leo is just: @color

import leoTest leoTest.doTests(all=False)

@nocolor test.leo defines the leoTest module. The doTests function is the code that supports @test and @suite nodes. **Important**: you could customize how @test and @suite nodes work by using your own script in the @button unit test node.
This is the end of the 'Scripting Leo' slide show.
@language rest
plugins_menu.py # plugin_manager.py # attrib_edit.py open_with.py mod_scripting.py rst3.py slideshow.py UNL.py nav_buttons.py
For MacOS X (darwin) the following are typical defaults:

vim_cmd = /Applications/gvim.app/Contents/MacOS/gvim --servername LEO vim_exe = /Applications/gvim.app/Contents/MacOS/gvim
# To create a tab named 'Test' or make it visible if it already exists: log = c.frame.log log.selectTab('Test') log.clearTab('Test')

# When first created, a tab contains a Tk.Text widget. # To write to this widget, add the tabName argument to g.es: g.es('Test',color='blue',tabName='Test')
@ The Execute Script command predefines three variables, c, g and p. c is the commander (see below) of the outline containing the script. g is the leo.core.leoGlobals module, containing global functions. p is the position (see below) of the presently selected node in the outline. @c

for z in (c,g,p): print(z)
@ g.app is the **application object** representing the entire Leo application. The ivars (instance variables) of g.app represent Leo's global variables. @c

print(g.app)
@ A commander is a controller (in model/view/controller terminology). Each open Leo window has its own commander. Commanders contain data describing the window.

c.rootPosition() is the position (see below) of the outline's root node. c.currentPosition() is the position of the outline's presently selected node. @c

print(c.rootPosition()) print(c.currentPosition())
@ A position represents the location of a particular node *in a tree traversal*. Positions are the primary way to access data in Leo.

For any position p, p.v is p's outline node (vnode) @c

print(p.v)

@ Iterators are the primary way of traversing the data in Leo outlines. In effect, iterators return a list of positions. @c

print('%d positions' % len(list(c.all_positions()))) print('%d unique nodes' % len(list(c.all_unique_nodes())))

@ Frames contains all the internal data needed to manage a Leo window. c.frame is commander's frame. @c

f = c.frame print(f)

# "Official" components of c.frame: print (f.c) # The frame's commander print(f.log) # The log pane. print(f.tree) # The outline pane. print(f.body) # The body pane.
@ A vnode represents node in Leo's outline, including all clones of the node. By convention, variables named v, v1 or v2 refer to vnodes.

For any position p, p.v is the vnode at that position, p.h is the headline text and p.b is the body text. @c

print (len(p.b),p.h)
@ The following slides list useful methods for scripting Leo.
Leo's outlines are composed of **nodes**.

Nodes consist of **headlines* and **body text**.

The **outline pane** at the top left shows headlines.

Selecting a headline in the outline pane shows the body text in the **body pane** at the bottom.

The **log window** is at the top right.

An emacs-like minibuffer is at the bottom.

You can do everything with keystrokes: no mouse is needed.
Leo has gone far beyond my wildest hopes. I have Leo's users to thank for that.

I would never again willing program without Leo. All of Leo's sources, documentation and scripts are contained in Leo outlines.

Leo's mission is to be the ultimate power tool for managing complex data like computer programs, books and databases. This task will never be finished.

Leo has a large community of users who push Leo in new directions.

Leo is a playground for exploring how to apply scripts to outlines.
Leo outlines are more flexible than ordinary outlines.

**Clones** are nodes that appear in multiple places in the outline.

With clones, your data are *not* limited to a hierarchy:

- You can organize a *single* Leo outline in as many ways as you like. - You don't have to choose a single "right" way to view your data. - You can create a new view of (or relation on) your data at any time.

Views (relations) are normal parts of a Leo outline, not something tacked onto the outline.

In technical terms, a Leo outline is a DAG (Directed Acyclic Graph), not just a tree.
Leo's power comes from applying Python scripts to Leo's outlines.

That's *all*.

We'll see how this works in the next few slides.
Any node can contain a Python script.

You can use a Leo outline as filing cabinet for scripts. Because of clones, this filing cabinet is more flexible than usual.

But this is a trivial way of combining outlines and scripts.

There are many more ways, some not at all obvious.
We call any script in a Leo outline a **Leo script** because all such scripts have *easy* access to *all* the data in the outline.

In fancy words, Leo has a DOM (Document Object Model).
Leo's execute-script command **composes** scripts from an outline node and its descendants.

The composition rules are simple, but they allow you to organize scripts any way you like.
Leo creates an environment in which some tasks become easier, and others just *disappear*. Some dogs that aren't barking:

- Leo remembers what you were doing the last time you worked on an outline. This is a small detail that makes a big difference.

- You almost never happen to open files in Leo. Leo loads @<file> nodes automatically when you load a Leo outline.

- Your views of data are permanent, until you decide you don't need them. You don't have to continually reinvent them.

- Views eliminate the need to search for text, and for bookmarks, etc.
@ Everything from '@' to '@c' is a comment.

It is easy to use Leo's DOM: Leo's execute-script command predefines c and g and p:

- c is the "commander" of the outline, giving full access to all it's data. - g is part of Leo's sources containing global utility classes and methods. - p is the presently selected position. @c

g.es('Hello','world',color='purple') # g.es prints its arguments to the log tab.

g.es(c) g.es(g) g.es(p)
@ For any position p, p.v is the node at that position, p.v.b is the node's body text, and p.v.h is the node's headline.

As a convenience, p.b and p.b are synonyms for p.v.b and p.v.h. @c

g.es('headline: ', p.h) g.es('len(body):', len(p.b))
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!--@+leo-ver=4-thin--> <!--@+node:ekr.20080301071818.1:@thin html/front.html--> <!--@@first-->

<!--@@language html--> <!--@@tabwidth -4-->

<!-- Last Modified: July 31, 2008 --> <!-- Author: Edward K. Ream, edreamleo@gmail.com -->

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <!--@<<head>>--> <!--@+node:ekr.20080301075734.1:<<head>>--> <title>Leo's Home Page</title>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

<meta name="description" content="This page describes Leo, a programmer's outliner." />

<meta name="keywords" content="LEO, LITERATE PROGRAMMING, OUTLINES, CWEB, NOWEB, EDWARD K. REAM, DONALD E. KNUTH, SILVIO LEVY, OPEN SOFTWARE" />

<!-- for GeoUrl --> <meta name="ICBM" content="43.0713768, -89.4188004" /> <meta name="DC.title" content="Leo's Home Page" /> <!--@-node:ekr.20080301075734.1:<<head>>--> <!--@nl--> </head>

<body bgcolor="#fffbdc"> <!--@<< main table >>--> <!--@+node:ekr.20080301075734.4:<< main table >>-->

<table border="0" bgcolor="#FFFBDC"> <tr> <td width="22%" height="21" align="center" bgcolor="#FFFBDC" valign="middle"> <b><font size="4">Leo's Home</font></b> </td>

<td bgcolor="#F0E68C" width="78%" height="21" align="center"> <!--@ << upper table >>--> <!--@+node:ekr.20080301080758:<< upper table>>-->

<table border="0" width="100%"> <tr> <td width="25%" align="center"> <a href="http://www.python.org/"> <img border="0" src="PythonPowered.gif" width="110" height="44" /></a> </td>

<td width="50%" align="center"> <a href="http://groups.google.com/group/leo-editor">Leo at Google Groups</a>

</td>

<td width="25%" align="center"> <a href="http://www.mind-mapping.org/">mind-mapping.org</a> </td> </tr> </table> <!--@nonl--> <!--@-node:ekr.20080301080758:<< upper table>>-->

<!--@nl--> </td> </tr>

<tr> <td bgcolor="#F0E68C" width="22%" height="143" valign="top"> <!--@ << left table >>--> <!--@+node:ekr.20080301075734.6:<< left table >>--> <table border="0" width="100%">

<tr><td width="50%" bgcolor="#DAA520"><b>Documentation</b></td></tr> <tr><td width="50%"> <!-- <a href="http://sourceforge.net/project/screenshots.php?group_id=3458">Screen Shots</a> --> <a href="http:screen-shots.html">Screen Shots</a> </td></tr> <tr><td width="50%"><a href="intro.html">Beginners Guide</a></td></tr> <tr><td width="50%"><a href="leo_toc.html">Users Guide</a></td></tr>

<tr><td width="50%"><a href="FAQ.html">FAQ</a></td></tr> <tr><td width="50%"> <a href="http://www.3dtree.com/ev/e/sbooks/leo/sbframetoc_ie.htm">Online tutorial</a> </td></tr> <tr><td width="50%"><a href="testimonials.html">Quotes</a></td></tr> <tr><td width="50%" bgcolor="#DAA520"><b>Links</b></td></tr> <tr><td width="50%">

<!--@ << download ref >>--> <!--@+node:ekr.20080306071848:<< download ref >> (contains version number)--> <a href="http://sourceforge.net/project/showfiles.php?group_id=3458&amp;package_id=29106"> Download Leo[4.7 beta 1]</a> <!--@nonl--> <!--@-node:ekr.20080306071848:<< download ref >> (contains version number)--> <!--@nl--> </td></tr>

<tr><td width="50%"><a href="http://sourceforge.net/project/?group_id=3458"> Leo at SourceForge</a></td></tr> <tr><td width="50%"><a href="http://groups.google.com/group/leo-editor">Leo at Google Groups</a></td></tr> <tr><td width="50%"><a href="http://launchpad.net/leo-editor">Leo at launchpad</a></td></tr> <!-- <tr><td width="50%"><a href="http://leo.tigris.org/source/browse/leo/">Leo's cvs repository</a></td></tr> --> <tr><td width="50%">Leo's <a href="http://leo.zwiki.org">Wiki</a></td></tr> <tr><td width="50%">Leo's <a href="http://www.greygreen.org/leo/">Daily snapshots</a></td></tr>

<tr><td width="50%"><a href="leoLinks.html">More links</a></td></tr> <tr><td width="50%" bgcolor="#DAA520"><b>Edward K. Ream</b></td></tr> <tr><td width="50%"><a href="ekr.html">Home</a></td></tr> <tr><td width="50%"><a href="mailto:edreamleo@gmail.com">Contact</a></td></tr> </table> <!--@-node:ekr.20080301075734.6:<< left table >>--> <!--@nl-->

</td>

<td width="78%" height="143" bgcolor="#FFFBDC"> <!--@ << center area >>--> <!--@+node:ekr.20080301082848:<< center area >>--> <h2> Leo is...</h2>

<ul> <!--@<< what is Leo list >>-->

<!--@+node:ekr.20080301083325:<< what is Leo list >>--> <li> A <i>versatile data management platform</i>.<br/> Leo shows <b>user-created</b> relationships among any kind of data:<br /> computer programs, web sites, etc.<br />

Leo shows multiple views of data within a single outline.</li> <li> An <i>outlining editor for programmers</i>.<br /> Leo supports <i>optional</i> <a HREF="http://www.eecs.harvard.edu/~nr/noweb/">noweb</a> and <a HREF="http://www-cs-faculty.stanford.edu/~knuth/cweb.html">CWEB</a> markup.</li>

<li>A flexible <i>browser</i> for projects, programs, classes or any other data.</li> <li>A <i>project manager</i>.</li> <li><i>Portable</i>. Leo runs on Windows, Linux and MacOS X.</li> <li><i>100% pure</i> <a HREF="http://www.python.org/"><i>Python</i></a>.</li>

<li><i>Fully scriptable using </i><a HREF="http://www.python.org/"><i>Python</i></a>.</li> <li>Leo's outline files are <a HREF="http://www.w3.org/XML/">XML</a> format.</li> <li> <a href="http://www.opensource.org/"><i>Open Software</i></a>, distributed under the <a href="http://www.opensource.org/licenses/mit-license.php/">

MIT License </a>. </li> <!--@nonl--> <!--@-node:ekr.20080301083325:<< what is Leo list >>--> <!--@nl--> </ul>

<p> Please use <a href="http://groups.google.com/group/leo-editor">leo-editor</a>

to ask questions and make suggestions. </p>

<p> Leo's author is <a HREF="ekr.html">Edward K. Ream</a> </p> <!--@nonl--> <!--@-node:ekr.20080301082848:<< center area >>--> <!--@nl-->

</td> </tr> </table> <!--@-node:ekr.20080301075734.4:<< main table >>--> <!--@nl--> </body> </html>

<!--@-node:ekr.20080301071818.1:@thin html/front.html--> <!--@-leo-->
With Leo, I take considerable care to organize programs and data clearly.

Outline structure becomes a new domain for creativity. We say that Leo outlines have **good style**. Such a notion does not exist in any other tool.
@ c.all_positions() is a generator returning each position of the outline.

Because of clones, a node may appear in more than one position. @c

n = len(list(c.all_positions()))

g.es('This outline contains %s positions' % (n),color='blue')

for p in c.all_positions(): sep = g.choose(p.hasChildren(),'+','-') indent = ' '*p.level() print ('%s%s %s' % (indent,sep,p.h))
@language python
@language python

@others # The @others directive inserts all "unnamed" descendent # nodes into the external file.

myClass().printHi()
class myClass:

@others

def printHi(self):

'''Print Hi on the log tab.'''

g.es('Hi',color='blue')
You can do things in Leo you can't even *think* about in other programs.

Leo has two goals:

1. To show you your data better than any other program. The *only* way to do this is to let *you* choose how you want to see your data, not just in one way, but *in as many ways as you want*.

Leo's outlines expand your *vision*. They are outlines "done right".

2. To allow you manipulate your data more simply and powerfully than in any other scripting environment. Leo aims to take scripting to another level.

Leo's scripts expand your *power*.

That's all :-)

Leo is a power tool, but everything in Leo derives from just these two goals.
Double clicking the icon box, or executing the double-click-icon-box command, will open up Leo's sources, and select the "Projects" node.

The Projects nodes contains task-centric views of Leo's sources. Each view typically contains notes in the top-level node, and clones of related sources in child nodes.

Thus, each project node creates a full-fledged **relation** on the data.

So Leo outlines combine the best features of hierarchical databases, relational databases and object-oriented databases.
import os

os.system(r'python c:\leo.repo\trunk\leo\test\slide-show-file.py')
Be an adult:

- Put yourself in the background.

- It's not about how smart you are.

- It's not about control.
You want people to have *better* ideas than yours.

I didn't invent *any* of Leo's features. I did expand some :-)

Every Leo feature came from somewhere (or someone) else.
@nocolor

Summary on Leo's web site. The main ways of using Leo for programming.

Fix bugs below.
Others will, in their enthusiasm, likely want to add complexity via features.

"Ed, you continue to push the envelope. The amazing thing is that the footprint isn't doubling every few months like it would be in another designer's hands."
**Nodes** contain **headlines** and **body text**.
@nocolor-node

Leo is a an unusual programmer's editor, a powerful scripting environment and a data management environment. Leo is also a successful open-source project.

Edward Ream, the creator of Leo, will say a few words about Leo and invite listeners to give Leo a closer look. For more information, google edreamleo.

Edward will also discuss what it takes to manage an open source project successfully.
- Write computer programs.

- Manage web sites.

- Write documentation.
- All of Leo's documentation is a Leo outline.
You can do things with Leo that you can't even *imagine* doing in other programs.

Which is why some of the world's great programmers use Leo.

I'd like to answer your questions now.

For details, google edreamleo.

For more questions send me email or post to leo-editor google group.
Google: "you and your research" by Richard Hamming.
This sections gives *hints* about how Leo works, and how the quotes given above might be true. For details, see Leo's web site.

def selectVisNext (self,event=None):

'''Select the visible node following the presently selected node.'''

c = self ; p = c.p if not p: return if not c.canSelectVisNext(): return

p.moveToVisNext(c) c.treeSelectHelper(p)
# This has an up arrow for a control key.

def selectVisBack (self,event=None):

'''Select the visible node preceding the presently selected node.'''

c = self ; p = c.p if not p: return if not c.canSelectVisBack(): return

p.moveToVisBack(c)

# g.trace(p.h) c.treeSelectHelper(p)
def goToParent (self,event=None):

'''Select the parent of the selected node.'''

c = self ; p = c.p

# g.trace(p.parent())

c.treeSelectHelper(p and p.parent())
def contractNodeOrGoToParent (self,event=None):

"""Simulate the left Arrow Key in folder of Windows Explorer."""

trace = False and not g.unitTesting c = self ; p = c.p redraw = False ; fullRedraw = False if p.hasChildren() and p.isExpanded(): if trace: g.trace('contract',p.h) c.contractNode() redraw = True # New in one-node world. elif p.hasParent() and p.parent().isVisible(c): redraw = False p.contract() # Make sure we know this node is contracted. # This "feature" is dubious. # To work properly, it requires a full redraw. if False: # self.sparse_goto_parent: for child in p.self_and_siblings(): if child != p and child.isExpanded(): child.contract() redraw = True ; fullRedraw = True if trace: g.trace('goto parent',p.h) c.goToParent()

if redraw: if fullRedraw or p.isCloned(): if trace: g.trace('full redraw',p.h) c.redraw() else: c.redraw_after_contract(p=p,setFocus=True)
def expandNodeAndGoToFirstChild (self,event=None):

"""If a node has children, expand it if needed and go to the first child."""

trace = False and not g.unitTesting c = self ; p = c.p

# New code. if p.hasChildren(): if p.isExpanded(): p.moveToFirstChild() if trace: g.trace('select',p.h) c.selectPosition(p) else: if trace: g.trace('expand',p.h) c.expandNode() # Calls redraw_after_expand. elif p.hasNext(): c.goToNextSibling() else: while p.hasParent(): p.moveToParent() if p.hasNext(): p.moveToNext() break c.selectPosition(p) c.treeFocusHelper()

def expandNodeOrGoToFirstChild (self,event=None):

"""Simulate the Right Arrow Key in folder of Windows Explorer."""

c = self ; p = c.p if p.hasChildren(): if not p.isExpanded(): c.expandNode() # Calls redraw_after_expand. else: c.redraw_after_expand(p.firstChild(),setFocus=True)
def contractNode (self,event=None):

'''Contract the presently selected node.'''

c = self ; p = c.p

p.contract()

if p.isCloned(): c.redraw() # A full redraw is necessary to handle clones. else: c.redraw_after_contract(p=p,setFocus=True)
def expandNode (self,event=None):

'''Expand the presently selected node.'''

trace = False and not g.unitTesting c = self ; p = c.p

p.expand()

if p.isCloned(): if trace: g.trace('***redraw') c.redraw() # Bug fix: 2009/10/03. else: c.redraw_after_expand(p,setFocus=True)

def treeSelectHelper (self,p):

c = self

if not p: p = c.p

if p: # Do not call expandAllAncestors here. c.selectPosition(p) c.redraw_after_select(p)

c.treeFocusHelper()
def moveToVisBack (self,c):

"""Move a position to the position of the previous visible node."""

trace = False and not g.unitTesting verbose = True p = self ; limit,limitIsVisible = c.visLimit() if trace and verbose: g.trace(p,'limit',limit,'limitIsVisible',limitIsVisible) if trace: g.trace('***entry','parent',p.parent(),'p',p,g.callers(5)) while p: # Short-circuit if possible. back = p.back() if trace: g.trace( 'back',back,'hasChildren',bool(back and back.hasChildren()), 'isExpanded',bool(back and back.isExpanded()))

if back and back.hasChildren() and back.isExpanded(): p.moveToThreadBack() elif back: p.moveToBack() else: p.moveToParent() # Same as p.moveToThreadBack() # if back and (not back.hasChildren() or not back.isExpanded()): # p.moveToBack() # else: # p.moveToThreadBack() if trace: g.trace(p.parent(),p) if p: if trace and verbose: g.trace('**p',p) done,val = self.checkVisBackLimit(limit,limitIsVisible,p) if done: if trace and verbose: g.trace('done',p) return val if p.isVisible(c): if trace and verbose: g.trace('isVisible',p) return p else: # assert not p. return p
def checkVisBackLimit (self,limit,limitIsVisible,p):

'''Return done, return-val'''

trace = True and not g.unitTesting c = p.v.context

if limit: if limit == p: if trace: g.trace('at limit',p) if limitIsVisible and p.isVisible(c): return True,p else: return True,None #return True,g.choose(limitIsVisible and p.isVisible(c),p,None) elif limit.isAncestorOf(p): return False,None else: if trace: g.trace('outside limit tree',limit,p) return True,None else: return False,None
def moveToVisNext (self,c):

"""Move a position to the position of the next visible node."""

trace = False and not g.unitTesting verbose = False p = self ; limit,limitIsVisible = c.visLimit() while p: if trace: g.trace('1',p.h) # if trace: g.trace('hasChildren %s, isExpanded %s %s' % ( # p.hasChildren(),p.isExpanded(),p.h)) # Short-circuit if possible. if p.hasNext() and p.hasChildren() and p.isExpanded(): p.moveToFirstChild() elif p.hasNext(): p.moveToNext() else: p.moveToThreadNext() # if p.hasNext() and (not p.hasChildren() or not p.isExpanded()): # p.moveToNext() # else: # p.moveToThreadNext() if trace: g.trace('2',p.h) if p: done,val = self.checkVisNextLimit(limit,p) if done: return val if p.isVisible(c): return p.copy() else: # assert not p. return p
def checkVisNextLimit (self,limit,p):

'''Return done, return-val'''

trace = False and not g.unitTesting

if limit: # Unlike moveToVisBack, being at the limit does not terminate. if limit == p: return False, None elif limit.isAncestorOf(p): return False,None else: if trace: g.trace('outside limit tree') return True,None else: return False,None
@language python
Proof:

- Relations are tuples, as are Leo views. - Because of clones, a Leo view can contain any other node.

QED.
@language python @tabwidth -4 @pagewidth 80

use_zodb = False

<< imports >>

@others
if use_zodb: # It may be important to import ZODB first. try: import ZODB import ZODB.FileStorage except ImportError: ZODB = None else: ZODB = None

import leo.core.leoGlobals as g

if g.app and g.app.use_psyco: # g.pr("enabled psyco classes",__file__) try: from psyco.classes import * except ImportError: pass

import time import re import itertools
if use_zodb and ZODB: class baseVnode (ZODB.Persistence.Persistent): pass else: class baseVnode (object): pass

class vnode (baseVnode): << vnode constants >> @others
# Define the meaning of status bits in new vnodes.

# Archived... clonedBit = 0x01 # True: vnode has clone mark. # unused 0x02 expandedBit = 0x04 # True: vnode is expanded. markedBit = 0x08 # True: vnode is marked orphanBit = 0x10 # True: vnode saved in .leo file, not derived file. selectedBit = 0x20 # True: vnode is current vnode. topBit = 0x40 # True: vnode was top vnode when saved.

# Not archived... richTextBit = 0x080 # Determines whether we use <bt> or <btr> tags. visitedBit = 0x100 dirtyBit = 0x200 writeBit = 0x400
# To support ZODB, the code must set v._p_changed = 1 whenever # v.unknownAttributes or any mutable vnode object changes.

def __init__ (self,context):

# The primary data: headline and body text. if g.isPython3: self._headString = 'newHeadline' self._bodyString = '' else: self._headString = unicode('newHeadline') self._bodyString = unicode('')

# Structure data... self.children = [] # Ordered list of all children of this node. self.parents = [] # Unordered list of all parents of this node.

# Other essential data... self.fileIndex = g.app.nodeIndices.getNewIndex() # The immutable file index for this vnode. # New in Leo 4.6 b2: allocate gnx (fileIndex) immediately. self.iconVal = 0 # The present value of the node's icon. self.statusBits = 0 # status bits

# v.t no longer exists. All code must now be aware of the one-node world. # self.t = self # For compatibility with scripts and plugins.

# Information that is never written to any file... self.context = context # The context containing context.hiddenRootNode. # Required so we can compute top-level siblings. # It is named .context rather than .c to emphasize its limited usage. self.insertSpot = None # Location of previous insert point. self.scrollBarSpot = None # Previous value of scrollbar position. self.selectionLength = 0 # The length of the selected body text. self.selectionStart = 0 # The start of the selected body text.
def __repr__ (self):

return "<vnode %d:'%s'>" % (id(self),self.cleanHeadString())

__str__ = __repr__
def dumpLink (self,link): return g.choose(link,link,"<none>")

def dump (self,label=""):

v = self print('%s %s %s' % ('-'*10,label,v)) print('len(parents) %s' % len(v.parents)) print('len(children) %s' % len(v.children)) print('parents %s' % g.listToString(v.parents)) print('children%s' % g.listToString(v.children))
if use_zodb and ZODB: def __hash__(self): return self.__hash__()
def findAtFileName (self,names,h=''):

"""Return the name following one of the names in nameList. Return an empty string."""

# Allow h argument for unit testing. if not h: h = self.headString()

if not g.match(h,0,'@'): return ""

i = g.skip_id(h,1,'-') word = h[:i] if word in names and g.match_word(h,0,word): name = h[i:].strip() # g.trace(repr(word),repr(name)) return name else: return ""
def anyAtFileNodeName (self):

"""Return the file name following an @file node or an empty string."""

names = ( "@auto", "@auto-rst", "@edit", "@file", "@thin", "@file-thin", "@thinfile", "@asis", "@file-asis", "@silentfile", "@noref", "@file-noref", "@rawfile", "@nosent", "@file-nosent", "@nosentinelsfile", "@shadow",)

return self.findAtFileName(names)
# These return the filename following @xxx, in v.headString. # Return the the empty string if v is not an @xxx node.

def atAutoNodeName (self,h=None): # # Prevent conflicts with autotrees plugin: don't allow @auto-whatever to match. # return g.match_word(h,0,tag) and not g.match(h,0,tag+'-') and h[len(tag):].strip() names = ("@auto","@auto-rst",) return self.findAtFileName(names,h=h)

def atAutoRstNodeName (self,h=None): names = ("@auto-rst",) return self.findAtFileName(names,h=h)

def atEditNodeName (self): names = ("@edit",) return self.findAtFileName(names)

def atFileNodeName (self): names = ("@file",) return self.findAtFileName(names)

def atNoSentinelsFileNodeName (self): names = ("@nosent", "@file-nosent", "@nosentinelsfile") return self.findAtFileName(names)

def atRawFileNodeName (self): names = ("@noref", "@file-noref", "@rawfile") return self.findAtFileName(names)

def atShadowFileNodeName (self): names = ("@shadow",) return self.findAtFileName(names)

def atSilentFileNodeName (self): names = ("@asis", "@file-asis", "@silentfile") return self.findAtFileName(names)

def atThinFileNodeName (self): names = ("@thin", "@file-thin", "@thinfile") return self.findAtFileName(names)

# New names, less confusing atNoSentFileNodeName = atNoSentinelsFileNodeName atNorefFileNodeName = atRawFileNodeName atAsisFileNodeName = atSilentFileNodeName
if g.unitTesting:

c,p = g.getTestVars()

table = ( ('@auto-rst rst-file','rst-file','rst-file'), ('@auto x','x',''), ('xyz','',''), )

for s,expected1,expected2 in table: result1 = p.v.atAutoNodeName(h=s) result2 = p.v.atAutoRstNodeName(h=s) assert result1 == expected1,'fail1: given %s expected %s got %s' % ( repr(s),repr(expected1),repr(result1)) assert result2 == expected2,'fail2: given %s expected %s got %s' % ( repr(s),repr(expected2),repr(result2))
def isAtAllNode (self):

"""Returns True if the receiver contains @others in its body at the start of a line."""

flag, i = g.is_special(self._bodyString,0,"@all") return flag
def isAnyAtFileNode (self):

"""Return True if v is any kind of @file or related node."""

# This routine should be as fast as possible. # It is called once for every vnode when writing a file.

h = self.headString() return h and h[0] == '@' and self.anyAtFileNodeName()
def isAtAutoNode (self): return g.choose(self.atAutoNodeName(),True,False)

def isAtAutoRstNode (self): return g.choose(self.atAutoRstNodeName(),True,False)

def isAtEditNode (self): return g.choose(self.atEditNodeName(),True,False)

def isAtFileNode (self): return g.choose(self.atFileNodeName(),True,False)

def isAtNoSentinelsFileNode (self): return g.choose(self.atNoSentinelsFileNodeName(),True,False)

def isAtRawFileNode (self): # @file-noref return g.choose(self.atRawFileNodeName(),True,False)

def isAtSilentFileNode (self): # @file-asis return g.choose(self.atSilentFileNodeName(),True,False)

def isAtShadowFileNode (self): return g.choose(self.atShadowFileNodeName(),True,False)

def isAtThinFileNode (self): return g.choose(self.atThinFileNodeName(),True,False)

# New names, less confusing: isAtNoSentFileNode = isAtNoSentinelsFileNode isAtNorefFileNode = isAtRawFileNode isAtAsisFileNode = isAtSilentFileNode
def isAtIgnoreNode (self):

"""Returns True if the receiver contains @ignore in its body at the start of a line."""

flag, i = g.is_special(self._bodyString, 0, "@ignore") return flag
def isAtOthersNode (self):

"""Returns True if the receiver contains @others in its body at the start of a line."""

flag, i = g.is_special(self._bodyString,0,"@others") return flag
def matchHeadline (self,pattern):

"""Returns True if the headline matches the pattern ignoring whitespace and case.

The headline may contain characters following the successfully matched pattern."""

v = self

h = g.toUnicode(v.headString(),'utf-8') h = h.lower().replace(' ','').replace('\t','')

pattern = g.toUnicode(pattern,'utf-8') pattern = pattern.lower().replace(' ','').replace('\t','')

return h.startswith(pattern)
def bodyString (self):

# This message should never be printed and we want to avoid crashing here! if not g.isUnicode(self._bodyString): s = "v.bodyString: Leo internal error: not unicode:" + repr(self._bodyString) g.es_print('',s,color="red")

# Make _sure_ we return a unicode string. return g.toUnicode(self._bodyString,g.app.tkEncoding)

getBody = bodyString
def firstChild (self):

v = self return v.children and v.children[0]
def hasChildren (self):

v = self return len(v.children) > 0

hasFirstChild = hasChildren
def lastChild (self):

v = self return v.children and v.children[-1] or None
# childIndex and nthChild are zero-based.

def nthChild (self, n):

v = self

if 0 <= n < len(v.children): return v.children[n] else: return None
def numberOfChildren (self):

v = self return len(v.children)
def directParents (self):

"""(New in 4.2) Return a list of all direct parent vnodes of a vnode.

This is NOT the same as the list of ancestors of the vnode."""

v = self return v.parents
def hasBody (self):

'''Return True if this vnode contains body text.'''

s = self._bodyString

return s and len(s) > 0
def headString (self):

"""Return the headline string."""

# This message should never be printed and we want to avoid crashing here! if not g.isUnicode(self._headString): s = "Leo internal error: not unicode:" + repr(self._headString) g.es_print('',s,color="red")

# Make _sure_ we return a unicode string. return g.toUnicode(self._headString,g.app.tkEncoding)

def cleanHeadString (self):

s = self._headString return g.toEncodedString(s,"ascii") # Replaces non-ascii characters by '?'
def isCloned (self):

return len(self.parents) > 1
def isDirty (self):

return (self.statusBits & self.dirtyBit) != 0
def isExpanded (self):

# g.trace( ( self.statusBits & self.expandedBit ) != 0, g.callers())

return ( self.statusBits & self.expandedBit ) != 0
def isMarked (self):

return ( self.statusBits & vnode.markedBit ) != 0
def isOrphan (self):

return ( self.statusBits & vnode.orphanBit ) != 0
def isSelected (self):

return ( self.statusBits & vnode.selectedBit ) != 0
def isTopBitSet (self):

return ( self.statusBits & self.topBit ) != 0
def isVisited (self):

return ( self.statusBits & vnode.visitedBit ) != 0
def isWriteBit (self):

v = self return (v.statusBits & v.writeBit) != 0
def status (self):

return self.statusBits
def clearDirty (self): v = self v.statusBits &= ~ v.dirtyBit

def findAllPotentiallyDirtyNodes(self):

trace = False and not g.unitTesting v = self ; c = v.context

# Set the starting nodes. nodes = [] newNodes = [v]

# Add nodes until no more are added. while newNodes: addedNodes = [] nodes.extend(newNodes) for v in newNodes: for v2 in v.parents: if v2 not in nodes and v2 not in addedNodes: addedNodes.append(v2) newNodes = addedNodes[:]

# Remove the hidden vnode. if c.hiddenRootNode in nodes: if trace: g.trace('removing hidden root',c.hiddenRootNode) nodes.remove(c.hiddenRootNode)

if trace: g.trace(nodes) return nodes
# Unlike p.setAllAncestorAtFileNodesDirty, # there is no setDescendentsDirty arg.

def setAllAncestorAtFileNodesDirty (self):

trace = False and not g.unitTesting verbose = False v = self dirtyVnodeList = []

# Calculate all nodes that are joined to p or parents of such nodes. nodes = v.findAllPotentiallyDirtyNodes()

if trace and verbose: for v in nodes: print v.isDirty(),v.isAnyAtFileNode(),v

dirtyVnodeList = [v for v in nodes if not v.isDirty() and v.isAnyAtFileNode()]

changed = len(dirtyVnodeList) > 0

for v in dirtyVnodeList: v.setDirty() # Do not call v.setDirty here!

if trace: g.trace("vnode",dirtyVnodeList)

return dirtyVnodeList
def setDirty (self):

self.statusBits |= self.dirtyBit
def clearClonedBit (self):

self.statusBits &= ~ self.clonedBit
def clearMarked (self):

self.statusBits &= ~ self.markedBit
def clearWriteBit (self): self.statusBits &= ~ self.writeBit
def clearOrphan (self):

self.statusBits &= ~ self.orphanBit
def clearVisited (self):

self.statusBits &= ~ self.visitedBit
def contract(self):

# if self.context.p.v == self: g.trace(self,g.callers(4))

self.statusBits &= ~ self.expandedBit

def expand(self):

# g.trace(self,g.callers(4))

self.statusBits |= self.expandedBit

def initExpandedBit (self):

# g.trace(self._headString)

self.statusBits |= self.expandedBit
def initStatus (self, status):

self.statusBits = status
def setClonedBit (self):

self.statusBits |= self.clonedBit

def initClonedBit (self, val):

if val: self.statusBits |= self.clonedBit else: self.statusBits &= ~ self.clonedBit
def setMarked (self):

self.statusBits |= self.markedBit

def initMarkedBit (self):

self.statusBits |= self.markedBit
def setOrphan (self):

self.statusBits |= self.orphanBit
# This only sets the selected bit.

def setSelected (self):

self.statusBits |= self.selectedBit
# Compatibility routine for scripts

def setVisited (self):

self.statusBits |= self.visitedBit
def setWriteBit (self): self.statusBits |= self.writeBit
def setBodyString (self,s,encoding="utf-8"):

trace = False and not g.unitTesting v = self if trace and v._bodyString != s: g.trace('v %s %s -> %s %s\nold: %s\nnew: %s' % ( v.h, len(v._bodyString),len(s),g.callers(5), v._bodyString,s)) v._bodyString = g.toUnicode(s,encoding,reportErrors=True)

def setHeadString (self,s,encoding="utf-8"): v = self v._headString = g.toUnicode(s,encoding,reportErrors=True)

initBodyString = setBodyString initHeadString = setHeadString setHeadText = setHeadString setTnodeText = setBodyString
def setFileIndex (self, index):

self.fileIndex = index
def computeIcon (self):

val = 0 ; v = self if v.hasBody(): val += 1 if v.isMarked(): val += 2 if v.isCloned(): val += 4 if v.isDirty(): val += 8 return val

def setIcon (self):

pass # Compatibility routine for old scripts
def setSelection (self, start, length):

v = self v.selectionStart = start v.selectionLength = length
def _addLink (self,childIndex,parent_v,adjust=True): '''Adjust links after adding a link to v.'''

trace = False and not g.unitTesting v = self

# Update parent_v.children & v.parents. parent_v.children.insert(childIndex,v) v.parents.append(parent_v) if trace: g.trace('*** added parent',parent_v,'to',v, 'len(parents)',len(v.parents))

# Set zodb changed flags. v._p_changed = 1 parent_v._p_changed = 1

# If v has only one parent, we adjust all # the parents links in the descendant tree. # This handles clones properly when undoing a delete. if adjust: if len(v.parents) == 1: for child in v.children: child._addParentLinks(parent=v)
def _addParentLinks(self,parent):

trace = False and not g.unitTesting v = self

v.parents.append(parent) if trace: g.trace( '*** added parent',parent,'to',v,'len(parents)',len(v.parents))

if len(v.parents) == 1: for child in v.children: child._addParentLinks(parent=v)
def _cutLink (self,childIndex,parent_v): '''Adjust links after cutting a link to v.''' v = self

assert parent_v.children[childIndex]==v del parent_v.children[childIndex] v.parents.remove(parent_v) v._p_changed = 1 parent_v._p_changed = 1

# If v has no more parents, we adjust all # the parent links in the descendant tree. # This handles clones properly when deleting a tree. if len(v.parents) == 0: for child in v.children: child._cutParentLinks(parent=v)
def _cutParentLinks(self,parent):

trace = False and not g.unitTesting v = self

if trace: g.trace('parent',parent,'v',v) v.parents.remove(parent)

if len(v.parents) == 0: for child in v.children: child._cutParentLinks(parent=v)
def _linkAsNthChild (self,parent_v,n):

"""Links self as the n'th child of vnode pv"""

v = self # The child node. v._addLink(n,parent_v)
def createOutlineFromCacheList(self,c,aList): """ Create outline structure from recursive aList built by p.makeCacheList.

Clones will be automatically created by gnx, but *not* for the top-level node. """

parent_v = self

#import pprint ; pprint.pprint(tree) parent_v = self h,b,gnx,children = aList if h is not None: v = parent_v v._headString = h v._bodyString = b

for z in children: h,b,gnx,grandChildren = z isClone,child_v = parent_v.fastAddLastChild(c,gnx) if isClone: if child_v.b != b: # or child_v.h # Bug fix: the last seen clone rules. child_v.h = h child_v.b = b # Bug fix: mark @<file> nodes dirty. child_v.setAllAncestorAtFileNodesDirty() child_v.setMarked() g.es("changed:",child_v.h,color="blue") else: child_v.createOutlineFromCacheList(c,z)
# Similar to createThinChild4 def fastAddLastChild(self,c,gnxString): '''Create new vnode as last child of the receiver.

If the gnx exists already, create a clone instead of new vnode. '''

trace = False and not g.unitTesting parent_v = self indices = g.app.nodeIndices gnxDict = c.fileCommands.gnxDict

if gnxString is None: v = None else: v = gnxDict.get(gnxString) is_clone = v is not None

if trace: g.trace( 'clone','%-5s' % (is_clone), 'parent_v',parent_v,'gnx',gnxString,'v',repr(v))

if not is_clone: v = vnode(context=c) if gnxString: gnx = indices.scanGnx(gnxString,0) v.fileIndex = gnx gnxDict[gnxString] = v

child_v = v child_v._linkAsNthChild(parent_v,parent_v.numberOfChildren()) child_v.setVisited() # Supress warning/deletion of unvisited nodes.

return is_clone,child_v
def __get_b(self):

v = self return v.bodyString()

def __set_b(self,val):

v = self v.setBodyString(val)

b = property( __get_b, __set_b, doc = "vnode body string property")
def __get_h(self):

v = self return v.headString()

def __set_h(self,val):

v = self v.setHeadString(val)

h = property( __get_h, __set_h, doc = "vnode headline string property")
def __get_u(self): v = self if not hasattr(v,'unknownAttributes'): v.unknownAttributes = {} return v.unknownAttributes

def __set_u(self,val): v = self if val is None: if hasattr(v,'unknownAttributes'): delattr(v,'unknownAttributes') elif type(val) == type({}): v.unknownAttributes = val else: raise ValueError

u = property( __get_u, __set_u, doc = "vnode unknownAttribute property")
def __get_gnx(self): v = self return g.app.nodeIndices.toString(v.fileIndex)

gnx = property( __get_gnx, # __set_gnx, doc = "vnode gnx property")
# Indices are Python dicts containing 'id','loc','time' and 'n' keys.

class nodeIndices (object):

"""A class to implement global node indices (gnx's)."""

@others
def __init__ (self,id):

"""ctor for nodeIndices class"""

self.userId = id self.defaultId = id

# A Major simplification: Only assign the timestamp once. self.setTimeStamp() self.lastIndex = 0
def areEqual (self,gnx1,gnx2):

"""Return True if all fields of gnx1 and gnx2 are equal"""

# works whatever the format of gnx1 and gnx2. # This should never throw an exception. return gnx1 == gnx2
# These are used by the fileCommands read/write code.

def getDefaultId (self):

"""Return the id to be used by default in all gnx's""" return self.defaultId

def setDefaultId (self,theId):

"""Set the id to be used by default in all gnx's""" self.defaultId = theId
def getNewIndex (self):

'''Create a new gnx.'''

self.lastIndex += 1 d = (self.userId,self.timeString,self.lastIndex) # g.trace(d) return d
def isGnx (self,gnx): try: theId,t,n = gnx return t != None except Exception: return False
def scanGnx (self,s,i):

"""Create a gnx from its string representation"""

if not g.isString(s): g.es("scanGnx: unexpected index type:",type(s),'',s,color="red") return None,None,None

s = s.strip()

theId,t,n = None,None,None i,theId = g.skip_to_char(s,i,'.') if g.match(s,i,'.'): i,t = g.skip_to_char(s,i+1,'.') if g.match(s,i,'.'): i,n = g.skip_to_char(s,i+1,'.') # Use self.defaultId for missing id entries. if theId == None or len(theId) == 0: theId = self.defaultId # Convert n to int. if n: try: n = int(n) except Exception: pass

return theId,t,n
def setTimestamp (self):

"""Set the timestamp string to be used by getNewIndex until further notice"""

self.timeString = time.strftime( "%Y%m%d%H%M%S", # Help comparisons; avoid y2k problems. time.localtime())

# g.trace(self.timeString,self.lastIndex,g.callers(4))

setTimeStamp = setTimestamp
def toString (self,index):

"""Convert a gnx (a tuple) to its string representation"""

try: theId,t,n = index if n in (None,0,'',): return "%s.%s" % (theId,t) else: return "%s.%s.%d" % (theId,t,n) except Exception: if not g.app.unitTesting: g.trace('unusual gnx',repr(index),g.callers()) try: theId,t,n = self.getNewIndex() if n in (None,0,'',): return "%s.%s" % (theId,t) else: return "%s.%s.%d" % (theId,t,n) except Exception: g.trace('double exception: returning original index') return repr(index)
<< about the position class >>

# Positions should *never* be saved by the ZOBD. class position (object): @others
@killcolor @

A position marks the spot in a tree traversal. A position p consists of a vnode p.v, a child index p._childIndex, and a stack of tuples (v,childIndex), one for each ancestor **at the spot in tree traversal. Positions p has a unique set of parents.

The p.moveToX methods may return a null (invalid) position p with p.v = None.

The tests "if p" or "if not p" are the _only_ correct way to test whether a position p is valid. In particular, tests like "if p is None" or "if p is not None" will not work properly.
def __init__ (self,v,childIndex=0,stack=None,trace=False):

'''Create a new position with the given childIndex and parent stack.'''

# To support ZODB the code must set v._p_changed = 1 # whenever any mutable vnode object changes.

self._childIndex = childIndex self.v = v

# New in Leo 4.5: stack entries are tuples (v,childIndex). if stack: self.stack = stack[:] # Creating a copy here is safest and best. else: self.stack = []

g.app.positions += 1

# if g.app.tracePositions and trace: g.trace(g.callers())

self.txtOffset = None # see self.textOffset()
def __eq__(self,p2):

"""Return True if two postions are equivalent."""

p1 = self

# Don't use g.trace: it might call p.__eq__ or p.__ne__. # print ('p.__eq__: %s %s' % ( # p1 and p1.v and p1.h,p2 and p2.v and p2.h))

if p2 is None or p2.v is None: return p1.v is None else: return ( p1.v == p2.v and p1._childIndex == p2._childIndex and p1.stack == p2.stack )

def __ne__(self,p2):

"""Return True if two postions are not equivalent."""

return not self.__eq__(p2) # For possible use in Python 2.x.
# No longer used. All code must now be aware of the one-node world.

# def __getattr__ (self,attr):

# """Convert references to p.t into references to p.v."""

# if attr=="t": # return self.v # else: # # New in 4.3: _silently_ raise the attribute error. # # This allows plugin code to use hasattr(p,attr) ! # if 0: # print("unknown position attribute: %s" % attr) # import traceback ; traceback.print_stack() # raise AttributeError(attr)
@ Tests such as 'if p' or 'if not p' are the _only_ correct ways to test whether a position p is valid. In particular, tests like 'if p is None' or 'if p is not None' will not work properly. @c

if g.isPython3:

def __bool__ ( self):

"""Return True if a position is valid."""

# Tracing this appears to cause unbounded prints. # print("__bool__",self.v and self.v.cleanHeadString())

return self.v is not None

else:

def __nonzero__ ( self):

"""Return True if a position is valid."""

# if g.app.trace: "__nonzero__",self.v

# g.trace(repr(self))

return self.v is not None
def __str__ (self):

p = self

if p.v: return "<pos %d childIndex: %d lvl: %d [%d] %s>" % ( id(p),p._childIndex,p.level(),len(p.stack),p.cleanHeadString()) else: return "<pos %d [%d] None>" % (id(p),len(p.stack))

__repr__ = __str__
def archivedPosition (self,root_p=None):

'''Return a representation of a position suitable for use in .leo files.'''

p = self

if root_p is None: aList = [z._childIndex for z in p.self_and_parents()] else: aList = [] for z in p.self_and_parents(): if z == root_p: aList.append(0) break else: aList.append(z._childIndex) # g.trace(aList)

aList.reverse() return aList
# Using this routine can generate huge numbers of temporary positions during a tree traversal.

def copy (self):

""""Return an independent copy of a position."""

# if g.app.tracePositions: g.trace(g.callers())

return position(self.v,self._childIndex,self.stack,trace=False)
def dumpLink (self,link):

return g.choose(link,link,"<none>")

def dump (self,label=""):

p = self if p.v: p.v.dump() # Don't print a label
def key (self):

p = self

# For unified nodes we must include a complete key, # so we can distinguish between clones. result = [] for z in p.stack: v,childIndex = z result.append('%s:%s' % (id(v),childIndex))

result.append('%s:%s' % (id(p.v),p._childIndex))

return '.'.join(result)
def __get_b(self):

p = self return p.bodyString()

def __set_b(self,val):

p = self ; c = p.v and p.v.context if c: c.setBodyString(p, val) # Don't redraw the screen: p.b must be fast. # c.redraw_after_icons_changed()

b = property( __get_b, __set_b, doc = "position body string property")
def __get_h(self):

p = self return p.headString()

def __set_h(self,val):

p = self ; c = p.v and p.v.context if c: c.setHeadString(p,val) # Don't redraw the screen: p.h must be fast. # c.redraw_after_head_changed()

h = property( __get_h, __set_h, doc = "position headline string property")
def __get_gnx(self): p = self return g.app.nodeIndices.toString(p.v.fileIndex)

gnx = property( __get_gnx, # __set_gnx, doc = "position gnx property")
def anyAtFileNodeName (self): return self.v.anyAtFileNodeName() def atAutoNodeName (self): return self.v.atAutoNodeName() def atEditNodeName (self): return self.v.atEditNodeName() def atFileNodeName (self): return self.v.atFileNodeName() def atNoSentinelsFileNodeName (self): return self.v.atNoSentinelsFileNodeName() def atRawFileNodeName (self): return self.v.atRawFileNodeName() def atShadowFileNodeName (self): return self.v.atShadowFileNodeName() def atSilentFileNodeName (self): return self.v.atSilentFileNodeName() def atThinFileNodeName (self): return self.v.atThinFileNodeName()

# New names, less confusing atNoSentFileNodeName = atNoSentinelsFileNodeName atNorefFileNodeName = atRawFileNodeName atAsisFileNodeName = atSilentFileNodeName

def isAnyAtFileNode (self): return self.v.isAnyAtFileNode() def isAtAllNode (self): return self.v.isAtAllNode() def isAtAutoNode (self): return self.v.isAtAutoNode() def isAtAutoRstNode (self): return self.v.isAtAutoRstNode() def isAtEditNode (self): return self.v.isAtEditNode() def isAtFileNode (self): return self.v.isAtFileNode() def isAtIgnoreNode (self): return self.v.isAtIgnoreNode() def isAtNoSentinelsFileNode (self): return self.v.isAtNoSentinelsFileNode() def isAtOthersNode (self): return self.v.isAtOthersNode() def isAtRawFileNode (self): return self.v.isAtRawFileNode() def isAtSilentFileNode (self): return self.v.isAtSilentFileNode() def isAtShadowFileNode (self): return self.v.isAtShadowFileNode() def isAtThinFileNode (self): return self.v.isAtThinFileNode()

# New names, less confusing: isAtNoSentFileNode = isAtNoSentinelsFileNode isAtNorefFileNode = isAtRawFileNode isAtAsisFileNode = isAtSilentFileNode

# Utilities. def matchHeadline (self,pattern): return self.v.matchHeadline(pattern)
def bodyString (self):

return self.v.bodyString()

def headString (self):

return self.v.headString()

def cleanHeadString (self):

return self.v.cleanHeadString()
def isDirty (self): return self.v.isDirty() def isExpanded (self): return self.v.isExpanded() def isMarked (self): return self.v.isMarked() def isOrphan (self): return self.v.isOrphan() def isSelected (self): return self.v.isSelected() def isTopBitSet (self): return self.v.isTopBitSet() def isVisited (self): return self.v.isVisited() def status (self): return self.v.status()
# This used to be time-critical code.

def childIndex(self):

p = self return p._childIndex
def directParents (self):

return self.v.directParents()
def hasChildren (self):

p = self return len(p.v.children) > 0

hasFirstChild = hasChildren

def numberOfChildren (self):

p = self return len(p.v.children)
# These methods are useful abbreviations. # Warning: they make copies of positions, so they should be used _sparingly_

def getBack (self): return self.copy().moveToBack() def getFirstChild (self): return self.copy().moveToFirstChild() def getLastChild (self): return self.copy().moveToLastChild() def getLastNode (self): return self.copy().moveToLastNode() def getLastVisible (self): return self.copy().moveToLastVisible() def getNext (self): return self.copy().moveToNext() def getNodeAfterTree (self): return self.copy().moveToNodeAfterTree() def getNthChild (self,n): return self.copy().moveToNthChild(n) def getParent (self): return self.copy().moveToParent() def getThreadBack (self): return self.copy().moveToThreadBack() def getThreadNext (self): return self.copy().moveToThreadNext()

# New in Leo 4.4.3 b2: add c args. def getVisBack (self,c): return self.copy().moveToVisBack(c) def getVisNext (self,c): return self.copy().moveToVisNext(c)

# These are efficient enough now that iterators are the normal way to traverse the tree!

back = getBack firstChild = getFirstChild lastChild = getLastChild lastNode = getLastNode lastVisible = getLastVisible # New in 4.2 (was in tk tree code). next = getNext nodeAfterTree = getNodeAfterTree nthChild = getNthChild parent = getParent threadBack = getThreadBack threadNext = getThreadNext visBack = getVisBack visNext = getVisNext

# New in Leo 4.4.3: hasVisBack = visBack hasVisNext = visNext
def hasBack(self): p = self return p.v and p._childIndex > 0

def hasNext(self): p = self try: parent_v = p._parentVnode() # Returns None if p.v is None. return p.v and parent_v and p._childIndex+1 < len(parent_v.children) except Exception: g.trace('*** Unexpected exception') g.es_exception() return None

def hasParent(self): p = self return p.v and len(p.stack) > 0

def hasThreadBack(self): p = self return p.hasParent() or p.hasBack() # Much cheaper than computing the actual value.
def hasThreadNext (self):

p = self if not p.v: return False

if p.hasChildren() or p.hasNext(): return True

n = len(p.stack) -1 while n >= 0: v,childIndex = p.stack[n] # See how many children v's parent has. if n == 0: parent_v = v.context.hiddenRootNode else: parent_v,junk = p.stack[n-1] if len(parent_v.children) > childIndex+1: # v has a next sibling. return True n -= 1 return False
def findRootPosition (self):

p = self.copy() while p.hasParent(): p.moveToParent() while p.hasBack(): p.moveToBack() return p
def isAncestorOf (self, p2):

p = self ; v = p.v

for z in p2.stack: v2,junk = z if v2 == v: return True

return False
def isCloned (self):

p = self return p.v.isCloned()
def isRoot (self):

p = self

return not p.hasParent() and not p.hasBack()
def isVisible (self,c):

p = self ; trace = False limit,limitIsVisible = c.visLimit() limit_v = limit and limit.v or None if p.v == limit_v: if trace: g.trace('*** at limit','limitIsVisible',limitIsVisible,p.h) return limitIsVisible

# It's much easier with a full stack. n = len(p.stack)-1 while n >= 0: progress = n # v,n = p.vParentWithStack(v,p.stack,n) v,junk = p.stack[n] if v == limit_v: # We are at a descendant of limit. if trace: g.trace('*** descendant of limit', 'limitIsVisible',limitIsVisible, 'limit.isExpanded()',limit.isExpanded(),'v',v) if limitIsVisible: return limit.isExpanded() else: # Ignore the expansion state of @chapter nodes. return True if not v.isExpanded(): if trace: g.trace('*** non-limit parent is not expanded:',v._headString,p.h) return False n -= 1 assert progress > n

return True
def level (self):

'''Return the number of p's parents.'''

p = self return p.v and len(p.stack) or 0

simpleLevel = level
def textOffset(self): ''' See http://tinyurl.com/5nescw for details '''

p = self

# caching of p.textOffset, we need to calculate it only once if p.txtOffset is not None: return p.txtOffset

p.txtOffset = 0 # walk back from the current position for cursor in p.self_and_parents(): # we also need the parent, the "text offset" is relative to it parent = cursor.parent() if parent == None: # root reached break parent_bodyString = parent.b if parent_bodyString == '': # organizer node continue parent_lines = parent_bodyString.split('\n') # check out if the cursor node is a section cursor_is_section = False cursor_headString = cursor.h if cursor_headString.startswith('<<'): cursor_is_section = True # section node for line in parent_lines: if cursor_is_section == True: # find out the section in the bodyString of the parent pos = line.find(cursor_headString) else: # otherwise find the "@others" directive in the bodyString of the parent pos = line.find('@others') if pos > 0: # break the iteration over lines if something is found break if pos > 0: p.txtOffset += pos if parent.v.isAnyAtFileNode(): # do not scan upper break

return p.txtOffset
# Clone bits are no longer used. # Dirty bits are handled carefully by the position class.

def clearMarked (self): return self.v.clearMarked() def clearOrphan (self): return self.v.clearOrphan() def clearVisited (self): return self.v.clearVisited()

def contract (self): return self.v.contract() def expand (self): return self.v.expand()

def initExpandedBit (self): return self.v.initExpandedBit() def initMarkedBit (self): return self.v.initMarkedBit() def initStatus (self, status): return self.v.initStatus(status)

def setMarked (self): return self.v.setMarked() def setOrphan (self): return self.v.setOrphan() def setSelected (self): return self.v.setSelected() def setVisited (self): return self.v.setVisited()
def computeIcon (self):

return self.v.computeIcon()

def setIcon (self):

pass # Compatibility routine for old scripts
def setSelection (self,start,length):

return self.v.setSelection(start,length)
def setBodyString (self,s,encoding="utf-8"):

p = self return p.v.setBodyString(s,encoding)

initBodyString = setBodyString setTnodeText = setBodyString scriptSetBodyString = setBodyString

def initHeadString (self,s,encoding="utf-8"):

p = self p.v.initHeadString(s,encoding)

def setHeadString (self,s,encoding="utf-8"):

p = self p.v.initHeadString(s,encoding) p.setDirty()
# Compatibility routine for scripts.

def clearVisitedInTree (self):

for p in self.self_and_subtree(): p.clearVisited()
def clearAllVisitedInTree (self):

for p in self.self_and_subtree(): p.v.clearVisited() p.v.clearWriteBit()
def clearDirty (self):

p = self p.v.clearDirty()
def findAllPotentiallyDirtyNodes(self):

p = self return p.v.findAllPotentiallyDirtyNodes()
def inAtIgnoreRange (self):

"""Returns True if position p or one of p's parents is an @ignore node."""

p = self

for p in p.self_and_parents(): if p.isAtIgnoreNode(): return True

return False
def setAllAncestorAtFileNodesDirty (self,setDescendentsDirty=False):

trace = False and not g.unitTesting verbose = False p = self dirtyVnodeList = []

# Calculate all nodes that are joined to p or parents of such nodes. nodes = p.findAllPotentiallyDirtyNodes()

if setDescendentsDirty: # N.B. Only mark _direct_ descendents of nodes. # Using the findAllPotentiallyDirtyNodes algorithm would mark way too many nodes. for p2 in p.subtree(): # Only @thin nodes need to be marked. if p2.v not in nodes and p2.isAtThinFileNode(): nodes.append(p2.v)

if trace and verbose: for v in nodes: print (v.isDirty(),v.isAnyAtFileNode(),v)

dirtyVnodeList = [v for v in nodes if not v.isDirty() and v.isAnyAtFileNode()] changed = len(dirtyVnodeList) > 0

for v in dirtyVnodeList: v.setDirty()

if trace: g.trace("position",dirtyVnodeList,g.callers(5))

return dirtyVnodeList
def setDirty (self,setDescendentsDirty=True):

'''Mark a node and all ancestor @file nodes dirty.'''

p = self ; dirtyVnodeList = []

# g.trace(p.h,g.callers(4))

if not p.v.isDirty(): p.v.setDirty() dirtyVnodeList.append(p.v)

# Important: this must be called even if p.v is already dirty. # Typing can change the @ignore state! dirtyVnodeList2 = p.setAllAncestorAtFileNodesDirty(setDescendentsDirty) dirtyVnodeList.extend(dirtyVnodeList2)

return dirtyVnodeList
@ - convertTreeToString and moreHead can't be vnode methods because they uses level(). - moreBody could be anywhere: it may as well be a postion method.
def convertTreeToString (self):

"""Convert a positions suboutline to a string in MORE format."""

p = self ; level1 = p.level()

array = [] for p in p.self_and_subtree(): array.append(p.moreHead(level1)+'\n') body = p.moreBody() if body: array.append(body +'\n')

return ''.join(array)
def moreHead (self, firstLevel,useVerticalBar=False):

"""Return the headline string in MORE format."""

# useVerticalBar is unused, but it would be useful in over-ridden methods.

p = self level = self.level() - firstLevel plusMinus = g.choose(p.hasChildren(), "+", "-")

return "%s%s %s" % ('\t'*level,plusMinus,p.h)
@ + test line - test line \ test line test line + test line - test line \ More lines... @c

def moreBody (self):

"""Returns the body string in MORE format.

Inserts a backslash before any leading plus, minus or backslash."""

p = self ; array = [] lines = p.b.split('\n') for s in lines: i = g.skip_ws(s,0) if i < len(s) and s[i] in ('+','-','\\'): s = s[:i] + '\\' + s[i:] array.append(s) return '\n'.join(array)
def children(self):

'''Return all children of p.'''

p = self p = p.firstChild() while p: yield p p.moveToNext() raise StopIteration

# Compatibility with old code. children_iter = children
def following_siblings(self): ''' Return all siblings that follow p, not including p. '''

p = self p = p.copy() # Always include the original node. p = p.next() while p: yield p p.moveToNext() raise StopIteration

# Compatibility with old code. following_siblings_iter = following_siblings
def nodes (self):

p = self p = p.copy() while p: yield p.v p.moveToThreadNext()

# Compatibility with old code. tnodes_iter = nodes vnodes_iter = nodes
def parents(self):

'''Return all parents of p.'''

p = self p = p.parent() while p: yield p p.moveToParent() raise StopIteration

# Compatibility with old code. parents_iter = parents
def self_and_parents(self):

'''Return p and all parents of p.'''

p = self p = p.copy() while p: yield p p.moveToParent() raise StopIteration

# Compatibility with old code. self_and_parents_iter = self_and_parents
def self_and_siblings(self): '''Return all siblings of p including p. '''

p = self p = p.copy() while p.hasBack(): p.moveToBack() while p: yield p p.moveToNext() raise StopIteration

# Compatibility with old code. self_and_siblings_iter = self_and_siblings
def self_and_subtree(self):

'''Return p's entire subtree, including p.'''

p = self p = p.copy() after = p.nodeAfterTree() while p and p != after: yield p p.moveToThreadNext() raise StopIteration

# Compatibility with old code. self_and_subtree_iter = self_and_subtree
def subtree(self):

'''Return all descendants of p, not including p.'''

p = self p = p.copy() after = p.nodeAfterTree() p.moveToThreadNext() while p and p != after: yield p p.moveToThreadNext() raise StopIteration

# Compatibility with old code. subtree_iter = subtree
def unique_nodes (self):

p = self p = p.copy() seen = set() while p: if p.v in seen: p.moveToNodeAfterTree() else: seen.add(p.v) yield p.v p.moveToThreadNext()

# Compatibility with old code. unique_tnodes_iter = unique_nodes unique_vnodes_iter = unique_nodes
def unique_subtree (self): '''Return unique positions in p's entire subtree, including p.'''

p = self p = p.copy() after = p.nodeAfterTree() seen = set() while p and p != after: if p.v in seen: p.moveToNodeAfterTree() else: seen.add(p.v) yield p p.moveToThreadNext() raise StopIteration

# Compatibility with old code. subtree_with_unique_tnodes_iter = unique_subtree subtree_with_unique_vnodes_iter = unique_subtree
def clone (self):

"""Create a clone of back.

Returns the newly created position."""

p = self p2 = p.copy() # Do *not* copy the vnode! p2._linkAfter(p) # This should "just work" return p2
# These used by unit tests and by the group_operations plugin.

def copyTreeAfter(self): p = self p2 = p.insertAfter() p.copyTreeFromSelfTo(p2) return p2

def copyTreeFromSelfTo(self,p2): p = self p2.v._headString = p.h p2.v._bodyString = p.b

# 2009/10/02: no need to copy arg to iter for child in p.children(): child2 = p2.insertAsLastChild() child.copyTreeFromSelfTo(child2)
@ This is the main delete routine. It deletes the receiver's entire tree from the screen. Because of the undo command we never actually delete vnodes or tnodes. @c

def doDelete (self,newNode=None):

"""Deletes position p from the outline."""

p = self p.setDirty() # Mark @file nodes dirty!

# Adjust newNode._childIndex if newNode is a following sibling of p. sib = p.copy() while sib.hasNext(): sib.moveToNext() if sib == newNode: newNode._childIndex -= 1 break

p._unlink()
def insertAfter (self):

"""Inserts a new position after self.

Returns the newly created position."""

p = self ; context = p.v.context p2 = self.copy()

p2.v = vnode(context=context) p2.v.iconVal = 0 p2._linkAfter(p)

return p2
def insertAsLastChild (self):

"""Inserts a new vnode as the last child of self.

Returns the newly created position."""

p = self n = p.numberOfChildren()

return p.insertAsNthChild(n)
def insertAsNthChild (self,n):

"""Inserts a new node as the the nth child of self. self must have at least n-1 children.

Returns the newly created position."""

p = self ; context = p.v.context p2 = self.copy()

p2.v = vnode(context=context) p2.v.iconVal = 0 p2._linkAsNthChild(p,n)

return p2
def invalidOutline (self, message):

p = self

if p.hasParent(): node = p.parent() else: node = p

g.alert("invalid outline: %s\n%s" % (message,node))
def moveAfter (self,a):

"""Move a position after position a."""

p = self # Do NOT copy the position!

# g.trace('before','p',p,p.stack,'\na',a,a.stack)

a._adjustPositionBeforeUnlink(p) p._unlink() p._linkAfter(a)

# g.trace('before','p',p,p.stack,'\na',a,a.stack)

return p
def moveToFirstChildOf (self,parent):

"""Move a position to the first child of parent."""

p = self # Do NOT copy the position! p._unlink() p._linkAsNthChild(parent,0) return p

def moveToLastChildOf (self,parent):

"""Move a position to the last child of parent."""

p = self # Do NOT copy the position! p._unlink() n = parent.numberOfChildren() p._linkAsNthChild(parent,n) return p
def moveToNthChildOf (self,parent,n):

"""Move a position to the nth child of parent."""

p = self # Do NOT copy the position!

parent._adjustPositionBeforeUnlink(p) p._unlink() p._linkAsNthChild(parent,n)

return p
def moveToRoot (self,oldRoot=None):

'''Moves a position to the root position.

Important: oldRoot must the previous root position if it exists.'''

p = self # Do NOT copy the position! if oldRoot: oldRoot._adjustPositionBeforeUnlink(p) p._unlink() p._linkAsRoot(oldRoot)

return p
# This routine checks the structure of the receiver's tree.

def validateOutlineWithParent (self,pv):

p = self result = True # optimists get only unpleasant surprises. parent = p.getParent() childIndex = p._childIndex

# g.trace(p,parent,pv) << validate parent ivar >> << validate childIndex ivar >> << validate x ivar >>

# Recursively validate all the children. for child in p.children(): r = child.validateOutlineWithParent(p) if not r: result = False

return result
if parent != pv: p.invalidOutline( "Invalid parent link: " + repr(parent))
if pv: if childIndex < 0: p.invalidOutline ( "missing childIndex" + childIndex ) elif childIndex >= pv.numberOfChildren(): p.invalidOutline ( "missing children entry for index: " + childIndex ) elif childIndex < 0: p.invalidOutline ( "negative childIndex" + childIndex )
if not p.v and pv: self.invalidOutline ( "Empty t" )
@ These routines change self to a new position "in place". That is, these methods must _never_ call p.copy().

When moving to a nonexistent position, these routines simply set p.v = None, leaving the p.stack unchanged. This allows the caller to "undo" the effect of the invalid move by simply restoring the previous value of p.v.

These routines all return self on exit so the following kind of code will work: after = p.copy().moveToNodeAfterTree()
def moveToBack (self):

"""Move self to its previous sibling."""

p = self ; n = p._childIndex

parent_v = p._parentVnode() # Returns None if p.v is None.

# Do not assume n is in range: this is used by positionExists. if parent_v and p.v and 0 < n <= len(parent_v.children): p._childIndex -= 1 p.v = parent_v.children[n-1] else: p.v = None

return p
def moveToFirstChild (self):

"""Move a position to it's first child's position."""

p = self

if p.v and p.v.children: p.stack.append((p.v,p._childIndex),) p.v = p.v.children[0] p._childIndex = 0 else: p.v = None

return p
def moveToLastChild (self):

"""Move a position to it's last child's position."""

p = self

if p.v and p.v.children: p.stack.append((p.v,p._childIndex),) n = len(p.v.children) p.v = p.v.children[n-1] p._childIndex = n-1 else: p.v = None

return p
def moveToLastNode (self):

"""Move a position to last node of its tree.

N.B. Returns p if p has no children."""

p = self

# Huge improvement for 4.2. while p.hasChildren(): p.moveToLastChild()

return p
def moveToNext (self):

"""Move a position to its next sibling."""

p = self ; n = p._childIndex

parent_v = p._parentVnode() # Returns None if p.v is None. if not p.v: g.trace('parent_v',parent_v,'p.v',p.v)

if p.v and parent_v and len(parent_v.children) > n+1: p._childIndex = n+1 p.v = parent_v.children[n+1] else: p.v = None

return p
def moveToNodeAfterTree (self):

"""Move a position to the node after the position's tree."""

p = self

while p: if p.hasNext(): p.moveToNext() break p.moveToParent()

return p
def moveToNthChild (self,n):

p = self

if p.v and len(p.v.children) > n: p.stack.append((p.v,p._childIndex),) p.v = p.v.children[n] p._childIndex = n else: p.v = None

return p
def moveToParent (self):

"""Move a position to its parent position."""

p = self

if p.v and p.stack: p.v,p._childIndex = p.stack.pop() else: p.v = None

return p
def moveToThreadBack (self):

"""Move a position to it's threadBack position."""

p = self

if p.hasBack(): p.moveToBack() p.moveToLastNode() else: p.moveToParent()

return p
def moveToThreadNext (self):

"""Move a position to threadNext position."""

p = self

if p.v: if p.v.children: p.moveToFirstChild() elif p.hasNext(): p.moveToNext() else: p.moveToParent() while p: if p.hasNext(): p.moveToNext() break #found p.moveToParent() # not found.

return p
def moveToVisBack (self,c):

"""Move a position to the position of the previous visible node."""

trace = False and not g.unitTesting verbose = True p = self ; limit,limitIsVisible = c.visLimit() if trace and verbose: g.trace(p,'limit',limit,'limitIsVisible',limitIsVisible) if trace: g.trace('***entry','parent',p.parent(),'p',p,g.callers(5)) while p: # Short-circuit if possible. back = p.back() if trace: g.trace( 'back',back,'hasChildren',bool(back and back.hasChildren()), 'isExpanded',bool(back and back.isExpanded()))

if back and back.hasChildren() and back.isExpanded(): p.moveToThreadBack() elif back: p.moveToBack() else: p.moveToParent() # Same as p.moveToThreadBack() # if back and (not back.hasChildren() or not back.isExpanded()): # p.moveToBack() # else: # p.moveToThreadBack() if trace: g.trace(p.parent(),p) if p: if trace and verbose: g.trace('**p',p) done,val = self.checkVisBackLimit(limit,limitIsVisible,p) if done: if trace and verbose: g.trace('done',p) return val if p.isVisible(c): if trace and verbose: g.trace('isVisible',p) return p else: # assert not p. return p
def checkVisBackLimit (self,limit,limitIsVisible,p):

'''Return done, return-val'''

trace = True and not g.unitTesting c = p.v.context

if limit: if limit == p: if trace: g.trace('at limit',p) if limitIsVisible and p.isVisible(c): return True,p else: return True,None #return True,g.choose(limitIsVisible and p.isVisible(c),p,None) elif limit.isAncestorOf(p): return False,None else: if trace: g.trace('outside limit tree',limit,p) return True,None else: return False,None
def moveToVisNext (self,c):

"""Move a position to the position of the next visible node."""

trace = False and not g.unitTesting verbose = False p = self ; limit,limitIsVisible = c.visLimit() while p: if trace: g.trace('1',p.h) # if trace: g.trace('hasChildren %s, isExpanded %s %s' % ( # p.hasChildren(),p.isExpanded(),p.h)) # Short-circuit if possible. if p.hasNext() and p.hasChildren() and p.isExpanded(): p.moveToFirstChild() elif p.hasNext(): p.moveToNext() else: p.moveToThreadNext() # if p.hasNext() and (not p.hasChildren() or not p.isExpanded()): # p.moveToNext() # else: # p.moveToThreadNext() if trace: g.trace('2',p.h) if p: done,val = self.checkVisNextLimit(limit,p) if done: return val if p.isVisible(c): return p.copy() else: # assert not p. return p
def checkVisNextLimit (self,limit,p):

'''Return done, return-val'''

trace = False and not g.unitTesting

if limit: # Unlike moveToVisBack, being at the limit does not terminate. if limit == p: return False, None elif limit.isAncestorOf(p): return False,None else: if trace: g.trace('outside limit tree') return True,None else: return False,None
# These methods are only for the use of low-level code # in leoNodes.py, leoFileCommands.py and leoUndo.py.
def _adjustPositionBeforeUnlink (self,p2):

'''Adjust position p before unlinking p2.'''

# p will change if p2 is a previous sibling of p or # p2 is a previous sibling of any ancestor of p.

trace = False and not g.unitTesting p = self ; sib = p.copy()

if trace: g.trace('entry',p.stack)

# A special case for previous siblings. # Adjust p._childIndex, not the stack's childIndex. while sib.hasBack(): sib.moveToBack() if sib == p2: p._childIndex -= 1 if trace: g.trace('***new index: %s\n%s' % ( p.h,p.stack)) return

# Adjust p's stack. stack = [] ; changed = False ; i = 0 while i < len(p.stack): v,childIndex = p.stack[i] p3 = position(v=v,childIndex=childIndex,stack=stack[:i]) while p3: if p2.v == p3.v: # A match with the to-be-moved node? stack.append((v,childIndex-1),) changed = True break # terminate only the inner loop. p3.moveToBack() else: stack.append((v,childIndex),) i += 1

if changed: if trace: g.trace('***new stack: %s\n%s' % ( p.h,stack)) p.stack = stack
if g.unitTesting:

c,p = g.getTestVars()

table = ( '1', '1-1','1-1-1','1-1-2', '1-2','1-2-1','1-2-2', '2', '2-1','2-1-1','2-1-2', '2-2','2-2-1','2-2-2', '3', '3-1','3-1-1','3-1-2', '3-2','3-2-1','3-2-2', )

for suffix in table: h = 'node %s' % suffix p2 = g.findNodeInTree(c,p,h) assert p2,h

table2 = ( ('2-1-2','2-1-1','2-1-1'), ('3','2','2'), )

for h1,h2,h3 in table2: p1 = g.findNodeInTree(c,p,'node %s' % h1) p2 = g.findNodeInTree(c,p,'node %s' % h2) p3 = g.findNodeInTree(c,p,'node %s' % h3) p1._adjustPositionBeforeUnlink(p2) result = p1 assert result.stack == p3.stack,'expected %s got %s' % ( p3.h,result and result.h or '<none>')

# Data. @others
# Node 1
# node 1-1
# node 1-1-1
# node 1-1-2
# node 1-2
# node 1-2-1
# node 1-2-2
# node 2
# node 2-1
# node 2-1-1
# node 2-1-2
# node 2-2
# node 2-2-1
# node 2-2-2
# node 3
# node 3-1
# node 3-1-1
# node 3-1-2
# node 3-2
# node 3-2-1
# node 3-2-2
def _linkAfter (self,p_after,adjust=True):

'''Link self after p_after.'''

p = self parent_v = p_after._parentVnode() # Returns None if p.v is None

# Init the ivars. p.stack = p_after.stack[:] p._childIndex = p_after._childIndex + 1

# Set the links. child = p.v n = p_after._childIndex+1 child._addLink(n,parent_v,adjust=adjust)
def _linkAsNthChild (self,parent,n,adjust=True):

p = self parent_v = parent.v

# Init the ivars. p.stack = parent.stack[:] p.stack.append((parent_v,parent._childIndex),) p._childIndex = n

child = p.v child._addLink(n,parent_v,adjust=adjust)

def _parentVnode (self):

'''Return the parent vnode. Return the hiddenRootNode if there is no other parent.'''

p = self

if p.v: data = p.stack and p.stack[-1] if data: v, junk = data return v else: return p.v.context.hiddenRootNode else: return None
def _linkAsRoot (self,oldRoot):

"""Link self as the root node."""

p = self assert(p.v)

hiddenRootNode = p.v.context.hiddenRootNode

if oldRoot: oldRootNode = oldRoot.v else: oldRootNode = None

# Init the ivars. p.stack = [] p._childIndex = 0

parent_v = hiddenRootNode child = p.v if not oldRoot: parent_v.children = [] child._addLink(0,parent_v)

return p
def _unlink (self):

'''Unlink the receiver p from the tree.'''

p = self ; n = p._childIndex parent_v = p._parentVnode() # returns None if p.v is None child = p.v assert(p.v) assert(parent_v)

# Delete the child. if (0 <= n < len(parent_v.children) and parent_v.children[n] == child ): # This is the only call to v._cutlink. child._cutLink(n,parent_v) else: self.badUnlink(parent_v,n,child)
def badUnlink (self,parent_v,n,child):

if 0 <= n < len(parent_v.children): g.trace('**can not happen: children[%s] != p.v' % (n)) g.trace('parent_v.children...\n', g.listToString(parent_v.children)) g.trace('parent_v',parent_v) g.trace('parent_v.children[n]',parent_v.children[n]) g.trace('child',child) g.trace('** callers:',g.callers()) if g.app.unitTesting: assert False, 'children[%s] != p.v' else: g.trace('**can not happen: bad child index: %s, len(children): %s' % ( n,len(parent_v.children))) g.trace('parent_v.children...\n', g.listToString(parent_v.children)) g.trace('parent_v',parent_v,'child',child) g.trace('** callers:',g.callers()) if g.app.unitTesting: assert False, 'bad child index: %s' % (n)
def makeCacheList(self):

'''Create a recursive list describing a tree for use by v.createOutlineFromCacheList. '''

p = self

return [ p.h,p.b,p.gnx, [p2.makeCacheList() for p2 in p.children()]]
class poslist(list): """ List of positions

This behaves like a normal list, with the distinction that it has select_h and select_b methods that can be used to search through immediate children of the nodes.

""" @others
def select_h(self, regex, flags = re.IGNORECASE): """ Find immediate child nodes of nodes in poslist with regex.

You can chain find_h / find_b with select_h / select_b like this to refine an outline search::

pl = c.find_h('@thin.*py').select_h('class.*').select_b('import (.*)')

""" pat = re.compile(regex, flags) res = poslist() for p in self: for child_p in p.children(): m = re.match(pat, child_p.h) if m: pc = child_p.copy() pc.mo = m res.append(pc) return res

def select_b(self, regex, flags = re.IGNORECASE ): """ Find all the nodes in poslist where body matches regex

You can chain find_h / find_b with select_h / select_b like this to refine an outline search::

pl = c.find_h('@thin.*py').select_h('class.*').select_b('import (.*)') """ pat = re.compile(regex, flags) res = poslist() for p in self: m = re.finditer(pat, p.b) t1,t2 = itertools.tee(m,2) try: first = t1.next() # if does not raise StopIteration... pc = p.copy() pc.matchiter = t2 res.append(pc)

except StopIteration: pass

return res

In Leo, **outline structure is real data that is available to scripts.**
Leo is written entirely in Python.
@language python

'''This script prints the number of nodes in the outline.'''

# Leo's execute-script command (ctrl-B) predefines c and g.

n = len(list(c.all_nodes())) # Count the number of nodes. fn = c.shortFileName() # Get the name of Leo outline. g.es('%s contains %s nodes' % (fn,n),color='red')
Leo headlines naturally describe body text.

Headlines are *metadata*. Body text is data.

By *convention*, headlines that start with @x declare **node types**.

Leo's core supports many types. Scripts and plugins support others.
# Scripts, including unit tests, can use outline structure, and types.

import leo.core.leoTest as leoTest leoTest.runEditCommandTest(c,p)
first line line 1 line a line b line c last line
first line line 1 line a line b line c last line
first line line 1 line a line b line c last line
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!--@+leo-ver=4-thin--> <!--@+node:ekr.20080301071818.1:@thin html/front.html--> <!--@@first-->

<!--@@language html--> <!--@@tabwidth -4-->

<!-- Last Modified: July 31, 2008 --> <!-- Author: Edward K. Ream, edreamleo@gmail.com -->

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <!--@<<head>>--> <!--@+node:ekr.20080301075734.1:<<head>>--> <title>Leo's Home Page</title>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

<meta name="description" content="This page describes Leo, a programmer's outliner." />

<meta name="keywords" content="LEO, LITERATE PROGRAMMING, OUTLINES, CWEB, NOWEB, EDWARD K. REAM, DONALD E. KNUTH, SILVIO LEVY, OPEN SOFTWARE" />

<!-- for GeoUrl --> <meta name="ICBM" content="43.0713768, -89.4188004" /> <meta name="DC.title" content="Leo's Home Page" /> <!--@-node:ekr.20080301075734.1:<<head>>--> <!--@nl--> </head>

<body bgcolor="#fffbdc"> <!--@<< main table >>--> <!--@+node:ekr.20080301075734.4:<< main table >>-->

<table border="0" bgcolor="#FFFBDC"> <tr> <td width="22%" height="21" align="center" bgcolor="#FFFBDC" valign="middle"> <b><font size="4">Leo's Home</font></b> </td>

<td bgcolor="#F0E68C" width="78%" height="21" align="center"> <!--@ << upper table >>--> <!--@+node:ekr.20080301080758:<< upper table>>-->

<table border="0" width="100%"> <tr> <td width="25%" align="center"> <a href="http://www.python.org/"> <img border="0" src="PythonPowered.gif" width="110" height="44" /></a> </td>

<td width="50%" align="center"> <a href="http://groups.google.com/group/leo-editor">Leo at Google Groups</a>

</td>

<td width="25%" align="center"> <a href="http://www.mind-mapping.org/">mind-mapping.org</a> </td> </tr> </table> <!--@nonl--> <!--@-node:ekr.20080301080758:<< upper table>>-->

<!--@nl--> </td> </tr>

<tr> <td bgcolor="#F0E68C" width="22%" height="143" valign="top"> <!--@ << left table >>--> <!--@+node:ekr.20080301075734.6:<< left table >>--> <table border="0" width="100%">

<tr><td width="50%" bgcolor="#DAA520"><b>Documentation</b></td></tr> <tr><td width="50%"> <!-- <a href="http://sourceforge.net/project/screenshots.php?group_id=3458">Screen Shots</a> --> <a href="http:screen-shots.html">Screen Shots</a> </td></tr> <tr><td width="50%"><a href="intro.html">Beginners Guide</a></td></tr> <tr><td width="50%"><a href="leo_toc.html">Users Guide</a></td></tr>

<tr><td width="50%"><a href="FAQ.html">FAQ</a></td></tr> <tr><td width="50%"> <a href="http://www.3dtree.com/ev/e/sbooks/leo/sbframetoc_ie.htm">Online tutorial</a> </td></tr> <tr><td width="50%"><a href="testimonials.html">Quotes</a></td></tr> <tr><td width="50%" bgcolor="#DAA520"><b>Links</b></td></tr> <tr><td width="50%">

<!--@ << download ref >>--> <!--@+node:ekr.20080306071848:<< download ref >> (contains version number)--> <a href="http://sourceforge.net/project/showfiles.php?group_id=3458&amp;package_id=29106"> Download Leo[4.7 beta 1]</a> <!--@nonl--> <!--@-node:ekr.20080306071848:<< download ref >> (contains version number)--> <!--@nl--> </td></tr>

<tr><td width="50%"><a href="http://sourceforge.net/project/?group_id=3458"> Leo at SourceForge</a></td></tr> <tr><td width="50%"><a href="http://groups.google.com/group/leo-editor">Leo at Google Groups</a></td></tr> <tr><td width="50%"><a href="http://launchpad.net/leo-editor">Leo at launchpad</a></td></tr> <!-- <tr><td width="50%"><a href="http://leo.tigris.org/source/browse/leo/">Leo's cvs repository</a></td></tr> --> <tr><td width="50%">Leo's <a href="http://leo.zwiki.org">Wiki</a></td></tr> <tr><td width="50%">Leo's <a href="http://www.greygreen.org/leo/">Daily snapshots</a></td></tr>

<tr><td width="50%"><a href="leoLinks.html">More links</a></td></tr> <tr><td width="50%" bgcolor="#DAA520"><b>Edward K. Ream</b></td></tr> <tr><td width="50%"><a href="ekr.html">Home</a></td></tr> <tr><td width="50%"><a href="mailto:edreamleo@gmail.com">Contact</a></td></tr> </table> <!--@-node:ekr.20080301075734.6:<< left table >>--> <!--@nl-->

</td>

<td width="78%" height="143" bgcolor="#FFFBDC"> <!--@ << center area >>--> <!--@+node:ekr.20080301082848:<< center area >>--> <h2> Leo is...</h2>

<ul> <!--@<< what is Leo list >>-->

<!--@+node:ekr.20080301083325:<< what is Leo list >>--> <li> A <i>versatile data management platform</i>.<br/> Leo shows <b>user-created</b> relationships among any kind of data:<br /> computer programs, web sites, etc.<br />

Leo shows multiple views of data within a single outline.</li> <li> An <i>outlining editor for programmers</i>.<br /> Leo supports <i>optional</i> <a HREF="http://www.eecs.harvard.edu/~nr/noweb/">noweb</a> and <a HREF="http://www-cs-faculty.stanford.edu/~knuth/cweb.html">CWEB</a> markup.</li>

<li>A flexible <i>browser</i> for projects, programs, classes or any other data.</li> <li>A <i>project manager</i>.</li> <li><i>Portable</i>. Leo runs on Windows, Linux and MacOS X.</li> <li><i>100% pure</i> <a HREF="http://www.python.org/"><i>Python</i></a>.</li>

<li><i>Fully scriptable using </i><a HREF="http://www.python.org/"><i>Python</i></a>.</li> <li>Leo's outline files are <a HREF="http://www.w3.org/XML/">XML</a> format.</li> <li> <a href="http://www.opensource.org/"><i>Open Software</i></a>, distributed under the <a href="http://www.opensource.org/licenses/mit-license.php/">

MIT License </a>. </li> <!--@nonl--> <!--@-node:ekr.20080301083325:<< what is Leo list >>--> <!--@nl--> </ul>

<p> Please use <a href="http://groups.google.com/group/leo-editor">leo-editor</a>

to ask questions and make suggestions. </p>

<p> Leo's author is <a HREF="ekr.html">Edward K. Ream</a> </p> <!--@nonl--> <!--@-node:ekr.20080301082848:<< center area >>--> <!--@nl-->

</td> </tr> </table> <!--@-node:ekr.20080301075734.4:<< main table >>--> <!--@nl--> </body> </html>

<!--@-node:ekr.20080301071818.1:@thin html/front.html--> <!--@-leo-->
# @button nodes create buttons in the icon bar. # Pressing the button, applies the script to the selected node.

g.es('%s characters in headline' % (len(p.h)))
@language python
@nocolor-node

To exercise the bug, do the following in test.leo:

- Expand the "prototypes" node. - Expand any child node. - Move beyond the now-expanded child node. - Hit Alt-Left. This will select the prototype node. The expanded child node will be marked by Leo as being contracted, but it *wont* be contracted actually, which confuses moveToVisNext.
@nocolor-node

The culprit was the sparse_goto_parent option. It never worked properly, and it caused the expansion state of nodes as seen by Leo to get out-of-synch with the expansion state as seen by the qt tree code.
s = p.bodyString()

s = s.replace('\n\n','***2***') s = s.replace('\n',' ') s = s.replace('***2***','\n\n') s = s.replace('\n ','\n') s = s.replace('\n ','\n') s = s.replace(' ',' ') s = s.replace(' ',' ') s = s.replace(' ',' ') s = s.replace(' ',' ')

p.setBodyString(s)
/home/edreamleo/leo.repo/trunk/leo/Icons/Lioness.jpg
*Nodes* contain *headlines* and *body text*.

Nodes and outlines are true Python objects.
@language python
@nocolor-node

To exercise the bug, do the following in test.leo:

- Expand the "prototypes" node. - Expand any child node. - Move beyond the now-expanded child node. - Hit Alt-Left. This will select the prototype node. The expanded child node will be marked by Leo as being contracted, but it *wont* be contracted actually, which confuses moveToVisNext.
@nocolor-node

The culprit was the sparse_goto_parent option. It never worked properly, and it caused the expansion state of nodes as seen by Leo to get out-of-synch with the expansion state as seen by the qt tree code.
def selectVisNext (self,event=None):

'''Select the visible node following the presently selected node.'''

c = self ; p = c.p if not p: return if not c.canSelectVisNext(): return

p.moveToVisNext(c) c.treeSelectHelper(p)
# This has an up arrow for a control key.

def selectVisBack (self,event=None):

'''Select the visible node preceding the presently selected node.'''

c = self ; p = c.p if not p: return if not c.canSelectVisBack(): return

p.moveToVisBack(c)

# g.trace(p.h) c.treeSelectHelper(p)
def goToParent (self,event=None):

'''Select the parent of the selected node.'''

c = self ; p = c.p

# g.trace(p.parent())

c.treeSelectHelper(p and p.parent())
def contractNodeOrGoToParent (self,event=None):

"""Simulate the left Arrow Key in folder of Windows Explorer."""

trace = False and not g.unitTesting c = self ; p = c.p redraw = False ; fullRedraw = False if p.hasChildren() and p.isExpanded(): if trace: g.trace('contract',p.h) c.contractNode() redraw = True # New in one-node world. elif p.hasParent() and p.parent().isVisible(c): redraw = False p.contract() # Make sure we know this node is contracted. # This "feature" is dubious. # To work properly, it requires a full redraw. if False: # self.sparse_goto_parent: for child in p.self_and_siblings(): if child != p and child.isExpanded(): child.contract() redraw = True ; fullRedraw = True if trace: g.trace('goto parent',p.h) c.goToParent()

if redraw: if fullRedraw or p.isCloned(): if trace: g.trace('full redraw',p.h) c.redraw() else: c.redraw_after_contract(p=p,setFocus=True)
def expandNodeAndGoToFirstChild (self,event=None):

"""If a node has children, expand it if needed and go to the first child."""

trace = False and not g.unitTesting c = self ; p = c.p

# New code. if p.hasChildren(): if p.isExpanded(): p.moveToFirstChild() if trace: g.trace('select',p.h) c.selectPosition(p) else: if trace: g.trace('expand',p.h) c.expandNode() # Calls redraw_after_expand. elif p.hasNext(): c.goToNextSibling() else: while p.hasParent(): p.moveToParent() if p.hasNext(): p.moveToNext() break c.selectPosition(p) c.treeFocusHelper()

def expandNodeOrGoToFirstChild (self,event=None):

"""Simulate the Right Arrow Key in folder of Windows Explorer."""

c = self ; p = c.p if p.hasChildren(): if not p.isExpanded(): c.expandNode() # Calls redraw_after_expand. else: c.redraw_after_expand(p.firstChild(),setFocus=True)
def contractNode (self,event=None):

'''Contract the presently selected node.'''

c = self ; p = c.p

p.contract()

if p.isCloned(): c.redraw() # A full redraw is necessary to handle clones. else: c.redraw_after_contract(p=p,setFocus=True)
def expandNode (self,event=None):

'''Expand the presently selected node.'''

trace = False and not g.unitTesting c = self ; p = c.p

p.expand()

if p.isCloned(): if trace: g.trace('***redraw') c.redraw() # Bug fix: 2009/10/03. else: c.redraw_after_expand(p,setFocus=True)

def treeSelectHelper (self,p):

c = self

if not p: p = c.p

if p: # Do not call expandAllAncestors here. c.selectPosition(p) c.redraw_after_select(p)

c.treeFocusHelper()
def moveToVisBack (self,c):

"""Move a position to the position of the previous visible node."""

trace = False and not g.unitTesting verbose = True p = self ; limit,limitIsVisible = c.visLimit() if trace and verbose: g.trace(p,'limit',limit,'limitIsVisible',limitIsVisible) if trace: g.trace('***entry','parent',p.parent(),'p',p,g.callers(5)) while p: # Short-circuit if possible. back = p.back() if trace: g.trace( 'back',back,'hasChildren',bool(back and back.hasChildren()), 'isExpanded',bool(back and back.isExpanded()))

if back and back.hasChildren() and back.isExpanded(): p.moveToThreadBack() elif back: p.moveToBack() else: p.moveToParent() # Same as p.moveToThreadBack() # if back and (not back.hasChildren() or not back.isExpanded()): # p.moveToBack() # else: # p.moveToThreadBack() if trace: g.trace(p.parent(),p) if p: if trace and verbose: g.trace('**p',p) done,val = self.checkVisBackLimit(limit,limitIsVisible,p) if done: if trace and verbose: g.trace('done',p) return val if p.isVisible(c): if trace and verbose: g.trace('isVisible',p) return p else: # assert not p. return p
def checkVisBackLimit (self,limit,limitIsVisible,p):

'''Return done, return-val'''

trace = True and not g.unitTesting c = p.v.context

if limit: if limit == p: if trace: g.trace('at limit',p) if limitIsVisible and p.isVisible(c): return True,p else: return True,None #return True,g.choose(limitIsVisible and p.isVisible(c),p,None) elif limit.isAncestorOf(p): return False,None else: if trace: g.trace('outside limit tree',limit,p) return True,None else: return False,None
def moveToVisNext (self,c):

"""Move a position to the position of the next visible node."""

trace = False and not g.unitTesting verbose = False p = self ; limit,limitIsVisible = c.visLimit() while p: if trace: g.trace('1',p.h) # if trace: g.trace('hasChildren %s, isExpanded %s %s' % ( # p.hasChildren(),p.isExpanded(),p.h)) # Short-circuit if possible. if p.hasNext() and p.hasChildren() and p.isExpanded(): p.moveToFirstChild() elif p.hasNext(): p.moveToNext() else: p.moveToThreadNext() # if p.hasNext() and (not p.hasChildren() or not p.isExpanded()): # p.moveToNext() # else: # p.moveToThreadNext() if trace: g.trace('2',p.h) if p: done,val = self.checkVisNextLimit(limit,p) if done: return val if p.isVisible(c): return p.copy() else: # assert not p. return p
def checkVisNextLimit (self,limit,p):

'''Return done, return-val'''

trace = False and not g.unitTesting

if limit: # Unlike moveToVisBack, being at the limit does not terminate. if limit == p: return False, None elif limit.isAncestorOf(p): return False,None else: if trace: g.trace('outside limit tree') return True,None else: return False,None
@language python @tabwidth -4 @pagewidth 80

<< A longish docstring >> << imports >>

class myClass: '''An example class''' @others
import os import sys
@language python # This is Leo markup. It applies to all descendant nodes.

# The execute-script command predefines g and c.

g.es('%s contains %s nodes' % ( c.shortFileName(), len(list(c.all_nodes()))), color='red')
By *convention*, headlines that start with @x declare **node types**.

Leo's core supports many types. Scripts and plugins support others.
@language python
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!--@+leo-ver=4-thin--> <!--@+node:ekr.20080301071818.1:@thin html/front.html--> <!--@@first-->

<!--@@language html--> <!--@@tabwidth -4-->

<!-- Last Modified: July 31, 2008 --> <!-- Author: Edward K. Ream, edreamleo@gmail.com -->

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <!--@<<head>>--> <!--@+node:ekr.20080301075734.1:<<head>>--> <title>Leo's Home Page</title>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

<meta name="description" content="This page describes Leo, a programmer's outliner." />

<meta name="keywords" content="LEO, LITERATE PROGRAMMING, OUTLINES, CWEB, NOWEB, EDWARD K. REAM, DONALD E. KNUTH, SILVIO LEVY, OPEN SOFTWARE" />

<!-- for GeoUrl --> <meta name="ICBM" content="43.0713768, -89.4188004" /> <meta name="DC.title" content="Leo's Home Page" /> <!--@-node:ekr.20080301075734.1:<<head>>--> <!--@nl--> </head>

<body bgcolor="#fffbdc"> <!--@<< main table >>--> <!--@+node:ekr.20080301075734.4:<< main table >>-->

<table border="0" bgcolor="#FFFBDC"> <tr> <td width="22%" height="21" align="center" bgcolor="#FFFBDC" valign="middle"> <b><font size="4">Leo's Home</font></b> </td>

<td bgcolor="#F0E68C" width="78%" height="21" align="center"> <!--@ << upper table >>--> <!--@+node:ekr.20080301080758:<< upper table>>-->

<table border="0" width="100%"> <tr> <td width="25%" align="center"> <a href="http://www.python.org/"> <img border="0" src="PythonPowered.gif" width="110" height="44" /></a> </td>

<td width="50%" align="center"> <a href="http://groups.google.com/group/leo-editor">Leo at Google Groups</a>

</td>

<td width="25%" align="center"> <a href="http://www.mind-mapping.org/">mind-mapping.org</a> </td> </tr> </table> <!--@nonl--> <!--@-node:ekr.20080301080758:<< upper table>>-->

<!--@nl--> </td> </tr>

<tr> <td bgcolor="#F0E68C" width="22%" height="143" valign="top"> <!--@ << left table >>--> <!--@+node:ekr.20080301075734.6:<< left table >>--> <table border="0" width="100%">

<tr><td width="50%" bgcolor="#DAA520"><b>Documentation</b></td></tr> <tr><td width="50%"> <!-- <a href="http://sourceforge.net/project/screenshots.php?group_id=3458">Screen Shots</a> --> <a href="http:screen-shots.html">Screen Shots</a> </td></tr> <tr><td width="50%"><a href="intro.html">Beginners Guide</a></td></tr> <tr><td width="50%"><a href="leo_toc.html">Users Guide</a></td></tr>

<tr><td width="50%"><a href="FAQ.html">FAQ</a></td></tr> <tr><td width="50%"> <a href="http://www.3dtree.com/ev/e/sbooks/leo/sbframetoc_ie.htm">Online tutorial</a> </td></tr> <tr><td width="50%"><a href="testimonials.html">Quotes</a></td></tr> <tr><td width="50%" bgcolor="#DAA520"><b>Links</b></td></tr> <tr><td width="50%">

<!--@ << download ref >>--> <!--@+node:ekr.20080306071848:<< download ref >> (contains version number)--> <a href="http://sourceforge.net/project/showfiles.php?group_id=3458&amp;package_id=29106"> Download Leo[4.7 beta 1]</a> <!--@nonl--> <!--@-node:ekr.20080306071848:<< download ref >> (contains version number)--> <!--@nl--> </td></tr>

<tr><td width="50%"><a href="http://sourceforge.net/project/?group_id=3458"> Leo at SourceForge</a></td></tr> <tr><td width="50%"><a href="http://groups.google.com/group/leo-editor">Leo at Google Groups</a></td></tr> <tr><td width="50%"><a href="http://launchpad.net/leo-editor">Leo at launchpad</a></td></tr> <!-- <tr><td width="50%"><a href="http://leo.tigris.org/source/browse/leo/">Leo's cvs repository</a></td></tr> --> <tr><td width="50%">Leo's <a href="http://leo.zwiki.org">Wiki</a></td></tr> <tr><td width="50%">Leo's <a href="http://www.greygreen.org/leo/">Daily snapshots</a></td></tr>

<tr><td width="50%"><a href="leoLinks.html">More links</a></td></tr> <tr><td width="50%" bgcolor="#DAA520"><b>Edward K. Ream</b></td></tr> <tr><td width="50%"><a href="ekr.html">Home</a></td></tr> <tr><td width="50%"><a href="mailto:edreamleo@gmail.com">Contact</a></td></tr> </table> <!--@-node:ekr.20080301075734.6:<< left table >>--> <!--@nl-->

</td>

<td width="78%" height="143" bgcolor="#FFFBDC"> <!--@ << center area >>--> <!--@+node:ekr.20080301082848:<< center area >>--> <h2> Leo is...</h2>

<ul> <!--@<< what is Leo list >>-->

<!--@+node:ekr.20080301083325:<< what is Leo list >>--> <li> A <i>versatile data management platform</i>.<br/> Leo shows <b>user-created</b> relationships among any kind of data:<br /> computer programs, web sites, etc.<br />

Leo shows multiple views of data within a single outline.</li> <li> An <i>outlining editor for programmers</i>.<br /> Leo supports <i>optional</i> <a HREF="http://www.eecs.harvard.edu/~nr/noweb/">noweb</a> and <a HREF="http://www-cs-faculty.stanford.edu/~knuth/cweb.html">CWEB</a> markup.</li>

<li>A flexible <i>browser</i> for projects, programs, classes or any other data.</li> <li>A <i>project manager</i>.</li> <li><i>Portable</i>. Leo runs on Windows, Linux and MacOS X.</li> <li><i>100% pure</i> <a HREF="http://www.python.org/"><i>Python</i></a>.</li>

<li><i>Fully scriptable using </i><a HREF="http://www.python.org/"><i>Python</i></a>.</li> <li>Leo's outline files are <a HREF="http://www.w3.org/XML/">XML</a> format.</li> <li> <a href="http://www.opensource.org/"><i>Open Software</i></a>, distributed under the <a href="http://www.opensource.org/licenses/mit-license.php/">

MIT License </a>. </li> <!--@nonl--> <!--@-node:ekr.20080301083325:<< what is Leo list >>--> <!--@nl--> </ul>

<p> Please use <a href="http://groups.google.com/group/leo-editor">leo-editor</a>

to ask questions and make suggestions. </p>

<p> Leo's author is <a HREF="ekr.html">Edward K. Ream</a> </p> <!--@nonl--> <!--@-node:ekr.20080301082848:<< center area >>--> <!--@nl-->

</td> </tr> </table> <!--@-node:ekr.20080301075734.4:<< main table >>--> <!--@nl--> </body> </html>

<!--@-node:ekr.20080301071818.1:@thin html/front.html--> <!--@-leo-->
import leo.core.leoTest as leoTest leoTest.runEditCommandTest(c,p)
first line line 1 line a line b line c last line
first line line 1 line a line b line c last line
first line line 1 line a line b line c last line
# @button nodes create buttons in the icon bar. # Pressing the button, applies the script to the selected node.

g.es('%s characters in headline' % (len(p.h)))
You can do things with Leo that you can't even *imagine* doing in other programs.

Some of the world's great programmers use Leo.
Peole get excited about Leo...
"The most amazing software since the invention of the spreadsheet."

"A breakthrough tool and a whole new way of writing code."

"Utterly changed my view of programming (indeed of writing) forever."
"They'll have to pry Leo out of my cold, dead fingers!"

"I have been absolutely seduced by Leo over the past few days...I can not put it down."

"There is almost an addictive withdrawal effect when I can complete an operation in so much less time with Leo & python than I had become used to."
"Word outlines are very useful. But Leo makes Word look like a clunky toy."

"Leo is *exactly* the kind of outliner I was looking for."
"Your current trajectory puts you on track to kick Emacs into the dustbin of computing history."

Note: Many people use Leo *with* vim, emacs or ipython.

"People complain about the lack of a project manager for [Python]. Leo clearly solves that problem and in a way that commercial tools can't touch."
"Leo is the best URL bookmark manager there is.

"I use Leo to organize my lists of pictures."
"I've already started converting the IT Procedures manual from Open Office to Leo...the possibility of keeping system maintenance scripts in the IT manual is mind boggling."

"Leo is the best outliner I have yet encountered for writing the early stages of academic papers."
"Leo allows the designer to reveal and discipline structure at many layers simultaneously: data structures, object structure, entity-relationship structure, client-server structure, design pattern structure, temporal structure, project management structure, and any other structure relevant to the system."

"When I 'Leo-ise' other people's code, Leo makes the code's structure so transparent that design faults become very quickly apparent."
"A breakthrough tool and a whole new way of writing code." -- Joe Orr
"They'll have to pry Leo out of my cold, dead fingers!" -- Travers A. Hough
"Leo makes Word [outlines] look like a clunky toy." -- Joe Orr
"[Leo is] on track to kick Emacs into the dustbin of computing history." -- Dan Winkler
"Leo is the best URL bookmark manager there is." -- Dan Winkler
"Leo is the best outliner I have yet encountered for writing the early stages of academic papers." -- Anon.
"Leo allows the designer to reveal..structure at many layers simultaneously..." -- Steven P. Schaefer

"When I 'Leo-ise' other people's code, Leo makes the code's structure so transparent that design faults become very quickly apparent." -- David McNab
@language rest
Thanks, David, for inviting me.

I'm excited to be here talking about the open-source project that I've been leading for 15+ years.
Programmers?

Use vim or emacs?

Any others?

TeX? Knuth? CWEB
C:\leo.repo\trunk\leo\Icons\Lioness.jpg
@language html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head></head>

<body bgcolor="#fffbdc">

<img src="C:\leo.repo\trunk\leo\Icons\Lioness.jpg">

<!-- <img src="C:\leo.repo\trunk\leo\Icons\Lioness.jpg" width="864" height="560"> <img src="C:\leo.repo\trunk\leo\Icons\LIONA2.JPG" width="555" height="804"> <img src="C:\leo.repo\trunk\leo\Icons\VincennesZoo20020622_061.jpg" width="2048" height="1536"> -->

</body> </html>
Leo creates a playground.
"...keeping system maintenance scripts in the IT manual is mind boggling." -- David Nichols
People get excited about Leo.

There is an Aha moment.
Google edreamleo

Email: edreamleo@gmail.com

Google Group: http://groups.google.com/group/leo-editor

C:\leo.repo\trunk\leo\Icons\Lioness.jpg
assert 1 == 2
@language rest

- Programs (and data!) are outlines in disguise, but...

viewing programs (or data) in an outline is not enough!

- Programs must be *part of* outlines.

- *Simple* markup connects outlines and programs. - @others allows you to see data at *any* level of detail. (Tail recursion isn't good enough: @others specifies indentation) - << section >> encapsulates hard-to-encapsulate programs/data.

- Outlines must be true objects. - Headlines are meta-data.

- There is no such thing a single "right" view of data.

- Clones let you create as many views: you can put clones in as many views as you like.

Usually, outlines are just features of IDE's.

With Leo, outlines are an *integral* part of programming (and data).
Leo is the combination of:

1. noweb markup. 2. @others, the *only* new essential markup in Leo. 3. MORE outlines with clones.
- Eliminate details without talking down to your audience.

- Spend *half* your time communicating your work.

- Tell stories.

* I've benefitted greatly from this talk.
You and your research: Richard Hamming.

Ideas that stick.

The TeXBook.

MORE

Literate Programming.

Leo

Python
I answer all my email: edreamleo@gmail.com

leo-editor:

Leo's users guide (being rewritten)
# Passive integration
'''This is my class.'''
def __init__ (self): pass
def doSomething (self):

pass
Studied TeX. Could not really understand it. Wandering in the wilderness. Aha 1: webs are outlines in disguise. Aha 2: More as a prototype. << section >> came from cweb I invented @others

Lucky: you won't invent sections or @others if you start with outlines. You must start with markup first.
Before: so what?

After: wow!

I've gotten aha's from working on this talk.
No other tool combines all 5 elements.

Most give only one.
No one right view of data.

Headlines create natural meta-data.

Leo outlines combine hierarchical, relational and oo database features.

@language python @tabwidth -4 @pagewidth 80

use_zodb = False

<< imports >>

@others
if use_zodb: # It may be important to import ZODB first. try: import ZODB import ZODB.FileStorage except ImportError: ZODB = None else: ZODB = None

import leo.core.leoGlobals as g

if g.app and g.app.use_psyco: # g.pr("enabled psyco classes",__file__) try: from psyco.classes import * except ImportError: pass

import time import re import itertools
if use_zodb and ZODB: class baseVnode (ZODB.Persistence.Persistent): pass else: class baseVnode (object): pass

class vnode (baseVnode): << vnode constants >> @others
# Define the meaning of status bits in new vnodes.

# Archived... clonedBit = 0x01 # True: vnode has clone mark. # unused 0x02 expandedBit = 0x04 # True: vnode is expanded. markedBit = 0x08 # True: vnode is marked orphanBit = 0x10 # True: vnode saved in .leo file, not derived file. selectedBit = 0x20 # True: vnode is current vnode. topBit = 0x40 # True: vnode was top vnode when saved.

# Not archived... richTextBit = 0x080 # Determines whether we use <bt> or <btr> tags. visitedBit = 0x100 dirtyBit = 0x200 writeBit = 0x400
# To support ZODB, the code must set v._p_changed = 1 whenever # v.unknownAttributes or any mutable vnode object changes.

def __init__ (self,context):

# The primary data: headline and body text. if g.isPython3: self._headString = 'newHeadline' self._bodyString = '' else: self._headString = unicode('newHeadline') self._bodyString = unicode('')

# Structure data... self.children = [] # Ordered list of all children of this node. self.parents = [] # Unordered list of all parents of this node.

# Other essential data... self.fileIndex = g.app.nodeIndices.getNewIndex() # The immutable file index for this vnode. # New in Leo 4.6 b2: allocate gnx (fileIndex) immediately. self.iconVal = 0 # The present value of the node's icon. self.statusBits = 0 # status bits

# v.t no longer exists. All code must now be aware of the one-node world. # self.t = self # For compatibility with scripts and plugins.

# Information that is never written to any file... self.context = context # The context containing context.hiddenRootNode. # Required so we can compute top-level siblings. # It is named .context rather than .c to emphasize its limited usage. self.insertSpot = None # Location of previous insert point. self.scrollBarSpot = None # Previous value of scrollbar position. self.selectionLength = 0 # The length of the selected body text. self.selectionStart = 0 # The start of the selected body text.
def __repr__ (self):

return "<vnode %d:'%s'>" % (id(self),self.cleanHeadString())

__str__ = __repr__
def dumpLink (self,link): return g.choose(link,link,"<none>")

def dump (self,label=""):

v = self print('%s %s %s' % ('-'*10,label,v)) print('len(parents) %s' % len(v.parents)) print('len(children) %s' % len(v.children)) print('parents %s' % g.listToString(v.parents)) print('children%s' % g.listToString(v.children))
if use_zodb and ZODB: def __hash__(self): return self.__hash__()
def findAtFileName (self,names,h=''):

"""Return the name following one of the names in nameList. Return an empty string."""

# Allow h argument for unit testing. if not h: h = self.headString()

if not g.match(h,0,'@'): return ""

i = g.skip_id(h,1,'-') word = h[:i] if word in names and g.match_word(h,0,word): name = h[i:].strip() # g.trace(repr(word),repr(name)) return name else: return ""
def anyAtFileNodeName (self):

"""Return the file name following an @file node or an empty string."""

names = ( "@auto", "@auto-rst", "@edit", "@file", "@thin", "@file-thin", "@thinfile", "@asis", "@file-asis", "@silentfile", "@noref", "@file-noref", "@rawfile", "@nosent", "@file-nosent", "@nosentinelsfile", "@shadow",)

return self.findAtFileName(names)
# These return the filename following @xxx, in v.headString. # Return the the empty string if v is not an @xxx node.

def atAutoNodeName (self,h=None): # # Prevent conflicts with autotrees plugin: don't allow @auto-whatever to match. # return g.match_word(h,0,tag) and not g.match(h,0,tag+'-') and h[len(tag):].strip() names = ("@auto","@auto-rst",) return self.findAtFileName(names,h=h)

def atAutoRstNodeName (self,h=None): names = ("@auto-rst",) return self.findAtFileName(names,h=h)

def atEditNodeName (self): names = ("@edit",) return self.findAtFileName(names)

def atFileNodeName (self): names = ("@file",) return self.findAtFileName(names)

def atNoSentinelsFileNodeName (self): names = ("@nosent", "@file-nosent", "@nosentinelsfile") return self.findAtFileName(names)

def atRawFileNodeName (self): names = ("@noref", "@file-noref", "@rawfile") return self.findAtFileName(names)

def atShadowFileNodeName (self): names = ("@shadow",) return self.findAtFileName(names)

def atSilentFileNodeName (self): names = ("@asis", "@file-asis", "@silentfile") return self.findAtFileName(names)

def atThinFileNodeName (self): names = ("@thin", "@file-thin", "@thinfile") return self.findAtFileName(names)

# New names, less confusing atNoSentFileNodeName = atNoSentinelsFileNodeName atNorefFileNodeName = atRawFileNodeName atAsisFileNodeName = atSilentFileNodeName
if g.unitTesting:

c,p = g.getTestVars()

table = ( ('@auto-rst rst-file','rst-file','rst-file'), ('@auto x','x',''), ('xyz','',''), )

for s,expected1,expected2 in table: result1 = p.v.atAutoNodeName(h=s) result2 = p.v.atAutoRstNodeName(h=s) assert result1 == expected1,'fail1: given %s expected %s got %s' % ( repr(s),repr(expected1),repr(result1)) assert result2 == expected2,'fail2: given %s expected %s got %s' % ( repr(s),repr(expected2),repr(result2))
def isAtAllNode (self):

"""Returns True if the receiver contains @others in its body at the start of a line."""

flag, i = g.is_special(self._bodyString,0,"@all") return flag
def isAnyAtFileNode (self):

"""Return True if v is any kind of @file or related node."""

# This routine should be as fast as possible. # It is called once for every vnode when writing a file.

h = self.headString() return h and h[0] == '@' and self.anyAtFileNodeName()
def isAtAutoNode (self): return g.choose(self.atAutoNodeName(),True,False)

def isAtAutoRstNode (self): return g.choose(self.atAutoRstNodeName(),True,False)

def isAtEditNode (self): return g.choose(self.atEditNodeName(),True,False)

def isAtFileNode (self): return g.choose(self.atFileNodeName(),True,False)

def isAtNoSentinelsFileNode (self): return g.choose(self.atNoSentinelsFileNodeName(),True,False)

def isAtRawFileNode (self): # @file-noref return g.choose(self.atRawFileNodeName(),True,False)

def isAtSilentFileNode (self): # @file-asis return g.choose(self.atSilentFileNodeName(),True,False)

def isAtShadowFileNode (self): return g.choose(self.atShadowFileNodeName(),True,False)

def isAtThinFileNode (self): return g.choose(self.atThinFileNodeName(),True,False)

# New names, less confusing: isAtNoSentFileNode = isAtNoSentinelsFileNode isAtNorefFileNode = isAtRawFileNode isAtAsisFileNode = isAtSilentFileNode
def isAtIgnoreNode (self):

"""Returns True if the receiver contains @ignore in its body at the start of a line."""

flag, i = g.is_special(self._bodyString, 0, "@ignore") return flag
def isAtOthersNode (self):

"""Returns True if the receiver contains @others in its body at the start of a line."""

flag, i = g.is_special(self._bodyString,0,"@others") return flag
def matchHeadline (self,pattern):

"""Returns True if the headline matches the pattern ignoring whitespace and case.

The headline may contain characters following the successfully matched pattern."""

v = self

h = g.toUnicode(v.headString(),'utf-8') h = h.lower().replace(' ','').replace('\t','')

pattern = g.toUnicode(pattern,'utf-8') pattern = pattern.lower().replace(' ','').replace('\t','')

return h.startswith(pattern)
def bodyString (self):

# This message should never be printed and we want to avoid crashing here! if not g.isUnicode(self._bodyString): s = "v.bodyString: Leo internal error: not unicode:" + repr(self._bodyString) g.es_print('',s,color="red")

# Make _sure_ we return a unicode string. return g.toUnicode(self._bodyString,g.app.tkEncoding)

getBody = bodyString
def firstChild (self):

v = self return v.children and v.children[0]
def hasChildren (self):

v = self return len(v.children) > 0

hasFirstChild = hasChildren
def lastChild (self):

v = self return v.children and v.children[-1] or None
# childIndex and nthChild are zero-based.

def nthChild (self, n):

v = self

if 0 <= n < len(v.children): return v.children[n] else: return None
def numberOfChildren (self):

v = self return len(v.children)
def directParents (self):

"""(New in 4.2) Return a list of all direct parent vnodes of a vnode.

This is NOT the same as the list of ancestors of the vnode."""

v = self return v.parents
def hasBody (self):

'''Return True if this vnode contains body text.'''

s = self._bodyString

return s and len(s) > 0
def headString (self):

"""Return the headline string."""

# This message should never be printed and we want to avoid crashing here! if not g.isUnicode(self._headString): s = "Leo internal error: not unicode:" + repr(self._headString) g.es_print('',s,color="red")

# Make _sure_ we return a unicode string. return g.toUnicode(self._headString,g.app.tkEncoding)

def cleanHeadString (self):

s = self._headString return g.toEncodedString(s,"ascii") # Replaces non-ascii characters by '?'
def isCloned (self):

return len(self.parents) > 1
def isDirty (self):

return (self.statusBits & self.dirtyBit) != 0
def isExpanded (self):

# g.trace( ( self.statusBits & self.expandedBit ) != 0, g.callers())

return ( self.statusBits & self.expandedBit ) != 0
def isMarked (self):

return ( self.statusBits & vnode.markedBit ) != 0
def isOrphan (self):

return ( self.statusBits & vnode.orphanBit ) != 0
def isSelected (self):

return ( self.statusBits & vnode.selectedBit ) != 0
def isTopBitSet (self):

return ( self.statusBits & self.topBit ) != 0
def isVisited (self):

return ( self.statusBits & vnode.visitedBit ) != 0
def isWriteBit (self):

v = self return (v.statusBits & v.writeBit) != 0
def status (self):

return self.statusBits
def clearDirty (self): v = self v.statusBits &= ~ v.dirtyBit

def findAllPotentiallyDirtyNodes(self):

trace = False and not g.unitTesting v = self ; c = v.context

# Set the starting nodes. nodes = [] newNodes = [v]

# Add nodes until no more are added. while newNodes: addedNodes = [] nodes.extend(newNodes) for v in newNodes: for v2 in v.parents: if v2 not in nodes and v2 not in addedNodes: addedNodes.append(v2) newNodes = addedNodes[:]

# Remove the hidden vnode. if c.hiddenRootNode in nodes: if trace: g.trace('removing hidden root',c.hiddenRootNode) nodes.remove(c.hiddenRootNode)

if trace: g.trace(nodes) return nodes
# Unlike p.setAllAncestorAtFileNodesDirty, # there is no setDescendentsDirty arg.

def setAllAncestorAtFileNodesDirty (self):

trace = False and not g.unitTesting verbose = False v = self dirtyVnodeList = []

# Calculate all nodes that are joined to p or parents of such nodes. nodes = v.findAllPotentiallyDirtyNodes()

if trace and verbose: for v in nodes: print v.isDirty(),v.isAnyAtFileNode(),v

dirtyVnodeList = [v for v in nodes if not v.isDirty() and v.isAnyAtFileNode()]

changed = len(dirtyVnodeList) > 0

for v in dirtyVnodeList: v.setDirty() # Do not call v.setDirty here!

if trace: g.trace("vnode",dirtyVnodeList)

return dirtyVnodeList
def setDirty (self):

self.statusBits |= self.dirtyBit
def clearClonedBit (self):

self.statusBits &= ~ self.clonedBit
def clearMarked (self):

self.statusBits &= ~ self.markedBit
def clearWriteBit (self): self.statusBits &= ~ self.writeBit
def clearOrphan (self):

self.statusBits &= ~ self.orphanBit
def clearVisited (self):

self.statusBits &= ~ self.visitedBit
def contract(self):

# if self.context.p.v == self: g.trace(self,g.callers(4))

self.statusBits &= ~ self.expandedBit

def expand(self):

# g.trace(self,g.callers(4))

self.statusBits |= self.expandedBit

def initExpandedBit (self):

# g.trace(self._headString)

self.statusBits |= self.expandedBit
def initStatus (self, status):

self.statusBits = status
def setClonedBit (self):

self.statusBits |= self.clonedBit

def initClonedBit (self, val):

if val: self.statusBits |= self.clonedBit else: self.statusBits &= ~ self.clonedBit
def setMarked (self):

self.statusBits |= self.markedBit

def initMarkedBit (self):

self.statusBits |= self.markedBit
def setOrphan (self):

self.statusBits |= self.orphanBit
# This only sets the selected bit.

def setSelected (self):

self.statusBits |= self.selectedBit
# Compatibility routine for scripts

def setVisited (self):

self.statusBits |= self.visitedBit
def setWriteBit (self): self.statusBits |= self.writeBit
def setBodyString (self,s,encoding="utf-8"):

trace = False and not g.unitTesting v = self if trace and v._bodyString != s: g.trace('v %s %s -> %s %s\nold: %s\nnew: %s' % ( v.h, len(v._bodyString),len(s),g.callers(5), v._bodyString,s)) v._bodyString = g.toUnicode(s,encoding,reportErrors=True)

def setHeadString (self,s,encoding="utf-8"): v = self v._headString = g.toUnicode(s,encoding,reportErrors=True)

initBodyString = setBodyString initHeadString = setHeadString setHeadText = setHeadString setTnodeText = setBodyString
def setFileIndex (self, index):

self.fileIndex = index
def computeIcon (self):

val = 0 ; v = self if v.hasBody(): val += 1 if v.isMarked(): val += 2 if v.isCloned(): val += 4 if v.isDirty(): val += 8 return val

def setIcon (self):

pass # Compatibility routine for old scripts
def setSelection (self, start, length):

v = self v.selectionStart = start v.selectionLength = length
def _addLink (self,childIndex,parent_v,adjust=True): '''Adjust links after adding a link to v.'''

trace = False and not g.unitTesting v = self

# Update parent_v.children & v.parents. parent_v.children.insert(childIndex,v) v.parents.append(parent_v) if trace: g.trace('*** added parent',parent_v,'to',v, 'len(parents)',len(v.parents))

# Set zodb changed flags. v._p_changed = 1 parent_v._p_changed = 1

# If v has only one parent, we adjust all # the parents links in the descendant tree. # This handles clones properly when undoing a delete. if adjust: if len(v.parents) == 1: for child in v.children: child._addParentLinks(parent=v)
def _addParentLinks(self,parent):

trace = False and not g.unitTesting v = self

v.parents.append(parent) if trace: g.trace( '*** added parent',parent,'to',v,'len(parents)',len(v.parents))

if len(v.parents) == 1: for child in v.children: child._addParentLinks(parent=v)
def _cutLink (self,childIndex,parent_v): '''Adjust links after cutting a link to v.''' v = self

assert parent_v.children[childIndex]==v del parent_v.children[childIndex] v.parents.remove(parent_v) v._p_changed = 1 parent_v._p_changed = 1

# If v has no more parents, we adjust all # the parent links in the descendant tree. # This handles clones properly when deleting a tree. if len(v.parents) == 0: for child in v.children: child._cutParentLinks(parent=v)
def _cutParentLinks(self,parent):

trace = False and not g.unitTesting v = self

if trace: g.trace('parent',parent,'v',v) v.parents.remove(parent)

if len(v.parents) == 0: for child in v.children: child._cutParentLinks(parent=v)
def _linkAsNthChild (self,parent_v,n):

"""Links self as the n'th child of vnode pv"""

v = self # The child node. v._addLink(n,parent_v)
def createOutlineFromCacheList(self,c,aList): """ Create outline structure from recursive aList built by p.makeCacheList.

Clones will be automatically created by gnx, but *not* for the top-level node. """

parent_v = self

#import pprint ; pprint.pprint(tree) parent_v = self h,b,gnx,children = aList if h is not None: v = parent_v v._headString = h v._bodyString = b

for z in children: h,b,gnx,grandChildren = z isClone,child_v = parent_v.fastAddLastChild(c,gnx) if isClone: if child_v.b != b: # or child_v.h # Bug fix: the last seen clone rules. child_v.h = h child_v.b = b # Bug fix: mark @<file> nodes dirty. child_v.setAllAncestorAtFileNodesDirty() child_v.setMarked() g.es("changed:",child_v.h,color="blue") else: child_v.createOutlineFromCacheList(c,z)
# Similar to createThinChild4 def fastAddLastChild(self,c,gnxString): '''Create new vnode as last child of the receiver.

If the gnx exists already, create a clone instead of new vnode. '''

trace = False and not g.unitTesting parent_v = self indices = g.app.nodeIndices gnxDict = c.fileCommands.gnxDict

if gnxString is None: v = None else: v = gnxDict.get(gnxString) is_clone = v is not None

if trace: g.trace( 'clone','%-5s' % (is_clone), 'parent_v',parent_v,'gnx',gnxString,'v',repr(v))

if not is_clone: v = vnode(context=c) if gnxString: gnx = indices.scanGnx(gnxString,0) v.fileIndex = gnx gnxDict[gnxString] = v

child_v = v child_v._linkAsNthChild(parent_v,parent_v.numberOfChildren()) child_v.setVisited() # Supress warning/deletion of unvisited nodes.

return is_clone,child_v
def __get_b(self):

v = self return v.bodyString()

def __set_b(self,val):

v = self v.setBodyString(val)

b = property( __get_b, __set_b, doc = "vnode body string property")
def __get_h(self):

v = self return v.headString()

def __set_h(self,val):

v = self v.setHeadString(val)

h = property( __get_h, __set_h, doc = "vnode headline string property")
def __get_u(self): v = self if not hasattr(v,'unknownAttributes'): v.unknownAttributes = {} return v.unknownAttributes

def __set_u(self,val): v = self if val is None: if hasattr(v,'unknownAttributes'): delattr(v,'unknownAttributes') elif type(val) == type({}): v.unknownAttributes = val else: raise ValueError

u = property( __get_u, __set_u, doc = "vnode unknownAttribute property")
def __get_gnx(self): v = self return g.app.nodeIndices.toString(v.fileIndex)

gnx = property( __get_gnx, # __set_gnx, doc = "vnode gnx property")
# Indices are Python dicts containing 'id','loc','time' and 'n' keys.

class nodeIndices (object):

"""A class to implement global node indices (gnx's)."""

@others
def __init__ (self,id):

"""ctor for nodeIndices class"""

self.userId = id self.defaultId = id

# A Major simplification: Only assign the timestamp once. self.setTimeStamp() self.lastIndex = 0
def areEqual (self,gnx1,gnx2):

"""Return True if all fields of gnx1 and gnx2 are equal"""

# works whatever the format of gnx1 and gnx2. # This should never throw an exception. return gnx1 == gnx2
# These are used by the fileCommands read/write code.

def getDefaultId (self):

"""Return the id to be used by default in all gnx's""" return self.defaultId

def setDefaultId (self,theId):

"""Set the id to be used by default in all gnx's""" self.defaultId = theId
def getNewIndex (self):

'''Create a new gnx.'''

self.lastIndex += 1 d = (self.userId,self.timeString,self.lastIndex) # g.trace(d) return d
def isGnx (self,gnx): try: theId,t,n = gnx return t != None except Exception: return False
def scanGnx (self,s,i):

"""Create a gnx from its string representation"""

if not g.isString(s): g.es("scanGnx: unexpected index type:",type(s),'',s,color="red") return None,None,None

s = s.strip()

theId,t,n = None,None,None i,theId = g.skip_to_char(s,i,'.') if g.match(s,i,'.'): i,t = g.skip_to_char(s,i+1,'.') if g.match(s,i,'.'): i,n = g.skip_to_char(s,i+1,'.') # Use self.defaultId for missing id entries. if theId == None or len(theId) == 0: theId = self.defaultId # Convert n to int. if n: try: n = int(n) except Exception: pass

return theId,t,n
def setTimestamp (self):

"""Set the timestamp string to be used by getNewIndex until further notice"""

self.timeString = time.strftime( "%Y%m%d%H%M%S", # Help comparisons; avoid y2k problems. time.localtime())

# g.trace(self.timeString,self.lastIndex,g.callers(4))

setTimeStamp = setTimestamp
def toString (self,index):

"""Convert a gnx (a tuple) to its string representation"""

try: theId,t,n = index if n in (None,0,'',): return "%s.%s" % (theId,t) else: return "%s.%s.%d" % (theId,t,n) except Exception: if not g.app.unitTesting: g.trace('unusual gnx',repr(index),g.callers()) try: theId,t,n = self.getNewIndex() if n in (None,0,'',): return "%s.%s" % (theId,t) else: return "%s.%s.%d" % (theId,t,n) except Exception: g.trace('double exception: returning original index') return repr(index)
<< about the position class >>

# Positions should *never* be saved by the ZOBD. class position (object): @others
@killcolor @

A position marks the spot in a tree traversal. A position p consists of a vnode p.v, a child index p._childIndex, and a stack of tuples (v,childIndex), one for each ancestor **at the spot in tree traversal. Positions p has a unique set of parents.

The p.moveToX methods may return a null (invalid) position p with p.v = None.

The tests "if p" or "if not p" are the _only_ correct way to test whether a position p is valid. In particular, tests like "if p is None" or "if p is not None" will not work properly.
def __init__ (self,v,childIndex=0,stack=None,trace=False):

'''Create a new position with the given childIndex and parent stack.'''

# To support ZODB the code must set v._p_changed = 1 # whenever any mutable vnode object changes.

self._childIndex = childIndex self.v = v

# New in Leo 4.5: stack entries are tuples (v,childIndex). if stack: self.stack = stack[:] # Creating a copy here is safest and best. else: self.stack = []

g.app.positions += 1

# if g.app.tracePositions and trace: g.trace(g.callers())

self.txtOffset = None # see self.textOffset()
def __eq__(self,p2):

"""Return True if two postions are equivalent."""

p1 = self

# Don't use g.trace: it might call p.__eq__ or p.__ne__. # print ('p.__eq__: %s %s' % ( # p1 and p1.v and p1.h,p2 and p2.v and p2.h))

if p2 is None or p2.v is None: return p1.v is None else: return ( p1.v == p2.v and p1._childIndex == p2._childIndex and p1.stack == p2.stack )

def __ne__(self,p2):

"""Return True if two postions are not equivalent."""

return not self.__eq__(p2) # For possible use in Python 2.x.
# No longer used. All code must now be aware of the one-node world.

# def __getattr__ (self,attr):

# """Convert references to p.t into references to p.v."""

# if attr=="t": # return self.v # else: # # New in 4.3: _silently_ raise the attribute error. # # This allows plugin code to use hasattr(p,attr) ! # if 0: # print("unknown position attribute: %s" % attr) # import traceback ; traceback.print_stack() # raise AttributeError(attr)
@ Tests such as 'if p' or 'if not p' are the _only_ correct ways to test whether a position p is valid. In particular, tests like 'if p is None' or 'if p is not None' will not work properly. @c

if g.isPython3:

def __bool__ ( self):

"""Return True if a position is valid."""

# Tracing this appears to cause unbounded prints. # print("__bool__",self.v and self.v.cleanHeadString())

return self.v is not None

else:

def __nonzero__ ( self):

"""Return True if a position is valid."""

# if g.app.trace: "__nonzero__",self.v

# g.trace(repr(self))

return self.v is not None
def __str__ (self):

p = self

if p.v: return "<pos %d childIndex: %d lvl: %d [%d] %s>" % ( id(p),p._childIndex,p.level(),len(p.stack),p.cleanHeadString()) else: return "<pos %d [%d] None>" % (id(p),len(p.stack))

__repr__ = __str__
def archivedPosition (self,root_p=None):

'''Return a representation of a position suitable for use in .leo files.'''

p = self

if root_p is None: aList = [z._childIndex for z in p.self_and_parents()] else: aList = [] for z in p.self_and_parents(): if z == root_p: aList.append(0) break else: aList.append(z._childIndex) # g.trace(aList)

aList.reverse() return aList
# Using this routine can generate huge numbers of temporary positions during a tree traversal.

def copy (self):

""""Return an independent copy of a position."""

# if g.app.tracePositions: g.trace(g.callers())

return position(self.v,self._childIndex,self.stack,trace=False)
def dumpLink (self,link):

return g.choose(link,link,"<none>")

def dump (self,label=""):

p = self if p.v: p.v.dump() # Don't print a label
def key (self):

p = self

# For unified nodes we must include a complete key, # so we can distinguish between clones. result = [] for z in p.stack: v,childIndex = z result.append('%s:%s' % (id(v),childIndex))

result.append('%s:%s' % (id(p.v),p._childIndex))

return '.'.join(result)
def __get_b(self):

p = self return p.bodyString()

def __set_b(self,val):

p = self ; c = p.v and p.v.context if c: c.setBodyString(p, val) # Don't redraw the screen: p.b must be fast. # c.redraw_after_icons_changed()

b = property( __get_b, __set_b, doc = "position body string property")
def __get_h(self):

p = self return p.headString()

def __set_h(self,val):

p = self ; c = p.v and p.v.context if c: c.setHeadString(p,val) # Don't redraw the screen: p.h must be fast. # c.redraw_after_head_changed()

h = property( __get_h, __set_h, doc = "position headline string property")
def __get_gnx(self): p = self return g.app.nodeIndices.toString(p.v.fileIndex)

gnx = property( __get_gnx, # __set_gnx, doc = "position gnx property")
def anyAtFileNodeName (self): return self.v.anyAtFileNodeName() def atAutoNodeName (self): return self.v.atAutoNodeName() def atEditNodeName (self): return self.v.atEditNodeName() def atFileNodeName (self): return self.v.atFileNodeName() def atNoSentinelsFileNodeName (self): return self.v.atNoSentinelsFileNodeName() def atRawFileNodeName (self): return self.v.atRawFileNodeName() def atShadowFileNodeName (self): return self.v.atShadowFileNodeName() def atSilentFileNodeName (self): return self.v.atSilentFileNodeName() def atThinFileNodeName (self): return self.v.atThinFileNodeName()

# New names, less confusing atNoSentFileNodeName = atNoSentinelsFileNodeName atNorefFileNodeName = atRawFileNodeName atAsisFileNodeName = atSilentFileNodeName

def isAnyAtFileNode (self): return self.v.isAnyAtFileNode() def isAtAllNode (self): return self.v.isAtAllNode() def isAtAutoNode (self): return self.v.isAtAutoNode() def isAtAutoRstNode (self): return self.v.isAtAutoRstNode() def isAtEditNode (self): return self.v.isAtEditNode() def isAtFileNode (self): return self.v.isAtFileNode() def isAtIgnoreNode (self): return self.v.isAtIgnoreNode() def isAtNoSentinelsFileNode (self): return self.v.isAtNoSentinelsFileNode() def isAtOthersNode (self): return self.v.isAtOthersNode() def isAtRawFileNode (self): return self.v.isAtRawFileNode() def isAtSilentFileNode (self): return self.v.isAtSilentFileNode() def isAtShadowFileNode (self): return self.v.isAtShadowFileNode() def isAtThinFileNode (self): return self.v.isAtThinFileNode()

# New names, less confusing: isAtNoSentFileNode = isAtNoSentinelsFileNode isAtNorefFileNode = isAtRawFileNode isAtAsisFileNode = isAtSilentFileNode

# Utilities. def matchHeadline (self,pattern): return self.v.matchHeadline(pattern)
def bodyString (self):

return self.v.bodyString()

def headString (self):

return self.v.headString()

def cleanHeadString (self):

return self.v.cleanHeadString()
def isDirty (self): return self.v.isDirty() def isExpanded (self): return self.v.isExpanded() def isMarked (self): return self.v.isMarked() def isOrphan (self): return self.v.isOrphan() def isSelected (self): return self.v.isSelected() def isTopBitSet (self): return self.v.isTopBitSet() def isVisited (self): return self.v.isVisited() def status (self): return self.v.status()
# This used to be time-critical code.

def childIndex(self):

p = self return p._childIndex
def directParents (self):

return self.v.directParents()
def hasChildren (self):

p = self return len(p.v.children) > 0

hasFirstChild = hasChildren

def numberOfChildren (self):

p = self return len(p.v.children)
# These methods are useful abbreviations. # Warning: they make copies of positions, so they should be used _sparingly_

def getBack (self): return self.copy().moveToBack() def getFirstChild (self): return self.copy().moveToFirstChild() def getLastChild (self): return self.copy().moveToLastChild() def getLastNode (self): return self.copy().moveToLastNode() def getLastVisible (self): return self.copy().moveToLastVisible() def getNext (self): return self.copy().moveToNext() def getNodeAfterTree (self): return self.copy().moveToNodeAfterTree() def getNthChild (self,n): return self.copy().moveToNthChild(n) def getParent (self): return self.copy().moveToParent() def getThreadBack (self): return self.copy().moveToThreadBack() def getThreadNext (self): return self.copy().moveToThreadNext()

# New in Leo 4.4.3 b2: add c args. def getVisBack (self,c): return self.copy().moveToVisBack(c) def getVisNext (self,c): return self.copy().moveToVisNext(c)

# These are efficient enough now that iterators are the normal way to traverse the tree!

back = getBack firstChild = getFirstChild lastChild = getLastChild lastNode = getLastNode lastVisible = getLastVisible # New in 4.2 (was in tk tree code). next = getNext nodeAfterTree = getNodeAfterTree nthChild = getNthChild parent = getParent threadBack = getThreadBack threadNext = getThreadNext visBack = getVisBack visNext = getVisNext

# New in Leo 4.4.3: hasVisBack = visBack hasVisNext = visNext
def hasBack(self): p = self return p.v and p._childIndex > 0

def hasNext(self): p = self try: parent_v = p._parentVnode() # Returns None if p.v is None. return p.v and parent_v and p._childIndex+1 < len(parent_v.children) except Exception: g.trace('*** Unexpected exception') g.es_exception() return None

def hasParent(self): p = self return p.v and len(p.stack) > 0

def hasThreadBack(self): p = self return p.hasParent() or p.hasBack() # Much cheaper than computing the actual value.
def hasThreadNext (self):

p = self if not p.v: return False

if p.hasChildren() or p.hasNext(): return True

n = len(p.stack) -1 while n >= 0: v,childIndex = p.stack[n] # See how many children v's parent has. if n == 0: parent_v = v.context.hiddenRootNode else: parent_v,junk = p.stack[n-1] if len(parent_v.children) > childIndex+1: # v has a next sibling. return True n -= 1 return False
def findRootPosition (self):

p = self.copy() while p.hasParent(): p.moveToParent() while p.hasBack(): p.moveToBack() return p
def isAncestorOf (self, p2):

p = self ; v = p.v

for z in p2.stack: v2,junk = z if v2 == v: return True

return False
def isCloned (self):

p = self return p.v.isCloned()
def isRoot (self):

p = self

return not p.hasParent() and not p.hasBack()
def isVisible (self,c):

p = self ; trace = False limit,limitIsVisible = c.visLimit() limit_v = limit and limit.v or None if p.v == limit_v: if trace: g.trace('*** at limit','limitIsVisible',limitIsVisible,p.h) return limitIsVisible

# It's much easier with a full stack. n = len(p.stack)-1 while n >= 0: progress = n # v,n = p.vParentWithStack(v,p.stack,n) v,junk = p.stack[n] if v == limit_v: # We are at a descendant of limit. if trace: g.trace('*** descendant of limit', 'limitIsVisible',limitIsVisible, 'limit.isExpanded()',limit.isExpanded(),'v',v) if limitIsVisible: return limit.isExpanded() else: # Ignore the expansion state of @chapter nodes. return True if not v.isExpanded(): if trace: g.trace('*** non-limit parent is not expanded:',v._headString,p.h) return False n -= 1 assert progress > n

return True
def level (self):

'''Return the number of p's parents.'''

p = self return p.v and len(p.stack) or 0

simpleLevel = level
def textOffset(self): ''' See http://tinyurl.com/5nescw for details '''

p = self

# caching of p.textOffset, we need to calculate it only once if p.txtOffset is not None: return p.txtOffset

p.txtOffset = 0 # walk back from the current position for cursor in p.self_and_parents(): # we also need the parent, the "text offset" is relative to it parent = cursor.parent() if parent == None: # root reached break parent_bodyString = parent.b if parent_bodyString == '': # organizer node continue parent_lines = parent_bodyString.split('\n') # check out if the cursor node is a section cursor_is_section = False cursor_headString = cursor.h if cursor_headString.startswith('<<'): cursor_is_section = True # section node for line in parent_lines: if cursor_is_section == True: # find out the section in the bodyString of the parent pos = line.find(cursor_headString) else: # otherwise find the "@others" directive in the bodyString of the parent pos = line.find('@others') if pos > 0: # break the iteration over lines if something is found break if pos > 0: p.txtOffset += pos if parent.v.isAnyAtFileNode(): # do not scan upper break

return p.txtOffset
# Clone bits are no longer used. # Dirty bits are handled carefully by the position class.

def clearMarked (self): return self.v.clearMarked() def clearOrphan (self): return self.v.clearOrphan() def clearVisited (self): return self.v.clearVisited()

def contract (self): return self.v.contract() def expand (self): return self.v.expand()

def initExpandedBit (self): return self.v.initExpandedBit() def initMarkedBit (self): return self.v.initMarkedBit() def initStatus (self, status): return self.v.initStatus(status)

def setMarked (self): return self.v.setMarked() def setOrphan (self): return self.v.setOrphan() def setSelected (self): return self.v.setSelected() def setVisited (self): return self.v.setVisited()
def computeIcon (self):

return self.v.computeIcon()

def setIcon (self):

pass # Compatibility routine for old scripts
def setSelection (self,start,length):

return self.v.setSelection(start,length)
def setBodyString (self,s,encoding="utf-8"):

p = self return p.v.setBodyString(s,encoding)

initBodyString = setBodyString setTnodeText = setBodyString scriptSetBodyString = setBodyString

def initHeadString (self,s,encoding="utf-8"):

p = self p.v.initHeadString(s,encoding)

def setHeadString (self,s,encoding="utf-8"):

p = self p.v.initHeadString(s,encoding) p.setDirty()
# Compatibility routine for scripts.

def clearVisitedInTree (self):

for p in self.self_and_subtree(): p.clearVisited()
def clearAllVisitedInTree (self):

for p in self.self_and_subtree(): p.v.clearVisited() p.v.clearWriteBit()
def clearDirty (self):

p = self p.v.clearDirty()
def findAllPotentiallyDirtyNodes(self):

p = self return p.v.findAllPotentiallyDirtyNodes()
def inAtIgnoreRange (self):

"""Returns True if position p or one of p's parents is an @ignore node."""

p = self

for p in p.self_and_parents(): if p.isAtIgnoreNode(): return True

return False
def setAllAncestorAtFileNodesDirty (self,setDescendentsDirty=False):

trace = False and not g.unitTesting verbose = False p = self dirtyVnodeList = []

# Calculate all nodes that are joined to p or parents of such nodes. nodes = p.findAllPotentiallyDirtyNodes()

if setDescendentsDirty: # N.B. Only mark _direct_ descendents of nodes. # Using the findAllPotentiallyDirtyNodes algorithm would mark way too many nodes. for p2 in p.subtree(): # Only @thin nodes need to be marked. if p2.v not in nodes and p2.isAtThinFileNode(): nodes.append(p2.v)

if trace and verbose: for v in nodes: print (v.isDirty(),v.isAnyAtFileNode(),v)

dirtyVnodeList = [v for v in nodes if not v.isDirty() and v.isAnyAtFileNode()] changed = len(dirtyVnodeList) > 0

for v in dirtyVnodeList: v.setDirty()

if trace: g.trace("position",dirtyVnodeList,g.callers(5))

return dirtyVnodeList
def setDirty (self,setDescendentsDirty=True):

'''Mark a node and all ancestor @file nodes dirty.'''

p = self ; dirtyVnodeList = []

# g.trace(p.h,g.callers(4))

if not p.v.isDirty(): p.v.setDirty() dirtyVnodeList.append(p.v)

# Important: this must be called even if p.v is already dirty. # Typing can change the @ignore state! dirtyVnodeList2 = p.setAllAncestorAtFileNodesDirty(setDescendentsDirty) dirtyVnodeList.extend(dirtyVnodeList2)

return dirtyVnodeList
@ - convertTreeToString and moreHead can't be vnode methods because they uses level(). - moreBody could be anywhere: it may as well be a postion method.
def convertTreeToString (self):

"""Convert a positions suboutline to a string in MORE format."""

p = self ; level1 = p.level()

array = [] for p in p.self_and_subtree(): array.append(p.moreHead(level1)+'\n') body = p.moreBody() if body: array.append(body +'\n')

return ''.join(array)
def moreHead (self, firstLevel,useVerticalBar=False):

"""Return the headline string in MORE format."""

# useVerticalBar is unused, but it would be useful in over-ridden methods.

p = self level = self.level() - firstLevel plusMinus = g.choose(p.hasChildren(), "+", "-")

return "%s%s %s" % ('\t'*level,plusMinus,p.h)
@ + test line - test line \ test line test line + test line - test line \ More lines... @c

def moreBody (self):

"""Returns the body string in MORE format.

Inserts a backslash before any leading plus, minus or backslash."""

p = self ; array = [] lines = p.b.split('\n') for s in lines: i = g.skip_ws(s,0) if i < len(s) and s[i] in ('+','-','\\'): s = s[:i] + '\\' + s[i:] array.append(s) return '\n'.join(array)
def children(self):

'''Return all children of p.'''

p = self p = p.firstChild() while p: yield p p.moveToNext() raise StopIteration

# Compatibility with old code. children_iter = children
def following_siblings(self): ''' Return all siblings that follow p, not including p. '''

p = self p = p.copy() # Always include the original node. p = p.next() while p: yield p p.moveToNext() raise StopIteration

# Compatibility with old code. following_siblings_iter = following_siblings
def nodes (self):

p = self p = p.copy() while p: yield p.v p.moveToThreadNext()

# Compatibility with old code. tnodes_iter = nodes vnodes_iter = nodes
def parents(self):

'''Return all parents of p.'''

p = self p = p.parent() while p: yield p p.moveToParent() raise StopIteration

# Compatibility with old code. parents_iter = parents
def self_and_parents(self):

'''Return p and all parents of p.'''

p = self p = p.copy() while p: yield p p.moveToParent() raise StopIteration

# Compatibility with old code. self_and_parents_iter = self_and_parents
def self_and_siblings(self): '''Return all siblings of p including p. '''

p = self p = p.copy() while p.hasBack(): p.moveToBack() while p: yield p p.moveToNext() raise StopIteration

# Compatibility with old code. self_and_siblings_iter = self_and_siblings
def self_and_subtree(self):

'''Return p's entire subtree, including p.'''

p = self p = p.copy() after = p.nodeAfterTree() while p and p != after: yield p p.moveToThreadNext() raise StopIteration

# Compatibility with old code. self_and_subtree_iter = self_and_subtree
def subtree(self):

'''Return all descendants of p, not including p.'''

p = self p = p.copy() after = p.nodeAfterTree() p.moveToThreadNext() while p and p != after: yield p p.moveToThreadNext() raise StopIteration

# Compatibility with old code. subtree_iter = subtree
def unique_nodes (self):

p = self p = p.copy() seen = set() while p: if p.v in seen: p.moveToNodeAfterTree() else: seen.add(p.v) yield p.v p.moveToThreadNext()

# Compatibility with old code. unique_tnodes_iter = unique_nodes unique_vnodes_iter = unique_nodes
def unique_subtree (self): '''Return unique positions in p's entire subtree, including p.'''

p = self p = p.copy() after = p.nodeAfterTree() seen = set() while p and p != after: if p.v in seen: p.moveToNodeAfterTree() else: seen.add(p.v) yield p p.moveToThreadNext() raise StopIteration

# Compatibility with old code. subtree_with_unique_tnodes_iter = unique_subtree subtree_with_unique_vnodes_iter = unique_subtree
def clone (self):

"""Create a clone of back.

Returns the newly created position."""

p = self p2 = p.copy() # Do *not* copy the vnode! p2._linkAfter(p) # This should "just work" return p2
# These used by unit tests and by the group_operations plugin.

def copyTreeAfter(self): p = self p2 = p.insertAfter() p.copyTreeFromSelfTo(p2) return p2

def copyTreeFromSelfTo(self,p2): p = self p2.v._headString = p.h p2.v._bodyString = p.b

# 2009/10/02: no need to copy arg to iter for child in p.children(): child2 = p2.insertAsLastChild() child.copyTreeFromSelfTo(child2)
@ This is the main delete routine. It deletes the receiver's entire tree from the screen. Because of the undo command we never actually delete vnodes or tnodes. @c

def doDelete (self,newNode=None):

"""Deletes position p from the outline."""

p = self p.setDirty() # Mark @file nodes dirty!

# Adjust newNode._childIndex if newNode is a following sibling of p. sib = p.copy() while sib.hasNext(): sib.moveToNext() if sib == newNode: newNode._childIndex -= 1 break

p._unlink()
def insertAfter (self):

"""Inserts a new position after self.

Returns the newly created position."""

p = self ; context = p.v.context p2 = self.copy()

p2.v = vnode(context=context) p2.v.iconVal = 0 p2._linkAfter(p)

return p2
def insertAsLastChild (self):

"""Inserts a new vnode as the last child of self.

Returns the newly created position."""

p = self n = p.numberOfChildren()

return p.insertAsNthChild(n)
def insertAsNthChild (self,n):

"""Inserts a new node as the the nth child of self. self must have at least n-1 children.

Returns the newly created position."""

p = self ; context = p.v.context p2 = self.copy()

p2.v = vnode(context=context) p2.v.iconVal = 0 p2._linkAsNthChild(p,n)

return p2
def invalidOutline (self, message):

p = self

if p.hasParent(): node = p.parent() else: node = p

g.alert("invalid outline: %s\n%s" % (message,node))
def moveAfter (self,a):

"""Move a position after position a."""

p = self # Do NOT copy the position!

# g.trace('before','p',p,p.stack,'\na',a,a.stack)

a._adjustPositionBeforeUnlink(p) p._unlink() p._linkAfter(a)

# g.trace('before','p',p,p.stack,'\na',a,a.stack)

return p
def moveToFirstChildOf (self,parent):

"""Move a position to the first child of parent."""

p = self # Do NOT copy the position! p._unlink() p._linkAsNthChild(parent,0) return p

def moveToLastChildOf (self,parent):

"""Move a position to the last child of parent."""

p = self # Do NOT copy the position! p._unlink() n = parent.numberOfChildren() p._linkAsNthChild(parent,n) return p
def moveToNthChildOf (self,parent,n):

"""Move a position to the nth child of parent."""

p = self # Do NOT copy the position!

parent._adjustPositionBeforeUnlink(p) p._unlink() p._linkAsNthChild(parent,n)

return p
def moveToRoot (self,oldRoot=None):

'''Moves a position to the root position.

Important: oldRoot must the previous root position if it exists.'''

p = self # Do NOT copy the position! if oldRoot: oldRoot._adjustPositionBeforeUnlink(p) p._unlink() p._linkAsRoot(oldRoot)

return p
# This routine checks the structure of the receiver's tree.

def validateOutlineWithParent (self,pv):

p = self result = True # optimists get only unpleasant surprises. parent = p.getParent() childIndex = p._childIndex

# g.trace(p,parent,pv) << validate parent ivar >> << validate childIndex ivar >> << validate x ivar >>

# Recursively validate all the children. for child in p.children(): r = child.validateOutlineWithParent(p) if not r: result = False

return result
if parent != pv: p.invalidOutline( "Invalid parent link: " + repr(parent))
if pv: if childIndex < 0: p.invalidOutline ( "missing childIndex" + childIndex ) elif childIndex >= pv.numberOfChildren(): p.invalidOutline ( "missing children entry for index: " + childIndex ) elif childIndex < 0: p.invalidOutline ( "negative childIndex" + childIndex )
if not p.v and pv: self.invalidOutline ( "Empty t" )
@ These routines change self to a new position "in place". That is, these methods must _never_ call p.copy().

When moving to a nonexistent position, these routines simply set p.v = None, leaving the p.stack unchanged. This allows the caller to "undo" the effect of the invalid move by simply restoring the previous value of p.v.

These routines all return self on exit so the following kind of code will work: after = p.copy().moveToNodeAfterTree()
def moveToBack (self):

"""Move self to its previous sibling."""

p = self ; n = p._childIndex

parent_v = p._parentVnode() # Returns None if p.v is None.

# Do not assume n is in range: this is used by positionExists. if parent_v and p.v and 0 < n <= len(parent_v.children): p._childIndex -= 1 p.v = parent_v.children[n-1] else: p.v = None

return p
def moveToFirstChild (self):

"""Move a position to it's first child's position."""

p = self

if p.v and p.v.children: p.stack.append((p.v,p._childIndex),) p.v = p.v.children[0] p._childIndex = 0 else: p.v = None

return p
def moveToLastChild (self):

"""Move a position to it's last child's position."""

p = self

if p.v and p.v.children: p.stack.append((p.v,p._childIndex),) n = len(p.v.children) p.v = p.v.children[n-1] p._childIndex = n-1 else: p.v = None

return p
def moveToLastNode (self):

"""Move a position to last node of its tree.

N.B. Returns p if p has no children."""

p = self

# Huge improvement for 4.2. while p.hasChildren(): p.moveToLastChild()

return p
def moveToNext (self):

"""Move a position to its next sibling."""

p = self ; n = p._childIndex

parent_v = p._parentVnode() # Returns None if p.v is None. if not p.v: g.trace('parent_v',parent_v,'p.v',p.v)

if p.v and parent_v and len(parent_v.children) > n+1: p._childIndex = n+1 p.v = parent_v.children[n+1] else: p.v = None

return p
def moveToNodeAfterTree (self):

"""Move a position to the node after the position's tree."""

p = self

while p: if p.hasNext(): p.moveToNext() break p.moveToParent()

return p
def moveToNthChild (self,n):

p = self

if p.v and len(p.v.children) > n: p.stack.append((p.v,p._childIndex),) p.v = p.v.children[n] p._childIndex = n else: p.v = None

return p
def moveToParent (self):

"""Move a position to its parent position."""

p = self

if p.v and p.stack: p.v,p._childIndex = p.stack.pop() else: p.v = None

return p
def moveToThreadBack (self):

"""Move a position to it's threadBack position."""

p = self

if p.hasBack(): p.moveToBack() p.moveToLastNode() else: p.moveToParent()

return p
def moveToThreadNext (self):

"""Move a position to threadNext position."""

p = self

if p.v: if p.v.children: p.moveToFirstChild() elif p.hasNext(): p.moveToNext() else: p.moveToParent() while p: if p.hasNext(): p.moveToNext() break #found p.moveToParent() # not found.

return p
def moveToVisBack (self,c):

"""Move a position to the position of the previous visible node."""

trace = False and not g.unitTesting verbose = True p = self ; limit,limitIsVisible = c.visLimit() if trace and verbose: g.trace(p,'limit',limit,'limitIsVisible',limitIsVisible) if trace: g.trace('***entry','parent',p.parent(),'p',p,g.callers(5)) while p: # Short-circuit if possible. back = p.back() if trace: g.trace( 'back',back,'hasChildren',bool(back and back.hasChildren()), 'isExpanded',bool(back and back.isExpanded()))

if back and back.hasChildren() and back.isExpanded(): p.moveToThreadBack() elif back: p.moveToBack() else: p.moveToParent() # Same as p.moveToThreadBack() # if back and (not back.hasChildren() or not back.isExpanded()): # p.moveToBack() # else: # p.moveToThreadBack() if trace: g.trace(p.parent(),p) if p: if trace and verbose: g.trace('**p',p) done,val = self.checkVisBackLimit(limit,limitIsVisible,p) if done: if trace and verbose: g.trace('done',p) return val if p.isVisible(c): if trace and verbose: g.trace('isVisible',p) return p else: # assert not p. return p
def checkVisBackLimit (self,limit,limitIsVisible,p):

'''Return done, return-val'''

trace = True and not g.unitTesting c = p.v.context

if limit: if limit == p: if trace: g.trace('at limit',p) if limitIsVisible and p.isVisible(c): return True,p else: return True,None #return True,g.choose(limitIsVisible and p.isVisible(c),p,None) elif limit.isAncestorOf(p): return False,None else: if trace: g.trace('outside limit tree',limit,p) return True,None else: return False,None
def moveToVisNext (self,c):

"""Move a position to the position of the next visible node."""

trace = False and not g.unitTesting verbose = False p = self ; limit,limitIsVisible = c.visLimit() while p: if trace: g.trace('1',p.h) # if trace: g.trace('hasChildren %s, isExpanded %s %s' % ( # p.hasChildren(),p.isExpanded(),p.h)) # Short-circuit if possible. if p.hasNext() and p.hasChildren() and p.isExpanded(): p.moveToFirstChild() elif p.hasNext(): p.moveToNext() else: p.moveToThreadNext() # if p.hasNext() and (not p.hasChildren() or not p.isExpanded()): # p.moveToNext() # else: # p.moveToThreadNext() if trace: g.trace('2',p.h) if p: done,val = self.checkVisNextLimit(limit,p) if done: return val if p.isVisible(c): return p.copy() else: # assert not p. return p
def checkVisNextLimit (self,limit,p):

'''Return done, return-val'''

trace = False and not g.unitTesting

if limit: # Unlike moveToVisBack, being at the limit does not terminate. if limit == p: return False, None elif limit.isAncestorOf(p): return False,None else: if trace: g.trace('outside limit tree') return True,None else: return False,None
# These methods are only for the use of low-level code # in leoNodes.py, leoFileCommands.py and leoUndo.py.
def _adjustPositionBeforeUnlink (self,p2):

'''Adjust position p before unlinking p2.'''

# p will change if p2 is a previous sibling of p or # p2 is a previous sibling of any ancestor of p.

trace = False and not g.unitTesting p = self ; sib = p.copy()

if trace: g.trace('entry',p.stack)

# A special case for previous siblings. # Adjust p._childIndex, not the stack's childIndex. while sib.hasBack(): sib.moveToBack() if sib == p2: p._childIndex -= 1 if trace: g.trace('***new index: %s\n%s' % ( p.h,p.stack)) return

# Adjust p's stack. stack = [] ; changed = False ; i = 0 while i < len(p.stack): v,childIndex = p.stack[i] p3 = position(v=v,childIndex=childIndex,stack=stack[:i]) while p3: if p2.v == p3.v: # A match with the to-be-moved node? stack.append((v,childIndex-1),) changed = True break # terminate only the inner loop. p3.moveToBack() else: stack.append((v,childIndex),) i += 1

if changed: if trace: g.trace('***new stack: %s\n%s' % ( p.h,stack)) p.stack = stack
if g.unitTesting:

c,p = g.getTestVars()

table = ( '1', '1-1','1-1-1','1-1-2', '1-2','1-2-1','1-2-2', '2', '2-1','2-1-1','2-1-2', '2-2','2-2-1','2-2-2', '3', '3-1','3-1-1','3-1-2', '3-2','3-2-1','3-2-2', )

for suffix in table: h = 'node %s' % suffix p2 = g.findNodeInTree(c,p,h) assert p2,h

table2 = ( ('2-1-2','2-1-1','2-1-1'), ('3','2','2'), )

for h1,h2,h3 in table2: p1 = g.findNodeInTree(c,p,'node %s' % h1) p2 = g.findNodeInTree(c,p,'node %s' % h2) p3 = g.findNodeInTree(c,p,'node %s' % h3) p1._adjustPositionBeforeUnlink(p2) result = p1 assert result.stack == p3.stack,'expected %s got %s' % ( p3.h,result and result.h or '<none>')

# Data. @others
# Node 1
# node 1-1
# node 1-1-1
# node 1-1-2
# node 1-2
# node 1-2-1
# node 1-2-2
# node 2
# node 2-1
# node 2-1-1
# node 2-1-2
# node 2-2
# node 2-2-1
# node 2-2-2
# node 3
# node 3-1
# node 3-1-1
# node 3-1-2
# node 3-2
# node 3-2-1
# node 3-2-2
def _linkAfter (self,p_after,adjust=True):

'''Link self after p_after.'''

p = self parent_v = p_after._parentVnode() # Returns None if p.v is None

# Init the ivars. p.stack = p_after.stack[:] p._childIndex = p_after._childIndex + 1

# Set the links. child = p.v n = p_after._childIndex+1 child._addLink(n,parent_v,adjust=adjust)
def _linkAsNthChild (self,parent,n,adjust=True):

p = self parent_v = parent.v

# Init the ivars. p.stack = parent.stack[:] p.stack.append((parent_v,parent._childIndex),) p._childIndex = n

child = p.v child._addLink(n,parent_v,adjust=adjust)

def _parentVnode (self):

'''Return the parent vnode. Return the hiddenRootNode if there is no other parent.'''

p = self

if p.v: data = p.stack and p.stack[-1] if data: v, junk = data return v else: return p.v.context.hiddenRootNode else: return None
def _linkAsRoot (self,oldRoot):

"""Link self as the root node."""

p = self assert(p.v)

hiddenRootNode = p.v.context.hiddenRootNode

if oldRoot: oldRootNode = oldRoot.v else: oldRootNode = None

# Init the ivars. p.stack = [] p._childIndex = 0

parent_v = hiddenRootNode child = p.v if not oldRoot: parent_v.children = [] child._addLink(0,parent_v)

return p
def _unlink (self):

'''Unlink the receiver p from the tree.'''

p = self ; n = p._childIndex parent_v = p._parentVnode() # returns None if p.v is None child = p.v assert(p.v) assert(parent_v)

# Delete the child. if (0 <= n < len(parent_v.children) and parent_v.children[n] == child ): # This is the only call to v._cutlink. child._cutLink(n,parent_v) else: self.badUnlink(parent_v,n,child)
def badUnlink (self,parent_v,n,child):

if 0 <= n < len(parent_v.children): g.trace('**can not happen: children[%s] != p.v' % (n)) g.trace('parent_v.children...\n', g.listToString(parent_v.children)) g.trace('parent_v',parent_v) g.trace('parent_v.children[n]',parent_v.children[n]) g.trace('child',child) g.trace('** callers:',g.callers()) if g.app.unitTesting: assert False, 'children[%s] != p.v' else: g.trace('**can not happen: bad child index: %s, len(children): %s' % ( n,len(parent_v.children))) g.trace('parent_v.children...\n', g.listToString(parent_v.children)) g.trace('parent_v',parent_v,'child',child) g.trace('** callers:',g.callers()) if g.app.unitTesting: assert False, 'bad child index: %s' % (n)
def makeCacheList(self):

'''Create a recursive list describing a tree for use by v.createOutlineFromCacheList. '''

p = self

return [ p.h,p.b,p.gnx, [p2.makeCacheList() for p2 in p.children()]]
class poslist(list): """ List of positions

This behaves like a normal list, with the distinction that it has select_h and select_b methods that can be used to search through immediate children of the nodes.

""" @others
def select_h(self, regex, flags = re.IGNORECASE): """ Find immediate child nodes of nodes in poslist with regex.

You can chain find_h / find_b with select_h / select_b like this to refine an outline search::

pl = c.find_h('@thin.*py').select_h('class.*').select_b('import (.*)')

""" pat = re.compile(regex, flags) res = poslist() for p in self: for child_p in p.children(): m = re.match(pat, child_p.h) if m: pc = child_p.copy() pc.mo = m res.append(pc) return res

def select_b(self, regex, flags = re.IGNORECASE ): """ Find all the nodes in poslist where body matches regex

You can chain find_h / find_b with select_h / select_b like this to refine an outline search::

pl = c.find_h('@thin.*py').select_h('class.*').select_b('import (.*)') """ pat = re.compile(regex, flags) res = poslist() for p in self: m = re.finditer(pat, p.b) t1,t2 = itertools.tee(m,2) try: first = t1.next() # if does not raise StopIteration... pc = p.copy() pc.matchiter = t2 res.append(pc)

except StopIteration: pass

return res

“Ed, you continue to push the envelope. The amazing thing is that the footprint isn’t doubling every few months like it would be in another designer’s hands. Adding features by removing constraints, hot refactoring while adding unit tests.”