# Chapter 7: Scripting Leo with Python

This chapter is a tutorial describing how to write Python scripts that get and modify data contained in Leo outlines. Scripts use Leo's, vnode, tnode and position classes to access data in Leo outlines. The combination of a vnode v and its tnode v.t represents a node's data. Leo needs both vnodes and tnodes to represent clones efficiently. Indeed, a tnode represents all data shared by cloned nodes. When Leo clones a vnode v, Leo creates a new, independent vnode v2 and sets v2.t == v.t. Thus, cloning a node does not duplicate any information in descendant trees, but descendants of a cloned node appear more than once on the screen (when all nodes are expanded). Positions represent a node at a particular place on the screen. Equivalently, a position indicates a particular place in a tree traversal. Iterators of the position class define various kinds of tree traversals.

Leo's execute-script command predefines several symbols. This makes it easy to access the data in Leo outlines and the Leo's own source code. g is predefined to the leoGlobals module. Scripts commonly use utility functions such as g.es, g.trace, etc. The g.app object represents Leo itself. The instance vars (ivars) of g.app are Leo's global variables and constants. The execute-script command predefines c to be the commander (see below) of the Leo outline in which the script is contained. Whenever possible, scripts should use the high-level methods of the commander to insert, delete or clone nodes. Finally, the execute-script commands predefines p to be the presently selected position.

This chapter describes only some of Leo's functions, classes and methods. However, your scripts have complete access to all of Leo's source code, that is, all the code in `LeoPy.leo`. You are not limited by what you see in this chapter.

Contents

- Chapter 7: Scripting Leo with Python
  - Commonly used classes
  - Predefined objects
  - g.es writes to the log pane
  - app.windowList: the list of all open frames
  - Getting and setting headline and body text
  - Ensuring that positions are valid
  - About copying positions
  - Traversing outlines
    - c.allNodes_iter
    - 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
    - Visiting each node once
  - Updating the screen
  - Invoking commands from scripts
  - Getting settings from @settings trees
  - Getting and setting preferences
  - Functions for finding and changing text from scripts
  - Functions defined in leoGlobals.py
  - Event handlers
    - Enabling idle time event handlers
  - How to make operations undoable
  - Redirecting output from scripts
  - Writing to different log tabs
  - Invoking dialogs using the g.app.gui class
  - Inserting and deleting icons
  - Customizing panes with different widgets
    - Official ivars
    - Common operations on Tk.Text widgets
    - Customizing the log pane
    - Customizing the body pane
    - Customizing the outine pane
  - Summary of the vnode and position classes
    - Iterators
    - Getters
    - Setters
  - Creating script buttons
  - Running Leo in batch mode

## Commonly used classes

Leo's source code is a collection of classes, along with utility functions in `leoGlobals.py`. Here are the classes and objects that scripts will commonly use:

g.app
  The application object representing the entire Leo application. The ivars (instance variables) of `g.app` represent Leo's global variables.
g.app.gui
  This is a wrapper class that shields Leo's core code from gui-dependent details. As described below, scripts can invoke dialogs using g.app.gui convenience methods.
commander
  An instance of the `Commands` class in `leoCommands.py`. Commanders represent commands for a particular window. Each open Leo window has its own commander. By convention, any variable named `c` is a commander.
frame
  An instance of the base `leoFrame` class in `leoFrame.py`. Frames contains all the internal data needed to manage a Leo window. Given a commander `c`, `c.frame` is commanders frame. Given a frame `f`, `f.c` is the frame's commander.
position

  An instance of the `position` class in `leoNodes.py`. A position object represents the location of a particular node in a tree traversal. By convention, variables named `p`, `p1` or `p2` are positions. For any `position` `p`, `p.v` is the vnode at that position and `p.v.t` is the tnode at that position. Positions are the primary way to access data. `c.currentPosition` and `c.rootPosition` return positions. From those starting point, it is possible to access the data in any node.

  Important: Positions can become invalid when the structure of the outline changes. As discussed below, plugins and scripts that store positions for use at a later time should make sure the position p is still valid by calling c.positionExists(p)

  Important: For compatibility with old (pre-4.2) scripts, `c.currentVnode` and `c.rootVnode` methods return positions not vnodes. Old scripts appear to be using vnodes; in fact they are using positions. I call such scripts confused scripts. Confused scripts work because the `position` class is designed to make them work. We'll see how this works in detail in About copying positions. This section is supremely important.

vnode
  An instance of the `vnode` class in `leoNodes.py`. vnodes represent one or more outline nodes on the screen. Normally, scripts access vnodes via the `position` class described below. By convention, variables named `v`, `v1` or `v2` refer to vnodes. Important: scripts normally should use positions, not vnodes.
tnode
  An instance of the `tnode` class in `leoNodes.py`. tnodes represent the actual data in a vnode, including headline and body text. For any vnode `v`, `v.t` is v's tnode. Cloned vnodes v1 and v2 share the same tnode. That is `v1.t == v2`. Important: If `p` is a `position`, `p.v.t` is the tnode associated with that position. Many positions may share the same tnode.

Important: With the exception of the p.v and v.t ivars, scripts should be careful to use only the methods of the position, vnode and tnode classes rather than the internal ivars of these classes. Doing so will ensure that the script is as simple as possible and that the script will continue to work regardless of future changes to the internals of these classes.

## Predefined objects

Leo's `Execute Script` command predefines `c` to be the commander of the outline containing the script. `g` and `p` are predefined as follows:

    import leoGlobals as g
    p = c.currentPosition()

These definitions provide an easy way to access or change any information in a Leo outline. For example, as discussed below, the following script will print every headline in the Leo outline in which the script occurs.

> for z in c.allNodes_iter():
>   print z.headString()

## g.es writes to the log pane

The g.es method prints its arguments to the Log tab of the log pane:

    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")

To create a tab named 'Test' or make it visible if it already exists:

    c.frame.log.selectTab('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')

## app.windowList: the list of all open frames

The windowlist attribute of the application instance contains the list of the frames of all open windows. The commands ivar of the frame gives the commander for that frame:

    windows = g.app.windowList # get the list of all open frames.
    g.es("windows...")
    for f in windows:
        c = f.c # c is f's commander
        g.es(f)
        g.es(f.shortFileName())
        g.es(c)
        g.es(c.rootPosition())

## Getting and setting headline and body text

Here is how to access the data of a Leo window:

    g.es(p) # p is already defined.
    p = c.currentPosition() # get the current position.
    g.es(p)
    g.es("head:",p.headString())
    g.es("body:",p.bodyString())

Here is how to access data at position `p`. Note: these methods work whether or not `p` is the current position:

    body = p.bodyString()
    head = p.headString()
    c.setBodyString(p,body) # set body text of p to body.
    c.setHeadString(p,head) # set headline text of p to head.

## Ensuring that positions are valid

Positions become invalid when the user deletes or moves the node to which the position refers. Plugins and scripts that store positions for use at a later time should make sure the position p is still valid by calling c.positionExists(p).

The following code will find a position p2 describing the same node as p:

    if not c.positionExists(p):
        for p2 in c.allNodes_iter():
            if p2.v == p.v:
                # found
                c.selectPosition(p2)
        else:
            print 'position no longer exists'

## About copying positions

Understanding this section is essential. By default, all iterators discussed below use a single position to move through the outline. This is a vital optimization; otherwise Leo would generate one or more position object for each node of a tree traversal. However, it means that it is useless to try to capture a position with:

    p2 = p  # Wrong.  p2 will change after this assignment.

Instead, 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.

Another way to solve this problem is to set `copy=True` when using an iterator:

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

This creates a dictionary of (unchanging!) positions, indexed via tnode. Warning The positions in this dictionary will become invalid when the outline's structure changes. It would be wrong to save a dictionary like this for use between commands.

Setting the `copy=True` argument to iterators is an acceptable strategy for infrequently used scripts; it is not acceptable for heavily used code in Leo's core: it would create huge numbers of positions that would immediately be candidates for garbage collection.

Important: 'Confused' scripts work because the `position` methods that simulate the old `vnode` methods automatically create copies of positions when 'moving' through an outline. Thus, confused scripts generate many more positions than would the equivalent script that uses `position` iterators. Such is the price of compatibility.

## Traversing outlines

The proper way to traverse an outline is with an iterator. Iterators are defined only by the position class; vnodes can not have iterators because vnodes may appear in multiple places in an outline.

### c.allNodes_iter

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:

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

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

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

Here is a way to count the distinct vnodes of an outline:

    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()))

### p.children_iter

The `p.children_iter` iterator returns a list of all children of position `p`:

    parent = p.parent()
    g.es("children of %s" % parent.headString(),color="purple")
    for p in parent.children_iter():
        g.es(p.headString())

### p.parents_iter & p.self_and_parents_iter

The `p.parents_iter` iterator returns a list of all parents of position `p`, excluding `p`:

    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())

The `p.self_and_parents_iter` iterator returns a list of all parents of position `p`, including `p`:

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

### p.siblings_iter & p.following_siblings_iter

The `p.siblings_iter` iterator returns a list of all siblings of position `p`:

    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())

The `p.following_siblings_iter` iterator returns a list of all siblings that follow position `p`:

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

### p.subtree_iter & p.self_and_subtree_iter

The p.subtree_iter iterator returns a list of all positions in `p`'s subtree, excluding `p`:

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

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

    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())

### Testing whether a position is valid

The tests:

    if p:       # Right
    if not p:   # Right

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

    if p is None:       # Wrong
    if p is not None:   # Wrong

### Visiting each node once

Joined nodes represent the same data. Joined nodes are vnodes v1 and v2 such that v1.t == v2.t. Joined vnodes are distinct (v1 != v2) if the vnodes are clones of each other. Joined nodes are in fact the same node (v1 == v2) if they are descendants of clone nodes. In particular, we can say that p1.v is joined to p2.v if p1.v.t == p2.v.t regardless of whether p1.v == p2.v. Thus a script can process nodes exactly once if it ignores nodes joined to previously visited nodes. A later section will provide an example of this common scripting pattern.

The following script illustrates a common idiom. It prints each headline of an outline, eliminating duplications that would happen as the result of cloned trees:

    d = {}
    for p in c.allNodes_iter():
        if p.v.t not in d:
            print p.headString()
            d[p.v.t] = p.v.t

As mentioned in the introduction, joined nodes share the same tnode. Thus, when we visit a position p we print p.headString() only if p.v.t is not already in the dictionary. We then enter p.v.t in the dictionary to prevent printing the headlines of any future node joined to p.v.

## Updating the screen

You can use `c.redraw_now` to redraw the entire screen immediately:

    c.redraw_now()

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

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

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

    c.endUpdate(flag)

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.

## Invoking commands from scripts

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:

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

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

    c.markHeadline()

You can invoke minibuffer commands by name. For example:

    c.executeMinibufferCommand('open-outline')

c.keyHandler.funcReturn contains the value returned from the command. In many cases, as above, this value is simply 'break'.

## Getting settings from @settings trees

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:

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

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

    c.config.getBool(settingName,default=None)
    c.config.getColor(settingName)
    c.config.getDirectory(settingName)
    c.config.getFloat(settingName)
    c.config.getInt(settingName)
    c.config.getLanguage(settingName)
    c.config.getRatio(settingName)
    c.config.getShortcut(settingName)
    c.config.getString(settingName)

These methods return None if no setting exists. The getBool 'default' argument to getBool gives the value to be returned if the setting does not exist.

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

    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)

`c.config.set` does not change the `@settings` tree; it simply changes the values returned by the getters.

## Getting and setting preferences

Each commander maintains its own preferences. Your scripts can get the following ivars:

    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))

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

    c.tab_width = -4    # Change this and see what happens.
    c.frame.setTabWidth(c.tab_width)

## Functions for finding and changing text from scripts

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:

    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))

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

    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))

## Functions defined in leoGlobals.py

`leoGlobals.py` contains many utility functions and constants. The following script prints all the names defined in `leoGlobals.py`:

    g.es("Names defined in leoGlobals.py",color="purple")
    names = g.__dict__.keys()
    names.sort()
    for name in names:
        g.es(name)

## Event handlers

Plugins and other scripts can register event handlers (also known as hooks) with code such as:

    leoPlugins.registerHandler("after-create-leo-frame",onCreate)
    leoPlugins.registerHandler("idle", on_idle)
    leoPlugins.registerHandler(("start2","open2","command2"), create_open_with_menu)

As shown above, a plugin may register one or more event handlers with a single call to `leoPlugins.registerHandler`. Once a hook is registered, Leo will call the registered function' at the named hook time. For example:

    leoPlugins.registerHandler("idle", on_idle)

causes Leo to call `on_idle` at "idle" time.

Event handlers must have the following signature:

    def myHook (tag, keywords):
        whatever

- `tag` is the name of the hook (a string).
- `keywords` is a Python dictionary containing additional information. The following section describes the contents of the `keywords` dictionary in detail.

Important: hooks should get the proper commander this way:

    c = keywords.get('c')

The following table tells about each event handler: its name, when it is called, and the additional arguments passed to the hook in the `keywords` dictionary. For some kind of hooks, Leo will skip its own normal processing if the hook returns anything other than None. The table indicates such hooks with 'yes' in the 'Stop?' column.

Important: Ever since Leo 4.2, the `v`, `old_v` and `new_v` keys in the keyword dictionary contain positions, not vnodes. These keys are deprecated. The `new_c` key is also deprecated. Plugins should use the `c` key instead.

Event name (tag argument) | Stop? | When called | Keys in keywords dict
--- | --- | --- | ---
'after-create-leo-frame' |  | after creating any frame | c
'after-redraw-outline' |  | end of tree.redraw | c (note 6)
'before-create-leo-frame' |  | before frame.finishCreate | c
'bodyclick1' | yes | before normal click in body | c,p,v,event
'bodyclick2' |  | after normal click in body | c,p,v,event
'bodydclick1' | yes | before double click in body | c,p,v,event
'bodydclick2' |  | after double click in body | c,p,v,event
'bodykey1' | yes | before body keystrokes | c,p,v,ch,oldSel,undoType
'bodykey2' |  | after body keystrokes | c,p,v,ch,oldSel,undoType
'bodyrclick1' | yes | before right click in body | c,p,v,event
'bodyrclick2' |  | after right click in body | c,p,v,event
'boxclick1' | yes | before click in +- box | c,p,v,event
'boxclick2' |  | after click in +- box | c,p,v,event
'clear-all-marks' |  | after clear-all-marks command | c,p,v
'clear-mark' |  | when mark is set | c,p,v
'close-frame' |  | in app.closeLeoWindow | c
'color-optional-markup' | yes * | (note 7) | colorer,p,v,s,i,j,colortag (note 7)
'command1' | yes | before each command | c,p,v,label (note 2)
'command2' |  | after each command | c,p,v,label (note 2)
'create-optional-menus' |  | (note 8) | c (note 8)
'create-popup-menu-items' |  | in tree.OnPopup | c,p,v,event (new)
'destroy-all-global-windows' |  | (note 12) | None
'draw-outline-box' | yes | when drawing +- box | tree,p,v,x,y
'draw-outline-icon' | yes | when drawing icon | tree,p,v,x,y
'draw-outline-node' | yes | when drawing node | tree,p,v,x,y
'draw-outline-text-box' | yes | when drawing headline | tree,p,v,x,y
'drag1' | yes | before start of drag | c,p,v,event
'drag2' |  | after start of drag | c,p,v,event
'dragging1' | yes | before continuing to drag | c,p,v,event
'dragging2' |  | after continuing to drag | c,p,v,event
'enable-popup-menu-items' |  | in tree.OnPopup | c,p,v,event
'end1' |  | start of app.quit() | None
'enddrag1' | yes | before end of drag | c,p,v,event
'enddrag2' |  | after end of drag | c,p,v,event
'headclick1' | yes | before normal click in headline | c,p,v,event
'headclick2' |  | after normal click in headline | c,p,v,event
'headrclick1' | yes | before right click in headline | c,p,v,event
'headrclick2' |  | after right click in headline | c,p,v,event
'headkey1' | yes | before headline keystrokes | c,p,v,ch (note 13)
'headkey2' |  | after headline keystrokes | c,p,v,ch (note 13)
'hoist-changed' |  | whenever the hoist stack changes | c
'hypercclick1' | yes | before control click in hyperlink | c,p,v,event
'hypercclick2' |  | after control click in hyperlink | c,p,v,event
'hyperenter1' | yes | before entering hyperlink | c,p,v,event
'hyperenter2' |  | after entering hyperlink | c,p,v,event
'hyperleave1' | yes | before leaving hyperlink | c,p,v,event
'hyperleave2' |  | after leaving hyperlink | c,p,v,event
'iconclick1' | yes | before single click in icon box | c,p,v,event
'iconclick2' |  | after single click in icon box | c,p,v,event
'iconrclick1' | yes | before right click in icon box | c,p,v,event
'iconrclick2' |  | after right click in icon box | c,p,v,event
'icondclick1' | yes | before double click in icon box | c,p,v,event
'icondclick2' |  | after double click in icon box | c,p,v,event
'idle' |  | periodically (at idle time) | c
'init-color-markup' |  | (note 7) | colorer,p,v (note 7)
'menu1' | yes | before creating menus | c,p,v (note 3)
'menu2' | yes | during creating menus | c,p,v (note 3)
'menu-update' | yes | before updating menus | c,p,v
'new' |  | start of New command | c,old_c,new_c (note 9)
'open1' | yes | before opening any file | c,old_c,new_c,fileName (note 4)
'open2' |  | after opening any file | c,old_c,new_c,fileName (note 4)
'openwith1' | yes | before Open With command | c,p,v,openType,arg,ext
'openwith2' |  | after Open With command | c,p,v,openType,arg,ext
'recentfiles1' | yes | before Recent Files command | c,p,v,fileName,closeFlag
'recentfiles2' |  | after Recent Files command | c,p,v,fileName,closeFlag
'redraw-entire-outline' | yes | start of tree.redraw | c (note 6)
'save1' | yes | before any Save command | c,p,v,fileName
'save2' |  | after any Save command | c,p,v,fileName
'scan-directives' |  | in scanDirectives | c,p,v,s,old_dict,dict,pluginsList (note 10)
'select1' | yes | before selecting a position | c,new_p,old_p,new_v,new_v
'select2' |  | after selecting a position | c,new_p,old_p,new_v,old_v
'select3' |  | after selecting a position | c,new_p,old_p,new_v,old_v
'set-mark' |  | when a mark is set | c,p,v
'show-popup-menu' |  | in tree.OnPopup | c,p,v,event
'start1' |  | after app.finishCreate() | None
'start2' |  | after opening first Leo window | c,p,v,fileName
'unselect1' | yes | before unselecting a vnode | c,new_p,old_p,new_v,old_v
'unselect2' |  | after unselecting a vnode | c,new_p,old_p,old_v,old_v
'@url1' | yes | before double-click @url node | c,p,v,url (note 5)
'@url2' |  | after double-click @url node | c,p,v(note 5)

Notes:

1. 'activate' and 'deactivate' hooks have been removed because they do not work as expected.

2. 'commands' hooks: The label entry in the keywords dict contains the 'canonicalized' form of the command, that is, the lowercase name of the command with all non-alphabetic characters removed. Commands hooks now set the label for undo and redo commands 'undo' and 'redo' rather than 'cantundo' and 'cantredo'.

3. 'menu1' hook: Setting `g.app.realMenuNameDict` in this hook is an easy way of translating menu names to other languages. Note: the 'new' names created this way affect only the actual spelling of the menu items, they do not affect how you specify shortcuts settings, nor do they affect the 'official' command names passed in `g.app.commandName`. For example:

       app().realMenuNameDict['Open...'] = 'Ouvre'.

4. 'open1' and 'open2' hooks: These are called with a keywords dict containing the following entries:

   - c: The commander of the newly opened window.
   - old_c: The commander of the previously open window.
   - new_c: (deprecated: use 'c' instead) The commander of the newly opened window.
   - fileName: The name of the file being opened.

   You can use `old_c.currentPosition()` and `c.currentPosition()` to get the current position in the old and new windows. Leo calls the 'open1' and 'open2' hooks only if the file is not already open. Leo will also call the 'open1' and 'open2' hooks if: a) a file is opened using the Recent Files menu and b) the file is not already open.

5. '@url1' and '@url2' hooks are only executed if the 'icondclick1' hook returns None.

6. These hooks are useful for testing.

7. These hooks allow plugins to parse and handle markup within doc parts, comments and Python `'''` strings. Note that these hooks are not called in Python `'''` strings. See the color_markup plugin for a complete example of how to use these hooks.

8. Leo calls the 'create-optional-menus' hook when creating menus. This hook need only create new menus in the correct order, without worrying about the placement of the menus in the menu bar. See the plugins_menu and scripts_menu plugins for examples of how to use this hook.

9. The `New` command calls 'new'. The 'new_c' key is deprecated. Use the 'c' key instead.

10. `g.scanDirectives` calls 'scan-directives' hook. `g.scanDirectives` returns a dictionary, say d. d.get('pluginsList') is an a list of tuples `(d,v,s,k)` where:

    - d is the spelling of the @directive, without the leading @.
    - v is the vnode containing the directive, _not_ the original vnode.
    - s[k:] is a string containing whatever follows the @directive. k has already been moved past any whitespace that follows the @directive.

    See the add_directives plugins directive for a complete example of how to use the 'scan-directives' hook.

11. `g.app.closeLeoWindow` calls the 'close-frame' hook just before removing the window from `g.app.windowList`. The hook code may remove the window from `app.windowList` to prevent `g.app.closeLeoWindow` from destroying the window.

12. `g.app.destroyAllGlobalWindows` calls the 'destroy-all-global-windows' hook. This hook gives plugins the chance to clean up after themselves when Leo shuts down.

13. New in Leo 4.4: Leo calls the 'headkey1' and 'headkey2' hooks only when the user completes the editing of a headline, and ch is always 'r', regardless of platform.

### Enabling idle time event handlers

Two methods in leoGlobals.py allow scripts and plugins to enable and disable 'idle' events. g.enableIdleTimeHook(idleTimeDelay=100) enables the "idle" hook. Afterwards, Leo will call the "idle" hook approximately every idleTimeDelay milliseconds. Leo will continue to call the "idle" hook periodically until disableIdleTimeHook is called. g.disableIdleTimeHook() disables the "idle" hook.

## How to make operations undoable

Plugins and scripts should call `u.beforeX` and `u.afterX` methods ato describe the operation that is being performed. Note: `u` is shorthand for `c.undoer`. Most `u.beforeX` methods return `undoData` that the client code merely passes to the corresponding `u.afterX` method. This data contains the 'before' snapshot. The `u.afterX` methods then create a bead containing both the 'before' and 'after' snapshots.

`u.beforeChangeGroup` and `u.afterChangeGroup` allow multiple calls to `u.beforeX` and `u.afterX` methods to be treated as a single undoable entry. See the code for the `Change All`, `Sort`, `Promote` and `Demote` commands for examples. The `u.beforeChangeGroup` and `u.afterChangeGroup` methods substantially reduce the number of `u.beforeX` and `afterX` methods needed.

Plugins and scripts may define their own `u.beforeX` and `afterX` methods. Indeed, `u.afterX` merely needs to set the `bunch.undoHelper` and `bunch.redoHelper` ivars to the methods used to undo and redo the operation. See the code for the various `u.beforeX` and `afterX` methods for guidance.

`p.setDirty` and `p.setAllAncestorAtFileNodesDirty` now return a `dirtyVnodeList` that all vnodes that became dirty as the result of an operation. More than one list may be generated: client code is responsible for merging lists using the pattern `dirtyVnodeList.extend(dirtyVnodeList2)`

See the section `<< How Leo implements unlimited undo >>` in `leoUndo.py` for more details. In general, the best way to see how to implement undo is to see how Leo's core calls the `u.beforeX` and `afterX` methods.

## Redirecting output from scripts

`leoGlobals.py` defines 6 convenience methods for redirecting stdout and stderr:

    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.

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:

    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()

## Writing to different log tabs

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:

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

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'. The put and putnl methods of the tkinterLog class also take an optional tabName argument which defaults to 'Log'.

Plugins and scripts may call the c.frame.canvas.createCanvas method to create a log tab containing a Tk.Canvas widget. Here is an example script:

    log = c.frame.log ; tag = 'my-canvas'
    w = log.canvasDict.get(tag)
    if not w:
        w = log.createCanvas(tag)
        w.configure(bg='yellow')
    log.selectTab(tag)

## Invoking dialogs using the g.app.gui class

Scripts can invoke various dialogs using the following methods of the g.app.gui object. Here is a partial list. You can use typing completion(default bindings: Alt-1 and Alt-2) to get the full list!

    g.app.gui.runAskOkCancelNumberDialog(c,title,message)
    g.app.gui.runAskOkCancelStringDialog(c,title,message)
    g.app.gui.runAskOkDialog(c,title,message=None,text='Ok')
    g.app.gui.runAskYesNoCancelDialog(c,title,message=None,
        yesMessage='Yes',noMessage='No',defaultButton='Yes')
    g.app.gui.runAskYesNoDialog(c,title,message=None)

The values returned are in ('ok','yes','no','cancel'), as indicated by the method names. Some dialogs also return strings or numbers, again as indicated by their names.

Scripts can run File Open and Save dialogs with these methods:

    g.app.gui.runOpenFileDialog(title,filetypes,defaultextension,multiple=False)
    g.app.gui.runSaveFileDialog(initialfile,title,filetypes,defaultextension)

For details about how to use these file dialogs, look for examples in Leo's own source code. The runOpenFileDialog returns a list of file names.

## Inserting and deleting icons

You can add an icon to the presently selected node with c.editCommands.insertIconFromFile(path). path is an absolute path or a path relative to the leo/Icons folder. A relative path is recommended if you plan to use the icons on machines with different directory structures.

For example:

    path = 'rt_arrow_disabled.gif'
    c.editCommands.insertIconFromFile(path)

Scripts can delete icons from the presently selected node using the following methods:

    c.editCommands.deleteFirstIcon()
    c.editCommands.deleteLastIcon()
    c.editCommands.deleteNodeIcons()

## Customizing panes with different widgets

Tk/Tkinter make it easy to customize the contents of any of Leo's panes. The following sections will discuss the 'official' ivars that make it possible for scripts to access and alter the contents of panes. The next three sections will give examples of modifying each pane.

### Official ivars

The c.frame.log class contains the following 'official' ivars:

    g.es('tabName',c.frame.log.tabName)     # The name of the active tab.
    g.es('tabFrame',c.frame.log.tabFrame)   # The Tk.Frame containing all the other widgets of the tab.
    g.es('logCtrl',c.frame.log.logCtrl)     # Tk.Text widget containing the log text.

The following ivars provide access to the body pane:

    g.es('bodyFrame',c.frame.body.frame)    # The Tk.Frame widget containing the c.frame.body.bodyCtrl

The following ivars provide access to the outline pane:

    g.es('canvas',c.frame.tree.canvas) # The Tk.Canvas on which Leo's outline is drawn.

Tkinter provides a way of determining the enclosing widget of any widget. The body text is enclosed in a Pmw.PanedWidget to support multiple editors.

> w = c.frame.body.bodyCtrl parent = w.pack_info().get('in') g.es('bodyCtrl.parent',parent) # The Tk.Frame containing the body text.

### Common operations on Tk.Text widgets

The following is no substitute for a full discussion of programming the Tk.Text widget: it can do lots.

To clear the log:

    w = c.frame.log.logCtrl
    w.delete('1.0','end')

To write a line to the end of the log:

    w = c.frame.log.logCtrl
    w.insert('end','This is a test\n')

To get the entire contents of the log:

    w = c.frame.log.logCtrl
    g.es(w.get('1.0','end')+'\n')

### Customizing the log pane

The following line removes the initial text widget:

    c.frame.log.logCtrl.pack_forget()

To make the text widget visible again:

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

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:

    import Tkinter as Tk

    # Remove the old contents.
    w = c.frame.log.logCtrl
    parent =  w.pack_info().get('in')
    w.pack_forget()

    # Replace with a red frame.
    f = c.frame.newLog = Tk.Frame(parent,background='red')
    f.pack(side='left',expand=1,fill='both')

And the following will restore the original pane:

    c.frame.newLog.pack_forget()
    w = c.frame.log.logCtrl
    w.pack(side='left',expand=1,fill='both')

### Customizing the body pane

Warning: you will find it hard to execute scripts after removing the body pane, so you had best make the following two scripts into script buttons before executing them :-)

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:

    import Tkinter as Tk

    w = c.frame.body.bodyCtrl
    parent =  w.pack_info().get('in')
    w.pack_forget()

    f = c.frame.newBody = Tk.Frame(parent,background='red')
    f.pack(side='left',expand=1,fill='both')

To restore:

    c.frame.newBody.pack_forget()
    w = c.frame.body.bodyCtrl
    w.pack(side='left',expand=1,fill='both')

### Customizing the outine pane

The following replaces the outline pane with a red frame:

    import Tkinter as Tk

    w = c.frame.tree.canvas
    parent =  w.pack_info().get('in')
    w.pack_forget()

    f = c.frame.newTree = Tk.Frame(parent,background='red')
    f.pack(side='left',expand=1,fill='both')

And this script restores the outline:

    c.frame.newTree.pack_forget()
    c.frame.tree.canvas.pack(side='left',expand=1,fill='both')

## Summary of the vnode and position classes

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`.

### Iterators

Iterators exist only in the position class:

    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.

### Getters

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

Returning strings:

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

Returning ints:

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

Returning bools representing property bits:

    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()

### Setters

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:

    c.setBodyString(p,s)  # Sets the body text of p.
    c.setHeadString(p,s)  # Sets the headline text of p.

Moving nodes:

    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.

The "visited" bit may be used by commands or scripts for any purpose. Many commands use this bits for tree traversal, so these bits do not persist:

    c.clearAllVisited() # Clears all visited bits in c's tree.
    p.clearVisited()
    p.setVisited()

## Creating script buttons

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:

    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

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`.

## Running Leo in batch mode

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:

    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"))