#@+leo-ver=5-thin
#@+node:ekr.20100120072650.6089: * @file ../doc/leoProjects.txt
#@+all
#@+node:ekr.20111103205308.9698: ** Unit tests
import leo.core.leoImport as leoImport
ic = c.importCommands
hs = leoImport.htmlScanner(importCommands=ic,atAuto=True)

s1 = '''
<table id="1"> <table id="2">
<contents/>
</table>
</table>
'''

s2 = '''
<table id="1"> 
<table id="2">
<contents/>
</table>
</table>
'''

t1 = 
assert result == expected,'expected...\n%s\ngot...\n%s' % (
    repr(expected),repr(result))
#@+node:ekr.20100907115157.5905: *3* @ignore Ancient tests
#@+node:ekr.20100208095423.5940: *4* @test leoCache
import leo.core.leoCache as leoCache

cacher = leoCache.cacher(c)

if 0:
    import os
    os.system('cls')

assert cacher.test()
#@+node:ekr.20100906165118.5915: *4* @test leoInkCommands
ic = c.inkscapeCommands
screenshot = r'c:\leo.repo\inkcall\some_screen_shot.png'
template_fn = r'c:\leo.repo\inkcall\template.svg'
png_fn = r'c:\leo.repo\inkcall\output.png'
svg_fn = r'c:\leo.repo\inkcall\temp.svg'
callouts = [
        "This goes here",
        "These are those, but slightly longer",
        "Then you pull this, but this text needs to be longer for testing",]
ic.run(
    screenshot,
    callouts=callouts,
    numbers=[2,4,17],
    edit_flag = True, # True: call inkscape to edit the working file.
    png_fn=png_fn, # Optional: Name of output png file.
    svg_fn=svg_fn, # Optional: Name of working svg file.
    template_fn=template_fn, # Optional: Name of template svg file.
)
#@+node:ekr.20111102123707.9629: *4* @ignore test of marked unit-test trees
#@+node:ekr.20111102123707.9630: *5* @test assert False
assert False
#@+node:ekr.20111102123707.9631: *5* @test assert True
assert True
#@+node:ekr.20111107092526.9799: *4* @test detection of external unit tests
# This test is redundant, and another test sets import_html_tags

# print('g.app.isExternalUnitTest',g.app.isExternalUnitTest)
if g.app.isExternalUnitTest:
    fn = c.shortFileName()
    assert fn.endswith('dynamicUnitTest.leo'),fn
    data = c.config.getData('import_html_tags')
    assert len(data) == 85 # length of data in leoSettings.leo.
else:
    data = c.config.getData('import_html_tags')
    assert len(data) == 85,len(data)
#@+node:ekr.20111107092526.9800: *5* doTests...
def doTests(c,all=None,marked=None,p=None,verbosity=1):

    trace = False ; verbose = False
    if all:
        p = c.rootPosition()
    elif not p:
        p = c.p
    p1 = c.p.copy() # 2011/10/31: always restore the selected position.
    
    g.trace(g.app.isExternalUnitTest)
    
    # This seems a bit risky when run in unitTest.leo.
    # c.save() # Eliminate the need for ctrl-s.
    
    if trace: g.trace('marked',marked,'c',c)

    try:
        g.unitTesting = g.app.unitTesting = True
        g.app.unitTestDict["fail"] = False
        g.app.unitTestDict['c'] = c
        g.app.unitTestDict['g'] = g
        g.app.unitTestDict['p'] = p and p.copy()

        # c.undoer.clearUndoState() # New in 4.3.1.
        changed = c.isChanged()
        suite = unittest.makeSuite(unittest.TestCase)

        # New in Leo 4.4.8: ignore everything in @ignore trees.
        last = None if all else p.nodeAfterTree()
        
        aList = findAllUnitTestNodes(c,p,last,all,marked,
            lookForMark=False,lookForNodes=True)
       
        found = False
        for p in aList:
            if isTestNode(p):
                if trace: g.trace('adding',p.h)
                test = makeTestCase(c,p)
            elif isSuiteNode(p): # @suite
                if trace: g.trace('adding',p.h)
                test = makeTestSuite(c,p)
            else:
                test = None
            if test:
                suite.addTest(test)
                found = True
        
        # Verbosity: 1: print just dots.
        if not found:
            # 2011/10/30: run the body of p as a unit test.
            test = makeTestCase(c,c.p)
            if test:
                suite.addTest(test)
                found = True
        if found:
            res = unittest.TextTestRunner(verbosity=verbosity).run(suite)
            # put info to db as well
            if g.enableDB:
                key = 'unittest/cur/fail'
                archive = [(t.p.gnx, trace) for (t, trace) in res.errors]
                c.cacher.db[key] = archive
        else:
            g.es_print('no %s@test or @suite nodes in %s outline' % (
                g.choose(marked,'marked ',''),
                g.choose(all,'entire','selected')),color='red')
    finally:
        c.setChanged(changed) # Restore changed state.
        if g.app.unitTestDict.get('restoreSelectedNode',True):
            c.contractAllHeadlines()
            c.redraw(p1)
        g.unitTesting = g.app.unitTesting = False
#@+node:ekr.20111107092526.9801: *6* class generalTestCase
class generalTestCase(unittest.TestCase):

    """Create a unit test from a snippet of code."""

    @others
#@+node:ekr.20111107092526.9802: *7* __init__ (generalTestCase)
def __init__ (self,c,p):

     # Init the base class.
    unittest.TestCase.__init__(self)

    self.c = c
    self.p = p.copy()
#@+node:ekr.20111107092526.9803: *7*  fail
def fail (self,msg=None):

    """Mark a unit test as having failed."""

    import leo.core.leoGlobals as g

    g.app.unitTestDict["fail"] = g.callers()
#@+node:ekr.20111107092526.9804: *7* tearDown
def tearDown (self):

    pass

    # Restore the outline.
    self.c.outerUpdate()
#@+node:ekr.20111107092526.9805: *7* setUp
def setUp (self):

    c = self.c ; p = self.p

    c.selectPosition(p.copy()) # 2010/02/03
#@+node:ekr.20111107092526.9806: *7* runTest (generalTestCase)
def runTest (self,define_g = True):

    trace = False
    c = self.c ; p = self.p.copy()
    script = g.getScript(c,p).strip()
    self.assert_(script)
    
    if c.shortFileName() == 'dynamicUnitTest.leo':
        c.write_script_file = True

    # New in Leo 4.4.3: always define the entries in g.app.unitTestDict.
    g.app.unitTestDict = {'c':c,'g':g,'p':p and p.copy()}

    if define_g:
        d = {'c':c,'g':g,'p':p and p.copy(),'self':self,}
    else:
        d = {'self':self,}

    script = script + '\n'
    if trace: g.trace('p: %s c: %s write script: %s script:\n%s' % (
        p and p.h,c.shortFileName(),c.write_script_file,script))

    # Execute the script. Let the unit test handle any errors!
    # 2011/11/02: pass the script sources to exec or execfile.
    if c.write_script_file:
        scriptFile = c.writeScriptFile(script)
        if g.isPython3:
            exec(compile(script,scriptFile,'exec'),d)
        else:
            execfile(scriptFile,d)
    else:
        exec(script,d)
#@+node:ekr.20111107092526.9807: *7* shortDescription
def shortDescription (self):

    s = self.p.h

    # g.trace(s)

    return s + '\n'
#@+node:ekr.20111107092526.9808: *6* makeTestSuite (leoTest)
@ This code executes the script in an @suite node.  This code assumes:
- The script creates a one or more unit tests.
- The script puts the result in g.app.scriptDict["suite"]
@c

def makeTestSuite (c,p):

    """Create a suite of test cases by executing the script in an @suite node."""

    p = p.copy()
    # g.trace('c.write_script_file',c.write_script_file)
    script = g.getScript(c,p).strip()
    if not script:
        print("no script in %s" % h)
        return None
    try:
        if 0: #debugging
            n,lines = 0,g.splitLines(script)
            for line in lines:
                print(n,line)
                n += 1
                
        # 2011/11/02: make script sources available.
        d = {'c':c,'g':g,'p':p}
        if c.write_script_file:
            scriptFile = c.writeScriptFile(script)
            if g.isPython3:
                exec(compile(script,scriptFile,'exec'),d)
            else:
                execfile(scriptFile,d)
        else:
            exec(script + '\n',d)
        suite = g.app.scriptDict.get("suite")
        if not suite:
            print("makeTestSuite: %s script did not set g.app.scriptDict" % p.h)
        return suite
    except Exception:
        print('makeTestSuite: exception creating test cases for %s' % p.h)
        g.es_exception()
        return None
#@+node:ekr.20111107092526.9809: *6* makeTestCase
def makeTestCase (c,p):

    p = p.copy()

    if p.b.strip():
        return generalTestCase(c,p)
    else:
        return None
#@+node:ekr.20111107092526.9810: *5* main & helpers (leoDynamicTest.py)
def main ():

    trace = False
    readSettings = True 
    tag = 'leoDynamicTests.leo'
    if trace: t1 = time.time()

    # Setting verbose=True prints messages that would be sent to the log pane.
    path,gui,silent = scanOptions()
    # print('(leoDynamicTest.py:main)','silent',silent)

    # Not loading plugins and not reading settings speeds things up considerably.
    bridge = leoBridge.controller(gui=gui,
        loadPlugins=False, # Must be False: plugins will fail when run externally.
        readSettings=True, # True adds about 0.3 seconds.  Is it useful?
        silent=True,
        verbose=False)

    if trace:
         t2 = time.time()
         print('%s open bridge:  %0.2fsec' % (tag,t2-t1))

    if bridge.isOpen():
        g = bridge.globals()
        g.app.silentMode = silent
        g.app.isExternalUnitTest = True
        path = g.os_path_finalize_join(g.app.loadDir,'..','test',path)
        c = bridge.openLeoFile(path)
        if trace:
            t3 = time.time()
            print('%s open file: %0.2fsec' % (tag,t3-t2))
        runUnitTests(c,g)
#@+node:ekr.20111107092526.9811: *6* runUnitTests
def runUnitTests (c,g):

    p = c.rootPosition()
    #g.es_print('running dynamic unit tests...')
    c.selectPosition(p)
    c.debugCommands.runAllUnitTestsLocally()
#@+node:ekr.20111107092526.9812: *6* scanOptions
def scanOptions():

    '''Handle all options and remove them from sys.argv.'''

    parser = optparse.OptionParser()
    parser.add_option('--path',dest='path')
    parser.add_option('--gui',dest="gui")
    parser.add_option('--silent',action="store_true",dest="silent")

    # Parse the options, and remove them from sys.argv.
    options, args = parser.parse_args()
    sys.argv = [sys.argv[0]] ; sys.argv.extend(args)

    # -- path
    # We can't finalize the path here, because g does not exist ye.
    path = options.path or 'dynamicUnitTest.leo'

    # -- gui
    gui = options.gui
    if gui: gui = gui.lower()
    if gui not in ('qttabs','qt'):
        gui = 'nullGui'

    # --silent
    silent = options.silent

    return path,gui,silent
#@+node:ekr.20111116161118.10248: *3* Recent tests
#@+node:ekr.20111104132424.9909: *4* @test assert True
# It's useful to have this do-nothing test.

assert True
#@+node:ekr.20111107092526.9819: *4* @test cls
g.cls() # Clear the screen
#@+node:ekr.20111110085739.10265: *4* @test html string
s = '''\
<HTML>
<head>
    <title>Bodystring</title>
</head>
<body class='bodystring'>
<div id='bodydisplay'></div>
</body>
</html>
'''

html_tags = ('body','head','html','table',) # 'div',
setting = 'import_html_tags'

# Settings now work when run externally.
c.config.set(setting,'data',html_tags)
tags = c.config.getData(setting)
assert tags == html_tags,len(tags)

g.app.unitTestDict ['expectedErrors'] = 0

showTree = True

c.importCommands.htmlUnitTest(p,s=s,showTree=showTree)

if showTree:
    # g.cls()
    for p in p.subtree():
        print('\n***** %s\n' %p.h)
        print(p.b)
#@+node:ekr.20120112100822.10003: *5* @file c:/leo.repo/trunk/leo/core/html string
@language xml
@tabwidth -4
@others

#@+node:ekr.20120112100822.10004: *6* html
<HTML>
@others
</html>
#@+node:ekr.20120112100822.10005: *7* head

<head>
    <title>Bodystring</title>
</head>
#@+node:ekr.20120112100822.10006: *7* body
<body class='bodystring'>
<div id='bodydisplay'></div>
</body>
#@+node:ekr.20120203153754.10033: *5* @file c:/leo.repo/trunk/leo/core/html string
@language xml
@tabwidth -4
@others

#@+node:ekr.20120203153754.10034: *6* html
<HTML>
@others
</html>
#@+node:ekr.20120203153754.10035: *7* head

<head>
    <title>Bodystring</title>
</head>
#@+node:ekr.20120203153754.10036: *7* body
<body class='bodystring'>
<div id='bodydisplay'></div>
</body>
#@+node:ekr.20120204061120.10061: *5* @file c:/leo.repo/trunk/leo/core/html string
@language xml
@tabwidth -4
@others

#@+node:ekr.20120204061120.10062: *6* html
<HTML>
@others
</html>
#@+node:ekr.20120204061120.10063: *7* head

<head>
    <title>Bodystring</title>
</head>
#@+node:ekr.20120204061120.10064: *7* body
<body class='bodystring'>
<div id='bodydisplay'></div>
</body>
#@+node:ekr.20111109151106.9746: *4* @test htmlScanner.filterTokens
import leo.core.leoImport as leoImport
ic = c.importCommands
hs = leoImport.htmlScanner(importCommands=ic,atAuto=True)
strip = hs.stripTokens
dump  = hs.formatTokens

s1 = '''<table id="1"><table id="2">
<contents/>
</table>
</table>'''

s2 = '<table id="1"><table id="2"><contents/></table></table>'
    
t1 = hs.tokenize(s1)
t2 = hs.tokenize(s2)
f1 = hs.filterTokens(t1)
f2 = hs.filterTokens(t2)

assert strip(f1) == strip(f2),'f1...\n%s\nf2...\n%s' % (
    dump(f1),dump(f2))
    
if 0:
    print(dump(f1))
#@+node:ekr.20111110084957.10092: *4* @test import dataN.html
fn = r'c:\recent\data.html'

# fn = r'c:\recent\data-smaller.html'
# fn = r'c:\recent\data666.html'

# These all pass on data.html:
    # html_tags = ('html','head','body',)
    # html_tags = ('html','head','body','table',)
    # html_tags = ('html','head','body','table','div',)
    # html_tags = ('html','head','body','table','div','script',)
    # html_tags = ('html','head','body','table','div','script','link',)
    # html_tags = ('html','head','body','table','div','script','link','p',)

html_tags = ('html','head','body','table','div','script','p','td','tr',)

# Settings now work when run externally.
setting = 'import_html_tags'
c.config.set(setting,'data',html_tags)
tags = c.config.getData(setting)
assert tags == html_tags,len(tags)

g.cls()

c.importCommands.importFilesCommand(files=[fn], treeType='@file')
#@+node:ekr.20111109105907.9795: *4* @test unicode stuff
@first # -*- coding: utf-8 -*-

table = (
    'test',
    'Ä 궯 奠',
    'Ä 궯 奠 after', # fails with cp6501: after is duplicated.
)

print('*'*20)
print('isPython3: %s' % g.isPython3)

for s in table:
    if g.isPython3:
        s = s.encode('ascii','replace') # create bytes.
    g.es(repr(s))
    g.es(s)
    g.pr ('g.pr(s)       : %s' % s)
    g.pr ('g.pr(repr(s)) : %s' % repr(s))
    print('print(s)      : %s' % s)
    print('print(repr(s)): %s' % s)
#@+node:ekr.20111113064104.9841: *4* @test external text operations
assert g.app.isExternalUnitTest

body = c.frame.body
assert repr(body.widget).startswith('stringTextWidget')
assert body.widget == body.bodyCtrl

w = body.bodyCtrl
w.setAllText(p.b)
assert p.b == w.getAllText()
#@+node:ekr.20111112131605.9789: *4* @test nullBody text operations
# print('isExternalUnitTest',g.app.isExternalUnitTest)

if g.app.isExternalUnitTest:
    body = c.frame.body
else:
    import leo.core.leoCommands as leoCommands
    import leo.core.leoFrame as leoFrame
    import leo.core.leoGui as leoGui
    
    # Important: external unit tests should execute in this environment.
    nullGui   = leoGui.nullGui('null gui')
    nullFrame = leoFrame.nullFrame(title='nullFrame title',gui=nullGui)
    c2 = leoCommands.Commands(nullFrame,fileName='<empty fileName>')
    nullFrame.c = c2
    body = leoFrame.nullBody(frame=nullFrame,parentFrame=None)
    assert repr(body).startswith('<leo.core.leoFrame.nullBody')

# Now test some basic operations.
assert repr(body.widget).startswith('stringTextWidget')
assert body.widget == body.bodyCtrl
w = body.bodyCtrl

w.setAllText(p.b)
assert p.b == w.getAllText()
#@+node:ekr.20111115155710.9835: *4* @test g.python_tokenize
# h = 'g.python_tokenize'
# p = p.firstChild()
# assert p.h == h
tokens = g.python_tokenize(p.b,line_numbers=False)

# tokens = [(kind,val) for (kind,val,line_number) in tokens]

# First, the basic check
tokens1 = [val for kind,val in tokens]
s = ''.join(tokens1)
assert p.b == s,repr(s)

if 0:
    for z in tokens:
        kind,val = z
        print('%6s %s' % (kind,repr(val)))
        
# Next, start filtering.
tokens = [(kind,g.choose(kind=='string','"S"',val)) for kind,val in tokens]

if 0: # Delete whitespace.
    tokens = [(kind,val) for (kind,val) in tokens if kind != 'ws']
    tokens = [(kind,g.choose(kind=='id',val+' ',val)) for (kind,val) in tokens]

# Last: stringize.
tokens = [val for kind,val in tokens if kind != 'comment']
# print(''.join(tokens))

if 1: # Print lines containing '='
    s = ''.join(tokens)
    for ch in '()[]{}<>.,:=+-/':
        s = s.replace(' '+ch,ch)
    aList = [z for z in g.splitLines(s)
        if z.find('=') > -1] # and not z.find('+=')>-1 and not z.find('-=')>-1]
    print(''.join(aList))
    
#@+node:ekr.20111204110514.10287: *4* @test p.moveToFirst/LastChild
def setup(p):
    while p.hasChildren():
        p.firstChild().doDelete()

child = p.firstChild()
assert child
setup(child)
p2 = child.insertAfter()
p2.h = "test"
try:
    assert c.positionExists(p2),p2
    p2.moveToFirstChildOf(child)
    assert c.positionExists(p2),p2
    p2.moveToLastChildOf(child)
    assert c.positionExists(p2),p2
finally:
    if 1:
        setup(child)
    c.redraw(p)
#@+node:ekr.20111210100047.10306: *5* child
#@+node:ekr.20111116161118.10247: *3* Old lint tests
#@+node:ekr.20111116103733.9845: *4*  Naming tests
# http://docs.python.org/reference/executionmodel.html#naming-and-binding

if 0:
    def test():
        a = b
        b = 1 # UnboundLocalError
        
print('***')
          
def test():
    global g2
    g2 = 4
    def test2():
        print(g2)
    test2()
    g2 = 3
    print(g2)
    
g2 = 'g2'
print(g2)

test()
#@+node:ekr.20111116103733.9846: *5* << define s>>
s = '''



'''

s = g.adjustTripleString(s,-4)
#@+node:ekr.20111116103733.9839: *4* @test create lots of data structures
# A simple prototype of data-centric design.
# 0.023 sec to create 100,000 dicts.
# 0.230 sec to create 1,000,000 dicts.

import time

t1 = time.clock()
n = 1000000

d = {}
for z in range(n):
    d[n] = {'n':n,}

t2 = time.clock()
print('Created %s dicts in %2.3f sec.' % (n,t2-t1))
#@+node:ekr.20111116103733.9844: *4* @test dumper (to outNodes)
import leo.core.leoGlobals as g
import sys
import lintutils as u

fn = 'c:/leo.repo/trunk/leo/core/leoApp.py'
out = 'c:/leo.repo/new-pylint/outNodes.txt'

outStream = open(out,'w')
u.AstDumper().dumpFileAsNodes(fn,outStream)
#@+node:ekr.20111116103733.9840: *4* @test dumper (to outString)
import leo.core.leoGlobals as g
import sys
import lintutils as u

fn = 'c:/leo.repo/trunk/leo/core/leoApp.py'
out = 'c:/leo.repo/new-pylint/outString.txt'

outStream = open(out,'w')
u.AstDumper(brief=True).dumpFileAsString(fn,outStream)
#@+node:ekr.20111116103733.9838: *4* @test speed of AstTraverser (one file)
import leo.core.leoGlobals as g
import leo.core.leoInspect as leoInspect
import ast
import time

fn = 'c:/leo.repo/trunk/leo/core/leoApp.py'
    
t1 = time.clock()
s = open(fn,'r').read()
t2 = time.clock()
tree = ast.parse(s,filename=fn,mode='exec')
t3 = time.clock()
leoInspect.AstTraverser(fn).visit(tree)
t4 = time.clock()

if 1:
    print('read:     %2.3f sec.' % (t2-t1))
    print('parse:    %2.3f sec.' % (t3-t2))
    print('traverse: %2.3f sec.' % (t4-t3))
    print('total:    %2.3f sec.' % (t4-t1))
#@+node:ekr.20111128103520.10237: *3* Tests of @shadow from unitTest.leo
#@+node:ekr.20111128103520.10238: *4* @@shadow ../test/unittest/at-shadow-test.py
@language python
@tabwidth -4
@others
#@+node:ekr.20111128103520.10239: *5* spam
def spam():
    pass
#@+node:ekr.20111128103520.10240: *5* eggs
def eggs():
    pass
#@+node:ekr.20111128103520.10241: *4* @@shadow unittest/at-shadow-line-number-test.py
@language python
@tabwidth -4
@others
#@+node:ekr.20111128103520.10242: *5* child
def child():
    pass
#@+node:ekr.20111128103520.10243: *4* @test @shadow: shape of tree
# Not valid for external tests: uses @<file> node.
if not g.app.isExternalUnitTest:

    h = '@shadow ../test/unittest/at-shadow-test.py'
    p = g.findNodeAnywhere(c,h)
    assert p
    
    table = (
        (p.firstChild(),'spam'),
        (p.firstChild().next(),'eggs')
    )
    
    assert not p.isDirty(),p.h # Do not ignore this failure!
    
    for p2,h2 in table:
        assert len(p2.h) == len(h2)
#@+node:ekr.20111128103520.10244: *4* @test goto-global-line @shadow
# Not valid for external tests: uses @<file> node.
if not g.app.isExternalUnitTest:

    h = '@shadow unittest/at-shadow-line-number-test.py'
    root1 = g.findNodeAnywhere(c,h)
    assert root1
    assert root1.isAnyAtFileNode()
    
    fileName,lines,n,root2 = c.goToLineNumber(c).setup_file(n=6,p=root1)
    assert fileName == h[8:],'fileName'
    assert root2 == root1
    
    if 0:
        print('root:%s, isRaw:%s, n:%s, len(lines): %s' % (
            root and root.h,isRaw,n,len(lines)))
#@+node:ekr.20111116103733.9818: *3* LeoInspect tests
#@+node:ekr.20111116103733.10672: *4* @test leoInspect with multiple files
import leo.core.leoInspect as inspect
import os
import time

<< define old_s >>
<< define s >>

@others

g.cls()

aList = (
    'leoAtFile.py',
    'leoEditCommands.py',
)
test(files=aList,print_stats=False,s=None,print_times=True)
#@+node:ekr.20111116103733.10451: *5* << define old_s >>
# import leo.core.leoGlobals
# import leo.core.leoGlobals as g
# from leo.core.leoGlobals import pr as pr2
# from leo.core.leoGlobals import trace

s_old = '''\
import sys

aGlobal = 5
# aGlobal2 is not explicitly defined.

c = [z for z in 'abc']

def myFunc():
    n1,n2,n3,junk,junk=sys.version_info
    a = self.b
    for z in a:
        print(z)
    with A() as a:
        print(a,b)
        
def test():
    a = b # UnboundLocalError.
    b = 1
    c = 2 # Any def will do at present.
    print(g)
    print(c.frame.body)
    print(c.frame.body.xxx.yyy)
    print(b.yyy) # no check will be made.
    print(xxx.yyy)
    for c in 'abc':
        print(c,b)
        print(g)


class myClass:
    
    def __init__(self,c):
        self.a = True
        self.b = None
        c.frame.xxxx
        
    def spam(self,a,b,c=5,*args,**keys):
        global aGlobal2
        aGlobal2 = 'abc'
        self.a = b
        self.a = x
        
    def no_self(a):
        pass
        
    def test_lambda(self):
        f = lambda a,b: a

    def test_comprehension(self):
        z2 = [z for z in 'abc']
        
aGlobal3 = 4 # This should be defined everywhere.

def test():
    # a = ','.join(['a','b'])
    p = 5
    # print(p.parent().h)
    # print(g.app.windowList[0])
    print(p)
    print(g)

'''

# import leo.core.leoCommands as leoCommands
#@+node:ekr.20111116103733.10452: *5* << define s >>
s = '''\

import leo.core.leoGlobals as g

def test(c):
    a = 5
    f = c.frame
    c.frame.body.bodyCtrl = w

'''

s = g.adjustTripleString(s,-4)
# print(s)
#@+node:ekr.20111116103733.10450: *5* test
def test(files,print_stats=True,s=None,print_times=True):
   
    t1 = time.clock()
    sd = inspect.SemanticData(controller=None)

    if s: # Use test string.
        fn = '<test file>'
        inspect.InspectTraverser(fn,sd).traverse(s)
    else:
        for fn in files:
            print(g.shortFileName(fn))
            s = inspect.LeoCoreFiles().get_source(fn)
            if s:
                inspect.InspectTraverser(fn,sd).traverse(s)
            else:
                print('file not found: %s' % (fn))
           
    sd.total_time = time.clock()-t1
    
    if print_times: sd.print_times()
    if print_stats: sd.print_stats()
#@+node:ekr.20111116103733.10449: *4* @test leoInspect.module.classes
import leo.core.leoInspect as inspect

dump_classes = False
print_modules = True
print_functions = False
print_stats = False
print_times = False

# if dump_modules or print_stats:
    # g.cls()

m = inspect.module(fn='leoApp.py',sd=None,
    print_stats=print_stats,print_times=print_times)
    
print(m)
for o in m.classes():
    if dump_classes:
        o.dump(verbose=False)
    if print_modules:
        print(o)
    if print_functions:
        for f in o.functions():
            print('  %s' % f)
#@+node:ekr.20111116161118.10212: *4* @test leoInspect.module.classes 2
import leo.core.leoInspect as inspect

g.cls()

m = inspect.module(c,'leoEditCommands.py')
    #,sd=None,print_stats=False,print_times=False)
    
for z in m.classes():
    print(z)
#@+node:ekr.20111116161118.10218: *4* @test leoInspect.module.defs
import leo.core.leoInspect as inspect

g.cls()

m = inspect.module(c,'leoEditCommands.py')
    #,sd=None,print_stats=False,print_times=False)
    
for z in m.defs():
    print(z)
#@+node:ekr.20111116161118.10181: *4* @test leoInspect.module.statements
import leo.core.leoInspect as inspect

g.cls()

m = inspect.module(c,'leoEditCommands.py')
    #,sd=None,print_stats=False,print_times=False)
    
for z in m.statements():
    print(z)
#@+node:ekr.20111116161118.10230: *4* @test leoInspect (leoEditCommands.py)
import leo.core.leoInspect as inspect

g.cls()

m = inspect.module(fn='leoEditCommands.py')

def show(o):
    print('%-5s %s' % (o.line_number(),o.format()))

var = '.widget'
func = 'w.insert'

if 0:
    
    print('\nAssignments to %s...\n' % (var))
    for o in m.assignments_to(var):
        show(o)
        
    print('\nAssignments using %s...\n' % (var))
    for o in m.assignments_using(var):
        show(o)
        
    print('\nCalls to %s...\n' % (func))
    for o in m.calls_to(func):
        show(o)
        
    if 1:
        classes = m.classes()
        for d in classes[0].defs():
            print('')
            print(d)
            for z in d.statements():
                # print(z.tree())
                # print(z.sd.dump_ast(z.tree()))
                lines = g.splitLines(z.format())
                for line in lines:
                    print('  %s' % (line))
#@+node:ekr.20111127153202.10231: *4* @test speed of AstTraverser (all Leo core files)
import leo.core.leoGlobals as g
import leo.core.leoInspect as inspect
import ast
import time

read_time,parse_time,traverse_time = 0.0,0.0,0.0
t_start = time.clock()
count = 0
for fn in inspect.LeoCoreFiles().files:
    count += 1
    t2 = time.clock()
    s = open(fn,'r').read()
    t3 = time.clock()
    tree = ast.parse(s,filename=fn,mode='exec')
    t4 = time.clock()
    inspect.AstTraverser(fn).visit(tree)
    t5 = time.clock()
    read_time += t3-t2
    parse_time += t4-t3
    traverse_time += t5-t4
t_end = time.clock()
total_time = t_end-t_start
if 1:
    print('files:    %s' % (count))
    print('read:     %2.3f sec.' % (read_time))
    print('parse:    %2.3f sec.' % (parse_time))
    print('traverse: %2.3f sec.' % (traverse_time))
    print('total:    %2.3f sec.' % (total_time))
#@+node:ekr.20111127090852.10227: *4* @test leoInspect (all core files)
import leo.core.leoInspect as inspect
import time

sd = inspect.SemanticData()
start_time = time.clock()

count = 0
for fn in inspect.LeoCoreFiles().files:
    print(fn)
    m = inspect.module(fn,sd=sd)
    count += 1

end_time = time.clock()
total_time = end_time-start_time

if 1:
    print('files: %s' % (count))
    print('total: %2.3f sec.' % (total_time))
if 1:
    sd.print_stats()
#@+node:ekr.20111117031039.10762: *4* @test leoInspect.module (s)
import leo.core.leoInspect as inspect

g.cls()

def show(o,indent):
    # print('\n%s\n' % o.sd.dump_ast(o.tree()))
    print('%s%s' % (' '*4*indent,o.format()))

<< define s >>
print('Input...\n%s\n' % (s.rstrip()))

m = inspect.module(s=s)

if 1:
    print(show(m,0))

print('\nAssignments to a...\n')
for o in m.assignments_to('a'):
    print(o.format())
    
print('\nAssignments using d...\n')
for o in m.assignments_using('d'):
    print(o.format())
    
print('\nCalls to f...\n')
for o in m.calls_to('f'):
    print(o.format())

if 1:
    for s in m.statements():
        show(s,0)
    
    for f in m.functions():
        show(f,0)
        for z in f.statements():
            show(z,1)
    
    for cls in m.classes():
        show(cls,0)
        for d in cls.defs():
            show(d,1)
            for z in d.statements():
                show(z,1)
#@+node:ekr.20111117031039.10763: *5* << define s >>
s = '''
x.y = b(arg1,arg2=5,*args,**args).c[1:2:3].d
a=b+c
f(a=1,b=2,*args,**keys)
'''


# def outer_function(a,b=99,c=88,*args,**keys):
    # print('hello')
    
# class myClass:
    # def method():
        # pass
    
#@+node:ekr.20111116103733.9838: *4* @test speed of AstTraverser (one file)
import leo.core.leoGlobals as g
import leo.core.leoInspect as leoInspect
import ast
import time

fn = 'c:/leo.repo/trunk/leo/core/leoApp.py'
    
t1 = time.clock()
s = open(fn,'r').read()
t2 = time.clock()
tree = ast.parse(s,filename=fn,mode='exec')
t3 = time.clock()
leoInspect.AstTraverser(fn).visit(tree)
t4 = time.clock()

if 1:
    print('read:     %2.3f sec.' % (t2-t1))
    print('parse:    %2.3f sec.' % (t3-t2))
    print('traverse: %2.3f sec.' % (t4-t3))
    print('total:    %2.3f sec.' % (t4-t1))
#@+node:ekr.20120116073928.10114: *3* Unit tests for settings
#@+node:ekr.20120126050844.10386: *4* @ignore print dicts unit tests
#@+node:ekr.20120117095916.10124: *5* @test printMenusList
def printMenusList(aList,level=0):
    
    for z in aList:
        a,b,c = z
        print('*** kind',a)
        if type(b) in (type(()),type([])):
            for z2 in b:
                a1,b1,c1 = z2
                if a1.startswith('@menu') and type(b1) in (type(()),type([])):
                    print()
                    print('*** inner menu: %s' % (level+1))
                    print(a1)
                    for z3 in b1:
                        print(z3)
                    if c1: print(c1)
                else:
                    print(z2)
            if c: print(c)
        else:
            print(b)
        print()
        break #
        
printMenusList(c.config.getMenusList())
       
#@+node:ekr.20120117095916.10140: *5* @test printInverseBindingDict
print('\ninverseBindingDict...\n')

d = c.k.computeInverseBindingDict()

for key in sorted(list(d.keys())):

    if 1 == len(d.get(key)):
        print(key,d.get(key))
    else:
        print()
        print(key)
        print(d.get(key))
        print()
#@+node:ekr.20120123113111.10925: *5* @test printBindingsDict
import leo.core.leoConfig as leoConfig # for ShortcutInfo
    
partial = True

d = c.k.bindingsDict
    # Keys are shortcuts; values are *lists* leoConfig.ShortcutInfo objects.
    
print('\nk.bindingsDict%s...\n' % ' (partial)' if partial else '')
    
for key in list(sorted(d.keys())):
    aList = d.get(key,[])
    for b in aList:
        assert isinstance(b,leoConfig.ShortcutInfo)
        if not partial or b.kind != 'leosettings.leo':
            print(b)
#@+node:ekr.20120117095916.10141: *5* @test printMasterBindingsDict
partial = True

panes = ('all','body','button','log','tree','text',
    'command','insert','overwrite',)

d = c.k.masterBindingsDict
    # Keys are scope names (in panes) or mode names.
    # Values are dicts:
        # keys are strokes; values are leoConfig.ShortcutInfo objects.
        
print('\nk.masterBindingsDict%s...\n' % ' (partial)' if partial else '')

for pane in sorted(list(d.keys())):
    kind = 'pane' if pane in panes else 'mode'
    print('%s: %s...' % (kind,pane))
    d2 = d.get(pane)
    for stroke in sorted(list(d2.keys())):
        b = d2.get(stroke)
        if not partial or b.kind != 'leosettings.leo':
            print('%6s %25s %17s %s' % (b.pane,stroke,b.kind,b.commandName))
            assert b.pane == pane
            assert b.stroke == stroke
    print()
#@+node:ekr.20120126080450.10187: *4* @ignore passed
#@+node:ekr.20120126080450.10189: *5* @test mode-related info
@

g.app.config.modeCommandsDict
    Keys are command names: enter-x-mode.
    Values are inner dictionaries:
        Keys are command names, values are lists of ShortcutInfo nodes.
@c

d = g.app.config.modeCommandsDict
    
for key in sorted(d.keys()):
    print('*** mode ***',key)
    d2 = d.get(key)
    for key2 in sorted(d2.keys()):
        aList = d2.get(key2)
        print(key2)
        for si in aList:
            print('   ',si)
#@+node:ekr.20120120095156.10262: *5* @test types of contents of settings dicts
@
ivar                    Keys                Values
----                    ----                ------
c.commandsDict          command names (1)   functions
k.inverseCommandsDict   func.__name__       command names
k.bindingsDict          shortcuts           list of ShortcutInfo objects
k.masterBindingsDict    scope names (2)     inner masterBindingDicts (3)
k.masterGuiBindingsDict strokes             list of widgets in which stoke is bound
k.settingsNameDict (4)  settings.lower()    "Real" Tk specifiers
inverseBindingDict (5)  command names       lists of tuples (pane,key)
modeCommandsDict (6)    command name (7)    inner modeCommandsDicts (8)

Notes:
(1) Command names are minibuffer names (strings)
(2) Scope names are 'all','text',etc.
(3) inner masterBindingDicts: Keys are strokes; values are ShortcutInfo objects.
(4) k.settingsNameDict has no inverse.
(5) inverseBindingDict is **not** an ivar: it is computed by k.computeInverseBindingDict.
(6) A global dict: g.app.gui.modeCommandsDict
(7) enter-x-command
(8) Keys are command names, values are lists of ShortcutInfo objects.
@c

si_type = c.k.ShortcutInfo
disabled_func_type = None # Should be any bound method.
k = c.k

@others

test_dict_of_objects(c.commandsDict,type('s'),disabled_func_type,'commandsDict')
test_dict_of_objects(k.inverseCommandsDict,type('s'),type('s'),'inverseCommandsDict')
test_dict_of_lists(k.bindingsDict,si_type,'bindingsDict')
test_dict_of_dicts(k.masterBindingsDict,si_type,'masterBindingsDict')
test_dict_of_lists(k.masterGuiBindingsDict,None,'masterGuiBindingsDict')
test_dict_of_objects(k.settingsNameDict,type('s'),type('s'),'settingsNameDict')
test_dict_of_lists(k.computeInverseBindingDict(),type(tuple()),'inverseBindingDict')

# Test individual dicts separately.
d = g.app.config.modeCommandsDict
test_dict_of_dicts(d,None,'modeCommandsDict')
for key in sorted(d.keys()):
    d2 = d.get(key)
    test_dict_of_lists(d2,si_type,'inner modeCommandsDict')
        # This requires a hack to special-case the
        # '*entry-commands*' and '*command-prompt*' keys.
#@+node:ekr.20120126080450.10193: *6* test_dict_of_dicts
def test_dict_of_dicts(d,theType,tag):

    assert d,tag

    for key in d.keys():
        d2 = d.get(key)
        assert type(d2) == type({})
        for key in d2.keys():
            obj = d2.get(key)
            if theType:
                assert type(obj) == theType,repr(obj)
#@+node:ekr.20120126080450.10191: *6* test_dict_of_lists
def test_dict_of_lists(d,theType,tag):

    assert d,tag

    for key in d.keys():
        obj = d.get(key)
        if key in ('*entry-commands*','*command-prompt*'):
            # Special case for g.app.config.modeCommandsDict
            assert type(obj)==type([]),repr(obj)
        else:
            assert type(obj) == type([])
            # Don't check types of list elements if theType is None.
            if theType:
                for z in obj:
                    assert type(z)==theType,'key: %s obj: %s' % (key,repr(obj))
#@+node:ekr.20120126080450.10195: *6* test_dict_of_objects
def test_dict_of_objects(d,keyType,valueType,tag):

    assert d,tag

    for key in d.keys():
        assert type(key) == keyType,repr(key)
        obj = d.get(key)
        # Don't check type of obj if valueType is None.
        if valueType:
            assert type(obj) == valueType,'\nobj: %s\nvalueType: %s' % (repr(obj),valueType)
#@+node:ekr.20120126080450.10194: *6* Unused
# import types
# types.ListType does not exist in Python 3.x.
# assert isinstance(aList,list().__class__)
#@+node:ekr.20120127084215.10238: *5* @test merge_settings_dicts
@others

# import os ; os.system('cls')
    
d1 = g.app.config.immutable_leo_settings_shortcuts_dict
d2 = g.app.config.immutable_my_leo_settings_shortcuts_dict
d3 = g.app.config.merge_settings_dicts(d1,d2)

if False:
    patterns = (
        'backward-find-character-extend-selection',
    )
    for pattern in patterns:
        print(dump_dict(d1,pattern,tag='d1'))
        print(dump_dict(d2,pattern,tag='d2'))
        print(dump_dict(d3,pattern,tag='d3'))

test(d1,d2,d3)
#@+node:ekr.20120127145909.10227: *6* dump & dump_dict (@test merge_settings_dicts)
def dump(aList,pattern=None,tag=None):
    
    return '\n'.join([repr(z) for z in aList])
    

def dump_dict(d,pattern=None,tag=None):
    
    result = [] # '\ndump of %s...' % (tag)
    
    for key in d.keys():
        if pattern in (key,None):
            result.append(key)
            aList = d.get(key)
            for z in aList:
                result.append('    %s' % (z))
                
    return '\n'.join(result)
#@+node:ekr.20120127084215.10239: *6* test (@test merge_settings_dicts)
def test(old_d,new_d,result_d):
    
    '''Test that result_d is the result of upating old_d with new_d.
    
    This test is tricky: only inverted dicts have ShortcutInfo nodes as keys.'''
    
    invert,uninvert = g.app.config.invert,g.app.config.uninvert

    # Compute the inversions of all the dicts.
    inv_old,inv_new,inv_res = invert(old_d),invert(new_d),invert(result_d)
    
    # Part 1: Ensure we test all keys.
    keys = list(inv_old.keys())
    keys.extend(list(inv_new.keys()))
    keys.extend(list(inv_res.keys()))
    keys = sorted(list(set(keys)))
    assert None not in keys
    for key in inv_old.keys(): assert key in keys,key
    for key in inv_new.keys(): assert key in keys,key
    for key in inv_res.keys(): assert key in keys,key
    
    # Part 2: Carefully test the inverted result.
    def si_name_key(si): return si.commandName or ''

    for key in keys:
        # Compute the *sorted* list of 
        res_list = sorted(inv_res.get(key,[]),key=si_name_key)
        old_list = sorted(inv_old.get(key,[]),key=si_name_key)
        new_list = sorted(inv_new.get(key,[]),key=si_name_key)
        assert res_list,'no res_list.get(%s)' % (key)
        # if new_list: print(key,dump(new_list))
        if new_list:
            assert new_list == res_list,'key %s\nnew:\n%s\nres:\n%s' % (
                key,dump(new_list),dump(res_list))
        else:
            assert old_list == res_list,'key %s\nold:\n%s\nres:\n%s' % (
                key,dump(old_list),dump(res_list))
    
    # Part 3: Test that result_d == uninvert(invert(result_d)).
    # A.  They must have the same keys.
    unv_res = uninvert(inv_res)
    assert sorted(list(result_d.keys())) == sorted(list(unv_res.keys()))

    # B. The values of for each key must match after being sorted.
    def si_stroke_key(si): return si.stroke or ''
        
    for key in sorted(result_d.keys()):
        res_list = sorted(result_d.get(key,[]),key=si_stroke_key)
        unv_list = sorted( unv_res.get(key,[]),key=si_stroke_key)
        assert res_list == unv_list,'key %s\nres:\n%s\nunv:\n%s' % (
            key,dump(res_list),dump(unv_list))
   
#@+node:ekr.20120203153754.10032: *5* @test KeyStroke
ks = c.k.KeyStroke

@others

a1 = ks('a')
a2 = ks('a')
b1 = ks('b')
assert a1 == a2
d = {}
d[a1] = a1.s
d[a2] = a2.s
d[b1] = b1.s

for key in sorted(d):
    print(key,d.get(key))
#@+node:ekr.20120205022040.17748: *5* @test g.TypedDict
d = g.TypedDictOfLists('ks',type('s'),type(9))
d.add('a',1)
d.add('a',2)
d.add('b',3)

print(d)
for s in sorted(d.keys()):
    print(s,d.get(s,[]))

print('after replace...')
d.replace('a',[8,9,10])

for s in sorted(d.keys()):
    print(s,d.get(s,[]))
#@+node:ekr.20120215062153.14233: *3* @mark-for-unit-tests
#@+node:ekr.20080412053100.5: *4* @settings
#@+node:ekr.20080412053100.4: *5* @bool fixedWindow = False
#@+node:ekr.20100902154544.5872: *5* @bool enable-abbreviations = True
#@+node:ekr.20111123042248.12701: *5* @enabled-plugins
# Leo loads plugins in the order they appear here.

# Highly-recommended plugins:

plugins_menu.py
free_layout.py # needs to be early
viewrendered.py
mod_scripting.py
bigdash.py
#@+node:ekr.20111031081007.9985: *5* @shortcuts
run-selected-unit-tests-externally = Alt-4 # Standard binding, unchanged.
run-marked-unit-tests-externally = Alt-5
run-marked-unit-tests-locally = Alt-6
#@+node:ekr.20120322073519.10401: ** b1
#@+node:ekr.20120318110848.9734: *3* Added import-org-mode script
#@+node:ekr.20120318110848.9735: *4* import-org-mode (command, not used)
class ImportOrgMode:
    @others

def importOrgMode (self,event):
    c = self.c
    self.ImportOrgMode(c).go(c.p)
    c.bodyWantsFocus()

if False and g.app.inScript:
    print('='*40)
    ImportOrgMode(c).test()
    print('done')
#@+node:ekr.20120318110848.9736: *5* ctor
def __init__ (self,c):
    
    self.c = c
#@+node:ekr.20120318110848.9737: *5* go
def go (self,p):
    
    '''Prompt for a file and pass the contents to scan().'''
#@+node:ekr.20120318110848.9738: *5* scan
def scan (self,fn,p,s):

    self.c = c
    root = p.insertAsLastChild()
    root.h = fn
    level,stack = 0,[root]
    body = ['@others\n']
    
    for s in g.splitLines(s):
        g.trace(repr(s))
        if s.startswith('*'):
            i,level = 0,0
            while s[i] == '*':
                i += 1
                level += 1
            if level > len(stack):
                g.trace('bad level',repr(s))
                last = None
            elif level == len(stack):
                last = stack[-1]
                last.b = ''.join(body)
            else:
                last = stack[-1]
                last.b = ''.join(body)
                stack = stack[:level]
            parent = stack[-1]
            p = parent.insertAsLastChild()
            p.h = s.strip()
            stack.append(p)
            body = []
        else:
            body.append(s)
            
    # Finish any trailing lines.
    if body:
        parent = stack[-1]
        parent.b = ''.join(body)
        
    root.contract()
    c.redraw(root)
#@+node:ekr.20120318110848.9739: *5* test
def test (self):
    
    s = '''
* A1
    a1.1
    a1.2
** B11
** B12
b12.1
*** C121
c121.1
    c121.2
c121.3
* A2
a2.1
** B21
*** C211
c211.1
*** C212
** B22
    b22.1
b22.1
* A3
* A4
a4.1
* A5
** B51
*** C511
**** D5111
***** E51111
** B52
*** C521
c521.1
'''

    tag = 'test-import-org-mode'
    p = g.findNodeAnywhere(c,tag)
    s = g.adjustTripleString(s,-4)
    if p:
        try:
            self.scan('test-file',p,s)
        except Exception:
            c.redraw(p)
    else:
        print('not found: %s' % tag)
#@+node:ekr.20120318110848.9740: *4* @@button import-org-mode
'''Import each file in the files list after the presently selected node.'''


files = (
    r'c:\Users\edreamleo\test\import-org-mode.txt',
    r'c:\Users\edreamleo\test\import-org-mode.txt',
)

@others

for fn in files:
    try:
        root = c.p.copy()
        f = open(fn)
        s = f.read()
        scan(c,fn,s)
        c.selectPosition(root)
    except IOError:
        print('can not open %s' % fn)
#@+node:ekr.20120318110848.9741: *5* scan
def scan (c,fn,s):

    last = root = c.p.insertAsLastChild()
    last.h = g.shortFileName(fn)
    level,stack = 0,[root]
    body = ['@others\n']
    
    for s in g.splitLines(s):
        if s.startswith('*'):
            i,level = 0,0
            while s[i] == '*':
                i += 1 ; level += 1
            if level > len(stack):
                g.trace('bad level',repr(s))
            elif level == len(stack):
                last.b = ''.join(body)
            else:
                last.b = ''.join(body)
                stack = stack[:level]
            parent = stack[-1]
            last = parent.insertAsLastChild()
            last.h = s.strip()
            stack.append(last)
            body = []
        else:
            body.append(s)
            
    # Finish any trailing lines.
    if body:
        last.b = ''.join(body)
        
    root.contract()
    c.redraw(root)
#@+node:ekr.20120318110848.9742: *4* test-import-org-mode
#@+node:ekr.20120318110848.9747: *3* Code for displaying a function call hierarchy in Leo
From Brian Theado

The other day I stumbled across Ville's code in scripts.leo which displays the
output of python's trace module in a leo outline. The output of the trace module
is not very friendly and I didn't find the result very usable. I was inspired to
write some code to translate the output so the tree of function calls is
displayed via Leo headlines. Thanks to Ville for sharing that code. I never
would have figure this out without that starting point.

Just copy (Ctrl-Shift-V) the child outline into a leo outline and hit ctrl-b on
the "call tree" node. The execution tree of the 'scroll-outline-up-line'
minibuffer command will be displayed to stdout and also as a tree of leo
headlines.
#@+node:ekr.20120318110848.9748: *4* call tree
import trace

@language python
@others

# http://docs.python.org/library/trace.html for documentation
# on the trace module
tracer = trace.Trace(countcallers=1)

# Trace a minibuffer command.

# Any function call will work. Leo's minibuffer commands are easily discoverable
# via tab completion and the 'print-commands' command.

#tracer.runfunc(c.executeMinibufferCommand, 'goto-prev-node')
tracer.runfunc(c.executeMinibufferCommand, 'scroll-outline-up-line')

top = p.insertAsLastChild().copy()
top.h = 'trace session'
displayCalltree(top, tracer.results().callers.keys())
c.redraw()
#@+node:ekr.20120318110848.9749: *5* displayCalltree
def displayCalltree(p, callinfo):
   '''
   Converts the function call hierarchy in 'callinfo' into a tree of function
   calls.  The function call tree is displayed to stdout as indented text
   and is inserted as a tree of leo nodes rooted at the given position 'p'
   '''
   callers = [k[0] for k in callinfo]
   callees = [k[1] for k in callinfo]

   # The first set of children will be those that don't have any callers
   # listed in callinfo
   toplevels = list(set(callers) - set(callees))
   positions = {}
   path = []

   # Depth-first traversal of the call hierarchy represented by 'callinfo'
   # 'levels' is a stack which grows during descend and shrinks
   # during ascend.  Each element of 'levels' is a list of unprocessed
   # siblings of each other
   levels = [toplevels]
   while len(levels) > 0:
       while len(levels[-1]) > 0:
           # Process the first element in the 'deepest' (i.e. last) list of siblings
           cur = levels[-1][0]
           levels[-1] = levels[-1][1:]
           indent = " " * 4 * (len(levels)-1)
           if cur not in path:
               if cur in positions.keys():
                   # Function already seen, so make a clone
                   clone = positions[cur].clone()
                   clone.moveToLastChildOf(p)
                   print (indent + "%s %s ..." % cur[1:])
               else:
                   # Haven't seen this function, so insert a new headline
                   p = p.insertAsLastChild().copy()
                   p.h = "%s %s" % cur[1:]
                   print (indent + p.h)

                   # Remember the position so it can be cloned if seen again
                   positions[cur] = p

                   # Find all callees of this function and descend
                   levels.append([c[1] for c in callinfo if c[0] == cur])
                   path.append(cur)
           else:
               r = p.insertAsLastChild().copy()
               r.h = "(recursive call) %s %s" % (cur[1], cur[2])
               print(indent + r.h + "...")

       # Ascend back up one level
       path = path[0:-1]
       p = p.parent()
       levels = levels[0:-1]
#@+node:ekr.20120318110848.9750: *5* trace session
#@+node:ekr.20120314064059.9737: *3* Use ctrl-click to open url's
- (Done) Added the following commands:
    
    - ctrl-click-icon
    - ctrl-click-at-cursor
    - open-url
    - open-url-under-cursor
    
- (Done) Double-click *only* edits headline.
- (Done) Only look at first line of the body in @url nodes.
- (Done) Ctrl-click in body allows spaces in url's.

#@+node:ekr.20120322073519.10402: ** final
#@+node:ekr.20120327163022.9737: *3* Bugs
#@+node:ekr.20120322073519.9785: *4* Fixed crasher in flattenOutline
Traceback (most recent call last):
  File "c:\leo.repo\trunk\leo\core\leoCommands.py", line 553, in doCommand
    val = command(event)
  File "c:\leo.repo\trunk\leo\core\leoCommands.py", line 2120, in flattenOutline
    c.importCommands.flattenOutline(fileName)
  File "c:\leo.repo\trunk\leo\core\leoImport.py", line 479, in flattenOutline
    theFile.write(s)
TypeError: must be str, not bytes
#@+node:ekr.20031218072017.1147: *5* ic.flattenOutline
def flattenOutline (self,fileName):

    c = self.c ; nl = g.u(self.output_newline)
    p = c.currentVnode()
    if not p: return
    self.setEncoding()
    firstLevel = p.level()

    try:
        theFile = open(fileName,'wb')
            # Fix crasher: open in 'wb' mode.
    except IOError:
        g.es("can not open",fileName,color="blue")
        c.testManager.fail()
        return

    for p in p.self_and_subtree():
        head = p.moreHead(firstLevel)
        s = head + nl
        if g.isPython3:
            s = g.toEncodedString(s,encoding=self.encoding,reportErrors=True)
        theFile.write(s)
        body = p.moreBody() # Inserts escapes.
        if len(body) > 0:
            s = g.toEncodedString(body + nl,self.encoding,reportErrors=True)
            theFile.write(s)
    theFile.close()
#@+node:ekr.20120323110755.9687: *4* Fix viewrendered crash
Traceback (most recent call last):
  File "c:\leo.repo\trunk\leo\core\leoPlugins.py", line 337, in callTagHandler
    result = handler(tag,keywords)
  File "c:\leo.repo\trunk\leo\plugins\viewrendered.py", line 560, in update
    f(s,keywords)
  File "c:\leo.repo\trunk\leo\plugins\viewrendered.py", line 655, in update_graphics_script
    pc.gs = QtGui.QGraphicsScene(pc.splitter)
AttributeError: 'ViewRenderedController' object has no attribute 'splitter'
#@+node:ekr.20120323124339.9722: *4* Fixed(mostly)scrolling problem with multiple editors
@language python
@language rest
Selecting body editor with clicks doesn't save/restore visual ivars.
The solution would be to create a new onClick event handler...
#@+node:ekr.20120212060348.10374: *5*  << global switches >>
trace_startup = False
    # These traces use print instead of g.trace so that
    # the traces can add class info the method name.

new_modes = False
    # True: use ModeController and ModeInfo classes.
if new_modes: print('***** new_modes')

new_keys = False
    # This project hardly seems urgent.
    # True: Qt input methods produce a **user setting**, not a stroke.
if new_keys: print('***** new_keys')

# Debugging options.
restore_selection_range = False

# Traces & disabling of scrolling problems.
no_scroll = False
    # True: disable all calls to w.setYScrollPosition.
no_see = False
    # True: disable all calls to w.see and w.seeInsertPoint.

trace_scroll = False
    # Trace calls to get/setYScrollPosition
trace_see = False
    # Trace calls to see and setInsertPoint.

# Switches to trace the garbage collector.
trace_gc = False           
trace_gc_calls = False    
trace_gc_calls = False 
trace_gc_verbose = False
trace_gc_inited = False

trace_masterCommand = False
trace_masterKeyHandler = False
trace_masterKeyHandlerGC = False
trace_minibuffer = False
trace_modes = False

enableDB = True
    # Don't even think about eliminating this constant:
    # it is needed for debugging.
    
# These print statements have been moved to writeWaitingLog.
# This allows for better --silent operation.
if 0:
    print('*** isPython3: %s' % isPython3)
    if not enableDB:
        print('** leoGlobals.py: caching disabled')
#@+node:ekr.20120324124808.9839: *5* Not changed
#@+node:ekr.20060528170438: *6* cycleEditorFocus (leoBody)
def cycleEditorFocus (self,event=None):

    '''Cycle keyboard focus between the body text editors.'''

    trace = False and not g.unitTesting
    c = self.c ; d = self.editorWidgets
    w = c.frame.body.bodyCtrl
    values = list(d.values())
    if len(values) > 1:
        i = values.index(w) + 1
        if i == len(values): i = 0
        w2 = list(d.values())[i]
        assert(w!=w2)
        self.selectEditor(w2)
        c.frame.body.bodyCtrl = w2
#@+node:ekr.20110605121601.18223: *6* Event handlers (qtBody)
#@+node:ekr.20110930174206.15472: *7* onFocusIn (qtBody)
def onFocusIn (self,obj):

    '''Handle a focus-in event in the body pane.'''

    trace = False and not g.unitTesting
    c = self.c
    
    if trace: g.trace(str(obj.objectName()))

    # 2010/08/01: Update the history only on focus in events.
    # 2011/04/02: Update history only in leoframe.tree.select.
    # c.nodeHistory.update(c.p)
    
    if obj.objectName() == 'richTextEdit':
        wrapper = hasattr(obj,'leo_wrapper') and obj.leo_wrapper
        if wrapper and wrapper != self.bodyCtrl:
            self.selectEditor(wrapper)
        self.onFocusColorHelper('focus-in',obj)
        obj.setReadOnly(False)
        obj.setFocus() # Weird, but apparently necessary.
#@+node:ekr.20110930174206.15473: *7* onFocusOut (qtBody)
def onFocusOut (self,obj):

    '''Handle a focus-out event in the body pane.'''
    
    trace = False and not g.unitTesting

    if trace: g.trace(str(obj.objectName()))
    
    # Apparently benign.
    if obj.objectName() == 'richTextEdit':
        self.onFocusColorHelper('focus-out',obj)
        obj.setReadOnly(True)
    
#@+node:ekr.20110605121601.18224: *7* onFocusColorHelper (qtBody)
badFocusColors = []

def onFocusColorHelper(self,kind,obj):

    trace = False and not g.unitTesting
    
    c = self.c ; w = c.frame.body.bodyCtrl
    
    if trace: g.trace(kind)
    
    if kind == 'focus-in':
        # if trace: g.trace('%9s' % (kind),'calling c.k.showStateColors()')
        c.k.showStateColors(inOutline=False,w=self.widget)
    else:
        bg = self.unselectedBackgroundColor
        fg = self.unselectedForegroundColor
        c.frame.body.setEditorColors(bg,fg)

    w.widget.ensureCursorVisible()
        # 2011/10/02: Fix cursor-movement bug.
#@+node:ekr.20111002125540.7021: *6* get/setYScrollPosition (LeoQTextBrowser)
def getYScrollPosition(self):
    
    trace = False and g.trace_scroll and not g.unitTesting

    w = self
    sb = w.verticalScrollBar()
    pos = sb.sliderPosition()
    if trace: g.trace(pos)
    return pos

def setYScrollPosition(self,pos):
    
    trace = g.trace_scroll and not g.unitTesting
    w = self

    if g.no_scroll:
        return
    elif pos is None:
        if trace: g.trace('None')
    else:
        if trace: g.trace(pos,g.callers())
        sb = w.verticalScrollBar()
        sb.setSliderPosition(pos)
#@+node:ekr.20031218072017.4037: *6* setSelectionAreas (leoBody)
def setSelectionAreas (self,before,sel,after):

    """Replace the body text by before + sel + after and
    set the selection so that the sel text is selected."""

    body = self ; w = body.bodyCtrl
    
    # 2012/02/05: save/restore Yscroll position.
    pos = w.getYScrollPosition()
    s = w.getAllText()
    before = before or ''
    sel = sel or ''
    after = after or ''
    w.delete(0,len(s))
    w.insert(0,before+sel+after)
    i = len(before)
    j = max(i,len(before)+len(sel)-1)
    # g.trace(i,j,repr(sel))
    w.setSelectionRange(i,j,insert=j)
    w.setYScrollPosition(pos)
    return i,j
#@+node:ekr.20110605121601.18098: *6* setYScrollPosition (leoQTextEditWidget)
def setYScrollPosition(self,pos):
    
    trace = False and g.trace_scroll and not g.unitTesting
    w = self.widget
    
    if g.no_scroll:
        return
    elif pos is None:
        if trace: g.trace('None')
    else:
        if trace: g.trace(pos,g.callers())
        sb = w.verticalScrollBar()
        sb.setSliderPosition(pos)
#@+node:ekr.20031218072017.3344: *6* v.__init
# 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.
    self._headString = g.u('newHeadline')
    self._bodyString = g.u('')

    # 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.
#@+node:ekr.20100303074003.5638: *6* v.saveCursorAndScroll
def saveCursorAndScroll(self,w):
    
    trace = (False or g.trace_scroll) and not g.unitTesting

    v = self
    if not w: return
    
    try:
        v.scrollBarSpot = w.getYScrollPosition()
        v.insertSpot = w.getInsertPoint()
        if trace: g.trace(v.scrollBarSpot,v.insertSpot)
    except AttributeError:
        # 2011/03/21: w may not support the high-level interface.
        pass
#@+node:ekr.20120324124808.9835: *5* Changed
@language python
@language rest

- Removed insert=None,new_p=None args from all versions of setAllText.
  These are entirely misguided, and may have contributed to scrolling problems.
  
  setAllText now *only* sets text, nothing else!

- All calls to leoMoveCursorHelper are follwed by code that updates
  v.insertSpot, v.selectionStart and v.selectionLength.
  
- v.restoreCursorAndScroll now *carefully* restores selection
  based on v.insertSpot, v.selectionStart and v.selectionLength.
  It also restores the scrollbar using v.scrollBarSpot.
  
- < < unselect the old node > > (selectHelper) now *only*
  sets v.scrollBarSpot.
  
#@+node:ekr.20050920084036.136: *6* exchangePointMark
def exchangePointMark (self,event):

    '''Exchange the point (insert point) with the mark (the other end of the selected text).'''

    trace = False and not g.unitTesting
    c = self.c
    w = self.editWidget(event)
    if not w: return

    if hasattr(w,'leoMoveCursorHelper'):
        w.leoMoveCursorHelper(kind='exchange',extend=False)
        # w.seeInsertPoint()
        # c.frame.updateStatusLine()
        # w.rememberSelectionAndScroll()
    else:
        c.widgetWantsFocusNow(w)
        i,j = w.getSelectionRange(sort=False)
        if i == j: return

        ins = w.getInsertPoint()
        ins = g.choose(ins==i,j,i)
        w.setInsertPoint(ins)
        w.setSelectionRange(i,j,insert=None)
#@+node:ekr.20090530181848.6035: *6* movePageHelper
def movePageHelper(self,event,kind,extend): # kind in back/forward.

    '''Move the cursor up/down one page, possibly extending the selection.'''

    trace = False and not g.unitTesting
    c = self.c ; w = self.editWidget(event)
    if not w: return

    linesPerPage = 15 # To do.

    if hasattr(w,'leoMoveCursorHelper'):
        extend = extend or self.extendMode
        w.leoMoveCursorHelper(
            kind=g.choose(kind=='forward','page-down','page-up'),
            extend=extend,linesPerPage=linesPerPage)
        # w.seeInsertPoint()
        # c.frame.updateStatusLine()
        # w.rememberSelectionAndScroll()
    else:
        ins = w.getInsertPoint()
        s = w.getAllText()
        lines = g.splitLines(s)
        row,col = g.convertPythonIndexToRowCol(s,ins)
        row2 = g.choose(kind=='back',
            max(0,row-linesPerPage),
            min(row+linesPerPage,len(lines)-1))
        if row == row2: return
        spot = g.convertRowColToPythonIndex(s,row2,col,lines=lines)
        if trace: g.trace('spot',spot,'row2',row2)
        self.extendHelper(w,extend,spot,upOrDown=True)
#@+node:ekr.20100109094541.6227: *6* moveToBufferHelper
def moveToBufferHelper (self,event,spot,extend):

    trace = False and not g.unitTesting
    c = self.c ; w = self.editWidget(event)
    if not w: return

    if hasattr(w,'leoMoveCursorHelper'):
        extend = extend or self.extendMode
        w.leoMoveCursorHelper(kind=spot,extend=extend)
        # w.seeInsertPoint()
        # c.frame.updateStatusLine()
        # w.rememberSelectionAndScroll()
    else:
        if spot == 'home':
            self.moveToHelper(event,0,extend=extend)
        elif spot == 'end':
            s = w.getAllText()
            self.moveToHelper(event,len(s),extend=extend)
        else:
            g.trace('can not happen: bad spot',spot)
#@+node:ekr.20100109094541.6228: *6* moveToCharacterHelper
def moveToCharacterHelper (self,event,spot,extend):

    trace = False and not g.unitTesting
    c = self.c ; w = self.editWidget(event)
    if not w: return

    if hasattr(w,'leoMoveCursorHelper'):
        extend = extend or self.extendMode
        w.leoMoveCursorHelper(kind=spot,extend=extend)
        # w.seeInsertPoint()
        # c.frame.updateStatusLine()
        # w.rememberSelectionAndScroll()
    else:
        i = w.getInsertPoint()
        if spot == 'left':
            i=max(0,i-1)
            self.moveToHelper(event,i,extend=extend)
        elif spot == 'right':
            i = min(i+1,len(w.getAllText()))
            self.moveToHelper(event,i,extend=extend)
        else:
            g.trace('can not happen: bad spot: %s' % spot)
#@+node:ekr.20060113105246.1: *6* moveUpOrDownHelper
def moveUpOrDownHelper (self,event,direction,extend):

    trace = False and not g.unitTesting
    c = self.c ; w = self.editWidget(event)
    if not w: return

    ins = w.getInsertPoint()
    s = w.getAllText()
    w.seeInsertPoint()

    if hasattr(w,'leoMoveCursorHelper'):
        extend = extend or self.extendMode
        w.leoMoveCursorHelper(kind=direction,extend=extend)
        # w.seeInsertPoint()
        # c.frame.updateStatusLine()
        # w.rememberSelectionAndScroll()
    else:
        # Find the start of the next/prev line.
        row,col = g.convertPythonIndexToRowCol(s,ins)
        if trace:
            gui_ins = w.toGuiIndex(ins)
            bbox = w.bbox(gui_ins)
            if bbox:
                x,y,width,height = bbox
                # bbox: x,y,width,height;  dlineinfo: x,y,width,height,offset
                g.trace('gui_ins',gui_ins,'dlineinfo',w.dlineinfo(gui_ins),'bbox',bbox)
                g.trace('ins',ins,'row',row,'col',col,
                    'event.x',event and event.x,'event.y',event and event.y)
                g.trace('subtracting line height',w.index('@%s,%s' % (x,y-height)))
                g.trace('adding      line height',w.index('@%s,%s' % (x,y+height)))
        i,j = g.getLine(s,ins)
        if direction == 'down':
            i2,j2 = g.getLine(s,j)
        else:
            i2,j2 = g.getLine(s,i-1)

        # The spot is the start of the line plus the column index.
        n = max(0,j2-i2-1) # The length of the new line.
        col2 = min(col,n)
        spot = i2 + col2
        if trace: g.trace('spot',spot,'n',n,'col',col,'line',repr(s[i2:j2]))

        self.extendHelper(w,extend,spot,upOrDown=True)
#@+node:ekr.20100109094541.6231: *6* moveWithinLineHelper
def moveWithinLineHelper (self,event,spot,extend):

    trace = False and not g.unitTesting
    c = self.c ; w = self.editWidget(event)
    if not w: return

    # g.trace(hasattr(w,'leoMoveCursorHelper'))
    
    # Bug fix: 2012/02/28: don't use the Qt end-line logic:
    # it apparently does not work for wrapped lines.
    if hasattr(w,'leoMoveCursorHelper') and spot != 'end-line':
        extend = extend or self.extendMode
        w.leoMoveCursorHelper(kind=spot,extend=extend)
        # w.seeInsertPoint()
        # c.frame.updateStatusLine()
        # w.rememberSelectionAndScroll()
    else:
        s = w.getAllText()
        ins = w.getInsertPoint()
        i,j = g.getLine(s,ins)
        if spot == 'start-line':
            self.moveToHelper(event,i,extend=extend)
        elif spot == 'end-line':
            # Bug fix: 2011/11/13: Significant in external tests.
            if g.match(s,j-1,'\n'): j -= 1
            self.moveToHelper(event,j,extend=extend)
        else:
            g.trace('can not happen: bad spot: %s' % spot)
#@+node:ekr.20110605121601.18077: *6* leoMoveCursorHelper & helper (leoQTextEditWidget)
def leoMoveCursorHelper (self,kind,extend=False,linesPerPage=15):

    '''Move the cursor in a QTextEdit.'''

    trace = False and not g.unitTesting
    verbose = True
    w = self.widget
    if trace:
        g.trace(kind,'extend',extend)
        if verbose:
            g.trace(len(w.toPlainText()))

    tc = QtGui.QTextCursor
    d = {
        'exchange': True, # Dummy.
        'down':tc.Down,'end':tc.End,'end-line':tc.EndOfLine,
        'home':tc.Start,'left':tc.Left,'page-down':tc.Down,
        'page-up':tc.Up,'right':tc.Right,'start-line':tc.StartOfLine,
        'up':tc.Up,
    }
    kind = kind.lower()
    op = d.get(kind)
    mode = g.choose(extend,tc.KeepAnchor,tc.MoveAnchor)

    if not op:
        return g.trace('can not happen: bad kind: %s' % kind)

    if kind in ('page-down','page-up'):
        self.pageUpDown(op, mode)
    elif kind == 'exchange': # exchange-point-and-mark
        cursor = w.textCursor()
        anchor = cursor.anchor()
        pos = cursor.position()
        cursor.setPosition(pos,tc.MoveAnchor)
        cursor.setPosition(anchor,tc.KeepAnchor)
        w.setTextCursor(cursor)
    else:
        if not extend:
            # Fix an annoyance. Make sure to clear the selection.
            cursor = w.textCursor()
            cursor.clearSelection()
            w.setTextCursor(cursor)
        w.moveCursor(op,mode)
        
    # 2012/03/25.  Add this common code.
    self.seeInsertPoint()
    self.rememberSelectionAndScroll()
    self.c.frame.updateStatusLine()
#@+node:btheado.20120129145543.8180: *7* pageUpDown
def pageUpDown (self, op, moveMode):

    '''The QTextEdit PageUp/PageDown functionality seems to be "baked-in"
       and not externally accessible.  Since Leo has its own keyhandling
       functionality, this code emulates the QTextEdit paging.  This is
       a straight port of the C++ code found in the pageUpDown method
       of gui/widgets/qtextedit.cpp'''

    control = self.widget
    cursor = control.textCursor()
    moved = False
    lastY = control.cursorRect(cursor).top()
    distance = 0
    # move using movePosition to keep the cursor's x
    while True:
        y = control.cursorRect(cursor).top()
        distance += abs(y - lastY)
        lastY = y
        moved = cursor.movePosition(op, moveMode)
        if (not moved or distance >= control.height()):
            break
    tc = QtGui.QTextCursor
    sb = control.verticalScrollBar()
    if moved:
        if (op == tc.Up):
            cursor.movePosition(tc.Down, moveMode)
            sb.triggerAction(QtGui.QAbstractSlider.SliderPageStepSub)
        else:
            cursor.movePosition(tc.Up, moveMode)
            sb.triggerAction(QtGui.QAbstractSlider.SliderPageStepAdd)
    control.setTextCursor(cursor)
#@+node:ekr.20110605121601.18209: *6* deactivateEditors (qtBody)
def deactivateEditors(self,wrapper):

    '''Deactivate all editors except wrapper's editor.'''

    trace = False and not g.unitTesting
    d = self.editorWidgets

    # Don't capture ivars here! assignPositionToEditor keeps them up-to-date. (??)
    for key in d:
        wrapper2 = d.get(key)
        w2 = wrapper2.widget
        if hasattr(w2,'leo_active'):
            active = w2.leo_active
        else:
            active = True
        if wrapper2 != wrapper and active:
            w2.leo_active = False
            self.unselectLabel(wrapper2)
            if trace: g.trace(w2)
            self.onFocusOut(w2)
#@+node:ekr.20120325032957.9730: *6* rememberSelectionAndScroll (leoQtBaseTextWidget)
def rememberSelectionAndScroll(self):

    trace = (False or g.trace_scroll) and not g.unitTesting

    w = self
    v = self.c.p.v # Always accurate.
    v.insertSpot = ins = w.getInsertPoint()
    i,j = w.getSelectionRange()
    if i > j: i,j = j,i
    assert(i<=j)
    v.selectionStart = i
    v.selectionLength = j-i
    v.scrollBarSpot = spot = w.getYScrollPosition()
    
    if trace: g.trace(id(v),id(w),i,j,ins,spot,v.h)
#@+node:ekr.20110605121601.18044: *6* selectAllText (leoQtBaseTextWidget)
def selectAllText(self,insert=None):

    w = self.widget
    w.selectAll()
    # if insert is not None:
        # self.setInsertPoint(insert)
    # Bug fix: 2012/03/25.
    self.setSelectionRange(0,'end',insert=insert)
   
#@+node:ekr.20110605121601.18203: *6* selectEditorHelper (qtBody)
def selectEditorHelper (self,wrapper):

    trace = False and not g.unitTesting
    c = self.c ; cc = c.chapterController
    d = self.editorWidgets
    assert isinstance(wrapper,leoQTextEditWidget),wrapper
    w = wrapper.widget
    assert isinstance(w,QtGui.QTextEdit),w

    if not w.leo_p:
        g.trace('no w.leo_p') 
        return 'break'

    # The actual switch.
    self.deactivateEditors(wrapper)
    self.recolorWidget (w.leo_p,wrapper) # switches colorizers.
    # g.trace('c.frame.body',c.frame.body)
    # g.trace('c.frame.body.bodyCtrl',c.frame.body.bodyCtrl)
    # g.trace('wrapper',wrapper)
    
    c.frame.body.bodyCtrl = wrapper
    c.frame.body.widget = wrapper # Major bug fix: 2011/04/06
    w.leo_active = True

    self.switchToChapter(wrapper)
    self.selectLabel(wrapper)

    if not self.ensurePositionExists(w):
        return g.trace('***** no position editor!')
    if not (hasattr(w,'leo_p') and w.leo_p):
        return g.trace('***** no w.leo_p',w)
        
    p = w.leo_p
    assert p,p

    if trace: g.trace('wrapper %s old %s p %s' % (
        id(wrapper),c.p.h,p.h))

    c.expandAllAncestors(p)
    c.selectPosition(p)
        # Calls assignPositionToEditor.
        # Calls p.v.restoreCursorAndScroll.
    c.redraw()
    c.recolor_now()
    c.bodyWantsFocus()
#@+node:ekr.20070423101911: *6* selectHelper (leoTree) (changed 4.10)
# Do **not** try to "optimize" this by returning if p==c.p.
# 2011/11/06: *event handlers* are called only if p != c.p.

def selectHelper (self,p,scroll):

    trace = False and not g.unitTesting
    verbose = False
    c = self.c ; frame = c.frame
    body = w = frame.body.bodyCtrl
    if not w: return # Defensive.

    old_p = c.p
    
    call_event_handlers = p != old_p

    if p:
        # 2009/10/10: selecting a foreign position
        # will not be pretty.
        assert p.v.context == c
    else:
        # Do *not* test c.positionExists(p) here.
        # We may be in the process of changing roots.
        return None # Not an error.

    # if trace and (verbose or call_event_handlers):
        # g.trace(p and p.h,g.callers())
            
    if call_event_handlers:
        unselect = not g.doHook("unselect1",c=c,new_p=p,old_p=old_p,new_v=p,old_v=old_p)
    else:
        unselect = True

    if unselect:
        << unselect the old node >>
        
    if call_event_handlers:
        g.doHook("unselect2",c=c,new_p=p,old_p=old_p,new_v=p,old_v=old_p)
        
    if call_event_handlers:
        if call_event_handlers:
            select = not g.doHook("unselect1",c=c,new_p=p,old_p=old_p,new_v=p,old_v=old_p)
        else:
            select = True
        if select:
            << select the new node >>
            c.nodeHistory.update(p) # Remember this position.
    else:
        if not g.doHook("select1",c=c,new_p=p,old_p=old_p,new_v=p,old_v=old_p):
            << select the new node >>
            c.nodeHistory.update(p) # Remember this position.
        
    c.setCurrentPosition(p)
    << set the current node >>
    p.restoreCursorAndScroll(w)
        # Was in setBodyTextAfterSelect (in <select the new node>)
    c.frame.body.assignPositionToEditor(p) # New in Leo 4.4.1.
    c.frame.updateStatusLine() # New in Leo 4.4.1.

    # if trace and (verbose or call_event_handlers):
        # g.trace('**** after old: %s new %s' % (
            # old_p and len(old_p.b),len(p.b)))

    # what UNL.py used to do
    c.frame.clearStatusLine()
    c.frame.putStatusLine(p.get_UNL())

    if call_event_handlers: # 2011/11/06
        g.doHook("select2",c=c,new_p=p,old_p=old_p,new_v=p,old_v=old_p)
        g.doHook("select3",c=c,new_p=p,old_p=old_p,new_v=p,old_v=old_p)
#@+node:ekr.20120325072403.7771: *7* << unselect the old node >> (selectHelper)
# Remember the position of the scrollbar *before* making any changes.
# This does not work if we are switching body editors!
    # if old_p:
        # old_p.v.scrollBarSpot = yview = body.getYScrollPosition() if body else None
        # if trace: g.trace('old scroll: %3s insert: %3s %s' % (
            # yview,old_p.v.insertSpot,old_p.h))
        
# Remember the selection range and insert point.
# This does not work if we are switching body editors!
# Instead, w.setInsertPoint and w.setSelectionRangeHelper should do the saving.

    # if old_p and body:
        # old_p.v.insertSpot = ins = body.getInsertPoint()
        # i,j = body.getSelectionRange()
        # assert(i<=j)
        # old_p.v.selectionStart = i
        # old_p.v.selectionLength = j-i
        # if trace: g.trace(i,j,ins)
    
# New in Leo 4.10: the code sets v.insertSpot as soon as it changes (not here).

if old_p != p:
    self.endEditLabel() # sets editPosition = None
    self.setUnselectedLabelState(old_p)
#@+node:ekr.20040803072955.133: *7* << set the current node >> (selectHelper)
self.setSelectedLabelState(p)

frame.scanForTabWidth(p) #GS I believe this should also get into the select1 hook

# Was in ctor.
use_chapters = c.config.getBool('use_chapters')

if use_chapters:
    cc = c.chapterController
    theChapter = cc and cc.getSelectedChapter()
    if theChapter:
        theChapter.p = p.copy()
        # g.trace('tkTree',theChapter.name,'v',id(p.v),p.h)

c.treeFocusHelper() # 2010/12/14
c.undoer.onSelect(old_p,p)
#@+node:ekr.20120325072403.7767: *7* << select the new node >> (selectHelper)
# Bug fix: we must always set this, even if we never edit the node.
self.revertHeadline = p.h
frame.setWrap(p)
self.setBodyTextAfterSelect(p,old_p)
#@+node:ekr.20110605121601.18092: *6* setAllText (leoQTextEditWidget) & helper (changed 4.10)
def setAllText(self,s):

    '''Set the text of the widget.

    If insert is None, the insert point, selection range and scrollbars are initied.
    Otherwise, the scrollbars are preserved.'''

    trace = False and not g.unitTesting
    verbose = False
    c,w = self.c,self.widget
    colorizer = c.frame.body.colorizer
    highlighter = colorizer.highlighter
    colorer = highlighter.colorer

    # Set a hook for the colorer.
    colorer.initFlag = True

    if trace and verbose: t1 = g.getTime()

    try:
        self.changingText = True # Disable onTextChanged
        colorizer.changingText = True
        w.setReadOnly(False)
        w.setPlainText(s)
    finally:
        self.changingText = False
        colorizer.changingText = False

    if trace and verbose: g.trace(g.timeSince(t1))
    
#@+node:ekr.20090608081524.6109: *6* setBodyTextAfterSelect (changed 4.10)
def setBodyTextAfterSelect (self,p,old_p):
    
    # trace = g.trace_scroll and not g.unitTesting

    # Always do this.  Otherwise there can be problems with trailing newlines.
    c = self.c ; w = c.frame.body.bodyCtrl
    s = p.v.b # Guaranteed to be unicode.
    old_s = w.getAllText()

    if p and p == old_p and c.frame.body.colorizer.isSameColorState() and s == old_s:
        pass
    else:
        # w.setAllText destroys all color tags, so do a full recolor.
        w.setAllText(s)
        colorizer = c.frame.body.colorizer
        if hasattr(colorizer,'setHighlighter'):
            colorizer.setHighlighter(p)
        self.frame.body.recolor(p)

    # This is now done after c.p has been changed.
        # p.restoreCursorAndScroll(w)
#@+node:ekr.20110605121601.18095: *6* setInsertPoint (leoQTextEditWidget)
def setInsertPoint(self,i):
    
    trace = (False or g.trace_scroll) and not g.unitTesting

    w = self.widget
    s = w.toPlainText()
    i = self.toPythonIndex(i)
    i = max(0,min(i,len(s)))
    cursor = w.textCursor()
    cursor.setPosition(i)
    w.setTextCursor(cursor)
    
    # Remember the values for v.restoreCursorAndScroll.
    v = self.c.p.v # Always accurate.
    v.insertSpot = i
    v.selectionStart = i
    v.selectionLength = 0
    v.scrollBarSpot = spot = w.getYScrollPosition()
    if trace: g.trace(id(v),id(w),i,spot,v.h)

#@+node:ekr.20110605121601.18096: *6* setSelectionRangeHelper & helper (leoQTextEditWidget)
def setSelectionRangeHelper(self,i,j,insert=None):

    trace = (False or g.trace_scroll) and not g.unitTesting

    w = self.widget
    i = self.toPythonIndex(i)
    j = self.toPythonIndex(j)

    n = self.lengthHelper()
    i = max(0,min(i,n))
    j = max(0,min(j,n))
    if insert is None:
        ins = max(i,j)
    else:
        ins = self.toPythonIndex(insert)
        ins = max(0,min(ins,n))

    # 2010/02/02: Use only tc.setPosition here.
    # Using tc.movePosition doesn't work.
    tc = w.textCursor()
    if i == j:
        tc.setPosition(i)
    elif ins == j:
        # Put the insert point at j
        tc.setPosition(i)
        tc.setPosition(j,tc.KeepAnchor)
    else:
        # Put the insert point a i
        tc.setPosition(j)
        tc.setPosition(i,tc.KeepAnchor)
    w.setTextCursor(tc)

    # Remember the values for v.restoreCursorAndScroll.
    v = self.c.p.v # Always accurate.
    v.insertSpot = ins
    if i > j: i,j = j,i
    assert(i<=j)
    v.selectionStart = i
    v.selectionLength = j-i
    v.scrollBarSpot = spot = w.getYScrollPosition()
    if trace: g.trace(id(v),id(v),i,j,ins,spot,v.h,g.callers())
#@+node:ekr.20110605121601.18097: *7* lengthHelper
def lengthHelper(self):

    '''Return the length of the text.'''

    w = self.widget
    tc = w.textCursor()
    tc.movePosition(QtGui.QTextCursor.End)
    n = tc.position()
    return n

#@+node:ekr.20100303074003.5636: *6* v.restoreCursorAndScroll (changed 4.10)
# Called only by leoTree.selectHelper.

def restoreCursorAndScroll (self,w):
    
    trace = (False or g.trace_scroll) and not g.unitTesting
    v = self
    ins = v.insertSpot
    start,n = v.selectionStart,v.selectionLength
    spot = v.scrollBarSpot

    if g.restore_selection_range and start is not None and n is not None:
        sel = (start,start+n)
        w.setSelectionRange(start,start+n,insert=ins)
    else:
        sel = (None,None)
        if ins is not None:
            w.setInsertPoint(ins)
            
    if g.no_scroll:
        return

    # Override any changes to the scrollbar setting that might
    # have been done above by w.setSelectionRange or w.setInsertPoint.
    if spot is not None:
        w.setYScrollPosition(spot)
    v.scrollBarSpot = spot
        
    if trace: g.trace('v: %s w: %s sel: %s ins: %s scroll: %s %s' % (
        id(v), id(w),sel,ins,spot,v.h))
        
    # Never call w.see here.
#@+node:ekr.20120327062318.9731: *4* Ensure selected @test node is run
In earlier version of Leo if one runs test externally with the selected
position under @test node, that @test was executed with (run-marked-unit-tests-externally)

The fix was to the "important special case" in TM.findAllUnitTestNodes.
#@+node:ekr.20120327062318.9732: *4* Made sure the new load code loads plugins at most once
@language python
@language rest

new load code, double init. for free layout
http://groups.google.com/group/leo-editor/browse_thread/thread/dd16ac6dc1832eb2

bookmarks.py was the culprit. The code in onCreate must test to see if c.free_layout already exists.
#@+node:ekr.20120326061010.9726: *4* fixed problem with file:/// url's on Windows
@language rest

http://groups.google.com/group/leo-editor/browse_thread/thread/bb063866875a81c3#

In my installation, now on the latest revision ( r5195) I'm still
experiencing an issue with the '@url command' using 'File-URL' in a Windows
environement.

I'm able to create the Leo User documentation locally. - However, when I
try to read the documentation using the 'File-URL'

file:///D:/Branches/leo-editor/leo/doc/html/_build/html/leo_toc.html

I get the following message in the Leo-Log.

<log>

File 'D:\D:\Branches\leo-editor\leo\doc\html\_build\html\leo_toc.html' does not exist

</log>

However if I enter this URL directly into FF it is found and displayed properly.

EKR: Obviously, the 'D:\D:\' is the problem.

The fix is simply to special-case file:/// on Windows in g.computeFileUrl.
#@+node:ekr.20120327163022.9736: *4* fixed get_fn in viewrendered plugin
@language rest
groups.google.com/group/leo-editor/browse_thread/thread/bb063866875a81c3/6162e6108b09428e

The new code is much like g.computeFileUrl.
#@+node:ekr.20120327163022.9741: *4* Restored special case for run-selected-unit-tests
@language rest

Added code to findAllUnitTestNodes to look up the tree for @test & @suite nodes
if none have been found so far.  Only for the run-unit-tests-externally/locally.
#@+node:ekr.20120321174708.9744: *4* Fixed failing unit tests in distro
@nocolor-node

The @test at.readOneAtShadowNode retains @shadow links node
give fail1: test not set up properly.
The outline is then corrupted, causing other unit tests to fail.
The partial solution is not to call the undo command in the finally clause.
#@+node:ekr.20120327163022.9739: *3* Features
#@+node:ekr.20120324124808.9833: *4* Alt-Home & Alt-End collapse all possible nodes
#@+node:ekr.20120326061010.9728: *4* Added g.restore_selection_range
@nocolor-node

If off, only the insert point is restored.

It's kinda pointless to make this a user option.
#@+node:ekr.20120212060348.10374: *5*  << global switches >>
trace_startup = False
    # These traces use print instead of g.trace so that
    # the traces can add class info the method name.

new_modes = False
    # True: use ModeController and ModeInfo classes.
if new_modes: print('***** new_modes')

new_keys = False
    # This project hardly seems urgent.
    # True: Qt input methods produce a **user setting**, not a stroke.
if new_keys: print('***** new_keys')

# Debugging options.
restore_selection_range = False

# Traces & disabling of scrolling problems.
no_scroll = False
    # True: disable all calls to w.setYScrollPosition.
no_see = False
    # True: disable all calls to w.see and w.seeInsertPoint.

trace_scroll = False
    # Trace calls to get/setYScrollPosition
trace_see = False
    # Trace calls to see and setInsertPoint.

# Switches to trace the garbage collector.
trace_gc = False           
trace_gc_calls = False    
trace_gc_calls = False 
trace_gc_verbose = False
trace_gc_inited = False

trace_masterCommand = False
trace_masterKeyHandler = False
trace_masterKeyHandlerGC = False
trace_minibuffer = False
trace_modes = False

enableDB = True
    # Don't even think about eliminating this constant:
    # it is needed for debugging.
    
# These print statements have been moved to writeWaitingLog.
# This allows for better --silent operation.
if 0:
    print('*** isPython3: %s' % isPython3)
    if not enableDB:
        print('** leoGlobals.py: caching disabled')
#@+node:ekr.20100303074003.5636: *5* v.restoreCursorAndScroll (changed 4.10)
# Called only by leoTree.selectHelper.

def restoreCursorAndScroll (self,w):
    
    trace = (False or g.trace_scroll) and not g.unitTesting
    v = self
    ins = v.insertSpot
    start,n = v.selectionStart,v.selectionLength
    spot = v.scrollBarSpot

    if g.restore_selection_range and start is not None and n is not None:
        sel = (start,start+n)
        w.setSelectionRange(start,start+n,insert=ins)
    else:
        sel = (None,None)
        if ins is not None:
            w.setInsertPoint(ins)
            
    if g.no_scroll:
        return

    # Override any changes to the scrollbar setting that might
    # have been done above by w.setSelectionRange or w.setInsertPoint.
    if spot is not None:
        w.setYScrollPosition(spot)
    v.scrollBarSpot = spot
        
    if trace: g.trace('v: %s w: %s sel: %s ins: %s scroll: %s %s' % (
        id(v), id(w),sel,ins,spot,v.h))
        
    # Never call w.see here.
#@+node:ekr.20120327163022.9738: *3* Home page
#@+node:ekr.20111027103125.16545: *4* Added link to home page from the TOC
#@+node:ekr.20120229173025.20629: *4* Removed online-tutorial link
http://groups.google.com/group/leo-editor/browse_thread/thread/2157d8bfc0f381f1

es, choosing Help-->"Open Online Tutorial" tries to go to a page on
3dTree.com the site for which is no longer held.

Should the Quick Intro be brought back instead? 
#@+node:ekr.20111027103125.16539: *4* Added search box to Leo's home page
<div id="searchbox" style="">
<h3>Quick search</h3>

<form class="search" method="get" action="search.html">

<p class="searchtip" style="font-size: 90%"> Enter search terms or a module, class or function name. </p>
</div>
#@+node:ekr.20111020120612.15896: *4* Added link to glossary from Leo's home page
#@+node:ekr.20111027103125.16544: *4* Added screen shot to Leo's home page
#@+node:ekr.20110930174206.15470: *4* Brought screen shots up to date
#@+node:ekr.20120326061010.9727: *4* Scaled the screenshot on home page
http://groups.google.com/group/leo-editor/browse_thread/thread/ea3c29888d8ac92b

> - Added a full-sized screenshot at the bottom of the page.
>  I'm not sure whether this is a good idea.  What do you think?

Scaled the screen-shot using:

http://stackoverflow.com/questions/3029422/image-auto-resize-to-fit-div-container
#@+node:ekr.20120323124339.9721: *3* Investigated problem with desktop shortcut
@language rest

http://groups.google.com/group/leo-editor/browse_thread/thread/a03661d8c1eec0c6

I'm experimenting with the latest version (WinXP). This is really odd. If I
launch Leo using the desktop shortcut, I'm not able to open *any* valid
.leo file except  LeoSettings.leo. This is the shortcut:
C:\Python32\pythonw.exe "C:\Program Files\Leo-4.10-b1\launchLeo.py"

However, if I launch using my Windows batch file, I'm able to open all
files as expected. The code is:
C:\Python32\python "C:\Program Files\Leo-4.10-b1\launchLeo.py" %*

Anyone else experiencing this? Any possible explanation?

EKR: Are you doing this in a console window? 

No, that's why I used the batch file so I can launch w/ a console. When I
do, it works fine. 

I just installed on another box w/ Python 2.6.2 That works fine. The other
box has Python 3.2, not sure why that matters, but it might be a clue.
#@+node:ekr.20120327163022.9742: *4* EKR response
@language rest

There are two differences between the two ways of launching.

1. pythonw.exe vs python.exe

2. The former has no "%*" argument.  It's possible that you have no
workbook.leo file in your home directory, which might cause a failure,
iirc.

I suggest first changing pythonw to python.  This will open a console,
but probably too briefly to read.  To fix this, add a -i argument,
which will drop python into interactive mode, which has the side
effect of leaving the console open.  This should tell you why exactly
nothing happens.

I suspect that adding the "%*" argument will fix the problem,
regardless of whether you use pythonw or python.

If not, please feel free to ask more questions.
#@-all

# Put this @language after the @all as a kind of permanent unit test.

#@@language python # Override the default .txt coloring.

#@@pagewidth 60
#@-leo
